@vsceasy/cli 0.1.6 → 0.1.8
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 +11 -0
- package/README.md +8 -2
- package/dist/bin/cli.js +382 -32
- package/dist/commands/groups.d.ts +1 -0
- package/dist/commands/store/add.d.ts +3 -0
- package/dist/index.js +244 -24
- package/dist/lib/store/add.d.ts +14 -0
- package/dist/lib/templatesData.d.ts +1 -1
- package/package.json +1 -1
- package/templates/_generators/crud/formApp.tsx.tpl +25 -10
- package/templates/_generators/helper/db.ts.tpl +35 -1
- package/templates/_generators/store/store.ts.tpl +23 -0
- package/templates/react/src/shared/vsceasy/bootstrap.ts +19 -6
- package/templates/react/src/shared/vsceasy/client.ts +27 -0
- package/templates/react/src/shared/vsceasy/define.ts +34 -4
- package/templates/react/src/shared/vsceasy/index.ts +4 -1
- package/templates/react/src/shared/vsceasy/store.ts +73 -0
|
@@ -6,3 +6,30 @@ export {
|
|
|
6
6
|
webviewState,
|
|
7
7
|
} from './rpc';
|
|
8
8
|
export type { RpcClient, Handlers, Transport, RpcClientOptions, WebviewApi } from './rpc';
|
|
9
|
+
|
|
10
|
+
export { defineStore } from './store';
|
|
11
|
+
export type { Store } from './store';
|
|
12
|
+
|
|
13
|
+
import type { RpcClient, Handlers } from './rpc';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Listen for a change pushed from the host over the RPC event channel, and run a
|
|
17
|
+
* callback when it arrives. This is the webview side of the reactivity model —
|
|
18
|
+
* the host emits with `server.emit(topic, …)` (usually wired via `watch()` /
|
|
19
|
+
* `watchEntity()`), and your visual element reacts here. Returns an unsubscribe
|
|
20
|
+
* function; call it on unmount.
|
|
21
|
+
*
|
|
22
|
+
* const api = connectWebview<TodoStatsViewApi>();
|
|
23
|
+
* // re-read + re-render whenever todos change on the host:
|
|
24
|
+
* const off = listen(api, 'todos:changed', () => refresh());
|
|
25
|
+
*
|
|
26
|
+
* It's a thin, named wrapper over `api.on(topic, handler)` so the place you
|
|
27
|
+
* listen reads clearly in the UI code. The payload (if any) is passed through.
|
|
28
|
+
*/
|
|
29
|
+
export function listen<H extends Handlers>(
|
|
30
|
+
api: RpcClient<H>,
|
|
31
|
+
topic: string,
|
|
32
|
+
handler: (payload?: unknown) => void,
|
|
33
|
+
): () => void {
|
|
34
|
+
return api.on(topic, handler);
|
|
35
|
+
}
|
|
@@ -2,6 +2,18 @@ import type * as vscode from 'vscode';
|
|
|
2
2
|
import type { Handlers } from './rpc';
|
|
3
3
|
import type { CodiconName } from './codiconNames';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Push a change to this webview over the RPC event channel. The webview reacts
|
|
7
|
+
* with `listen(api, topic, …)`. Wire it to a data source with `watch()` /
|
|
8
|
+
* `watchEntity()` inside `rpc()`:
|
|
9
|
+
*
|
|
10
|
+
* rpc: (vscode, ctx, emit) => {
|
|
11
|
+
* watchEntity(Todos, () => emit('todos:changed'));
|
|
12
|
+
* return { stats: () => … };
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
export type RpcEmit = (topic: string, payload?: unknown) => void;
|
|
16
|
+
|
|
5
17
|
export interface PanelDef<H extends Handlers = Handlers> {
|
|
6
18
|
/** Stable id. Default: file basename. Used as command suffix and webview key. */
|
|
7
19
|
id?: string;
|
|
@@ -13,8 +25,11 @@ export interface PanelDef<H extends Handlers = Handlers> {
|
|
|
13
25
|
column?: 'active' | 'beside' | 'one' | 'two' | 'three';
|
|
14
26
|
/** Keep DOM alive when hidden. Default: true. */
|
|
15
27
|
retainContext?: boolean;
|
|
16
|
-
/**
|
|
17
|
-
|
|
28
|
+
/**
|
|
29
|
+
* RPC handlers — receives the vscode namespace, the extension context, and
|
|
30
|
+
* `emit` for pushing change events to this webview (see {@link RpcEmit}).
|
|
31
|
+
*/
|
|
32
|
+
rpc?: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext, emit: RpcEmit) => H;
|
|
18
33
|
/** Optional command palette entry that opens this panel. Default: true. */
|
|
19
34
|
command?:
|
|
20
35
|
| boolean
|
|
@@ -125,8 +140,11 @@ export interface SubpanelDef<H extends Handlers = Handlers> {
|
|
|
125
140
|
ui?: string;
|
|
126
141
|
/** Keep DOM alive when hidden. Default: true. */
|
|
127
142
|
retainContext?: boolean;
|
|
128
|
-
/**
|
|
129
|
-
|
|
143
|
+
/**
|
|
144
|
+
* RPC handlers — receives the vscode namespace, the extension context, and
|
|
145
|
+
* `emit` for pushing change events to this webview (see {@link RpcEmit}).
|
|
146
|
+
*/
|
|
147
|
+
rpc?: (vscode: typeof import('vscode'), ctx: vscode.ExtensionContext, emit: RpcEmit) => H;
|
|
130
148
|
}
|
|
131
149
|
|
|
132
150
|
export function defineSubpanel<H extends Handlers = Handlers>(def: SubpanelDef<H>): SubpanelDef<H> {
|
|
@@ -228,6 +246,18 @@ export interface TreeViewDef {
|
|
|
228
246
|
vscode: typeof import('vscode'),
|
|
229
247
|
ctx: vscode.ExtensionContext,
|
|
230
248
|
) => TreeNode[] | Promise<TreeNode[]>;
|
|
249
|
+
/**
|
|
250
|
+
* Keep the tree live. Receives `refresh` — call it (directly or as a callback)
|
|
251
|
+
* to re-run `getChildren`. Subscribe to a data source here so the tree updates
|
|
252
|
+
* itself; return an unsubscribe to clean up.
|
|
253
|
+
*
|
|
254
|
+
* watch: (refresh) => watchEntity(Todos, refresh),
|
|
255
|
+
*/
|
|
256
|
+
watch?: (
|
|
257
|
+
refresh: () => void,
|
|
258
|
+
vscode: typeof import('vscode'),
|
|
259
|
+
ctx: vscode.ExtensionContext,
|
|
260
|
+
) => (() => void) | void;
|
|
231
261
|
}
|
|
232
262
|
|
|
233
263
|
export function defineTreeView(def: TreeViewDef): TreeViewDef {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
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';
|
|
2
|
+
export type { PanelDef, CommandDef, MenuDef, MenuItem, MenuIcon, StatusBarDef, StatusBarMenuItem, KeybindingDef, SubpanelDef, TreeViewDef, TreeNode, JobDef, JobSchedule, CodiconName, RpcEmit } from './define';
|
|
3
3
|
export { bootstrap } from './bootstrap';
|
|
4
4
|
export type { Registry, BootstrapOptions, ActivateHook } from './bootstrap';
|
|
5
5
|
export {
|
|
@@ -11,3 +11,6 @@ export {
|
|
|
11
11
|
webviewState,
|
|
12
12
|
} from './rpc';
|
|
13
13
|
export type { Transport, RpcClient, Handlers, RpcClientOptions, WebviewApi } from './rpc';
|
|
14
|
+
export { defineStore, watch } from './store';
|
|
15
|
+
export type { Store, Watchable } from './store';
|
|
16
|
+
export { listen } from './client';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive stores — a tiny, framework-agnostic observable value.
|
|
3
|
+
*
|
|
4
|
+
* A store holds one value and notifies subscribers when it changes. It's the
|
|
5
|
+
* non-ORM half of the reactivity model: use it for arbitrary state (a counter,
|
|
6
|
+
* a flag, a selection) that a visual element should track.
|
|
7
|
+
*
|
|
8
|
+
* const counter = defineStore(0);
|
|
9
|
+
* counter.subscribe((v) => console.log('now', v));
|
|
10
|
+
* counter.set(1); // logs: now 1
|
|
11
|
+
* counter.update((n) => n + 1);
|
|
12
|
+
*
|
|
13
|
+
* Pair it with `watch()` on the host to push changes to a webview, and
|
|
14
|
+
* `listen()` on the webview to react — see those helpers below and in client.ts.
|
|
15
|
+
*/
|
|
16
|
+
export interface Store<T> {
|
|
17
|
+
/** Read the current value. */
|
|
18
|
+
get(): T;
|
|
19
|
+
/** Replace the value and notify subscribers (no-op if `Object.is`-equal). */
|
|
20
|
+
set(next: T): void;
|
|
21
|
+
/** Derive the next value from the current one, then `set` it. */
|
|
22
|
+
update(fn: (current: T) => T): void;
|
|
23
|
+
/** Subscribe to changes. Returns an unsubscribe function. */
|
|
24
|
+
subscribe(cb: (value: T) => void): () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function defineStore<T>(initial: T): Store<T> {
|
|
28
|
+
let value = initial;
|
|
29
|
+
const subs = new Set<(value: T) => void>();
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
get: () => value,
|
|
33
|
+
set(next) {
|
|
34
|
+
if (Object.is(value, next)) return;
|
|
35
|
+
value = next;
|
|
36
|
+
subs.forEach((cb) => {
|
|
37
|
+
try { cb(value); } catch { /* a bad subscriber must not break a set */ }
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
update(fn) {
|
|
41
|
+
this.set(fn(value));
|
|
42
|
+
},
|
|
43
|
+
subscribe(cb) {
|
|
44
|
+
subs.add(cb);
|
|
45
|
+
return () => { subs.delete(cb); };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Anything that can be watched: it exposes a `subscribe(cb)` returning an
|
|
52
|
+
* unsubscribe function. Stores satisfy this directly. (ORM entities are watched
|
|
53
|
+
* with `watchEntity` from your generated `db.ts`, which has the same shape.)
|
|
54
|
+
*/
|
|
55
|
+
export interface Watchable {
|
|
56
|
+
subscribe(cb: (...args: any[]) => void): () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Bridge a watchable source to a side-effect — typically an RPC emit that pushes
|
|
61
|
+
* the change to a subscribed webview. Runs `effect` on every change. Returns an
|
|
62
|
+
* unsubscribe function; register it on the panel's `ctx.subscriptions` (wrapped
|
|
63
|
+
* in `{ dispose }`) so it's cleaned up when the extension deactivates.
|
|
64
|
+
*
|
|
65
|
+
* // host side, in a panel's rpc():
|
|
66
|
+
* watch(badgeCount, () => server.emit('badge:changed'));
|
|
67
|
+
*
|
|
68
|
+
* For ORM entities, use `watchEntity(Todos, () => server.emit('todos:changed'))`
|
|
69
|
+
* from your generated db.ts — same idea, same return.
|
|
70
|
+
*/
|
|
71
|
+
export function watch(source: Watchable, effect: () => void): () => void {
|
|
72
|
+
return source.subscribe(() => effect());
|
|
73
|
+
}
|