diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b4785ce --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,160 @@ +name: Release + +# Publishes @inkform/framework or @inkform/cli to public npm. +# +# Authentication is npm Trusted Publishing (OIDC) — there is NO npm token +# stored in this repository's secrets. GitHub mints a short-lived OIDC token +# for this specific workflow file, and npm accepts it only because the +# package's "Trusted publisher" settings on npmjs.com name this repo AND +# this exact filename (.github/workflows/release.yml). Renaming this file +# breaks publishing until the npm-side config is updated to match — that is +# the security property, not an accident. Publishes made this way also carry +# a provenance attestation automatically (the "Built and signed on GitHub +# Actions" badge on npm), with no --provenance flag needed. +# +# Trigger: push an annotated tag naming the package and its version. +# +# framework-v0.5.0 → publishes packages/framework +# cli-v0.5.0 → publishes packages/cli +# +# The tag's version MUST equal the version already committed in that +# package's package.json — the job refuses to guess. Bump, commit, push, +# THEN tag. workflow_dispatch runs the whole thing in --dry-run mode by +# default so you can rehearse a release without publishing anything. + +on: + push: + tags: + - 'framework-v*' + - 'cli-v*' + workflow_dispatch: + inputs: + package: + description: 'Which package to release' + required: true + type: choice + options: [framework, cli] + dry_run: + description: 'Dry run (pack and validate, publish nothing)' + required: true + type: boolean + default: true + +permissions: + contents: read + id-token: write # required: this is what lets npm verify the OIDC claim + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + + # Second gate, independent of npm. Add required reviewers to the + # "npm-publish" environment in Settings → Environments and every publish + # pauses for a human approval, so a tag push alone can never ship. + # Referencing an environment that doesn't exist yet is harmless. + environment: npm-publish + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + registry-url: 'https://registry.npmjs.org' + + # setup-node ships npm 10.x with Node 22; trusted publishing needs + # npm >= 11.5.1. Without this the publish falls back to looking for an + # auth token and fails with a confusing ENEEDAUTH. + - name: Use an npm that supports trusted publishing + run: | + npm install -g npm@latest + npm --version + + # Every ${{ }} value below is passed through `env:` and read as a + # shell variable, never expanded into the script body. GitHub splices + # expansions in as raw text before bash ever sees the file, so an + # inline `'${{ github.ref_name }}'` in a run block is a script-injection + # sink — a tag name containing a quote and a semicolon would execute. + # Only maintainers can push tags here, but a workflow holding npm + # publish rights shouldn't rely on that as its only defense. + - name: Resolve target package + id: target + env: + EVENT: ${{ github.event_name }} + INPUT_PACKAGE: ${{ inputs.package }} + REF: ${{ github.ref_name }} + run: | + set -euo pipefail + if [ "$EVENT" = 'workflow_dispatch' ]; then + SLUG="$INPUT_PACKAGE" + TAG_VERSION='' + else + SLUG="${REF%%-v*}" + TAG_VERSION="${REF#*-v}" + fi + + case "$SLUG" in + framework) DIR='packages/framework' ;; + cli) DIR='packages/cli' ;; + *) echo "::error::Unrecognized release target '$SLUG'"; exit 1 ;; + esac + + NAME=$(node -p "require('./$DIR/package.json').name") + VERSION=$(node -p "require('./$DIR/package.json').version") + + if [ -n "$TAG_VERSION" ] && [ "$TAG_VERSION" != "$VERSION" ]; then + echo "::error::Tag says v$TAG_VERSION but $DIR/package.json says $VERSION. Commit the version bump before tagging." + exit 1 + fi + + echo "dir=$DIR" >> "$GITHUB_OUTPUT" + echo "name=$NAME" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Releasing $NAME@$VERSION from $DIR" + + # npm rejects a re-publish of an existing version with a bare 403. + # Failing here instead says what actually went wrong. + - name: Refuse to republish an existing version + env: + NAME: ${{ steps.target.outputs.name }} + VERSION: ${{ steps.target.outputs.version }} + run: | + set -euo pipefail + if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then + echo "::error::$NAME@$VERSION is already on npm. Bump the version — published versions are immutable." + exit 1 + fi + echo "$NAME@$VERSION is unpublished. Proceeding." + + - run: npm ci + + # Same gates as ci.yml, re-run here on the exact commit being shipped. + # A green PR check is not proof that the tagged commit is green. + - run: npm run lint + - run: npm run typecheck + - run: npm test + - run: npm run build + - run: npm audit --audit-level=high + + - name: Preview tarball contents + env: + DIR: ${{ steps.target.outputs.dir }} + run: npm pack --dry-run --workspace "$DIR" + + - name: Publish to npm + if: ${{ github.event_name == 'push' || !inputs.dry_run }} + env: + DIR: ${{ steps.target.outputs.dir }} + run: npm publish --workspace "$DIR" + + - name: Dry run only — nothing published + if: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }} + env: + NAME: ${{ steps.target.outputs.name }} + VERSION: ${{ steps.target.outputs.version }} + run: echo "Dry run complete for $NAME@$VERSION. Re-run with dry_run unchecked, or push a tag, to publish." diff --git a/CHANGELOG.md b/CHANGELOG.md index 1215b00..74113c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,63 @@ All notable changes to this project are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions track `packages/framework`'s own `package.json`. +## [0.5.0] — 2026-08-30 + +### Added + +- **Every page is served as Markdown at its own URL** — append `.md` to any + docs page (or content-negotiate) and get the source back as clean Markdown. + Makes the whole site directly consumable by agents and LLM tooling without + scraping rendered HTML. +- **`@inkform/framework/markdown`** — a structural MDX-to-Markdown converter + that preserves link URLs and component labels instead of flattening them + away, plus `@inkform/framework/page-actions` (``): a per-page + *Copy Markdown* / *Open in…* control rendered above the page title. +- **Expanded AI tool menu** — a two-column menu driven by a single data + registry (`ai-tools.ts`) rather than hardcoded links: ChatGPT, Claude, + Google (AI Overview), and copy-the-command entries for Claude Code, + OpenCode, Codex, and Antigravity. Monochrome brand icons throughout. +- **`@inkform/framework/secondary-top-nav` and `/scrollable-top-nav`** — + unified secondary navigation with mobile scroll hints and de-duplicated + anchors/navbar links. + +### Changed + +- Copy actions give real feedback — copied-state on the button, with a + confetti flourish on success (tokenized colors, no hardcoded hex). +- Glyph and clipboard helpers deduplicated into shared modules. + +### Fixed + +- The VS Code MCP install link pointed at the wrong handler. +- The ChatGPT share link now uses the `prompt` parameter. +- Mobile *Open* menu is capped at `80vw` instead of overflowing the viewport. +- The left column of the AI menu now shares the right column's gutter off the + divider. +- **`@inkform/framework/reactions` resolves again.** The subpath export was + dropped from the exports map in 0.4.0's development while + `src/reactions.tsx` kept shipping, so the export documented in the package + README and in the guides resolved to nothing. Every export present in 0.4.0 + is present in 0.5.0 — this release is purely additive. +- **Scaffolded projects get the current framework.** Every template and + example declared `"@inkform/framework": "^0.3.0"`. For a 0.x package that + range means `>=0.3.0 <0.4.0`, so `npx @inkform/cli init` followed by + `npm install` resolved to 0.3.0 — no native API reference renderer, no MCP + server, no AI ask-box, no `llms.txt`. The CLI rewrites a scaffolded + project's `name` and `version` but never touched this range. Now `^0.5.0`. +- **Workspace shadowing fixed at the root.** The same stale range meant the + local `packages/framework` no longer satisfied what the templates and + examples asked for, so npm fetched a real 0.3.0 from the registry into each + of the six workspaces' own `node_modules` — shadowing the live source. This + is what `scripts/prune-workspace-shadows.mjs` had been deleting on every + `postinstall`; the lockfile is 139 lines lighter without those entries. The + script stays as a safety net, with its root-cause note corrected. + +### Security + +- 4 lockfile advisories patched (3 high, 1 moderate); archived templates + bumped to Next 16.2.12, clearing 54 Dependabot alerts. + ## [0.4.0] — 2026-07-21 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6a04d50..f3df7a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,7 @@ documentation theme that lands in the CLI's theme picker. - [Contributing to the CLI](#contributing-to-the-cli) - [Pull request process](#pull-request-process) - [Conventions](#conventions) +- [Releases (maintainers)](#releases-maintainers) - [Source mirror (maintainers)](#source-mirror-maintainers) --- @@ -388,6 +389,20 @@ chore: bump @inkform/framework to 0.4.1 --- +## Releases (maintainers) + +`@inkform/framework` and `@inkform/cli` are published to npm by +`.github/workflows/release.yml`, triggered by pushing a version tag +(`framework-v0.5.0`, `cli-v0.5.0`). It re-runs the full CI gate against the +tagged commit, then publishes with npm Trusted Publishing — a short-lived +OIDC token, no npm secret stored in this repository, and a provenance +attestation on every release. + +Nobody publishes from a laptop. Full procedure and the one-time npm/GitHub +setup: [`packages/framework/PUBLISHING.md`](packages/framework/PUBLISHING.md). + +--- + ## Source mirror (maintainers) This public repo is kept in sync with a private working monorepo via diff --git a/examples/inkform-docs/package.json b/examples/inkform-docs/package.json index 95f8d01..71a0a6d 100644 --- a/examples/inkform-docs/package.json +++ b/examples/inkform-docs/package.json @@ -12,7 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", diff --git a/examples/markdown-docs/package.json b/examples/markdown-docs/package.json index 4a959a3..a3868a5 100644 --- a/examples/markdown-docs/package.json +++ b/examples/markdown-docs/package.json @@ -12,7 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", diff --git a/examples/pokeapi-docs/package.json b/examples/pokeapi-docs/package.json index 25a733d..af4ab37 100644 --- a/examples/pokeapi-docs/package.json +++ b/examples/pokeapi-docs/package.json @@ -13,7 +13,7 @@ "test:e2e": "playwright test" }, "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", diff --git a/package-lock.json b/package-lock.json index c243a64..58251c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "version": "0.2.0", "license": "MIT", "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", @@ -40,34 +40,12 @@ "typescript": "^5" } }, - "examples/inkform-docs/node_modules/@inkform/framework": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@inkform/framework/-/framework-0.3.0.tgz", - "integrity": "sha512-BRLtjNPS5UT06IToODYWHm1nSg6MYJSDMXe6qWnSNqPexFCDlLDbbeq5BreDNHR+ASJGQ2Q9t3ckukT+0tVu0A==", - "license": "MIT", - "dependencies": { - "gray-matter": "^4.0.3", - "next-mdx-remote": "^6.0.0", - "rehype-pretty-code": "^0.14.1", - "rehype-slug": "^6.0.0", - "remark-directive": "^3.0.0", - "remark-gfm": "^4.0.1", - "shiki": "^1.24.0", - "unist-util-visit": "^5.0.0", - "yaml": "^2.6.0" - }, - "peerDependencies": { - "next": ">=16", - "react": ">=19", - "react-dom": ">=19" - } - }, "examples/markdown-docs": { "name": "@inkform/example-markdown", "version": "0.2.0", "license": "MIT", "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", @@ -81,34 +59,12 @@ "typescript": "^5" } }, - "examples/markdown-docs/node_modules/@inkform/framework": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@inkform/framework/-/framework-0.3.0.tgz", - "integrity": "sha512-BRLtjNPS5UT06IToODYWHm1nSg6MYJSDMXe6qWnSNqPexFCDlLDbbeq5BreDNHR+ASJGQ2Q9t3ckukT+0tVu0A==", - "license": "MIT", - "dependencies": { - "gray-matter": "^4.0.3", - "next-mdx-remote": "^6.0.0", - "rehype-pretty-code": "^0.14.1", - "rehype-slug": "^6.0.0", - "remark-directive": "^3.0.0", - "remark-gfm": "^4.0.1", - "shiki": "^1.24.0", - "unist-util-visit": "^5.0.0", - "yaml": "^2.6.0" - }, - "peerDependencies": { - "next": ">=16", - "react": ">=19", - "react-dom": ">=19" - } - }, "examples/pokeapi-docs": { "name": "@inkform/example-pokeapi", "version": "0.2.0", "license": "MIT", "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", @@ -123,28 +79,6 @@ "typescript": "^5" } }, - "examples/pokeapi-docs/node_modules/@inkform/framework": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@inkform/framework/-/framework-0.3.0.tgz", - "integrity": "sha512-BRLtjNPS5UT06IToODYWHm1nSg6MYJSDMXe6qWnSNqPexFCDlLDbbeq5BreDNHR+ASJGQ2Q9t3ckukT+0tVu0A==", - "license": "MIT", - "dependencies": { - "gray-matter": "^4.0.3", - "next-mdx-remote": "^6.0.0", - "rehype-pretty-code": "^0.14.1", - "rehype-slug": "^6.0.0", - "remark-directive": "^3.0.0", - "remark-gfm": "^4.0.1", - "shiki": "^1.24.0", - "unist-util-visit": "^5.0.0", - "yaml": "^2.6.0" - }, - "peerDependencies": { - "next": ">=16", - "react": ">=19", - "react-dom": ">=19" - } - }, "node_modules/@ai-sdk/anthropic": { "version": "4.0.21", "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.21.tgz", @@ -7720,7 +7654,7 @@ }, "packages/framework": { "name": "@inkform/framework", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "dependencies": { "@ai-sdk/anthropic": "^4.0.16", @@ -7764,7 +7698,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", @@ -7778,34 +7712,12 @@ "typescript": "^5" } }, - "templates/canopy/node_modules/@inkform/framework": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@inkform/framework/-/framework-0.3.0.tgz", - "integrity": "sha512-BRLtjNPS5UT06IToODYWHm1nSg6MYJSDMXe6qWnSNqPexFCDlLDbbeq5BreDNHR+ASJGQ2Q9t3ckukT+0tVu0A==", - "license": "MIT", - "dependencies": { - "gray-matter": "^4.0.3", - "next-mdx-remote": "^6.0.0", - "rehype-pretty-code": "^0.14.1", - "rehype-slug": "^6.0.0", - "remark-directive": "^3.0.0", - "remark-gfm": "^4.0.1", - "shiki": "^1.24.0", - "unist-util-visit": "^5.0.0", - "yaml": "^2.6.0" - }, - "peerDependencies": { - "next": ">=16", - "react": ">=19", - "react-dom": ">=19" - } - }, "templates/galley": { "name": "@inkform/theme-galley", "version": "0.1.0", "license": "MIT", "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", @@ -7819,34 +7731,12 @@ "typescript": "^5" } }, - "templates/galley/node_modules/@inkform/framework": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@inkform/framework/-/framework-0.3.0.tgz", - "integrity": "sha512-BRLtjNPS5UT06IToODYWHm1nSg6MYJSDMXe6qWnSNqPexFCDlLDbbeq5BreDNHR+ASJGQ2Q9t3ckukT+0tVu0A==", - "license": "MIT", - "dependencies": { - "gray-matter": "^4.0.3", - "next-mdx-remote": "^6.0.0", - "rehype-pretty-code": "^0.14.1", - "rehype-slug": "^6.0.0", - "remark-directive": "^3.0.0", - "remark-gfm": "^4.0.1", - "shiki": "^1.24.0", - "unist-util-visit": "^5.0.0", - "yaml": "^2.6.0" - }, - "peerDependencies": { - "next": ">=16", - "react": ">=19", - "react-dom": ">=19" - } - }, "templates/shadcn": { "name": "@inkform/theme-shadcn", "version": "0.1.0", "license": "MIT", "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", @@ -7859,28 +7749,6 @@ "pagefind": "^1.5.2", "typescript": "^5" } - }, - "templates/shadcn/node_modules/@inkform/framework": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@inkform/framework/-/framework-0.3.0.tgz", - "integrity": "sha512-BRLtjNPS5UT06IToODYWHm1nSg6MYJSDMXe6qWnSNqPexFCDlLDbbeq5BreDNHR+ASJGQ2Q9t3ckukT+0tVu0A==", - "license": "MIT", - "dependencies": { - "gray-matter": "^4.0.3", - "next-mdx-remote": "^6.0.0", - "rehype-pretty-code": "^0.14.1", - "rehype-slug": "^6.0.0", - "remark-directive": "^3.0.0", - "remark-gfm": "^4.0.1", - "shiki": "^1.24.0", - "unist-util-visit": "^5.0.0", - "yaml": "^2.6.0" - }, - "peerDependencies": { - "next": ">=16", - "react": ">=19", - "react-dom": ">=19" - } } } } diff --git a/packages/framework/PUBLISHING.md b/packages/framework/PUBLISHING.md index 2e4f135..cfbe4a7 100644 --- a/packages/framework/PUBLISHING.md +++ b/packages/framework/PUBLISHING.md @@ -1,84 +1,125 @@ # Publishing — `@inkform/framework` + `@inkform/cli` -Two packages in this monorepo are actually published to public npm under the -`@inkform` org (org already exists on npmjs.com): - -- **`packages/framework`** → `@inkform/framework` (MIT, `private: false`, - `publishConfig.access: public`). Ships TypeScript source directly; - consumers add `transpilePackages: ['@inkform/framework']` (no build step). -- **`packages/cli`** → `@inkform/cli` (MIT, `private: false`), bin command - `inkform-docs`. Scaffolds new projects by fetching a template directly from - GitHub (`github:inkform-dev/framework/templates/` via `giget`) — it - does **not** fetch the 5 themes or 2 examples as npm packages. Those - (`templates/*`, `examples/*`) are `private: true` and stay that way; they - are not meant to be installed from npm at all, only copied by the CLI or - cloned directly. - -This is the only publish blocker left from earlier passes (tracked as "the -temporary dependency bridge" — every consumer today declares -`"@inkform/framework": "npm:@freewrite-cms/framework@^0.2.0"`, an alias to -the last real published snapshot, since `@inkform/framework` itself has never -been published). Publishing closes that out permanently. - -## Prerequisites (one-time, needs your own npm login — not something an -## agent session can do; publishing to public npm is a real, hard-to-reverse -## action) +Two packages in this monorepo are published to public npm under the +`@inkform` org: -```bash -npm whoami # confirm you're logged in as an @inkform org member -# if not: -npm login -``` +- **`packages/framework`** → [`@inkform/framework`](https://www.npmjs.com/package/@inkform/framework) + (MIT, `publishConfig.access: public`). Ships TypeScript source directly; + consumers add `transpilePackages: ['@inkform/framework']`. No build step, + so there is nothing to compile before publishing. +- **`packages/cli`** → [`@inkform/cli`](https://www.npmjs.com/package/@inkform/cli), + bin command `inkform-docs`. Scaffolds projects by fetching a template + from GitHub (`github:inkform-dev/framework/templates/` via `giget`), + not from npm. + +`templates/*` and `examples/*` are `private: true` and stay that way — they +are copied by the CLI or cloned directly, never installed from npm. -## Release — do framework first, then cli (cli doesn't depend on framework -## at publish time, but framework going out first means anyone testing cli -## against a fresh `npm install` immediately gets a real, resolvable -## `@inkform/framework`) +Releases run through +[`.github/workflows/release.yml`](../../.github/workflows/release.yml). +**Nobody publishes from a laptop, and no npm token exists anywhere in this +repo.** + +--- -```bash -cd packages/framework -npm version # bumps package.json, no git tag pushed automatically -npm publish -``` +## How authentication works (npm Trusted Publishing) -```bash -cd ../cli -npm version -npm publish -``` +The release workflow authenticates to npm with a short-lived OIDC token that +GitHub mints for that workflow run. npm accepts it because each package's +"Trusted publisher" settings on npmjs.com name this repository *and this +exact workflow filename*. There is no `NPM_TOKEN` secret to leak, rotate, or +scope, and a fork cannot publish: a fork's OIDC claim carries the fork's own +repository name and npm rejects it. -Both commands run from a clean `git status` (commit first) so the published -`package.json` version matches what's in git. +Two consequences worth knowing before you touch anything: -## After publishing — retire the dependency-alias bridge +- **Renaming or moving `.github/workflows/release.yml` breaks publishing** + until the filename is updated on npmjs.com to match. That coupling is the + security property. +- Every publish made this way carries a **provenance attestation** — the + "Built and signed on GitHub Actions" badge on the npm page, linking the + tarball back to the exact commit and workflow run that produced it. -Every consumer currently pins the OLD published snapshot under the new name: +--- -```json -"@inkform/framework": "npm:@freewrite-cms/framework@^0.2.0" -``` +## Cutting a release -Once the real `@inkform/framework` is live on npm, change this in every -`package.json` that has it (all 5 themes, both examples, and — in the -**separate** `cms/` repo — `apps/blog`, `apps/docs`, and -`packages/templates/{blog-only,docs-only,unified}`) to a plain version range -matching whatever you just published: +Framework first, then CLI. They version independently; there is no +requirement that their numbers match. -```json -"@inkform/framework": "^0.3.0" -``` +1. **Bump the version** in `packages//package.json`. Published + versions are immutable, so this must be a version that has never been + published — the workflow checks and refuses otherwise. +2. **Update `CHANGELOG.md`** at the repo root (it tracks + `packages/framework`'s version). +3. **Commit and push to `main`.** The tag must point at a commit that is + actually on the branch. +4. **Tag and push the tag:** -Then, in each repo: + ```bash + git tag -a framework-v0.5.0 -m "@inkform/framework 0.5.0" + git push origin framework-v0.5.0 + ``` -```bash -npm install # re-resolves the lockfile against the real package -npm run build # confirm nothing broke -``` + The prefix selects the package: `framework-v*` → `packages/framework`, + `cli-v*` → `packages/cli`. The version in the tag must equal the version + in that package's `package.json`; the workflow refuses to guess. + +5. **Approve the deployment** if the `npm-publish` environment has required + reviewers configured (recommended — see below). + +> **Tag promptly after merging a release PR.** `templates/*` declare a real +> npm range (`"@inkform/framework": "^"`), and the CLI scaffolds +> straight from GitHub `main` via giget — it does not pin a ref. Between +> merging a version bump and the tag actually publishing, `npx @inkform/cli +> init` hands users a `package.json` asking for a version npm doesn't have +> yet, and their `npm install` fails. The window is however long the +> `npm-publish` approval sits unattended, so don't merge a release PR you +> aren't around to approve. + +The workflow re-runs the full CI gate (`lint`, `typecheck`, `test`, `build`, +`npm audit --audit-level=high`) against the tagged commit before publishing. +A green PR check is not proof the tagged commit is green. + +### Rehearsing without publishing + +Actions → Release → *Run workflow* → pick the package, leave **Dry run** +checked. Everything runs including `npm pack --dry-run`, and the publish step +is skipped. Useful for confirming the tarball contents after changing +`files` or `exports`. -This is a mechanical find-and-replace across ~10 `package.json` files plus a -lockfile regeneration in each of the two repos (`framework/` and `cms/`) — -safe to do in one pass once the npm publish itself has happened, since it's -just pointing at the real thing instead of the alias. +--- + +## One-time setup + +Already done once per package, recorded here for whoever has to redo it: + +**On npmjs.com** — package page → Settings → Trusted Publisher → GitHub Actions: + +| Field | Value | +| --- | --- | +| Organization or user | `inkform-dev` | +| Repository | `framework` | +| Workflow filename | `release.yml` | +| Environment name | `npm-publish` | + +Then, on the same settings page, set publishing access to **"Require +two-factor authentication and disallow tokens."** That kills classic +automation tokens as a publish path entirely; trusted publishing is +unaffected by it. + +**On GitHub** — Settings → Environments → `npm-publish` → add yourself as a +required reviewer. This is a second, independent gate: even someone who can +push a tag cannot ship without a human approving the run. + +--- + +## Versioning + +Versions are bumped by hand, and `CHANGELOG.md` is written by hand. If that +becomes a chore across more than these two packages, +[Changesets](https://github.com/changesets/changesets) automates both — but +it earns its keep at four or five packages, not two. ## Optional: ship compiled JS instead of source @@ -87,25 +128,10 @@ To let consumers skip `transpilePackages`, add a build step and point ```bash npm i -D tsup -# package.json # "scripts": { "build": "tsup src/*.ts src/*.tsx --format esm --dts --external next,react,react-dom" } # "files": ["dist"], exports → ./dist/*.js ``` -Not required — TS-source-direct + `transpilePackages` works fine and is what -every template/example already does. Only worth it if a consumer outside -this monorepo's own conventions complains about build times or wants to -avoid the `transpilePackages` requirement. - -## Versioning across the monorepo - -For coordinated releases of `@inkform/framework` + `@inkform/cli`, -[Changesets](https://github.com/changesets/changesets) is recommended: - -```bash -npm i -D @changesets/cli && npx changeset init -# per change: npx changeset → npx changeset version → npx changeset publish -``` - -See the repository `CONTRIBUTING.md` for the dev workflow and the source-mirror -arrangement. +Not required — TS-source-direct works fine and is what every template and +example already does. Only worth it if a consumer outside this monorepo's +conventions wants to avoid `transpilePackages`. diff --git a/packages/framework/package.json b/packages/framework/package.json index 931da9e..75160b0 100644 --- a/packages/framework/package.json +++ b/packages/framework/package.json @@ -1,6 +1,6 @@ { "name": "@inkform/framework", - "version": "0.4.0", + "version": "0.5.0", "description": "Standalone Next.js + MDX framework for documentation, API reference (OpenAPI), blog, and changelog sites. The rendering engine behind the inkform docs themes — usable on its own.", "license": "MIT", "author": "Charan", @@ -57,6 +57,7 @@ "./theme-toggle": "./src/theme-toggle.tsx", "./subscribe-form": "./src/subscribe-form.tsx", "./analytics-script": "./src/analytics-script.tsx", + "./reactions": "./src/reactions.tsx", "./secondary-top-nav": "./src/secondary-top-nav.tsx", "./scrollable-top-nav": "./src/scrollable-top-nav.tsx", "./comments": "./src/comments.tsx" diff --git a/scripts/prune-workspace-shadows.mjs b/scripts/prune-workspace-shadows.mjs index e36f536..d91f260 100644 --- a/scripts/prune-workspace-shadows.mjs +++ b/scripts/prune-workspace-shadows.mjs @@ -1,13 +1,24 @@ #!/usr/bin/env node /** - * npm workspaces occasionally materializes a real, physical copy of a - * `@inkform/*` internal package inside a consuming workspace's own - * node_modules (e.g. `examples/pokeapi-docs/node_modules/@inkform/framework`) - * instead of relying on the root-level symlink to `packages/framework`. When - * that happens, Node's module resolution finds the nested copy FIRST — which - * can be an arbitrarily stale snapshot (observed: frozen at an old version, - * missing exports and fields added since) — silently shadowing the real, - * live workspace source and breaking typecheck/build in confusing ways. + * Safety net against a `@inkform/*` internal package being materialized as a + * real, physical copy inside a consuming workspace's own node_modules (e.g. + * `examples/pokeapi-docs/node_modules/@inkform/framework`) instead of + * resolving through the root-level symlink to `packages/framework`. When that + * happens, Node's module resolution finds the nested copy FIRST — an + * arbitrarily stale snapshot, missing exports and fields added since — + * silently shadowing the live workspace source and breaking typecheck/build + * in confusing ways. + * + * This is NOT an npm quirk, which is what this comment used to claim. The + * cause was a version range: every template and example declared + * `"@inkform/framework": "^0.3.0"` while `packages/framework` had moved to + * 0.4.0. For a 0.x package `^0.3.0` means `>=0.3.0 <0.4.0`, so the local + * workspace no longer satisfied it and npm correctly went to the registry + * for a real 0.3.0 — in all six workspaces, recorded in the lockfile. Those + * ranges now track the current major, so nothing should be pruned. Kept + * because the failure is silent and confusing when it does happen; if this + * script starts reporting again, suspect a range that has drifted out of + * step with `packages/framework`'s version rather than npm. * * Every internal `@inkform/*` package is workspace-local; there is never a * reason to keep a nested copy. Runs automatically via `postinstall` so diff --git a/templates/canopy/package.json b/templates/canopy/package.json index 0da6283..ca5b141 100644 --- a/templates/canopy/package.json +++ b/templates/canopy/package.json @@ -12,7 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", diff --git a/templates/galley/package.json b/templates/galley/package.json index 3da1c60..b12c8a9 100644 --- a/templates/galley/package.json +++ b/templates/galley/package.json @@ -12,7 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1", diff --git a/templates/shadcn/package.json b/templates/shadcn/package.json index cf882a8..7bd9f3f 100644 --- a/templates/shadcn/package.json +++ b/templates/shadcn/package.json @@ -12,7 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@inkform/framework": "^0.3.0", + "@inkform/framework": "^0.5.0", "lucide-react": "^0.483.0", "next": "16.2.12", "react": "19.2.1",