@vsceasy/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +474 -0
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +9044 -0
- package/dist/cli.d.ts +3 -0
- package/dist/commands/command/add.d.ts +3 -0
- package/dist/commands/components/add.d.ts +3 -0
- package/dist/commands/create.d.ts +3 -0
- package/dist/commands/crud/add.d.ts +3 -0
- package/dist/commands/db/init.d.ts +3 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/groups.d.ts +16 -0
- package/dist/commands/helper/add.d.ts +3 -0
- package/dist/commands/job/add.d.ts +3 -0
- package/dist/commands/menu/add.d.ts +3 -0
- package/dist/commands/menu/edit.d.ts +3 -0
- package/dist/commands/model/add.d.ts +3 -0
- package/dist/commands/panel/add.d.ts +3 -0
- package/dist/commands/publish/init.d.ts +3 -0
- package/dist/commands/rpc/add.d.ts +3 -0
- package/dist/commands/statusBar/add.d.ts +3 -0
- package/dist/commands/subpanel/add.d.ts +3 -0
- package/dist/commands/test/setup.d.ts +3 -0
- package/dist/commands/treeView/add.d.ts +3 -0
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/wizard.d.ts +3 -0
- package/dist/data/codicons.d.ts +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3169 -0
- package/dist/lib/command/add.d.ts +31 -0
- package/dist/lib/components/add.d.ts +20 -0
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/crud/add.d.ts +19 -0
- package/dist/lib/crud/crudConfig.d.ts +37 -0
- package/dist/lib/crud/parseModel.d.ts +33 -0
- package/dist/lib/db/init.d.ts +16 -0
- package/dist/lib/db/wire.d.ts +10 -0
- package/dist/lib/doctor.d.ts +30 -0
- package/dist/lib/findProject.d.ts +10 -0
- package/dist/lib/helper/add.d.ts +14 -0
- package/dist/lib/iconPicker.d.ts +7 -0
- package/dist/lib/index.d.ts +46 -0
- package/dist/lib/interactive.d.ts +30 -0
- package/dist/lib/job/add.d.ts +24 -0
- package/dist/lib/menu/add.d.ts +13 -0
- package/dist/lib/menu/edit.d.ts +39 -0
- package/dist/lib/menuTree.d.ts +33 -0
- package/dist/lib/model/add.d.ts +27 -0
- package/dist/lib/model/parseFields.d.ts +14 -0
- package/dist/lib/panel/add.d.ts +29 -0
- package/dist/lib/publish/init.d.ts +12 -0
- package/dist/lib/rpc/add.d.ts +22 -0
- package/dist/lib/scaffold.d.ts +13 -0
- package/dist/lib/statusBar/add.d.ts +33 -0
- package/dist/lib/subpanel/add.d.ts +20 -0
- package/dist/lib/testSetup/index.d.ts +10 -0
- package/dist/lib/treeView/add.d.ts +13 -0
- package/dist/lib/upgrade.d.ts +22 -0
- package/dist/lib/validate.d.ts +14 -0
- package/dist/lib/wizard/run.d.ts +13 -0
- package/package.json +67 -0
- package/templates/_generators/command/command.ts.tpl +8 -0
- package/templates/_generators/components/Button.tsx.tpl +12 -0
- package/templates/_generators/components/Card.tsx.tpl +22 -0
- package/templates/_generators/components/Field.tsx.tpl +20 -0
- package/templates/_generators/components/Input.tsx.tpl +10 -0
- package/templates/_generators/components/List.tsx.tpl +29 -0
- package/templates/_generators/components/components.css.tpl +66 -0
- package/templates/_generators/components/index.ts.tpl +10 -0
- package/templates/_generators/crud/formApp.tsx.tpl +83 -0
- package/templates/_generators/crud/formNav.ts.tpl +19 -0
- package/templates/_generators/crud/formPanel.ts.tpl +32 -0
- package/templates/_generators/crud/listApp.tsx.tpl +84 -0
- package/templates/_generators/crud/listPanel.ts.tpl +30 -0
- package/templates/_generators/crud/main.tsx.tpl +6 -0
- package/templates/_generators/crud/service.ts.tpl +27 -0
- package/templates/_generators/helper/cache.ts.tpl +117 -0
- package/templates/_generators/helper/config.ts.tpl +36 -0
- package/templates/_generators/helper/db.ts.tpl +322 -0
- package/templates/_generators/helper/notifications.ts.tpl +45 -0
- package/templates/_generators/helper/secrets.ts.tpl +36 -0
- package/templates/_generators/helper/state.ts.tpl +44 -0
- package/templates/_generators/job/job.ts.tpl +10 -0
- package/templates/_generators/menu/menu.ts.tpl +21 -0
- package/templates/_generators/model/model.ts.tpl +17 -0
- package/templates/_generators/panel/App.tsx.tpl +10 -0
- package/templates/_generators/panel/main.tsx.tpl +6 -0
- package/templates/_generators/panel/panel.ts.tpl +5 -0
- package/templates/_generators/panel/templates/dashboard/App.tsx.tpl +41 -0
- package/templates/_generators/panel/templates/form/App.tsx.tpl +44 -0
- package/templates/_generators/panel/templates/list/App.tsx.tpl +40 -0
- package/templates/_generators/publish/CHANGELOG.md.tpl +8 -0
- package/templates/_generators/publish/README.md.tpl +23 -0
- package/templates/_generators/statusBar/statusBar.ts.tpl +7 -0
- package/templates/_generators/subpanel/App.tsx.tpl +10 -0
- package/templates/_generators/subpanel/main.tsx.tpl +6 -0
- package/templates/_generators/subpanel/subpanel.ts.tpl +6 -0
- package/templates/_generators/test/_helpers.ts.tpl +120 -0
- package/templates/_generators/test/sample.test.ts.tpl +38 -0
- package/templates/_generators/test/vitest.config.ts.tpl +23 -0
- package/templates/_generators/test/vscode.stub.ts.tpl +109 -0
- package/templates/_generators/treeView/treeView.ts.tpl +16 -0
- package/templates/react/.vscode/launch.json +34 -0
- package/templates/react/.vscode/tasks.json +32 -0
- package/templates/react/.vscodeignore +8 -0
- package/templates/react/README.md +50 -0
- package/templates/react/package.json +54 -0
- package/templates/react/scripts/gen.ts +395 -0
- package/templates/react/src/commands/hello.ts +6 -0
- package/templates/react/src/extension/extension.ts +5 -0
- package/templates/react/src/panels/dashboard.ts +21 -0
- package/templates/react/src/shared/api.ts +7 -0
- package/templates/react/src/shared/vsceasy/bootstrap.ts +657 -0
- package/templates/react/src/shared/vsceasy/client.ts +8 -0
- package/templates/react/src/shared/vsceasy/codiconNames.ts +196 -0
- package/templates/react/src/shared/vsceasy/define.ts +269 -0
- package/templates/react/src/shared/vsceasy/index.ts +13 -0
- package/templates/react/src/shared/vsceasy/rpc.ts +214 -0
- package/templates/react/src/webview/panels/dashboard/App.tsx +31 -0
- package/templates/react/src/webview/panels/dashboard/main.tsx +6 -0
- package/templates/react/src/webview/styles.css +33 -0
- package/templates/react/tsconfig.json +17 -0
- package/templates/react/vite.config.ts +42 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"displayName": "{{displayName}}",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"publisher": "{{publisher}}",
|
|
7
|
+
"engines": {
|
|
8
|
+
"vscode": "^1.85.0"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/extension.js",
|
|
11
|
+
"contributes": {
|
|
12
|
+
"commands": [
|
|
13
|
+
{
|
|
14
|
+
"command": "{{commandPrefix}}.hello",
|
|
15
|
+
"title": "{{displayName}}: Hello"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"command": "{{commandPrefix}}.openDashboard",
|
|
19
|
+
"title": "{{displayName}}: Open Dashboard"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"activationEvents": [],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"gen:scan": "bun scripts/gen.ts",
|
|
26
|
+
"gen": "bun run gen:scan && bun run build:ext",
|
|
27
|
+
"dev": "bun run gen:scan && concurrently -k -n ext,ui -c blue,magenta \"bun run dev:ext\" \"bun run dev:ui\"",
|
|
28
|
+
"dev:ext": "esbuild src/extension/extension.ts --bundle --platform=node --target=node18 --external:vscode --outfile=dist/extension.js --watch --sourcemap",
|
|
29
|
+
"dev:ui": "vite build --watch --mode development",
|
|
30
|
+
"launch": "bun run build && code --extensionDevelopmentPath=$PWD --new-window",
|
|
31
|
+
"build": "bun run gen:scan && bun run build:ext && bun run build:ui",
|
|
32
|
+
"build:ext": "esbuild src/extension/extension.ts --bundle --platform=node --target=node18 --external:vscode --outfile=dist/extension.js --sourcemap",
|
|
33
|
+
"build:ext:prod": "esbuild src/extension/extension.ts --bundle --platform=node --target=node18 --external:vscode --outfile=dist/extension.js --minify",
|
|
34
|
+
"build:ui": "vite build",
|
|
35
|
+
"build:prod": "bun run gen:scan && bun run build:ext:prod && bun run build:ui",
|
|
36
|
+
"package": "bun run build:prod && vsce package --no-dependencies"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"@types/react": "^18.2.0",
|
|
41
|
+
"@types/react-dom": "^18.2.0",
|
|
42
|
+
"@types/vscode": "^1.85.0",
|
|
43
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
44
|
+
"@vscode/vsce": "^2.24.0",
|
|
45
|
+
"concurrently": "^8.2.0",
|
|
46
|
+
"esbuild": "^0.20.0",
|
|
47
|
+
"typescript": "^5.3.0",
|
|
48
|
+
"vite": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"react": "^18.2.0",
|
|
52
|
+
"react-dom": "^18.2.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Scans src/panels, src/commands, and src/menus; writes src/extension/_registry.ts
|
|
3
|
+
// and syncs package.json#contributes (commands, viewsContainers, views).
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
const ROOT = process.cwd();
|
|
9
|
+
const SRC = path.join(ROOT, 'src');
|
|
10
|
+
const PANELS_DIR = path.join(SRC, 'panels');
|
|
11
|
+
const COMMANDS_DIR = path.join(SRC, 'commands');
|
|
12
|
+
const MENUS_DIR = path.join(SRC, 'menus');
|
|
13
|
+
const STATUS_BARS_DIR = path.join(SRC, 'statusBars');
|
|
14
|
+
const SUBPANELS_DIR = path.join(SRC, 'subpanels');
|
|
15
|
+
const TREE_VIEWS_DIR = path.join(SRC, 'treeViews');
|
|
16
|
+
const JOBS_DIR = path.join(SRC, 'jobs');
|
|
17
|
+
const OUT = path.join(SRC, 'extension', '_registry.ts');
|
|
18
|
+
const PKG_PATH = path.join(ROOT, 'package.json');
|
|
19
|
+
|
|
20
|
+
interface Discovered {
|
|
21
|
+
id: string;
|
|
22
|
+
importPath: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function scan(dir: string, registryDir: string): Discovered[] {
|
|
26
|
+
if (!fs.existsSync(dir)) return [];
|
|
27
|
+
return fs
|
|
28
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
29
|
+
.filter((e) => e.isFile() && /\.(ts|tsx)$/.test(e.name) && !e.name.startsWith('_'))
|
|
30
|
+
.map((e) => {
|
|
31
|
+
const id = e.name.replace(/\.(ts|tsx)$/, '');
|
|
32
|
+
const abs = path.join(dir, e.name);
|
|
33
|
+
const rel = path.relative(registryDir, abs).replace(/\\/g, '/').replace(/\.(ts|tsx)$/, '');
|
|
34
|
+
const importPath = rel.startsWith('.') ? rel : `./${rel}`;
|
|
35
|
+
return { id, importPath };
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeRegistry(
|
|
40
|
+
panels: Discovered[],
|
|
41
|
+
commands: Discovered[],
|
|
42
|
+
menus: Discovered[],
|
|
43
|
+
statusBars: Discovered[],
|
|
44
|
+
subpanels: Discovered[],
|
|
45
|
+
treeViews: Discovered[],
|
|
46
|
+
jobs: Discovered[],
|
|
47
|
+
prefix: string,
|
|
48
|
+
) {
|
|
49
|
+
const lines: string[] = [
|
|
50
|
+
'// AUTO-GENERATED — do not edit. Run `bun run gen`.',
|
|
51
|
+
`import type { Registry } from '../shared/vsceasy';`,
|
|
52
|
+
...panels.map((p, i) => `import panel${i} from '${p.importPath}';`),
|
|
53
|
+
...commands.map((c, i) => `import command${i} from '${c.importPath}';`),
|
|
54
|
+
...menus.map((m, i) => `import menu${i} from '${m.importPath}';`),
|
|
55
|
+
...statusBars.map((s, i) => `import statusBar${i} from '${s.importPath}';`),
|
|
56
|
+
...subpanels.map((w, i) => `import subpanel${i} from '${w.importPath}';`),
|
|
57
|
+
...treeViews.map((t, i) => `import treeView${i} from '${t.importPath}';`),
|
|
58
|
+
...jobs.map((j, i) => `import job${i} from '${j.importPath}';`),
|
|
59
|
+
'',
|
|
60
|
+
'export const registry: Registry = {',
|
|
61
|
+
` prefix: ${JSON.stringify(prefix)},`,
|
|
62
|
+
' panels: {',
|
|
63
|
+
...panels.map((p, i) => ` ${JSON.stringify(p.id)}: panel${i},`),
|
|
64
|
+
' },',
|
|
65
|
+
' commands: {',
|
|
66
|
+
...commands.map((c, i) => ` ${JSON.stringify(c.id)}: command${i},`),
|
|
67
|
+
' },',
|
|
68
|
+
' menus: {',
|
|
69
|
+
...menus.map((m, i) => ` ${JSON.stringify(m.id)}: menu${i},`),
|
|
70
|
+
' },',
|
|
71
|
+
' statusBars: {',
|
|
72
|
+
...statusBars.map((s, i) => ` ${JSON.stringify(s.id)}: statusBar${i},`),
|
|
73
|
+
' },',
|
|
74
|
+
' subpanels: {',
|
|
75
|
+
...subpanels.map((w, i) => ` ${JSON.stringify(w.id)}: subpanel${i},`),
|
|
76
|
+
' },',
|
|
77
|
+
' treeViews: {',
|
|
78
|
+
...treeViews.map((t, i) => ` ${JSON.stringify(t.id)}: treeView${i},`),
|
|
79
|
+
' },',
|
|
80
|
+
' jobs: {',
|
|
81
|
+
...jobs.map((j, i) => ` ${JSON.stringify(j.id)}: job${i},`),
|
|
82
|
+
' },',
|
|
83
|
+
'};',
|
|
84
|
+
'',
|
|
85
|
+
];
|
|
86
|
+
fs.mkdirSync(path.dirname(OUT), { recursive: true });
|
|
87
|
+
fs.writeFileSync(OUT, lines.join('\n'));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function syncPackageJson(
|
|
91
|
+
panels: Discovered[],
|
|
92
|
+
commands: Discovered[],
|
|
93
|
+
menus: Discovered[],
|
|
94
|
+
subpanels: Discovered[],
|
|
95
|
+
treeViews: Discovered[],
|
|
96
|
+
prefix: string,
|
|
97
|
+
displayName: string,
|
|
98
|
+
) {
|
|
99
|
+
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
100
|
+
const contributes = (pkg.contributes ??= {});
|
|
101
|
+
const cmds: Array<{ command: string; title: string; category?: string; enablement?: string }> = [];
|
|
102
|
+
const keybindings: Array<{ command: string; key: string; mac?: string; when?: string }> = [];
|
|
103
|
+
const palette: Array<{ command: string; when?: string }> = [];
|
|
104
|
+
|
|
105
|
+
for (const c of commands) {
|
|
106
|
+
const def = loadDef(path.join(COMMANDS_DIR, c.id + '.ts')) ?? loadDef(path.join(COMMANDS_DIR, c.id + '.tsx'));
|
|
107
|
+
const fullId = `${prefix}.${def?.id ?? c.id}`;
|
|
108
|
+
cmds.push({
|
|
109
|
+
command: fullId,
|
|
110
|
+
title: def?.title ?? c.id,
|
|
111
|
+
category: def?.category ?? displayName,
|
|
112
|
+
...(def?.when ? { enablement: def.when } : {}),
|
|
113
|
+
});
|
|
114
|
+
if (def?.when) palette.push({ command: fullId, when: def.when });
|
|
115
|
+
if (def?.keybindings) {
|
|
116
|
+
for (const kb of def.keybindings) {
|
|
117
|
+
keybindings.push({
|
|
118
|
+
command: fullId,
|
|
119
|
+
key: kb.key,
|
|
120
|
+
...(kb.mac ? { mac: kb.mac } : {}),
|
|
121
|
+
...(kb.when ? { when: kb.when } : {}),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const p of panels) {
|
|
127
|
+
const def = loadDef(path.join(PANELS_DIR, p.id + '.ts')) ?? loadDef(path.join(PANELS_DIR, p.id + '.tsx'));
|
|
128
|
+
if (def?.command === false) continue;
|
|
129
|
+
const opts = typeof def?.command === 'object' ? def!.command : {};
|
|
130
|
+
cmds.push({
|
|
131
|
+
command: `${prefix}.open${capitalize(def?.id ?? p.id)}`,
|
|
132
|
+
title: (opts as any).title ?? `Open ${def?.title ?? p.id}`,
|
|
133
|
+
category: (opts as any).category ?? displayName,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
contributes.commands = cmds;
|
|
138
|
+
if (keybindings.length) {
|
|
139
|
+
contributes.keybindings = keybindings;
|
|
140
|
+
} else {
|
|
141
|
+
delete contributes.keybindings;
|
|
142
|
+
}
|
|
143
|
+
if (palette.length) {
|
|
144
|
+
contributes.menus ??= {};
|
|
145
|
+
contributes.menus.commandPalette = palette;
|
|
146
|
+
} else if (contributes.menus) {
|
|
147
|
+
delete contributes.menus.commandPalette;
|
|
148
|
+
if (Object.keys(contributes.menus).length === 0) delete contributes.menus;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Menus → viewsContainers.activitybar + views.<containerId>
|
|
152
|
+
const containers: Array<{ id: string; title: string; icon: string }> = [];
|
|
153
|
+
const views: Record<string, Array<{ id: string; name: string; type?: 'webview' }>> = {};
|
|
154
|
+
|
|
155
|
+
// Index subpanels by menu they belong to
|
|
156
|
+
const wvByMenu: Record<string, Array<{ id: string; name: string }>> = {};
|
|
157
|
+
for (const w of subpanels) {
|
|
158
|
+
const def = loadSubpanelDef(path.join(SUBPANELS_DIR, w.id + '.ts'))
|
|
159
|
+
?? loadSubpanelDef(path.join(SUBPANELS_DIR, w.id + '.tsx'));
|
|
160
|
+
if (!def?.menu) continue;
|
|
161
|
+
const viewId = `${prefix}-${def.menu}-${def.id ?? w.id}`;
|
|
162
|
+
const name = def.title ?? w.id;
|
|
163
|
+
(wvByMenu[def.menu] ??= []).push({ id: viewId, name });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Index tree views by their menu container
|
|
167
|
+
const tvByMenu: Record<string, Array<{ id: string; name: string }>> = {};
|
|
168
|
+
for (const t of treeViews) {
|
|
169
|
+
const def = loadSubpanelDef(path.join(TREE_VIEWS_DIR, t.id + '.ts'))
|
|
170
|
+
?? loadSubpanelDef(path.join(TREE_VIEWS_DIR, t.id + '.tsx'));
|
|
171
|
+
if (!def?.menu) continue;
|
|
172
|
+
const viewId = `${prefix}-${def.menu}-${def.id ?? t.id}`;
|
|
173
|
+
const name = def.title ?? t.id;
|
|
174
|
+
(tvByMenu[def.menu] ??= []).push({ id: viewId, name });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const m of menus) {
|
|
178
|
+
const def = loadMenuDef(path.join(MENUS_DIR, m.id + '.ts')) ?? loadMenuDef(path.join(MENUS_DIR, m.id + '.tsx'));
|
|
179
|
+
// VS Code requires viewsContainer / view ids to match /^[A-Za-z0-9_-]+$/ — no dots.
|
|
180
|
+
const menuId = def?.id ?? m.id;
|
|
181
|
+
const containerId = `${prefix}-${menuId}`;
|
|
182
|
+
const title = def?.title ?? m.id;
|
|
183
|
+
const icon = resolveIconForPkg(def?.icon);
|
|
184
|
+
containers.push({ id: containerId, title, icon });
|
|
185
|
+
const containerViews: Array<{ id: string; name: string; type?: 'webview' }> = [
|
|
186
|
+
{ id: containerId, name: title }, // primary tree view
|
|
187
|
+
];
|
|
188
|
+
for (const v of wvByMenu[menuId] ?? []) {
|
|
189
|
+
containerViews.push({ id: v.id, name: v.name, type: 'webview' });
|
|
190
|
+
}
|
|
191
|
+
for (const t of tvByMenu[menuId] ?? []) {
|
|
192
|
+
containerViews.push({ id: t.id, name: t.name });
|
|
193
|
+
}
|
|
194
|
+
views[containerId] = containerViews;
|
|
195
|
+
}
|
|
196
|
+
if (containers.length) {
|
|
197
|
+
(contributes.viewsContainers ??= {}).activitybar = containers;
|
|
198
|
+
contributes.views = views;
|
|
199
|
+
} else {
|
|
200
|
+
delete contributes.viewsContainers?.activitybar;
|
|
201
|
+
if (contributes.viewsContainers && Object.keys(contributes.viewsContainers).length === 0) {
|
|
202
|
+
delete contributes.viewsContainers;
|
|
203
|
+
}
|
|
204
|
+
delete contributes.views;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fs.writeFileSync(PKG_PATH, JSON.stringify(pkg, null, 2) + '\n');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function loadDef(file: string): {
|
|
211
|
+
id?: string;
|
|
212
|
+
title?: string;
|
|
213
|
+
category?: string;
|
|
214
|
+
command?: any;
|
|
215
|
+
when?: string;
|
|
216
|
+
keybindings?: Array<{ key: string; mac?: string; when?: string }>;
|
|
217
|
+
} | null {
|
|
218
|
+
if (!fs.existsSync(file)) return null;
|
|
219
|
+
const src = fs.readFileSync(file, 'utf8');
|
|
220
|
+
const grab = (key: string) => {
|
|
221
|
+
const m = new RegExp(`\\b${key}\\s*:\\s*(['"\`])((?:\\\\.|(?!\\1).)*)\\1`).exec(src);
|
|
222
|
+
return m?.[2];
|
|
223
|
+
};
|
|
224
|
+
const command =
|
|
225
|
+
/\bcommand\s*:\s*false\b/.test(src) ? false :
|
|
226
|
+
/\bcommand\s*:\s*true\b/.test(src) ? true :
|
|
227
|
+
undefined;
|
|
228
|
+
return {
|
|
229
|
+
id: grab('id'),
|
|
230
|
+
title: grab('title'),
|
|
231
|
+
category: grab('category'),
|
|
232
|
+
when: grab('when'),
|
|
233
|
+
command,
|
|
234
|
+
keybindings: parseKeybindings(src),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Extract keybinding(s) declared on a defineCommand call. Supports string, object, or array shorthand. */
|
|
239
|
+
function parseKeybindings(src: string): Array<{ key: string; mac?: string; when?: string }> {
|
|
240
|
+
// 1) Plain string: keybinding: 'ctrl+shift+h'
|
|
241
|
+
const stringMatch = /\bkeybinding\s*:\s*(['"`])([^'"`]+)\1\s*,?/.exec(src);
|
|
242
|
+
if (stringMatch) return [{ key: stringMatch[2] }];
|
|
243
|
+
|
|
244
|
+
// 2) Single object: keybinding: { key: '...', mac?: '...', when?: '...' }
|
|
245
|
+
const objMatch = /\bkeybinding\s*:\s*\{([^}]+)\}/.exec(src);
|
|
246
|
+
if (objMatch) {
|
|
247
|
+
const obj = parseKbObject(objMatch[1]);
|
|
248
|
+
return obj ? [obj] : [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 3) Array shorthand: keybinding: [ 'ctrl+a', { key: 'ctrl+b', mac: 'cmd+b' } ]
|
|
252
|
+
const arrMatch = /\bkeybinding\s*:\s*\[([\s\S]*?)\]/.exec(src);
|
|
253
|
+
if (arrMatch) {
|
|
254
|
+
const inner = arrMatch[1];
|
|
255
|
+
const out: Array<{ key: string; mac?: string; when?: string }> = [];
|
|
256
|
+
const strRe = /(['"`])([^'"`]+)\1/g;
|
|
257
|
+
const objRe = /\{([^}]+)\}/g;
|
|
258
|
+
let sm: RegExpExecArray | null;
|
|
259
|
+
while ((sm = strRe.exec(inner))) out.push({ key: sm[2] });
|
|
260
|
+
let om: RegExpExecArray | null;
|
|
261
|
+
while ((om = objRe.exec(inner))) {
|
|
262
|
+
const o = parseKbObject(om[1]);
|
|
263
|
+
if (o) out.push(o);
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function parseKbObject(body: string): { key: string; mac?: string; when?: string } | null {
|
|
271
|
+
const grab = (key: string) => {
|
|
272
|
+
const m = new RegExp(`\\b${key}\\s*:\\s*(['"\`])((?:\\\\.|(?!\\1).)*)\\1`).exec(body);
|
|
273
|
+
return m?.[2];
|
|
274
|
+
};
|
|
275
|
+
const key = grab('key');
|
|
276
|
+
if (!key) return null;
|
|
277
|
+
const mac = grab('mac');
|
|
278
|
+
const when = grab('when');
|
|
279
|
+
return { key, ...(mac ? { mac } : {}), ...(when ? { when } : {}) };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
interface MenuLoaded {
|
|
283
|
+
id?: string;
|
|
284
|
+
title?: string;
|
|
285
|
+
icon?: string | { path?: string; light?: string; dark?: string };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function loadMenuDef(file: string): MenuLoaded | null {
|
|
289
|
+
if (!fs.existsSync(file)) return null;
|
|
290
|
+
const src = fs.readFileSync(file, 'utf8');
|
|
291
|
+
const grab = (key: string) => {
|
|
292
|
+
const m = new RegExp(`\\b${key}\\s*:\\s*(['"\`])((?:\\\\.|(?!\\1).)*)\\1`).exec(src);
|
|
293
|
+
return m?.[2];
|
|
294
|
+
};
|
|
295
|
+
let icon: MenuLoaded['icon'];
|
|
296
|
+
const iconString = grab('icon');
|
|
297
|
+
if (iconString !== undefined) {
|
|
298
|
+
icon = iconString;
|
|
299
|
+
} else {
|
|
300
|
+
// Try object form: icon: { path: '...' } OR { light: '...', dark: '...' }
|
|
301
|
+
const objMatch = /\bicon\s*:\s*\{([^}]+)\}/.exec(src);
|
|
302
|
+
if (objMatch) {
|
|
303
|
+
const body = objMatch[1];
|
|
304
|
+
const p = /\bpath\s*:\s*(['"\`])((?:\\.|(?!\1).)*)\1/.exec(body);
|
|
305
|
+
const l = /\blight\s*:\s*(['"\`])((?:\\.|(?!\1).)*)\1/.exec(body);
|
|
306
|
+
const d = /\bdark\s*:\s*(['"\`])((?:\\.|(?!\1).)*)\1/.exec(body);
|
|
307
|
+
if (p) icon = { path: p[2] };
|
|
308
|
+
else if (l && d) icon = { light: l[2], dark: d[2] };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { id: grab('id'), title: grab('title'), icon };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
interface SubpanelLoaded {
|
|
315
|
+
id?: string;
|
|
316
|
+
title?: string;
|
|
317
|
+
menu?: string;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function loadSubpanelDef(file: string): SubpanelLoaded | null {
|
|
321
|
+
if (!fs.existsSync(file)) return null;
|
|
322
|
+
const src = fs.readFileSync(file, 'utf8');
|
|
323
|
+
const grab = (key: string) => {
|
|
324
|
+
const m = new RegExp(`\\b${key}\\s*:\\s*(['"\`])((?:\\\\.|(?!\\1).)*)\\1`).exec(src);
|
|
325
|
+
return m?.[2];
|
|
326
|
+
};
|
|
327
|
+
return { id: grab('id'), title: grab('title'), menu: grab('menu') };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function resolveIconForPkg(icon: MenuLoaded['icon']): string {
|
|
331
|
+
// VS Code's viewsContainers.activitybar.icon must be a string (path to SVG or codicon ref via "$(name)").
|
|
332
|
+
if (!icon) return '$(symbol-misc)';
|
|
333
|
+
if (typeof icon === 'string') return `$(${icon})`;
|
|
334
|
+
if ('path' in icon && icon.path) return icon.path;
|
|
335
|
+
if ('light' in icon && icon.light) return icon.light;
|
|
336
|
+
return '$(symbol-misc)';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function capitalize(s: string): string {
|
|
340
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function ensurePanelHtml(panels: Discovered[]) {
|
|
344
|
+
ensureBundleHtml(path.join(SRC, 'webview', 'panels'), panels);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function ensureSubpanelHtml(views: Discovered[]) {
|
|
348
|
+
ensureBundleHtml(path.join(SRC, 'webview', 'subpanels'), views);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function ensureBundleHtml(baseDir: string, entries: Discovered[]) {
|
|
352
|
+
for (const e of entries) {
|
|
353
|
+
const dir = path.join(baseDir, e.id);
|
|
354
|
+
if (!fs.existsSync(dir)) continue;
|
|
355
|
+
const htmlPath = path.join(dir, 'index.html');
|
|
356
|
+
if (fs.existsSync(htmlPath)) continue;
|
|
357
|
+
const mainCandidates = ['main.tsx', 'main.ts', 'index.tsx', 'index.ts'];
|
|
358
|
+
const main = mainCandidates.find((f) => fs.existsSync(path.join(dir, f))) ?? 'main.tsx';
|
|
359
|
+
fs.writeFileSync(
|
|
360
|
+
htmlPath,
|
|
361
|
+
`<!DOCTYPE html>
|
|
362
|
+
<html><head><meta charset="UTF-8" /></head>
|
|
363
|
+
<body><div id="root"></div><script type="module" src="./${main}"></script></body>
|
|
364
|
+
</html>
|
|
365
|
+
`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function main() {
|
|
371
|
+
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
372
|
+
const prefix: string =
|
|
373
|
+
pkg.vsceasy?.commandPrefix ?? pkg.name.replace(/^@[^/]+\//, '').replace(/[^a-zA-Z0-9]+/g, '');
|
|
374
|
+
const displayName: string = pkg.displayName ?? pkg.name;
|
|
375
|
+
|
|
376
|
+
const registryDir = path.dirname(OUT);
|
|
377
|
+
const panels = scan(PANELS_DIR, registryDir);
|
|
378
|
+
const commands = scan(COMMANDS_DIR, registryDir);
|
|
379
|
+
const menus = scan(MENUS_DIR, registryDir);
|
|
380
|
+
const statusBars = scan(STATUS_BARS_DIR, registryDir);
|
|
381
|
+
const subpanels = scan(SUBPANELS_DIR, registryDir);
|
|
382
|
+
const treeViews = scan(TREE_VIEWS_DIR, registryDir);
|
|
383
|
+
const jobs = scan(JOBS_DIR, registryDir);
|
|
384
|
+
|
|
385
|
+
writeRegistry(panels, commands, menus, statusBars, subpanels, treeViews, jobs, prefix);
|
|
386
|
+
syncPackageJson(panels, commands, menus, subpanels, treeViews, prefix, displayName);
|
|
387
|
+
ensurePanelHtml(panels);
|
|
388
|
+
ensureSubpanelHtml(subpanels);
|
|
389
|
+
|
|
390
|
+
console.log(
|
|
391
|
+
`✓ vsceasy gen → ${panels.length} panel(s), ${commands.length} command(s), ${menus.length} menu(s), ${statusBars.length} statusBar(s), ${subpanels.length} subpanel(s), ${treeViews.length} treeView(s), ${jobs.length} job(s)`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
main();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { definePanel } from '../shared/vsceasy';
|
|
2
|
+
import type { DashboardApi } from '../shared/api';
|
|
3
|
+
|
|
4
|
+
export default definePanel<DashboardApi>({
|
|
5
|
+
title: '{{displayName}} Dashboard',
|
|
6
|
+
rpc: (vscode) => ({
|
|
7
|
+
async getInfo() {
|
|
8
|
+
return {
|
|
9
|
+
workspace: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? null,
|
|
10
|
+
vscodeVersion: vscode.version,
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
async showMessage(text) {
|
|
14
|
+
await vscode.window.showInformationMessage(text);
|
|
15
|
+
},
|
|
16
|
+
async listFiles(pattern) {
|
|
17
|
+
const uris = await vscode.workspace.findFiles(pattern, '**/node_modules/**', 100);
|
|
18
|
+
return uris.map((u) => vscode.workspace.asRelativePath(u));
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// RPC contracts — one interface per panel. Imported by both extension and webview.
|
|
2
|
+
|
|
3
|
+
export interface DashboardApi {
|
|
4
|
+
getInfo(): Promise<{ workspace: string | null; vscodeVersion: string }>;
|
|
5
|
+
showMessage(text: string): Promise<void>;
|
|
6
|
+
listFiles(pattern: string): Promise<string[]>;
|
|
7
|
+
}
|