create-grist-widget 0.2.12 → 0.2.13
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 +5 -0
- package/package.json +1 -1
- package/template/.github/workflows/deploy.yml +13 -4
- package/template/README.md +6 -0
- package/template/scripts/deploy.mjs +12 -2
- package/template/src/components/grist-status-chip.tsx +18 -72
- package/template/src/components/template-landing.tsx +7 -4
package/README.md
CHANGED
|
@@ -46,6 +46,11 @@ simplest way to get from zero to a deployed widget:
|
|
|
46
46
|
folder `/ (root)`. This can only be set once `gh-pages` exists, which is
|
|
47
47
|
why it's a step *after* the first push, not before.
|
|
48
48
|
|
|
49
|
+
> ⚠️ **Not `main`.** Pointing Pages at `main` serves this repo's raw,
|
|
50
|
+
> unbuilt source instead of the built site — the symptom is a blank page
|
|
51
|
+
> with a `/src/main.tsx` 404 in the browser console, even though the
|
|
52
|
+
> workflow itself reports success.
|
|
53
|
+
|
|
49
54
|
Once that's saved, your widget is live at two URLs:
|
|
50
55
|
|
|
51
56
|
- `https://<owner>.github.io/<repo>/latest/` — mutable, always points at
|
package/package.json
CHANGED
|
@@ -13,8 +13,11 @@ name: Deploy widget to GitHub Pages
|
|
|
13
13
|
# One-time setup this workflow can't do for you: enable Pages (Settings ->
|
|
14
14
|
# Pages -> Source: Deploy from a branch -> gh-pages -> / (root); this workflow
|
|
15
15
|
# creates the gh-pages branch itself if missing, but Pages must be pointed at
|
|
16
|
-
# it once
|
|
17
|
-
#
|
|
16
|
+
# it once -- NOT main, which serves this repo's raw unbuilt source instead
|
|
17
|
+
# (symptom: a blank page with a 404 for /src/main.tsx in the browser
|
|
18
|
+
# console, even though this workflow reports success) and make sure Actions
|
|
19
|
+
# has write access (Settings -> Actions -> General -> Workflow permissions ->
|
|
20
|
+
# Read and write permissions).
|
|
18
21
|
|
|
19
22
|
on:
|
|
20
23
|
push:
|
|
@@ -56,10 +59,16 @@ jobs:
|
|
|
56
59
|
uses: actions/setup-node@v4
|
|
57
60
|
with:
|
|
58
61
|
node-version: 22
|
|
59
|
-
cache: pnpm
|
|
60
62
|
|
|
63
|
+
# Not --frozen-lockfile, and no `cache: pnpm` above: a freshly
|
|
64
|
+
# scaffolded repo never has a committed pnpm-lock.yaml (the CLI's
|
|
65
|
+
# auto-commit runs before anyone's first `pnpm install`), and
|
|
66
|
+
# --frozen-lockfile/cache: pnpm both hard-require one to exist —
|
|
67
|
+
# found live, the very first real deploy failed with "Dependencies
|
|
68
|
+
# lock file is not found". A plain install resolves fresh every run
|
|
69
|
+
# regardless of whether a lockfile was ever committed.
|
|
61
70
|
- name: Install dependencies
|
|
62
|
-
run: pnpm install
|
|
71
|
+
run: pnpm install
|
|
63
72
|
|
|
64
73
|
- name: Clone gh-pages into ./site
|
|
65
74
|
run: |
|
package/template/README.md
CHANGED
|
@@ -92,6 +92,12 @@ automatically.
|
|
|
92
92
|
→ `/ (root)`. The workflow creates the `gh-pages` branch itself the first
|
|
93
93
|
time it runs (if it doesn't exist yet), but Pages needs to be pointed at
|
|
94
94
|
it once.
|
|
95
|
+
> ⚠️ **Not `main`.** If Pages is left on (or accidentally set to) `main`,
|
|
96
|
+
> it serves this repo's raw, unbuilt source — including a script tag
|
|
97
|
+
> pointing at `/src/main.tsx` — instead of the built site. The symptom is
|
|
98
|
+
> a blank/black page with a 404 for `/src/main.tsx` in the browser
|
|
99
|
+
> console, even though the workflow itself reports success (it pushed the
|
|
100
|
+
> right build to `gh-pages`; Pages is just reading from the wrong place).
|
|
95
101
|
2. **Settings → Actions → General → Workflow permissions** → "Read and write
|
|
96
102
|
permissions". New repos sometimes default the workflow's token to
|
|
97
103
|
read-only, which would fail the push to `gh-pages` with a 403.
|
|
@@ -133,12 +133,22 @@ export function placeTarget({ siteDir, channel, version, distDir, sha, ref }) {
|
|
|
133
133
|
}
|
|
134
134
|
return { placed: "dev" }
|
|
135
135
|
}
|
|
136
|
-
// release
|
|
136
|
+
// release: immutable v<version>/, mutable latest/, and the same build
|
|
137
|
+
// placed directly at the site root too. main.tsx renders the showcase hub
|
|
138
|
+
// (TemplateLanding) at any URL with no recognized channel suffix — the
|
|
139
|
+
// bare root included — so without this, a real scaffolded repo's own
|
|
140
|
+
// https://owner.github.io/repo/ has nothing deployed there at all and
|
|
141
|
+
// 404s instead of ever reaching that hub. Reusing the release dist here is
|
|
142
|
+
// exactly like latest/ reusing it: the asset URLs it references (under
|
|
143
|
+
// v<version>/assets/) already exist from the versionDir placement above,
|
|
144
|
+
// so nothing 404s.
|
|
137
145
|
const versionDir = join(siteDir, `v${version}`)
|
|
138
146
|
replaceDir(versionDir, distDir)
|
|
139
147
|
const latestDir = join(siteDir, "latest")
|
|
140
148
|
replaceDir(latestDir, distDir)
|
|
141
|
-
|
|
149
|
+
mkdirSync(siteDir, { recursive: true })
|
|
150
|
+
cpSync(distDir, siteDir, { recursive: true })
|
|
151
|
+
return { placed: `v${version}`, latest: "latest", root: true }
|
|
142
152
|
}
|
|
143
153
|
|
|
144
154
|
/**
|
|
@@ -49,7 +49,7 @@ function StatusDot({ pulsing, className }: { pulsing: boolean; className: string
|
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** Pure render of a display status
|
|
52
|
+
/** Pure render of a display status. */
|
|
53
53
|
function ChipVisual({ display, secondsLeft }: { display: HandshakeDisplayStatus; secondsLeft: number }) {
|
|
54
54
|
switch (display.kind) {
|
|
55
55
|
case "connecting":
|
|
@@ -91,81 +91,27 @@ function RealChipContents() {
|
|
|
91
91
|
return <ChipVisual display={display} secondsLeft={secondsLeft} />
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// A fixed, looping sequence for the showcase hub -- there's no real Grist to
|
|
95
|
-
// connect to on that page (it's shown precisely because a real widget isn't
|
|
96
|
-
// embedded), so this is a cosmetic demo of the same visual states a real
|
|
97
|
-
// widget's chip goes through, not a real handshake attempt.
|
|
98
|
-
const DEMO_SEQUENCE: readonly { display: HandshakeDisplayStatus; ms: number }[] = [
|
|
99
|
-
{ display: { kind: "connecting" }, ms: 1500 },
|
|
100
|
-
{ display: { kind: "retrying", attempts: 1 }, ms: 3000 },
|
|
101
|
-
{ display: { kind: "connected" }, ms: 3500 },
|
|
102
|
-
]
|
|
103
|
-
|
|
104
|
-
function DemoChipContents() {
|
|
105
|
-
const [step, setStep] = useState(0)
|
|
106
|
-
const [countdownState, setCountdownState] = useState({ step, secondsLeft: 3 })
|
|
107
|
-
const current = DEMO_SEQUENCE[step]
|
|
108
|
-
|
|
109
|
-
// Reset the mini countdown the moment the demo cycle enters "retrying" --
|
|
110
|
-
// an "adjust state during render" pattern, not a setState-in-effect.
|
|
111
|
-
if (current.display.kind === "retrying" && countdownState.step !== step) {
|
|
112
|
-
setCountdownState({ step, secondsLeft: 3 })
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
const advance = setTimeout(() => {
|
|
117
|
-
setStep((s) => (s + 1) % DEMO_SEQUENCE.length)
|
|
118
|
-
}, current.ms)
|
|
119
|
-
return () => clearTimeout(advance)
|
|
120
|
-
}, [step, current.ms])
|
|
121
|
-
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
if (current.display.kind !== "retrying") return
|
|
124
|
-
const id = setInterval(() => {
|
|
125
|
-
setCountdownState((s) => ({ ...s, secondsLeft: s.secondsLeft > 0 ? s.secondsLeft - 1 : 0 }))
|
|
126
|
-
}, 1000)
|
|
127
|
-
return () => clearInterval(id)
|
|
128
|
-
}, [step, current.display.kind])
|
|
129
|
-
|
|
130
|
-
return <ChipVisual display={current.display} secondsLeft={countdownState.secondsLeft} />
|
|
131
|
-
}
|
|
132
|
-
|
|
133
94
|
/**
|
|
134
|
-
* Small pill showing Grist handshake status
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* it never drives the real widget's connection, just reflects it.
|
|
141
|
-
* - `demo`: a cosmetic, looping cycle through the same visual states, used
|
|
142
|
-
* on the showcase hub page where no real Grist connection is ever
|
|
143
|
-
* attempted -- a reference to the tech behind the template, not a status
|
|
144
|
-
* readout.
|
|
95
|
+
* Small pill showing live Grist handshake status while actually embedded in
|
|
96
|
+
* Grist. Mounts its own `<GristHandshakeProvider>` alongside
|
|
97
|
+
* `<GristWidgetProvider>` -- documented (grist-widget-sdk/advanced) as safe
|
|
98
|
+
* to coexist, since both share the page's `ensureGristReady()` singleton.
|
|
99
|
+
* Purely observational: it never drives the real widget's connection, just
|
|
100
|
+
* reflects it.
|
|
145
101
|
*/
|
|
146
|
-
export function GristStatusChip({
|
|
147
|
-
demo = false,
|
|
148
|
-
className,
|
|
149
|
-
}: {
|
|
150
|
-
demo?: boolean
|
|
151
|
-
className?: string
|
|
152
|
-
}) {
|
|
153
|
-
const pill = (
|
|
154
|
-
<div
|
|
155
|
-
className={cn(
|
|
156
|
-
"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",
|
|
157
|
-
className,
|
|
158
|
-
)}
|
|
159
|
-
>
|
|
160
|
-
{demo ? <DemoChipContents /> : <RealChipContents />}
|
|
161
|
-
</div>
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if (demo) return pill
|
|
165
|
-
|
|
102
|
+
export function GristStatusChip({ className }: { className?: string }) {
|
|
166
103
|
return (
|
|
167
104
|
<GristHandshakeProvider>
|
|
168
|
-
<div className="pointer-events-none fixed top-2 right-2 z-50">
|
|
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 />
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
169
115
|
</GristHandshakeProvider>
|
|
170
116
|
)
|
|
171
117
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useState } from "react"
|
|
2
2
|
|
|
3
|
-
import { GristStatusChip } from "@/components/grist-status-chip"
|
|
4
3
|
import { Button } from "@/components/ui/button"
|
|
5
4
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
6
5
|
import { DEV_URL, formatDate, useVersions, versionUrl } from "@/lib/showcase-versions"
|
|
@@ -70,9 +69,6 @@ export function TemplateLanding() {
|
|
|
70
69
|
return (
|
|
71
70
|
<div className="mx-auto max-w-2xl px-6 py-16">
|
|
72
71
|
<header className="mb-10 text-center">
|
|
73
|
-
<div className="mb-4 flex justify-center">
|
|
74
|
-
<GristStatusChip demo />
|
|
75
|
-
</div>
|
|
76
72
|
<p className="mb-2 text-sm font-medium text-muted-foreground">
|
|
77
73
|
grist-widget-template
|
|
78
74
|
</p>
|
|
@@ -157,6 +153,13 @@ git push origin dev`}</code>
|
|
|
157
153
|
enable Pages: Settings → Pages → Source → "Deploy from a
|
|
158
154
|
branch" → branch <code className="text-foreground">gh-pages</code>{" "}
|
|
159
155
|
→ <code className="text-foreground">/ (root)</code>.
|
|
156
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
157
|
+
Not <code className="text-foreground">main</code> — that
|
|
158
|
+
serves this repo's raw, unbuilt source instead of the built
|
|
159
|
+
site (a blank page with a{" "}
|
|
160
|
+
<code className="text-foreground">/src/main.tsx</code> 404
|
|
161
|
+
in the console).
|
|
162
|
+
</p>
|
|
160
163
|
<p className="mt-2 text-xs text-muted-foreground">
|
|
161
164
|
If that first run fails with a permissions error pushing to{" "}
|
|
162
165
|
<code className="text-foreground">gh-pages</code>, Settings
|