From b71a3458c35fec44c6ae992cd9abef0cb9b74202 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Thu, 3 Sep 2026 11:14:18 +0100 Subject: [PATCH 1/6] refactor(workspace): move the package into packages/trilean Every file belonging to the package itself -- src/, test/, scripts/, its manifest, tsconfigs, tsdown/vitest/wrangler configs, its ESLint config, README, and CHANGELOG -- moves under packages/trilean/, leaving the repository root free to hold workspace-level configuration only. LICENSE is copied rather than moved: the package needs its own copy in the published tarball, and the repository still needs one at its root. Pure relocation, byte for byte -- every path is a 100% rename and no file's content changes. The root workspace configuration that makes this directory a pnpm workspace member lands in the following commit. --- AGENTS.md => packages/trilean/AGENTS.md | 0 CHANGELOG.md => packages/trilean/CHANGELOG.md | 0 CLAUDE.md => packages/trilean/CLAUDE.md | 0 packages/trilean/LICENSE | 21 +++++++++++++++++++ README.md => packages/trilean/README.md | 0 .../trilean/eslint.config.ts | 0 package.json => packages/trilean/package.json | 0 .../trilean/scripts}/generate-json-schema.ts | 0 {src => packages/trilean/src}/complex.test.ts | 0 {src => packages/trilean/src}/complex.ts | 0 .../trilean/src}/computed-value.test.ts | 0 .../trilean/src}/computed-value.ts | 0 .../trilean/src}/derived-aggregates.test.ts | 0 .../trilean/src}/derived-aggregates.ts | 0 .../trilean/src}/derived-connectives.test.ts | 0 .../trilean/src}/derived-connectives.ts | 0 .../trilean/src}/derived-patterns.test.ts | 0 .../trilean/src}/derived-patterns.ts | 0 .../trilean/src}/derived-values.test.ts | 0 .../trilean/src}/derived-values.ts | 0 {src => packages/trilean/src}/evaluation.ts | 0 .../src}/evaluator.indeterminacy.test.ts | 0 .../trilean/src}/evaluator.test.ts | 0 {src => packages/trilean/src}/evaluator.ts | 0 {src => packages/trilean/src}/functions.ts | 0 .../trilean/src}/golden-examples.test.ts | 0 {src => packages/trilean/src}/index.ts | 0 {src => packages/trilean/src}/json-value.ts | 0 {src => packages/trilean/src}/resolvers.ts | 0 .../src}/test-support/golden-example.ts | 0 {src => packages/trilean/src}/tree.test.ts | 0 {src => packages/trilean/src}/tree.ts | 0 .../trilean/src}/truth-tables.test.ts | 0 .../integration/complex-arithmetic.test.ts | 0 .../test}/integration/composed-rules.test.ts | 0 .../integration/delegate-handler.test.ts | 0 .../integration/function-registry.test.ts | 0 .../json-schema-consistency.test.ts | 0 .../integration/pattern-matching.test.ts | 0 .../test}/integration/schema-pipeline.test.ts | 0 .../test}/integration/tree-reference.test.ts | 0 {test => packages/trilean/test}/smoke.test.ts | 0 .../trilean/test}/workers/trilean.test.ts | 0 .../trilean/tsconfig.json | 0 .../trilean/tsconfig.node.json | 0 .../trilean/tsdown.config.ts | 0 .../trilean/vitest.config.ts | 0 .../trilean/wrangler.jsonc | 0 48 files changed, 21 insertions(+) rename AGENTS.md => packages/trilean/AGENTS.md (100%) rename CHANGELOG.md => packages/trilean/CHANGELOG.md (100%) rename CLAUDE.md => packages/trilean/CLAUDE.md (100%) create mode 100644 packages/trilean/LICENSE rename README.md => packages/trilean/README.md (100%) rename eslint.config.ts => packages/trilean/eslint.config.ts (100%) rename package.json => packages/trilean/package.json (100%) rename {scripts => packages/trilean/scripts}/generate-json-schema.ts (100%) rename {src => packages/trilean/src}/complex.test.ts (100%) rename {src => packages/trilean/src}/complex.ts (100%) rename {src => packages/trilean/src}/computed-value.test.ts (100%) rename {src => packages/trilean/src}/computed-value.ts (100%) rename {src => packages/trilean/src}/derived-aggregates.test.ts (100%) rename {src => packages/trilean/src}/derived-aggregates.ts (100%) rename {src => packages/trilean/src}/derived-connectives.test.ts (100%) rename {src => packages/trilean/src}/derived-connectives.ts (100%) rename {src => packages/trilean/src}/derived-patterns.test.ts (100%) rename {src => packages/trilean/src}/derived-patterns.ts (100%) rename {src => packages/trilean/src}/derived-values.test.ts (100%) rename {src => packages/trilean/src}/derived-values.ts (100%) rename {src => packages/trilean/src}/evaluation.ts (100%) rename {src => packages/trilean/src}/evaluator.indeterminacy.test.ts (100%) rename {src => packages/trilean/src}/evaluator.test.ts (100%) rename {src => packages/trilean/src}/evaluator.ts (100%) rename {src => packages/trilean/src}/functions.ts (100%) rename {src => packages/trilean/src}/golden-examples.test.ts (100%) rename {src => packages/trilean/src}/index.ts (100%) rename {src => packages/trilean/src}/json-value.ts (100%) rename {src => packages/trilean/src}/resolvers.ts (100%) rename {src => packages/trilean/src}/test-support/golden-example.ts (100%) rename {src => packages/trilean/src}/tree.test.ts (100%) rename {src => packages/trilean/src}/tree.ts (100%) rename {src => packages/trilean/src}/truth-tables.test.ts (100%) rename {test => packages/trilean/test}/integration/complex-arithmetic.test.ts (100%) rename {test => packages/trilean/test}/integration/composed-rules.test.ts (100%) rename {test => packages/trilean/test}/integration/delegate-handler.test.ts (100%) rename {test => packages/trilean/test}/integration/function-registry.test.ts (100%) rename {test => packages/trilean/test}/integration/json-schema-consistency.test.ts (100%) rename {test => packages/trilean/test}/integration/pattern-matching.test.ts (100%) rename {test => packages/trilean/test}/integration/schema-pipeline.test.ts (100%) rename {test => packages/trilean/test}/integration/tree-reference.test.ts (100%) rename {test => packages/trilean/test}/smoke.test.ts (100%) rename {test => packages/trilean/test}/workers/trilean.test.ts (100%) rename tsconfig.json => packages/trilean/tsconfig.json (100%) rename tsconfig.node.json => packages/trilean/tsconfig.node.json (100%) rename tsdown.config.ts => packages/trilean/tsdown.config.ts (100%) rename vitest.config.ts => packages/trilean/vitest.config.ts (100%) rename wrangler.jsonc => packages/trilean/wrangler.jsonc (100%) diff --git a/AGENTS.md b/packages/trilean/AGENTS.md similarity index 100% rename from AGENTS.md rename to packages/trilean/AGENTS.md diff --git a/CHANGELOG.md b/packages/trilean/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to packages/trilean/CHANGELOG.md diff --git a/CLAUDE.md b/packages/trilean/CLAUDE.md similarity index 100% rename from CLAUDE.md rename to packages/trilean/CLAUDE.md diff --git a/packages/trilean/LICENSE b/packages/trilean/LICENSE new file mode 100644 index 0000000..e6cf1d1 --- /dev/null +++ b/packages/trilean/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Joseph Mearman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/packages/trilean/README.md similarity index 100% rename from README.md rename to packages/trilean/README.md diff --git a/eslint.config.ts b/packages/trilean/eslint.config.ts similarity index 100% rename from eslint.config.ts rename to packages/trilean/eslint.config.ts diff --git a/package.json b/packages/trilean/package.json similarity index 100% rename from package.json rename to packages/trilean/package.json diff --git a/scripts/generate-json-schema.ts b/packages/trilean/scripts/generate-json-schema.ts similarity index 100% rename from scripts/generate-json-schema.ts rename to packages/trilean/scripts/generate-json-schema.ts diff --git a/src/complex.test.ts b/packages/trilean/src/complex.test.ts similarity index 100% rename from src/complex.test.ts rename to packages/trilean/src/complex.test.ts diff --git a/src/complex.ts b/packages/trilean/src/complex.ts similarity index 100% rename from src/complex.ts rename to packages/trilean/src/complex.ts diff --git a/src/computed-value.test.ts b/packages/trilean/src/computed-value.test.ts similarity index 100% rename from src/computed-value.test.ts rename to packages/trilean/src/computed-value.test.ts diff --git a/src/computed-value.ts b/packages/trilean/src/computed-value.ts similarity index 100% rename from src/computed-value.ts rename to packages/trilean/src/computed-value.ts diff --git a/src/derived-aggregates.test.ts b/packages/trilean/src/derived-aggregates.test.ts similarity index 100% rename from src/derived-aggregates.test.ts rename to packages/trilean/src/derived-aggregates.test.ts diff --git a/src/derived-aggregates.ts b/packages/trilean/src/derived-aggregates.ts similarity index 100% rename from src/derived-aggregates.ts rename to packages/trilean/src/derived-aggregates.ts diff --git a/src/derived-connectives.test.ts b/packages/trilean/src/derived-connectives.test.ts similarity index 100% rename from src/derived-connectives.test.ts rename to packages/trilean/src/derived-connectives.test.ts diff --git a/src/derived-connectives.ts b/packages/trilean/src/derived-connectives.ts similarity index 100% rename from src/derived-connectives.ts rename to packages/trilean/src/derived-connectives.ts diff --git a/src/derived-patterns.test.ts b/packages/trilean/src/derived-patterns.test.ts similarity index 100% rename from src/derived-patterns.test.ts rename to packages/trilean/src/derived-patterns.test.ts diff --git a/src/derived-patterns.ts b/packages/trilean/src/derived-patterns.ts similarity index 100% rename from src/derived-patterns.ts rename to packages/trilean/src/derived-patterns.ts diff --git a/src/derived-values.test.ts b/packages/trilean/src/derived-values.test.ts similarity index 100% rename from src/derived-values.test.ts rename to packages/trilean/src/derived-values.test.ts diff --git a/src/derived-values.ts b/packages/trilean/src/derived-values.ts similarity index 100% rename from src/derived-values.ts rename to packages/trilean/src/derived-values.ts diff --git a/src/evaluation.ts b/packages/trilean/src/evaluation.ts similarity index 100% rename from src/evaluation.ts rename to packages/trilean/src/evaluation.ts diff --git a/src/evaluator.indeterminacy.test.ts b/packages/trilean/src/evaluator.indeterminacy.test.ts similarity index 100% rename from src/evaluator.indeterminacy.test.ts rename to packages/trilean/src/evaluator.indeterminacy.test.ts diff --git a/src/evaluator.test.ts b/packages/trilean/src/evaluator.test.ts similarity index 100% rename from src/evaluator.test.ts rename to packages/trilean/src/evaluator.test.ts diff --git a/src/evaluator.ts b/packages/trilean/src/evaluator.ts similarity index 100% rename from src/evaluator.ts rename to packages/trilean/src/evaluator.ts diff --git a/src/functions.ts b/packages/trilean/src/functions.ts similarity index 100% rename from src/functions.ts rename to packages/trilean/src/functions.ts diff --git a/src/golden-examples.test.ts b/packages/trilean/src/golden-examples.test.ts similarity index 100% rename from src/golden-examples.test.ts rename to packages/trilean/src/golden-examples.test.ts diff --git a/src/index.ts b/packages/trilean/src/index.ts similarity index 100% rename from src/index.ts rename to packages/trilean/src/index.ts diff --git a/src/json-value.ts b/packages/trilean/src/json-value.ts similarity index 100% rename from src/json-value.ts rename to packages/trilean/src/json-value.ts diff --git a/src/resolvers.ts b/packages/trilean/src/resolvers.ts similarity index 100% rename from src/resolvers.ts rename to packages/trilean/src/resolvers.ts diff --git a/src/test-support/golden-example.ts b/packages/trilean/src/test-support/golden-example.ts similarity index 100% rename from src/test-support/golden-example.ts rename to packages/trilean/src/test-support/golden-example.ts diff --git a/src/tree.test.ts b/packages/trilean/src/tree.test.ts similarity index 100% rename from src/tree.test.ts rename to packages/trilean/src/tree.test.ts diff --git a/src/tree.ts b/packages/trilean/src/tree.ts similarity index 100% rename from src/tree.ts rename to packages/trilean/src/tree.ts diff --git a/src/truth-tables.test.ts b/packages/trilean/src/truth-tables.test.ts similarity index 100% rename from src/truth-tables.test.ts rename to packages/trilean/src/truth-tables.test.ts diff --git a/test/integration/complex-arithmetic.test.ts b/packages/trilean/test/integration/complex-arithmetic.test.ts similarity index 100% rename from test/integration/complex-arithmetic.test.ts rename to packages/trilean/test/integration/complex-arithmetic.test.ts diff --git a/test/integration/composed-rules.test.ts b/packages/trilean/test/integration/composed-rules.test.ts similarity index 100% rename from test/integration/composed-rules.test.ts rename to packages/trilean/test/integration/composed-rules.test.ts diff --git a/test/integration/delegate-handler.test.ts b/packages/trilean/test/integration/delegate-handler.test.ts similarity index 100% rename from test/integration/delegate-handler.test.ts rename to packages/trilean/test/integration/delegate-handler.test.ts diff --git a/test/integration/function-registry.test.ts b/packages/trilean/test/integration/function-registry.test.ts similarity index 100% rename from test/integration/function-registry.test.ts rename to packages/trilean/test/integration/function-registry.test.ts diff --git a/test/integration/json-schema-consistency.test.ts b/packages/trilean/test/integration/json-schema-consistency.test.ts similarity index 100% rename from test/integration/json-schema-consistency.test.ts rename to packages/trilean/test/integration/json-schema-consistency.test.ts diff --git a/test/integration/pattern-matching.test.ts b/packages/trilean/test/integration/pattern-matching.test.ts similarity index 100% rename from test/integration/pattern-matching.test.ts rename to packages/trilean/test/integration/pattern-matching.test.ts diff --git a/test/integration/schema-pipeline.test.ts b/packages/trilean/test/integration/schema-pipeline.test.ts similarity index 100% rename from test/integration/schema-pipeline.test.ts rename to packages/trilean/test/integration/schema-pipeline.test.ts diff --git a/test/integration/tree-reference.test.ts b/packages/trilean/test/integration/tree-reference.test.ts similarity index 100% rename from test/integration/tree-reference.test.ts rename to packages/trilean/test/integration/tree-reference.test.ts diff --git a/test/smoke.test.ts b/packages/trilean/test/smoke.test.ts similarity index 100% rename from test/smoke.test.ts rename to packages/trilean/test/smoke.test.ts diff --git a/test/workers/trilean.test.ts b/packages/trilean/test/workers/trilean.test.ts similarity index 100% rename from test/workers/trilean.test.ts rename to packages/trilean/test/workers/trilean.test.ts diff --git a/tsconfig.json b/packages/trilean/tsconfig.json similarity index 100% rename from tsconfig.json rename to packages/trilean/tsconfig.json diff --git a/tsconfig.node.json b/packages/trilean/tsconfig.node.json similarity index 100% rename from tsconfig.node.json rename to packages/trilean/tsconfig.node.json diff --git a/tsdown.config.ts b/packages/trilean/tsdown.config.ts similarity index 100% rename from tsdown.config.ts rename to packages/trilean/tsdown.config.ts diff --git a/vitest.config.ts b/packages/trilean/vitest.config.ts similarity index 100% rename from vitest.config.ts rename to packages/trilean/vitest.config.ts diff --git a/wrangler.jsonc b/packages/trilean/wrangler.jsonc similarity index 100% rename from wrangler.jsonc rename to packages/trilean/wrangler.jsonc From 073d9476cc3b452015cbec8e1e9e4dcb3f25cd81 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Thu, 3 Sep 2026 11:19:19 +0100 Subject: [PATCH 2/6] build(workspace): make the repository a pnpm workspace with a turbo pipeline pnpm-workspace.yaml gains the packages glob and linkWorkspacePackages, so a sibling added later is symlinked from the workspace rather than fetched from the registry, and keeps the install-time settings (build allowlist, release-age gate) that can only take effect at a workspace root. The root package.json is private and publishes nothing; it holds the task wrappers, the shared tooling devDependencies, and the git hooks. Tooling that is a property of the repository -- ESLint's root config, prettier, commitlint, lint-staged -- stays here, because only one copy of each can ever take effect; tooling that is genuinely per-package stays in the package. turbo.json grows the tasks each package's underscore-prefixed scripts already carried, ordered by ^_build so a sibling's dist/ exists before anything resolves an import through it. _typecheck, _lint and _test:smoke additionally depend on the package's own _build: the smoke suite and the JSON-schema generator both import ../dist/ by relative path. attw --pack becomes a task of its own rather than a bare CI step, depending on the build whose output it inspects. tsconfig.base.json holds the compiler options every package extends, so tightening a flag is one edit rather than one per package. The root tsconfig.json covers the root's own config files, which are otherwise in no program at all -- a file type-aware ESLint then refuses to lint. lint-staged runs eslint once per package directory, because ESLint's flat config resolves from the working directory: a root run over a staged file under packages/ would apply the root config, which ignores packages/**, and report the file as ignored rather than linting it. The root README describes the workspace and points at the package's own README for the library itself; AGENTS.md and CLAUDE.md symlink to it as they already do inside the package. --- .tool-versions | 1 + AGENTS.md | 1 + CLAUDE.md | 1 + CONTRIBUTING.md | 8 +- README.md | 53 +++ eslint.config.ts | 39 ++ lint-staged.config.ts | 46 +- package.json | 60 +++ packages/trilean/package.json | 25 +- packages/trilean/tsconfig.json | 15 +- pnpm-lock.yaml | 811 ++++++++++++++++++++++++--------- pnpm-workspace.yaml | 20 +- tsconfig.base.json | 19 + tsconfig.json | 10 + turbo.json | 98 +++- 15 files changed, 936 insertions(+), 271 deletions(-) create mode 100644 .tool-versions create mode 120000 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 README.md create mode 100644 eslint.config.ts create mode 100644 package.json create mode 100644 tsconfig.base.json create mode 100644 tsconfig.json diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..8febcc3 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 22 diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b122c0c..b19295f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,13 +8,13 @@ ## Before opening a PR -`pnpm run prepublishOnly` runs the same gate CI enforces: lint, typecheck, `unit` + `integration` tests, build, `smoke` test, `publint`, and `attw --pack`. It does not run the `workers` project — run `pnpm test:workers` separately for any change touching `src/` (evaluator, schemas, resolvers), since that's what actually proves the isomorphism constraint below rather than merely asserting it. +`pnpm --dir packages/trilean run prepublishOnly` runs the same gate CI enforces for that package: lint, typecheck, `unit` + `integration` tests, build, `smoke` test, `publint`, and `attw --pack`. It does not run the `workers` project — run `pnpm test:workers` separately for any change touching `src/` (evaluator, schemas, resolvers), since that's what actually proves the isomorphism constraint below rather than merely asserting it. ## Constraints a change must preserve -- **Isomorphism.** `src/**/*.ts` must never import a `node:*` module or reference `Buffer` — enforced by `eslint.config.ts`'s isomorphism guard and runtime-checked by the `workers` project running the evaluator inside a real Cloudflare Workers isolate (`test/workers/`, `wrangler.jsonc`). A change that needs a Node API belongs in a script or test file, never in `src/`. -- **[Design principles](README.md#design-principles).** No assumptions about consumer data, three evaluation outcomes never two, derived constructs built as compositions rather than new logic, one schema with mechanically derived artefacts. These hold across the whole design; an implementation change that would violate one needs the principle itself revisited first, not a quiet exception. -- **Generated files are never hand-edited.** `schemas/trilean.schema.json` is produced by `scripts/generate-json-schema.ts` as part of `pnpm build` and is gitignored — if the shipped schema looks wrong, fix the Zod schema or the generator script, not the output. +- **Isomorphism.** `packages/trilean/src/**/*.ts` must never import a `node:*` module or reference `Buffer` — enforced by that package's own `eslint.config.ts` isomorphism guard and runtime-checked by the `workers` project running the evaluator inside a real Cloudflare Workers isolate (`packages/trilean/test/workers/`, `packages/trilean/wrangler.jsonc`). A change that needs a Node API belongs in a script or test file, never in `src/`. +- **[Design principles](packages/trilean/README.md#design-principles).** No assumptions about consumer data, three evaluation outcomes never two, derived constructs built as compositions rather than new logic, one schema with mechanically derived artefacts. These hold across the whole design; an implementation change that would violate one needs the principle itself revisited first, not a quiet exception. +- **Generated files are never hand-edited.** `packages/trilean/schemas/trilean.schema.json` is produced by `packages/trilean/scripts/generate-json-schema.ts` as part of `pnpm build` and is gitignored — if the shipped schema looks wrong, fix the Zod schema or the generator script, not the output. ## Releases diff --git a/README.md b/README.md new file mode 100644 index 0000000..e88865d --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# trilean workspace + +[![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/trilean) [![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/trilean) [![Release](https://img.shields.io/github/v/release/ExaDev/trilean)](https://github.com/ExaDev/trilean/releases/latest) [![CI](https://img.shields.io/github/actions/workflow/status/ExaDev/trilean/ci.yml?branch=main)](https://github.com/ExaDev/trilean/actions) + +The pnpm workspace holding the trilean packages. This file describes the repository; for the library itself — what it is, how the evaluation model works, and the full API — see [`packages/trilean/README.md`](packages/trilean/README.md). + +## Packages + +| Package | Directory | Published as | +| --- | --- | --- | +| [trilean](packages/trilean/README.md) | `packages/trilean` | [`trilean`](https://www.npmjs.com/package/trilean) on npm, [`@exadev/trilean`](https://github.com/ExaDev/trilean/pkgs/npm/trilean) on GitHub Packages | + +Each package is versioned, released, and published independently of every other, from its own commit history. There is no lockstep version shared across the workspace. + +## Commands + +Every command runs from the repository root and fans out across the workspace through Turborepo, which caches each task against its declared inputs — an unchanged package replays its recorded result instead of re-running the work. + +```sh +pnpm install # install the workspace and set up the git hooks +pnpm build # tsdown build plus JSON-schema generation, per package +pnpm lint # eslint, zero warnings, per package and at the root +pnpm typecheck # tsc --noEmit plus attw --pack against each published package +pnpm test # the unit project +pnpm test:coverage # the unit project with coverage +pnpm test:integration # the integration project +pnpm test:smoke # rebuilds, then exercises the built dist/ through the exports map +pnpm test:workers # the workers project, inside the real workerd runtime +``` + +The `pnpm ` scripts are thin wrappers over `turbo run _`; the underscore-prefixed name is the one that carries the real command in each package's own manifest. Running a task inside a single package works the same way (`pnpm --dir packages/trilean test`), reaching the same cached pipeline. + +## Layout + +``` +. workspace root: tooling config, the task pipeline, release orchestration +├── packages/ +│ └── trilean/ the published library, with its own README, CHANGELOG, and configs +├── pnpm-workspace.yaml package globs and pnpm's install-time settings +├── turbo.json the task pipeline every package's tasks are ordered and cached by +├── tsconfig.base.json the compiler options every package extends +└── release-workspace.config.ts how each package is versioned, tagged, and published +``` + +Configuration that is genuinely per-package — its ESLint config's file scoping and import bans, its tsconfigs, its vitest projects, its wrangler config — lives in the package. Configuration that is a property of the repository — commit-message rules, the pre-commit hook, formatting, the release pipeline — lives at the root, because only one copy of each can ever take effect. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the hooks, the gate a change has to pass, and the constraints the library's design depends on. Security reports go through [SECURITY.md](SECURITY.md). + +## Licence + +MIT — see [LICENSE](LICENSE). diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..cff2115 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,39 @@ +import { exadevConfig } from "@exadev/eslint-config"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; +import globals from "globals"; + +// Lints the workspace root's own files -- its TypeScript tooling configs and its prose and data -- and nothing else. +// +// packages/** stays ignored here. The package keeps its own eslint.config.ts, because file scoping, tsconfig wiring, and the isomorphism import bans are genuinely per-package concerns; a root run that also walked the package would apply this program's tsconfig to source files it does not contain. +export default exadevConfig( + {}, + { + ignores: [ + "packages/**", + "node_modules", + ".turbo", + "coverage", + // Generated by semantic-release from commit messages; nothing about its formatting is a human decision, and the same goes for the lockfile. + "CHANGELOG.md", + "pnpm-lock.yaml", + ], + }, + { + languageOptions: { + parserOptions: { + project: ["./tsconfig.json"], + tsconfigRootDir: import.meta.dirname, + }, + globals: { ...globals.node }, + }, + }, + { + rules: { + "@typescript-eslint/consistent-type-imports": [ + "error", + { fixStyle: "inline-type-imports" }, + ], + }, + }, + eslintPluginPrettierRecommended, +); diff --git a/lint-staged.config.ts b/lint-staged.config.ts index f571a4f..5dd9b87 100644 --- a/lint-staged.config.ts +++ b/lint-staged.config.ts @@ -1,7 +1,51 @@ +import { relative, sep } from "node:path"; import type { Configuration } from "lint-staged"; +// ESLint's flat config resolves from the working directory, not from each linted file's own directory, so running `eslint --fix` at the workspace root over staged files in packages// would apply the root config -- which ignores packages/** -- and report every one of them as ignored rather than linting it. Each package owns its own eslint.config.ts, so the fix has to run once per package, in that package's directory. Files outside packages/ are linted by the root config in one final invocation. + +const PACKAGES_DIR = "packages"; + +// packages / / . A path with fewer segments than that names the packages directory itself or a package's own root entry, neither of which is a file inside a package. +const SEGMENTS_IN_SHALLOWEST_PACKAGE_FILE_PATH = 3; + +function packageDirectoryOf(repoRelativePath: string): string | undefined { + const segments = repoRelativePath.split(sep); + const [first, second] = segments; + if ( + first !== PACKAGES_DIR || + second === undefined || + segments.length < SEGMENTS_IN_SHALLOWEST_PACKAGE_FILE_PATH + ) { + return undefined; + } + return `${PACKAGES_DIR}${sep}${second}`; +} + +function groupByDirectory( + absolutePaths: readonly string[], +): ReadonlyMap { + const grouped = new Map(); + for (const absolutePath of absolutePaths) { + const repoRelativePath = relative(process.cwd(), absolutePath); + const directory = packageDirectoryOf(repoRelativePath) ?? "."; + const existing = grouped.get(directory); + if (existing === undefined) { + grouped.set(directory, [repoRelativePath]); + } else { + existing.push(repoRelativePath); + } + } + return grouped; +} + const config: Configuration = { - "*.ts": "eslint --fix --cache", + "*.ts": (files) => + [...groupByDirectory(files)].map(([directory, paths]) => { + const pathsRelativeToDirectory = paths.map( + (path) => `"${directory === "." ? path : relative(directory, path)}"`, + ); + return `pnpm --dir ${directory} exec eslint --fix ${pathsRelativeToDirectory.join(" ")}`; + }), }; export default config; diff --git a/package.json b/package.json new file mode 100644 index 0000000..12a3052 --- /dev/null +++ b/package.json @@ -0,0 +1,60 @@ +{ + "name": "trilean-monorepo", + "private": true, + "description": "Workspace root for the trilean ecosystem. Never published -- it exists to hold the workspace, the task pipeline, the shared tooling configuration, and the release orchestration.", + "type": "module", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/ExaDev/trilean.git" + }, + "homepage": "https://github.com/ExaDev/trilean#readme", + "bugs": { + "url": "https://github.com/ExaDev/trilean/issues" + }, + "engines": { + "node": ">=20" + }, + "packageManager": "pnpm@11.6.0", + "scripts": { + "build": "turbo run _build", + "lint": "turbo run _lint", + "_lint": "eslint . --fix --cache --max-warnings 0", + "typecheck": "turbo run _typecheck _typecheck:attw", + "_typecheck": "tsc -p tsconfig.json", + "test": "turbo run _test", + "test:coverage": "turbo run _test:coverage", + "test:integration": "turbo run _test:integration", + "test:smoke": "turbo run _test:smoke", + "test:workers": "turbo run _test:workers", + "prepush": "turbo run _prepush", + "release": "semantic-release-workspace release --config release-workspace.config.ts", + "prepare": "husky" + }, + "devDependencies": { + "@commitlint/cli": "^21.2.2", + "@commitlint/config-conventional": "^21.2.2", + "@eslint/js": "^10.0.1", + "@exadev/eslint-config": "^2.10.2", + "@exadev/semantic-release-workspace": "^1.2.1", + "@semantic-release/changelog": "^7.0.0", + "@semantic-release/commit-analyzer": "^13.0.1", + "@semantic-release/git": "^11.0.1", + "@semantic-release/github": "^12.0.9", + "@semantic-release/npm": "^13.1.5", + "@semantic-release/release-notes-generator": "^14.1.1", + "@types/node": "^26.4.0", + "conventional-changelog-conventionalcommits": "^10.4.0", + "eslint": "^10.9.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.6", + "globals": "^17.11.0", + "husky": "^9.1.7", + "lint-staged": "^17.4.1", + "prettier": "^3.9.6", + "semantic-release": "^25.0.9", + "turbo": "^2.10.12", + "typescript": "^6.0.3", + "typescript-eslint": "^8.68.0" + } +} diff --git a/packages/trilean/package.json b/packages/trilean/package.json index 71c4b2e..89a49cb 100644 --- a/packages/trilean/package.json +++ b/packages/trilean/package.json @@ -5,9 +5,10 @@ "type": "module", "repository": { "type": "git", - "url": "git+https://github.com/ExaDev/trilean.git" + "url": "git+https://github.com/ExaDev/trilean.git", + "directory": "packages/trilean" }, - "homepage": "https://github.com/ExaDev/trilean#readme", + "homepage": "https://github.com/ExaDev/trilean/tree/main/packages/trilean", "bugs": { "url": "https://github.com/ExaDev/trilean/issues" }, @@ -47,7 +48,6 @@ "node": ">=20" }, "license": "MIT", - "packageManager": "pnpm@11.6.0", "dependencies": { "zod": "^4.5.4" }, @@ -56,8 +56,9 @@ "_build": "tsdown && tsx scripts/generate-json-schema.ts", "lint": "turbo run _lint", "_lint": "eslint . --fix --cache --max-warnings 0", - "typecheck": "turbo run _typecheck", + "typecheck": "turbo run _typecheck _typecheck:attw", "_typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.node.json", + "_typecheck:attw": "attw --pack", "test": "turbo run _test", "_test": "vitest run --project unit", "test:coverage": "turbo run _test:coverage", @@ -70,9 +71,7 @@ "_test:workers": "vitest run --project workers", "prepush": "turbo run _prepush", "_prepush": "true", - "prepublishOnly": "pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run test:integration && pnpm run build && pnpm run test:smoke && publint && attw --pack", - "prepare": "husky", - "release": "semantic-release" + "prepublishOnly": "pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run test:integration && pnpm run build && pnpm run test:smoke && publint && attw --pack" }, "keywords": [ "json", @@ -87,16 +86,7 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.18.5", "@cloudflare/vitest-pool-workers": "^0.22.0", - "@commitlint/cli": "^21.2.2", - "@commitlint/config-conventional": "^21.2.2", - "@eslint/js": "^10.0.1", "@exadev/eslint-config": "^2.10.2", - "@semantic-release/changelog": "^7.0.0", - "@semantic-release/commit-analyzer": "^13.0.1", - "@semantic-release/git": "^11.0.1", - "@semantic-release/github": "^12.0.9", - "@semantic-release/npm": "^13.1.5", - "@semantic-release/release-notes-generator": "^14.1.1", "@types/node": "^26.4.0", "@vitest/coverage-v8": "^4.1.11", "canonicalize": "^4.0.0", @@ -104,11 +94,8 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.6", "globals": "^17.11.0", - "husky": "^9.1.7", - "lint-staged": "^17.4.1", "prettier": "^3.9.6", "publint": "^0.3.24", - "semantic-release": "^25.0.9", "tsdown": "^0.22.14", "tsx": "^4.23.13", "turbo": "^2.10.12", diff --git a/packages/trilean/tsconfig.json b/packages/trilean/tsconfig.json index 9200e51..13e017d 100644 --- a/packages/trilean/tsconfig.json +++ b/packages/trilean/tsconfig.json @@ -1,18 +1,7 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2024", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "noUncheckedIndexedAccess": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "verbatimModuleSyntax": true, - "isolatedModules": true, - "esModuleInterop": true, - "noEmit": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, + // No "node" types and a WebWorker lib: this is the isomorphism gate, so a Node-only global referenced from runtime src/ fails to compile here rather than at some consumer's runtime. "lib": ["ES2024", "WebWorker"], "types": [], "resolveJsonModule": true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ec04d5..924dbb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,23 +4,16 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@semantic-release/release-notes-generator>conventional-changelog-writer': 9.2.1 + importers: .: - dependencies: - zod: - specifier: ^4.5.4 - version: 4.5.4 devDependencies: - '@arethetypeswrong/cli': - specifier: ^0.18.5 - version: 0.18.5 - '@cloudflare/vitest-pool-workers': - specifier: ^0.22.0 - version: 0.22.0(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11) '@commitlint/cli': specifier: ^21.2.2 - version: 21.2.2(@types/node@26.4.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3) + version: 21.2.2(@types/node@26.4.1)(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2)(typescript@6.0.3) '@commitlint/config-conventional': specifier: ^21.2.2 version: 21.2.2 @@ -29,7 +22,10 @@ importers: version: 10.0.1(eslint@10.9.1(jiti@2.6.1)) '@exadev/eslint-config': specifier: ^2.10.2 - version: 2.10.2(eslint@10.9.1(jiti@2.6.1))(typescript-eslint@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(typescript@6.0.3) + version: 2.10.4(eslint@10.9.1(jiti@2.6.1))(typescript-eslint@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(typescript@6.0.3) + '@exadev/semantic-release-workspace': + specifier: ^1.2.1 + version: 1.2.1(@semantic-release/changelog@7.0.0(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/git@11.0.1(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/github@12.0.9(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/npm@13.1.5(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/release-notes-generator@14.1.1(semantic-release@25.0.9(typescript@6.0.3)))(semantic-release@25.0.9(typescript@6.0.3))(typescript@6.0.3) '@semantic-release/changelog': specifier: ^7.0.0 version: 7.0.0(semantic-release@25.0.9(typescript@6.0.3)) @@ -50,13 +46,10 @@ importers: version: 14.1.1(semantic-release@25.0.9(typescript@6.0.3)) '@types/node': specifier: ^26.4.0 - version: 26.4.0 - '@vitest/coverage-v8': - specifier: ^4.1.11 - version: 4.1.11(vitest@4.1.11) - canonicalize: - specifier: ^4.0.0 - version: 4.0.0 + version: 26.4.1 + conventional-changelog-conventionalcommits: + specifier: ^10.4.0 + version: 10.4.0 eslint: specifier: ^10.9.1 version: 10.9.1(jiti@2.6.1) @@ -68,7 +61,7 @@ importers: version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.9.1(jiti@2.6.1)))(eslint@10.9.1(jiti@2.6.1))(prettier@3.9.6) globals: specifier: ^17.11.0 - version: 17.11.0 + version: 17.12.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -78,12 +71,61 @@ importers: prettier: specifier: ^3.9.6 version: 3.9.6 - publint: - specifier: ^0.3.24 - version: 0.3.24 semantic-release: specifier: ^25.0.9 version: 25.0.9(typescript@6.0.3) + turbo: + specifier: ^2.10.12 + version: 2.10.12 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: ^8.68.0 + version: 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + + packages/trilean: + dependencies: + zod: + specifier: ^4.5.4 + version: 4.5.4 + devDependencies: + '@arethetypeswrong/cli': + specifier: ^0.18.5 + version: 0.18.5 + '@cloudflare/vitest-pool-workers': + specifier: ^0.22.0 + version: 0.22.0(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11) + '@exadev/eslint-config': + specifier: ^2.10.2 + version: 2.10.4(eslint@10.9.1(jiti@2.6.1))(typescript-eslint@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(typescript@6.0.3) + '@types/node': + specifier: ^26.4.0 + version: 26.4.1 + '@vitest/coverage-v8': + specifier: ^4.1.11 + version: 4.1.11(vitest@4.1.11) + canonicalize: + specifier: ^4.0.0 + version: 4.0.0 + eslint: + specifier: ^10.9.1 + version: 10.9.1(jiti@2.6.1) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@10.9.1(jiti@2.6.1)) + eslint-plugin-prettier: + specifier: ^5.5.6 + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.9.1(jiti@2.6.1)))(eslint@10.9.1(jiti@2.6.1))(prettier@3.9.6) + globals: + specifier: ^17.11.0 + version: 17.12.0 + prettier: + specifier: ^3.9.6 + version: 3.9.6 + publint: + specifier: ^0.3.24 + version: 0.3.24 tsdown: specifier: ^0.22.14 version: 0.22.14(@arethetypeswrong/core@0.18.5)(publint@0.3.24)(tsx@4.23.13)(typescript@6.0.3) @@ -98,10 +140,10 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.68.0 - version: 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + version: 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) + version: 4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) packages: @@ -309,156 +351,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.28.1': resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.28.1': resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.28.1': resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.28.1': resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.28.1': resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.28.1': resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.28.1': resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.28.1': resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.28.1': resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.28.1': resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.28.1': resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.28.1': resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.28.1': resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.28.1': resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.28.1': resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.28.1': resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.28.1': resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.28.1': resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.28.1': resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.28.1': resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.28.1': resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.28.1': resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.10.1': resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -498,8 +696,8 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@exadev/eslint-config@2.10.2': - resolution: {integrity: sha512-UAGK9aPkpd2mC3x8XQbJcnmdhYhOdH2TQGzVD88QZRMKm9FlASXWcnJpwox8miV6RVVmzOaKhxZJCj8QGGDM6w==} + '@exadev/eslint-config@2.10.4': + resolution: {integrity: sha512-E+YLfg3RbQ5DVfWMiRdpJo9sjYfwWzuRef6JidWbvoQxHMkEp7IWEPedfIvBvBPQPjK8hDVUMdnKPojdhxEpYA==} engines: {node: '>=20'} peerDependencies: '@next/eslint-plugin-next': ^16.3.2 @@ -519,6 +717,19 @@ packages: eslint-plugin-react-hooks: optional: true + '@exadev/semantic-release-workspace@1.2.1': + resolution: {integrity: sha512-qMyGpKS4Ksof37MKZ/Zso/jIGyMiUOR98KtSg9JBkc//S8KiVgrBmkL/E0/WRWbQlk2OIJfCbV0/UYtc8fd1eg==} + engines: {node: '>=20'} + hasBin: true + peerDependencies: + '@semantic-release/changelog': ^7.0.0 + '@semantic-release/commit-analyzer': ^13.0.1 + '@semantic-release/git': ^11.0.1 + '@semantic-release/github': ^12.0.9 + '@semantic-release/npm': ^13.1.5 + '@semantic-release/release-notes-generator': ^14.1.1 + semantic-release: ^25.0.9 + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -777,8 +988,8 @@ packages: '@octokit/types@18.0.0': resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==} - '@oxc-project/types@0.147.0': - resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} + '@oxc-project/types@0.148.0': + resolution: {integrity: sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==} '@pkgr/core@0.3.6': resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} @@ -812,98 +1023,98 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - '@rolldown/binding-android-arm-eabi@1.2.6': - resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} + '@rolldown/binding-android-arm-eabi@1.2.7': + resolution: {integrity: sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.6': - resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==} + '@rolldown/binding-android-arm64@1.2.7': + resolution: {integrity: sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.6': - resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==} + '@rolldown/binding-darwin-arm64@1.2.7': + resolution: {integrity: sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.6': - resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==} + '@rolldown/binding-darwin-x64@1.2.7': + resolution: {integrity: sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.6': - resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==} + '@rolldown/binding-freebsd-x64@1.2.7': + resolution: {integrity: sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.6': - resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.7': + resolution: {integrity: sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.6': - resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==} + '@rolldown/binding-linux-arm64-gnu@1.2.7': + resolution: {integrity: sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.6': - resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==} + '@rolldown/binding-linux-arm64-musl@1.2.7': + resolution: {integrity: sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.6': - resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==} + '@rolldown/binding-linux-ppc64-gnu@1.2.7': + resolution: {integrity: sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.6': - resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==} + '@rolldown/binding-linux-s390x-gnu@1.2.7': + resolution: {integrity: sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.6': - resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==} + '@rolldown/binding-linux-x64-gnu@1.2.7': + resolution: {integrity: sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.6': - resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==} + '@rolldown/binding-linux-x64-musl@1.2.7': + resolution: {integrity: sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.6': - resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==} + '@rolldown/binding-openharmony-arm64@1.2.7': + resolution: {integrity: sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.6': - resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==} + '@rolldown/binding-win32-arm64-msvc@1.2.7': + resolution: {integrity: sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.6': - resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==} + '@rolldown/binding-win32-x64-msvc@1.2.7': + resolution: {integrity: sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1029,69 +1240,69 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@26.4.0': - resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} + '@types/node@26.4.1': + resolution: {integrity: sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@typescript-eslint/eslint-plugin@8.68.0': - resolution: {integrity: sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==} + '@typescript-eslint/eslint-plugin@8.69.0': + resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.68.0 + '@typescript-eslint/parser': ^8.69.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.68.0': - resolution: {integrity: sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==} + '@typescript-eslint/parser@8.69.0': + resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.68.0': - resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} + '@typescript-eslint/project-service@8.69.0': + resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.68.0': - resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} + '@typescript-eslint/scope-manager@8.69.0': + resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.68.0': - resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} + '@typescript-eslint/tsconfig-utils@8.69.0': + resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.68.0': - resolution: {integrity: sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==} + '@typescript-eslint/type-utils@8.69.0': + resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.68.0': - resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} + '@typescript-eslint/types@8.69.0': + resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.68.0': - resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} + '@typescript-eslint/typescript-estree@8.69.0': + resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.68.0': - resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} + '@typescript-eslint/utils@8.69.0': + resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.68.0': - resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} + '@typescript-eslint/visitor-keys@8.69.0': + resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@4.1.11': @@ -1325,8 +1536,8 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - argue-cli@3.1.0: - resolution: {integrity: sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==} + argue-cli@3.2.0: + resolution: {integrity: sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==} engines: {node: '>=22'} argv-formatter@1.0.0: @@ -1439,6 +1650,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} @@ -1466,10 +1681,19 @@ packages: engines: {node: '>=18'} hasBin: true + conventional-changelog-writer@9.2.1: + resolution: {integrity: sha512-StlYSmW3wLedRaqohJMpP3YuWiAqtgD0/cpsai9frdDkXv7rxj0hRbpJrubPYvJxJsjplONsOobEQnPuS9UgCg==} + engines: {node: '>=22'} + hasBin: true + conventional-commits-filter@5.0.0: resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} engines: {node: '>=18'} + conventional-commits-filter@6.0.1: + resolution: {integrity: sha512-cs+LadpH7Kpw0M3k8wurk+sOVVDAENA0iK4OBOrkL94j5lEVYRJ4j3zd2bhY9qgzyrPqthdcYT3axzRN7AliMg==} + engines: {node: '>=22'} + conventional-commits-parser@6.4.0: resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} engines: {node: '>=18'} @@ -1604,6 +1828,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1713,8 +1942,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.6: - resolution: {integrity: sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==} + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -1815,8 +2044,8 @@ packages: resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} engines: {node: '>=20'} - globals@17.11.0: - resolution: {integrity: sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==} + globals@17.12.0: + resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} engines: {node: '>=18'} graceful-fs@4.2.10: @@ -1884,8 +2113,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.7: - resolution: {integrity: sha512-dML0wP6oak21rsNYCJpJB6O1BJIEwNpGrTw0URPfAk4hm0e3pRfCtzkfB6olBcXcVlU2rouCyz7lCyRB0OMVCA==} + ignore@7.0.8: + resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==} engines: {node: '>= 4'} import-fresh@3.3.1: @@ -2475,8 +2704,8 @@ packages: resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} engines: {node: '>=4'} - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + postcss@8.5.27: + resolution: {integrity: sha512-79Iho8QeYyooJ8e9lCRyTVlyTAkS/kXBYKff6TMzS3kEWGQ8Ds5UEtXpGrSUDLUWok6QTvxeYy0GO8fopHnaSA==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2588,8 +2817,8 @@ packages: vue-tsc: optional: true - rolldown@1.2.6: - resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} + rolldown@1.2.7: + resolution: {integrity: sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -2772,8 +3001,8 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -2872,8 +3101,8 @@ packages: resolution: {integrity: sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==} engines: {node: '>=20'} - typescript-eslint@8.68.0: - resolution: {integrity: sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==} + typescript-eslint@8.69.0: + resolution: {integrity: sha512-B3MltX0VqjUBNEe3b3sSuiRbfa6XrfHFtBiPamjT5AsW/dfq+y+bc0wyuS9DxAS1LyzCxRp2+rxzpLUvqM2BvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2955,6 +3184,10 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + validate-npm-package-name@8.0.0: + resolution: {integrity: sha512-SCv6OOV6Xj2/3cXy3dGmADluJTNcL3o7hZAglNPTe+WYuEuvxgJzxPrSDLZhF+CwyQOubqgecjMmTJGMVLWjYQ==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + verkit@0.3.2: resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} engines: {node: '>=18.12.0'} @@ -3240,7 +3473,7 @@ snapshots: cjs-module-lexer: 1.2.3 esbuild: 0.28.1 miniflare: 5.20260815.0-alpha - vitest: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) + vitest: 4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) wrangler: 4.124.0 zod: 4.4.3 transitivePeerDependencies: @@ -3266,15 +3499,15 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@21.2.2(@types/node@26.4.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + '@commitlint/cli@21.2.2(@types/node@26.4.1)(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': dependencies: '@commitlint/config-conventional': 21.2.2 '@commitlint/format': 21.2.2 '@commitlint/lint': 21.2.2 - '@commitlint/load': 21.2.2(@types/node@26.4.0)(typescript@6.0.3) - '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.2) + '@commitlint/load': 21.2.2(@types/node@26.4.1)(typescript@6.0.3) + '@commitlint/read': 21.2.1(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2) '@commitlint/types': 21.2.0 - tinyexec: 1.3.0 + tinyexec: 1.3.1 yargs: 18.1.0 transitivePeerDependencies: - '@types/node' @@ -3316,14 +3549,14 @@ snapshots: '@commitlint/rules': 21.2.2 '@commitlint/types': 21.2.0 - '@commitlint/load@21.2.2(@types/node@26.4.0)(typescript@6.0.3)': + '@commitlint/load@21.2.2(@types/node@26.4.1)(typescript@6.0.3)': dependencies: '@commitlint/config-validator': 21.2.0 '@commitlint/execute-rule': 21.0.1 '@commitlint/resolve-extends': 21.2.2 '@commitlint/types': 21.2.0 cosmiconfig: 9.0.2(typescript@6.0.3) - cosmiconfig-typescript-loader: 6.3.0(@types/node@26.4.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.4.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) es-toolkit: 1.52.0 is-plain-obj: 4.1.0 picocolors: 1.1.1 @@ -3339,12 +3572,12 @@ snapshots: conventional-changelog-angular: 9.4.0 conventional-commits-parser: 7.1.2 - '@commitlint/read@21.2.1(conventional-commits-parser@7.1.2)': + '@commitlint/read@21.2.1(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2)': dependencies: '@commitlint/top-level': 21.2.0 '@commitlint/types': 21.2.0 - '@conventional-changelog/git-client': 3.1.2(conventional-commits-parser@7.1.2) - tinyexec: 1.3.0 + '@conventional-changelog/git-client': 3.1.2(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2) + tinyexec: 1.3.1 transitivePeerDependencies: - conventional-commits-filter - conventional-commits-parser @@ -3375,12 +3608,13 @@ snapshots: conventional-commits-parser: 7.1.2 picocolors: 1.1.1 - '@conventional-changelog/git-client@3.1.2(conventional-commits-parser@7.1.2)': + '@conventional-changelog/git-client@3.1.2(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2)': dependencies: '@simple-libs/child-process-utils': 2.0.0 '@simple-libs/stream-utils': 2.0.0 semver: 7.8.5 optionalDependencies: + conventional-commits-filter: 6.0.1 conventional-commits-parser: 7.1.2 '@conventional-changelog/template@1.4.0': {} @@ -3397,81 +3631,159 @@ snapshots: '@esbuild/aix-ppc64@0.28.1': optional: true + '@esbuild/aix-ppc64@0.28.2': + optional: true + '@esbuild/android-arm64@0.28.1': optional: true + '@esbuild/android-arm64@0.28.2': + optional: true + '@esbuild/android-arm@0.28.1': optional: true + '@esbuild/android-arm@0.28.2': + optional: true + '@esbuild/android-x64@0.28.1': optional: true + '@esbuild/android-x64@0.28.2': + optional: true + '@esbuild/darwin-arm64@0.28.1': optional: true + '@esbuild/darwin-arm64@0.28.2': + optional: true + '@esbuild/darwin-x64@0.28.1': optional: true + '@esbuild/darwin-x64@0.28.2': + optional: true + '@esbuild/freebsd-arm64@0.28.1': optional: true + '@esbuild/freebsd-arm64@0.28.2': + optional: true + '@esbuild/freebsd-x64@0.28.1': optional: true + '@esbuild/freebsd-x64@0.28.2': + optional: true + '@esbuild/linux-arm64@0.28.1': optional: true + '@esbuild/linux-arm64@0.28.2': + optional: true + '@esbuild/linux-arm@0.28.1': optional: true + '@esbuild/linux-arm@0.28.2': + optional: true + '@esbuild/linux-ia32@0.28.1': optional: true + '@esbuild/linux-ia32@0.28.2': + optional: true + '@esbuild/linux-loong64@0.28.1': optional: true + '@esbuild/linux-loong64@0.28.2': + optional: true + '@esbuild/linux-mips64el@0.28.1': optional: true + '@esbuild/linux-mips64el@0.28.2': + optional: true + '@esbuild/linux-ppc64@0.28.1': optional: true + '@esbuild/linux-ppc64@0.28.2': + optional: true + '@esbuild/linux-riscv64@0.28.1': optional: true + '@esbuild/linux-riscv64@0.28.2': + optional: true + '@esbuild/linux-s390x@0.28.1': optional: true + '@esbuild/linux-s390x@0.28.2': + optional: true + '@esbuild/linux-x64@0.28.1': optional: true + '@esbuild/linux-x64@0.28.2': + optional: true + '@esbuild/netbsd-arm64@0.28.1': optional: true + '@esbuild/netbsd-arm64@0.28.2': + optional: true + '@esbuild/netbsd-x64@0.28.1': optional: true + '@esbuild/netbsd-x64@0.28.2': + optional: true + '@esbuild/openbsd-arm64@0.28.1': optional: true + '@esbuild/openbsd-arm64@0.28.2': + optional: true + '@esbuild/openbsd-x64@0.28.1': optional: true + '@esbuild/openbsd-x64@0.28.2': + optional: true + '@esbuild/openharmony-arm64@0.28.1': optional: true + '@esbuild/openharmony-arm64@0.28.2': + optional: true + '@esbuild/sunos-x64@0.28.1': optional: true + '@esbuild/sunos-x64@0.28.2': + optional: true + '@esbuild/win32-arm64@0.28.1': optional: true + '@esbuild/win32-arm64@0.28.2': + optional: true + '@esbuild/win32-ia32@0.28.1': optional: true + '@esbuild/win32-ia32@0.28.2': + optional: true + '@esbuild/win32-x64@0.28.1': optional: true + '@esbuild/win32-x64@0.28.2': + optional: true + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.1(jiti@2.6.1))': dependencies: eslint: 10.9.1(jiti@2.6.1) @@ -3506,17 +3818,34 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@exadev/eslint-config@2.10.2(eslint@10.9.1(jiti@2.6.1))(typescript-eslint@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(typescript@6.0.3)': + '@exadev/eslint-config@2.10.4(eslint@10.9.1(jiti@2.6.1))(typescript-eslint@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(typescript@6.0.3)': dependencies: '@eslint/js': 10.0.1(eslint@10.9.1(jiti@2.6.1)) - '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) eslint: 10.9.1(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 - typescript-eslint: 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + typescript-eslint: 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) transitivePeerDependencies: - supports-color + '@exadev/semantic-release-workspace@1.2.1(@semantic-release/changelog@7.0.0(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/git@11.0.1(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/github@12.0.9(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/npm@13.1.5(semantic-release@25.0.9(typescript@6.0.3)))(@semantic-release/release-notes-generator@14.1.1(semantic-release@25.0.9(typescript@6.0.3)))(semantic-release@25.0.9(typescript@6.0.3))(typescript@6.0.3)': + dependencies: + '@semantic-release/changelog': 7.0.0(semantic-release@25.0.9(typescript@6.0.3)) + '@semantic-release/commit-analyzer': 13.0.1(semantic-release@25.0.9(typescript@6.0.3)) + '@semantic-release/git': 11.0.1(semantic-release@25.0.9(typescript@6.0.3)) + '@semantic-release/github': 12.0.9(semantic-release@25.0.9(typescript@6.0.3)) + '@semantic-release/npm': 13.1.5(semantic-release@25.0.9(typescript@6.0.3)) + '@semantic-release/release-notes-generator': 14.1.1(semantic-release@25.0.9(typescript@6.0.3)) + commander: 15.0.0 + cosmiconfig: 9.0.2(typescript@6.0.3) + semantic-release: 25.0.9(typescript@6.0.3) + tinyglobby: 0.2.17 + validate-npm-package-name: 8.0.0 + yaml: 2.9.0 + transitivePeerDependencies: + - typescript + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -3729,7 +4058,7 @@ snapshots: dependencies: '@octokit/openapi-types': 29.0.1 - '@oxc-project/types@0.147.0': {} + '@oxc-project/types@0.148.0': {} '@pkgr/core@0.3.6': {} @@ -3759,55 +4088,55 @@ snapshots: '@publint/pack@0.1.7': dependencies: - tinyexec: 1.3.0 + tinyexec: 1.3.1 '@quansync/fs@1.0.0': dependencies: quansync: 1.0.0 - '@rolldown/binding-android-arm-eabi@1.2.6': + '@rolldown/binding-android-arm-eabi@1.2.7': optional: true - '@rolldown/binding-android-arm64@1.2.6': + '@rolldown/binding-android-arm64@1.2.7': optional: true - '@rolldown/binding-darwin-arm64@1.2.6': + '@rolldown/binding-darwin-arm64@1.2.7': optional: true - '@rolldown/binding-darwin-x64@1.2.6': + '@rolldown/binding-darwin-x64@1.2.7': optional: true - '@rolldown/binding-freebsd-x64@1.2.6': + '@rolldown/binding-freebsd-x64@1.2.7': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.6': + '@rolldown/binding-linux-arm-gnueabihf@1.2.7': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.6': + '@rolldown/binding-linux-arm64-gnu@1.2.7': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.6': + '@rolldown/binding-linux-arm64-musl@1.2.7': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.6': + '@rolldown/binding-linux-ppc64-gnu@1.2.7': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.6': + '@rolldown/binding-linux-s390x-gnu@1.2.7': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.6': + '@rolldown/binding-linux-x64-gnu@1.2.7': optional: true - '@rolldown/binding-linux-x64-musl@1.2.6': + '@rolldown/binding-linux-x64-musl@1.2.7': optional: true - '@rolldown/binding-openharmony-arm64@1.2.6': + '@rolldown/binding-openharmony-arm64@1.2.7': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.6': + '@rolldown/binding-win32-arm64-msvc@1.2.7': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.6': + '@rolldown/binding-win32-x64-msvc@1.2.7': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -3897,7 +4226,7 @@ snapshots: '@semantic-release/release-notes-generator@14.1.1(semantic-release@25.0.9(typescript@6.0.3))': dependencies: conventional-changelog-angular: 8.3.1 - conventional-changelog-writer: 8.4.0 + conventional-changelog-writer: 9.2.1 conventional-commits-filter: 5.0.0 conventional-commits-parser: 6.4.0 debug: 4.4.3 @@ -3957,63 +4286,63 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@26.4.0': + '@types/node@26.4.1': dependencies: undici-types: 8.3.0 '@types/normalize-package-data@2.4.4': {} - '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/type-utils': 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/parser': 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/type-utils': 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.69.0 eslint: 10.9.1(jiti@2.6.1) - ignore: 7.0.7 + ignore: 7.0.8 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3 eslint: 10.9.1(jiti@2.6.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.68.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.69.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.68.0': + '@typescript-eslint/scope-manager@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 - '@typescript-eslint/tsconfig-utils@8.68.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.69.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) debug: 4.4.3 eslint: 10.9.1(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@6.0.3) @@ -4021,14 +4350,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.68.0': {} + '@typescript-eslint/types@8.69.0': {} - '@typescript-eslint/typescript-estree@8.68.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.69.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.68.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/project-service': 8.69.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3 minimatch: 10.2.6 semver: 7.8.5 @@ -4038,20 +4367,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/utils@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) eslint: 10.9.1(jiti@2.6.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.68.0': + '@typescript-eslint/visitor-keys@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/types': 8.69.0 eslint-visitor-keys: 5.0.1 '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': @@ -4066,7 +4395,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) + vitest: 4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) '@vitest/expect@4.1.11': dependencies: @@ -4077,13 +4406,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0))': + '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0) + vite: 8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0) '@vitest/pretty-format@4.1.11': dependencies: @@ -4206,7 +4535,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.6 + fast-uri: 3.1.7 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4234,7 +4563,7 @@ snapshots: argparse@2.0.1: {} - argue-cli@3.1.0: {} + argue-cli@3.2.0: {} argv-formatter@1.0.0: {} @@ -4336,6 +4665,8 @@ snapshots: commander@10.0.1: {} + commander@15.0.0: {} + compare-func@2.0.0: dependencies: array-ify: 1.0.0 @@ -4368,8 +4699,18 @@ snapshots: meow: 13.2.0 semver: 7.8.5 + conventional-changelog-writer@9.2.1: + dependencies: + '@conventional-changelog/template': 1.4.0 + '@simple-libs/stream-utils': 2.0.0 + argue-cli: 3.2.0 + conventional-commits-filter: 6.0.1 + semver: 7.8.5 + conventional-commits-filter@5.0.0: {} + conventional-commits-filter@6.0.1: {} + conventional-commits-parser@6.4.0: dependencies: '@simple-libs/stream-utils': 1.2.0 @@ -4378,7 +4719,7 @@ snapshots: conventional-commits-parser@7.1.2: dependencies: '@simple-libs/stream-utils': 2.0.0 - argue-cli: 3.1.0 + argue-cli: 3.2.0 convert-hrtime@5.0.0: {} @@ -4388,9 +4729,9 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.3.0(@types/node@26.4.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.4.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): dependencies: - '@types/node': 26.4.0 + '@types/node': 26.4.1 cosmiconfig: 9.0.2(typescript@6.0.3) jiti: 2.6.1 typescript: 6.0.3 @@ -4496,6 +4837,35 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -4639,7 +5009,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.6: {} + fast-uri@3.1.7: {} fdir@6.5.0(picomatch@4.0.7): optionalDependencies: @@ -4731,7 +5101,7 @@ snapshots: dependencies: ini: 6.0.0 - globals@17.11.0: {} + globals@17.12.0: {} graceful-fs@4.2.10: {} @@ -4792,7 +5162,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.7: {} + ignore@7.0.8: {} import-fresh@3.3.1: dependencies: @@ -4967,7 +5337,7 @@ snapshots: dependencies: picomatch: 4.0.7 string-argv: 0.3.2 - tinyexec: 1.3.0 + tinyexec: 1.3.1 optionalDependencies: yaml: 2.9.0 @@ -5242,7 +5612,7 @@ snapshots: find-up: 2.1.0 load-json-file: 4.0.0 - postcss@8.5.26: + postcss@8.5.27: dependencies: nanoid: 3.3.18 picocolors: 1.1.1 @@ -5336,12 +5706,12 @@ snapshots: resolve-pkg-maps@1.0.0: {} - rolldown-plugin-dts@0.27.14(rolldown@1.2.6)(typescript@6.0.3): + rolldown-plugin-dts@0.27.14(rolldown@1.2.7)(typescript@6.0.3): dependencies: dts-resolver: 3.0.0 get-tsconfig: 5.0.0-beta.5 obug: 2.1.4 - rolldown: 1.2.6 + rolldown: 1.2.7 yuku-ast: 0.8.7 yuku-codegen: 0.8.7 yuku-parser: 0.8.7 @@ -5350,26 +5720,26 @@ snapshots: transitivePeerDependencies: - oxc-resolver - rolldown@1.2.6: + rolldown@1.2.7: dependencies: - '@oxc-project/types': 0.147.0 + '@oxc-project/types': 0.148.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.6 - '@rolldown/binding-android-arm64': 1.2.6 - '@rolldown/binding-darwin-arm64': 1.2.6 - '@rolldown/binding-darwin-x64': 1.2.6 - '@rolldown/binding-freebsd-x64': 1.2.6 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.6 - '@rolldown/binding-linux-arm64-gnu': 1.2.6 - '@rolldown/binding-linux-arm64-musl': 1.2.6 - '@rolldown/binding-linux-ppc64-gnu': 1.2.6 - '@rolldown/binding-linux-s390x-gnu': 1.2.6 - '@rolldown/binding-linux-x64-gnu': 1.2.6 - '@rolldown/binding-linux-x64-musl': 1.2.6 - '@rolldown/binding-openharmony-arm64': 1.2.6 - '@rolldown/binding-win32-arm64-msvc': 1.2.6 - '@rolldown/binding-win32-x64-msvc': 1.2.6 + '@rolldown/binding-android-arm-eabi': 1.2.7 + '@rolldown/binding-android-arm64': 1.2.7 + '@rolldown/binding-darwin-arm64': 1.2.7 + '@rolldown/binding-darwin-x64': 1.2.7 + '@rolldown/binding-freebsd-x64': 1.2.7 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.7 + '@rolldown/binding-linux-arm64-gnu': 1.2.7 + '@rolldown/binding-linux-arm64-musl': 1.2.7 + '@rolldown/binding-linux-ppc64-gnu': 1.2.7 + '@rolldown/binding-linux-s390x-gnu': 1.2.7 + '@rolldown/binding-linux-x64-gnu': 1.2.7 + '@rolldown/binding-linux-x64-musl': 1.2.7 + '@rolldown/binding-openharmony-arm64': 1.2.7 + '@rolldown/binding-win32-arm64-msvc': 1.2.7 + '@rolldown/binding-win32-x64-msvc': 1.2.7 sade@1.8.1: dependencies: @@ -5595,7 +5965,7 @@ snapshots: tinybench@2.9.0: {} - tinyexec@1.3.0: {} + tinyexec@1.3.1: {} tinyglobby@0.2.17: dependencies: @@ -5626,9 +5996,9 @@ snapshots: import-without-cache: 0.4.0 obug: 2.1.4 picomatch: 4.0.7 - rolldown: 1.2.6 - rolldown-plugin-dts: 0.27.14(rolldown@1.2.6)(typescript@6.0.3) - tinyexec: 1.3.0 + rolldown: 1.2.7 + rolldown-plugin-dts: 0.27.14(rolldown@1.2.7)(typescript@6.0.3) + tinyexec: 1.3.1 tinyglobby: 0.2.17 tree-kill: 1.2.2 unconfig-core: 7.5.0 @@ -5649,7 +6019,7 @@ snapshots: tsx@4.23.13: dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 @@ -5678,12 +6048,12 @@ snapshots: dependencies: tagged-tag: 1.0.0 - typescript-eslint@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3): + typescript-eslint@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/parser': 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.9.1(jiti@2.6.1))(typescript@6.0.3) eslint: 10.9.1(jiti@2.6.1) typescript: 6.0.3 transitivePeerDependencies: @@ -5742,27 +6112,29 @@ snapshots: validate-npm-package-name@5.0.1: {} + validate-npm-package-name@8.0.0: {} + verkit@0.3.2: {} - vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0): + vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 - postcss: 8.5.26 - rolldown: 1.2.6 + postcss: 8.5.27 + rolldown: 1.2.7 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.4.0 - esbuild: 0.28.1 + '@types/node': 26.4.1 + esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.6.1 tsx: 4.23.13 yaml: 2.9.0 - vitest@4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)): + vitest@4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -5776,13 +6148,13 @@ snapshots: picomatch: 4.0.7 std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 1.3.0 + tinyexec: 1.3.1 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0) + vite: 8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.13)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 26.4.0 + '@types/node': 26.4.1 '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) transitivePeerDependencies: - msw @@ -5846,8 +6218,7 @@ snapshots: y18n@5.0.8: {} - yaml@2.9.0: - optional: true + yaml@2.9.0: {} yargs-parser@20.2.9: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6f207b2..4152b63 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,24 @@ +packages: + - "packages/*" + +# pnpm 11 defaults this to false, which means a dependency on a sibling package resolves from the npm registry even when the workspace holds a version satisfying the range. Setting it true links a sibling whenever the declared range is satisfied by the workspace version, and falls back to the registry when it is not. There is one package here today, so nothing exercises it yet -- it is set now so the first sibling added is linked rather than silently downloaded. +linkWorkspacePackages: true + +# workerd (the Cloudflare Workers runtime @cloudflare/vitest-pool-workers drives the workers test suite through) and esbuild (its bundler) both ship native binaries installed via a postinstall script -- pnpm 11 ignores those by default, so allow them explicitly. allowBuilds: esbuild: true workerd: true + +# 60 minutes. pnpm reads workspace-level settings only from the workspace root, so this file is the one place they can take effect at all. minimumReleaseAge: 60 -# @exadev/eslint-config is an org-owned devDependency, not a third-party supply-chain -# risk -- exempted so a just-published fix (verified and published by us) doesn't have to wait out the cooldown meant for external packages. + +# Bare package names (no @version), per pnpm's own documented syntax (https://pnpm.io/settings#minimumreleaseageexclude). Both are org-owned packages published only by their own semantic-release CI with npm OIDC trusted publishing, so a version-agnostic exemption is a deliberate, low-risk choice rather than an entry needing an edit on every release. minimumReleaseAgeExclude: - "@exadev/eslint-config" + - "@exadev/semantic-release-workspace" + +overrides: + # release-workspace.config.ts's generateNotes step uses the conventionalcommits preset, whose templates call a helper that only conventional-changelog-writer@9 or newer provides. @semantic-release/release-notes-generator depends on conventional-changelog-writer@^8, so without this the preset loads and then throws from inside handlebars at render time -- the preset itself detects the too-old writer and registers a helper whose only job is to raise "requires conventional-changelog-writer@9 or newer". Scoped to release-notes-generator rather than global because it is the only consumer that renders with the writer at all: @semantic-release/commit-analyzer declares the same dependency but never imports it, using only the preset's whatBump. + # + # This override is what lets the changelog use the conventionalcommits preset at all. The single-package release config this replaces worked around the same bug by generating notes with the angular preset instead, which loses the per-type section headings. + "@semantic-release/release-notes-generator>conventional-changelog-writer": 9.2.1 diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..bfc254e --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,19 @@ +{ + // The compiler options every package in this workspace shares, in one place. Each package's own tsconfig.json extends this and keeps only what genuinely differs for it -- its `lib` and `types` (the isomorphism gate), its own `include`/`exclude`. Its tsconfig.node.json extends that package's tsconfig.json in turn, so it inherits this transitively. + "compilerOptions": { + "target": "ES2024", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + // Every indexed read is typed as possibly-undefined, so the code narrows properly instead of trusting a bound. + "noUncheckedIndexedAccess": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..75906d7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + // The workspace root's own tooling configs, which are Node programs rather than library source: ESLint's and Prettier's configs, commitlint's, lint-staged's, and the release orchestrator's. packages/trilean has its own pair of tsconfigs and is not part of this program. + "extends": "./tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2024"], + "types": ["node"] + }, + // A glob rather than a hand-listed set: a config file absent from every program is not a silent no-op, type-aware ESLint fails on it outright with "not found in any of the provided project(s)", so adding one must not also require an edit here. + "include": ["*.config.ts"] +} diff --git a/turbo.json b/turbo.json index 9d357d6..8a6fb4e 100644 --- a/turbo.json +++ b/turbo.json @@ -1,44 +1,118 @@ { "$schema": "https://turborepo.com/schema.json", - "globalDependencies": ["pnpm-lock.yaml"], + "globalDependencies": [ + "pnpm-lock.yaml", + ".tool-versions", + "prettier.config.ts", + "tsconfig.base.json" + ], + + // Every task name is the underscore-prefixed one the package already used for its real command (`_build` runs tsdown; the package's own `build` script is `turbo run _build`). Running the public names from here instead would make turbo invoke those wrapper scripts, which invoke turbo again -- the recursive-call case Turborepo's own docs warn against. The wrapper scripts stay in place and stay usable from inside a single package, but the root pipeline reaches straight past them to the leaf commands. + // + // Every task declares `^_build`, its dependencies' builds: a workspace sibling is a symlink whose dist/ exists only once its own build has run, and tsc, type-aware eslint, and vitest all resolve imports through it. With one package that edge is inert today; it is declared so the first sibling added is ordered correctly rather than racing. + // + // `_typecheck`, `_lint` and `_test:smoke` additionally depend on the package's OWN `_build`, which is not the same thing and is not optional here: test/smoke.test.ts and scripts/generate-json-schema.ts both import from ../dist/ by relative path, so tsc, eslint's typed rules, and the smoke suite itself all need this package's dist/ present before they run. "tasks": { + "_build": { + "dependsOn": ["^_build"], + // scripts/** covers the JSON-schema generation step that runs as part of the build; package.json is an input because that generation reads it. + "inputs": ["src/**", "tsdown.config.ts", "scripts/**", "package.json"], + // schemas/** is the build's second output, alongside tsdown's dist/. + "outputs": ["dist/**", "schemas/**"] + }, "_typecheck": { - "dependsOn": ["_build"], + "dependsOn": ["^_build", "_build"], "inputs": ["$TURBO_DEFAULT$", "tsconfig.json", "tsconfig.node.json"], "outputs": [] }, - "_lint": { + // attw --pack inspects the package's own dist/ against its own package.json exports map, so it depends on this package's own `_build` rather than on `^_build` alone. It was previously a bare `pnpm exec attw --pack` step in ci.yml's Typecheck job, run after `pnpm typecheck` purely to reuse the dist/ that task had left behind; as a task it is cached and ordered like everything else. + "_typecheck:attw": { "dependsOn": ["_build"], + "inputs": ["package.json"], + "outputs": [] + }, + "_lint": { + "dependsOn": ["^_build", "_build"], "inputs": ["$TURBO_DEFAULT$", "eslint.config.ts"], "outputs": [".eslintcache"] }, "_test": { + "dependsOn": ["^_build"], "inputs": ["src/**", "vitest.config.ts", "package.json"], "outputs": [] }, "_test:coverage": { + "dependsOn": ["^_build"], "inputs": ["src/**", "vitest.config.ts", "package.json"], "outputs": ["coverage/**"] }, "_test:integration": { - "inputs": ["src/**", "package.json", "vitest.config.ts", "test/integration/**"], + "dependsOn": ["^_build"], + "inputs": [ + "src/**", + "package.json", + "vitest.config.ts", + "test/integration/**" + ], "outputs": [] }, "_test:smoke": { - "dependsOn": ["_build"], - "inputs": ["src/**", "tsdown.config.ts", "package.json", "vitest.config.ts", "test/smoke.test.ts"], + "dependsOn": ["^_build", "_build"], + "inputs": [ + "src/**", + "tsdown.config.ts", + "package.json", + "vitest.config.ts", + "test/smoke.test.ts" + ], + // Deliberately empty rather than dist/**: dist/ is `_build`'s output, and two tasks claiming the same output directory means a cache replay of one can overwrite the other's. The smoke run itself produces nothing worth caching. "outputs": [] }, "_test:workers": { - "inputs": ["src/**", "package.json", "vitest.config.ts", "wrangler.jsonc", "test/workers/**"], + "dependsOn": ["^_build"], + "inputs": [ + "src/**", + "package.json", + "vitest.config.ts", + "wrangler.jsonc", + "test/workers/**" + ], "outputs": [] }, - "_build": { - "inputs": ["src/**", "tsdown.config.ts", "package.json", "scripts/**"], - "outputs": ["dist/**", "schemas/**"] - }, "_prepush": { - "dependsOn": ["_lint", "_typecheck", "_test", "_test:integration", "_test:smoke", "_test:workers"], + "dependsOn": [ + "_lint", + "_typecheck", + "_typecheck:attw", + "_test", + "_test:integration", + "_test:smoke", + "_test:workers" + ], + "outputs": [] + }, + + // The workspace root's own tooling files (eslint.config.ts, commitlint.config.ts, lint-staged.config.ts, release-workspace.config.ts) are held to the same lint and typecheck gates as package code. Registered as root tasks so `turbo run _lint` and `turbo run _typecheck` cover them without a separate command. + "//#_lint": { + "inputs": [ + "eslint.config.ts", + "prettier.config.ts", + "commitlint.config.ts", + "lint-staged.config.ts", + "release-workspace.config.ts", + "tsconfig.json" + ], + "outputs": [".eslintcache"] + }, + "//#_typecheck": { + "inputs": [ + "eslint.config.ts", + "prettier.config.ts", + "commitlint.config.ts", + "lint-staged.config.ts", + "release-workspace.config.ts", + "tsconfig.json" + ], "outputs": [] } } From 2d58fb7c561fbe239532d582ca71703b6b87a8f6 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Thu, 3 Sep 2026 11:21:48 +0100 Subject: [PATCH 3/6] ci(workspace): run every check as a turbo task from the workspace root Each job ran its command against the repository root when that root was also the package; now it runs the same task across the workspace through turbo, so a package added later is covered with no edit here. Pull-request runs pass --affected, scoping every task to the packages the branch actually changed. The pnpm/Node/install/cache preamble each job repeated is extracted into a composite action, which also carries the turbo cache restore that makes one root job per task cheaper than re-running the work per package. attw --pack was a bare step in the Typecheck job, run after pnpm typecheck purely to reuse the dist/ that task had left behind; it is a turbo task now and `pnpm typecheck` reaches it, so the step goes. The release job runs the workspace orchestrator and reports what it released by diffing the remote's tags across the run, which is what the mirror and attestation jobs key off -- they check out that tag rather than main, since a queued release run can push further commits in between, and build and pack from packages/trilean. Dependabot gains the package manifests under packages/*, which a `directory: "/"` entry alone leaves unwatched, and a github-actions entry covering both the workflows and the composite action's own uses: steps -- including the ones holding id-token: write to sign release attestations. --- .github/actions/setup-workspace/action.yml | 45 ++++ .github/dependabot.yml | 25 +- .github/workflows/ci.yml | 288 ++++++++++----------- 3 files changed, 199 insertions(+), 159 deletions(-) create mode 100644 .github/actions/setup-workspace/action.yml diff --git a/.github/actions/setup-workspace/action.yml b/.github/actions/setup-workspace/action.yml new file mode 100644 index 0000000..ca24c5e --- /dev/null +++ b/.github/actions/setup-workspace/action.yml @@ -0,0 +1,45 @@ +name: Setup workspace +description: Set up pnpm + Node, install the workspace from the frozen lockfile, restore turbo's local cache, then run a command. Requires checkout before use. + +inputs: + task: + description: Task identifier, used for the step name and as part of the turbo cache key (lint, typecheck, test, build, release). + required: true + command: + description: Shell command to execute after install. + required: true + github-token: + description: GitHub token for commands that need it (e.g. the release orchestrator). + required: false + default: "" + +runs: + using: composite + steps: + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v7 + with: + # A single source of truth for the whole workspace, rather than a version literal repeated in every job. registry-url is deliberately absent everywhere: setting it makes setup-node write an .npmrc containing an _authToken line, and that line wins over the OIDC token exchange -- so the setting that looks like it configures the registry is exactly the one that would stop trusted publishing working. + node-version-file: .tool-versions + cache: pnpm + + - run: pnpm install --frozen-lockfile + shell: bash + + # turbo's local filesystem cache, keyed per task. A package whose inputs (per turbo.json) are unchanged since a prior run on this lockfile/pipeline generation replays that task's recorded result -- including its declared outputs (e.g. dist/**, .eslintcache) -- instead of re-running tsdown/tsc/eslint/vitest. + # + # hashFiles(pnpm-lock.yaml, turbo.json) puts the dependency set and the task graph itself into the key, so either changing starts a fresh cache lineage rather than inheriting a stale one; github.run_id keeps every run's own save key unique. actions/cache treats a key as immutable -- a save under a key that already exists is silently skipped -- so without run_id, only the very first run for a given lockfile/turbo.json pair would ever actually save anything, and no later run could repair or extend a cache left behind by a partially failed one. The two-level restore-keys then prefers a cache from the same lockfile/turbo.json generation and only falls back to the newest cache for this task when that generation has no entry yet. + - uses: actions/cache@v6 + with: + path: .turbo + key: turbo-${{ inputs.task }}-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} + restore-keys: | + turbo-${{ inputs.task }}-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- + turbo-${{ inputs.task }}-${{ runner.os }}- + + - name: Run ${{ inputs.task }} + run: ${{ inputs.command }} + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 09ae9f5..489346f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,11 @@ version: 2 + updates: - package-ecosystem: "npm" - directory: "/" + # `directories` (plural, glob-capable) covers the workspace root's own manifest plus every package's, in one entry rather than one per manifest -- a `directory: "/"` entry alone would leave every dependency declared inside packages/* unwatched. + directories: + - "/" + - "/packages/*" schedule: interval: "daily" cooldown: @@ -9,3 +13,22 @@ updates: commit-message: prefix: "build" include: "scope" + + # The npm entry above never covers the actions themselves, so every `uses:` in the workflows -- checkout, setup-node, pnpm/action-setup, cache, attest -- would be pinned to a major and then left to drift, including the ones that hold `id-token: write` and sign release attestations. `/` covers the workflow files; `/.github/actions/*` covers the composite action's own `uses:` steps, which live in a separate manifest Dependabot does not reach from the root. + - package-ecosystem: "github-actions" + directories: + - "/" + - "/.github/actions/*" + schedule: + # Weekly rather than the npm entry's daily: action releases are far less frequent, and a daily poll would only add noise. + interval: "weekly" + cooldown: + default-days: 7 + commit-message: + # `ci` rather than the npm entry's `build`: these updates change the CI definition, and commitlint accepts both types. The scope Dependabot appends makes it `ci(deps)`. + prefix: "ci" + include: "scope" + groups: + # One catch-all group, majors included. Unlike an npm major, an action major bump is a one-line change that either passes CI or does not, so there is nothing gained by isolating it into its own pull request. + actions: + patterns: ["*"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c04ca87..ff49518 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,14 +1,26 @@ name: CI +# One workflow for the whole workspace. Every check runs its task across the workspace through turbo from the repository root, rather than against a single package directory, so adding a package needs no edit here. + on: push: branches: [main] pull_request: workflow_dispatch: +# A new push to a pull request supersedes that PR's in-flight run. A push to main never cancels: the release job publishes to npm and pushes tags mid-run, and cancelling it partway leaves real work half-done. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read +env: + # On a pull request, restrict every turbo task to the packages the branch actually changed (and their dependents); on main, run the whole workspace so the caches the next run restores from are complete and the release gate covers everything. --affected compares against the base branch, so the checkouts below use fetch-depth: 0. + TURBO_FLAGS: ${{ github.event_name == 'pull_request' && '--affected' || '' }} + TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || '' }} + jobs: commitlint: name: Commitlint @@ -21,7 +33,7 @@ jobs: - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v7 with: - node-version: '22' + node-version-file: .tool-versions cache: pnpm - run: pnpm install --frozen-lockfile - name: Validate the last commit with commitlint @@ -34,79 +46,51 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 with: - node-version: '22' - cache: pnpm - - run: pnpm install --frozen-lockfile - # turbo's local filesystem cache, keyed per task/job. A job whose inputs (per turbo.json) are unchanged since a prior run on this lockfile/pipeline generation replays that task's recorded result -- including its declared outputs (e.g. dist/**, .eslintcache) -- instead of re-running eslint/tsc/tsdown/vitest. - # - # hashFiles(pnpm-lock.yaml, turbo.json) puts the dependency set and the task graph itself into the key, so either changing starts a fresh cache lineage rather than inheriting a stale one; github.run_id keeps every run's own save key unique. actions/cache treats a key as immutable -- a save under a key that already exists is silently skipped -- so without run_id, only the very first run for a given lockfile/turbo.json pair would ever actually save anything, and no later run could repair or extend a cache left behind by a partially failed one. The two-level restore-keys then prefers a cache from the same lockfile/turbo.json generation and only falls back to the newest cache for this task when that generation has no entry yet. - - uses: actions/cache@v6 + fetch-depth: 0 + - uses: ./.github/actions/setup-workspace with: - path: .turbo - key: turbo-lint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-lint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-lint-${{ runner.os }}- - - run: pnpm lint + task: lint + command: pnpm lint $TURBO_FLAGS typecheck: name: Typecheck runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 with: - node-version: '22' - cache: pnpm - - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 + fetch-depth: 0 + - uses: ./.github/actions/setup-workspace with: - path: .turbo - key: turbo-typecheck-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-typecheck-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-typecheck-${{ runner.os }}- - # turbo's _typecheck task depends on _build, so this already builds dist/ before type-checking. attw (are-the-types-wrong) needs that same dist/, which is why it runs straight after with no separate build step. - - run: pnpm typecheck - - run: pnpm exec attw --pack + task: typecheck + # _typecheck:attw (attw --pack per published package, checking each package's declared types resolve under every module resolution mode) is one of the tasks `pnpm typecheck` runs through turbo, depending on that package's own _build -- no separate build step or bare `pnpm exec attw --pack` needed to give it a dist/ to inspect. + command: pnpm typecheck $TURBO_FLAGS test: name: Test runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 permissions: contents: read code-quality: write # to upload the cobertura coverage report below steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 with: - node-version: '22' - cache: pnpm - - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 + fetch-depth: 0 + - uses: ./.github/actions/setup-workspace with: - path: .turbo - key: turbo-test-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-test-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-test-${{ runner.os }}- - - run: pnpm test:coverage + task: test + command: pnpm test:coverage $TURBO_FLAGS - name: Upload coverage report # Code Quality requires the org on GitHub Team/Enterprise Cloud, which ExaDev is not yet on, so the upload call itself will fail until that changes -- fail-on-error: false keeps that failure a log annotation instead of gating the release job below on a feature we can't turn on yet. Also guarded against fork PRs, which never hold the code-quality: write permission to upload. if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository uses: actions/upload-code-coverage@v1 with: - file: coverage/cobertura-coverage.xml + file: packages/trilean/coverage/cobertura-coverage.xml language: typescript label: unit fail-on-error: false @@ -114,83 +98,73 @@ jobs: test-integration: name: Integration test runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 with: - node-version: '22' - cache: pnpm - - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 + fetch-depth: 0 + - uses: ./.github/actions/setup-workspace with: - path: .turbo - key: turbo-test-integration-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-test-integration-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-test-integration-${{ runner.os }}- - - run: pnpm test:integration + task: test-integration + command: pnpm test:integration $TURBO_FLAGS test-smoke: name: Smoke test runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 with: - node-version: '22' - cache: pnpm - - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 + fetch-depth: 0 + - uses: ./.github/actions/setup-workspace with: - path: .turbo - key: turbo-test-smoke-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-test-smoke-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-test-smoke-${{ runner.os }}- - - run: pnpm test:smoke + task: test-smoke + command: pnpm test:smoke $TURBO_FLAGS test-workers: name: Workers runtime test runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 with: - node-version: '22' - cache: pnpm - # pnpm install builds the workerd binary (allowBuilds in pnpm-workspace.yaml), so this runs the evaluator inside a real Cloudflare Workers isolate -- enforcing zero Node-only API usage at runtime rather than by assertion. - - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 + fetch-depth: 0 + # The install inside the composite action builds the workerd binary (allowBuilds in pnpm-workspace.yaml), so this runs the evaluator inside a real Cloudflare Workers isolate -- enforcing zero Node-only API usage at runtime rather than by assertion. + - uses: ./.github/actions/setup-workspace with: - path: .turbo - key: turbo-test-workers-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-test-workers-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-test-workers-${{ runner.os }}- - - run: pnpm test:workers + task: test-workers + command: pnpm test:workers $TURBO_FLAGS release: name: Release - needs: [commitlint, lint, typecheck, test, test-integration, test-smoke, test-workers] + needs: + [ + commitlint, + lint, + typecheck, + test, + test-integration, + test-smoke, + test-workers, + ] if: github.ref == 'refs/heads/main' && github.event_name == 'push' + # Two pushes to main close together must never run this job at the same time: the orchestrator pushes release commits, tags and dependency bumps to main mid-run, and semantic-release's own stale-checkout guard refuses to publish the moment it sees a commit on main this checkout does not have. Queue the second run behind the first rather than cancelling either -- cancelling mid-release would leave a published package with no committed version bump. + concurrency: + group: release-workspace + cancel-in-progress: false runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 30 permissions: - contents: write # to push the release commit/tag and create the GitHub Release + contents: write # to push the release commit and tags, and create GitHub Releases issues: write # to comment on released issues pull-requests: write # to comment on released pull requests id-token: write # OIDC identity for npm trusted publishing (no NPM_TOKEN) outputs: - published: ${{ steps.before.outputs.version != steps.after.outputs.version }} - version: ${{ steps.after.outputs.version }} + released: ${{ steps.released.outputs.released }} + version: ${{ steps.released.outputs.version }} + tag: ${{ steps.released.outputs.tag }} steps: - # main's ruleset requires every change to land via a pull request, and the default GITHUB_TOKEN has no bypass for that -- @semantic-release/git's release-commit push is a direct push to main, so it needs a token from an actor the ruleset explicitly allows through instead. The org-wide "exadev" GitHub App (installed with access to every ExaDev repo, unlike the documents.js-family repos' own narrower, selected-repository app) is that actor, added as an Integration bypass_actor on this repo's ruleset. + # main's ruleset requires every change to land via a pull request, and the default GITHUB_TOKEN has no bypass for that -- the orchestrator's release commit is a direct push to main, so it needs a token from an actor the ruleset explicitly allows through instead. The org-wide "exadev" GitHub App is that actor, added as an Integration bypass_actor on this repo's ruleset. - name: Generate a token for the release push id: app-token uses: actions/create-github-app-token@v2 @@ -199,39 +173,48 @@ jobs: private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - uses: actions/checkout@v7 with: - # semantic-release analyses the full commit history since the last release. + # ref: main, not the bare event SHA. The orchestrator pushes to the current branch by name, so a detached HEAD stops the run with a WorkspaceStateError rather than pushing HEAD:HEAD; naming the branch is what makes checkout attach HEAD to it. fetch-depth: 0 because each package's release range is analysed from its own last matching tag. + ref: main fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v7 with: - node-version: '22' + node-version-file: .tool-versions cache: pnpm # registry-url is deliberately absent. Setting it makes setup-node write an .npmrc containing an _authToken line, and that line wins over the OIDC token exchange -- so the setting that looks like it configures the registry is exactly the one that would stop trusted publishing working. - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 - with: - path: .turbo - key: turbo-release-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-release-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-release-${{ runner.os }}- - - name: Read pre-release version - id: before - run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" - name: Upgrade npm for OIDC trusted publishing (needs npm CLI >=11.5.1) run: npm install -g npm@latest - - name: Release - # HUSKY=0 so the commit-msg hook never fires against the automated release commit. - run: HUSKY=0 pnpm exec semantic-release + - name: Record the tags present before the orchestrator runs + # The orchestrator creates each package's `name@version` tag in this same checkout as it releases, so diffing the remote's tags across the release step is the exact record of what this run released. Written to RUNNER_TEMP, not the working tree: the single-commit strategy discovers what it touched via `git status` and requires a clean tree to start from, so a scratch file inside the checkout would fail that check every run. + run: git ls-remote --tags origin | sed 's|.*refs/tags/||' | grep -v '\^{}' | sort > "$RUNNER_TEMP/release-tags-before.txt" + - name: Release every package that changed, in dependency order + # The orchestrator discovers the workspace from pnpm-workspace.yaml, orders packages topologically, and runs semantic-release per package with commits path-filtered to that package's own directory and tags in `name@version` form. HUSKY=0 so local git hooks never fire against the automated commit. + run: HUSKY=0 pnpm release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # npm publish provenance for every package the run publishes, matching each package.json's own publishConfig.provenance. + NPM_CONFIG_PROVENANCE: "true" # Blanked, not omitted -- an inherited NPM_TOKEN/NODE_AUTH_TOKEN from a workflow-level env block, reusable workflow, or composite action would otherwise be used in preference to the OIDC exchange. - NPM_TOKEN: '' - NODE_AUTH_TOKEN: '' - - name: Read post-release version - id: after - run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" + NPM_TOKEN: "" + NODE_AUTH_TOKEN: "" + - name: Collect what this run released + id: released + # The post-release jobs below mirror and attest the one published package, so they need its tag and version rather than a matrix. A tag that appeared during the release step and names this package is that record; nothing new means nothing released, and every downstream job skips. + run: | + git ls-remote --tags origin | sed 's|.*refs/tags/||' | grep -v '\^{}' | sort > "$RUNNER_TEMP/release-tags-after.txt" + TAG=$(comm -13 "$RUNNER_TEMP/release-tags-before.txt" "$RUNNER_TEMP/release-tags-after.txt" | grep '^trilean@' || true) + if [ -z "$TAG" ]; then + echo "released=false" >> "$GITHUB_OUTPUT" + echo "::notice::No new trilean tag; nothing was released by this run." + exit 0 + fi + { + echo "released=true" + echo "tag=$TAG" + echo "version=${TAG##*@}" + } >> "$GITHUB_OUTPUT" notify-hive: name: Notify novus-power/hive @@ -264,9 +247,9 @@ jobs: publish-github-packages: name: Publish mirror to GitHub Packages needs: release - if: needs.release.outputs.published == 'true' + if: needs.release.outputs.released == 'true' runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 permissions: contents: read packages: write @@ -276,41 +259,39 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: main # the release commit semantic-release just pushed + # The tag, not main: a queued release run could have pushed further commits and tags to main between this run's release job finishing and this job starting, and the tag points at the exact release commit whose packages/trilean is the released state. + ref: ${{ needs.release.outputs.tag }} - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v7 with: - node-version: '22' + node-version-file: .tool-versions cache: pnpm - # Deliberately NOT setup-node's own registry-url/scope inputs: those write an @exadev:registry=https://npm.pkg.github.com/ *install-time* scope-to-registry mapping into .npmrc, which would redirect this package's own @exadev-scoped devDependency installs (e.g. @exadev/eslint-config, published only to the default registry) through GitHub Packages too, breaking `pnpm install` below. publishConfig.registry (set explicitly below) already fully determines pnpm publish's *target* registry on its own; only the host-scoped auth token below is genuinely needed, added after install so install never sees any @exadev scope mapping at all. + # Deliberately NOT setup-node's own registry-url/scope inputs: those write an @exadev:registry=https://npm.pkg.github.com/ *install-time* scope-to-registry mapping into .npmrc, which would redirect this workspace's own @exadev-scoped devDependency installs (@exadev/eslint-config, @exadev/semantic-release-workspace, published only to the default registry) through GitHub Packages too, breaking `pnpm install` below. publishConfig.registry (set explicitly below) already fully determines pnpm publish's *target* registry on its own; only the host-scoped auth token below is genuinely needed, added after install so install never sees any @exadev scope mapping at all. - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 - with: - path: .turbo - key: turbo-publish-github-packages-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-publish-github-packages-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-publish-github-packages-${{ runner.os }}- - run: pnpm build - name: Rewrite the package name for the GitHub Packages mirror - # trilean itself stays unscoped on npmjs.org (see package.json's own "name"), but GitHub Packages' npm registry structurally requires every package it hosts to be scoped to the owning org -- an unscoped `trilean` publish there is rejected outright. This job's own package.json rewrite (never committed -- it runs against the checkout's working tree only) is what lets the same build ship under both names without the primary npm publish above ever carrying the @exadev scope. + # trilean itself stays unscoped on npmjs.org (see the package's own "name"), but GitHub Packages' npm registry structurally requires every package it hosts to be scoped to the owning org -- an unscoped `trilean` publish there is rejected outright. This job's own package.json rewrite (never committed -- it runs against the checkout's working tree only) is what lets the same build ship under both names without the primary npm publish ever carrying the @exadev scope. + working-directory: packages/trilean run: npm pkg set name="@exadev/trilean" - name: Rewrite the registry for the GitHub Packages mirror # publishConfig.registry has to be overridden explicitly: without it, pnpm publish would target registry.npmjs.org -- the registry the primary, unscoped npm publish already used in the release job above -- instead of GitHub Packages. + working-directory: packages/trilean run: npm pkg set publishConfig.registry="https://npm.pkg.github.com" - name: Drop provenance for the GitHub Packages mirror # npm honours publishConfig.provenance against whatever registry it is publishing to, and signing a provenance statement needs an OIDC token this job deliberately holds no permission to mint -- so leaving the field set fails the publish outright with 'Provenance generation in GitHub Actions requires "write" access to the "id-token" permission', before a single byte is uploaded. Granting that permission is not the fix: sigstore provenance is an npmjs.org feature GitHub Packages does not host, so the mirror would be signing an attestation none of its consumers could ever resolve. The primary npm publish in the release job above keeps provenance, which is where it means something. + working-directory: packages/trilean run: npm pkg delete publishConfig.provenance - name: Configure the GitHub Packages auth token for publish only run: echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc - - run: pnpm publish --access public --no-git-checks + - working-directory: packages/trilean + run: pnpm publish --access public --no-git-checks attest-npm: name: Attest SBOM and build provenance (npm) needs: release - if: needs.release.outputs.published == 'true' + if: needs.release.outputs.released == 'true' runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 permissions: contents: read id-token: write @@ -318,40 +299,35 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: main # the release commit semantic-release just pushed + ref: ${{ needs.release.outputs.tag }} # the release commit the orchestrator tagged, not whatever main has moved on to by now - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v7 with: - node-version: '22' + node-version-file: .tool-versions cache: pnpm - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 - with: - path: .turbo - key: turbo-attest-npm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-attest-npm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-attest-npm-${{ runner.os }}- - run: pnpm build # Pack into a directory of its own, separate from dist/ (tsdown's raw build output). The attestation subject has to be the artefact that actually ships -- attesting dist/ itself would mix in files that never leave the repo, producing digests that match nothing a consumer can download. - - run: pnpm pack --pack-destination release-artifact - - run: pnpm sbom --sbom-format spdx --prod > release-artifact/sbom.spdx.json + - working-directory: packages/trilean + run: pnpm pack --pack-destination release-artifact + - working-directory: packages/trilean + run: pnpm sbom --sbom-format spdx --prod > release-artifact/sbom.spdx.json - name: Attest SBOM uses: actions/attest@v4 with: - subject-path: release-artifact/*.tgz - sbom-path: release-artifact/sbom.spdx.json + subject-path: packages/trilean/release-artifact/*.tgz + sbom-path: packages/trilean/release-artifact/sbom.spdx.json - name: Attest build provenance uses: actions/attest@v4 with: - subject-path: release-artifact/*.tgz + subject-path: packages/trilean/release-artifact/*.tgz attest-github-packages: name: Attest SBOM and build provenance (GitHub Packages) needs: release - if: needs.release.outputs.published == 'true' + if: needs.release.outputs.released == 'true' runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 permissions: contents: read id-token: write @@ -359,32 +335,28 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: main # the release commit semantic-release just pushed + ref: ${{ needs.release.outputs.tag }} # the release commit the orchestrator tagged, not whatever main has moved on to by now - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v7 with: - node-version: '22' + node-version-file: .tool-versions cache: pnpm - run: pnpm install --frozen-lockfile - - uses: actions/cache@v6 - with: - path: .turbo - key: turbo-attest-github-packages-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.run_id }} - restore-keys: | - turbo-attest-github-packages-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- - turbo-attest-github-packages-${{ runner.os }}- - run: pnpm build # The GitHub Packages mirror ships under a different name (see publish-github-packages) and is therefore a genuinely different artefact from the one attest-npm covers above -- attesting only the unscoped tarball would leave the scoped mirror with no provenance a consumer could verify. This job's own attest@v4 attestations land in this repository's own attestation store regardless of which registry the tarball is later published to, so no id-token/OIDC conflict with publish-github-packages' own npm-registry auth exists here. - name: Rewrite the package name to match the GitHub Packages mirror + working-directory: packages/trilean run: npm pkg set name="@exadev/trilean" - - run: pnpm pack --pack-destination release-artifact - - run: pnpm sbom --sbom-format spdx --prod > release-artifact/sbom.spdx.json + - working-directory: packages/trilean + run: pnpm pack --pack-destination release-artifact + - working-directory: packages/trilean + run: pnpm sbom --sbom-format spdx --prod > release-artifact/sbom.spdx.json - name: Attest SBOM uses: actions/attest@v4 with: - subject-path: release-artifact/*.tgz - sbom-path: release-artifact/sbom.spdx.json + subject-path: packages/trilean/release-artifact/*.tgz + sbom-path: packages/trilean/release-artifact/sbom.spdx.json - name: Attest build provenance uses: actions/attest@v4 with: - subject-path: release-artifact/*.tgz + subject-path: packages/trilean/release-artifact/*.tgz From feb0ecee61a754dc55e3f3c858cb928e2fcbccb9 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Thu, 3 Sep 2026 11:23:11 +0100 Subject: [PATCH 4/6] build(release): version and publish each package on its own history semantic-release ran once for the whole repository, which was the same thing as the package while there was only one of it. It now runs through @exadev/semantic-release-workspace, once per package, with each package's commits path-filtered to its own directory and its tags in name@version form -- so a package's version follows its own changes and a change to one never bumps another. commitStrategy "single" makes one commit for the whole run rather than one per released package, which is why @semantic-release/git is absent from the plugin list: its own prepare step would produce exactly the per-package commit that mode replaces. Release notes move from the angular preset to conventionalcommits, so the changelog groups entries under a heading per commit type. That preset was unusable before -- it renders only against conventional-changelog-writer 9 or newer, which @semantic-release/release-notes-generator does not depend on -- and the pnpm override added with the workspace root supplies it, so the workaround the angular preset represented is no longer needed. commitTypes keeps its role as the single source of truth for which commit types exist: commitlint's type-enum, the analyser's release levels, and now each type's changelog heading all derive from the one list. --- CONTRIBUTING.md | 6 ++-- commitlint.config.ts | 2 +- release-workspace.config.ts | 64 ++++++++++++++++++++++++++++++++++++ release.config.ts | 65 ------------------------------------- 4 files changed, 69 insertions(+), 68 deletions(-) create mode 100644 release-workspace.config.ts delete mode 100644 release.config.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b19295f..77d4c4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ `pnpm install` sets up git hooks automatically (`prepare: husky`). From then on: - **Pre-commit** lints and auto-fixes staged files (`lint-staged`). -- **commit-msg** validates the message against [Conventional Commits](https://www.conventionalcommits.org/), enforced by commitlint. The allowed types and the release level each one triggers are defined once, in `release.config.ts`'s `commitTypes` (commitlint imports the same list, so a type can never pass one check and fail the other). As a rule: `feat` releases minor, a breaking-change footer/`!` releases major, everything else (`fix`, `refactor`, `docs`, `test`, `chore`, …) releases patch. +- **commit-msg** validates the message against [Conventional Commits](https://www.conventionalcommits.org/), enforced by commitlint. The allowed types, the release level each one triggers, and its changelog heading are defined once, in `release-workspace.config.ts`'s `commitTypes` (commitlint imports the same list, so a type can never pass one check and fail the other). As a rule: `feat` releases minor, a breaking-change footer/`!` releases major, everything else (`fix`, `refactor`, `docs`, `test`, `chore`, …) releases patch. - **Pre-push** runs the `unit` project (fast) to catch obvious breakage before it reaches CI, which runs the full matrix — `integration`, `smoke`, and `workers` included. ## Before opening a PR @@ -18,4 +18,6 @@ ## Releases -Merging to `main` runs `semantic-release` in CI: it decides the version bump from the commit types since the last release, publishes to npm, tags, and writes `CHANGELOG.md`. Never hand-bump `package.json`'s version or edit `CHANGELOG.md` directly — both are overwritten by the next release. +Merging to `main` runs [`@exadev/semantic-release-workspace`](https://www.npmjs.com/package/@exadev/semantic-release-workspace) in CI, which runs semantic-release once per package. Each package is analysed against only the commits that touched its own directory, so its version tracks its own history and a change to one package never bumps another. A release publishes to npm, tags as `@`, and writes that package's own `CHANGELOG.md`. + +Never hand-bump a `package.json` version or edit a `CHANGELOG.md` directly — both are overwritten by the next release. A commit's *scope* is free text and does not route it anywhere; what decides which package releases is which files the commit changed. diff --git a/commitlint.config.ts b/commitlint.config.ts index c1fac9b..569891c 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -1,4 +1,4 @@ -import { commitTypes } from "./release.config"; +import { commitTypes } from "./release-workspace.config"; export default { extends: ["@commitlint/config-conventional"], diff --git a/release-workspace.config.ts b/release-workspace.config.ts new file mode 100644 index 0000000..447479a --- /dev/null +++ b/release-workspace.config.ts @@ -0,0 +1,64 @@ +import type { ReleaseWorkspaceOptions } from "@exadev/semantic-release-workspace"; + +type ReleaseLevel = "major" | "minor" | "patch" | false; + +interface CommitType { + readonly type: string; + readonly release: ReleaseLevel; + readonly section: string; +} + +/** + * Single source of truth for the conventional-commit types this repository uses. commitlint's allowed type-enum (commitlint.config.ts imports this), the commit analyser's releaseRules, and the changelog's section headings all derive from it, so a type cannot trigger a release without also being accepted by commit-msg validation, or appear in a changelog under no heading. + * + * Defined here rather than in a shared commit-types.ts: this file is loaded through cosmiconfig, which transpiles only the file it loads, so a sibling .ts module would not resolve from it. commitlint's jiti loader has no such limit, so it imports commitTypes from here. + */ +export const commitTypes: readonly CommitType[] = [ + { type: "feat", release: "minor", section: "Features" }, + { type: "fix", release: "patch", section: "Bug Fixes" }, + { type: "perf", release: "patch", section: "Performance Improvements" }, + { type: "revert", release: "patch", section: "Reverts" }, + { type: "refactor", release: "patch", section: "Code Refactoring" }, + { type: "docs", release: "patch", section: "Documentation" }, + { type: "style", release: "patch", section: "Styles" }, + { type: "test", release: "patch", section: "Tests" }, + { type: "build", release: "patch", section: "Build System" }, + { type: "ci", release: "patch", section: "Continuous Integration" }, + { type: "chore", release: "patch", section: "Miscellaneous Chores" }, +]; + +/** + * Runs on `main`, once per push, through @exadev/semantic-release-workspace rather than semantic-release directly. + * + * The orchestrator discovers every package from pnpm-workspace.yaml, orders them so a package releases only after each workspace sibling it depends on has, and runs semantic-release per package with the commit list path-filtered to that package's own directory and its tags in `name@version` form. So each package's version tracks its own history: `packages/trilean` continues from the version its own `trilean@x.y.z` tags record, and a package added later starts its own sequence without disturbing it. + * + * `commitStrategy: "single"` produces one commit for the whole run -- every version bump, changelog write, and dependency-range rewrite together -- instead of one commit per released package plus one per bump. @semantic-release/git is deliberately absent from the plugin list because of it: that plugin's own prepare step would make exactly the per-package commit this mode exists to replace, and the orchestrator rejects the combination outright rather than producing both. + */ +const config: Pick< + ReleaseWorkspaceOptions, + "branches" | "commitStrategy" | "plugins" | "analyzeCommits" | "generateNotes" +> = { + branches: ["main"], + commitStrategy: "single", + plugins: [ + "@semantic-release/changelog", + ["@semantic-release/npm", { npmPublish: true }], + "@semantic-release/github", + ], + analyzeCommits: { + preset: "conventionalcommits", + releaseRules: [ + { breaking: true, release: "major" }, + ...commitTypes.map(({ type, release }) => ({ type, release })), + ], + }, + generateNotes: { + // The conventionalcommits preset, not angular: it is the one that groups the changelog by commit type, and the presetConfig below names every type's section. It renders only against conventional-changelog-writer 9 or newer, which @semantic-release/release-notes-generator does not itself depend on -- see the pnpm override that supplies it. + preset: "conventionalcommits", + presetConfig: { + types: commitTypes.map(({ type, section }) => ({ type, section })), + }, + }, +}; + +export default config; diff --git a/release.config.ts b/release.config.ts deleted file mode 100644 index 7665058..0000000 --- a/release.config.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { Options } from "semantic-release"; - -type ReleaseLevel = "major" | "minor" | "patch" | false; - -interface CommitType { - readonly type: string; - readonly release: ReleaseLevel; -} - -/** - * Single source of truth for the conventional-commit types this project uses. commitlint's allowed type-enum (commitlint.config.ts imports this) and commit-analyzer's releaseRules below both derive from it, so a type can't trigger a release without also being accepted by commit-msg validation, or the reverse. - * - * Defined here rather than in a shared commit-types.ts: semantic-release loads this file via cosmiconfig, which transpiles only this one file to ESM, so a sibling .ts module would not resolve. commitlint's jiti loader has no such limit, so it imports commitTypes from here. - */ -export const commitTypes: readonly CommitType[] = [ - { type: "feat", release: "minor" }, - { type: "fix", release: "patch" }, - { type: "perf", release: "patch" }, - { type: "revert", release: "patch" }, - { type: "refactor", release: "patch" }, - { type: "docs", release: "patch" }, - { type: "style", release: "patch" }, - { type: "test", release: "patch" }, - { type: "build", release: "patch" }, - { type: "ci", release: "patch" }, - { type: "chore", release: "patch" }, -]; - -/** - * Runs on `main`. Analyses commits since the last tag, bumps the version, publishes to npmjs.org (trusted OIDC publishing, no stored token -- see .github/workflows/ci.yml), creates a versioned tag and GitHub Release with generated notes, and commits CHANGELOG.md + package.json back to main. The release commit's [skip ci] message avoids a redundant CI run. - */ -const config: Options = { - branches: ["main"], - plugins: [ - [ - "@semantic-release/commit-analyzer", - { - preset: "conventionalcommits", - releaseRules: [ - { breaking: true, release: "major" }, - ...commitTypes.map((t) => ({ type: t.type, release: t.release })), - ], - }, - ], - [ - "@semantic-release/release-notes-generator", - { - // Deliberately angular, not conventionalcommits: conventional-changelog-writer's bundled commit partial doesn't match the conventionalcommits preset's function-based partial signature, producing a changelog with a version header and nothing under it. commitTypes still drives commit-analyzer's releaseRules and commitlint's type-enum regardless of which changelog preset is used. - preset: "angular", - }, - ], - "@semantic-release/changelog", - ["@semantic-release/npm", { npmPublish: true }], - "@semantic-release/github", - [ - "@semantic-release/git", - { - assets: ["CHANGELOG.md", "package.json"], - message: "chore(release): ${nextRelease.version} [skip ci]", - }, - ], - ], -}; - -export default config; From c856decf3e1887503a739db345343928547b613c Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Thu, 3 Sep 2026 11:24:56 +0100 Subject: [PATCH 5/6] docs(release): record which tag the package's version sequence continues from The per-package tag format is what carries a version across from before the workspace existed, and it differs from the one a single-package release used -- v1.3.0 then, trilean@1.3.0 now. Nothing in the config says so, and the consequence of not knowing is severe rather than cosmetic: with no tag in the new format, semantic-release finds no previous release, restarts the package at 1.0.0, and its first publish collides with a version the registry already holds. --- release-workspace.config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release-workspace.config.ts b/release-workspace.config.ts index 447479a..99485d4 100644 --- a/release-workspace.config.ts +++ b/release-workspace.config.ts @@ -32,6 +32,8 @@ export const commitTypes: readonly CommitType[] = [ * * The orchestrator discovers every package from pnpm-workspace.yaml, orders them so a package releases only after each workspace sibling it depends on has, and runs semantic-release per package with the commit list path-filtered to that package's own directory and its tags in `name@version` form. So each package's version tracks its own history: `packages/trilean` continues from the version its own `trilean@x.y.z` tags record, and a package added later starts its own sequence without disturbing it. * + * That tag format is what carries a package's version across from before the workspace existed, and it is not the format a single-package release used: `v1.3.0` then, `trilean@1.3.0` now. A package brought in from a repository of its own therefore needs a tag in the new format created at the commit its last old-format tag names -- semantic-release derives the previous version from the last tag matching the format it is configured with, and finds nothing at all without one, so the package would restart at 1.0.0 and its first publish would collide with a version the registry already holds. `trilean@1.3.0` exists for exactly that reason and points at the same commit as `v1.3.0`. + * * `commitStrategy: "single"` produces one commit for the whole run -- every version bump, changelog write, and dependency-range rewrite together -- instead of one commit per released package plus one per bump. @semantic-release/git is deliberately absent from the plugin list because of it: that plugin's own prepare step would make exactly the per-package commit this mode exists to replace, and the orchestrator rejects the combination outright rather than producing both. */ const config: Pick< From 2f2fc4ad877c95f3d61c76bdd4071d5e859f8d10 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Thu, 3 Sep 2026 14:27:29 +0100 Subject: [PATCH 6/6] fix(ci): match notify-hive's release check to the restructured job's real output notify-hive still gated on needs.release.outputs.published, the flat single-package workflow's output name before the release job was restructured to emit released/version/tag. The condition silently evaluated to false on every run -- GitHub Actions treats a reference to a non-existent output as an empty string, which never equals 'true' -- so the job would never have fired. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff49518..1793dd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,7 +219,7 @@ jobs: notify-hive: name: Notify novus-power/hive needs: release - if: needs.release.outputs.published == 'true' + if: needs.release.outputs.released == 'true' runs-on: ubuntu-latest timeout-minutes: 5 permissions: