@vsceasy/cli 0.1.6 → 0.1.7

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 CHANGED
@@ -41,6 +41,11 @@ All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.
41
41
  - Bumped to **0.1.0** — first signed/tagged release.
42
42
  - Dropped Svelte/Vue/Vanilla mentions from docs and CLI options. Only React UI is supported in this release.
43
43
 
44
+ ### Fixed
45
+ - **`crud add --menu` flag parsing** — passing a raw policy string (`--menu none`, `--menu new:<id>`, `--menu existing:<id>`) non-interactively was mis-mapped to `existing:<literal>`, so `--menu none` failed with `Menu not found: src/menus/none.ts`. The flag forms now work the same as the interactive choices (which was already the documented behavior).
46
+ - **Generated CRUD form wiped on reveal** — the scaffolded form panel re-ran its loader on every focus/visibility change. With no row pending it reset to an empty "New" form, discarding whatever the user was typing (e.g. ticking a boolean then losing focus). The loader now only resets on the initial mount; later reveals adopt a newly-requested edit row but otherwise leave the in-progress form untouched. Re-run `crud add` (or pull the change into existing `*/App.tsx` form panels) to pick up the fix.
47
+ - **Generated CRUD date fields didn't prefill when editing** — `<input type="date">` only accepts a `yyyy-MM-dd` value, but stored dates are ISO strings (or `Date` objects), so editing a row showed the date field blank. The generated form now normalizes through a `toDateInput()` helper. Re-run `crud add` (or add the helper to existing form panels) to pick up the fix.
48
+
44
49
  ## [0.0.1] — 2026-05-21
45
50
 
46
51
  - Initial MVP. React template, typed RPC bridge, file-based registry, CLI generators (`panel`, `command`, `menu`, `rpc`, `statusBar`, `subpanel`), `doctor`, `upgrade`.
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
- # vsceasy
1
+ <p align="center">
2
+ <img src="assets/logo-mark.svg" alt="vsceasy octopus mascot" width="96" height="96" />
3
+ </p>
2
4
 
3
- Build VS Code extensions fast. React UI + typed RPC bridge between extension and webview + zero-config build.
5
+ <h1 align="center">vsceasy</h1>
6
+
7
+ <p align="center">Build VS Code extensions fast. React UI + typed RPC bridge between extension and webview + zero-config build.</p>
4
8
 
5
9
  > Status: v0.1 — React UI. Typed RPC bridge + file-based registry + scaffolding for panels, commands, menus, tree views, subpanels, status bars.
6
10
 
package/dist/bin/cli.js CHANGED
@@ -3826,7 +3826,7 @@ var init_scaffold = __esm(() => {
3826
3826
  });
3827
3827
 
3828
3828
  // src/lib/templatesData.ts
3829
- var TEMPLATES_VERSION = "0.1.6", TEMPLATE_FILES;
3829
+ var TEMPLATES_VERSION = "0.1.7", TEMPLATE_FILES;
3830
3830
  var init_templatesData = __esm(() => {
3831
3831
  TEMPLATE_FILES = {
3832
3832
  "_generators/command/command.ts.tpl": `import { defineCommand } from '../shared/vsceasy';
@@ -4025,21 +4025,35 @@ type FormState = Partial<{{Name}}>;
4025
4025
 
4026
4026
  const emptyForm: FormState = {{emptyFormLiteral}};
4027
4027
 
4028
+ // \`<input type="date">\` only accepts a \`yyyy-MM-dd\` value. Stored dates may be
4029
+ // ISO strings or Date objects, so normalize before binding to the input.
4030
+ function toDateInput(v: unknown): string {
4031
+ if (v == null || v === '') return '';
4032
+ const d = v instanceof Date ? v : new Date(v as string);
4033
+ return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);
4034
+ }
4035
+
4028
4036
  export function App() {
4029
4037
  const [form, setForm] = useState<FormState>(emptyForm);
4030
4038
  const [editingId, setEditingId] = useState<{{Name}}['{{primaryKey}}'] | null>(null);
4031
4039
  const [error, setError] = useState<string | null>(null);
4032
4040
  const [saving, setSaving] = useState(false);
4033
4041
 
4034
- const load = useCallback(async () => {
4035
- // The list stashes the row id before revealing this panel. Pull it (the host
4036
- // clears it after handing it over) and pre-fill the form for editing.
4042
+ const load = useCallback(async (initial: boolean) => {
4043
+ // The list stashes a row id before revealing this panel. Pull it (the host
4044
+ // clears it after handing it over).
4037
4045
  const id = await api.pendingId();
4038
4046
  if (id == null || id === '') {
4039
- setForm(emptyForm);
4040
- setEditingId(null);
4047
+ // No row was requested. On the first mount, start with an empty "new" form.
4048
+ // On later reveals (focus/visibility), DON'T reset — that would wipe a form
4049
+ // the user is busy filling in. Just leave the current state as-is.
4050
+ if (initial) {
4051
+ setForm(emptyForm);
4052
+ setEditingId(null);
4053
+ }
4041
4054
  return;
4042
4055
  }
4056
+ // The list asked to edit a specific row — load it, replacing the current form.
4043
4057
  const row = await api.get(id);
4044
4058
  if (row) {
4045
4059
  setForm(row);
@@ -4048,11 +4062,12 @@ export function App() {
4048
4062
  }, []);
4049
4063
 
4050
4064
  useEffect(() => {
4051
- void load();
4052
- // Webviews retain state when hidden, so re-load whenever the panel is
4053
- // revealed — the list may have asked to edit a different row.
4054
- const onFocus = () => { void load(); };
4055
- const onVisible = () => { if (document.visibilityState === 'visible') void load(); };
4065
+ void load(true);
4066
+ // Webviews retain state when hidden, so re-check on reveal: the list may have
4067
+ // asked to edit a different row. When nothing is pending, \`load\` leaves the
4068
+ // in-progress form untouched (see above).
4069
+ const onFocus = () => { void load(false); };
4070
+ const onVisible = () => { if (document.visibilityState === 'visible') void load(false); };
4056
4071
  window.addEventListener('focus', onFocus);
4057
4072
  document.addEventListener('visibilitychange', onVisible);
4058
4073
  return () => {
@@ -12542,7 +12557,7 @@ function renderInput(field, override) {
12542
12557
  case "boolean":
12543
12558
  return wrap(` <input type="checkbox" checked={!!form.${name}} onChange={(e) => onChange('${name}', e.target.checked as any)} />`);
12544
12559
  case "date":
12545
- return wrap(` <input type="date"${required} value={(form.${name} as any) ?? ''} onChange={(e) => onChange('${name}', e.target.value as any)} />`);
12560
+ return wrap(` <input type="date"${required} value={toDateInput(form.${name})} onChange={(e) => onChange('${name}', e.target.value as any)} />`);
12546
12561
  case "select": {
12547
12562
  const opts = (spec.options ?? []).map((o) => ` <option value=${JSON.stringify(o)}>${escapeJsx(o)}</option>`).join(`
12548
12563
  `);
@@ -12747,14 +12762,16 @@ var init_add23 = __esm(() => {
12747
12762
  const projectRoot = findProjectRoot();
12748
12763
  const templatesRoot = findTemplatesRoot();
12749
12764
  let menuSpec;
12750
- const choice = String(args.menu ?? NONE_SENTINEL2);
12751
- if (choice === NONE_SENTINEL2) {
12765
+ const choice = String(args.menu ?? NONE_SENTINEL2).trim();
12766
+ if (choice === NONE_SENTINEL2 || choice === "none") {
12752
12767
  menuSpec = "none";
12753
12768
  } else if (choice === NEW_SENTINEL) {
12754
12769
  const id = args.newMenuId ? String(args.newMenuId).trim() : await import_cli_maker18.prompt(" new menu id: ");
12755
12770
  if (!id)
12756
12771
  throw new Error("New menu id required.");
12757
12772
  menuSpec = `new:${id}`;
12773
+ } else if (choice.startsWith("new:") || choice.startsWith("existing:")) {
12774
+ menuSpec = choice;
12758
12775
  } else {
12759
12776
  menuSpec = `existing:${choice}`;
12760
12777
  }
@@ -12902,12 +12919,21 @@ var init_cli = __esm(() => {
12902
12919
  import_cli_maker20 = __toESM(require_dist(), 1);
12903
12920
  cli = new import_cli_maker20.CLI("vsceasy", "Build VS Code extensions fast — React UI + typed RPC bridge + zero-config build.", {
12904
12921
  interactive: true,
12905
- version: "0.1.6",
12922
+ version: "0.1.7",
12906
12923
  introAnimation: {
12907
12924
  enabled: true,
12908
12925
  preset: "retro-space",
12909
12926
  title: "vsceasy",
12910
- subtitle: "VS Code Extension Framework"
12927
+ subtitle: "VS Code Extension Framework",
12928
+ asciiArt: [
12929
+ ' .-""""-.',
12930
+ " / o o \\",
12931
+ " | .. |",
12932
+ " \\ '--' /",
12933
+ " /`-.__.-`\\",
12934
+ " _/ /|/||\\|\\ \\_",
12935
+ " `--`-`-``-`-`--`"
12936
+ ]
12911
12937
  },
12912
12938
  defaultCommands: {
12913
12939
  rotatePassphrase: false,
package/dist/index.js CHANGED
@@ -2238,7 +2238,7 @@ var init_upgrade = __esm(() => {
2238
2238
  });
2239
2239
 
2240
2240
  // src/lib/templatesData.ts
2241
- var TEMPLATES_VERSION = "0.1.6", TEMPLATE_FILES;
2241
+ var TEMPLATES_VERSION = "0.1.7", TEMPLATE_FILES;
2242
2242
  var init_templatesData = __esm(() => {
2243
2243
  TEMPLATE_FILES = {
2244
2244
  "_generators/command/command.ts.tpl": `import { defineCommand } from '../shared/vsceasy';
@@ -2437,21 +2437,35 @@ type FormState = Partial<{{Name}}>;
2437
2437
 
2438
2438
  const emptyForm: FormState = {{emptyFormLiteral}};
2439
2439
 
2440
+ // \`<input type="date">\` only accepts a \`yyyy-MM-dd\` value. Stored dates may be
2441
+ // ISO strings or Date objects, so normalize before binding to the input.
2442
+ function toDateInput(v: unknown): string {
2443
+ if (v == null || v === '') return '';
2444
+ const d = v instanceof Date ? v : new Date(v as string);
2445
+ return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);
2446
+ }
2447
+
2440
2448
  export function App() {
2441
2449
  const [form, setForm] = useState<FormState>(emptyForm);
2442
2450
  const [editingId, setEditingId] = useState<{{Name}}['{{primaryKey}}'] | null>(null);
2443
2451
  const [error, setError] = useState<string | null>(null);
2444
2452
  const [saving, setSaving] = useState(false);
2445
2453
 
2446
- const load = useCallback(async () => {
2447
- // The list stashes the row id before revealing this panel. Pull it (the host
2448
- // clears it after handing it over) and pre-fill the form for editing.
2454
+ const load = useCallback(async (initial: boolean) => {
2455
+ // The list stashes a row id before revealing this panel. Pull it (the host
2456
+ // clears it after handing it over).
2449
2457
  const id = await api.pendingId();
2450
2458
  if (id == null || id === '') {
2451
- setForm(emptyForm);
2452
- setEditingId(null);
2459
+ // No row was requested. On the first mount, start with an empty "new" form.
2460
+ // On later reveals (focus/visibility), DON'T reset — that would wipe a form
2461
+ // the user is busy filling in. Just leave the current state as-is.
2462
+ if (initial) {
2463
+ setForm(emptyForm);
2464
+ setEditingId(null);
2465
+ }
2453
2466
  return;
2454
2467
  }
2468
+ // The list asked to edit a specific row — load it, replacing the current form.
2455
2469
  const row = await api.get(id);
2456
2470
  if (row) {
2457
2471
  setForm(row);
@@ -2460,11 +2474,12 @@ export function App() {
2460
2474
  }, []);
2461
2475
 
2462
2476
  useEffect(() => {
2463
- void load();
2464
- // Webviews retain state when hidden, so re-load whenever the panel is
2465
- // revealed — the list may have asked to edit a different row.
2466
- const onFocus = () => { void load(); };
2467
- const onVisible = () => { if (document.visibilityState === 'visible') void load(); };
2477
+ void load(true);
2478
+ // Webviews retain state when hidden, so re-check on reveal: the list may have
2479
+ // asked to edit a different row. When nothing is pending, \`load\` leaves the
2480
+ // in-progress form untouched (see above).
2481
+ const onFocus = () => { void load(false); };
2482
+ const onVisible = () => { if (document.visibilityState === 'visible') void load(false); };
2468
2483
  window.addEventListener('focus', onFocus);
2469
2484
  document.addEventListener('visibilitychange', onVisible);
2470
2485
  return () => {
@@ -6723,7 +6738,7 @@ function renderInput(field, override) {
6723
6738
  case "boolean":
6724
6739
  return wrap(` <input type="checkbox" checked={!!form.${name}} onChange={(e) => onChange('${name}', e.target.checked as any)} />`);
6725
6740
  case "date":
6726
- return wrap(` <input type="date"${required} value={(form.${name} as any) ?? ''} onChange={(e) => onChange('${name}', e.target.value as any)} />`);
6741
+ return wrap(` <input type="date"${required} value={toDateInput(form.${name})} onChange={(e) => onChange('${name}', e.target.value as any)} />`);
6727
6742
  case "select": {
6728
6743
  const opts = (spec.options ?? []).map((o) => ` <option value=${JSON.stringify(o)}>${escapeJsx(o)}</option>`).join(`
6729
6744
  `);
@@ -1,2 +1,2 @@
1
- export declare const TEMPLATES_VERSION = "0.1.6";
1
+ export declare const TEMPLATES_VERSION = "0.1.7";
2
2
  export declare const TEMPLATE_FILES: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vsceasy/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Build VS Code extensions fast — React UI + typed RPC bridge between extension and webview + file-based routing for panels, commands, menus, tree views, and subpanels.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -9,21 +9,35 @@ type FormState = Partial<{{Name}}>;
9
9
 
10
10
  const emptyForm: FormState = {{emptyFormLiteral}};
11
11
 
12
+ // `<input type="date">` only accepts a `yyyy-MM-dd` value. Stored dates may be
13
+ // ISO strings or Date objects, so normalize before binding to the input.
14
+ function toDateInput(v: unknown): string {
15
+ if (v == null || v === '') return '';
16
+ const d = v instanceof Date ? v : new Date(v as string);
17
+ return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);
18
+ }
19
+
12
20
  export function App() {
13
21
  const [form, setForm] = useState<FormState>(emptyForm);
14
22
  const [editingId, setEditingId] = useState<{{Name}}['{{primaryKey}}'] | null>(null);
15
23
  const [error, setError] = useState<string | null>(null);
16
24
  const [saving, setSaving] = useState(false);
17
25
 
18
- const load = useCallback(async () => {
19
- // The list stashes the row id before revealing this panel. Pull it (the host
20
- // clears it after handing it over) and pre-fill the form for editing.
26
+ const load = useCallback(async (initial: boolean) => {
27
+ // The list stashes a row id before revealing this panel. Pull it (the host
28
+ // clears it after handing it over).
21
29
  const id = await api.pendingId();
22
30
  if (id == null || id === '') {
23
- setForm(emptyForm);
24
- setEditingId(null);
31
+ // No row was requested. On the first mount, start with an empty "new" form.
32
+ // On later reveals (focus/visibility), DON'T reset — that would wipe a form
33
+ // the user is busy filling in. Just leave the current state as-is.
34
+ if (initial) {
35
+ setForm(emptyForm);
36
+ setEditingId(null);
37
+ }
25
38
  return;
26
39
  }
40
+ // The list asked to edit a specific row — load it, replacing the current form.
27
41
  const row = await api.get(id);
28
42
  if (row) {
29
43
  setForm(row);
@@ -32,11 +46,12 @@ export function App() {
32
46
  }, []);
33
47
 
34
48
  useEffect(() => {
35
- void load();
36
- // Webviews retain state when hidden, so re-load whenever the panel is
37
- // revealed — the list may have asked to edit a different row.
38
- const onFocus = () => { void load(); };
39
- const onVisible = () => { if (document.visibilityState === 'visible') void load(); };
49
+ void load(true);
50
+ // Webviews retain state when hidden, so re-check on reveal: the list may have
51
+ // asked to edit a different row. When nothing is pending, `load` leaves the
52
+ // in-progress form untouched (see above).
53
+ const onFocus = () => { void load(false); };
54
+ const onVisible = () => { if (document.visibilityState === 'visible') void load(false); };
40
55
  window.addEventListener('focus', onFocus);
41
56
  document.addEventListener('visibilitychange', onVisible);
42
57
  return () => {