@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +474 -0
  4. package/dist/bin/cli.d.ts +1 -0
  5. package/dist/bin/cli.js +9044 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/commands/command/add.d.ts +3 -0
  8. package/dist/commands/components/add.d.ts +3 -0
  9. package/dist/commands/create.d.ts +3 -0
  10. package/dist/commands/crud/add.d.ts +3 -0
  11. package/dist/commands/db/init.d.ts +3 -0
  12. package/dist/commands/doctor.d.ts +3 -0
  13. package/dist/commands/groups.d.ts +16 -0
  14. package/dist/commands/helper/add.d.ts +3 -0
  15. package/dist/commands/job/add.d.ts +3 -0
  16. package/dist/commands/menu/add.d.ts +3 -0
  17. package/dist/commands/menu/edit.d.ts +3 -0
  18. package/dist/commands/model/add.d.ts +3 -0
  19. package/dist/commands/panel/add.d.ts +3 -0
  20. package/dist/commands/publish/init.d.ts +3 -0
  21. package/dist/commands/rpc/add.d.ts +3 -0
  22. package/dist/commands/statusBar/add.d.ts +3 -0
  23. package/dist/commands/subpanel/add.d.ts +3 -0
  24. package/dist/commands/test/setup.d.ts +3 -0
  25. package/dist/commands/treeView/add.d.ts +3 -0
  26. package/dist/commands/upgrade.d.ts +3 -0
  27. package/dist/commands/wizard.d.ts +3 -0
  28. package/dist/data/codicons.d.ts +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +3169 -0
  31. package/dist/lib/command/add.d.ts +31 -0
  32. package/dist/lib/components/add.d.ts +20 -0
  33. package/dist/lib/config.d.ts +10 -0
  34. package/dist/lib/crud/add.d.ts +19 -0
  35. package/dist/lib/crud/crudConfig.d.ts +37 -0
  36. package/dist/lib/crud/parseModel.d.ts +33 -0
  37. package/dist/lib/db/init.d.ts +16 -0
  38. package/dist/lib/db/wire.d.ts +10 -0
  39. package/dist/lib/doctor.d.ts +30 -0
  40. package/dist/lib/findProject.d.ts +10 -0
  41. package/dist/lib/helper/add.d.ts +14 -0
  42. package/dist/lib/iconPicker.d.ts +7 -0
  43. package/dist/lib/index.d.ts +46 -0
  44. package/dist/lib/interactive.d.ts +30 -0
  45. package/dist/lib/job/add.d.ts +24 -0
  46. package/dist/lib/menu/add.d.ts +13 -0
  47. package/dist/lib/menu/edit.d.ts +39 -0
  48. package/dist/lib/menuTree.d.ts +33 -0
  49. package/dist/lib/model/add.d.ts +27 -0
  50. package/dist/lib/model/parseFields.d.ts +14 -0
  51. package/dist/lib/panel/add.d.ts +29 -0
  52. package/dist/lib/publish/init.d.ts +12 -0
  53. package/dist/lib/rpc/add.d.ts +22 -0
  54. package/dist/lib/scaffold.d.ts +13 -0
  55. package/dist/lib/statusBar/add.d.ts +33 -0
  56. package/dist/lib/subpanel/add.d.ts +20 -0
  57. package/dist/lib/testSetup/index.d.ts +10 -0
  58. package/dist/lib/treeView/add.d.ts +13 -0
  59. package/dist/lib/upgrade.d.ts +22 -0
  60. package/dist/lib/validate.d.ts +14 -0
  61. package/dist/lib/wizard/run.d.ts +13 -0
  62. package/package.json +67 -0
  63. package/templates/_generators/command/command.ts.tpl +8 -0
  64. package/templates/_generators/components/Button.tsx.tpl +12 -0
  65. package/templates/_generators/components/Card.tsx.tpl +22 -0
  66. package/templates/_generators/components/Field.tsx.tpl +20 -0
  67. package/templates/_generators/components/Input.tsx.tpl +10 -0
  68. package/templates/_generators/components/List.tsx.tpl +29 -0
  69. package/templates/_generators/components/components.css.tpl +66 -0
  70. package/templates/_generators/components/index.ts.tpl +10 -0
  71. package/templates/_generators/crud/formApp.tsx.tpl +83 -0
  72. package/templates/_generators/crud/formNav.ts.tpl +19 -0
  73. package/templates/_generators/crud/formPanel.ts.tpl +32 -0
  74. package/templates/_generators/crud/listApp.tsx.tpl +84 -0
  75. package/templates/_generators/crud/listPanel.ts.tpl +30 -0
  76. package/templates/_generators/crud/main.tsx.tpl +6 -0
  77. package/templates/_generators/crud/service.ts.tpl +27 -0
  78. package/templates/_generators/helper/cache.ts.tpl +117 -0
  79. package/templates/_generators/helper/config.ts.tpl +36 -0
  80. package/templates/_generators/helper/db.ts.tpl +322 -0
  81. package/templates/_generators/helper/notifications.ts.tpl +45 -0
  82. package/templates/_generators/helper/secrets.ts.tpl +36 -0
  83. package/templates/_generators/helper/state.ts.tpl +44 -0
  84. package/templates/_generators/job/job.ts.tpl +10 -0
  85. package/templates/_generators/menu/menu.ts.tpl +21 -0
  86. package/templates/_generators/model/model.ts.tpl +17 -0
  87. package/templates/_generators/panel/App.tsx.tpl +10 -0
  88. package/templates/_generators/panel/main.tsx.tpl +6 -0
  89. package/templates/_generators/panel/panel.ts.tpl +5 -0
  90. package/templates/_generators/panel/templates/dashboard/App.tsx.tpl +41 -0
  91. package/templates/_generators/panel/templates/form/App.tsx.tpl +44 -0
  92. package/templates/_generators/panel/templates/list/App.tsx.tpl +40 -0
  93. package/templates/_generators/publish/CHANGELOG.md.tpl +8 -0
  94. package/templates/_generators/publish/README.md.tpl +23 -0
  95. package/templates/_generators/statusBar/statusBar.ts.tpl +7 -0
  96. package/templates/_generators/subpanel/App.tsx.tpl +10 -0
  97. package/templates/_generators/subpanel/main.tsx.tpl +6 -0
  98. package/templates/_generators/subpanel/subpanel.ts.tpl +6 -0
  99. package/templates/_generators/test/_helpers.ts.tpl +120 -0
  100. package/templates/_generators/test/sample.test.ts.tpl +38 -0
  101. package/templates/_generators/test/vitest.config.ts.tpl +23 -0
  102. package/templates/_generators/test/vscode.stub.ts.tpl +109 -0
  103. package/templates/_generators/treeView/treeView.ts.tpl +16 -0
  104. package/templates/react/.vscode/launch.json +34 -0
  105. package/templates/react/.vscode/tasks.json +32 -0
  106. package/templates/react/.vscodeignore +8 -0
  107. package/templates/react/README.md +50 -0
  108. package/templates/react/package.json +54 -0
  109. package/templates/react/scripts/gen.ts +395 -0
  110. package/templates/react/src/commands/hello.ts +6 -0
  111. package/templates/react/src/extension/extension.ts +5 -0
  112. package/templates/react/src/panels/dashboard.ts +21 -0
  113. package/templates/react/src/shared/api.ts +7 -0
  114. package/templates/react/src/shared/vsceasy/bootstrap.ts +657 -0
  115. package/templates/react/src/shared/vsceasy/client.ts +8 -0
  116. package/templates/react/src/shared/vsceasy/codiconNames.ts +196 -0
  117. package/templates/react/src/shared/vsceasy/define.ts +269 -0
  118. package/templates/react/src/shared/vsceasy/index.ts +13 -0
  119. package/templates/react/src/shared/vsceasy/rpc.ts +214 -0
  120. package/templates/react/src/webview/panels/dashboard/App.tsx +31 -0
  121. package/templates/react/src/webview/panels/dashboard/main.tsx +6 -0
  122. package/templates/react/src/webview/styles.css +33 -0
  123. package/templates/react/tsconfig.json +17 -0
  124. 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,8 @@
1
+ # Change Log
2
+
3
+ All notable changes follow [Keep a Changelog](https://keepachangelog.com/).
4
+
5
+ ## [Unreleased]
6
+
7
+ ### Added
8
+ - Initial release.
@@ -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,7 @@
1
+ import { defineStatusBar } from '../shared/vsceasy';
2
+
3
+ export default defineStatusBar({
4
+ text: '{{text}}',{{iconLine}}{{tooltipLine}}
5
+ alignment: '{{alignment}}',
6
+ priority: {{priority}},{{commandLine}}{{panelLine}}
7
+ });
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ {{apiBlock}}
3
+ export function App() {
4
+ return (
5
+ <div className="sidebar-view">
6
+ <h2>{{title}}</h2>
7
+ <p>Edit <code>src/webview/subpanels/{{name}}/App.tsx</code> to start building.</p>
8
+ </div>
9
+ );
10
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+ import '../../styles.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(<App />);
@@ -0,0 +1,6 @@
1
+ import { defineSubpanel } from '../shared/vsceasy';
2
+ {{apiImport}}
3
+ export default defineSubpanel{{apiGeneric}}({
4
+ title: '{{title}}',
5
+ menu: '{{menu}}',{{rpcBlock}}
6
+ });
@@ -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,8 @@
1
+ .vscode/**
2
+ src/**
3
+ scripts/**
4
+ node_modules/**
5
+ .gitignore
6
+ tsconfig.json
7
+ vite.config.ts
8
+ *.map
@@ -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
+ ```