Skip to content

Commit fff41a2

Browse files
committed
install package improvements, new actions, code tweaks
1 parent d3b4f89 commit fff41a2

10 files changed

+135
-68
lines changed

.vscode/launch.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
{
2-
"version": "0.1.0",
2+
"version": "0.2.0",
33
"configurations": [
44
{
55
"name": "Run with extension",
66
"type": "extensionHost",
77
"request": "launch",
88
"runtimeExecutable": "${execPath}",
9+
"preLaunchTask": "compile",
910
"args": [
1011
"${workspaceFolder}",
1112
"--extensionDevelopmentPath=${workspaceFolder}"

.vscode/tasks.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "compile",
6+
"type": "npm",
7+
"script": "compile",
8+
"group": "build"
9+
}
10+
]
11+
}

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A VS Code extension allowing to browse through React Native Directory and perfor
1010
## 📦 Installation
1111

1212
> [!tip]
13-
> The extension is currently in the development phase, and only manual build and installation is supported at this time.
13+
> The extension is currently in the development phase, only manual build and installation is supported at this time.
1414
1515
1. Make sure you have [Bun](https://bun.sh/docs/installation) installed.
1616
1. Checkout the repository locally.
@@ -31,7 +31,7 @@ A VS Code extension allowing to browse through React Native Directory and perfor
3131
1. Run the following commands to install dependencies and compile source:
3232

3333
```sh
34-
bun install && bun compile
34+
bun install
3535
```
3636
1. In VS Code:
3737
* Open folder containing the extension repository.

bun.lock

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"lockfileVersion": 1,
33
"workspaces": {
44
"": {
5-
"name": "vs-react-native-directory",
5+
"name": "vscode-react-native-directory",
66
"dependencies": {
77
"axios": "^1.7.9",
88
"preferred-pm": "^4.1.1",
@@ -18,6 +18,7 @@
1818
"globals": "^15.15.0",
1919
"jiti": "^2.4.2",
2020
"prettier": "^3.5.1",
21+
"rimraf": "6.0.1",
2122
"typescript": "^5.7.3",
2223
"typescript-eslint": "^8.24.0",
2324
},
@@ -532,6 +533,8 @@
532533

533534
"reusify": ["[email protected]", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
534535

536+
"rimraf": ["[email protected]", "", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="],
537+
535538
"run-parallel": ["[email protected]", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
536539

537540
"safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],

eslint.config.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import eslint from '@eslint/js';
1+
import js from '@eslint/js';
22
import type { Linter } from 'eslint';
3-
import pluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
3+
import prettierRecommended from 'eslint-plugin-prettier/recommended';
44
import globals from 'globals';
5-
import pluginTS from 'typescript-eslint';
5+
import ts from 'typescript-eslint';
66

77
const config: Linter.Config[] = [
88
{
99
ignores: ['**/build', '**/node_modules']
1010
},
11-
eslint.configs.recommended,
12-
...(pluginTS.configs.recommended as Linter.Config[]),
13-
pluginPrettierRecommended,
11+
js.configs.recommended,
12+
...(ts.configs.recommended as Linter.Config[]),
13+
prettierRecommended,
1414
{
1515
languageOptions: {
1616
globals: globals.node

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-react-native-directory",
33
"displayName": "React Native Directory",
44
"description": "Find and install right packages for all of your React Native apps right from VS Code.",
5-
"publisher": "react-native-community",
5+
"publisher": "react-native-directory",
66
"categories": ["Other"],
77
"keywords": ["react", "react-native", "directory", "search"],
88
"license": "MIT",
@@ -27,7 +27,7 @@
2727
"vscode": "^1.97.0"
2828
},
2929
"scripts": {
30-
"compile": "tsc -p .",
30+
"compile": "rimraf build && tsc -p .",
3131
"lint": "eslint .",
3232
"package": "vsce package"
3333
},
@@ -46,6 +46,7 @@
4646
"globals": "^15.15.0",
4747
"jiti": "^2.4.2",
4848
"prettier": "^3.5.1",
49+
"rimraf": "6.0.1",
4950
"typescript": "^5.7.3",
5051
"typescript-eslint": "^8.24.0"
5152
}

src/extension.ts

+72-46
Original file line numberDiff line numberDiff line change
@@ -3,80 +3,92 @@ import * as vscode from 'vscode';
33
import { QuickPickItemKind } from 'vscode';
44
import preferredPM from 'preferred-pm';
55
import { DirectoryEntry } from './types';
6-
import { ENTRY_OPTION, fetchData } from './utils';
6+
import { ENTRY_OPTION, fetchData, getCommandToRun, STRINGS } from './utils';
7+
8+
export async function activate(context: vscode.ExtensionContext) {
9+
const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath ?? vscode.workspace.rootPath;
10+
const manager = vscode.workspace.getConfiguration('npm').get<string>('packageManager', 'npm');
11+
12+
const shouldCheckPreferred = workspacePath && (!manager || manager === 'auto');
13+
const preferredManager = shouldCheckPreferred ? ((await preferredPM(workspacePath))?.name ?? 'npm') : manager;
714

8-
export function activate(context: vscode.ExtensionContext) {
915
const disposable = vscode.commands.registerCommand('extension.showQuickPick', async () => {
1016
const packagesPick = vscode.window.createQuickPick<DirectoryEntry>();
1117

12-
packagesPick.placeholder = 'Loading directory data...';
18+
packagesPick.placeholder = STRINGS.PLACEHOLDER_BUSY;
1319
packagesPick.title = 'Search in React Native Directory';
1420
packagesPick.matchOnDescription = false;
1521
packagesPick.matchOnDetail = false;
16-
1722
packagesPick.busy = true;
18-
packagesPick.show();
1923

24+
packagesPick.show();
2025
packagesPick.items = await fetchData();
26+
2127
packagesPick.busy = false;
22-
packagesPick.placeholder = 'Search for a package';
28+
packagesPick.placeholder = STRINGS.PLACEHOLDER;
2329

2430
packagesPick.onDidChangeValue(async (value) => {
2531
packagesPick.busy = true;
2632
packagesPick.title = 'Search in React Native Directory';
2733
packagesPick.items = await fetchData(value);
28-
2934
packagesPick.busy = false;
3035
});
3136

3237
packagesPick.onDidAccept(() => {
3338
const selectedEntry = packagesPick.selectedItems[0];
3439

35-
const optionPick = vscode.window.createQuickPick();
36-
optionPick.title = `"${selectedEntry.label}" options`;
37-
optionPick.items = [
38-
{ label: ENTRY_OPTION.INSTALL, alwaysShow: true },
39-
{ label: `Open URLs`, kind: QuickPickItemKind.Separator },
40-
{ label: ENTRY_OPTION.VISIT_REPO, alwaysShow: true },
41-
{ label: ENTRY_OPTION.VISIT_NPM, alwaysShow: true },
42-
{ label: 'Copy data', kind: QuickPickItemKind.Separator },
43-
{ label: ENTRY_OPTION.COPY_NAME, alwaysShow: true },
44-
{ label: ENTRY_OPTION.COPY_REPO_URL, alwaysShow: true },
45-
{ label: ENTRY_OPTION.COPY_NPM_URL, alwaysShow: true },
40+
const possibleActions = [
41+
workspacePath && {
42+
label: ENTRY_OPTION.INSTALL,
43+
description: `with ${preferredManager}${selectedEntry.dev ? ' as devDependency' : ''}`
44+
},
45+
{ label: `open URLs`, kind: QuickPickItemKind.Separator },
46+
selectedEntry.github.urls.homepage && {
47+
label: ENTRY_OPTION.VISIT_HOMEPAGE,
48+
description: selectedEntry.github.urls.homepage
49+
},
50+
{ label: ENTRY_OPTION.VISIT_REPO },
51+
{ label: ENTRY_OPTION.VISIT_NPM },
52+
{ label: ENTRY_OPTION.VIEW_BUNDLEPHOBIA },
53+
selectedEntry.github.license && {
54+
label: ENTRY_OPTION.VIEW_LICENSE,
55+
description: selectedEntry.github.license.name
56+
},
57+
{ label: 'copy data', kind: QuickPickItemKind.Separator },
58+
{ label: ENTRY_OPTION.COPY_NAME },
59+
{ label: ENTRY_OPTION.COPY_REPO_URL },
60+
{ label: ENTRY_OPTION.COPY_NPM_URL },
4661
{ label: '', kind: QuickPickItemKind.Separator },
47-
{ label: ENTRY_OPTION.GO_BACK, alwaysShow: true }
48-
];
62+
{ label: ENTRY_OPTION.GO_BACK }
63+
].filter((option) => !!option && typeof option === 'object');
64+
65+
const optionPick = vscode.window.createQuickPick();
66+
optionPick.title = `Actions for "${selectedEntry.label}"`;
67+
optionPick.placeholder = 'Select an action';
68+
optionPick.items = possibleActions;
4969
optionPick.show();
5070

5171
optionPick.onDidAccept(async () => {
52-
const actionOption = optionPick.selectedItems[0];
72+
const selectedAction = optionPick.selectedItems[0];
5373

54-
switch (actionOption.label) {
74+
switch (selectedAction.label) {
5575
case ENTRY_OPTION.INSTALL: {
56-
const workspacePath =
57-
vscode.workspace.workspaceFolders?.[0].uri.fsPath ?? vscode.env.appRoot ?? vscode.workspace.rootPath;
58-
59-
const manager = vscode.workspace.getConfiguration('npm').get('packageManager');
60-
61-
if (workspacePath) {
62-
const preferredManager =
63-
!manager || manager === 'auto' ? ((await preferredPM(workspacePath))?.name ?? 'npm') : manager;
64-
65-
exec(`${preferredManager} install ${selectedEntry.label}`, { cwd: workspacePath }, (error, stout) => {
66-
if (error) {
67-
vscode.window.showErrorMessage(
68-
`An error occurred while trying to install the package: ${error.message}`
69-
);
70-
return;
71-
}
72-
vscode.window.showInformationMessage(
73-
`\`${selectedEntry.npmPkg}\` package has been installed in current workspace using \`${preferredManager}\`: ${stout}`
76+
exec(getCommandToRun(selectedEntry, preferredManager), { cwd: workspacePath }, (error, stout) => {
77+
if (error) {
78+
vscode.window.showErrorMessage(
79+
`An error occurred while trying to install the \`${selectedEntry.npmPkg}\` package: ${error.message}`
7480
);
75-
optionPick.hide();
76-
});
77-
} else {
78-
vscode.window.showErrorMessage(`Cannot determine current workspace path`);
79-
}
81+
return;
82+
}
83+
vscode.window.showInformationMessage(
84+
`\`${selectedEntry.npmPkg}\` package has been installed${selectedEntry.dev ? ' as `devDependency`' : ''} in current workspace using \`${preferredManager}\`: ${stout}`
85+
);
86+
optionPick.hide();
87+
});
88+
break;
89+
}
90+
case ENTRY_OPTION.VISIT_HOMEPAGE: {
91+
vscode.env.openExternal(vscode.Uri.parse(selectedEntry.github.urls.homepage!));
8092
break;
8193
}
8294
case ENTRY_OPTION.VISIT_REPO: {
@@ -87,6 +99,14 @@ export function activate(context: vscode.ExtensionContext) {
8799
vscode.env.openExternal(vscode.Uri.parse(`https://www.npmjs.com/package/${selectedEntry.npmPkg}`));
88100
break;
89101
}
102+
case ENTRY_OPTION.VIEW_BUNDLEPHOBIA: {
103+
vscode.env.openExternal(vscode.Uri.parse(`https://bundlephobia.com/package/${selectedEntry.npmPkg}`));
104+
break;
105+
}
106+
case ENTRY_OPTION.VIEW_LICENSE: {
107+
vscode.env.openExternal(vscode.Uri.parse(selectedEntry.github.license.url));
108+
break;
109+
}
90110
case ENTRY_OPTION.COPY_NAME: {
91111
vscode.env.clipboard.writeText(selectedEntry.npmPkg);
92112
vscode.window.showInformationMessage('Package name copied to clipboard');
@@ -103,8 +123,14 @@ export function activate(context: vscode.ExtensionContext) {
103123
break;
104124
}
105125
case ENTRY_OPTION.GO_BACK: {
106-
packagesPick.items = await fetchData(packagesPick.value);
126+
packagesPick.placeholder = STRINGS.PLACEHOLDER_BUSY;
127+
packagesPick.busy = true;
128+
107129
packagesPick.show();
130+
packagesPick.items = await fetchData(packagesPick.value);
131+
132+
packagesPick.placeholder = STRINGS.PLACEHOLDER;
133+
packagesPick.busy = false;
108134
break;
109135
}
110136
}

src/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { type QuickPickItem } from 'vscode';
22

3-
export type DirectoryEntry = QuickPickItem & Library;
3+
export type DirectoryEntry = QuickPickItem & PackageData;
44

55
/**
66
* Mirror of https://github.com/react-native-community/directory/blob/main/types/index.ts#L41
77
*/
8-
export type Library = {
8+
export type PackageData = {
99
githubUrl: string;
1010
ios?: boolean;
1111
android?: boolean;

src/utils.ts

+31-7
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
import * as vscode from 'vscode';
22
import axios from 'axios';
33

4-
import { DirectoryEntry, Library } from './types';
4+
import { DirectoryEntry, PackageData } from './types';
5+
6+
const BASE_API_URL = 'https://reactnative.directory/api/libraries';
57

68
export const numberFormatter = new Intl.NumberFormat('en-EN', { notation: 'compact' });
79

810
export enum ENTRY_OPTION {
911
INSTALL = 'Install package in the current workspace',
12+
VISIT_HOMEPAGE = 'Visit homepage',
1013
VISIT_REPO = 'Visit GitHub repository',
1114
VISIT_NPM = 'Visit npm registry entry',
15+
VIEW_BUNDLEPHOBIA = 'View BundlePhobia analysis',
16+
VIEW_LICENSE = 'View license details',
1217
COPY_NAME = 'Copy package name',
1318
COPY_REPO_URL = 'Copy GitHub repository URL',
1419
COPY_NPM_URL = 'Copy npm registry URL',
1520
GO_BACK = '$(newline) Go back to search'
1621
}
1722

18-
function getDetailLabel(item: Library) {
23+
export enum STRINGS {
24+
PLACEHOLDER_BUSY = 'Loading directory data...',
25+
PLACEHOLDER = 'Search for a package'
26+
}
27+
28+
function getDetailLabel(item: PackageData) {
1929
const platforms = [
2030
item.android ? 'Android' : null,
2131
item.ios ? 'iOS' : null,
@@ -40,16 +50,30 @@ function getDetailLabel(item: Library) {
4050
.join(' ');
4151
}
4252

53+
export function getCommandToRun({ dev, npmPkg }: DirectoryEntry, preferredManager: string): string {
54+
switch (preferredManager) {
55+
case 'bun':
56+
case 'pnpm':
57+
case 'yarn':
58+
return `${preferredManager} add${dev ? ' -D' : ''} ${npmPkg}`;
59+
default:
60+
return `${preferredManager} install${dev ? ' -D' : ''} ${npmPkg}`;
61+
}
62+
}
63+
4364
export async function fetchData(query?: string): Promise<DirectoryEntry[]> {
4465
try {
45-
const url = query
46-
? `https://reactnative.directory/api/libraries?search=${encodeURIComponent(query)}&order=downloads`
47-
: `https://reactnative.directory/api/libraries`;
66+
const apiUrl = new URL(BASE_API_URL);
67+
68+
if (query) {
69+
apiUrl.searchParams.append('search', encodeURIComponent(query));
70+
apiUrl.searchParams.append('order', 'downloads');
71+
}
4872

49-
const { data } = await axios.get(url);
73+
const { data } = await axios.get(apiUrl.href);
5074

5175
if ('libraries' in data && Array.isArray(data.libraries)) {
52-
return data.libraries.map((item: Library) => ({
76+
return data.libraries.map((item: PackageData) => ({
5377
label: item.npmPkg,
5478
description: item.github.description,
5579
detail: getDetailLabel(item),

tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"lib": ["es2021"],
88
"esModuleInterop": true,
99
"sourceMap": false,
10-
"strict": true
10+
"strict": true,
11+
"exactOptionalPropertyTypes": true
1112
},
1213
"exclude": [
1314
"eslint.config.ts",

0 commit comments

Comments
 (0)