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 +25 -7
- package/bin/create-grist-widget.mjs +125 -9
- package/bin/grist-widget-deploy.mjs +92 -0
- package/examples/reads-demo/WidgetApp.tsx +2 -1
- package/examples/safe-parse/WidgetApp.tsx +2 -1
- package/manifest.json +2 -2
- package/package.json +3 -2
- package/{template/scripts → src}/deploy.mjs +13 -81
- package/src/git-gate.mjs +2 -2
- package/src/upgrade.mjs +562 -0
- package/template/.github/dependabot.yml +5 -4
- package/template/.github/workflows/deploy.yml +32 -11
- package/template/AGENTS.md +36 -11
- package/template/README.md +19 -13
- package/template/package.json +2 -1
- package/template/src/app/App.smoke.test.tsx +20 -0
- package/template/src/main.tsx +1 -1
- package/template/vite.config.ts +1 -1
- package/vendor/compose-example.mjs +23 -20
- /package/template/src/{App.test.tsx → app/App.test.tsx} +0 -0
- /package/template/src/{App.tsx → app/App.tsx} +0 -0
- /package/template/src/{grist-types.example.ts → app/grist-types.example.ts} +0 -0
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
|
|
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`
|
|
65
|
-
`src/
|
|
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
|
|
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
|
-
//
|
|
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>
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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": "
|
|
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.
|
|
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
|
|
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
|
|
2
|
-
// src/
|
|
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
|