create-grist-widget 0.2.14 → 0.2.16
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
|
@@ -132,7 +132,7 @@ jobs:
|
|
|
132
132
|
UPDATE_VERSIONS="--update-versions"
|
|
133
133
|
fi
|
|
134
134
|
node scripts/deploy.mjs finalize \
|
|
135
|
-
--site site $UPDATE_VERSIONS --push \
|
|
135
|
+
--site site --repo "$REPO_NAME" $UPDATE_VERSIONS --push \
|
|
136
136
|
--commit-message "deploy: ${{ steps.plan.outputs.context }} @ ${{ github.sha }}"
|
|
137
137
|
|
|
138
138
|
cleanup:
|
package/template/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Base template to build a Grist widget with `grist-widget-sdk`.
|
|
|
6
6
|
|
|
7
7
|
Opened outside a Grist iframe, `main.tsx` picks between two components purely by URL shape (`src/lib/showcase-routing.ts`, no router needed): a bare path with no recognized channel suffix renders `TemplateLanding` (its own hero, onboarding, and released-version index); `/latest/`, `/dev/`, or `/v<version>/` renders `ChannelNotice` instead — a distinct hero explaining which build this is, chips to jump to any other version/channel (`src/lib/showcase-versions.ts`), and a copy-this-URL helper for pasting into Grist's custom widget field.
|
|
8
8
|
|
|
9
|
-
When actually embedded, `GristStatusChip` (`src/components/grist-status-chip.tsx`) shows a small pill with live handshake status — connecting, retrying with a countdown, connected, or unavailable — using `
|
|
9
|
+
When actually embedded, `GristStatusChip` (`src/components/grist-status-chip.tsx`) shows a small pill with live handshake status — connecting, retrying with a countdown, connected, or unavailable — using `useAmbientGristHandshake()` from `grist-widget-sdk/advanced`. It must be mounted *inside* `GristWidgetProvider` (see `src/main.tsx`): it only observes that provider's own handshake manager and never mounts a second one, so it's purely observational and can never duplicate or race the real handshake (see `apps/docs/api/handshake.md`).
|
|
10
10
|
|
|
11
11
|
`src/App.tsx` uses `useGrist<TaskRow, TaskMapped>()` for the selected row (`w.record`, `w.mode`) and remounts the UI with `key={rowKey}` when the row changes — same pattern as `widgets/create-email-draft`. See `src/grist-types.example.ts` for typing patterns.
|
|
12
12
|
|
|
@@ -86,6 +86,19 @@ of changes — it's the permanent working/preview branch for this widget, not
|
|
|
86
86
|
a one-off feature branch to delete and recreate. Deleting it retires `/dev/`
|
|
87
87
|
automatically.
|
|
88
88
|
|
|
89
|
+
> ⚠️ **Used GitHub's "Use this template" button instead of `npm create
|
|
90
|
+
> grist-widget`?** That path copies this repo's default branch verbatim —
|
|
91
|
+
> it doesn't run any of the CLI's own setup (renaming `package.json`,
|
|
92
|
+
> titles, etc.), and it won't reset `package.json`'s `version` back to a
|
|
93
|
+
> fresh `0.0.1` for you. Do both by hand before your first real release.
|
|
94
|
+
> Also **never manually seed/copy `gh-pages` content** from another repo
|
|
95
|
+
> (e.g. to get Pages showing something immediately) — each `v<version>/`
|
|
96
|
+
> is only recognized as a genuine prior release if it carries a
|
|
97
|
+
> `showcase-meta.json` naming *this* repo (written by this pipeline's own
|
|
98
|
+
> `place` step); anything else is treated as unpublished and safely
|
|
99
|
+
> rebuilt over, but it's simpler to just let the workflow create
|
|
100
|
+
> `gh-pages` itself (it does, as an orphan branch, on its first real run).
|
|
101
|
+
|
|
89
102
|
**One-time setup** (the workflow can't do this part for you):
|
|
90
103
|
|
|
91
104
|
1. **Settings → Pages** → Source: "Deploy from a branch" → branch `gh-pages`
|
package/template/package.json
CHANGED
|
@@ -58,10 +58,33 @@ export function basePathFor(repo, channel, version) {
|
|
|
58
58
|
return `/${repo}/${leaf}/`
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Was `versionDir` actually published by *this* repo's own pipeline? Checks
|
|
63
|
+
* for a `showcase-meta.json` whose `repo` field matches -- not just that the
|
|
64
|
+
* directory exists. A `gh-pages` branch manually seeded from another repo's
|
|
65
|
+
* export (found live: a freshly-created widget repo's `gh-pages` already
|
|
66
|
+
* contained `v<version>/` directories copied from the template repo it came
|
|
67
|
+
* from, some with no meta file at all, one with a foreign `showcase-meta.json`
|
|
68
|
+
* whose `sha` didn't belong to this repo's own history) would otherwise look
|
|
69
|
+
* indistinguishable from a real prior release and get silently treated as
|
|
70
|
+
* "already published" forever, permanently squatting on that version path.
|
|
71
|
+
*/
|
|
72
|
+
function isGenuineRelease(versionDir, repo) {
|
|
73
|
+
const metaPath = join(versionDir, "showcase-meta.json")
|
|
74
|
+
if (!existsSync(metaPath)) return false
|
|
75
|
+
try {
|
|
76
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"))
|
|
77
|
+
return meta.repo === repo
|
|
78
|
+
} catch {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
/**
|
|
62
84
|
* Build the release/dev plan from this repo's own package.json + the current
|
|
63
|
-
* gh-pages tree. Release is skipped when its immutable v<version> dir
|
|
64
|
-
*
|
|
85
|
+
* gh-pages tree. Release is skipped when its immutable v<version> dir was
|
|
86
|
+
* genuinely published by this repo before (unless force), so re-pushing main
|
|
87
|
+
* without a version bump is a no-op.
|
|
65
88
|
*/
|
|
66
89
|
export function plan(siteDir, repo, event, ref, { force = false } = {}) {
|
|
67
90
|
const isRelease = event === "workflow_dispatch" || ref === "refs/heads/main" || ref === "main"
|
|
@@ -72,7 +95,7 @@ export function plan(siteDir, repo, event, ref, { force = false } = {}) {
|
|
|
72
95
|
const version = pkg.version
|
|
73
96
|
if (!version) throw new Error("package.json has no version")
|
|
74
97
|
const versionDir = join(siteDir, `v${version}`)
|
|
75
|
-
const exists =
|
|
98
|
+
const exists = isGenuineRelease(versionDir, repo)
|
|
76
99
|
return {
|
|
77
100
|
context: "release",
|
|
78
101
|
version,
|
|
@@ -125,15 +148,19 @@ export function injectSelfReload(html, sha) {
|
|
|
125
148
|
* show this widget's own release history -- not the grist-widget-sdk
|
|
126
149
|
* monorepo's (found live: every scaffolded widget's version index showed
|
|
127
150
|
* the monorepo's versions instead of its own, since showcase-versions.ts
|
|
128
|
-
* used to hardcode that URL).
|
|
151
|
+
* used to hardcode that URL). Entries whose meta has no `repo` field, or a
|
|
152
|
+
* `repo` that doesn't match this repo, are skipped -- a `gh-pages` branch
|
|
153
|
+
* manually seeded from another repo's export (found live) would otherwise
|
|
154
|
+
* leak that repo's own version history into this one's index.
|
|
129
155
|
*/
|
|
130
|
-
export function buildVersionsManifest(siteDir) {
|
|
156
|
+
export function buildVersionsManifest(siteDir, repo) {
|
|
131
157
|
const entries = []
|
|
132
158
|
for (const name of existsSync(siteDir) ? readdirSync(siteDir) : []) {
|
|
133
159
|
if (!/^v\d+\.\d+\.\d+/.test(name)) continue
|
|
134
160
|
const metaPath = join(siteDir, name, "showcase-meta.json")
|
|
135
161
|
if (!existsSync(metaPath)) continue
|
|
136
162
|
const meta = JSON.parse(readFileSync(metaPath, "utf8"))
|
|
163
|
+
if (meta.repo !== repo) continue
|
|
137
164
|
entries.push({ version: meta.version, publishedAt: meta.publishedAt })
|
|
138
165
|
}
|
|
139
166
|
entries.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt))
|
|
@@ -151,7 +178,7 @@ function replaceDir(dest, srcDist) {
|
|
|
151
178
|
}
|
|
152
179
|
|
|
153
180
|
/** Place a freshly built dist into the gh-pages tree. */
|
|
154
|
-
export function placeTarget({ siteDir, channel, version, distDir, sha, ref }) {
|
|
181
|
+
export function placeTarget({ siteDir, channel, version, distDir, sha, ref, repo }) {
|
|
155
182
|
if (!existsSync(distDir)) throw new Error(`dist not found: ${distDir}`)
|
|
156
183
|
if (channel === "dev") {
|
|
157
184
|
const devDir = join(siteDir, "dev")
|
|
@@ -179,7 +206,7 @@ export function placeTarget({ siteDir, channel, version, distDir, sha, ref }) {
|
|
|
179
206
|
replaceDir(versionDir, distDir)
|
|
180
207
|
writeFileSync(
|
|
181
208
|
join(versionDir, "showcase-meta.json"),
|
|
182
|
-
JSON.stringify({ version, publishedAt: new Date().toISOString(), sha }, null, 2) + "\n",
|
|
209
|
+
JSON.stringify({ version, publishedAt: new Date().toISOString(), sha, repo }, null, 2) + "\n",
|
|
183
210
|
)
|
|
184
211
|
const latestDir = join(siteDir, "latest")
|
|
185
212
|
replaceDir(latestDir, distDir)
|
|
@@ -211,9 +238,9 @@ function sleep(ms) {
|
|
|
211
238
|
}
|
|
212
239
|
|
|
213
240
|
/** Regenerate versions.json (release runs only), then commit + push with rebase-retry. */
|
|
214
|
-
export function finalize({ siteDir, updateVersions, push, commitMessage, remoteBranch = "gh-pages" }) {
|
|
241
|
+
export function finalize({ siteDir, updateVersions, push, commitMessage, remoteBranch = "gh-pages", repo }) {
|
|
215
242
|
if (updateVersions) {
|
|
216
|
-
const versions = buildVersionsManifest(siteDir)
|
|
243
|
+
const versions = buildVersionsManifest(siteDir, repo)
|
|
217
244
|
writeFileSync(join(siteDir, "versions.json"), JSON.stringify(versions, null, 2) + "\n")
|
|
218
245
|
}
|
|
219
246
|
git(["add", "-A"], siteDir)
|
|
@@ -282,6 +309,7 @@ function main() {
|
|
|
282
309
|
distDir: args.dist,
|
|
283
310
|
sha: args.sha,
|
|
284
311
|
ref: args.ref,
|
|
312
|
+
repo: args.repo,
|
|
285
313
|
})
|
|
286
314
|
console.log("placed:", JSON.stringify(res))
|
|
287
315
|
break
|
|
@@ -297,6 +325,7 @@ function main() {
|
|
|
297
325
|
updateVersions: !!args["update-versions"],
|
|
298
326
|
push: !!args.push,
|
|
299
327
|
commitMessage: args["commit-message"],
|
|
328
|
+
repo: args.repo,
|
|
300
329
|
})
|
|
301
330
|
console.log("finalize:", JSON.stringify(res))
|
|
302
331
|
break
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react"
|
|
2
|
-
import {
|
|
2
|
+
import { useAmbientGristHandshake, type GristWidgetSnapshot } from "grist-widget-sdk/advanced"
|
|
3
3
|
|
|
4
4
|
import { deriveDisplayStatus, type HandshakeDisplayStatus } from "@/lib/grist-handshake-status"
|
|
5
5
|
import { cn } from "@/lib/utils"
|
|
@@ -83,8 +83,7 @@ function ChipVisual({ display, secondsLeft }: { display: HandshakeDisplayStatus;
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function RealChipContents() {
|
|
87
|
-
const { snapshot } = useGristHandshakeContext()
|
|
86
|
+
function RealChipContents({ snapshot }: { snapshot: GristWidgetSnapshot }) {
|
|
88
87
|
const display = deriveDisplayStatus(snapshot.lifecycle)
|
|
89
88
|
const retryAttempts = display.kind === "retrying" ? display.attempts : 0
|
|
90
89
|
const secondsLeft = useRetryCountdown(retryAttempts, display.kind === "retrying")
|
|
@@ -93,25 +92,25 @@ function RealChipContents() {
|
|
|
93
92
|
|
|
94
93
|
/**
|
|
95
94
|
* Small pill showing live Grist handshake status while actually embedded in
|
|
96
|
-
* Grist.
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
95
|
+
* Grist. Must be mounted *inside* `<GristWidgetProvider>` -- it only observes
|
|
96
|
+
* that provider's own handshake manager (`useAmbientGristHandshake()`,
|
|
97
|
+
* `grist-widget-sdk/advanced`) and never mounts its own, so it can't ever run
|
|
98
|
+
* a second, competing `grist.ready()` call. Renders nothing outside a
|
|
99
|
+
* provider.
|
|
101
100
|
*/
|
|
102
101
|
export function GristStatusChip({ className }: { className?: string }) {
|
|
102
|
+
const handshake = useAmbientGristHandshake()
|
|
103
|
+
if (!handshake) return null
|
|
103
104
|
return (
|
|
104
|
-
<
|
|
105
|
-
<div
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<RealChipContents />
|
|
113
|
-
</div>
|
|
105
|
+
<div className="pointer-events-none fixed top-2 right-2 z-50">
|
|
106
|
+
<div
|
|
107
|
+
className={cn(
|
|
108
|
+
"flex items-center gap-1.5 rounded-full border bg-background/90 px-2.5 py-1 text-xs text-muted-foreground shadow-sm backdrop-blur",
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
<RealChipContents snapshot={handshake.snapshot} />
|
|
114
113
|
</div>
|
|
115
|
-
</
|
|
114
|
+
</div>
|
|
116
115
|
)
|
|
117
116
|
}
|
package/template/src/main.tsx
CHANGED
|
@@ -35,20 +35,18 @@ createRoot(document.getElementById("root")!).render(
|
|
|
35
35
|
<TemplateLanding />
|
|
36
36
|
</div>
|
|
37
37
|
) : isEmbedded ? (
|
|
38
|
-
|
|
38
|
+
<GristWidgetProvider options={GRIST_OPTIONS}>
|
|
39
39
|
<GristStatusChip />
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
>
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</GristWidgetProvider>
|
|
51
|
-
</>
|
|
40
|
+
<GristBoundary
|
|
41
|
+
gate={GRIST_OPTIONS.columns?.length ? "canRender" : "ready"}
|
|
42
|
+
>
|
|
43
|
+
<div className="min-h-full w-full bg-background text-foreground">
|
|
44
|
+
<GristSdkAlerts>
|
|
45
|
+
<App />
|
|
46
|
+
</GristSdkAlerts>
|
|
47
|
+
</div>
|
|
48
|
+
</GristBoundary>
|
|
49
|
+
</GristWidgetProvider>
|
|
52
50
|
) : (
|
|
53
51
|
<div className="min-h-full w-full bg-background text-foreground">
|
|
54
52
|
<ChannelNotice channel={channel} hubPath={hubPath} />
|