From 1ced205a04ab7cc0e99bb562c1c32c70c582cbc5 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 4 Nov 2026 10:02:21 -0800 Subject: [PATCH] Have the quickstart offer commit statuses, and host Dormouse The quickstart stopped at local setup, so following it got no statuses, and a push from a repository the service doesn't list got a 404. It now offers the rest: the App, the secret, the workflow, and what approving means. Dormouse is listed, with a database of its own. The action no longer installs pnpm globally, which would have replaced the version a repository pins for its own build. Co-Authored-By: Claude Opus 5.5 --- .github/actions/selfie/action.yml | 4 +- README.md | 2 +- cli/skills/quickstart/SKILL.md | 75 +++++++++++++++++++++++++++++-- docs/specs/cloud.md | 1 + ui/src/worker/index.ts | 2 + ui/wrangler.toml | 5 +++ 6 files changed, 82 insertions(+), 7 deletions(-) diff --git a/.github/actions/selfie/action.yml b/.github/actions/selfie/action.yml index d3988cc..918f483 100644 --- a/.github/actions/selfie/action.yml +++ b/.github/actions/selfie/action.yml @@ -15,8 +15,8 @@ runs: run: | selfie_root="$(cd "$GITHUB_ACTION_PATH/../../.." && pwd)" echo "SELFIE_ROOT=$selfie_root" >> "$GITHUB_ENV" - npm install --global pnpm@10.30.1 - (cd "$selfie_root" && pnpm install --frozen-lockfile --filter "selfie-cli..." --ignore-scripts) + # Not installed globally, since a repository may pin another pnpm for its own build. + (cd "$selfie_root" && npx --yes pnpm@10.30.1 install --frozen-lockfile --filter "selfie-cli..." --ignore-scripts) - name: Build the Storybook and render every story shell: bash # Exits 0 once a batch is recorded, even with failed stories, which the status says. diff --git a/README.md b/README.md index 1bce294..87e6992 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ pnpm install there. Then run this, and follow what it prints: node --disable-warning=ExperimentalWarning /cli/src/main.ts skill quickstart ``` -It works out how your Storybook builds, writes `selfie.config.json`, proves that a story really renders, and warns you about anything in your way, such as uncommitted changes. It won't commit anything without asking. You'll get a URL to open, and what to do next. +It works out how your Storybook builds, writes `selfie.config.json`, proves that a story really renders, warns you about anything in your way, such as uncommitted changes, and offers to set up commit statuses on GitHub. It won't commit anything without asking. You'll get a URL to open, and what to do next. You'll need Node 26 or later, pnpm, and Docker. Selfie isn't on npm yet, which is why this starts from a checkout. diff --git a/cli/skills/quickstart/SKILL.md b/cli/skills/quickstart/SKILL.md index 8f76b0f..58ed41b 100644 --- a/cli/skills/quickstart/SKILL.md +++ b/cli/skills/quickstart/SKILL.md @@ -1,13 +1,13 @@ --- name: quickstart -description: Set up Selfie in a repository with a Storybook, and get a person to their first rendered story. Use when someone asks to set up, install, or try Selfie. Works out the configuration, checks it really renders, and warns about anything that will get in their way. +description: Set up Selfie in a repository with a Storybook, get a person to their first rendered story, and offer commit statuses on GitHub. Use when someone asks to set up, install, or try Selfie. Works out the configuration, checks it really renders, and warns about anything that will get in their way. --- # Set up Selfie Get this repository to where a person can open Selfie and see their stories. Work it out yourself and don't ask for what you can find. Say what you found and every warning at the end, in plain words. -Never commit, stash, delete, or install a Storybook without asking. Those are the person's to decide. Writing `selfie.config.json` is the one change you're here to make. +Never commit, stash, delete, or install a Storybook without asking. Those are the person's to decide. Writing `selfie.config.json` is the one change you're here to make, until the person asks for [commit statuses](#6-offer-commit-statuses). Below, `selfie` means however it runs here: `selfie` if it's on the PATH, or `node --disable-warning=ExperimentalWarning /cli/src/main.ts`. @@ -63,7 +63,7 @@ The first render downloads the browser, which can take minutes, so say that befo Check each of these, and warn about what applies: -- **Uncommitted changes:** `git status --porcelain`, which counts untracked files, and now includes `selfie.config.json`. Rendering still works, but every render is marked as uncommitted, so what it shows may not match the commit. List the files, and separate the config you just wrote from what was already there. Don't touch them: say what to commit or ignore. +- **Uncommitted changes:** `git status --porcelain`, which counts untracked files, and now includes `selfie.config.json`. Rendering still works, but every render is marked as uncommitted, so what it shows may not match the commit, and `selfie push` won't send it. Say that a folder that's always there, such as one of worktrees, belongs in `.gitignore`. List the files, and separate the config you just wrote from what was already there. Don't touch them: say what to commit or ignore. - **The built Storybook isn't ignored:** `git check-ignore `. If it isn't, the built Storybook itself makes every render uncommitted. Say to add it to `.gitignore`. - **A shallow clone:** `git rev-parse --is-shallow-repository`. Approvals from before where its history stops can't be reached, so stories may ask again. - **A big Storybook:** say how many stories there are, and that rendering them all takes a while. Don't guess how long from one story. @@ -82,4 +82,71 @@ Then report, briefly: 2. To set what later renders compare with: **Render all**, **Review the newest batch**, **Approve all**. Any branch will do. 3. Then change a component, commit, restart `selfie`, and **Render all**. The tree shows only what changed since what was approved. -If `selfie flakes` reported anything, mention `selfie skill fix-flakes`. +If `selfie flakes` reported anything, mention `selfie skill fix-flakes`. Say that a story whose play function throws reads as failed, and can't be approved until it's fixed, so a project with some will show them on its first full render. + +Approvals made here stay here until they're pushed, so someone who goes on to step 6 approves on the site. + +## 6. Offer commit statuses + +Selfie can give every pushed commit a `selfie` status on GitHub, linked to its renders and its Storybook: red if a story failed or a change was rejected, yellow if something's undecided, green if it all matches what's approved. Offer this at the end, and do it only if the person says yes, since it commits a workflow and needs them to act on GitHub. + +Say first what it asks of them, then go through these in order, and stop and say so if one fails. + +1. **Check the service hosts this repository**, from the owner and name in its `origin`: + + ```sh + curl -s -o /dev/null -w '%{http_code}\n' https://hackathon.selfie.dev/api/repositories// + ``` + + 200 means it does. 404 means it doesn't, since the service only hosts the public repositories written into `ui/src/worker/index.ts`, so stop here and say so. + +2. **Have the person install the GitHub App on this repository.** It's theirs to do, at . It only writes statuses and reads commits. Wait until they say it's done. + +3. **Set the upload token as a repository secret**, which can upload and can't approve anything. It's kept in a file, so never print it: + + ```sh + gh secret set SELFIE_UPLOAD_TOKEN -R / < ~/.config/selfie-cloud/upload-token + ``` + + If that file isn't there, stop, and ask the person for the token. + +4. **Write `.github/workflows/selfie.yml`**, and fill in the step that installs the repository's dependencies, using its package manager at the version it pins: + + ```yaml + name: Selfie + on: + push: + branches: ["**"] + jobs: + selfie: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + with: + # What a commit reaches is what its approvals come from, so this needs all of its history. + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 26 + # Install the repository's dependencies here, such as `npm ci`, or `pnpm/action-setup` then `pnpm install --frozen-lockfile`. + - uses: diffplug/selfie-hackathon/.github/actions/selfie@main + with: + upload-token: ${{ secrets.SELFIE_UPLOAD_TOKEN }} + ``` + + It needs Node 26 for Selfie, and arm64 so that its images match the pinned environment. If the repository pins another Node for its own scripts, leave that as it is, and say that this combination hasn't been tried if it's the first. + +5. **Ask before committing and pushing** `selfie.config.json`, the workflow, and any `.gitignore` change, on a branch or wherever the person says. Pushing starts the first run, which takes a few minutes, and more for a big Storybook. + +6. **Check the run**, with `gh run list --branch ` and `gh run view --log-failed`. Once it's done, the commit has a status: + + ```sh + gh api repos///commits//status --jq '.statuses[] | [.context, .state, .description] | join(" | ")' + ``` + +Then tell them: + +- Every story reads as added at first, so the status is yellow, or red if any story failed. +- To approve, follow the status's **Details** link, sign in with the writer's password, and **Approve all**. That's what later commits compare with, and a merge commit reaches it by itself, as long as the repository merges with merge commits rather than squashing or rebasing. +- To see those approvals locally: `SELFIE_REMOTE=https://hackathon.selfie.dev selfie pull`. +- The `selfie` status only blocks merging if they make it a required check in branch protection. diff --git a/docs/specs/cloud.md b/docs/specs/cloud.md index 3080dfc..4946efa 100644 --- a/docs/specs/cloud.md +++ b/docs/specs/cloud.md @@ -54,6 +54,7 @@ Source of truth: `commitStatusOf` in `ui/src/shared/commit-status.ts`, `publish` - **Must post one status per commit, in the context `selfie`, linking to its batch's page.** - **Must post it from the newest finished batch at that commit, at the end of a push and on every decision made here, after answering**, so nobody waits on GitHub to see their decision. - **Must keep a decision if GitHub can't take its status**, and log why. The next decision or push says it again. +- **Must leave a repository's own tools alone in that action**, installing Selfie's dependencies with its own pnpm, since a repository may pin another for its build. - **Must render on every pushed branch, through `.github/actions/selfie` on an arm64 runner, with all of a commit's history**, so what's pinned matches (`docs/specs/render.md`) and what it reaches is known. A failed story is a result the status says, and only a push that fails fails the job. - **Never make a commit's status required from here.** Whether `selfie` blocks a merge is the repository owner's choice, in branch protection. diff --git a/ui/src/worker/index.ts b/ui/src/worker/index.ts index d85b77f..9fef704 100644 --- a/ui/src/worker/index.ts +++ b/ui/src/worker/index.ts @@ -9,6 +9,7 @@ import { githubApp } from "./github.ts"; interface Env { readonly ASSETS: { fetch(request: Request): Promise }; readonly BLOBS: R2BucketLike; + readonly DB_1183663019: D1DatabaseLike; readonly DB_1442175397: D1DatabaseLike; readonly DB_1445291541: D1DatabaseLike; readonly REVIEW_ORIGIN: string; @@ -28,6 +29,7 @@ export default { projects: [ { id: "1442175397", owner: "diffplug", name: "selfie-hackathon", defaultBranch: "main", db: env.DB_1442175397 }, { id: "1445291541", owner: "diffplug", name: "selfie-sandbox", defaultBranch: "main", db: env.DB_1445291541 }, + { id: "1183663019", owner: "diffplug", name: "dormouse", defaultBranch: "main", db: env.DB_1183663019 }, ], bucket: env.BLOBS, reviewOrigin: env.REVIEW_ORIGIN, diff --git a/ui/wrangler.toml b/ui/wrangler.toml index 149f3ba..47d8d9b 100644 --- a/ui/wrangler.toml +++ b/ui/wrangler.toml @@ -24,6 +24,11 @@ binding = "DB_1442175397" database_name = "selfie-project-1442175397" database_id = "89537c43-e95e-44d6-b2ec-b35b489d82b9" +[[d1_databases]] +binding = "DB_1183663019" +database_name = "selfie-project-1183663019" +database_id = "2bee75dd-fa10-47a3-95f4-8912044bf801" + [[d1_databases]] binding = "DB_1445291541" database_name = "selfie-project-1445291541"