@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,196 @@
1
+ /**
2
+ * AUTO-GENERATED by scripts/genCodiconTypes.ts — do not edit.
3
+ *
4
+ * Curated subset of VS Code codicon names that the CLI's icon picker offers.
5
+ * Used by `MenuIcon` for editor autocomplete. Any string is still accepted at
6
+ * runtime via the `(string & {})` fallback in define.ts.
7
+ *
8
+ * Full list: https://microsoft.github.io/vscode-codicons/dist/codicon.html
9
+ */
10
+ export type CodiconName =
11
+ | 'account'
12
+ | 'add'
13
+ | 'archive'
14
+ | 'beaker'
15
+ | 'bell'
16
+ | 'bell-dot'
17
+ | 'book'
18
+ | 'bookmark'
19
+ | 'broadcast'
20
+ | 'browser'
21
+ | 'bug'
22
+ | 'calendar'
23
+ | 'check'
24
+ | 'clock'
25
+ | 'close'
26
+ | 'cloud'
27
+ | 'cloud-download'
28
+ | 'cloud-upload'
29
+ | 'code'
30
+ | 'comment'
31
+ | 'comment-discussion'
32
+ | 'compass'
33
+ | 'console'
34
+ | 'dashboard'
35
+ | 'database'
36
+ | 'debug'
37
+ | 'debug-alt'
38
+ | 'debug-breakpoint'
39
+ | 'debug-console'
40
+ | 'debug-continue'
41
+ | 'debug-disconnect'
42
+ | 'debug-pause'
43
+ | 'debug-restart'
44
+ | 'debug-start'
45
+ | 'debug-step-into'
46
+ | 'debug-step-out'
47
+ | 'debug-step-over'
48
+ | 'debug-stop'
49
+ | 'desktop-download'
50
+ | 'edit'
51
+ | 'error'
52
+ | 'export'
53
+ | 'extensions'
54
+ | 'eye'
55
+ | 'eye-closed'
56
+ | 'file'
57
+ | 'file-binary'
58
+ | 'file-code'
59
+ | 'file-media'
60
+ | 'file-pdf'
61
+ | 'file-symlink-directory'
62
+ | 'file-symlink-file'
63
+ | 'file-text'
64
+ | 'file-zip'
65
+ | 'files'
66
+ | 'filter'
67
+ | 'flag'
68
+ | 'flame'
69
+ | 'folder'
70
+ | 'folder-active'
71
+ | 'folder-library'
72
+ | 'folder-opened'
73
+ | 'gear'
74
+ | 'git-branch'
75
+ | 'git-commit'
76
+ | 'git-compare'
77
+ | 'git-fork'
78
+ | 'git-merge'
79
+ | 'git-pull-request'
80
+ | 'github'
81
+ | 'github-alt'
82
+ | 'github-inverted'
83
+ | 'globe'
84
+ | 'graph'
85
+ | 'heart'
86
+ | 'history'
87
+ | 'home'
88
+ | 'image'
89
+ | 'inbox'
90
+ | 'info'
91
+ | 'json'
92
+ | 'key'
93
+ | 'layout'
94
+ | 'layout-panel'
95
+ | 'layout-sidebar-left'
96
+ | 'layout-sidebar-right'
97
+ | 'lightbulb'
98
+ | 'link'
99
+ | 'link-external'
100
+ | 'list-flat'
101
+ | 'list-ordered'
102
+ | 'list-selection'
103
+ | 'list-tree'
104
+ | 'list-unordered'
105
+ | 'location'
106
+ | 'lock'
107
+ | 'mail'
108
+ | 'markdown'
109
+ | 'megaphone'
110
+ | 'mortar-board'
111
+ | 'mute'
112
+ | 'new-file'
113
+ | 'new-folder'
114
+ | 'note'
115
+ | 'notebook'
116
+ | 'organization'
117
+ | 'output'
118
+ | 'package'
119
+ | 'pencil'
120
+ | 'person'
121
+ | 'pin'
122
+ | 'pinned'
123
+ | 'play'
124
+ | 'play-circle'
125
+ | 'plug'
126
+ | 'preferences'
127
+ | 'preview'
128
+ | 'pulse'
129
+ | 'question'
130
+ | 'record'
131
+ | 'refresh'
132
+ | 'remove'
133
+ | 'replace'
134
+ | 'repo'
135
+ | 'repo-clone'
136
+ | 'repo-forked'
137
+ | 'repo-pull'
138
+ | 'repo-push'
139
+ | 'rocket'
140
+ | 'save'
141
+ | 'save-all'
142
+ | 'search'
143
+ | 'server'
144
+ | 'server-environment'
145
+ | 'server-process'
146
+ | 'settings'
147
+ | 'settings-gear'
148
+ | 'shield'
149
+ | 'source-control'
150
+ | 'sparkle'
151
+ | 'split-horizontal'
152
+ | 'split-vertical'
153
+ | 'star'
154
+ | 'star-empty'
155
+ | 'star-full'
156
+ | 'stop'
157
+ | 'stop-circle'
158
+ | 'symbol-array'
159
+ | 'symbol-boolean'
160
+ | 'symbol-class'
161
+ | 'symbol-color'
162
+ | 'symbol-constant'
163
+ | 'symbol-enum'
164
+ | 'symbol-event'
165
+ | 'symbol-field'
166
+ | 'symbol-function'
167
+ | 'symbol-interface'
168
+ | 'symbol-key'
169
+ | 'symbol-method'
170
+ | 'symbol-misc'
171
+ | 'symbol-module'
172
+ | 'symbol-namespace'
173
+ | 'symbol-numeric'
174
+ | 'symbol-parameter'
175
+ | 'symbol-property'
176
+ | 'symbol-snippet'
177
+ | 'symbol-string'
178
+ | 'symbol-variable'
179
+ | 'sync'
180
+ | 'tag'
181
+ | 'terminal'
182
+ | 'terminal-bash'
183
+ | 'terminal-cmd'
184
+ | 'terminal-linux'
185
+ | 'terminal-powershell'
186
+ | 'tools'
187
+ | 'trash'
188
+ | 'unlock'
189
+ | 'unmute'
190
+ | 'verified'
191
+ | 'video'
192
+ | 'wand'
193
+ | 'warning'
194
+ | 'watch'
195
+ | 'window'
196
+ | 'zap';
@@ -0,0 +1,269 @@
1
+ import type * as vscode from 'vscode';
2
+ import type { Handlers } from './rpc';
3
+ import type { CodiconName } from './codiconNames';
4
+
5
+ export interface PanelDef<H extends Handlers = Handlers> {
6
+ /** Stable id. Default: file basename. Used as command suffix and webview key. */
7
+ id?: string;
8
+ /** Tab title. */
9
+ title: string;
10
+ /** Webview bundle name under dist/webview/<ui>/. Default: same as id. */
11
+ ui?: string;
12
+ /** Where to open. Default: 'active'. */
13
+ column?: 'active' | 'beside' | 'one' | 'two' | 'three';
14
+ /** Keep DOM alive when hidden. Default: true. */
15
+ retainContext?: boolean;
16
+ /** RPC handlers — receives vscode namespace + extension context. */
17
+ rpc?: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext) => H;
18
+ /** Optional command palette entry that opens this panel. Default: true. */
19
+ command?:
20
+ | boolean
21
+ | { title?: string; category?: string };
22
+ }
23
+
24
+ export interface CommandDef {
25
+ /** Stable id. Default: file basename. */
26
+ id?: string;
27
+ /** Command palette title. */
28
+ title: string;
29
+ /** Optional category prefix (default: extension displayName). */
30
+ category?: string;
31
+ /** Handler. Receives vscode + extension context. */
32
+ run: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext, ...args: unknown[]) => unknown | Promise<unknown>;
33
+ /**
34
+ * Keyboard shortcut. String shorthand uses the same key on every platform.
35
+ * Object form supports `mac` override and a VS Code `when` clause.
36
+ * Written to package.json#contributes.keybindings by `bun run gen`.
37
+ */
38
+ keybinding?: string | KeybindingDef | (string | KeybindingDef)[];
39
+ /**
40
+ * VS Code `when` clause that controls visibility/enablement of this command
41
+ * in the command palette and auto-generated menu entries. Written to
42
+ * `contributes.commands[].enablement` and used as the default `when` on
43
+ * the palette menu entry by `bun run gen`.
44
+ *
45
+ * Examples: `'editorTextFocus'`, `'resourceLangId == typescript'`,
46
+ * `'explorerResourceIsFolder && !virtualWorkspace'`.
47
+ * Reference: https://code.visualstudio.com/api/references/when-clause-contexts
48
+ */
49
+ when?: string;
50
+ }
51
+
52
+ export interface KeybindingDef {
53
+ /** Default key combo (e.g. 'ctrl+shift+h'). */
54
+ key: string;
55
+ /** Override combo on macOS (e.g. 'cmd+shift+h'). */
56
+ mac?: string;
57
+ /** VS Code context `when` clause (e.g. 'editorTextFocus'). */
58
+ when?: string;
59
+ }
60
+
61
+ export function definePanel<H extends Handlers = Handlers>(def: PanelDef<H>): PanelDef<H> {
62
+ return def;
63
+ }
64
+
65
+ export function defineCommand(def: CommandDef): CommandDef {
66
+ return def;
67
+ }
68
+
69
+ // --- Menus (Activity Bar + Tree View) ---
70
+
71
+ export type MenuIcon =
72
+ | CodiconName // known codicon (autocompletes)
73
+ | (string & {}) // any codicon name (escape hatch, keeps autocomplete)
74
+ | { path: string } // single SVG path relative to project root
75
+ | { light: string; dark: string }; // theme-aware SVG paths
76
+
77
+ export type { CodiconName };
78
+
79
+ export interface MenuItem {
80
+ /** Display label. */
81
+ label: string;
82
+ /** Optional icon (codicon name or asset path). */
83
+ icon?: MenuIcon;
84
+ /** Optional tooltip / hover description. */
85
+ description?: string;
86
+ /** Open a panel by id (file basename in src/panels/). */
87
+ panel?: string;
88
+ /** Execute a command by id (file basename in src/commands/). */
89
+ command?: string;
90
+ /** Open an external URL in the user's browser. */
91
+ url?: string;
92
+ /** Run an arbitrary handler (full vscode access). */
93
+ run?: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext) => unknown | Promise<unknown>;
94
+ /** Nested items — renders as a collapsible group. */
95
+ children?: MenuItem[];
96
+ /** Initial collapsed state for groups. Default: 'expanded'. */
97
+ collapsed?: 'expanded' | 'collapsed';
98
+ }
99
+
100
+ export interface MenuDef {
101
+ /** Stable id. Default: file basename. Becomes the view container id. */
102
+ id?: string;
103
+ /** Title shown at the top of the sidebar panel and as the activity bar tooltip. */
104
+ title: string;
105
+ /** Activity bar icon. Codicon string OR SVG path(s). */
106
+ icon: MenuIcon;
107
+ /** Items shown in the tree view. */
108
+ items: MenuItem[];
109
+ }
110
+
111
+ export function defineMenu(def: MenuDef): MenuDef {
112
+ return def;
113
+ }
114
+
115
+ // --- Webview Views (inline sidebar sections) ---
116
+
117
+ export interface SubpanelDef<H extends Handlers = Handlers> {
118
+ /** Stable id. Default: file basename. */
119
+ id?: string;
120
+ /** Section header shown in the sidebar. */
121
+ title: string;
122
+ /** Menu (activity bar container) this view lives in — basename in src/menus/. */
123
+ menu: string;
124
+ /** Webview bundle name under dist/webview/<ui>/. Default: same as id. */
125
+ ui?: string;
126
+ /** Keep DOM alive when hidden. Default: true. */
127
+ retainContext?: boolean;
128
+ /** RPC handlers — receives vscode namespace + extension context. */
129
+ rpc?: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext) => H;
130
+ }
131
+
132
+ export function defineSubpanel<H extends Handlers = Handlers>(def: SubpanelDef<H>): SubpanelDef<H> {
133
+ return def;
134
+ }
135
+
136
+ // --- Status Bar items ---
137
+
138
+ export interface StatusBarDef {
139
+ /** Stable id. Default: file basename. */
140
+ id?: string;
141
+ /** Display text. May include `$(codicon)` syntax. */
142
+ text: string;
143
+ /** Tooltip on hover. */
144
+ tooltip?: string;
145
+ /** Optional codicon, prepended as `$(icon) text` when both present. */
146
+ icon?: CodiconName | (string & {});
147
+ /** Bar side. Default: 'left'. */
148
+ alignment?: 'left' | 'right';
149
+ /** Higher = leftmost on its side. Default: 100. */
150
+ priority?: number;
151
+ /** Command id to run on click (basename in src/commands/, or full vscode command id). */
152
+ command?: string;
153
+ /** Open a panel by id (basename in src/panels/). Takes precedence over `command` when set. */
154
+ panel?: string;
155
+ /** Background color theme key (e.g. 'statusBarItem.warningBackground'). */
156
+ backgroundColor?: string;
157
+ /**
158
+ * Rich markdown tooltip (overrides `tooltip`). Supports command links
159
+ * (`[text](command:ext.foo)`), codicons (`$(rocket)`), and HTML.
160
+ * Rendered on hover. Mimics Copilot/GitLens popup style.
161
+ */
162
+ tooltipMarkdown?: string;
163
+ /**
164
+ * Open a popup menu on click instead of running a single command/panel.
165
+ * Each item runs its `command`, opens its `panel`, or opens its `url`.
166
+ */
167
+ menu?: StatusBarMenuItem[];
168
+ }
169
+
170
+ export interface StatusBarMenuItem {
171
+ /** Display label. May include `$(codicon)`. */
172
+ label: string;
173
+ /** Inline secondary text. */
174
+ description?: string;
175
+ /** Detail line (smaller, below). */
176
+ detail?: string;
177
+ /** Command id (basename in src/commands/) or full vscode command id. */
178
+ command?: string;
179
+ /** Panel id (basename in src/panels/). */
180
+ panel?: string;
181
+ /** External URL. */
182
+ url?: string;
183
+ }
184
+
185
+ export function defineStatusBar(def: StatusBarDef): StatusBarDef {
186
+ return def;
187
+ }
188
+
189
+ // --- Tree Views (data-driven) ---
190
+
191
+ export interface TreeNode {
192
+ /** Display label. */
193
+ label: string;
194
+ /** Stable id, defaults to label. Used for reveal/select. */
195
+ id?: string;
196
+ /** Optional icon. */
197
+ icon?: MenuIcon;
198
+ /** Tooltip on hover. */
199
+ tooltip?: string;
200
+ /** Right-aligned description text. */
201
+ description?: string;
202
+ /** Context value used by `view/item/context` menu entries. */
203
+ contextValue?: string;
204
+ /** Initial state when this node has children. Default: 'collapsed'. */
205
+ collapsed?: 'expanded' | 'collapsed';
206
+ /** Eagerly provided children. If omitted, getChildren(this) is called lazily. */
207
+ children?: TreeNode[];
208
+ /** Click handler — run an arbitrary callback when the node is selected. */
209
+ run?: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext) => unknown | Promise<unknown>;
210
+ /** Click → open a panel by id. */
211
+ panel?: string;
212
+ /** Click → run a command by id. */
213
+ command?: string;
214
+ }
215
+
216
+ export interface TreeViewDef {
217
+ /** Stable id. Default: file basename. */
218
+ id?: string;
219
+ /** Sidebar section header. */
220
+ title: string;
221
+ /** Activity bar container id (menu basename in src/menus/). */
222
+ menu: string;
223
+ /** Show "Collapse All" button. Default: true. */
224
+ showCollapseAll?: boolean;
225
+ /** Initial / refreshed nodes. Called on mount and whenever the view is refreshed. */
226
+ getChildren: (
227
+ parent: TreeNode | undefined,
228
+ vscode: typeof import('vscode'),
229
+ ctx: vscode.ExtensionContext,
230
+ ) => TreeNode[] | Promise<TreeNode[]>;
231
+ }
232
+
233
+ export function defineTreeView(def: TreeViewDef): TreeViewDef {
234
+ return def;
235
+ }
236
+
237
+ // --- Jobs (recurring / event-triggered tasks) ---
238
+
239
+ export type JobSchedule =
240
+ /** Run every <interval>. Accepts ms number or duration string: "30s", "5m", "2h", "1d". */
241
+ | { every: string | number; runOnStart?: boolean }
242
+ /** Run at HH:MM local time, every day. */
243
+ | { dailyAt: string }
244
+ /** Run on a VS Code lifecycle event. */
245
+ | { on: 'startup' | 'saveDocument' | 'openDocument' | 'changeActiveEditor' | 'changeConfig' }
246
+ /** Run on filesystem changes matching a glob (relative to workspace). */
247
+ | { onFile: string };
248
+
249
+ export interface JobDef {
250
+ /** Stable id. Default: file basename. */
251
+ id?: string;
252
+ /** Display label (used in logs + opt. status bar). */
253
+ title: string;
254
+ /** When to run. */
255
+ schedule: JobSchedule;
256
+ /**
257
+ * Skip if last successful run was less than this many ms ago. Stored in
258
+ * `context.globalState` under `vsceasy.job.<id>.lastRun`. Useful for jobs
259
+ * that should at most run every N hours regardless of how often the
260
+ * trigger fires.
261
+ */
262
+ minIntervalMs?: number;
263
+ /** The actual work. Errors are caught and logged — they never crash the host. */
264
+ run: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext) => unknown | Promise<unknown>;
265
+ }
266
+
267
+ export function defineJob(def: JobDef): JobDef {
268
+ return def;
269
+ }
@@ -0,0 +1,13 @@
1
+ export { definePanel, defineCommand, defineMenu, defineStatusBar, defineSubpanel, defineTreeView, defineJob } from './define';
2
+ export type { PanelDef, CommandDef, MenuDef, MenuItem, MenuIcon, StatusBarDef, StatusBarMenuItem, KeybindingDef, SubpanelDef, TreeViewDef, TreeNode, JobDef, JobSchedule, CodiconName } from './define';
3
+ export { bootstrap } from './bootstrap';
4
+ export type { Registry, BootstrapOptions, ActivateHook } from './bootstrap';
5
+ export {
6
+ createRpcClient,
7
+ createRpcServer,
8
+ webviewTransport,
9
+ vscodeApiTransport,
10
+ connectWebview,
11
+ webviewState,
12
+ } from './rpc';
13
+ export type { Transport, RpcClient, Handlers, RpcClientOptions, WebviewApi } from './rpc';
@@ -0,0 +1,214 @@
1
+ // Typed RPC bridge — webview <-> extension.
2
+ // Used by both sides. Transport-agnostic core + thin adapters.
3
+
4
+ export type RpcMessage =
5
+ | { id: string; kind: 'call'; method: string; args: unknown[] }
6
+ | { id: string; kind: 'result'; ok: true; value: unknown }
7
+ | { id: string; kind: 'result'; ok: false; error: { message: string; stack?: string } }
8
+ | { kind: 'event'; topic: string; payload: unknown };
9
+
10
+ export interface Transport {
11
+ send(msg: RpcMessage): void;
12
+ onMessage(handler: (msg: RpcMessage) => void): () => void;
13
+ }
14
+
15
+ // --- Server (extension side) ---
16
+
17
+ /**
18
+ * Loose constraint: an interface with method members. Using `object` (instead
19
+ * of a Record with index signature) lets user-declared interfaces satisfy the
20
+ * constraint without forcing them to declare `[k: string]: any`.
21
+ */
22
+ export type Handlers = object;
23
+
24
+ export function createRpcServer<H extends Handlers>(transport: Transport, handlers: H) {
25
+ const off = transport.onMessage(async (msg) => {
26
+ if (msg.kind !== 'call') return;
27
+ const fn = (handlers as Record<string, (...args: any[]) => any>)[msg.method];
28
+ if (!fn) {
29
+ transport.send({
30
+ id: msg.id,
31
+ kind: 'result',
32
+ ok: false,
33
+ error: { message: `Unknown RPC method: ${msg.method}` },
34
+ });
35
+ return;
36
+ }
37
+ try {
38
+ const value = await fn(...msg.args);
39
+ transport.send({ id: msg.id, kind: 'result', ok: true, value });
40
+ } catch (err: any) {
41
+ transport.send({
42
+ id: msg.id,
43
+ kind: 'result',
44
+ ok: false,
45
+ error: { message: String(err?.message ?? err), stack: err?.stack },
46
+ });
47
+ }
48
+ });
49
+
50
+ return {
51
+ emit(topic: string, payload: unknown) {
52
+ transport.send({ kind: 'event', topic, payload });
53
+ },
54
+ dispose: off,
55
+ };
56
+ }
57
+
58
+ // --- Client (webview side) ---
59
+
60
+ export type RpcClient<H extends Handlers> = {
61
+ [K in keyof H]: H[K] extends (...args: infer A) => infer R
62
+ ? (...args: A) => Promise<Awaited<R>>
63
+ : never;
64
+ } & {
65
+ on(topic: string, handler: (payload: any) => void): () => void;
66
+ };
67
+
68
+ export interface RpcClientOptions {
69
+ /**
70
+ * Max wait (ms) for a call's reply before rejecting with a timeout error.
71
+ * Prevents hangs when the extension host reloads mid-flight during `bun run dev`.
72
+ * Default: 15000. Set to 0 to disable.
73
+ */
74
+ callTimeoutMs?: number;
75
+ }
76
+
77
+ export function createRpcClient<H extends Handlers>(
78
+ transport: Transport,
79
+ opts: RpcClientOptions = {},
80
+ ): RpcClient<H> {
81
+ const callTimeoutMs = opts.callTimeoutMs ?? 15000;
82
+ const pending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void; timer?: ReturnType<typeof setTimeout> }>();
83
+ const listeners = new Map<string, Set<(p: any) => void>>();
84
+
85
+ transport.onMessage((msg) => {
86
+ if (msg.kind === 'result') {
87
+ const p = pending.get(msg.id);
88
+ if (!p) return;
89
+ pending.delete(msg.id);
90
+ if (p.timer) clearTimeout(p.timer);
91
+ if (msg.ok) p.resolve(msg.value);
92
+ else p.reject(Object.assign(new Error(msg.error.message), { stack: msg.error.stack }));
93
+ } else if (msg.kind === 'event') {
94
+ listeners.get(msg.topic)?.forEach((l) => l(msg.payload));
95
+ }
96
+ });
97
+
98
+ let counter = 0;
99
+ const newId = () => `r${++counter}_${Date.now()}`;
100
+
101
+ const proxy = new Proxy({} as any, {
102
+ get(_t, prop: string) {
103
+ if (prop === 'on') {
104
+ return (topic: string, handler: (p: any) => void) => {
105
+ let set = listeners.get(topic);
106
+ if (!set) listeners.set(topic, (set = new Set()));
107
+ set.add(handler);
108
+ return () => set!.delete(handler);
109
+ };
110
+ }
111
+ return (...args: unknown[]) =>
112
+ new Promise((resolve, reject) => {
113
+ const id = newId();
114
+ const entry: { resolve: (v: any) => void; reject: (e: any) => void; timer?: ReturnType<typeof setTimeout> } = { resolve, reject };
115
+ if (callTimeoutMs > 0) {
116
+ entry.timer = setTimeout(() => {
117
+ if (pending.delete(id)) {
118
+ reject(new Error(`RPC \`${prop}\` timed out after ${callTimeoutMs}ms (extension host reloaded?)`));
119
+ }
120
+ }, callTimeoutMs);
121
+ }
122
+ pending.set(id, entry);
123
+ try {
124
+ transport.send({ id, kind: 'call', method: prop, args });
125
+ } catch (err) {
126
+ pending.delete(id);
127
+ if (entry.timer) clearTimeout(entry.timer);
128
+ reject(err);
129
+ }
130
+ });
131
+ },
132
+ });
133
+
134
+ return proxy as RpcClient<H>;
135
+ }
136
+
137
+ // --- Transports ---
138
+
139
+ export function webviewTransport(webview: { postMessage(m: any): any; onDidReceiveMessage: any }): Transport {
140
+ return {
141
+ send: (m) => webview.postMessage(m),
142
+ onMessage: (h) => {
143
+ const sub = webview.onDidReceiveMessage((m: RpcMessage) => h(m));
144
+ return () => sub.dispose();
145
+ },
146
+ };
147
+ }
148
+
149
+ export function vscodeApiTransport(vscode: { postMessage(m: any): void }): Transport {
150
+ return {
151
+ send: (m) => vscode.postMessage(m),
152
+ onMessage: (h) => {
153
+ const listener = (e: MessageEvent) => h(e.data as RpcMessage);
154
+ window.addEventListener('message', listener);
155
+ return () => window.removeEventListener('message', listener);
156
+ },
157
+ };
158
+ }
159
+
160
+ declare global {
161
+ function acquireVsCodeApi(): WebviewApi;
162
+ }
163
+
164
+ export interface WebviewApi {
165
+ postMessage(m: any): void;
166
+ getState(): unknown;
167
+ setState<T>(s: T): T;
168
+ }
169
+
170
+ let _cachedVscode: WebviewApi | null = null;
171
+ function vscodeApi(): WebviewApi {
172
+ // acquireVsCodeApi() may only be called once per webview lifetime.
173
+ if (_cachedVscode) return _cachedVscode;
174
+ return (_cachedVscode = acquireVsCodeApi());
175
+ }
176
+
177
+ /** One-liner for webview: returns a typed RPC client. */
178
+ export function connectWebview<H extends Handlers>(opts?: RpcClientOptions): RpcClient<H> {
179
+ return createRpcClient<H>(vscodeApiTransport(vscodeApi()), opts);
180
+ }
181
+
182
+ /**
183
+ * Typed `vscode.getState() / setState()` wrapper for webviews. State survives
184
+ * panel hide/show, host reloads triggered by `retainContextWhenHidden`, and
185
+ * is the recommended way to persist scroll positions, form data, and selection.
186
+ *
187
+ * Usage:
188
+ * const state = webviewState<{ query: string }>({ query: '' });
189
+ * state.set({ query: 'foo' });
190
+ * const { query } = state.get();
191
+ */
192
+ export function webviewState<T>(defaults: T): {
193
+ get(): T;
194
+ set(next: T | ((prev: T) => T)): T;
195
+ patch(partial: Partial<T>): T;
196
+ } {
197
+ const api = vscodeApi();
198
+ const init = (): T => ({ ...defaults, ...((api.getState() as T | undefined) ?? {}) });
199
+ return {
200
+ get: init,
201
+ set(next) {
202
+ const current = init();
203
+ const value = typeof next === 'function' ? (next as (p: T) => T)(current) : next;
204
+ api.setState(value);
205
+ return value;
206
+ },
207
+ patch(partial) {
208
+ const current = init();
209
+ const value = { ...current, ...partial };
210
+ api.setState(value);
211
+ return value;
212
+ },
213
+ };
214
+ }