@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,41 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Button, Card } from '../../components';
|
|
3
|
+
import '../../components/components.css';
|
|
4
|
+
{{apiBlock}}
|
|
5
|
+
interface Stats {
|
|
6
|
+
total: number;
|
|
7
|
+
active: number;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function App() {
|
|
12
|
+
const [stats, setStats] = useState<Stats | null>(null);
|
|
13
|
+
const [loading, setLoading] = useState(true);
|
|
14
|
+
|
|
15
|
+
async function refresh() {
|
|
16
|
+
setLoading(true);
|
|
17
|
+
try {
|
|
18
|
+
{{statsCall}}
|
|
19
|
+
} finally {
|
|
20
|
+
setLoading(false);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
void refresh();
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="app">
|
|
30
|
+
<h1>{{title}}</h1>
|
|
31
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(8rem, 1fr))', gap: '0.75rem' }}>
|
|
32
|
+
<Card title="Total"><strong style={{ fontSize: '1.5rem' }}>{stats?.total ?? '—'}</strong></Card>
|
|
33
|
+
<Card title="Active"><strong style={{ fontSize: '1.5rem' }}>{stats?.active ?? '—'}</strong></Card>
|
|
34
|
+
<Card title="Updated"><span>{stats?.updatedAt ?? '—'}</span></Card>
|
|
35
|
+
</div>
|
|
36
|
+
<div style={{ marginTop: '0.75rem' }}>
|
|
37
|
+
<Button variant="secondary" onClick={refresh} disabled={loading}>{loading ? 'Loading…' : 'Refresh'}</Button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, Input, Field, Card } from '../../components';
|
|
3
|
+
import '../../components/components.css';
|
|
4
|
+
{{apiBlock}}
|
|
5
|
+
export function App() {
|
|
6
|
+
const [name, setName] = useState('');
|
|
7
|
+
const [email, setEmail] = useState('');
|
|
8
|
+
const [status, setStatus] = useState<string | null>(null);
|
|
9
|
+
const [busy, setBusy] = useState(false);
|
|
10
|
+
|
|
11
|
+
async function onSubmit(e: React.FormEvent) {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
setBusy(true);
|
|
14
|
+
setStatus(null);
|
|
15
|
+
try {
|
|
16
|
+
{{submitCall}}
|
|
17
|
+
setStatus('Saved.');
|
|
18
|
+
setName('');
|
|
19
|
+
setEmail('');
|
|
20
|
+
} catch (err) {
|
|
21
|
+
setStatus(err instanceof Error ? err.message : 'Failed.');
|
|
22
|
+
} finally {
|
|
23
|
+
setBusy(false);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="app">
|
|
29
|
+
<h1>{{title}}</h1>
|
|
30
|
+
<Card title="New entry">
|
|
31
|
+
<form onSubmit={onSubmit}>
|
|
32
|
+
<Field label="Name" htmlFor="name">
|
|
33
|
+
<Input id="name" value={name} onChange={(e) => setName(e.target.value)} placeholder="Jane Doe" required />
|
|
34
|
+
</Field>
|
|
35
|
+
<Field label="Email" htmlFor="email">
|
|
36
|
+
<Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="jane@acme.io" />
|
|
37
|
+
</Field>
|
|
38
|
+
<Button type="submit" disabled={busy}>{busy ? 'Saving…' : 'Save'}</Button>
|
|
39
|
+
{status ? <span style={{ marginLeft: '0.75rem' }}>{status}</span> : null}
|
|
40
|
+
</form>
|
|
41
|
+
</Card>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Button, List, Card } from '../../components';
|
|
3
|
+
import '../../components/components.css';
|
|
4
|
+
{{apiBlock}}
|
|
5
|
+
interface Row {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function App() {
|
|
11
|
+
const [rows, setRows] = useState<Row[]>([]);
|
|
12
|
+
const [loading, setLoading] = useState(true);
|
|
13
|
+
|
|
14
|
+
async function refresh() {
|
|
15
|
+
setLoading(true);
|
|
16
|
+
try {
|
|
17
|
+
{{loadCall}}
|
|
18
|
+
} finally {
|
|
19
|
+
setLoading(false);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
void refresh();
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="app">
|
|
29
|
+
<h1>{{title}}</h1>
|
|
30
|
+
<Card title="Items" actions={<Button variant="secondary" onClick={refresh} disabled={loading}>{loading ? 'Loading…' : 'Refresh'}</Button>}>
|
|
31
|
+
<List
|
|
32
|
+
items={rows}
|
|
33
|
+
getKey={(r) => r.id}
|
|
34
|
+
renderItem={(r) => r.label}
|
|
35
|
+
empty="No items yet."
|
|
36
|
+
/>
|
|
37
|
+
</Card>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# {{displayName}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- (describe what this extension does)
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
VS Code ≥ 1.80
|
|
12
|
+
|
|
13
|
+
## Extension Settings
|
|
14
|
+
|
|
15
|
+
(list your `contributes.configuration` entries)
|
|
16
|
+
|
|
17
|
+
## Known Issues
|
|
18
|
+
|
|
19
|
+
—
|
|
20
|
+
|
|
21
|
+
## Release Notes
|
|
22
|
+
|
|
23
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { createRpcServer, createRpcClient } from '../shared/vsceasy/rpc';
|
|
3
|
+
import type { Transport, Handlers, RpcClient } from '../shared/vsceasy/rpc';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal `vscode` namespace mock — covers the surface most extensions touch.
|
|
7
|
+
* Extend per test by spreading or assigning new spies onto the returned object.
|
|
8
|
+
*
|
|
9
|
+
* const vscode = mockVscode();
|
|
10
|
+
* vscode.window.showInformationMessage('hi');
|
|
11
|
+
* expect(vscode.window.showInformationMessage).toHaveBeenCalledWith('hi');
|
|
12
|
+
*/
|
|
13
|
+
export function mockVscode() {
|
|
14
|
+
const subscriptions: { dispose(): void }[] = [];
|
|
15
|
+
return {
|
|
16
|
+
window: {
|
|
17
|
+
showInformationMessage: vi.fn(),
|
|
18
|
+
showWarningMessage: vi.fn(),
|
|
19
|
+
showErrorMessage: vi.fn(),
|
|
20
|
+
showInputBox: vi.fn(),
|
|
21
|
+
showQuickPick: vi.fn(),
|
|
22
|
+
withProgress: vi.fn(async (_opts: any, task: any) => task({ report: vi.fn() })),
|
|
23
|
+
createStatusBarItem: vi.fn(() => ({ show: vi.fn(), dispose: vi.fn(), text: '' })),
|
|
24
|
+
createTreeView: vi.fn(() => ({ dispose: vi.fn() })),
|
|
25
|
+
createWebviewPanel: vi.fn(),
|
|
26
|
+
activeTextEditor: undefined as any,
|
|
27
|
+
},
|
|
28
|
+
workspace: {
|
|
29
|
+
getConfiguration: vi.fn(() => ({ get: vi.fn(), update: vi.fn(async () => undefined) })),
|
|
30
|
+
onDidChangeConfiguration: vi.fn(() => ({ dispose: vi.fn() })),
|
|
31
|
+
findFiles: vi.fn(async () => [] as any[]),
|
|
32
|
+
workspaceFolders: [] as any[],
|
|
33
|
+
},
|
|
34
|
+
commands: {
|
|
35
|
+
registerCommand: vi.fn((_id: string, _fn: any) => ({ dispose: vi.fn() })),
|
|
36
|
+
executeCommand: vi.fn(async () => undefined),
|
|
37
|
+
},
|
|
38
|
+
env: {
|
|
39
|
+
openExternal: vi.fn(async () => true),
|
|
40
|
+
},
|
|
41
|
+
Uri: { parse: (s: string) => ({ toString: () => s }), file: (p: string) => ({ fsPath: p, toString: () => p }) },
|
|
42
|
+
ProgressLocation: { Notification: 15, SourceControl: 1, Window: 10 } as const,
|
|
43
|
+
ConfigurationTarget: { Global: 1, Workspace: 2, WorkspaceFolder: 3 } as const,
|
|
44
|
+
TreeItemCollapsibleState: { None: 0, Collapsed: 1, Expanded: 2 } as const,
|
|
45
|
+
StatusBarAlignment: { Left: 1, Right: 2 } as const,
|
|
46
|
+
EventEmitter: class {
|
|
47
|
+
private listeners: Array<(v: any) => void> = [];
|
|
48
|
+
event = (l: (v: any) => void) => {
|
|
49
|
+
this.listeners.push(l);
|
|
50
|
+
return { dispose: () => (this.listeners = this.listeners.filter((x) => x !== l)) };
|
|
51
|
+
};
|
|
52
|
+
fire(v: any) {
|
|
53
|
+
this.listeners.forEach((l) => l(v));
|
|
54
|
+
}
|
|
55
|
+
dispose() {
|
|
56
|
+
this.listeners = [];
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
Disposable: { from: (...d: any[]) => ({ dispose: () => d.forEach((x) => x.dispose?.()) }) },
|
|
60
|
+
subscriptions,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type VscodeMock = ReturnType<typeof mockVscode>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Minimal `ExtensionContext` mock — backed by in-memory Maps for state/secrets.
|
|
68
|
+
*/
|
|
69
|
+
export function mockContext() {
|
|
70
|
+
const wm = new Map<string, unknown>();
|
|
71
|
+
const gm = new Map<string, unknown>();
|
|
72
|
+
const sm = new Map<string, string>();
|
|
73
|
+
const memento = (m: Map<string, unknown>) => ({
|
|
74
|
+
get: (k: string, d?: unknown) => (m.has(k) ? m.get(k) : d),
|
|
75
|
+
update: async (k: string, v: unknown) => void (v === undefined ? m.delete(k) : m.set(k, v)),
|
|
76
|
+
keys: () => Array.from(m.keys()),
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
subscriptions: [] as { dispose(): void }[],
|
|
80
|
+
workspaceState: memento(wm),
|
|
81
|
+
globalState: memento(gm),
|
|
82
|
+
secrets: {
|
|
83
|
+
get: async (k: string) => sm.get(k),
|
|
84
|
+
store: async (k: string, v: string) => void sm.set(k, v),
|
|
85
|
+
delete: async (k: string) => void sm.delete(k),
|
|
86
|
+
onDidChange: () => ({ dispose: () => {} }),
|
|
87
|
+
},
|
|
88
|
+
extensionPath: '/mock',
|
|
89
|
+
extensionUri: { fsPath: '/mock' },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build an in-memory RPC pair (server + typed client). No webview involved.
|
|
95
|
+
* Useful for testing your handlers end-to-end with the same types the UI sees.
|
|
96
|
+
*
|
|
97
|
+
* const handlers = { greet: (n: string) => `hi ${n}` };
|
|
98
|
+
* const api = mockRpcPair<typeof handlers>(handlers);
|
|
99
|
+
* expect(await api.greet('Jairo')).toBe('hi Jairo');
|
|
100
|
+
*/
|
|
101
|
+
export function mockRpcPair<H extends Handlers>(handlers: H): RpcClient<H> {
|
|
102
|
+
const aListeners = new Set<(m: any) => void>();
|
|
103
|
+
const bListeners = new Set<(m: any) => void>();
|
|
104
|
+
const aToB: Transport = {
|
|
105
|
+
send: (m) => bListeners.forEach((l) => l(m)),
|
|
106
|
+
onMessage: (h) => {
|
|
107
|
+
aListeners.add(h);
|
|
108
|
+
return () => aListeners.delete(h);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const bToA: Transport = {
|
|
112
|
+
send: (m) => aListeners.forEach((l) => l(m)),
|
|
113
|
+
onMessage: (h) => {
|
|
114
|
+
bListeners.add(h);
|
|
115
|
+
return () => bListeners.delete(h);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
createRpcServer<H>(bToA, handlers);
|
|
119
|
+
return createRpcClient<H>(aToB, { callTimeoutMs: 0 });
|
|
120
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mockVscode, mockContext, mockRpcPair } from './_helpers';
|
|
3
|
+
|
|
4
|
+
describe('vscode mock', () => {
|
|
5
|
+
it('captures notification calls', () => {
|
|
6
|
+
const vscode = mockVscode();
|
|
7
|
+
vscode.window.showInformationMessage('hello');
|
|
8
|
+
expect(vscode.window.showInformationMessage).toHaveBeenCalledWith('hello');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('persists state via mock context', async () => {
|
|
12
|
+
const ctx = mockContext();
|
|
13
|
+
await ctx.workspaceState.update('key', 42);
|
|
14
|
+
expect(ctx.workspaceState.get('key')).toBe(42);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('RPC pair', () => {
|
|
19
|
+
it('round-trips a typed handler call', async () => {
|
|
20
|
+
const handlers = {
|
|
21
|
+
async greet(name: string) {
|
|
22
|
+
return `hi ${name}`;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const api = mockRpcPair<typeof handlers>(handlers);
|
|
26
|
+
expect(await api.greet('Jairo')).toBe('hi Jairo');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('propagates handler errors', async () => {
|
|
30
|
+
const handlers = {
|
|
31
|
+
async boom() {
|
|
32
|
+
throw new Error('nope');
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const api = mockRpcPair<typeof handlers>(handlers);
|
|
36
|
+
await expect(api.boom()).rejects.toThrow('nope');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
resolve: {
|
|
6
|
+
alias: {
|
|
7
|
+
// `vscode` is only available inside the extension host. Tests stub it
|
|
8
|
+
// with an empty module so importing any file that does `import * as vscode`
|
|
9
|
+
// doesn't crash. Use `mockVscode()` from `_helpers.ts` for the surface
|
|
10
|
+
// you actually need to assert against.
|
|
11
|
+
vscode: path.resolve(__dirname, 'src/__tests__/__mocks__/vscode.ts'),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
test: {
|
|
15
|
+
include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
|
|
16
|
+
environment: 'node',
|
|
17
|
+
globals: false,
|
|
18
|
+
coverage: {
|
|
19
|
+
include: ['src/**/*.{ts,tsx}'],
|
|
20
|
+
exclude: ['src/extension/_registry.ts', 'src/webview/**', 'src/__tests__/**'],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated `vscode` module stub for vitest. Replaces the real `vscode`
|
|
3
|
+
* import inside tests (the real module only exists in the extension host).
|
|
4
|
+
*
|
|
5
|
+
* The stub exposes the enums and class shapes the runtime touches at module
|
|
6
|
+
* load time so files that do `import * as vscode from 'vscode'` don't crash
|
|
7
|
+
* during test collection.
|
|
8
|
+
*
|
|
9
|
+
* To assert behaviour against `vscode.window.X`, use `mockVscode()` from
|
|
10
|
+
* `_helpers.ts` and inject it into the code under test directly.
|
|
11
|
+
*/
|
|
12
|
+
export const TreeItemCollapsibleState = { None: 0, Collapsed: 1, Expanded: 2 } as const;
|
|
13
|
+
export const StatusBarAlignment = { Left: 1, Right: 2 } as const;
|
|
14
|
+
export const ConfigurationTarget = { Global: 1, Workspace: 2, WorkspaceFolder: 3 } as const;
|
|
15
|
+
export const ProgressLocation = { SourceControl: 1, Window: 10, Notification: 15 } as const;
|
|
16
|
+
export const ViewColumn = { Active: -1, Beside: -2, One: 1, Two: 2, Three: 3 } as const;
|
|
17
|
+
export const ThemeIcon = class { constructor(public id: string) {} };
|
|
18
|
+
export const ThemeColor = class { constructor(public id: string) {} };
|
|
19
|
+
export const TreeItem = class { constructor(public label: string, public collapsibleState?: number) {} };
|
|
20
|
+
export const MarkdownString = class {
|
|
21
|
+
value = '';
|
|
22
|
+
isTrusted = false;
|
|
23
|
+
supportHtml = false;
|
|
24
|
+
constructor(value = '') {
|
|
25
|
+
this.value = value;
|
|
26
|
+
}
|
|
27
|
+
appendMarkdown(v: string) {
|
|
28
|
+
this.value += v;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export const Uri = {
|
|
33
|
+
parse: (s: string) => ({ toString: () => s, fsPath: s }),
|
|
34
|
+
file: (p: string) => ({ fsPath: p, toString: () => p }),
|
|
35
|
+
joinPath: (base: { fsPath: string }, ...segs: string[]) => ({
|
|
36
|
+
fsPath: [base.fsPath, ...segs].join('/'),
|
|
37
|
+
toString: () => [base.fsPath, ...segs].join('/'),
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
export const EventEmitter = class<T> {
|
|
41
|
+
private listeners: Array<(v: T) => void> = [];
|
|
42
|
+
event = (l: (v: T) => void) => {
|
|
43
|
+
this.listeners.push(l);
|
|
44
|
+
return { dispose: () => (this.listeners = this.listeners.filter((x) => x !== l)) };
|
|
45
|
+
};
|
|
46
|
+
fire(v: T) {
|
|
47
|
+
this.listeners.forEach((l) => l(v));
|
|
48
|
+
}
|
|
49
|
+
dispose() {
|
|
50
|
+
this.listeners = [];
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export const Disposable = {
|
|
54
|
+
from: (...d: Array<{ dispose: () => void }>) => ({ dispose: () => d.forEach((x) => x.dispose()) }),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const noop = () => {};
|
|
58
|
+
const noopDisposable = { dispose: noop };
|
|
59
|
+
const noopAsync = async () => undefined;
|
|
60
|
+
const noopReg = () => noopDisposable;
|
|
61
|
+
|
|
62
|
+
export const window = {
|
|
63
|
+
showInformationMessage: noopAsync,
|
|
64
|
+
showWarningMessage: noopAsync,
|
|
65
|
+
showErrorMessage: noopAsync,
|
|
66
|
+
showInputBox: noopAsync,
|
|
67
|
+
showQuickPick: noopAsync,
|
|
68
|
+
withProgress: async (_o: unknown, task: (p: { report: () => void }) => unknown) =>
|
|
69
|
+
task({ report: noop }),
|
|
70
|
+
createStatusBarItem: () => ({ show: noop, hide: noop, dispose: noop, text: '' }),
|
|
71
|
+
createTreeView: () => ({ dispose: noop }),
|
|
72
|
+
createWebviewPanel: () => ({
|
|
73
|
+
webview: { html: '', onDidReceiveMessage: noopReg, postMessage: noop, asWebviewUri: (u: unknown) => u },
|
|
74
|
+
onDidDispose: noopReg,
|
|
75
|
+
dispose: noop,
|
|
76
|
+
}),
|
|
77
|
+
registerWebviewViewProvider: noopReg,
|
|
78
|
+
activeTextEditor: undefined as unknown,
|
|
79
|
+
onDidChangeActiveTextEditor: noopReg,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const workspace = {
|
|
83
|
+
getConfiguration: () => ({ get: noop, update: noopAsync, has: () => false, inspect: () => undefined }),
|
|
84
|
+
onDidChangeConfiguration: noopReg,
|
|
85
|
+
findFiles: async () => [] as unknown[],
|
|
86
|
+
workspaceFolders: [] as unknown[],
|
|
87
|
+
asRelativePath: (p: string) => p,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const commands = {
|
|
91
|
+
registerCommand: noopReg,
|
|
92
|
+
executeCommand: noopAsync,
|
|
93
|
+
getCommands: async () => [] as string[],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const env = {
|
|
97
|
+
openExternal: async () => true,
|
|
98
|
+
clipboard: { writeText: noopAsync, readText: async () => '' },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const extensions = {
|
|
102
|
+
getExtension: () => undefined,
|
|
103
|
+
all: [] as unknown[],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const languages = {
|
|
107
|
+
registerHoverProvider: noopReg,
|
|
108
|
+
registerCompletionItemProvider: noopReg,
|
|
109
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineTreeView, TreeNode } from '../shared/vsceasy';
|
|
2
|
+
|
|
3
|
+
export default defineTreeView({
|
|
4
|
+
title: '{{title}}',
|
|
5
|
+
menu: '{{menu}}',
|
|
6
|
+
getChildren: async (parent, vscode, ctx) => {
|
|
7
|
+
if (!parent) {
|
|
8
|
+
return [
|
|
9
|
+
{ label: 'Item 1', icon: 'file', tooltip: 'Replace with real data' },
|
|
10
|
+
{ label: 'Group', icon: 'folder', collapsed: 'collapsed', children: [] },
|
|
11
|
+
] as TreeNode[];
|
|
12
|
+
}
|
|
13
|
+
// Lazy children — return based on parent.id / parent.contextValue.
|
|
14
|
+
return [];
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.2.0",
|
|
3
|
+
"configurations": [
|
|
4
|
+
{
|
|
5
|
+
"name": "Run Extension",
|
|
6
|
+
"type": "extensionHost",
|
|
7
|
+
"request": "launch",
|
|
8
|
+
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
|
9
|
+
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
|
10
|
+
"sourceMaps": true,
|
|
11
|
+
"smartStep": true,
|
|
12
|
+
"preLaunchTask": "vsceasy: build"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "Run Extension (watch / HMR)",
|
|
16
|
+
"type": "extensionHost",
|
|
17
|
+
"request": "launch",
|
|
18
|
+
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
|
19
|
+
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
|
20
|
+
"sourceMaps": true,
|
|
21
|
+
"smartStep": true,
|
|
22
|
+
"preLaunchTask": "vsceasy: dev"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "Attach to Extension Host",
|
|
26
|
+
"type": "node",
|
|
27
|
+
"request": "attach",
|
|
28
|
+
"port": 9229,
|
|
29
|
+
"skipFiles": ["<node_internals>/**"],
|
|
30
|
+
"sourceMaps": true,
|
|
31
|
+
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"label": "vsceasy: build",
|
|
6
|
+
"type": "shell",
|
|
7
|
+
"command": "bun run build",
|
|
8
|
+
"presentation": { "reveal": "silent", "panel": "dedicated" },
|
|
9
|
+
"problemMatcher": []
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"label": "vsceasy: dev",
|
|
13
|
+
"type": "shell",
|
|
14
|
+
"command": "bun run dev",
|
|
15
|
+
"isBackground": true,
|
|
16
|
+
"presentation": { "reveal": "always", "panel": "dedicated" },
|
|
17
|
+
"group": { "kind": "build", "isDefault": true },
|
|
18
|
+
"problemMatcher": {
|
|
19
|
+
"owner": "esbuild-watch",
|
|
20
|
+
"pattern": {
|
|
21
|
+
"regexp": "^\\s*✘\\s*\\[ERROR\\]\\s+(.*)$",
|
|
22
|
+
"message": 1
|
|
23
|
+
},
|
|
24
|
+
"background": {
|
|
25
|
+
"activeOnStart": true,
|
|
26
|
+
"beginsPattern": ".*build started.*|.*\\[watch\\] build started.*",
|
|
27
|
+
"endsPattern": ".*built in.*|.*\\[watch\\] build finished.*"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# {{displayName}}
|
|
2
|
+
|
|
3
|
+
Generated with [`vsceasy`](https://www.npmjs.com/package/vsceasy).
|
|
4
|
+
|
|
5
|
+
## Develop
|
|
6
|
+
|
|
7
|
+
Two ways to launch the Extension Development Host:
|
|
8
|
+
|
|
9
|
+
### Option A — one-shot launch (recommended for first run)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun install
|
|
13
|
+
bun run launch
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Builds extension + webview once, then opens a new VS Code window with the extension loaded.
|
|
17
|
+
Then in the dev window: Command Palette → **{{displayName}}: Open Dashboard**.
|
|
18
|
+
|
|
19
|
+
### Option B — watch mode + F5
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun install
|
|
23
|
+
bun run dev # leave running — watches both extension + webview
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then in VS Code (this folder open):
|
|
27
|
+
- Press <kbd>F5</kbd> → picks **Run Extension** launch config → opens Extension Development Host.
|
|
28
|
+
- Re-press <kbd>Ctrl/Cmd+R</kbd> inside the dev host after code changes to reload.
|
|
29
|
+
|
|
30
|
+
> Note: `bun run dev` only builds — it does NOT launch VS Code. F5 (or `bun run launch`) does.
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
- Command Palette → **{{displayName}}: Hello** → info toast
|
|
35
|
+
- Command Palette → **{{displayName}}: Open Dashboard** → React webview
|
|
36
|
+
|
|
37
|
+
## Structure
|
|
38
|
+
|
|
39
|
+
- `src/extension/extension.ts` — entry, registers commands
|
|
40
|
+
- `src/extension/panels/DashboardPanel.ts` — webview panel + RPC handlers
|
|
41
|
+
- `src/webview/App.tsx` — React UI (typed RPC client)
|
|
42
|
+
- `src/shared/api.ts` — RPC contract (types flow to both sides)
|
|
43
|
+
- `src/shared/rpc.ts` — bridge implementation
|
|
44
|
+
|
|
45
|
+
## Package as `.vsix`
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bun run build
|
|
49
|
+
bun run package # → {{name}}-0.0.1.vsix
|
|
50
|
+
```
|