create-grist-widget 0.3.26 → 0.3.28

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/README.md CHANGED
@@ -21,13 +21,14 @@ dependencies automatically, detecting whichever package manager you invoked this
21
21
  what's available — or just run `npm create grist-widget` with no name for an interactive
22
22
  prompt that asks for both.
23
23
 
24
- ## The four commands
24
+ ## The five commands
25
25
 
26
26
  | Command | What it does |
27
27
  | --- | --- |
28
28
  | `create <name> [--example <id>]` | Scaffold a new widget into `./<name>` (the command above, or run explicitly) |
29
29
  | `init [--example <id>]` | Scaffold into the **current** directory instead of a new one — for an already-cloned or agent-initialized repo |
30
30
  | `use <example-id>` | Swap the current project's widget for a different example |
31
+ | `upgrade` | Bring an existing widget's template files up to this version's template |
31
32
  | `list [--json]` | Print the available starting examples |
32
33
 
33
34
  Common flags for `create`/`init`: `--yes` skips interactive prompts and uses
@@ -52,7 +53,7 @@ npx create-grist-widget init --example form-edit
52
53
  ```
53
54
 
54
55
  Files that already exist and are identical to the template are left untouched; the four
55
- identity-bearing files (`package.json`, `README.md`, `index.html`, `src/App.tsx`) get the
56
+ identity-bearing files (`package.json`, `README.md`, `index.html`, `src/app/App.tsx`) get the
56
57
  usual name/title substitution even if present. Anything else that already exists **and**
57
58
  differs from the template's own copy stops the run with a list of the conflicting paths —
58
59
  pass `--force` to overwrite anyway. If `.git` already exists, `init` never auto-commits;
@@ -61,18 +62,35 @@ initial commit exactly like `create` does.
61
62
 
62
63
  ### `use` — swapping to a different example later
63
64
 
64
- Changed your mind about which example to start from, or want to compare two? `use` swaps
65
- `src/App.tsx` (and any example-specific files) for a different one, in place:
65
+ Changed your mind about which example to start from, or want to compare two? `use`
66
+ replaces the widget, `src/app/`, with a different example:
66
67
 
67
68
  ```bash
68
69
  create-grist-widget use task-board
69
70
  ```
70
71
 
71
- It only ever touches `src/**` — never `main.tsx`, `vite.config.ts`, or the deploy
72
- workflow — and it requires a clean git working tree first (`git status --porcelain`
72
+ It touches nothing else but the `src/components/ui/` primitives and dependencies the example
73
+ needs — never `main.tsx`, `vite.config.ts`, or the deploy workflow — and it requires a clean git working tree first (`git status --porcelain`
73
74
  empty), so the swap is always a single reviewable, revertible diff. Commit or stash first
74
75
  if you have uncommitted changes; `use` refuses to run otherwise.
75
76
 
77
+ ### `upgrade` — picking up template changes
78
+
79
+ Inside a widget, on `dev` with nothing uncommitted, after updating its `create-grist-widget`
80
+ dependency:
81
+
82
+ ```bash
83
+ pnpm exec create-grist-widget upgrade
84
+ ```
85
+
86
+ It rewrites the template's files (`src/main.tsx`, the workflows, the build configuration) to
87
+ this version's copies, deletes the ones earlier templates wrote that are gone now, merges the
88
+ template's scripts and dependency versions into `package.json`, and drops pnpm age exceptions
89
+ that are old enough not to matter. Nothing of the widget's own is touched (`src/app/` and its
90
+ smoke test, `src/components/ui/`, `src/index.css`, `README.md`…). It refuses to
91
+ run on `main`, with uncommitted changes, or on a widget still in `src/App.tsx`. Details:
92
+ [Upgrading a widget](https://gristwidgets.com/docs/cli/upgrading).
93
+
76
94
  ### `list` — what's available
77
95
 
78
96
  ```bash
@@ -80,7 +98,7 @@ create-grist-widget list # id / title / description, one per line
80
98
  create-grist-widget list --json # the same, as JSON
81
99
  ```
82
100
 
83
- > **Note:** `create`, `init`, `use`, and `list` are themselves valid widget names — a bare
101
+ > **Note:** `create`, `init`, `use`, `upgrade`, and `list` are themselves valid widget names — a bare
84
102
  > `create-grist-widget list` runs the `list` command, not "scaffold a widget named list."
85
103
  > Use the explicit form (`create-grist-widget create list`) for a widget with one of these
86
104
  > names.
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // create-grist-widget — scaffold a Grist custom widget, fully offline.
3
3
  //
4
- // Four verbs (batch H, docs/planning/2026-08-execution-plan.md §98-105):
4
+ // Five verbs (batch H, docs/planning/2026-08-execution-plan.md §98-105, and
5
+ // T2 of docs/planning/2026-11-01-widget-lifecycle-plan.md):
5
6
  // create <name> [--example <id>] scaffold a new widget into ./<name>
6
7
  // init [--example <id>] scaffold into the current directory
7
- // use <example-id> swap the current project's widget
8
+ // use <example-id> swap the current project's widget (src/app/)
9
+ // upgrade bring the template's files up to this CLI's
8
10
  // list [--json] print the available starting examples
9
11
  //
10
12
  // `create`/`init`/`use` share one compose engine — see
@@ -13,7 +15,8 @@
13
15
  // scripts/build-template.mjs alongside the template itself, along with
14
16
  // examples/, ui-source/, and manifest.json — see that script for why).
15
17
  // Nothing here ever hits the network: everything list/use/create --example
16
- // need already shipped in the tarball.
18
+ // need already shipped in the tarball. The one exception is `upgrade`, which
19
+ // asks the npm registry when versions in pnpm's age exceptions were published.
17
20
 
18
21
  import {
19
22
  cpSync,
@@ -33,6 +36,7 @@ import { createComposer } from "../vendor/compose-example.mjs"
33
36
  import { checkGitGate } from "../src/git-gate.mjs"
34
37
  import { addedAgeExceptions } from "../src/age-exceptions.mjs"
35
38
  import { installPlan } from "../src/package-manager.mjs"
39
+ import { runUpgrade, upgradeRefusal } from "../src/upgrade.mjs"
36
40
 
37
41
  const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
38
42
  const TEMPLATE_DIR = path.join(PKG_ROOT, "template")
@@ -87,7 +91,9 @@ Usage:
87
91
  npm create grist-widget <name> [options] Scaffold into ./<name>
88
92
  create-grist-widget create <name> [options] Same as above, explicit
89
93
  create-grist-widget init [options] Scaffold into the current directory
90
- create-grist-widget use <example-id> Swap the current project's widget
94
+ create-grist-widget use <example-id> Replace the current project's widget (src/app/)
95
+ create-grist-widget upgrade Bring the template's files up to this CLI's
96
+ (run it on dev, in a widget: pnpm exec create-grist-widget upgrade)
91
97
  create-grist-widget list [--json] List available starting examples
92
98
 
93
99
  Options (create / init):
@@ -104,7 +110,7 @@ Options (create / init):
104
110
  --help, -h Show this help
105
111
  --version, -v Show this CLI's version
106
112
 
107
- Note: "create", "init", "use", and "list" are themselves valid widget names.
113
+ Note: "create", "init", "use", "upgrade", and "list" are themselves valid widget names.
108
114
  A bare "create-grist-widget list" runs the list command, not "scaffold a
109
115
  widget named list" — use "create-grist-widget create list" for that.`)
110
116
  }
@@ -116,7 +122,7 @@ function applyIdentityEdits(target, rawName) {
116
122
  ["package.json", [["grist-widget-template-vite", rawName]]],
117
123
  ["README.md", [["grist-widget-template-vite", rawName]]],
118
124
  ["index.html", [["<title>Grist Widget</title>", `<title>${title}</title>`]]],
119
- ["src/App.tsx", [['title: "Grist Widget Template",', `title: "${title}",`]]],
125
+ ["src/app/App.tsx", [['title: "Grist Widget Template",', `title: "${title}",`]]],
120
126
  ]
121
127
  for (const [file, subs] of edits) {
122
128
  const p = path.join(target, file)
@@ -223,7 +229,7 @@ function printAgeExceptionsNote(ageExceptions) {
223
229
  `\nNote: ${ageExceptions.join(", ")} came out less than 24 hours ago. pnpm only installs versions` +
224
230
  "\nthat recent when told to, so it listed them under minimumReleaseAgeExclude in pnpm-workspace.yaml." +
225
231
  "\nThat entry is committed: without it, every later install (yours and CI's) rejects the lockfile." +
226
- "\nIt only covers the SDK released together with this CLI, and is moot once the version is a day old."
232
+ "\nIt only covers the SDK and CLI versions this scaffold was made with, and is moot once a day old."
227
233
  )
228
234
  }
229
235
 
@@ -393,7 +399,7 @@ async function cmdInit(argv) {
393
399
  // common case, a pristine "Use this template" clone); anything else that
394
400
  // already exists and differs from the template's own pristine copy blocks
395
401
  // the run, listed, unless --force.
396
- const IDENTITY_FILES = new Set(["package.json", "README.md", "index.html", "src/App.tsx"])
402
+ const IDENTITY_FILES = new Set(["package.json", "README.md", "index.html", "src/app/App.tsx"])
397
403
  const templateFiles = listFilesRecursive(TEMPLATE_DIR)
398
404
  if (!args.force) {
399
405
  const conflicts = []
@@ -470,11 +476,21 @@ function cmdUse(argv) {
470
476
  }
471
477
 
472
478
  const cwd = process.cwd()
479
+ // Scaffolds older than the src/app/ layout keep their widget in src/, and
480
+ // their main.tsx imports ./App.tsx: an example written to src/app/ would
481
+ // never show.
482
+ if (existsSync(path.join(cwd, "src", "App.tsx"))) {
483
+ console.error(
484
+ "use: this project keeps its widget in src/App.tsx. Widgets now live in src/app/: move it there first" +
485
+ " (see https://gristwidgets.com/docs/cli/upgrading), then run use again."
486
+ )
487
+ process.exit(1)
488
+ }
473
489
  const gate = checkGitGate(cwd)
474
490
  if (!gate.ok) {
475
491
  if (gate.reason === "no-repo") {
476
492
  console.error(
477
- 'use: this command overwrites src/App.tsx and must run inside a git repository so the change is easy to review and undo. Run "git init" first.'
493
+ 'use: this command replaces src/app/ and must run inside a git repository so the change is easy to review and undo. Run "git init" first.'
478
494
  )
479
495
  } else {
480
496
  console.error(
@@ -493,6 +509,105 @@ function cmdUse(argv) {
493
509
  console.log(" pnpm dev")
494
510
  }
495
511
 
512
+ // ---------------------------------------------------------------------------
513
+ // upgrade
514
+ // ---------------------------------------------------------------------------
515
+
516
+ const CLI_CHANGELOG_URL =
517
+ "https://github.com/ArthurBlanchon/grist-widget-sdk/blob/main/packages/create-grist-widget/CHANGELOG.md"
518
+
519
+ /** When `name@version` was published, from the registry `npm` is configured for in `cwd`; null if unknown. */
520
+ function npmPublishTimes(cwd) {
521
+ const cache = new Map()
522
+ return (name, version) => {
523
+ if (!cache.has(name)) {
524
+ let times = null
525
+ try {
526
+ const out = runCommand("npm", ["view", name, "time", "--json"], {
527
+ cwd,
528
+ encoding: "utf8",
529
+ stdio: ["ignore", "pipe", "ignore"],
530
+ timeout: 30_000,
531
+ })
532
+ times = JSON.parse(out)
533
+ } catch {
534
+ // Offline, or not on that registry: the entry stays.
535
+ }
536
+ cache.set(name, times)
537
+ }
538
+ const at = cache.get(name)?.[version]
539
+ return at ? new Date(at) : null
540
+ }
541
+ }
542
+
543
+ function printList(title, items) {
544
+ if (items.length === 0) return
545
+ console.log(`\n${title}`)
546
+ for (const item of items) console.log(` ${item}`)
547
+ }
548
+
549
+ function cmdUpgrade(argv) {
550
+ parseArgs(argv)
551
+ const cwd = process.cwd()
552
+ const refusal = upgradeRefusal(cwd, { cliVersion: cliPkg.version })
553
+ if (refusal) {
554
+ console.error(refusal.message)
555
+ process.exit(1)
556
+ }
557
+
558
+ const report = runUpgrade({
559
+ cwd,
560
+ templateDir: TEMPLATE_DIR,
561
+ cliVersion: cliPkg.version,
562
+ publishedAt: npmPublishTimes(cwd),
563
+ })
564
+ const changed =
565
+ report.written.length +
566
+ report.deleted.length +
567
+ report.tsconfigs.length +
568
+ report.packageChanges.length +
569
+ report.ageRemoved.length >
570
+ 0
571
+
572
+ const from = report.fromVersion && report.fromVersion !== cliPkg.version ? ` (from ${report.fromVersion})` : ""
573
+ if (!changed) {
574
+ console.log(`Already on create-grist-widget ${cliPkg.version}'s template: nothing to change.`)
575
+ } else {
576
+ console.log(`Upgraded to create-grist-widget ${cliPkg.version}'s template${from}.`)
577
+ }
578
+ printList("Template files rewritten:", report.written)
579
+ printList("Deleted (earlier templates wrote them, nothing uses them now):", report.deleted)
580
+ printList(
581
+ "Kept: earlier templates wrote these, but a file of yours still refers to them (delete them once it doesn't):",
582
+ report.keptRetired.map(({ file, by }) => `${file} (by ${by.join(", ")})`)
583
+ )
584
+ printList(
585
+ "Switched to grist-widget-sdk's compiler settings, keeping these types:",
586
+ report.tsconfigs.map(({ file, types }) => `${file} (types: ${types.join(", ") || "none"})`)
587
+ )
588
+ printList(
589
+ "Not switched to grist-widget-sdk's compiler settings (could not be read; do it by hand):",
590
+ report.tsconfigsUnreadable
591
+ )
592
+ printList("package.json:", report.packageChanges)
593
+ printList("pnpm age exceptions removed (old enough now that pnpm no longer needs them):", report.ageRemoved)
594
+ printList("pnpm age exceptions kept (their publish time could not be looked up):", report.ageUnknown)
595
+ if (!changed) return
596
+
597
+ console.log("\nNext:")
598
+ // Only what pnpm-lock.yaml records: dependencies, packageManager, age exceptions.
599
+ const lockfileChanges = report.packageChanges.filter((c) => /^(dependencies|devDependencies|packageManager)\b/.test(c))
600
+ if (lockfileChanges.length || report.ageRemoved.length) {
601
+ console.log(" pnpm install # update pnpm-lock.yaml")
602
+ }
603
+ console.log(" pnpm typecheck && pnpm lint && pnpm test")
604
+ console.log(" git diff # review, then commit and push dev")
605
+ console.log(
606
+ `\nAnything a template change needs beyond these files is in the changelog entries${from ? ` after ${report.fromVersion}` : ""}:` +
607
+ `\n ${CLI_CHANGELOG_URL}`
608
+ )
609
+ }
610
+
496
611
  // ---------------------------------------------------------------------------
497
612
  // list
498
613
  // ---------------------------------------------------------------------------
@@ -534,6 +649,7 @@ async function main() {
534
649
  }
535
650
  if (first === "list") return cmdList(rest)
536
651
  if (first === "use") return cmdUse(rest)
652
+ if (first === "upgrade") return cmdUpgrade(rest)
537
653
  if (first === "init") return cmdInit(rest)
538
654
  if (first === "create") return cmdCreate(rest)
539
655
  // Bare invocation — `npm create grist-widget my-widget` (npm's `create`
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ // grist-widget-deploy — the GitHub Pages deploy steps a widget's
3
+ // .github/workflows/deploy.yml runs (plan, place, remove, finalize,
4
+ // reset-branches). See src/deploy.mjs for what each one does.
5
+ //
6
+ // The publish job runs this next to a `contents: write` token, after an
7
+ // install with --ignore-scripts, so this file and src/deploy.mjs must import
8
+ // node builtins only: no other dependency code may run there
9
+ // (tests/deploy-imports.test.mjs).
10
+
11
+ import {
12
+ finalize,
13
+ placeTarget,
14
+ plan,
15
+ removeDevDir,
16
+ resetBranchesIfFirstRelease,
17
+ } from "../src/deploy.mjs"
18
+
19
+ function parseArgs(argv) {
20
+ const out = { _: [] }
21
+ for (let i = 0; i < argv.length; i++) {
22
+ const a = argv[i]
23
+ if (a.startsWith("--")) {
24
+ const key = a.slice(2)
25
+ const next = argv[i + 1]
26
+ if (next === undefined || next.startsWith("--")) out[key] = true
27
+ else {
28
+ out[key] = next
29
+ i++
30
+ }
31
+ } else out._.push(a)
32
+ }
33
+ return out
34
+ }
35
+
36
+ const [cmd, ...rest] = process.argv.slice(2)
37
+ const args = parseArgs(rest)
38
+
39
+ switch (cmd) {
40
+ case "plan": {
41
+ const result = plan(args.site, args.repo, args.event, args.ref, {
42
+ force: !!args.force,
43
+ })
44
+ process.stdout.write(JSON.stringify(result))
45
+ break
46
+ }
47
+ case "place": {
48
+ const res = placeTarget({
49
+ siteDir: args.site,
50
+ channel: args.channel,
51
+ version: args.version,
52
+ distDir: args.dist,
53
+ sha: args.sha,
54
+ ref: args.ref,
55
+ repo: args.repo,
56
+ })
57
+ console.log("placed:", JSON.stringify(res))
58
+ break
59
+ }
60
+ case "remove": {
61
+ const res = removeDevDir(args.site)
62
+ console.log("remove:", JSON.stringify(res))
63
+ break
64
+ }
65
+ case "finalize": {
66
+ const res = finalize({
67
+ siteDir: args.site,
68
+ updateVersions: !!args["update-versions"],
69
+ push: !!args.push,
70
+ commitMessage: args["commit-message"],
71
+ repo: args.repo,
72
+ })
73
+ console.log("finalize:", JSON.stringify(res))
74
+ break
75
+ }
76
+ case "reset-branches": {
77
+ const res = resetBranchesIfFirstRelease(
78
+ args["repo-dir"] || ".",
79
+ args.repo,
80
+ undefined,
81
+ args["owner-repo"] || process.env.GITHUB_REPOSITORY
82
+ )
83
+ console.log("reset-branches:", JSON.stringify(res))
84
+ break
85
+ }
86
+ default:
87
+ console.error(
88
+ `grist-widget-deploy: unknown subcommand "${cmd || "(none)"}". ` +
89
+ "Expected plan, place, remove, finalize or reset-branches."
90
+ )
91
+ process.exit(2)
92
+ }
@@ -7,7 +7,8 @@ import {
7
7
  } from "grist-widget-sdk"
8
8
 
9
9
  export const GRIST_OPTIONS: UseGristOptions = {
10
- requiredAccess: "read table",
10
+ // fetchTableRows and fetchRow read any table, which Grist only allows with full document access.
11
+ requiredAccess: "full",
11
12
  }
12
13
 
13
14
  export const WIDGET_METADATA = {
@@ -10,7 +10,8 @@ import {
10
10
  type TaskRow = { Title: string; Due: Date | null }
11
11
 
12
12
  export const GRIST_OPTIONS: UseGristOptions = {
13
- requiredAccess: "read table",
13
+ // fetchTable reads any table, which Grist only allows with full document access.
14
+ requiredAccess: "full",
14
15
  suppressAlerts: ["section-not-linked"],
15
16
  }
16
17
 
package/manifest.json CHANGED
@@ -134,7 +134,7 @@
134
134
  "id": "reads-demo",
135
135
  "title": "Reads demo",
136
136
  "description": "listTables, fetchTableRows, fetchRow, fetchSelectedRecord.",
137
- "requiredAccess": "read table",
137
+ "requiredAccess": "full",
138
138
  "columns": [],
139
139
  "allowSelectBy": false,
140
140
  "hasCustomOptions": false,
@@ -160,7 +160,7 @@
160
160
  "id": "safe-parse",
161
161
  "title": "Safe parse",
162
162
  "description": "safeParseGristTableData with per-cell issues.",
163
- "requiredAccess": "read table",
163
+ "requiredAccess": "full",
164
164
  "columns": [],
165
165
  "allowSelectBy": false,
166
166
  "hasCustomOptions": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-grist-widget",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
4
4
  "description": "Scaffold a new Grist custom widget from the grist-widget-sdk Vite template",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,7 +8,8 @@
8
8
  "node": "^22.22.0 || >=24.15.0"
9
9
  },
10
10
  "bin": {
11
- "create-grist-widget": "./bin/create-grist-widget.mjs"
11
+ "create-grist-widget": "./bin/create-grist-widget.mjs",
12
+ "grist-widget-deploy": "./bin/grist-widget-deploy.mjs"
12
13
  },
13
14
  "files": [
14
15
  "bin",
@@ -1,6 +1,11 @@
1
- #!/usr/bin/env node
2
1
  // Deploy publisher for GitHub Pages (gh-pages branch), single-widget repo.
3
2
  //
3
+ // Shipped in create-grist-widget and run through its `grist-widget-deploy`
4
+ // bin (bin/grist-widget-deploy.mjs) by a widget's .github/workflows/deploy.yml,
5
+ // so a fix reaches every widget as a dependency update. The publish job runs
6
+ // it next to a `contents: write` token: it imports node builtins only, which
7
+ // tests/deploy-imports.test.mjs enforces.
8
+ //
4
9
  // Adapted from the grist-widget-sdk monorepo's scripts/deploy/publish.mjs —
5
10
  // same two channels, minus the multi-widget folder loop and manifest.json
6
11
  // (there's only one widget here, so nothing to catalog):
@@ -15,7 +20,7 @@
15
20
  // - mutable /<repo>/dev/ (+ version.json + self-reload)
16
21
  //
17
22
  // Design notes: dependency-free (node builtins only). Pure helpers are
18
- // exported for testing; the CLI is a thin dispatch at the bottom.
23
+ // exported for testing; the CLI is a thin dispatch in bin/grist-widget-deploy.mjs.
19
24
  //
20
25
  // Subcommands:
21
26
  // plan --site <dir> --repo <name> --event <push|workflow_dispatch>
@@ -347,7 +352,9 @@ function git(args, cwd) {
347
352
  function sleep(ms) {
348
353
  // synchronous backoff (deploy step is not latency-sensitive)
349
354
  const end = Date.now() + ms
350
- while (Date.now() < end) {}
355
+ while (Date.now() < end) {
356
+ // busy-wait: the retry loop runs synchronously
357
+ }
351
358
  }
352
359
 
353
360
  /** Regenerate versions.json (release runs only), then commit + push with rebase-retry. */
@@ -457,6 +464,9 @@ function identityEdits({ packageName, title }) {
457
464
  ["package.json", [["grist-widget-template-vite", packageName]]],
458
465
  ["README.md", [["grist-widget-template-vite", packageName]]],
459
466
  ["index.html", [["<title>Grist Widget</title>", `<title>${title}</title>`]]],
467
+ ["src/app/App.tsx", [['title: "Grist Widget Template",', `title: "${title}",`]]],
468
+ // Where the widget lived before the src/app/ layout: a repo copied from
469
+ // an older template repository still has it there.
460
470
  ["src/App.tsx", [['title: "Grist Widget Template",', `title: "${title}",`]]],
461
471
  ]
462
472
  }
@@ -572,81 +582,3 @@ export function resetBranchesIfFirstRelease(
572
582
  git(["push", "--force", "origin", "HEAD:refs/heads/dev"], repoDir)
573
583
  return { deletedBranches, devReset: true, versionReset, identityReset, renamedFiles, installUrl }
574
584
  }
575
-
576
- // ----------------------------------------------------------------------------
577
- // CLI
578
- // ----------------------------------------------------------------------------
579
-
580
- function parseArgs(argv) {
581
- const out = { _: [] }
582
- for (let i = 0; i < argv.length; i++) {
583
- const a = argv[i]
584
- if (a.startsWith("--")) {
585
- const key = a.slice(2)
586
- const next = argv[i + 1]
587
- if (next === undefined || next.startsWith("--")) out[key] = true
588
- else { out[key] = next; i++ }
589
- } else out._.push(a)
590
- }
591
- return out
592
- }
593
-
594
- function main() {
595
- const [cmd, ...rest] = process.argv.slice(2)
596
- const args = parseArgs(rest)
597
-
598
- switch (cmd) {
599
- case "plan": {
600
- const result = plan(args.site, args.repo, args.event, args.ref, {
601
- force: !!args.force,
602
- })
603
- process.stdout.write(JSON.stringify(result))
604
- break
605
- }
606
- case "place": {
607
- const res = placeTarget({
608
- siteDir: args.site,
609
- channel: args.channel,
610
- version: args.version,
611
- distDir: args.dist,
612
- sha: args.sha,
613
- ref: args.ref,
614
- repo: args.repo,
615
- })
616
- console.log("placed:", JSON.stringify(res))
617
- break
618
- }
619
- case "remove": {
620
- const res = removeDevDir(args.site)
621
- console.log("remove:", JSON.stringify(res))
622
- break
623
- }
624
- case "finalize": {
625
- const res = finalize({
626
- siteDir: args.site,
627
- updateVersions: !!args["update-versions"],
628
- push: !!args.push,
629
- commitMessage: args["commit-message"],
630
- repo: args.repo,
631
- })
632
- console.log("finalize:", JSON.stringify(res))
633
- break
634
- }
635
- case "reset-branches": {
636
- const res = resetBranchesIfFirstRelease(
637
- args["repo-dir"] || ".",
638
- args.repo,
639
- undefined,
640
- args["owner-repo"] || process.env.GITHUB_REPOSITORY,
641
- )
642
- console.log("reset-branches:", JSON.stringify(res))
643
- break
644
- }
645
- default:
646
- console.error(`unknown subcommand: ${cmd || "(none)"}`)
647
- process.exit(2)
648
- }
649
- }
650
-
651
- // Only run the CLI when executed directly (not when imported by tests).
652
- if (import.meta.url === `file://${process.argv[1]}`) main()
package/src/git-gate.mjs CHANGED
@@ -1,5 +1,5 @@
1
- // The safety gate `use <example-id>` rests on: overlayExampleSrc overwrites
2
- // src/App.tsx (and any other example-specific files) in place, so the only
1
+ // The safety gate `use <example-id>` rests on: overlayExampleSrc replaces
2
+ // src/app/ (the widget's own folder) with the example's files, so the only
3
3
  // thing that makes that recoverable is a clean git tree to diff/revert
4
4
  // against. `create`/`init` always leave a repo behind today (see
5
5
  // bin/create-grist-widget.mjs's gitInitCommit), but `use` checks for one