create-grist-widget 0.3.26 → 0.3.27

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.
@@ -223,7 +223,7 @@ function printAgeExceptionsNote(ageExceptions) {
223
223
  `\nNote: ${ageExceptions.join(", ")} came out less than 24 hours ago. pnpm only installs versions` +
224
224
  "\nthat recent when told to, so it listed them under minimumReleaseAgeExclude in pnpm-workspace.yaml." +
225
225
  "\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."
226
+ "\nIt only covers the SDK and CLI versions this scaffold was made with, and is moot once a day old."
227
227
  )
228
228
  }
229
229
 
@@ -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.27",
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. */
@@ -572,81 +579,3 @@ export function resetBranchesIfFirstRelease(
572
579
  git(["push", "--force", "origin", "HEAD:refs/heads/dev"], repoDir)
573
580
  return { deletedBranches, devReset: true, versionReset, identityReset, renamedFiles, installUrl }
574
581
  }
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()
@@ -21,12 +21,13 @@ updates:
21
21
  cooldown:
22
22
  default-days: 7
23
23
  groups:
24
- # Minor/patch bumps arrive together as one PR. grist-widget-sdk is
25
- # left out so its updates get a PR of their own, whose description
26
- # links its release notes.
24
+ # Minor/patch bumps arrive together as one PR. grist-widget-sdk and
25
+ # create-grist-widget (the deploy command deploy.yml runs) are left out
26
+ # so their updates get a PR of their own, whose description links
27
+ # their release notes.
27
28
  routine:
28
29
  update-types: ["minor", "patch"]
29
- exclude-patterns: ["grist-widget-sdk"]
30
+ exclude-patterns: ["grist-widget-sdk", "create-grist-widget"]
30
31
  # @vitejs/plugin-react pins a vite major in its peer range, so their
31
32
  # majors only build when they move together.
32
33
  vite:
@@ -21,12 +21,16 @@ name: Deploy widget to GitHub Pages
21
21
  # - push to dev -> dev channel: mutable /<repo>/dev/
22
22
  # with version.json + a self-reload snippet, for live review inside Grist.
23
23
  #
24
- # All logic lives in scripts/deploy.mjs (node builtins only).
24
+ # All logic lives in the `grist-widget-deploy` command of create-grist-widget,
25
+ # a devDependency (node builtins only), so deploy fixes arrive as dependency
26
+ # updates.
25
27
  #
26
28
  # Two jobs, so no dependency code ever runs next to a write token. `build`
27
29
  # installs and builds (Vite and its plugins are third-party code) with a
28
30
  # read-only token, and hands dist/ over as an artifact. `publish` holds
29
- # `contents: write` but installs nothing: it only runs scripts/deploy.mjs.
31
+ # `contents: write`: it installs with --ignore-scripts, so no dependency code
32
+ # runs, and then runs grist-widget-deploy alone, at the version the lockfile
33
+ # pins.
30
34
  #
31
35
  # One-time setup this workflow can't do for you: enable Pages (Settings ->
32
36
  # Pages -> Source: Deploy from a branch -> gh-pages -> / (root); this workflow
@@ -53,7 +57,7 @@ on:
53
57
  # Nothing at workflow level: each job states what it needs.
54
58
  permissions: {}
55
59
 
56
- # Serialize deploys onto the single shared gh-pages branch. deploy.mjs
60
+ # Serialize deploys onto the single shared gh-pages branch. grist-widget-deploy
57
61
  # additionally rebases-and-retries on push, so interleaving runs never clobber.
58
62
  concurrency:
59
63
  group: pages-gh-pages
@@ -130,7 +134,7 @@ jobs:
130
134
  REF: ${{ github.ref }}
131
135
  FORCE: ${{ github.event.inputs.force == 'true' && '--force' || '' }}
132
136
  run: |
133
- PLAN=$(node scripts/deploy.mjs plan \
137
+ PLAN=$(node_modules/.bin/grist-widget-deploy plan \
134
138
  --site site --repo "$REPO_NAME" \
135
139
  --event "$EVENT_NAME" \
136
140
  --ref "$REF" \
@@ -179,16 +183,26 @@ jobs:
179
183
  REF: ${{ github.ref }}
180
184
  steps:
181
185
  # Credentials stay persisted here: the first-release reset below pushes
182
- # with them. Nothing in this job installs or runs dependency code.
186
+ # with them. No dependency code runs in this job (see the install step).
183
187
  - name: Checkout
184
188
  uses: actions/checkout@v7
185
189
 
190
+ - name: Setup pnpm
191
+ uses: pnpm/action-setup@v6
192
+
186
193
  - name: Setup Node
187
194
  uses: actions/setup-node@v7
188
195
  with:
189
196
  node-version: 22
190
197
  package-manager-cache: false
191
198
 
199
+ # Only for grist-widget-deploy, at the version (and integrity hash) the
200
+ # lockfile pins. --ignore-scripts: no dependency's install script runs,
201
+ # and the steps below call the bin directly, never through `pnpm
202
+ # exec`/`pnpm run`, whose dependency check may reinstall with scripts.
203
+ - name: Install the deploy command
204
+ run: pnpm install --ignore-scripts
205
+
192
206
  - name: Clone gh-pages into ./site
193
207
  env:
194
208
  GH_TOKEN: ${{ github.token }}
@@ -219,7 +233,7 @@ jobs:
219
233
  SHA="$(git rev-parse --short HEAD)"
220
234
  VERSION_ARG=""
221
235
  [ -n "$VERSION" ] && VERSION_ARG="--version $VERSION"
222
- node scripts/deploy.mjs place \
236
+ node_modules/.bin/grist-widget-deploy place \
223
237
  --site site --repo "$REPO_NAME" \
224
238
  --channel "$CONTEXT" $VERSION_ARG \
225
239
  --dist dist \
@@ -237,13 +251,13 @@ jobs:
237
251
  if [ "$CONTEXT" = "release" ]; then
238
252
  UPDATE_VERSIONS="--update-versions"
239
253
  fi
240
- node scripts/deploy.mjs finalize \
254
+ node_modules/.bin/grist-widget-deploy finalize \
241
255
  --site site --repo "$REPO_NAME" $UPDATE_VERSIONS --push \
242
256
  --commit-message "deploy: ${CONTEXT} @ ${SHA_FULL}"
243
257
 
244
258
  - name: Reset repo to a clean first-release state
245
259
  if: needs.build.outputs.context == 'release' && needs.build.outputs.firstRelease == 'true'
246
- run: node scripts/deploy.mjs reset-branches --repo "$REPO_NAME"
260
+ run: node_modules/.bin/grist-widget-deploy reset-branches --repo "$REPO_NAME"
247
261
 
248
262
  cleanup:
249
263
  # When the `dev` branch is deleted, remove /dev/ so the dev URL retires
@@ -253,15 +267,22 @@ jobs:
253
267
  permissions:
254
268
  contents: write
255
269
  steps:
256
- - name: Checkout (default branch — for scripts/deploy.mjs)
270
+ - name: Checkout (default branch — for its pinned grist-widget-deploy)
257
271
  uses: actions/checkout@v7
258
272
 
273
+ - name: Setup pnpm
274
+ uses: pnpm/action-setup@v6
275
+
259
276
  - name: Setup Node
260
277
  uses: actions/setup-node@v7
261
278
  with:
262
279
  node-version: 22
263
280
  package-manager-cache: false
264
281
 
282
+ # As in `publish`: no dependency scripts run, and the bin is called directly.
283
+ - name: Install the deploy command
284
+ run: pnpm install --ignore-scripts
285
+
265
286
  - name: Clone gh-pages into ./site (skip if it does not exist)
266
287
  id: clone
267
288
  env:
@@ -283,7 +304,7 @@ jobs:
283
304
  env:
284
305
  DELETED_REF: ${{ github.event.ref }}
285
306
  run: |
286
- node scripts/deploy.mjs remove --site site
287
- node scripts/deploy.mjs finalize \
307
+ node_modules/.bin/grist-widget-deploy remove --site site
308
+ node_modules/.bin/grist-widget-deploy finalize \
288
309
  --site site --push \
289
310
  --commit-message "cleanup: retire dev URL for ${DELETED_REF}"
@@ -65,10 +65,10 @@ mapping, and rendering all live in `src/App.tsx` — that's the one file to edit
65
65
  | --- | --- |
66
66
  | The widget | `src/App.tsx` |
67
67
  | Widget test (the pattern to copy for new tests) | `src/App.test.tsx` |
68
+ | Smoke test (one smokeTestWidget call; CI runs it on SDK updates) | `src/App.smoke.test.tsx` |
68
69
  | Grist wiring (mountGristWidget, inside vs. outside Grist) | `src/main.tsx` |
69
70
  | ui primitives | `src/components/ui/` |
70
- | Deploy workflow | `.github/workflows/deploy.yml` |
71
- | Deploy script (version guard, channel logic) | `scripts/deploy.mjs` |
71
+ | Deploy workflow (runs grist-widget-deploy from the create-grist-widget devDependency: version guard, channel logic) | `.github/workflows/deploy.yml` |
72
72
  | Pull request checks (lint, test, build) | `.github/workflows/ci.yml` |
73
73
  | Dependency update PRs (weekly, against the dev branch) | `.github/dependabot.yml` |
74
74
  | Full walkthrough (dev loop, deploying, embedding) | `README.md` |
@@ -96,7 +96,7 @@ Full detail, including the two live URLs (`/latest/` and `/v<version>/`): `READM
96
96
  is the trusted real one
97
97
  - Merging `dev` into `main` without bumping `package.json`'s `version` first
98
98
  - Adding `minimumReleaseAgeExclude` entries to `pnpm-workspace.yaml` to get an install or a
99
- check green, or deleting the one pnpm wrote for `grist-widget-sdk` on release day (see
99
+ check green, or deleting the ones pnpm wrote for `grist-widget-sdk` and `create-grist-widget` on release day (see
100
100
  `README.md`): pnpm's 24 h age check protects the widget's supply chain
101
101
  - Merging a Dependabot pull request whose `ci.yml` checks are red, or retargeting one at
102
102
  `main`: updates go through `dev` like any other change. Template-file updates (this
@@ -17,7 +17,7 @@ When actually embedded, the widget shows connection state only when something is
17
17
  - **ESLint** blocks direct `grist` global usage in `src/` — use the SDK only.
18
18
  - Edit `GRIST_OPTIONS.columns` in `App.tsx` to change the required columns (or remove the array entirely to read raw, unmapped records instead); `mountGristWidget()` gates the widget on `canRender` whenever columns are declared, so it renders once they're mapped.
19
19
 
20
- Run `pnpm test` — see `src/App.test.tsx` for the pattern (`renderWithGrist` + `presets` from `grist-widget-sdk/emulator/testing`, no browser or real Grist doc needed). Full guide: [Testing](https://gristwidgets.com/docs/emulator/testing).
20
+ Run `pnpm test`. `src/App.smoke.test.tsx` is the smoke test, one `smokeTestWidget()` call that checks the widget renders inside an emulated Grist with every declared column mapped, and that CI runs on every pull request; see `src/App.test.tsx` for the pattern (`renderWithGrist` + `presets` from `grist-widget-sdk/emulator/testing`, no browser or real Grist doc needed). Full guide: [Testing](https://gristwidgets.com/docs/emulator/testing).
21
21
 
22
22
  **Monorepo dev:** this template resolves `grist-widget-sdk` from `packages/core/dist` (like the other widgets), not from SDK source. After changing the SDK, run `pnpm prebuild` or `pnpm --filter grist-widget-sdk build` before `pnpm dev`.
23
23
 
@@ -43,11 +43,12 @@ pnpm, even under `npm create`, unless it was run with `--no-install` or
43
43
  without pnpm installed. Versions are pinned with their integrity hashes, CI
44
44
  installs exactly that, and Dependabot updates it. A `package-lock.json` isn't
45
45
  used: the workflows install with pnpm. pnpm 11 won't install a version
46
- published less than 24 hours ago. So a widget scaffolded on the day of an SDK
47
- release also gets one `minimumReleaseAgeExclude` entry in
48
- `pnpm-workspace.yaml`, for that SDK version only, written by pnpm itself.
49
- Keep it: without it, every install (yours and CI's) rejects the lockfile
50
- until the version is a day old. After that the entry is harmless.
46
+ published less than 24 hours ago. So a widget scaffolded on the day of a
47
+ release also gets `minimumReleaseAgeExclude` entries in `pnpm-workspace.yaml`,
48
+ written by pnpm itself, for the `grist-widget-sdk` and `create-grist-widget`
49
+ versions it was made with, and nothing else. Keep them: without them, every
50
+ install (yours and CI's) rejects the lockfile until the version is a day old.
51
+ After that the entries are harmless.
51
52
 
52
53
  ### Live preview
53
54
 
@@ -68,8 +69,10 @@ directory, same as `scripts/smoke/create-widget.sh` does.
68
69
 
69
70
  ## Deployment
70
71
 
71
- A bundled GitHub Actions workflow (`.github/workflows/deploy.yml` +
72
- `scripts/deploy.mjs`) publishes this widget to **your own** GitHub Pages.
72
+ A bundled GitHub Actions workflow (`.github/workflows/deploy.yml`, which runs
73
+ the `grist-widget-deploy` command of the `create-grist-widget` devDependency)
74
+ publishes this widget to **your own** GitHub Pages. Fixes to the deploy logic
75
+ arrive as updates of that devDependency.
73
76
 
74
77
  ### The workflow: always develop on `dev`, release by merging to `main`
75
78
 
@@ -17,7 +17,7 @@
17
17
  "@tailwindcss/vite": "^4.3.3",
18
18
  "class-variance-authority": "^0.7.1",
19
19
  "clsx": "^2.1.1",
20
- "grist-widget-sdk": "^0.3.26",
20
+ "grist-widget-sdk": "^0.3.27",
21
21
  "lucide-react": "^1.61.0",
22
22
  "radix-ui": "^1.8.1",
23
23
  "react": "^19.3.1",
@@ -35,6 +35,7 @@
35
35
  "@types/react": "^19.3.0",
36
36
  "@types/react-dom": "^19.3.0",
37
37
  "@vitejs/plugin-react": "^6.1.2",
38
+ "create-grist-widget": "^0.3.27",
38
39
  "eslint": "^10.13.0",
39
40
  "eslint-plugin-react-hooks": "^7.0.1",
40
41
  "eslint-plugin-react-refresh": "^0.5.7",
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Smoke test: the widget renders inside an emulated Grist, mounted as
3
+ * `main.tsx` mounts it, with every column `GRIST_OPTIONS` declares mapped.
4
+ *
5
+ * It only reads `GRIST_OPTIONS`, so it stays valid as the widget changes,
6
+ * and it improves with each SDK release. `.github/workflows/ci.yml` runs it
7
+ * on every pull request, Dependabot's `grist-widget-sdk` updates included:
8
+ * an SDK release that breaks this widget turns that PR red before the merge.
9
+ * Widget-specific assertions go in their own tests (see `App.test.tsx`).
10
+ */
11
+ import { it } from "vitest"
12
+ import { smokeTestWidget } from "grist-widget-sdk/emulator/testing"
13
+
14
+ import App, { GRIST_OPTIONS } from "./App"
15
+
16
+ it("renders inside Grist with every declared column mapped", () =>
17
+ smokeTestWidget({ App, options: GRIST_OPTIONS }))
@@ -98,7 +98,7 @@ export function createComposer({ templateDir, examplesDir, uiDir, manifestPath }
98
98
  // only remove files this function itself may have added, so a stale
99
99
  // sibling from a *different* previous example doesn't linger.
100
100
  for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
101
- if (entry.name === "App.tsx" || entry.name === "App.test.tsx") continue
101
+ if (entry.name === "App.tsx" || entry.name === "App.test.tsx" || entry.name === "App.smoke.test.tsx") continue
102
102
  if (["components", "lib", "assets", "index.css", "vite-env.d.ts", "main.tsx"].includes(entry.name)) {
103
103
  continue
104
104
  }
@@ -175,11 +175,14 @@ export function createComposer({ templateDir, examplesDir, uiDir, manifestPath }
175
175
  * `workspace:^` (meaningless outside the pnpm workspace) — a `file:`
176
176
  * reference to either `packages/core` (workspace source, already built), a
177
177
  * packed tarball, or (for the CLI's `create`) a real published semver range.
178
+ * `cliDependency` does the same for the template's `create-grist-widget`
179
+ * devDependency (its deploy bin); without one, the devDependency is dropped,
180
+ * since a composed example is only typechecked, built and tested here.
178
181
  *
179
182
  * For composing *many* examples against one shared `pnpm install`, use
180
183
  * {@link overlayExampleSrc} directly instead (see its own doc comment).
181
184
  */
182
- function composeExample({ exampleId, destDir, sdkDependency }) {
185
+ function composeExample({ exampleId, destDir, sdkDependency, cliDependency }) {
183
186
  rmSync(destDir, { recursive: true, force: true })
184
187
  cpSync(templateDir, destDir, {
185
188
  recursive: true,
@@ -192,6 +195,8 @@ export function createComposer({ templateDir, examplesDir, uiDir, manifestPath }
192
195
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"))
193
196
  pkg.name = exampleId
194
197
  pkg.dependencies["grist-widget-sdk"] = sdkDependency
198
+ if (cliDependency) pkg.devDependencies["create-grist-widget"] = cliDependency
199
+ else delete pkg.devDependencies["create-grist-widget"]
195
200
  Object.assign(pkg.dependencies, EXTRA_DEPENDENCIES[exampleId] ?? {})
196
201
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
197
202