Commit Graph

25 Commits

Author SHA1 Message Date
dependabot[bot] 31827e97a6 chore(deps-dev): bump the dev-dependencies group across 1 directory with 10 updates (#2142)
Bumps the dev-dependencies group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@changesets/cli](https://github.com/changesets/changesets) | `2.31.0` | `2.31.1` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.58.0` | `0.59.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.73.0` | `1.74.0` |
| [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) | `0.24.0` | `0.25.0` |
| [pkg-pr-new](https://github.com/stackblitz-labs/pkg.pr.new/tree/HEAD/packages/cli) | `0.0.75` | `0.0.78` |
| [prettier](https://github.com/prettier/prettier) | `3.9.1` | `3.9.5` |
| [@tailwindcss/cli](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-cli) | `4.3.1` | `4.3.3` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.9` | `4.1.10` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.9` | `4.1.10` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.1` | `4.3.3` |



Updates `@changesets/cli` from 2.31.0 to 2.31.1
- [Release notes](https://github.com/changesets/changesets/releases)
- [Commits](https://github.com/changesets/changesets/compare/@changesets/cli@2.31.0...@changesets/cli@2.31.1)

Updates `oxfmt` from 0.58.0 to 0.59.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxfmt_v0.59.0/npm/oxfmt)

Updates `oxlint` from 1.73.0 to 1.74.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.74.0/npm/oxlint)

Updates `oxlint-tsgolint` from 0.24.0 to 0.25.0
- [Release notes](https://github.com/oxc-project/tsgolint/releases)
- [Commits](https://github.com/oxc-project/tsgolint/compare/v0.24.0...v0.25.0)

Updates `pkg-pr-new` from 0.0.75 to 0.0.78
- [Commits](https://github.com/stackblitz-labs/pkg.pr.new/commits/v0.0.78/packages/cli)

Updates `prettier` from 3.9.1 to 3.9.5
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.9.1...3.9.5)

Updates `@tailwindcss/cli` from 4.3.1 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-cli)

Updates `@vitest/browser-playwright` from 4.1.9 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/browser-playwright)

Updates `@vitest/ui` from 4.1.9 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/ui)

Updates `@tailwindcss/vite` from 4.3.1 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-vite)

---
updated-dependencies:
- dependency-name: "@changesets/cli"
  dependency-version: 2.31.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: oxfmt
  dependency-version: 0.59.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxlint
  dependency-version: 1.74.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxlint-tsgolint
  dependency-version: 0.25.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: pkg-pr-new
  dependency-version: 0.0.78
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: prettier
  dependency-version: 3.9.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@tailwindcss/cli"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@vitest/ui"
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 14:19:05 +01:00
Matt Kane b530d4f1ea chore(deps): update oxlint 1.73 and oxlint-tsgolint 0.24 (#1919)
Bumps oxlint 1.71.0 to 1.73.0 and oxlint-tsgolint 0.23.0 to 0.24.0.

The stricter no-unnecessary-type-assertion rule flagged 82 redundant
assertions. Removed them via autofix, dropped the now-dangling
no-unsafe-type-assertion disable comments, removed the type-only imports
left unused, and added justified no-base-to-string suppressions at the
few sites where a removed assertion had been narrowing an unknown scalar
for String().

Compile-time only; emitted output is unchanged.
2026-07-10 15:19:41 +00:00
Matt Kane 138bb2faa2 chore: bump oxfmt to 0.58.0 and pin CI npx version (#1869)
Runs the format pass with the updated formatter and pins oxfmt in
the auto-format and format-command workflows so npx doesn't silently
fetch a newer version than what's used locally.
2026-07-08 06:42:23 +00:00
dependabot[bot] bb84dda914 chore(deps-dev): bump the dev-dependencies group with 8 updates (#1699)
Bumps the dev-dependencies group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm) | `4.11.3` | `4.12.1` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.60.0` | `1.61.1` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.54.0` | `0.56.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.69.0` | `1.71.0` |
| [prettier](https://github.com/prettier/prettier) | `3.8.4` | `3.9.1` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.8` | `4.1.9` |
| [playwright](https://github.com/microsoft/playwright) | `1.60.0` | `1.61.1` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.8` | `4.1.9` |


Updates `@axe-core/playwright` from 4.11.3 to 4.12.1
- [Release notes](https://github.com/dequelabs/axe-core-npm/releases)
- [Changelog](https://github.com/dequelabs/axe-core-npm/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/dequelabs/axe-core-npm/commits)

Updates `@playwright/test` from 1.60.0 to 1.61.1
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.60.0...v1.61.1)

Updates `oxfmt` from 0.54.0 to 0.56.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxfmt_v0.56.0/npm/oxfmt)

Updates `oxlint` from 1.69.0 to 1.71.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.71.0/npm/oxlint)

Updates `prettier` from 3.8.4 to 3.9.1
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.4...3.9.1)

Updates `@vitest/browser-playwright` from 4.1.8 to 4.1.9
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.9/packages/browser-playwright)

Updates `playwright` from 1.60.0 to 1.61.1
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.60.0...v1.61.1)

Updates `@vitest/ui` from 4.1.8 to 4.1.9
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.9/packages/ui)

---
updated-dependencies:
- dependency-name: "@axe-core/playwright"
  dependency-version: 4.12.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@playwright/test"
  dependency-version: 1.61.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxfmt
  dependency-version: 0.56.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxlint
  dependency-version: 1.71.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: prettier
  dependency-version: 3.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.1.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: playwright
  dependency-version: 1.61.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@vitest/ui"
  dependency-version: 4.1.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 17:47:54 +01:00
Matt Kane 5b6c542c78 Upgrade pnpm 2026-07-01 06:05:49 +01:00
dependabot[bot] 82c7352a5b chore(deps-dev): bump the dev-dependencies group with 4 updates (#1529)
Bumps the dev-dependencies group with 4 updates: [@e18e/eslint-plugin](https://github.com/e18e/eslint-plugin), [prettier](https://github.com/prettier/prettier), [@tailwindcss/cli](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-cli) and [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite).


Updates `@e18e/eslint-plugin` from 0.5.0 to 0.5.1
- [Release notes](https://github.com/e18e/eslint-plugin/releases)
- [Commits](https://github.com/e18e/eslint-plugin/compare/0.5.0...0.5.1)

Updates `prettier` from 3.8.3 to 3.8.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.3...3.8.4)

Updates `@tailwindcss/cli` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-cli)

Updates `@tailwindcss/vite` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite)

---
updated-dependencies:
- dependency-name: "@e18e/eslint-plugin"
  dependency-version: 0.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: prettier
  dependency-version: 3.8.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@tailwindcss/cli"
  dependency-version: 4.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 19:57:21 +01:00
dependabot[bot] 74bc6ee5d8 chore(deps-dev): bump the dev-dependencies group across 1 directory with 6 updates (#1412)
Bumps the dev-dependencies group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.52.0` | `0.54.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.67.0` | `1.69.0` |
| [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography) | `0.5.19` | `0.5.20` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.7` | `4.1.8` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.7` | `4.1.8` |
| [@flue/cli](https://github.com/withastro/flue/tree/HEAD/packages/cli) | `0.8.1` | `0.10.0` |



Updates `oxfmt` from 0.52.0 to 0.54.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxfmt_v0.54.0/npm/oxfmt)

Updates `oxlint` from 1.67.0 to 1.69.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.69.0/npm/oxlint)

Updates `@tailwindcss/typography` from 0.5.19 to 0.5.20
- [Release notes](https://github.com/tailwindlabs/tailwindcss-typography/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.19...v0.5.20)

Updates `@vitest/browser-playwright` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.8/packages/browser-playwright)

Updates `@vitest/ui` from 4.1.7 to 4.1.8
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.8/packages/ui)

Updates `@flue/cli` from 0.8.1 to 0.10.0
- [Changelog](https://github.com/withastro/flue/blob/main/CHANGELOG.md)
- [Commits](https://github.com/withastro/flue/commits/v0.10.0/packages/cli)

---
updated-dependencies:
- dependency-name: oxfmt
  dependency-version: 0.54.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxlint
  dependency-version: 1.69.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@tailwindcss/typography"
  dependency-version: 0.5.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@vitest/ui"
  dependency-version: 4.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@flue/cli"
  dependency-version: 0.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 21:25:36 +01:00
dependabot[bot] 7a66d39631 chore(deps-dev): bump the dev-dependencies group across 1 directory with 15 updates (#1265)
Bumps the dev-dependencies group with 15 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm) | `4.11.1` | `4.11.3` |
| [@changesets/changelog-github](https://github.com/changesets/changesets) | `0.5.2` | `0.7.0` |
| [@changesets/cli](https://github.com/changesets/changesets) | `2.29.8` | `2.31.0` |
| [@e18e/eslint-plugin](https://github.com/e18e/eslint-plugin) | `0.2.0` | `0.5.0` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.58.0` | `1.60.0` |
| [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.34.0` | `0.52.0` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.66.0` | `1.67.0` |
| [prettier](https://github.com/prettier/prettier) | `3.8.1` | `3.8.3` |
| [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `7.29.0` | `7.29.7` |
| [@tailwindcss/cli](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-cli) | `4.1.18` | `4.3.0` |
| [@vitest/browser-playwright](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright) | `4.1.5` | `4.1.7` |
| [playwright](https://github.com/microsoft/playwright) | `1.58.2` | `1.60.0` |
| [vitest-browser-react](https://github.com/vitest-community/vitest-browser-react) | `2.0.5` | `2.2.0` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.5` | `4.1.7` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.2.1` | `4.3.0` |



Updates `@axe-core/playwright` from 4.11.1 to 4.11.3
- [Release notes](https://github.com/dequelabs/axe-core-npm/releases)
- [Changelog](https://github.com/dequelabs/axe-core-npm/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/dequelabs/axe-core-npm/compare/v4.11.1...v4.11.3)

Updates `@changesets/changelog-github` from 0.5.2 to 0.7.0
- [Release notes](https://github.com/changesets/changesets/releases)
- [Commits](https://github.com/changesets/changesets/compare/@changesets/read@0.5.2...@changesets/changelog-github@0.7.0)

Updates `@changesets/cli` from 2.29.8 to 2.31.0
- [Release notes](https://github.com/changesets/changesets/releases)
- [Commits](https://github.com/changesets/changesets/commits/@changesets/cli@2.31.0)

Updates `@e18e/eslint-plugin` from 0.2.0 to 0.5.0
- [Release notes](https://github.com/e18e/eslint-plugin/releases)
- [Commits](https://github.com/e18e/eslint-plugin/compare/0.2.0...0.5.0)

Updates `@playwright/test` from 1.58.0 to 1.60.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.58.0...v1.60.0)

Updates `oxfmt` from 0.34.0 to 0.52.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxfmt_v0.52.0/npm/oxfmt)

Updates `oxlint` from 1.66.0 to 1.67.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.67.0/npm/oxlint)

Updates `prettier` from 3.8.1 to 3.8.3
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.1...3.8.3)

Updates `@babel/core` from 7.29.0 to 7.29.7
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.29.7/packages/babel-core)

Updates `@tailwindcss/cli` from 4.1.18 to 4.3.0
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-cli)

Updates `@vitest/browser-playwright` from 4.1.5 to 4.1.7
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.7/packages/browser-playwright)

Updates `playwright` from 1.58.2 to 1.60.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.58.2...v1.60.0)

Updates `vitest-browser-react` from 2.0.5 to 2.2.0
- [Release notes](https://github.com/vitest-community/vitest-browser-react/releases)
- [Commits](https://github.com/vitest-community/vitest-browser-react/compare/v2.0.5...v2.2.0)

Updates `@vitest/ui` from 4.1.5 to 4.1.7
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.7/packages/ui)

Updates `@tailwindcss/vite` from 4.2.1 to 4.3.0
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-vite)

---
updated-dependencies:
- dependency-name: "@axe-core/playwright"
  dependency-version: 4.11.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@changesets/changelog-github"
  dependency-version: 0.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@changesets/cli"
  dependency-version: 2.31.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@e18e/eslint-plugin"
  dependency-version: 0.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@playwright/test"
  dependency-version: 1.60.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxfmt
  dependency-version: 0.52.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: oxlint
  dependency-version: 1.67.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: prettier
  dependency-version: 3.8.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@babel/core"
  dependency-version: 7.29.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@tailwindcss/cli"
  dependency-version: 4.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: playwright
  dependency-version: 1.60.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: vitest-browser-react
  dependency-version: 2.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: "@vitest/ui"
  dependency-version: 4.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-01 17:53:58 +01:00
Matt Kane d4e306c69a Investigate-bot fix gate, preview self-consistency, and bot identity fixes (#1259)
* ci: publish all public packages as previews and pin pkg-pr-new

Derive the pkg.pr.new publish set from globs (./packages/*,
./packages/plugins/*) instead of a hardcoded list. pkg.pr.new skips
private packages, so the test fixtures are excluded automatically while
every public package, including the previously missing registry-client,
plugin-types and auth-atproto, now gets a preview.

This keeps preview installs self-consistent: emdash's preview references
sibling packages via workspace:*, and pkg.pr.new can only rewrite those
to matching preview URLs when the siblings are published in the same
run. Omitting one made the dep fall back to npm's released version,
which broke when source had drifted (e.g. registry-client's ./env
export added without a release).

Also pin pkg-pr-new as a root devDependency and run it via pnpm exec
rather than pnpm dlx, per the tool's CI guidance.

* fix(ci): correct investigate-bot identity and split triage labels

Two orchestrator-workflow fixes:

- Commit identity was emdash-bot[bot] / bot@emdashcms.com, but the
  GitHub App slug is emdashbot. Use emdashbot[bot] and the
  users.noreply.github.com attribution email, matching every other
  workflow. This also makes the github.actor guards in auto-format and
  auto-extract match the bot's own commits.
- intended-behavior outcomes now get a new triage/by-design label
  instead of sharing triage/reproduced with confirmed bugs. The two
  need opposite follow-up (likely close vs. needs a fix), so they
  should not share a label.

* feat(flue): broaden investigate-bot fix gate, run fix on a cheaper model

The fix stage only ran at verify=bug AND diagnose.confidence=high, where
high meant 'mechanical, one-line, no ambiguity'. That conflated two
independent questions: is the root cause certain, and is the fix
obvious? Real, fixable bugs (e.g. #1178, #1199) were parked at
triage/reproduced because one clearly-correct fix existed among several
shapes, which forced a medium rating.

Decouple the axes:
- confidence now rates root-cause certainty only.
- a new fixApproach (mechanical | clear-best-option | needs-design-decision)
  rates fix clarity.
- the gate becomes verdict=bug AND confidence!=low AND
  fixApproach!=needs-design-decision.

Diagnose also now emits a concrete proposedFix (always), which feeds the
fix stage as its spec and doubles as the maintainer's starting point
when the fix is deferred.

The fix stage runs on a separate, cheaper agent (kimi-k2.6) in its own
session: the reasoning is already done, so it is guided implementation.
It shares the on-disk checkout, so staged edits still reach the
orchestrator. Configurable via FLUE_FIX_MODEL.

Reframe the skill cost model: the output is a reporter-verified
candidate branch a maintainer reviews, not a merge, so a clear,
test-backed fix is worth attempting even when it is more than a
one-liner. Update diagnose/verify/fix skills, _INVESTIGATE.md and
README; delete the stale PLAN.md.

* fix(ci): wire triage/by-design into label cleanup and project sync

Addresses review on #1259. The new triage/by-design label was missing
from two places:

- investigate.yml's reproducing-transition cleanup loop, so a
  re-triggered by-design issue could carry triage/by-design alongside
  triage/reproducing.
- triage-project-sync.yml's STATE_BY_LABEL/PRECEDENCE, so by-design
  issues resolved to no state and stopped syncing to the board.

Maps triage/by-design -> 'By design' board option (terminal verdict,
ranked just below reproduced in precedence). The 'By design' single-
select option must be added to Project #3's 'Triage State' field; until
then the sync warns-and-skips rather than failing.
2026-06-01 12:24:22 +01:00
Matt Kane 20c87fe924 chore: gate lint warnings in CI, clear the existing pile (#1147)
* fix(workerd): clear all lint warnings and tsgo errors

- Replace untyped `as T` casts in bridge dispatch with predicate-backed `require*`/`optional*` helpers
- Introduce `asContentDb()` for dynamic ec_* tables (single justified narrowing)
- Drop unnecessary `as keyof Database` casts for tables already in the static schema
- Validate marshaled RequestInit at the http/fetch boundary
- Typed HttpError class in backing-service for status-bearing errors
- getPluginStorageConfig now returns the real PluginStorageConfig shape
- WorkerdSandboxedPlugin implements SandboxedPluginInstance (the previous SandboxedPlugin symbol did not exist)
- Add typecheck script so the package participates in pnpm typecheck

No runtime behaviour changes.

* chore: bump oxlint and update disable-comment format

Upgrade oxlint (1.49 -> 1.66) and oxlint-tsgolint (0.15 -> 0.23). The
newer oxlint renamed the unused unicorn/prevent-abbreviations rule and
switched the canonical typescript-eslint plugin name to typescript,
which changes how inline disable comments are written.

Mechanical rename: `typescript-eslint(rule-name)` -> `typescript/rule-name`
in all eslint-disable comments. Without this, ~120 disable comments
stopped suppressing the rules they were meant to.

Also drops unicorn/prevent-abbreviations from .oxlintrc.json (no longer
a valid rule).

* ci: gate lint warnings so they cannot regress

`pnpm lint` now passes `--deny-warnings` to oxlint, so any warning is
a non-zero exit. CI inherits this through the existing lint job.

The blocker was the existing pile of warnings, so this commit also
clears them:

- `packages/core/src/astro/middleware.ts`: collapse duplicated
  `virtualSandboxRunnerModule as Record<...>` casts behind one local
  binding inside a block-form disable, and convert the remaining
  parenthesis-form disables that newer oxlint stopped recognising.
- `packages/core/src/astro/middleware/auth.ts` and 14 API route files:
  drop unnecessary `emdash!` non-null assertions; the preceding
  `requireDb(emdash?.db)` guard already narrows.
- `packages/core/src/emdash-runtime.ts`: drop unnecessary
  `as ResolvedPlugin[]` and `emdash!` casts; annotate the two
  remaining trusted dynamic-import sites with a single disable line.
- `packages/plugin-cli/src/build/pipeline.ts`: replace the chain of
  `as Record<string, unknown>` casts with `isRecord` /
  `isStringArray` predicate narrowing.
- `packages/cloudflare/src/sandbox/bridge.ts`: drop the
  `this.env.DB as D1Database` cast (already that type).
- `packages/core/src/client/index.ts`,
  `packages/core/src/astro/integration/index.ts`,
  `packages/registry-client/src/credentials/index.ts`: remove three
  unused imports.

Newer oxlint flagged config-level issues too:

- `packages/workerd/tsconfig.json`: add explicit `rootDir`.
- `packages/contentful-to-portable-text/tsconfig.json`: drop the
  `rootDir` that excluded `test/**/*` from `include`.
- `packages/core/src/page/absolute-url.ts`: fix the disable comment
  rule name so `no-control-regex` is suppressed (the regex
  intentionally matches control chars).
- `lunaria.config.ts`: convert block-form disable to next-line form
  with the new rule path.

Finally, four newer rules are disabled at the repo level:
`no-underscore-dangle` (Portable Text uses `_type`/`_key` by spec),
`typescript/consistent-return`, `typescript/no-unnecessary-type-conversion`,
`typescript/no-unnecessary-type-parameters`, and
`typescript/no-useless-default-assignment` (the rule errors out under
`strict: false`, which the test plugins use deliberately). These can
be re-enabled in follow-up PRs once their hits are triaged.
2026-05-22 15:25:38 +00:00
Matt Kane 792f73c12c chore: bump pnpm to 11.1.3 and pin scaffolded sites to a recent pnpm (#1115)
- Bump root packageManager via `corepack use pnpm@latest`.
- Sync script bakes the root's packageManager into each template's
  package.json, so scaffolded sites auto-track the monorepo pin.
- create-emdash strips packageManager when the user picks npm/yarn/bun
  so corepack doesn't force pnpm on a non-pnpm user.
- Drop dead `pnpm.onlyBuiltDependencies` from demo/fixture/template
  package.json files; pnpm 11 ignores `package.json#pnpm` and the root
  `allowBuilds` already covers these binaries.
- AGENTS.md / auto-implementer.md: drop `--silent` from lint commands;
  pnpm 11 prints the `$ command` line to stderr, so JSON pipes cleanly
  without it.
2026-05-20 08:13:53 +01:00
Matt Kane 74a9078b00 ci(release): reconcile lockfile before gated changeset commands (#1104)
* ci(release): reconcile lockfile before gated changeset commands

verifyDepsBeforeRun: error gates pnpm run/exec. The frozen install in the release job does not refresh the lockfile's overrides hash, so a lockfile whose overrides drifted from pnpm-workspace.yaml passes the frozen install but trips the gate on `pnpm run changeset:version` / `pnpm changeset publish`, killing every release before it versions or publishes. A non-frozen install is not gated and reconciles the hash; changesets/action commits the result, self-healing the repo.

* ci(release): reconcile lockfile inside the changeset command, not as a prior step

changesets/action does git checkout changeset-release/main + git reset --hard right before running the version/publish command, so any reconcile placed in an earlier workflow step is discarded by that reset and the gated 'pnpm changeset' call still trips verifyDepsBeforeRun. Move the non-frozen install into a single non-gated node entrypoint the action invokes, so it runs after the action's git work and immediately before the gated pnpm call. Drop the now-unreferenced changeset:version script.

* style: format

* ci(release): use --prefer-frozen-lockfile for the reconcile

changeset publish rebuilds packages via prepublishOnly at pack time. A non-frozen reconcile could re-resolve in-range transitive deps so shipped bits diverge from tested bits. prefer-frozen reconciles the deps state to satisfy verifyDepsBeforeRun without re-resolving when the lockfile is satisfiable, and falls back to a full install (e.g. after changeset version bumps workspace versions) when it isn't.

* ci(release): revert reconcile to --no-frozen-lockfile

The PR's purpose is to reliably clear ERR_PNPM_VERIFY_DEPS_BEFORE_RUN. --no-frozen-lockfile is pnpm's documented remediation and unconditionally refreshes the deps-state hash. --prefer-frozen-lockfile's fast path is gated by a satisfiability check that may not include the settings hash, so it could skip the rewrite in exactly the stale-metadata case this fixes. The shipped-vs-tested concern that motivated prefer-frozen is low-probability, pre-existing, and negligible when the lockfile is satisfiable (no re-resolution occurs).

---------

Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
2026-05-19 20:00:37 +01:00
Matt Kane c0ce915c55 feat(plugin-cli): sandboxed plugin authoring CLI (#1057)
* feat(registry-cli): extend manifest schema with identity + trust contract

First phase of the sandboxed plugin redesign (#1028b). Adds the
manifest fields that make `src/index.ts` and the in-code descriptor
factory redundant. The trust contract is now hand-authored in the
manifest, where a security reviewer can find it without grep.

New required fields:

- `slug`: ASCII letter then letters/digits/hyphens/underscores, max 64
  chars. Matches the registry lexicon's rkey grammar via the shared
  PLUGIN_SLUG_RE in @emdash-cms/plugin-types.
- `version`: semver 2.0 subset, no build-metadata (atproto rkeys can't
  contain `+`). Validated via PLUGIN_VERSION_RE.
- `publisher`: now required (was optional in #1028a). The runtime
  cannot compute the plugin's AT URI without it; making it optional
  meant the plugin couldn't load locally before first publish.

New optional fields with sensible defaults:

- `capabilities`: array of capability strings. Defaults to []. Each
  entry validated against the current vocabulary; deprecated names are
  hard-rejected with a hint at the replacement (no deprecation window
  for new authoring).
- `allowedHosts`: array of host patterns. Defaults to []. Required
  non-empty when `network:request` is declared without
  `:unrestricted`. Forbidden when `:unrestricted` is declared.
- `storage`: map of collection name -> { indexes, uniqueIndexes? }.
  Defaults to {}.

The cross-field rule for network:request / allowedHosts mirrors the
release-extension lexicon's networkRequestConstraints behaviour, so
authors hit the schema error here rather than a PDS validation error
at publish time.

Schema regenerated. 33 new tests; 204 total passing.

Part of #1028b. The bundle rewrite, init command, plugin migrations,
and `localPlugin` dev helper land in subsequent commits.

* feat(registry-cli): bundle reads identity + trust contract from manifest

Second phase of the sandboxed plugin redesign (#1028b). Bundle no longer
imports src/index.ts for a descriptor factory; the manifest is the
source of truth for identity (slug, version) and the trust contract
(capabilities, allowedHosts, storage). Bundle still probes the runtime
code for the hook/route surface — that's a syntactic property that
needs the code to exist.

Changes to bundle:

- Drop the main-entry build and descriptor extraction. No more
  src/index.ts probing, no more `createPlugin` / default-factory /
  default-object format detection.
- Replace `resolveEntries`: just locates emdash-plugin.jsonc (loaded
  through the same loader the CLI's validate uses) and confirms
  src/plugin.ts exists. No more package.json `exports` parsing.
- Replace `extractResolvedPlugin` with `assembleResolvedPlugin`: builds
  the ResolvedPlugin shape from the manifest, then probes
  src/plugin.ts for hook/route names.
- Probe (renamed from `augmentWithSandboxProbe` to `probePluginSurface`)
  now reads src/plugin.ts. Hard-fails if the default export isn't a
  definePlugin result.
- New error codes: MISSING_MANIFEST, MISSING_PLUGIN_ENTRY,
  MANIFEST_INVALID. Old MISSING_PACKAGE_JSON / MISSING_ENTRYPOINT /
  MAIN_BUILD_FAILED gone.
- Admin entry handling (admin.js, adminPages, adminWidgets) deferred
  to a follow-up issue. The redesign hasn't touched admin yet; that
  surface stays as-is and is gated on the descriptor's `admin` field
  which no longer exists. When admin lands again it'll be a manifest
  field with its own probe.

Changes to translate.ts:

- `NormalisedManifest` gains slug, version, publisher (required),
  capabilities, allowedHosts, storage. Publisher is no longer
  Optional — the schema enforces it.

Fixtures:

- `minimal-plugin/`: src/index.ts gone, sandbox-entry.ts renamed to
  plugin.ts, new emdash-plugin.jsonc with identity + trust contract.
- `bad-plugin/`: stripped to manifest-only (no src/), exercises
  MISSING_PLUGIN_ENTRY. Old "declares hooks but no sandbox entry"
  case isn't possible anymore — there's no descriptor declaring
  anything.

Net diff: -228 lines.

* feat(registry-cli): init command scaffolds a sandboxed plugin

Third phase of the redesign (#1028b). Adds `emdash-registry init [name]`
which produces the three-file plugin layout introduced by the previous
commits: emdash-plugin.jsonc, src/plugin.ts, package.json, plus a
tsconfig, README, .gitignore, and a passing test.

Modes:
- Interactive (default on a TTY): clack prompts for each unset field
  with sensible defaults. ESC / Ctrl+C cancels cleanly.
- `--yes` / `-y` (non-interactive): no prompts; unset fields become
  TODO placeholders in the manifest. The author fixes them before
  first use.
- Non-TTY (CI, pipes): same as `--yes`; prompting into a non-
  interactive stdin would hang.

Pre-fills:
- Publisher: the active session's handle from FileCredentialStore.
  Resolved through @atcute/identity-resolver to a DID before write
  so the runtime never sees a mutable handle. The handle is emitted
  as a `// <handle>` line comment next to the pinned DID for `git
  diff` readability — same convention as the post-publish write-back.
- Author name / email: `git config user.name` / `user.email`.
- Repo: `git remote get-url origin`, normalised from SSH to https
  (`git@github.com:foo/bar.git` → `https://github.com/foo/bar`).
  Falls back to `package.json#repository.url` if no git remote.
- License, description: `package.json` in the target dir if one
  exists (for the "scaffold into existing repo skeleton" case).

Slug defaults to the positional `name`, `basename(--dir)`, or
basename(cwd) in that order. Every flag is optional in every mode.

Exported `resolveHandleToDid` from manifest/publisher.ts so init
can use the same resolver the post-publish write-back does.

Tests: 44 new (template renderers, scaffold filesystem behaviour,
environment probe). 249 total in the package.

* feat(plugins): migrate in-tree sandboxed plugins to the new layout

Fourth phase of the redesign (#1028b). Moves the 5 in-tree sandboxed
plugins to the manifest + src/plugin.ts shape so they become the
canonical references a plugin author looks at.

Each plugin's layout changes from:

  src/index.ts          (descriptor factory, ~50 lines)
  src/sandbox-entry.ts  (runtime code via definePlugin)
  package.json          (main / exports / files / build scripts)

to:

  emdash-plugin.jsonc   (identity + trust contract + admin surface)
  src/plugin.ts         (runtime code, unchanged)
  package.json          (private, typecheck script only)

Plugins migrated:
- atproto
- audit-log
- marketplace-test
- sandboxed-test
- webhook-notifier

Schema gains `admin` (pages + widgets) since four of the five plugins
declare admin surface. Mirrors PluginAdminPage / PluginDashboardWidget
in core. Atproto's plugin.test.ts rewritten to assert against the
manifest instead of the deleted descriptor factory.

KNOWN BREAKAGE: demos that import the old factories
(`auditLogPlugin()`, `webhookNotifierPlugin()`) from
astro.config.mjs are broken until the next commit ships
`@emdash-cms/registry-cli/dev`'s `localPlugin(dir)` helper and
updates the demos.

All published plugins still work — the bundled manifest.json shape
is unchanged. Only authoring changed.

* feat(registry-cli): add localPlugin(dir) dev helper + wire demos

Final piece of the sandboxed-plugin redesign (#1028b). Closes the gap
the plugin migrations opened — demos that previously imported
`auditLogPlugin()` / `webhookNotifierPlugin()` factories now consume
the plugins through their source directories.

New subpath `@emdash-cms/registry-cli/dev` exports `localPlugin(dir)`,
which:

- Reads `<dir>/emdash-plugin.jsonc` via the same loader the CLI uses.
- Confirms `<dir>/src/plugin.ts` exists.
- Resolves the manifest's publisher (handle → DID) so the descriptor
  is in canonical form.
- Returns a PluginDescriptor-shaped object with `entrypoint` set to
  the absolute `file://` URL of `src/plugin.ts`. Vite resolves the
  URL through its standard fs path resolver — no build step needed.

The descriptor carries id, version, capabilities, allowedHosts,
storage, and (when declared) adminPages + adminWidgets from the
manifest. Plugins that don't expose admin surface pass through
without the optional fields, keeping the descriptor tidy.

Demos updated:
- demos/simple: auditLogPlugin() → localPlugin("../../packages/plugins/audit-log")
- demos/plugins-demo: auditLog + webhookNotifier the same way
- demos/cloudflare: webhookNotifier via localPlugin
- infra/cache-demo, infra/blog-demo: same

Trusted plugins (formsPlugin, embedsPlugin, apiTestPlugin) keep their
factory-based imports — they're not on the new shape and aren't part
of this redesign's scope.

Errors surface as a structured LocalPluginError with codes:
- MANIFEST_INVALID
- PLUGIN_ENTRY_MISSING
- PUBLISHER_UNRESOLVED

Tests: 10 new (descriptor shape, error paths, admin pass-through).
259 total in the package.

* feat(plugin-cli): rework sandboxed plugin authoring, build, and CLI

Renames @emdash-cms/registry-cli to @emdash-cms/plugin-cli and the
binary emdash-registry to emdash-plugin. Adds build + dev commands,
consolidates the build pipeline so bundle is a thin packaging step on
top of build. Introduces a strict author-facing SandboxedPlugin type
via the new emdash/plugin type-only subpath; sandboxed plugins now
default-export a bare { hooks?, routes? } object with satisfies
SandboxedPlugin and have no runtime emdash import. Drops definePlugin
and the build shim for sandboxed plugins (definePlugin is native-only
now). Migrates the five in-tree sandboxed plugins to the new shape.
Manifest version is optional and reconciled with package.json#version.

* fix(plugin-cli): adversarial review fixes

- init scaffold emits the new `satisfies SandboxedPlugin` shape and
  npm-shape package.json (build/dev scripts, ./sandbox export, plugin-cli
  devDep) instead of the broken `definePlugin` template
- publish reads package.json#version and reconciles via normaliseManifest
  so the new "version in package.json only" pattern actually publishes;
  malformed package.json surfaces a CliError, not a misleading
  VERSION_MISSING further down
- dev watcher serialises rebuilds (queue collapsed to one follow-up),
  closes the watcher before draining pending on Ctrl-C, short-circuits
  scheduleRebuild during shutdown, handles Windows path separators in
  the outDir ignore glob, clears pending+queuedTrigger in finally so an
  IIFE rejection can't deadlock the session, and removes SIGINT handlers
  on shutdown
- adapter normalises ctx.request to SandboxedRequest shape in-process
  so handlers see the same { url, method, headers: Record } promised by
  the strict type; null/array/non-object default exports rejected with
  a plugin-id-bearing message
- build's readPackageMeta rejects empty/non-string version with the
  same strictness as publish, killing the build-pass/publish-fail
  asymmetry
- pipeline probe rejects invalid hook config (errorPolicy, priority,
  timeout) so untyped JS authors get a build error rather than a
  silently-wrong runtime contract
- versionless minimal-plugin fixture so bundle/publish/build integration
  tests exercise the package.json-as-source-of-truth path
- definePlugin error wording softened for native-plugin authors whose
  id field has a typo
- pipeline error messages and stale comments updated for the no-shim,
  no-definePlugin authoring shape
- removed dead EMDASH_SHIM from the Cloudflare sandbox runner
- changesets retargeted to @emdash-cms/plugin-cli; scaffold/atproto/core
  comments scrubbed for stale registry-cli references

* style: format

* docs(changesets): switch plugin migration examples to diff fences

* style: format

* Fix changeset ordering

* fix(ci): plugin build uses node-direct path; sweep stale registry-cli refs

In-workspace plugins use `node node_modules/@emdash-cms/plugin-cli/dist/index.mjs build`
because pnpm doesn't create the bin shim for a workspace package whose
bin target doesn't exist at install time. Plugin authors outside the
workspace get a published bin with a real dist, so `emdash-plugin build`
works for them via the natural scaffold.

Also fixes stale registry-cli references the rename pass missed:
- .oxfmtrc.json: schema ignore path
- .oxlintrc.json: 7 type-aware-cost allowlist entries
- .github/workflows/ci.yml: build filter includes plugin-cli for test:unit
- package.json: test:unit script
- packages/plugin-types/package.json: description

The schema file is regenerated to match what gen-schema produces. The
previously committed version had been hand-reformatted post-regen and
disagreed with the generator's output.

* fix(ci): remove legacy marketplace bundle path; address review findings

- Delete `packages/marketplace/tests/publish-e2e.test.ts` — invoked the
  legacy `emdash plugin bundle` from core CLI against the new
  manifest-driven plugin layout, which it doesn't understand.
- Remove the validate-plugins CI job — it used the same legacy CLI
  command. Plugin validation is now covered by `pnpm build`, which
  runs the new `emdash-plugin build` probe + manifest checks against
  every in-tree sandboxed plugin.
- Fix `no-base-to-string` lint errors in audit-log/plugin.ts. The
  canonical ContentHookEvent types `event.content.id` as unknown;
  `String(unknown)` lands on '[object Object]' for record IDs. Added
  a small `stringifyId` helper that returns '' for non-string/number
  inputs so the caller's existence check skips bad rows.
- pipeline.ts now hard-errors when the probed module has no `default`
  export, instead of silently falling through to an empty plugin
  (build had been writing dist/ artifacts with empty hooks/routes for
  any source that used `export const plugin = ...`).
- Scaffold README camelCases hyphenated slugs for the import binding.
  Slugs like `my-plugin` were producing `import my-plugin from ...`
  which is a syntax error. Test added with a hyphenated fixture.

Both bot review comments addressed.

* style: format

* fix(plugin-cli): bump test timeout to 30s for bundle tests on slow CI

bundle.test.ts > 'produces a tarball + manifest for a minimal valid
plugin' timed out at the 5s default on the GitHub-hosted runner.
The test runs the full build pipeline (tsdown probe + transpile +
tarball pack), which is fast locally (<2s) but cold-starts at 5-8s
on CI. Bump to 30s globally for the plugin-cli vitest config.

* chore: update lockfile

---------

Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com>
Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
2026-05-18 15:01:00 +01:00
Matt Kane 5051274bf0 chore: harden pnpm supply-chain config (#1068)
* chore: harden pnpm supply-chain config

Pin pnpm (packageManager: pnpm@11.1.2) so the build-script/trust schema
is deterministic, and add supply-chain hardening to pnpm-workspace.yaml:

- minimumReleaseAge 1440 (24h publish cooldown); @rolldown/* and
  @emnapi/* excluded (vite8/rolldown-rc exact-pins these per-arch
  binaries; their release cadence structurally conflicts with the
  cooldown and they are not an attack surface worth the friction)
- strictDepBuilds + explicit allowBuilds: build scripts run only for
  @parcel/watcher, sharp, better-sqlite3, esbuild, workerd;
  core-js-pure denied (donation postinstall, not needed)
- dangerouslyAllowAllBuilds false
- blockExoticSubdeps true, with an @astrojs/telemetry -> 3.3.0 override
  (infra/cache-demo's pkg.pr.new Astro cache-support preview pulls
  telemetry via URL; the override keeps the guard on)
- trustPolicy no-downgrade with a reviewed, version-pinned
  trustPolicyExclude (vite@6.4.1, chokidar@4.0.3, semver@6.3.1,
  @portabletext/toolkit@3.0.3, reselect@5.1.1 -- all benign
  publish-method changes / old pre-provenance pins, not takeovers)
- verifyStoreIntegrity, strictStorePkgContentCheck, verifyDepsBeforeRun
  error

Move enable-pre-post-scripts from .npmrc to pnpm-workspace.yaml
(enablePrePostScripts: true; canonical location, also silences the
npm "unknown config" warning). Remove .npmrc entirely -- provenance
is implied by OIDC trusted publishing.

Lockfile regenerated under the hardened config with pnpm 11.1.2.

* chore: address Copilot review on supply-chain hardening

- Remove stale root pnpm.onlyBuiltDependencies (dead under pinned
  pnpm 11; allowBuilds in pnpm-workspace.yaml is the single source)
- Narrow cooldown excludes: @rolldown/* -> @rolldown/binding-*, and
  @emnapi/* -> @emnapi/core/@emnapi/runtime, so the cooldown stays
  active for JS like @rolldown/pluginutils / @emnapi/wasi-threads
- Drop the dangling CONTRIBUTING.md pointer (no such policy doc)
- Override @ungap/structured-clone to ^1.3.1 (1.3.0 deprecated,
  CWE-502)
- Regenerate the lockfile from a clean store so the astro pkg.pr.new
  tarball regains its SRI integrity (the earlier warm-store regen
  dropped it; @astrojs/cloudflare and @lunariajs/core never had SRI,
  pre-PR included -- pnpm doesn't record it for those URLs)

Re-resolved clean under pnpm 11.1.2 with the narrowed excludes.

* fix: resolve #1053 type errors for strict consumers (#1076)

* fix: resolve #1053 type errors for strict consumers

Closes #1053.

Two parts:

1. wordpress-plugin.ts: the analyze-endpoint error body from
   response.json() is unknown under @cloudflare/workers-types; narrow
   it before reading .message (was the reported TS18046). The other
   reported error (byline.ts kysely Transaction variance) was TS5.x
   behaviour, resolved by the TS6 upgrade in #1074.

2. Stop shipping raw .ts for the source-exported subpaths
   (emdash/routes/*, emdash/api/route-utils, emdash/api/schemas,
   emdash/auth/providers/*). They are compiled to dist (.mjs +
   .d.mts), so a strict consumer's tsc only ever sees declarations
   (skipLibCheck covers them), eliminating the dual-package Database
   identity wall that is #1053's root cause. ./ui and .astro stay
   source (the consumer's Astro build must process them).

   - tsdown: route entrypoints fed via inputOptions.input (literal
     object, not entry globs -- [param] dirs are glob char-classes).
     entryFileNames + resolveRoute share one routeArtifactName() so
     rolldown's reserved [name]/[hash] placeholders cannot mangle
     dynamic-route artifacts.
   - A fast static guard (scripts/typecheck-public-source.mjs, wired
     into CI) fails if any subpath export ships raw .ts/.tsx again.

Verified: pnpm build, pnpm typecheck, demos+templates typecheck,
demo build (route injection e2e), lint baseline unchanged.

* style: format

* fix(build): externalize self/optional deps so route entries don't bundle them

Compiling the route/admin entries made tsdown try to bundle deps it
could not resolve at build time -- 'emdash' (the package importing
itself) and '@cloudflare/kumo' (admin-only, not an emdash dep). CI
escalates tsdown's bundling advisory to a fatal error.

- Externalize 'emdash' (self): compiled routes import it; resolved at
  the consumer's runtime where the package is installed.
- Externalize @aws-sdk/* (optional S3 deps, runtime-only).
- Keep the *-admin.tsx providers as source (bridge the admin React +
  @cloudflare/kumo runtime, like .astro/./ui); revert their exports.
- inlineOnly: false -- nothing is unintentionally bundled (all deps
  external, only our own src is); silences the CI-escalated advisory.

Guard's allowlist generalized to RUNTIME_COUPLED (Astro + admin React).

* test: route-injection entrypoint now resolves to compiled artifact

resolveRoute resolves emdash/routes/* to the compiled dist artifact
with routeArtifactName applied ([ ] -> _), so the media catch-all
route's entrypoint is api/media/file/_...key_ not [...key].ts. The
catch-all pattern assertion (the actual guarantee) is unchanged.

---------

Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>

---------

Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
2026-05-18 10:28:23 +00:00
Matt Kane ed917d9d53 chore: upgrade to TypeScript 6, pin tsgo beta, fix dual-load config (#1074)
- catalog typescript ^5.9.3 -> ^6.0.3 (current stable)
- pin @typescript/native-preview to 7.0.0-dev.20260421.2 (beta) to stop
  silent compiler drift from the floating ^7.0.0-dev range
- runtime.ts: key stored config on a Symbol.for registry entry instead of
  a typed globalThis var, removing the TS2403 dual-load (dist+src) error
  and matching the existing isolate-singleton pattern
- auth/create-emdash: add explicit types:[node]; the catalog bump's
  lockfile re-resolution stopped implicit @types/node auto-inclusion
2026-05-17 08:17:18 +00:00
ppppangu 06d5f3f016 fix(forms): unwrap public definition responses (#983) 2026-05-11 06:16:48 +00:00
Matt Kane 943df46d62 feat: plugin registry packages (lexicons, client, CLI) (#923)
* feat(registry-lexicons): scaffold @emdash-cms/registry-lexicons package

Adds a new package that generates TypeScript types and runtime validation
schemas from the EmDash plugin registry lexicons under
com.emdashcms.experimental.*. First package on the implementation path for
RFC 0001 (Decentralized Plugin Registry).

The lexicons themselves still live on the wip/plugin-rfc branch; they are
copied into the package at build time so the published artifact ships them.

Codegen via @atcute/lex-cli (matches the rest of the codebase, which uses
@atcute/* rather than @atproto/*). The single external lexicon ref to
com.atproto.label.defs#label resolves through @atcute/atproto.

Generated output is checked into src/generated/ so consumers do not need
the codegen toolchain. The public API exposes namespace re-exports
(PackageProfile, PackageRelease, AggregatorSearchPackages, ...), an NSID
constant map, and module augmentation of @atcute/lexicons/ambient so
@atcute/client callers get strong typing on these records and XRPC methods
automatically.

Tests build representative records, validate them via the generated runtime
schemas, and assert NSID-map completeness, catching codegen drift,
schema/type drift, and NSID typos.

oxlint: ignores **/src/generated/** (codegen output uses side-effect imports
for module augmentation that the linter flags).

EXPERIMENTAL: NSIDs and shapes will change while RFC 0001 is in flight.

* feat(registry-client): scaffold @emdash-cms/registry-client package

Atproto-aware client for the EmDash plugin registry, structured as three
independent layers so consumers pull in only what they need:

  - **credentials**: persists publisher sessions between CLI invocations.
    Three implementations: FileCredentialStore (~/.emdash/credentials.json
    with mode 0600 and atomic temp-file rename), EnvCredentialStore
    (read-only, reads EMDASH_PUBLISHER_* env vars for CI), and
    MemoryCredentialStore (tests). defaultCredentialStore() picks env vs
    file based on which env vars are set.
  - **publishing**: thin wrapper over @atcute/client for repo operations
    against the publisher's PDS — putRecord, uploadBlob, getRecord,
    listRecords. Built around a pre-authenticated atproto fetch handler;
    the interactive OAuth flow lives in the CLI (separate PR), not here,
    so this layer stays unit-testable without a real PDS.
  - **discovery**: read-only XRPC client over an aggregator. No
    authentication. Threads atproto-accept-labelers through every request
    so callers can configure which labellers' hard-takedown labels the
    aggregator applies.

The two publisher-side and consumer-side surfaces are deliberately split —
the admin UI's install flow only needs discovery and shouldn't have to
pull in OAuth deps.

Reuses atcute primitives throughout rather than rolling our own:

  - Did, Handle, Nsid, Blob, ResourceUri from @atcute/lexicons
  - isDid, isHandle from @atcute/lexicons/syntax for env-var validation
  - ClientResponseError from @atcute/client (re-exported as the canonical
    error type for consumers; replaces hand-rolled DiscoveryError /
    PublishingError)
  - ok() helper from @atcute/client to throw on non-2xx, keeping the call
    sites linear

Ambient module augmentation for com.atproto.repo.* XRPC methods is pulled
in via type-only side-effect import of @atcute/atproto, so we get strongly
typed putRecord/getRecord/etc. calls without dragging the runtime
validation schemas into the publishing client.

Adds @atcute/atproto, @atcute/client, @atcute/lex-cli, @atcute/lexicons,
and @atcute/oauth-node-client to the workspace catalog so future packages
share one source of truth for atcute pinning. The new
@emdash-cms/registry-lexicons and @emdash-cms/registry-client packages
both consume catalog refs.

31 tests cover credential store semantics (in-memory, filesystem
atomicity, env-var validation, read-only error paths), discovery client
behaviour (XRPC paths, accept-labelers header threading, error mapping),
and publishing client behaviour (putRecord body shape, validate flag,
error mapping, listRecords pagination).

* feat(registry-cli): scaffold @emdash-cms/registry-cli, build registry-* packages

Adds a standalone CLI for the experimental EmDash plugin registry, distributed
as @emdash-cms/registry-cli with a single emdash-registry binary. Also
switches @emdash-cms/registry-client and @emdash-cms/registry-lexicons from
shipping TypeScript source to building dist/ via tsdown, so consumers
download compiled JS + .d.ts and don't carry a TS toolchain.

Why a separate CLI rather than extending the core emdash CLI: the publishing
flow needs atproto OAuth, a loopback HTTP server, and Node-only deps. Most
EmDash users (site owners, content editors) never publish a plugin. Splitting
keeps these deps out of the core CMS install. Plugin authors run via npx or
install globally; site runtime stays atproto-free.

Subcommands:

  - login    Interactive atproto OAuth via a loopback HTTP server. Spins up
             a server on a random ephemeral 127.0.0.1 port, opens the
             user's browser at the AS authorization URL, awaits the callback,
             exchanges the code, and persists both the OAuth library's
             StoredSession blob (~/.emdash/oauth/) and the publisher's
             display info (~/.emdash/credentials.json).
  - logout   Revoke the active session (or a specific DID) and remove
             stored state. Falls back to local-only cleanup if remote
             revoke fails, so users always end up logged out locally.
  - whoami   List the active session and any others stored. No network.
  - search   Free-text search the aggregator. Read-only; no auth.
  - info     Show package details. Routes to getPackage (DID + slug) or
             resolvePackage (handle + slug) based on input shape.
  - publish  Stub. Real bundle + atproto write path lands in a follow-up
             PR; for now points users at the legacy emdash plugin publish.

OAuth state is held in two FileStores under ~/.emdash/oauth/, both with
mode 0600 and atomic temp-file rename. The OAuth library treats the
contents as opaque; we just round-trip them.

The aggregator URL is configurable per-invocation via --aggregator <url>
or the EMDASH_REGISTRY_URL env var, falling back to the experimental
default. Resolution precedence has unit tests.

Build / packaging changes for the existing registry-* packages:

  - registry-lexicons: tsdown bundles src/ + generated/ to dist/ as ESM
    + .d.ts. Codegen still writes to src/generated/ (checked in for
    consumers without the lex-cli toolchain). build: copies the JSON
    lexicons from the repo root if present, otherwise uses the
    in-package copy -- so the package is buildable on any branch.
  - registry-client: tsdown bundles all four entry points
    (root + credentials + publishing + discovery) to dist/ as ESM + .d.ts.
    Tests still run against src/ via vitest's ts pipeline.
  - Both add publint + attw to their check scripts.

Discovery client tests fixed: getPackage / resolvePackage / listReleases /
getLatestRelease all take {did|handle, slug|package} pairs, not AT URIs.
Earlier scaffold had the wrong shapes. The new info command and tests
both match the actual lexicon contracts.

Catalogs @atcute/identity-resolver alongside the other @atcute/* deps.

* feat(registry-cli, plugin-types): bundling, publish flow, shared manifest types

Three things in one commit because they're tightly coupled:

1. Adds bundling and publishing to @emdash-cms/registry-cli.

   - bundle: copied from packages/core/src/cli/commands/bundle.ts as a
     deliberately temporary duplicate. Refactored from a citty handler
     full of process.exit and consola calls into a programmatic
     bundlePlugin(opts) function that returns a typed BundleResult or
     throws BundleError with a structured code (MISSING_PACKAGE_JSON,
     MISSING_ENTRYPOINT, MAIN_BUILD_FAILED, INVALID_PLUGIN_FORMAT,
     TRUSTED_ONLY_FEATURE, BACKEND_BUILD_FAILED, VALIDATION_FAILED).
     The citty wrapper in command.ts is now ~50 lines that delegates to
     the API. The legacy core copy stays until phase 1 cutover; both
     copies will diverge as we evolve registry-cli, then the legacy one
     gets deleted wholesale.

   - publish: takes --tarball <path> --url <url>, computes a sha2-256
     multibase-multihash, extracts the manifest from the tarball,
     verifies the remote URL matches the local checksum, resumes the
     active publisher session, bootstraps a com.emdashcms.experimental
     .package.profile record on first publish (with --license and
     --security-email/--security-url required) or reuses the existing
     one, and puts the package.release record at <slug>:<version> with
     the artifact URL + checksum.

   - Programmatic API exported from the package root: bundlePlugin,
     BundleError, BundleResult, BundleLogger, sha256Multihash, plus
     re-exports of the manifest contract types. CLI consumers get the
     binary; tooling consumers can import from
     @emdash-cms/registry-cli directly.

2. Adds @emdash-cms/plugin-types as the shared manifest contract.

   Same types were duplicated in core and registry-cli. Now they live
   once in a small types-only package, consumed by both. The vocabulary
   includes PluginCapability, the legacy-rename map (CAPABILITY_RENAMES,
   isDeprecatedCapability, normalizeCapability, normalizeCapabilities),
   the manifest shape (PluginManifest, ManifestHookEntry,
   ManifestRouteEntry, PluginAdminConfig, PluginStorageConfig,
   StorageCollectionConfig).

   Core's plugins/types.ts now imports and re-exports these — existing
   internal callers keep working because the symbols are still exported
   from the same module path. Core keeps its own stricter PluginManifest
   interface (uses keyof PluginHooks for hook names, typed
   PluginAdminPage[], etc.) with a compile-time assertion that it
   remains assignable to the shared version.

   After the registry phase 1 cutover removes the legacy bundling code
   from core, both sides will continue depending on this single source
   of truth — no drift.

3. Test coverage.

   - 16 unit tests for bundle/utils.ts pure helpers (extractManifest,
     findNodeBuiltinImports across the various import patterns,
     findSourceExports for both string and conditional export shapes).
   - 8 end-to-end bundle tests against a fixture plugin in
     tests/fixtures/minimal-plugin/. Each test invokes bundlePlugin
     against a real source directory, runs tsdown, writes the tarball
     to a temp dir, unpacks it, and asserts the manifest body and
     entry list.
   - 1 multihash vector test against a known sha2-256("hello world")
     value, captured from the running encoder. The previous suite of 4
     tests was tautological ("is deterministic", "differs between
     distinct inputs") — replaced with the single contract-level
     assertion.
   - 10 plugin-types tests covering the rename map's terminal
     property, the prototype-key safety of the type guard, the
     dedup-on-normalize behaviour for manifests declaring both legacy
     and canonical names.

   Total across the registry family + core: 3188 tests passing.

Library hygiene:

   - Found and dropped publishing.uploadBlob from registry-client. The
     RFC's artifact model puts checksums on author-hosted URLs, so the
     atproto blob upload path is dead code.
   - Replaced hand-rolled isDid regex / DiscoveryError-PublishingError /
     custom blob ref types with their atcute-provided equivalents
     (isDid from @atcute/lexicons/syntax, ClientResponseError from
     @atcute/client, Blob from @atcute/lexicons).
   - sha256Multihash uses @atcute/multibase toBase32 for the encoding
     (consistent with the rest of the codebase's @atcute/* surface)
     and @oslojs/crypto/sha2 for the digest. atcute has no multihash
     helper because @atcute/cid would emit a CIDv1, which is a
     different shape from the multibase-multihash the FAIR / registry
     RFC specifies.
   - Catalogued @atcute/multibase, @oslojs/crypto, @atcute/client.

oxlint exemption: packages/registry-cli/src/**/*.ts now matches core's
**/cli/**/*.ts pattern for the no-unsafe-type-assertion rule, since the
copied bundling code uses the same as-cast patterns and registry-cli
is itself a CLI package.

* feat(registry-cli): extract publishRelease() API, refuse version overwrites, mock PDS tests

Three changes that go together:

1. Refactor publish into a programmatic API + thin CLI wrapper.

   commands/publish.ts was a 400-line citty handler that mixed flag
   parsing, filesystem credentials, OAuth resume, HTTP fetching, manifest
   extraction, FAIR/atproto record building, and consola output. Pulled
   the core flow into src/publish/api.ts as publishRelease(opts) that
   takes pre-built inputs (PublishingClient, manifest, checksum, url,
   ProfileBootstrap) and returns a typed PublishResult or throws
   PublishError with a structured code. Mirrors the bundle/api.ts
   bundlePlugin shape.

   PublishError codes:
   - DEPRECATED_CAPABILITY: manifest declares one of the legacy
     capability names. Bundle warns; publish refuses.
   - PROFILE_BOOTSTRAP_MISSING_FIELD: first publish without --license
     or --security-email/--security-url. Lexicon enforces both, but
     surfacing the failure here gives an actionable error before any
     network round-trip.
   - RELEASE_ALREADY_PUBLISHED: a release record at <slug>:<version>
     already exists in the publisher's repo.

2. Refuse to overwrite an existing release by default.

   FAIR specifies version-record immutability; aggregators and
   labellers may treat any change to <slug>:<version> as a takedown
   event. Previous publishRelease silently overwrote (because the PDS
   accepts putRecord on existing rkeys). Now it checks for an existing
   record and refuses unless allowOverwrite: true.

   Two side effects:

   - Subsequent publishes that pass first-publish-only flags
     (--license, --author-*, --security-*) get a warning naming each
     ignored flag. The existing profile wins; flags are silently
     dropped on the wire, and previously the user got no signal.
   - The CLI surface adds --allow-overwrite for the rare case where
     overwriting is intentional (consumers haven't installed yet, etc).
     The error message points at it.

3. Mock PDS test fixture and 14 new publish tests.

   tests/mock-pds.ts implements the FetchHandlerObject contract that
   PublishingClient.fromHandler accepts, with an in-memory record map
   keyed by AT URI. Tests use it to drive the publish flow without OAuth,
   filesystem credentials, or a live PDS. Returns realistic atproto error
   payloads (RecordNotFound, InvalidRequest) so the publish flow's
   ClientResponseError-keyed error handling exercises the same paths a
   real PDS would trigger. Reusable for any future test that drives
   PublishingClient.

   New tests cover:
   - First publish: profile + release records, populated from
     ProfileBootstrap fields, hard-failing on missing license or
     security contact, accepting securityUrl as an alternative to
     securityEmail.
   - Subsequent release: existing profile preserved (CID and bytes
     unchanged), ignoredProfileFields names every overlap, undefined
     profile reports empty.
   - Version collision: refused by default with detail { slug, version },
     overwritten + signalled when allowOverwrite is true, original bytes
     preserved on the refused path.
   - Deprecated-capability hard-fail runs before any XRPC call (asserted
     via pds.calls).
   - Slug derivation strips leading @ and replaces / for scoped npm names.

   Each test asserts what's in the mock PDS after the call, so we catch
   regressions in both happy and error paths.

Other tweaks:

- README rewritten honestly: search/info/publish work in code, but the
  aggregator side isn't deployed yet. Lead with bundle as the
  introductory example.
- bundle command's "next steps" hint updated to match the new
  --url-only publish shape.
- publishRelease re-exported from the package's programmatic API so
  tooling can call it directly without spawning a subprocess.

* fix(registry): adversarial-review pass — atomic publish, lexicon validation, hardening

Addresses the findings from the adversarial review of the registry PR. Five
critical bugs and a long tail of high/medium/low items.

CRITICAL

- Profile + release writes now happen in a single `com.atproto.repo.applyWrites`
  commit. Previous flow issued separate `putRecord` calls so a network blip
  between them could leave a profile with no release (or vice versa). The
  atomic batch also lets us update the existing profile's `lastUpdated` on
  every release without a second round-trip.

- `PublishingClient.putRecord` defaults to `validate: true` (was `false`).
  The PDS now validates every registry record against its lexicon at write
  time. The `unsafePutRecord` escape hatch exists for callers writing
  records the PDS doesn't yet know how to validate. Default-off validation
  silently bypassed every constraint we'd spent the lexicon files defining.

- Slug and version validated against the lexicon constraints upstream of
  the network round-trip. `deriveSlugFromId`/`isPluginSlug`/`isPluginVersion`
  live in `@emdash-cms/plugin-types` so both the bundler and the publisher
  use the same regex (^[a-z][a-z0-9_-]*$ / ^[a-zA-Z0-9.-]+$). New
  `PublishError` codes `INVALID_SLUG` and `INVALID_VERSION` produce
  actionable errors instead of opaque PDS rejections like `InvalidRequest`.

- OAuth callback server holds the response open until the CLI tells it
  what to render, so the user's browser shows "Login complete" only AFTER
  atcute has validated the callback params. Previously, ANY GET to /callback
  resolved the promise and rendered success. A stray browser tab firing at
  the loopback could trick users into thinking they were logged in when
  they weren't.

- Tarball URL validation: rejects `file:`, non-http(s) schemes, IPv4
  RFC-1918 ranges, IPv6 ULA / link-local. Streams the body with a 5MB cap
  so a malicious URL can't OOM the CLI. Manifest extraction wraps the
  modern-tar gunzip in a try/catch that produces a clean error if the URL
  served HTML instead of a tarball. Manifest extraction now runs BEFORE
  any "tarball looks fine" output so a malformed file fails loudly.

HIGH

- The "type-level guard" in core's `PluginManifest` actually enforces
  drift now: `const _check: _AssertManifestCompat = true` errors if the
  conditional resolves to `never`. Previous `type _CompatCheck = ...`
  was decorative because `type X = never` is legal at the type level.

- Bundler descriptor extraction tightened. We only call `createPlugin()`
  or the default export -- no more speculative-call-every-named-export
  loop that would mis-resolve a plugin with helper functions returning
  {id, version}-shaped objects. Dynamic import results validated via
  `isResolvedPluginShape` / `isPluginDescriptorShape` runtime guards
  before being treated as a plugin.

- The bundler's `emdash` shim is now a Proxy that throws on any access
  other than `definePlugin`. Plugins that import other things from
  `emdash` (e.g. `defineCronTask`) used to silently get `undefined`
  values that tree-shaking eliminated, masking real bugs. The fixture
  plugin now uses `import { definePlugin } from "emdash"` so the shim
  resolution path is actually exercised by the bundle tests.

- `PublishingClient.putRecord` is generic over `RegistryRecords[C]`
  (the lexicon-derived type map). Compile-time check that you can't put a
  profile-shaped record into a release collection. New `applyWrites`
  method takes a typed `PublishOperation[]` for the same reason.

- Bundler tmpDir uses `mkdtemp(tmpdir())` instead of a fixed
  `.emdash-bundle-tmp` under the plugin source. Concurrent bundle runs
  no longer trample each other; the tmpdir doesn't show up in `git
  status`. Misplaced `.endsWith` cleanup guard removed.

- File writes (`FileCredentialStore`, `FileStore`) now pass
  `flush: true` to writeFile for durability. Atomic rename was already
  torn-write safe; `flush: true` (Node 21.1+) fsyncs the contents
  before rename so a power loss can't surface an empty inode pointing at
  unwritten data.

- `EnvCredentialStore` stamps `updatedAt` once at construction, not on
  every read. Successive `current()` / `get()` / `list()` now agree
  on the timestamp.

- `getRecordOrNull` returns `{ uri, cid, value } | null` sentinel
  instead of the value alone. `if (existingProfile !== null)` instead
  of truthiness, so a legitimately-falsy stored value can't be mistaken
  for "no record".

- Profile-bootstrap field validation runs before any network round-trip:
  if `license` or security contact is missing on first publish, the
  CLI fails fast with no `getRecord` calls issued. Was previously
  caught after the existence check.

- Hard-fails when a standard-format descriptor declares hooks/routes but
  no sandbox entry exists. The bundler can't probe for the hook/route
  names; emitting a manifest that promises functionality the bundle
  can't deliver is worse than refusing to bundle.

- Discovery client always *overwrites* the `atproto-accept-labelers`
  request header rather than only setting it if absent. Caller-supplied
  values can no longer override the aggregator's policy.

MEDIUM

- New mock PDS faithfully models the rejections a real PDS would issue:
  rejects writes whose `repo` field doesn't match the mock's DID,
  rejects rkeys outside atproto's record-key alphabet, supports
  `applyWrites` with all-or-nothing atomic-commit semantics, derives
  CIDs from record content (so identical bytes round-trip to identical
  CIDs, matching real-PDS behaviour). Tests now assert XRPC call counts
  via `pds.callsTo(nsid)` rather than counting on mock implementation
  details.

- Empty-string `--license=""` etc. flags rejected up front with
  "--license cannot be empty" instead of bubbling up as a confusing
  missing-field error.

- `emdash-registry switch <did>` implemented (whoami previously
  printed "TODO: not yet implemented" to end users).

- Search command takes `--cursor`. The advice on a paginated result
  now points users at the cursor flag instead of suggesting they bump
  `--limit` past the aggregator's 100-result cap.

- `info` parses the lexicon-typed profile via `safeParse(PackageProfile.mainSchema, ...)`
  instead of casting to a hand-rolled `ProfileFields` interface. Falls
  back to best-effort string extraction with a warning if the record
  doesn't validate.

LOW

- escapeHtml also escapes single-quote and forward-slash for defence in
  depth.

- `isErrnoException` checks `typeof code === "string"` so a non-fs
  Error with a non-string `code` property doesn't pass the guard.

- Proxy-shim writing hoisted into a single `writeEmdashShim` helper
  used by both the main bundle and the sandbox probe.

- Module-scope regexes for IP-literal detection so they aren't recompiled
  per call (caught by lint).

- `@emdash-cms/plugin-types` added to `test:unit`.

- oxlint `no-unsafe-type-assertion` carve-out narrowed from a blanket
  `packages/registry-cli/src/**/*.ts` to the four files that legitimately
  use casts at trust boundaries (bundle/api, oauth, publish/api, etc.).

- `extractManifestFromTarball` accepts both `manifest.json` and
  `./manifest.json` since modern-tar's exact naming isn't pinned.

- index.ts doc comment updated -- publish is no longer a stub.

NEW TESTS

- 14 plugin-types tests for slug/version validation.
- 21 publish tests up from 14: cover atomic batches, parallel reads,
  refusal preserves bytes, overwrite semantics, deprecated capability /
  invalid slug / invalid version all hard-fail before any network call,
  scoped npm names, malformed-existing-profile fallback path.
- Bundle tests for "hooks declared but no sandbox entry" hard-fail and
  for concurrent bundle runs not colliding on tmpdir.
- registry-client gets an applyWrites batch test plus an
  unsafePutRecord/skipValidation test.

3227 tests passing across plugin-types, registry-lexicons,
registry-client, registry-cli, and core. Workspace typecheck clean.
Lint clean across all touched files.

* registry-cli: address round-2 review findings

C1 Lexicon validation: validateLocally now safeParses every record against
PackageProfile/PackageRelease/PackageReleaseExtension before applyWrites.
applyWrites is sent with skipValidation: true since the PDS doesn't know
our experimental NSIDs. New PublishError code: LEXICON_VALIDATION_FAILED.

C2 Release extension: publishRelease now embeds a packageReleaseExtension
record inside release.extensions, built from manifest.capabilities and
manifest.allowedHosts via buildDeclaredAccess. Without it, sandbox runtimes
have no contract to enforce.

H1 Semver: PLUGIN_VERSION_RE tightened to real semver-2.0 BNF (no build
metadata, no leading zeros). Tests cover the boundary cases.

H2 Redirect handling: fetchTarball follows redirects manually and
re-validates each hop against validatePublishUrl. Defeats the trick of a
public URL that 302s to 169.254.169.254 or localhost.

H3 MockPds: create rejects when key exists, update rejects when key
absent. Matches real PDS semantics so tests don't pass on broken paths.

H4 MockPds atomicity: existence checks moved to up-front validation pass.

H5 swapCommit: documented why we don't pass it (single-publisher repos
don't need optimistic CAS).

H6 --json purity: redirectConsolaToStderr swaps the global reporter so all
human messages go to stderr; only the final JSON object hits stdout.

H7 _AssertManifestCompat: added comment explaining the one-direction
check is intentional (shared is wider; bidirectional would fail because
shared uses string for hook names while core narrows to HookName).

M1 Manifest validation: assertManifestShape runs after JSON.parse so a
malicious tarball with garbage manifest.json fails fast with a clear
error.

M2 Descriptor guards: now check element types of capabilities and
allowedHosts arrays.

M3/M4 Sandbox probe: hooks/routes without function handlers now hard-fail
with INVALID_PLUGIN_FORMAT instead of silently ending up with undefined
handlers.

M5 tmpDir leak: import("tsdown") moved inside the try block so a
missing/broken tsdown install doesn't orphan the tmpdir.

M6 Directory fsync: after rename, fsync the directory so the rename is
durable across power loss. Best-effort -- some filesystems reject opening
a directory.

M7 stampLastUpdated: documented that lexicon validation in step 5 catches
invalid round-tripped fields.

L1 Shim: documented why named-import errors are caught at build time and
don't need shim-level handling.

L2 --local help: clarified it doesn't skip the download.

L3 Plain HTTP: validatePublishUrl now requires https. The cost is zero in
2026 and it shuts the door on novel checksum-bypass attacks.

* registry-cli: address round-3 review findings

H1 SSRF — IPv4-mapped IPv6: validatePublishUrl now rejects ::ffff:1.2.3.4
and ::1.2.3.4 forms. v4 deny list expanded to include 0.0.0.0/8 and CGNAT
100.64.0.0/10.

H2 Redirect DNS resolution: fetchTarball now DNS-resolves the initial URL
and every redirect hop's hostname against the same private-IP allow-list,
defending against a public hostname pointed at 10.x or 169.254.169.254.

H3 declaredAccess vs bundler warning: publishRelease now hard-fails
network:request with no allowedHosts (lexicon treats {} as unrestricted,
contradicting the bundler warning). Authors must list hosts or upgrade to
network:request:unrestricted. The bundler warning text now matches the
publish-time refusal.

H4 write implies read: buildDeclaredAccess now always emits content.read
and media.read when content.write or media.write is present, matching the
documented lexicon semantics.

H5 stampLastUpdated: always normalizes $type to the current
NSID.packageProfile, so an existing record from an earlier shape doesn't
break every subsequent publish via lexicon validation.

H6 --json error path: every failure path now emits a structured
{ error: { code, message } } JSON object on stdout in --json mode, so
piped consumers see the same JSON contract for success and failure.
Internal CliError class carries stable error codes for non-PublishError
failures.

H7 assertManifestShape: rejects arrays as storage/admin (typeof [] is
'object'), validates that hooks/routes entries are strings or
non-array/non-null objects, with a describeJsonValue helper for clearer
error messages.

M1 MockPds: documented that pre-batch existence checks diverge from a
real PDS's post-batch MST snapshot semantics. Doesn't affect coverage
today.

M2 redirectConsolaToStderr: returns a restore function captured by the
outer try/finally so an in-process wrapper that runs publish then
continues with another command gets its consola back.

M3 fsyncDir: docstring now honestly describes platform reality (Linux
durable, macOS already covered by file fsync, Windows benign no-op).

M4 _AssertManifestCompat: clarified that the one-direction check is
intentional; runtime narrowing of the wider wire shape happens in core's
manifest-schema.ts via zod.

M5 declaredAccess capability gap: warn at publish about users:* and
hooks.*:register capabilities that have no declaredAccess mapping today
(lexicon limitation).

M6 validateLocally: documented that atcute's v.object accepts unknown
keys silently; aggregators MUST do their own strict validation.

L1 INVALID_MANIFEST: now actually thrown (in the network:request hard-
fail from H3).

L2 multihash: docstring corrected (output is 56 chars total, not 34).

* registry-cli: address round-4 review findings

CRITICAL-1 IPv6 bracket bug: Node URL parser keeps brackets and converts
embedded IPv4 to hex pairs. validatePublishUrl now strips brackets and
detects:
- IPv4-mapped dotted form (::ffff:1.2.3.4)
- IPv4-mapped hex form (::ffff:hhhh:hhhh) which decodes to v4 then re-runs
  the v4 private check
- IPv4-compatible (::1.2.3.4)
- ULA (fc00::/7), link-local (fe80::/10), loopback (::1), unspecified (::)
- NAT64 prefix (64:ff9b::a.b.c.d) caught by trailing-hex-pair fallback

CRITICAL-2 ULA/link-local IPv6 bracket bug: same root cause; same fix.
The pre-existing IPv6_ULA_FC_RE patterns now run on bracket-stripped
input so they actually match.

26 new test cases in tests/url-validation.test.ts covering all the
SSRF-shaped IPv6 inputs that previously slipped through.

H1 process.exit bypass: capture exitCode in catch, run finally, then
exit. Reporters are restored on both success and failure paths.

H2 CliError.exitCode wired up: catch reads error.exitCode (defaults to 1)
so user-error (2) vs publish-failure (1) is preserved in scripts.

H3 stampLastUpdated whitelist: re-emit only schema-known fields rather
than spreading the existing record. Prevents leftover fields from earlier
experimental shapes from surviving a republish.

H4 validateResolvedHost handles bracketed v6: strips brackets first; the
short-circuit-on-colon path is now safe because validatePublishUrl is
guaranteed to have caught any v6 literal first.

H5 TOCTOU DNS rebinding: documented the residual window (validation
lookup vs fetch lookup) explicitly. Full mitigation requires resolving
once and binding the connection to a literal; we accept the residual
risk for the publish CLI and document it.

H6 unmapped capability detection: derived from what buildDeclaredAccess
actually emitted, via capabilityIsRepresented(). A future capability
added to plugin-types but not to buildDeclaredAccess now surfaces in the
warning automatically.

M1 IPV6_V4_MAPPED regex: replaced with strict ::ffff: dotted/hex variants
plus a separate ::compat-dotted variant, eliminating false positives.

M2 dns lookup: kept lookup() for now; documented residual /etc/hosts
trust in the comment.

M3 hoisted dns import: top-level import { lookup as dnsLookup }; no more
microtask hop per redirect hop, no more import-resolution failure mode.

M4 --json schema doc: flag description now lists success and failure
shapes inline.

M5 assertManifestShape doc: renamed the responsibility honestly to
'best-effort structural sanity check' and pointed callers needing full
validation at packages/core/src/plugins/manifest-schema.ts.

M6 mock-pds claim softened: removed the unverified 'post-batch MST'
claim, replaced with 'behaviour may vary; validate before depending'.

* registry-cli: address round-5 review findings (M-1, M-2)

M-1 trailing-dot bypass: stripTrailingDot canonicalises FQDN form before
the .local / localhost equality checks. mDNS resolvers respond to both
'foo.local' and 'foo.local.', so the syntactic guard had to too.

M-2 NAT64 catch-all false positives: replaced the generic 'last two hex
groups encode v4' fallback with explicit prefix patterns: NAT64
(64:ff9b::/96) and 6to4 (2002::/16). Public v6 addresses whose suffix
coincidentally encodes a private v4 (e.g. 2001:db8::a00:1) no longer
false-positive reject.

Also fixed an IPv4-compat hex normalisation gap: '::169.254.169.254'
gets normalised by Node's URL parser to '::a9fe:a9fe' (no 'ffff:'
prefix). The IPV6_V4_COMPAT_HEX_RE branch catches that form;
IPV6_V4_MAPPED_HEX_RE only catches the '::ffff:' variant.

3 new test cases for the trailing-dot variants, 6to4 with embedded
private v4, and the negative case (public v6 with private-looking
suffix). Total 82 tests passing.

* registry: address copilot review + CI build fix

CI: build registry packages (not just emdash deps) so the unit-test job
can resolve workspace links to dist/. The registry packages aren't
direct deps of emdash, so 'emdash...' filter left them unbuilt and
their tests failed at module resolution.

credentials/file.ts:
- Preserve on-disk version (no silent downgrade); reject forward-version
  files explicitly so an older CLI doesn't blindly overwrite a newer
  format.
- Validate every session entry structurally (did/handle/pds/updatedAt),
  cross-check the map key equals session.did, verify currentDid points
  at an existing session.
- Update header comment to match actual behaviour: extra top-level
  fields are NOT preserved on round-trip.

PublisherSession.handle: now 'string | null' rather than the branded
Handle template literal. login.ts persists null when handle resolution
fails; whoami/switch/publish/publish-success-line all render
session.handle ?? session.did. No more 'unknown.invalid' placeholder
that misleads users.

discovery/listReleases: docstring clarifies descending semver order
(not 'reverse-chronological').

Changeset: 'publish' is no longer described as a stub; the actual
behaviour (fetch tarball, checksum, manifest extraction, atomic
applyWrites with declaredAccess extension) is documented.

tsdown.config.ts comment: matches the actual '.mjs'/'.d.mts' output.

registry-client docs (src/index.ts + README): reference 'emdash-registry'
(the actual binary name) rather than the imagined 'emdash plugin' path.

registry-cli README: EXPERIMENTAL note clarifies what works today
(publish writes to PDS) vs what needs a deployed aggregator (search,
info). Command list now includes 'switch'.

* registry: address PR review comments

profile.ts: read PDS URL from session.getTokenInfo().aud instead of the
nonexistent getSession.pdsUrl field. The Bluesky lexicon's getSession
output schema does not include pdsUrl, so the previous code persisted
pds: '' on every successful login, locking the user out of subsequent
commands once the new credentials validator rejected the empty pds.

FileCredentialStore.put(): refuse to persist a session that doesn't
pass isPublisherSession. Catches upstream regressions at write time
rather than at next-read.

Credential validator hardening:
- isDid runtime check on session.did and currentDid (was: typeof
  string), so hand-edited corruptions surface here rather than in the
  OAuth library.
- Object.hasOwn instead of `in` for currentDid -> sessions check, so
  prototype-chain names (toString, constructor) can't slip through.
- Sessions map is null-prototype, so bracket access on unknown keys
  can't resolve through Object.prototype.
- Version range check requires Number.isInteger and a positive value;
  rejects NaN, negative, fractional.

login.ts: --json output and success line now use handleForStorage
(null when handle resolution fails), so we don't render the DID twice
in adjacent lines or emit DID-as-handle in JSON.

URL validation: added RFC 8215 NAT64 local-use prefix (64:ff9b:1::/48)
to the SSRF deny list.

lex.config.ts: codegen reads from the in-package lexicons/ directory
(was: nonexistent repo-root path).

Doc fixes:
- credentials/types.ts: emdash plugin -> emdash-registry
- credentials/env.ts: removed reference to unused EMDASH_PUBLISHER_SESSION
- registry-cli README: switch <did> (was: switch <handle-or-did>)

* registry-client: fix typecheck failure in put() validator

The isPublisherSession type guard narrows the negative branch to never
when the input is already typed PublisherSession, so reading fields
off the value for the error message produced TS2339. Use safeStringify
on the whole value instead.
2026-05-07 06:50:11 +01:00
Matt Kane 333acee0e5 chore(ci): pin node 22 floor, fix bot bypasses on CLA and PR Compliance (#879)
- Add `engines.node: ">=22"` so workflows using `node-version-file: package.json`
  (review, bonk) stop falling back to Node 20 when no version is declared.
- CLA Assistant: add `opencode` to the allowlist. ask-bonk PRs commit with git
  author `opencode`, which has no GitHub user mapping, so the action's
  committer-identity check never matched the existing `ask-bonk[bot]` entry.
- PR Compliance:
  - Switch from `pull_request` to `pull_request_target` so the comment step
    works on PRs from forks (was hitting 403 Resource not accessible by
    integration).
  - Filter on `pull_request.user.login` instead of `github.actor`. Actor
    becomes a maintainer on synchronize/edited events triggered by pushing
    or merging into a bot branch, which let bot PRs slip past the gate.
2026-05-01 07:56:36 +01:00
Matt Kane f97d6ab0f1 Add query-count perf harness + instrumentation (#653)
* feat: add query-count perf harness + instrumentation

Opt-in Kysely log hook gated behind EMDASH_QUERY_LOG=1 emits per-request
NDJSON on stdout so a harness can count DB queries per route. Zero
overhead when disabled. Exposed at emdash/database/instrumentation so
@emdash-cms/cloudflare can wire the same hook into its per-request D1
session Kysely.

Adds fixtures/perf-site (minimal blog-style fixture, dual sqlite/d1
config), scripts/query-counts.mjs (pnpm query-counts), committed
snapshot files for both targets, and a CI job that runs both.

* fix(perf): invoke emdash CLI directly in query-counts harness

pnpm exec emdash fails in CI because bin symlinks aren't linked for
workspace-local packages (see scripts/relink-bins-if-needed.mjs, which
early-exits under CI). Invoke the built CLI entry by absolute path
instead so the harness works in both CI and local dev.

* ci(perf): build all packages for query-counts job

The fixture config imports from @emdash-cms/cloudflare for the d1 path,
so `pnpm run --filter emdash... build` (which only walks emdash's
deps, not its dependents) leaves cloudflare unbuilt and astro fails
to resolve the import when loading the config.

* fix(perf): wait for TCP port instead of parsing stdout for ready

The ready-regex approach was fragile — in CI, the cloudflare adapter's
dev mode wraps output in [vite] prefixes and the "ready in" line
sometimes never matches (observed on the D1 seed step: typegen POST
succeeded but ready timeout still fired).

TCP-connect is the real question anyway ("is the server accepting
connections?"). It also doesn't warm a fresh workerd isolate —
workerd defers isolate creation to the first HTTP request — so the
per-route cold-isolate measurement stays honest.

* fix(perf): seed D1 before building for preview

`astro dev` (the seed step) leaves .wrangler/deploy/ without the
build-time config.json that cloudflare adapter's preview requires, so
running `astro build` after the seed is what makes the subsequent
`astro preview` spins work.
2026-04-19 07:55:41 +01:00
Matt Kane d4bfddba70 fix(ci): use script for changeset version command
The changesets action passes the version string as arguments to the
binary, not to a shell. "pnpm changeset version && pnpm install"
was parsed as extra args to changeset, causing "Too many arguments"
error. Use a package.json script instead.
2026-04-12 15:34:41 +01:00
Benjamin Price a378912697 fix: relink CLI bins after build only when needed (#482)
* fix: relink CLI bins after build only when needed

pnpm only creates bin symlinks for workspace packages when the target
file exists at install time. Since the CLI lives in dist/, it doesn't
exist until after the first build, so `emdash seed` fails for new
contributors. Add a postbuild script that detects missing or stale
bins and relinks only when necessary — zero overhead on normal builds.

* fix: rewrite relink script as Node for cross-platform support

Replace the shell script with a Node script so it works on Windows.
Also drop the mtime check — the bundler rewrites the CLI on every
build so it would trigger a spurious relink every time. Now only
relinks when the built CLI exists but the bin symlink is missing.
2026-04-12 14:59:08 +01:00
Matt Kane cde3834ddc Add Lunaria translation tracking and i18n dashboard (#461)
* Add Lunaria translation tracking with PO dictionary support

Configure Lunaria to track PO files directly using dictionary mode
(via lunariajs/lunaria#178). Add Spanish as first translation locale.

- Add lunaria.config.json with PO dictionary tracking
- Add Spanish locale to Lingui config and extract empty catalog
- Add locale:extract and locale:compile scripts to root

* Switch Lunaria config to TypeScript with defineConfig

* Fix Lunaria config: include only source file, not all PO files

* Add translation status dashboard deployable to i18n.emdashcms.com

Static HTML dashboard generated from Lunaria, deployed as Cloudflare
Workers static assets. Shows per-locale completion with progress bars,
missing keys, and GitHub edit links.

* Move Lunaria build/deploy scripts into i18n/ package

* Add Lunaria GitHub Action for PR translation impact comments

* Add i18n workspace package, update lockfile

* style: format

* Replace Spanish with German as first translation locale

* fix ts

* style: format

* Add wrangler

---------

Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
2026-04-11 19:31:09 +00:00
Yan e868a60f06 Fix globs in package.json scripts 2026-04-01 15:29:23 -03:00
Matt Kane 2e863566b3 Fix scope 2026-04-01 10:58:32 +01:00
Matt Kane 43fcb9a131 first commit 2026-04-01 10:44:22 +01:00