create-grist-widget 0.2.23 → 0.2.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-grist-widget",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "description": "Scaffold a new Grist custom widget from the grist-widget-sdk Vite template",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -2,22 +2,18 @@ name: Deploy widget to GitHub Pages
2
2
 
3
3
  # Two channels, one workflow:
4
4
  # - push to main / manual dispatch -> release channel: immutable
5
- # /<repo>/v<version>/ + mutable /<repo>/latest/.
5
+ # /<repo>/v<version>/ + mutable /<repo>/latest/, plus the same build at
6
+ # the site root (the showcase hub landing page).
6
7
  # Rebuilt only when package.json's version has no v<version> dir yet, so
7
8
  # re-pushing main without a version bump is a no-op deploy.
8
- # A push that *creates* the main ref, authored by GitHub's own web-flow
9
- # bot -- fired by "Use this template" copying this repo onto a brand-new
10
- # ref -- is skipped entirely instead: it would otherwise publish
11
- # whatever boilerplate version package.json happens to have checked in
12
- # and permanently spend the first-release override before anyone's done
13
- # real work. (Checking github.event.created alone isn't enough: a CLI
14
- # scaffold's own first `git push -u origin main` also creates that ref
15
- # for the first time, but is a real user's genuine first release and
16
- # must not be skipped -- the web-flow committer check tells the two
17
- # apart.) That skipped run still clears any foreign content the copy
18
- # brought along (stray v<version>/ dirs, a root/latest/ still pointing
19
- # at the source repo's own path) via `cleanup-foreign`, so root/latest/
20
- # aren't left blank until the widget's real first release.
9
+ # A repo's first genuine release always resolves to v0.0.1 regardless of
10
+ # package.json's version, so "Use this template" (whose initial push to
11
+ # main by GitHub's web-flow bot fires this workflow) publishes a clean
12
+ # v0.0.1 + /latest/ + root right away, discarding any inherited canary
13
+ # version. On that same first release, the "Reset repo to a clean
14
+ # first-release state" step below rewrites package.json's version back to
15
+ # 0.0.1 in the repo itself, so the *next* release bumps to 0.0.2 rather
16
+ # than the inherited number.
21
17
  # - push to dev -> dev channel: mutable /<repo>/dev/
22
18
  # with version.json + a self-reload snippet, for live review inside Grist.
23
19
  #
@@ -104,8 +100,7 @@ jobs:
104
100
  --site site --repo "$REPO_NAME" \
105
101
  --event "${{ github.event_name }}" \
106
102
  --ref "${{ github.ref }}" \
107
- ${{ github.event.inputs.force == 'true' && '--force' || '' }} \
108
- ${{ github.event.created == true && github.event.head_commit.committer.email == 'noreply@github.com' && '--created' || '' }})
103
+ ${{ github.event.inputs.force == 'true' && '--force' || '' }})
109
104
  echo "Plan: $PLAN"
110
105
  node -e '
111
106
  const p = JSON.parse(process.argv[1]);
@@ -113,7 +108,6 @@ jobs:
113
108
  const out = process.env.GITHUB_OUTPUT;
114
109
  fs.appendFileSync(out, `context=${p.context}\n`);
115
110
  fs.appendFileSync(out, `skip=${p.skip ? "true" : "false"}\n`);
116
- fs.appendFileSync(out, `reason=${p.reason || ""}\n`);
117
111
  fs.appendFileSync(out, `version=${p.version || ""}\n`);
118
112
  fs.appendFileSync(out, `base=${p.base}\n`);
119
113
  fs.appendFileSync(out, `firstRelease=${p.firstRelease ? "true" : "false"}\n`);
@@ -138,19 +132,9 @@ jobs:
138
132
  --sha "$SHA" --ref "${{ github.ref }}"
139
133
 
140
134
  - name: Skip (already published)
141
- if: steps.plan.outputs.skip == 'true' && steps.plan.outputs.reason != 'ref-created'
135
+ if: steps.plan.outputs.skip == 'true'
142
136
  run: echo "== skip — v${{ steps.plan.outputs.version }} already published"
143
137
 
144
- # The ref-created push never reaches Build/Place, so it's the only
145
- # path where cleanupForeignVersionsIfFirstRelease (normally a side
146
- # effect of Place) needs to run standalone -- otherwise foreign
147
- # content from "Use this template" (stray v<version>/ dirs, a
148
- # root/latest/ pointing at the source repo's own path) sits untouched
149
- # until the widget's real first release.
150
- - name: Clean foreign content (ref-created push published nothing)
151
- if: steps.plan.outputs.reason == 'ref-created'
152
- run: node scripts/deploy.mjs cleanup-foreign --site site --repo "$REPO_NAME"
153
-
154
138
  - name: Finalize (versions.json + commit + push)
155
139
  run: |
156
140
  UPDATE_VERSIONS=""
@@ -161,15 +145,18 @@ jobs:
161
145
  --site site --repo "$REPO_NAME" $UPDATE_VERSIONS --push \
162
146
  --commit-message "deploy: ${{ steps.plan.outputs.context }} @ ${{ github.sha }}"
163
147
 
164
- # On this repo's first genuine release only: prune every branch except
165
- # main/dev/gh-pages, and reset dev to main's current tip. Establishes
166
- # the schema every widget should start from (main == dev, gh-pages is
167
- # CI-managed only) regardless of how the repo was created -- a CLI
168
- # scaffold already satisfies this trivially; a repo copied via GitHub's
169
- # "Use this template" often doesn't (a stray inherited branch, or dev
170
- # tracking the source template's own unrelated preview history).
148
+ # On this repo's first genuine release only: reset package.json's version
149
+ # to 0.0.1 (so the next release bumps to 0.0.2 rather than any inherited
150
+ # canary version), prune every branch except main/dev/gh-pages, and
151
+ # reset dev to main's current tip. Establishes the state every widget
152
+ # should start from regardless of how the repo was created -- a CLI
153
+ # scaffold already satisfies it trivially; a repo copied via GitHub's
154
+ # "Use this template" often doesn't (an inherited version, a stray
155
+ # branch, or dev tracking the source template's own preview history).
171
156
  # Placed after Finalize so it never runs if the release itself failed.
172
- - name: Reset branches on first genuine release
157
+ # The version-reset commit re-triggers this workflow on main, but that
158
+ # run sees v0.0.1 already published (firstRelease=false) and no-ops.
159
+ - name: Reset repo to a clean first-release state
173
160
  if: steps.plan.outputs.context == 'release' && steps.plan.outputs.firstRelease == 'true'
174
161
  run: node scripts/deploy.mjs reset-branches
175
162
 
@@ -90,39 +90,32 @@ automatically.
90
90
  > grist-widget`?** That path copies this repo's default branch verbatim —
91
91
  > it doesn't run any of the CLI's own setup (renaming `package.json`,
92
92
  > titles, etc.), and whatever version happens to be checked into the
93
- > source repo's `package.json` (which can be well past `0.0.1` — see below)
94
- > comes along with it. You don't need to reset it by hand: your first real
93
+ > source repo's `package.json` (which can be well past `0.0.1`) comes along
94
+ > with it. **The moment the repo is created, GitHub's own initial push to
95
+ > `main` fires this workflow and publishes a clean `v0.0.1`** — your first
95
96
  > release always resolves to `v0.0.1` regardless of what `package.json`
96
- > says, and that value stays untouched in the file — only the deploy
97
- > pipeline's own idea of "which version is this" is overridden, once, for
98
- > that first release. If you also checked **"Include all branches"** (to
99
- > get `gh-pages`/Pages already set up, no manual Settings step), your first
100
- > real release also automatically clears out every leftover `v<version>/`
101
- > it finds — a `v<version>/` only counts as a genuine prior release once it
102
- > carries a `showcase-meta.json` naming *this* repo (written by this
103
- > pipeline's own `place` step), so on a repo with no genuine releases yet,
104
- > everything else is provably inherited noise and gets safely wiped on your
105
- > first push to `main`. Also on that same first release, every branch
106
- > except `main`, `dev`, and `gh-pages` gets pruned, and `dev` gets
107
- > force-reset to match `main`'s tip — a fresh widget always starts from
108
- > `main == dev`, regardless of whether it came from the CLI (already true)
109
- > or a template copy (may have inherited a stray branch, or a `dev` full of
110
- > the source template's own unrelated preview history). All three of these
111
- > only ever apply before your *first* genuine release — once one exists,
112
- > they're permanently a no-op, so it's still safest to never manually
113
- > re-seed `gh-pages` again after that point.
114
- >
115
- > "Use this template" also immediately fires this workflow once on its own,
116
- > the moment the repo is created — before you've cloned it, let alone
117
- > changed anything. That run is recognized (GitHub's own web-flow bot
118
- > creating the `main` ref) and skipped entirely: nothing gets published,
119
- > and it doesn't count as your first release, so the `0.0.1` override above
120
- > still applies in full once you actually push real changes. If you also
121
- > checked "Include all branches", that same skipped run still clears out
122
- > whatever foreign content came along with it — stray `v<version>/` dirs,
123
- > and a root/`latest/` still pointing at the *source* repo's own path
124
- > (which would otherwise render blank until your real first release) —
125
- > without needing to publish anything of its own to do so.
97
+ > says, so `/`, `/latest/`, and `/v0.0.1/` all go live right away (once
98
+ > you've done the one-time Pages setup below). You don't reset the version
99
+ > by hand: on that first release the pipeline also **rewrites
100
+ > `package.json`'s version back to `0.0.1` in the repo itself** (a commit it
101
+ > pushes to `main`/`dev`), so your *next* release naturally bumps to
102
+ > `0.0.2` instead of jumping to whatever inherited number you started with.
103
+ > If you also checked **"Include all branches"** (to get `gh-pages`/Pages
104
+ > already set up, no manual Settings step), that first release also clears
105
+ > out every leftover bit of inherited content it finds — stray
106
+ > `v<version>/` dirs, and a root/`latest/` still pointing at the *source*
107
+ > repo's own path — before writing its own. A `v<version>/` only counts as
108
+ > a genuine prior release once it carries a `showcase-meta.json` naming
109
+ > *this* repo, so on a repo with no genuine releases yet, everything else is
110
+ > provably inherited noise and gets safely replaced. On that same first
111
+ > release, every branch except `main`, `dev`, and `gh-pages` is pruned and
112
+ > `dev` is force-reset to match `main`'s tip — a fresh widget always starts
113
+ > from `main == dev`, whether it came from the CLI (already true) or a
114
+ > template copy (may have inherited a stray branch, or a `dev` full of the
115
+ > source template's own unrelated preview history). All of this only ever
116
+ > applies before your *first* genuine release — once one exists, it's
117
+ > permanently a no-op, so it's still safest to never manually re-seed
118
+ > `gh-pages` again after that point.
126
119
 
127
120
  **One-time setup** (the workflow can't do this part for you):
128
121
 
@@ -19,27 +19,18 @@
19
19
  //
20
20
  // Subcommands:
21
21
  // plan --site <dir> --repo <name> --event <push|workflow_dispatch>
22
- // --ref <ref> [--force] [--created]
22
+ // --ref <ref> [--force]
23
23
  // Prints JSON { context: "release"|"dev", version?, base, skip }.
24
24
  // Release is skipped when v<version> already exists on gh-pages
25
25
  // (idempotence: re-pushing main without a version bump is a no-op).
26
- // --created additionally skips unconditionally, without consuming the
27
- // first-release version override -- see plan()'s own comment. Callers
28
- // must only pass --created for GitHub's own server-side "Use this
29
- // template" push, never a real user push that merely happens to create
30
- // a ref for the first time (e.g. a CLI scaffold's first `git push -u
31
- // origin main`) -- see deploy.yml's wiring for the distinguishing check.
26
+ // A repo's first genuine release always resolves to v0.0.1 regardless of
27
+ // package.json's version -- so "Use this template" (whose initial push
28
+ // to main fires this workflow) publishes a clean v0.0.1 straight away.
32
29
  //
33
30
  // place --site <dir> --repo <name> --channel <release|dev>
34
31
  // [--version <v>] --dist <dir> --sha <sha> [--ref <ref>]
35
32
  // Copies a freshly built dist into the gh-pages tree.
36
33
  //
37
- // cleanup-foreign --site <dir> --repo <name>
38
- // Standalone entry point for `cleanupForeignVersionsIfFirstRelease` --
39
- // used on a skipped ref-created push (see `plan`), which never reaches
40
- // `place` (the only other caller of this cleanup) so foreign content
41
- // from "Use this template" would otherwise sit untouched indefinitely.
42
- //
43
34
  // remove --site <dir>
44
35
  // Removes dev/ from the gh-pages tree (retire the dev URL when the `dev`
45
36
  // branch is deleted). Never touches a release or latest/.
@@ -50,7 +41,8 @@
50
41
  //
51
42
  // reset-branches [--repo-dir <dir>]
52
43
  // On a repo's first genuine release only (caller gates on plan()'s
53
- // firstRelease): prunes every branch except main/dev/gh-pages and
44
+ // firstRelease): resets package.json's version to 0.0.1, prunes every
45
+ // branch except main/dev/gh-pages, and
54
46
  // force-resets dev to main's current tip.
55
47
 
56
48
  import {
@@ -113,7 +105,7 @@ function hasGenuineRelease(siteDir, repo) {
113
105
  * genuinely published by this repo before (unless force), so re-pushing main
114
106
  * without a version bump is a no-op.
115
107
  */
116
- export function plan(siteDir, repo, event, ref, { force = false, created = false } = {}) {
108
+ export function plan(siteDir, repo, event, ref, { force = false } = {}) {
117
109
  const isRelease = event === "workflow_dispatch" || ref === "refs/heads/main" || ref === "main"
118
110
  if (!isRelease) {
119
111
  return { context: "dev", base: basePathFor(repo, "dev") }
@@ -123,35 +115,20 @@ export function plan(siteDir, repo, event, ref, { force = false, created = false
123
115
  // whatever package.json's version field says -- found live: a repo copied
124
116
  // via GitHub's "Use this template" inherited "0.2.18" from the source
125
117
  // template repo's own main branch (itself a canary-stamped scaffold that
126
- // had been promoted there), instead of starting fresh. Once a repo has a
127
- // genuine release, this no longer applies -- its own version bumps are
128
- // its own responsibility from then on, same as `cleanupForeignVersionsIfFirstRelease`.
118
+ // had been promoted there), instead of starting fresh. This is what makes
119
+ // "Use this template" publish a clean v0.0.1 the moment the repo is
120
+ // created (GitHub's own initial push to main fires this workflow): the
121
+ // copy's inherited canary version is ignored for that first release. To
122
+ // keep the *next* release clean too, the workflow's first-release reset
123
+ // step rewrites package.json's version back to 0.0.1 in the repo itself
124
+ // (see `resetBranchesIfFirstRelease`), so the second release naturally
125
+ // bumps from 0.0.1 rather than the inherited canary number. Once a repo
126
+ // has a genuine release, this override no longer applies -- its own
127
+ // version bumps are its own responsibility from then on.
129
128
  const firstRelease = !hasGenuineRelease(siteDir, repo)
130
129
  const version = firstRelease ? "0.0.1" : pkg.version
131
130
  if (!version) throw new Error("package.json has no version")
132
131
  const versionDir = join(siteDir, `v${version}`)
133
- // GitHub's "Use this template" copies the source repo's main branch onto a
134
- // brand-new ref, which fires this same push-to-main event itself --
135
- // before anyone has done any real work. Without this check, that event
136
- // would consume the first-release override above on whatever unmodified
137
- // boilerplate happens to be checked in, so the widget's *actual* first
138
- // release (once someone merges real content) no longer counts as
139
- // firstRelease and publishes under that boilerplate's version verbatim
140
- // instead of a clean 0.0.1 -- found live (a monorepo widget moved into its
141
- // own repo this way published its real first release as "v0.2.21").
142
- // Skipping this event entirely (never placing anything, never touching
143
- // gh-pages) keeps hasGenuineRelease() false, so the override survives
144
- // intact for whenever a real release actually happens.
145
- if (created && event === "push") {
146
- return {
147
- context: "release",
148
- version,
149
- base: basePathFor(repo, "release", version),
150
- skip: true,
151
- reason: "ref-created",
152
- firstRelease,
153
- }
154
- }
155
132
  const exists = isGenuineRelease(versionDir, repo)
156
133
  return {
157
134
  context: "release",
@@ -254,11 +231,11 @@ function replaceDir(dest, srcDist) {
254
231
  * template" -> "Include all branches"): stray `v<version>/` directories,
255
232
  * but also a root `index.html`/`latest/` still referencing the *source*
256
233
  * repo's own base path (found live: a fresh copy's root and `latest/`
257
- * pointed at `/grist-widget-template/v0.2.22/assets/...`, 404ing on this
258
- * repo's own Pages site -- a blank page -- because the ref-created push
259
- * that used to overwrite them with this repo's own build is now skipped
260
- * entirely, see `plan()`). Safe to clear unconditionally: a repo with zero
261
- * genuine releases can't have any of its own history to lose. Once a
234
+ * pointed at `/grist-widget-template/v0.2.22/assets/...`, which 404s on
235
+ * this repo's own Pages site). Called by `placeTarget` right before it
236
+ * writes this repo's own first release, so the inherited noise is replaced
237
+ * rather than layered under. Safe to clear unconditionally: a repo with
238
+ * zero genuine releases can't have any of its own history to lose. Once a
262
239
  * genuine release exists for this repo, buildVersionsManifest is non-empty
263
240
  * and this is permanently a no-op.
264
241
  */
@@ -372,19 +349,50 @@ export function finalize({ siteDir, updateVersions, push, commitMessage, remoteB
372
349
  }
373
350
 
374
351
  /**
375
- * Establish the schema every widget repo should start from -- `main` and
376
- * `dev` identical, `gh-pages` managed by CI only -- by pruning every other
377
- * branch and force-resetting `dev` to `main`'s current tip (the commit
378
- * already checked out at `repoDir`). Runs once, gated by the caller on
379
- * `plan()`'s `firstRelease` flag, so it applies equally to a CLI scaffold
380
- * (already satisfies this trivially -- nothing to prune, dev already equals
381
- * main) and a repo copied via GitHub's "Use this template" (may have a
382
- * stray inherited branch, or `dev` tracking the source template's own
383
- * unrelated preview history). Operates on `repoDir`, the repo's own
384
- * checkout -- a different directory than the gh-pages `siteDir` every other
385
- * function here works with.
352
+ * Establish the state every widget repo should start from, on its first
353
+ * genuine release only (gated by the caller on `plan()`'s `firstRelease`):
354
+ *
355
+ * 1. Reset package.json's version to 0.0.1. The first release already
356
+ * published v0.0.1 (forced by plan() regardless of package.json), but a
357
+ * repo copied via GitHub's "Use this template" still carries the source
358
+ * template's inherited canary version in the file (found live: a copy
359
+ * shipped 0.2.23) -- so its *next* release would jump straight to
360
+ * v0.2.24 instead of a clean v0.0.2. Rewriting the file to 0.0.1 here,
361
+ * committed to main, makes the next bump land on 0.0.2. A CLI scaffold
362
+ * already ships 0.0.1, so this is a no-op there.
363
+ * 2. main == dev, gh-pages CI-managed only: prune every other branch and
364
+ * force-reset `dev` to `main`'s current tip (which now carries the
365
+ * version-reset commit). A CLI scaffold satisfies this trivially; a
366
+ * "Use this template" copy may have a stray inherited branch, or a
367
+ * `dev` tracking the source template's own unrelated preview history.
368
+ *
369
+ * Operates on `repoDir`, the repo's own checkout -- a different directory
370
+ * than the gh-pages `siteDir` every other function here works with. The
371
+ * version-reset commit re-triggers this workflow on main, but that run sees
372
+ * a genuine release already published (firstRelease=false) and skips both
373
+ * the release and this reset, so there's no loop.
386
374
  */
387
375
  export function resetBranchesIfFirstRelease(repoDir, keep = ["main", "dev", "gh-pages"]) {
376
+ let versionReset = false
377
+ const pkgPath = join(repoDir, "package.json")
378
+ if (existsSync(pkgPath)) {
379
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"))
380
+ if (pkg.version !== "0.0.1") {
381
+ pkg.version = "0.0.1"
382
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n")
383
+ git(["add", "package.json"], repoDir)
384
+ git(
385
+ [
386
+ "-c", "user.name=github-actions[bot]",
387
+ "-c", "user.email=41898282+github-actions[bot]@users.noreply.github.com",
388
+ "commit", "-m", "chore: reset version to 0.0.1 for a clean first release",
389
+ ],
390
+ repoDir,
391
+ )
392
+ git(["push", "origin", "HEAD:refs/heads/main"], repoDir)
393
+ versionReset = true
394
+ }
395
+ }
388
396
  const raw = git(["ls-remote", "--heads", "origin"], repoDir)
389
397
  const branches = raw
390
398
  ? raw.split("\n").map((line) => line.split("refs/heads/")[1]).filter(Boolean)
@@ -396,7 +404,7 @@ export function resetBranchesIfFirstRelease(repoDir, keep = ["main", "dev", "gh-
396
404
  deletedBranches.push(b)
397
405
  }
398
406
  git(["push", "--force", "origin", "HEAD:refs/heads/dev"], repoDir)
399
- return { deletedBranches, devReset: true }
407
+ return { deletedBranches, devReset: true, versionReset }
400
408
  }
401
409
 
402
410
  // ----------------------------------------------------------------------------
@@ -425,7 +433,6 @@ function main() {
425
433
  case "plan": {
426
434
  const result = plan(args.site, args.repo, args.event, args.ref, {
427
435
  force: !!args.force,
428
- created: !!args.created,
429
436
  })
430
437
  process.stdout.write(JSON.stringify(result))
431
438
  break
@@ -448,11 +455,6 @@ function main() {
448
455
  console.log("remove:", JSON.stringify(res))
449
456
  break
450
457
  }
451
- case "cleanup-foreign": {
452
- const res = cleanupForeignVersionsIfFirstRelease(args.site, args.repo)
453
- console.log("cleanup-foreign:", JSON.stringify(res))
454
- break
455
- }
456
458
  case "finalize": {
457
459
  const res = finalize({
458
460
  siteDir: args.site,