From 7127cfa19ac1c42b891f31f97d3cc5f09d2155ce Mon Sep 17 00:00:00 2001 From: lvbt-bot Date: Wed, 23 Sep 2026 20:32:14 +0000 Subject: [PATCH] chore: publish LVBT repository standard v0.3.5 Refresh this template from the reviewed v0.3.5 source. --- .agents/plugins/marketplace.json | 2 +- .claude/settings.json | 2 +- .codex/hooks.json | 2 +- .githooks/commit-msg | 4 +- .githooks/pre-commit | 4 +- .githooks/pre-push | 4 +- .githooks/prepare-commit-msg | 4 +- .github/renovate.json | 16 +-- .gitignore | 4 + .lvbt/web-platform.json | 6 +- .../.agents/plugins/marketplace.json | 2 +- .../examples/with-astro/.claude/settings.json | 2 +- .../examples/with-astro/.codex/hooks.json | 2 +- .../examples/with-astro/.githooks/commit-msg | 4 +- .../examples/with-astro/.githooks/pre-commit | 4 +- .../examples/with-astro/.githooks/pre-push | 4 +- .../with-astro/.githooks/prepare-commit-msg | 4 +- .../examples/with-astro/.github/renovate.json | 16 +-- .../examples/with-astro/.gitignore | 4 + .../with-astro/.markdownlint-cli2.jsonc | 1 + .lvbt/web-platform/examples/with-astro/.npmrc | 1 + .../examples/with-astro/AGENTS.md | 6 +- .../examples/with-astro/README.md | 2 +- .../with-astro/apps/site/eslint.config.js | 2 +- .../with-astro/apps/site/package.json | 10 +- .../with-astro/apps/site/playwright.config.ts | 2 +- .../apps/site/tests/e2e/home.spec.ts | 4 +- .../with-astro/apps/site/tsconfig.json | 2 +- .../with-astro/apps/site/vitest.config.ts | 2 +- .../docs/development/reference/glossary.md | 4 +- .../examples/with-astro/package.json | 4 +- .../examples/with-astro/prettier.config.js | 2 +- .../.agents/plugins/marketplace.json | 2 +- .../with-vite-react/.claude/settings.json | 2 +- .../with-vite-react/.codex/hooks.json | 2 +- .../with-vite-react/.githooks/commit-msg | 4 +- .../with-vite-react/.githooks/pre-commit | 4 +- .../with-vite-react/.githooks/pre-push | 4 +- .../.githooks/prepare-commit-msg | 4 +- .../with-vite-react/.github/renovate.json | 16 +-- .../examples/with-vite-react/.gitignore | 4 + .../with-vite-react/.markdownlint-cli2.jsonc | 1 + .../examples/with-vite-react/.npmrc | 1 + .../examples/with-vite-react/AGENTS.md | 6 +- .../examples/with-vite-react/README.md | 2 +- .../with-vite-react/apps/app/eslint.config.js | 2 +- .../with-vite-react/apps/app/package.json | 10 +- .../apps/app/playwright.config.ts | 2 +- .../apps/app/tests/e2e/app.spec.ts | 4 +- .../with-vite-react/apps/app/tsconfig.json | 2 +- .../with-vite-react/apps/app/vitest.config.ts | 2 +- .../docs/development/reference/glossary.md | 4 +- .../examples/with-vite-react/package.json | 4 +- .../with-vite-react/prettier.config.js | 2 +- .lvbt/web-platform/packages/cli/package.json | 4 +- .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../packages/cli/src/lib/check/contract.mjs | 14 +- .../packages/eslint-config/package.json | 4 +- .../packages/playwright-config/index.js | 2 +- .../packages/playwright-config/package.json | 4 +- .../packages/prettier-config/package.json | 4 +- .../packages/typescript-config/package.json | 4 +- .../packages/vitest-config/index.js | 2 +- .../packages/vitest-config/package.json | 4 +- .../packages/web-platform/README.md | 11 +- .../packages/web-platform/package.json | 8 +- .../web-platform/src/doctor-cloudflare.ts | 59 ++++---- .../web-platform/src/provision-analytics.ts | 4 +- .../tests/doctor-cloudflare.test.ts | 91 ++++++++++++- .../tests/provision-analytics.test.ts | 31 ++++- .../standards/template-publication.ts | 126 ++++++++++++++++++ .../standards/web-platform-cli.ts | 30 ++++- .../standards/web-platform-source.ts | 2 +- .lvbt/web-platform/standards/web-platform.ts | 81 ++++++++++- .npmrc | 1 + AGENTS.md | 6 +- README.md | 2 +- docs/development/reference/glossary.md | 4 +- package.json | 4 +- packages/example/eslint.config.js | 2 +- packages/example/package.json | 8 +- packages/example/tsconfig.json | 2 +- packages/example/vitest.config.ts | 2 +- pnpm-lock.yaml | 50 +++---- prettier.config.js | 2 +- 86 files changed, 565 insertions(+), 223 deletions(-) create mode 100644 .lvbt/web-platform/examples/with-astro/.npmrc create mode 100644 .lvbt/web-platform/examples/with-vite-react/.npmrc create mode 100644 .lvbt/web-platform/standards/template-publication.ts create mode 100644 .npmrc diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 3109e3f..559bf3d 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -8,7 +8,7 @@ "name": "lvbt-contributions", "source": { "source": "local", - "path": "../../node_modules/@lvbt/cli/plugins/lvbt-contributions" + "path": "../../node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions" }, "policy": { "installation": "AVAILABLE", diff --git a/.claude/settings.json b/.claude/settings.json index d1c0e32..6704e7d 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -4,7 +4,7 @@ "source": { "source": "github", "repo": "LasVegasForTransit/repository-tooling", - "ref": "v0.2.8" + "ref": "v0.3.5" } } }, diff --git a/.codex/hooks.json b/.codex/hooks.json index e6c5136..4d785b8 100644 --- a/.codex/hooks.json +++ b/.codex/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lvbt/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", + "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", "timeout": 10 } ] diff --git a/.githooks/commit-msg b/.githooks/commit-msg index f16cc88..ab3a79e 100755 --- a/.githooks/commit-msg +++ b/.githooks/commit-msg @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/commit-msg.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/commit-msg.sh" "$@" diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 1310c52..48fc8aa 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/pre-commit.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/pre-commit.sh" "$@" diff --git a/.githooks/pre-push b/.githooks/pre-push index 76aa3a8..366e9ce 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/pre-push.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/pre-push.sh" "$@" diff --git a/.githooks/prepare-commit-msg b/.githooks/prepare-commit-msg index 464132c..81b471a 100755 --- a/.githooks/prepare-commit-msg +++ b/.githooks/prepare-commit-msg @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/prepare-commit-msg.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/prepare-commit-msg.sh" "$@" diff --git a/.github/renovate.json b/.github/renovate.json index d7160ad..293369c 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -11,24 +11,10 @@ "enabled": true, "schedule": ["before 6am on monday"] }, - "customManagers": [ - { - "description": "The @lvbt/* packages install from git tags of repository-tooling with a path: suffix that the npm manager does not parse, and the Claude Code marketplace pins the same tag. Track both as one dependency on the repository's tags.", - "customType": "regex", - "managerFilePatterns": ["/(^|/)package\\.json$/", "/(^|/)\\.claude/settings\\.json$/"], - "matchStrings": [ - "github:LasVegasForTransit/repository-tooling#(?v\\d+\\.\\d+\\.\\d+)&path:/packages/[a-z-]+", - "\"repo\": \"LasVegasForTransit/repository-tooling\",\\s*\"ref\": \"(?v\\d+\\.\\d+\\.\\d+)\"" - ], - "depNameTemplate": "LasVegasForTransit/repository-tooling", - "datasourceTemplate": "github-tags", - "versioningTemplate": "semver" - } - ], "packageRules": [ { "groupName": "LVBT repository standard", - "matchDepNames": ["LasVegasForTransit/repository-tooling"] + "matchPackageNames": ["@lasvegasfortransit/*"] }, { "groupName": "dev dependencies (non-major)", diff --git a/.gitignore b/.gitignore index cff9ebb..39d43f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ node_modules/ dist/ coverage/ +test-results/ +playwright-report/ +blob-report/ +**/playwright/.cache/ .turbo/ .wrangler/ .env diff --git a/.lvbt/web-platform.json b/.lvbt/web-platform.json index e124081..029cfba 100644 --- a/.lvbt/web-platform.json +++ b/.lvbt/web-platform.json @@ -1,9 +1,9 @@ { "formatVersion": 1, "preset": "lvbt-web", - "release": "v0.2.8", - "commit": "8c33f0f2855fb1ce919df0c076d866392b83946e", - "contentHash": "f649fa8382fb42b4c8aafb4681406f75f3baf4e24f97ccb78eb5ce2198f74996", + "release": "v0.3.5", + "commit": "657c8ecf60b2cb6791f4b731f0b03c9756384712", + "contentHash": "849142b984ac92bf36a930904a064f31e2a5ecfcbb2e2dc509caa41a77f24a27", "executables": [ "examples/with-astro/.githooks/commit-msg", "examples/with-astro/.githooks/pre-commit", diff --git a/.lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json b/.lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json index 3109e3f..559bf3d 100644 --- a/.lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json +++ b/.lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json @@ -8,7 +8,7 @@ "name": "lvbt-contributions", "source": { "source": "local", - "path": "../../node_modules/@lvbt/cli/plugins/lvbt-contributions" + "path": "../../node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions" }, "policy": { "installation": "AVAILABLE", diff --git a/.lvbt/web-platform/examples/with-astro/.claude/settings.json b/.lvbt/web-platform/examples/with-astro/.claude/settings.json index d1c0e32..6704e7d 100644 --- a/.lvbt/web-platform/examples/with-astro/.claude/settings.json +++ b/.lvbt/web-platform/examples/with-astro/.claude/settings.json @@ -4,7 +4,7 @@ "source": { "source": "github", "repo": "LasVegasForTransit/repository-tooling", - "ref": "v0.2.8" + "ref": "v0.3.5" } } }, diff --git a/.lvbt/web-platform/examples/with-astro/.codex/hooks.json b/.lvbt/web-platform/examples/with-astro/.codex/hooks.json index e6c5136..4d785b8 100644 --- a/.lvbt/web-platform/examples/with-astro/.codex/hooks.json +++ b/.lvbt/web-platform/examples/with-astro/.codex/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lvbt/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", + "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", "timeout": 10 } ] diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/commit-msg b/.lvbt/web-platform/examples/with-astro/.githooks/commit-msg index f16cc88..ab3a79e 100755 --- a/.lvbt/web-platform/examples/with-astro/.githooks/commit-msg +++ b/.lvbt/web-platform/examples/with-astro/.githooks/commit-msg @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/commit-msg.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/commit-msg.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/pre-commit b/.lvbt/web-platform/examples/with-astro/.githooks/pre-commit index 1310c52..48fc8aa 100755 --- a/.lvbt/web-platform/examples/with-astro/.githooks/pre-commit +++ b/.lvbt/web-platform/examples/with-astro/.githooks/pre-commit @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/pre-commit.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/pre-commit.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/pre-push b/.lvbt/web-platform/examples/with-astro/.githooks/pre-push index 76aa3a8..366e9ce 100755 --- a/.lvbt/web-platform/examples/with-astro/.githooks/pre-push +++ b/.lvbt/web-platform/examples/with-astro/.githooks/pre-push @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/pre-push.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/pre-push.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg b/.lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg index 464132c..81b471a 100755 --- a/.lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg +++ b/.lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/prepare-commit-msg.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/prepare-commit-msg.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.github/renovate.json b/.lvbt/web-platform/examples/with-astro/.github/renovate.json index d7160ad..293369c 100644 --- a/.lvbt/web-platform/examples/with-astro/.github/renovate.json +++ b/.lvbt/web-platform/examples/with-astro/.github/renovate.json @@ -11,24 +11,10 @@ "enabled": true, "schedule": ["before 6am on monday"] }, - "customManagers": [ - { - "description": "The @lvbt/* packages install from git tags of repository-tooling with a path: suffix that the npm manager does not parse, and the Claude Code marketplace pins the same tag. Track both as one dependency on the repository's tags.", - "customType": "regex", - "managerFilePatterns": ["/(^|/)package\\.json$/", "/(^|/)\\.claude/settings\\.json$/"], - "matchStrings": [ - "github:LasVegasForTransit/repository-tooling#(?v\\d+\\.\\d+\\.\\d+)&path:/packages/[a-z-]+", - "\"repo\": \"LasVegasForTransit/repository-tooling\",\\s*\"ref\": \"(?v\\d+\\.\\d+\\.\\d+)\"" - ], - "depNameTemplate": "LasVegasForTransit/repository-tooling", - "datasourceTemplate": "github-tags", - "versioningTemplate": "semver" - } - ], "packageRules": [ { "groupName": "LVBT repository standard", - "matchDepNames": ["LasVegasForTransit/repository-tooling"] + "matchPackageNames": ["@lasvegasfortransit/*"] }, { "groupName": "dev dependencies (non-major)", diff --git a/.lvbt/web-platform/examples/with-astro/.gitignore b/.lvbt/web-platform/examples/with-astro/.gitignore index 18995c8..c65b6f7 100644 --- a/.lvbt/web-platform/examples/with-astro/.gitignore +++ b/.lvbt/web-platform/examples/with-astro/.gitignore @@ -1,6 +1,10 @@ node_modules/ dist/ coverage/ +test-results/ +playwright-report/ +blob-report/ +**/playwright/.cache/ .turbo/ .wrangler/ .env diff --git a/.lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc b/.lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc index 14459ad..350e5df 100644 --- a/.lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc +++ b/.lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc @@ -19,6 +19,7 @@ "dist", "**/dist", ".turbo", + ".lvbt/web-platform", "docs/superpowers", "CHANGELOG.md", ], diff --git a/.lvbt/web-platform/examples/with-astro/.npmrc b/.lvbt/web-platform/examples/with-astro/.npmrc new file mode 100644 index 0000000..e520ccd --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.npmrc @@ -0,0 +1 @@ +@lasvegasfortransit:registry=https://npm.pkg.github.com diff --git a/.lvbt/web-platform/examples/with-astro/AGENTS.md b/.lvbt/web-platform/examples/with-astro/AGENTS.md index a599239..51ae712 100644 --- a/.lvbt/web-platform/examples/with-astro/AGENTS.md +++ b/.lvbt/web-platform/examples/with-astro/AGENTS.md @@ -25,9 +25,9 @@ authorizes creating an issue or pull request. It carries the organization checkl templates, and the only approved creation helper: ```bash -node node_modules/@lvbt/cli/plugins/lvbt-contributions/scripts/github-create.mjs issue \ +node node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/scripts/github-create.mjs issue \ --type bug|feature --title --body-file <file> -node node_modules/@lvbt/cli/plugins/lvbt-contributions/scripts/github-create.mjs pr \ +node node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/scripts/github-create.mjs pr \ --title <title> --body-file <file> --base main ``` @@ -43,5 +43,5 @@ change crosses boundaries; never invent one for a feature, file, task, or role. ## The repository standard -Lint, format, TypeScript, and test settings extend the `@lvbt/*` packages from +Lint, format, TypeScript, and test settings extend the `@lasvegasfortransit/*` packages from `LasVegasForTransit/repository-tooling`. Change a shared rule there, not here. diff --git a/.lvbt/web-platform/examples/with-astro/README.md b/.lvbt/web-platform/examples/with-astro/README.md index 4ec3fc9..98140f7 100644 --- a/.lvbt/web-platform/examples/with-astro/README.md +++ b/.lvbt/web-platform/examples/with-astro/README.md @@ -28,5 +28,5 @@ repository's boundaries. `pnpm run deploy` builds and runs `wrangler deploy` for every app with a wrangler config; `.github/workflows/deploy.yml` does the same on every push to `main`. -Lint, format, TypeScript, and test settings extend the `@lvbt/*` packages from +Lint, format, TypeScript, and test settings extend the `@lasvegasfortransit/*` packages from [`LasVegasForTransit/repository-tooling`](https://github.com/LasVegasForTransit/repository-tooling). diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/eslint.config.js b/.lvbt/web-platform/examples/with-astro/apps/site/eslint.config.js index 50f29f2..fe2b986 100644 --- a/.lvbt/web-platform/examples/with-astro/apps/site/eslint.config.js +++ b/.lvbt/web-platform/examples/with-astro/apps/site/eslint.config.js @@ -1,3 +1,3 @@ -import { config } from '@lvbt/eslint-config/base'; +import { config } from '@lasvegasfortransit/eslint-config/base'; export default config; diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/package.json b/.lvbt/web-platform/examples/with-astro/apps/site/package.json index a062108..7cb718f 100644 --- a/.lvbt/web-platform/examples/with-astro/apps/site/package.json +++ b/.lvbt/web-platform/examples/with-astro/apps/site/package.json @@ -1,5 +1,5 @@ { - "name": "@lvbt/site", + "name": "@lasvegasfortransit/site", "version": "0.0.0", "private": true, "type": "module", @@ -20,10 +20,10 @@ }, "devDependencies": { "@astrojs/check": "catalog:", - "@lvbt/eslint-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/eslint-config", - "@lvbt/playwright-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/playwright-config", - "@lvbt/typescript-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/typescript-config", - "@lvbt/vitest-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/vitest-config", + "@lasvegasfortransit/eslint-config": "0.3.5", + "@lasvegasfortransit/playwright-config": "0.3.5", + "@lasvegasfortransit/typescript-config": "0.3.5", + "@lasvegasfortransit/vitest-config": "0.3.5", "@playwright/test": "catalog:", "@types/node": "catalog:", "eslint": "catalog:", diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/playwright.config.ts b/.lvbt/web-platform/examples/with-astro/apps/site/playwright.config.ts index 47d462a..5caad84 100644 --- a/.lvbt/web-platform/examples/with-astro/apps/site/playwright.config.ts +++ b/.lvbt/web-platform/examples/with-astro/apps/site/playwright.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from '@playwright/test'; -import { foregroundServerEnvironment, sharedConfig } from '@lvbt/playwright-config'; +import { foregroundServerEnvironment, sharedConfig } from '@lasvegasfortransit/playwright-config'; const url = 'http://127.0.0.1:4321'; diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/tests/e2e/home.spec.ts b/.lvbt/web-platform/examples/with-astro/apps/site/tests/e2e/home.spec.ts index a4b97af..550fd00 100644 --- a/.lvbt/web-platform/examples/with-astro/apps/site/tests/e2e/home.spec.ts +++ b/.lvbt/web-platform/examples/with-astro/apps/site/tests/e2e/home.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { expectNoAccessibilityViolations } from '@lvbt/playwright-config/accessibility'; -import { monitorPageHealth } from '@lvbt/playwright-config/page-health'; +import { expectNoAccessibilityViolations } from '@lasvegasfortransit/playwright-config/accessibility'; +import { monitorPageHealth } from '@lasvegasfortransit/playwright-config/page-health'; test('the home page has one main heading', async ({ page }) => { const health = monitorPageHealth(page); diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json b/.lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json index 898e748..fd1637b 100644 --- a/.lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json +++ b/.lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@lvbt/typescript-config/astro.json", + "extends": "@lasvegasfortransit/typescript-config/astro.json", "include": [".astro/types.d.ts", "src", "tests", "*.config.ts"], "exclude": ["dist"] } diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/vitest.config.ts b/.lvbt/web-platform/examples/with-astro/apps/site/vitest.config.ts index 2474677..4222831 100644 --- a/.lvbt/web-platform/examples/with-astro/apps/site/vitest.config.ts +++ b/.lvbt/web-platform/examples/with-astro/apps/site/vitest.config.ts @@ -1,5 +1,5 @@ import { defineConfig } from 'vitest/config'; -import { sharedConfig } from '@lvbt/vitest-config'; +import { sharedConfig } from '@lasvegasfortransit/vitest-config'; export default defineConfig({ ...sharedConfig }); diff --git a/.lvbt/web-platform/examples/with-astro/docs/development/reference/glossary.md b/.lvbt/web-platform/examples/with-astro/docs/development/reference/glossary.md index 6f18f37..39abe60 100644 --- a/.lvbt/web-platform/examples/with-astro/docs/development/reference/glossary.md +++ b/.lvbt/web-platform/examples/with-astro/docs/development/reference/glossary.md @@ -20,10 +20,10 @@ of every tool for the whole organization. A package writes `"catalog:"` as a ver pinned one. <a id="eslint"></a>**ESLint**: finds likely bugs and enforces code rules. The rules come from -`@lvbt/eslint-config`. +`@lasvegasfortransit/eslint-config`. <a id="prettier"></a>**Prettier**: formats code the same way for everyone, so formatting is never -discussed in review. The settings come from `@lvbt/prettier-config`. +discussed in review. The settings come from `@lasvegasfortransit/prettier-config`. <a id="vitest"></a>**Vitest**: runs unit tests under `tests/`. diff --git a/.lvbt/web-platform/examples/with-astro/package.json b/.lvbt/web-platform/examples/with-astro/package.json index 4f9ee51..fdb9b97 100644 --- a/.lvbt/web-platform/examples/with-astro/package.json +++ b/.lvbt/web-platform/examples/with-astro/package.json @@ -23,8 +23,8 @@ "*": "prettier --write --ignore-unknown" }, "devDependencies": { - "@lvbt/cli": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/cli", - "@lvbt/prettier-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/prettier-config", + "@lasvegasfortransit/cli": "0.3.5", + "@lasvegasfortransit/prettier-config": "0.3.5", "lint-staged": "catalog:", "markdownlint-cli2": "catalog:", "markdownlint-rule-relative-links": "catalog:", diff --git a/.lvbt/web-platform/examples/with-astro/prettier.config.js b/.lvbt/web-platform/examples/with-astro/prettier.config.js index 06fd3db..c2d836b 100644 --- a/.lvbt/web-platform/examples/with-astro/prettier.config.js +++ b/.lvbt/web-platform/examples/with-astro/prettier.config.js @@ -1,4 +1,4 @@ -import lvbt from '@lvbt/prettier-config'; +import lvbt from '@lasvegasfortransit/prettier-config'; export default { ...lvbt, diff --git a/.lvbt/web-platform/examples/with-vite-react/.agents/plugins/marketplace.json b/.lvbt/web-platform/examples/with-vite-react/.agents/plugins/marketplace.json index 3109e3f..559bf3d 100644 --- a/.lvbt/web-platform/examples/with-vite-react/.agents/plugins/marketplace.json +++ b/.lvbt/web-platform/examples/with-vite-react/.agents/plugins/marketplace.json @@ -8,7 +8,7 @@ "name": "lvbt-contributions", "source": { "source": "local", - "path": "../../node_modules/@lvbt/cli/plugins/lvbt-contributions" + "path": "../../node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions" }, "policy": { "installation": "AVAILABLE", diff --git a/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json b/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json index d1c0e32..6704e7d 100644 --- a/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json +++ b/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json @@ -4,7 +4,7 @@ "source": { "source": "github", "repo": "LasVegasForTransit/repository-tooling", - "ref": "v0.2.8" + "ref": "v0.3.5" } } }, diff --git a/.lvbt/web-platform/examples/with-vite-react/.codex/hooks.json b/.lvbt/web-platform/examples/with-vite-react/.codex/hooks.json index e6c5136..4d785b8 100644 --- a/.lvbt/web-platform/examples/with-vite-react/.codex/hooks.json +++ b/.lvbt/web-platform/examples/with-vite-react/.codex/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lvbt/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", + "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", "timeout": 10 } ] diff --git a/.lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg b/.lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg index f16cc88..ab3a79e 100755 --- a/.lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/commit-msg.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/commit-msg.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit index 1310c52..48fc8aa 100755 --- a/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/pre-commit.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/pre-commit.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-push b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-push index 76aa3a8..366e9ce 100755 --- a/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-push +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-push @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/pre-push.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/pre-push.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-vite-react/.githooks/prepare-commit-msg b/.lvbt/web-platform/examples/with-vite-react/.githooks/prepare-commit-msg index 464132c..81b471a 100755 --- a/.lvbt/web-platform/examples/with-vite-react/.githooks/prepare-commit-msg +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/prepare-commit-msg @@ -1,5 +1,5 @@ #!/usr/bin/env sh -# The shared hook lives in @lvbt/cli; repository-specific steps go below it. +# The shared hook lives in @lasvegasfortransit/cli; repository-specific steps go below it. set -eu ROOT=$(git rev-parse --show-toplevel) -sh "$ROOT/node_modules/@lvbt/cli/hooks/prepare-commit-msg.sh" "$@" +sh "$ROOT/node_modules/@lasvegasfortransit/cli/hooks/prepare-commit-msg.sh" "$@" diff --git a/.lvbt/web-platform/examples/with-vite-react/.github/renovate.json b/.lvbt/web-platform/examples/with-vite-react/.github/renovate.json index d7160ad..293369c 100644 --- a/.lvbt/web-platform/examples/with-vite-react/.github/renovate.json +++ b/.lvbt/web-platform/examples/with-vite-react/.github/renovate.json @@ -11,24 +11,10 @@ "enabled": true, "schedule": ["before 6am on monday"] }, - "customManagers": [ - { - "description": "The @lvbt/* packages install from git tags of repository-tooling with a path: suffix that the npm manager does not parse, and the Claude Code marketplace pins the same tag. Track both as one dependency on the repository's tags.", - "customType": "regex", - "managerFilePatterns": ["/(^|/)package\\.json$/", "/(^|/)\\.claude/settings\\.json$/"], - "matchStrings": [ - "github:LasVegasForTransit/repository-tooling#(?<currentValue>v\\d+\\.\\d+\\.\\d+)&path:/packages/[a-z-]+", - "\"repo\": \"LasVegasForTransit/repository-tooling\",\\s*\"ref\": \"(?<currentValue>v\\d+\\.\\d+\\.\\d+)\"" - ], - "depNameTemplate": "LasVegasForTransit/repository-tooling", - "datasourceTemplate": "github-tags", - "versioningTemplate": "semver" - } - ], "packageRules": [ { "groupName": "LVBT repository standard", - "matchDepNames": ["LasVegasForTransit/repository-tooling"] + "matchPackageNames": ["@lasvegasfortransit/*"] }, { "groupName": "dev dependencies (non-major)", diff --git a/.lvbt/web-platform/examples/with-vite-react/.gitignore b/.lvbt/web-platform/examples/with-vite-react/.gitignore index cff9ebb..39d43f7 100644 --- a/.lvbt/web-platform/examples/with-vite-react/.gitignore +++ b/.lvbt/web-platform/examples/with-vite-react/.gitignore @@ -1,6 +1,10 @@ node_modules/ dist/ coverage/ +test-results/ +playwright-report/ +blob-report/ +**/playwright/.cache/ .turbo/ .wrangler/ .env diff --git a/.lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc b/.lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc index 14459ad..350e5df 100644 --- a/.lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc +++ b/.lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc @@ -19,6 +19,7 @@ "dist", "**/dist", ".turbo", + ".lvbt/web-platform", "docs/superpowers", "CHANGELOG.md", ], diff --git a/.lvbt/web-platform/examples/with-vite-react/.npmrc b/.lvbt/web-platform/examples/with-vite-react/.npmrc new file mode 100644 index 0000000..e520ccd --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.npmrc @@ -0,0 +1 @@ +@lasvegasfortransit:registry=https://npm.pkg.github.com diff --git a/.lvbt/web-platform/examples/with-vite-react/AGENTS.md b/.lvbt/web-platform/examples/with-vite-react/AGENTS.md index a599239..51ae712 100644 --- a/.lvbt/web-platform/examples/with-vite-react/AGENTS.md +++ b/.lvbt/web-platform/examples/with-vite-react/AGENTS.md @@ -25,9 +25,9 @@ authorizes creating an issue or pull request. It carries the organization checkl templates, and the only approved creation helper: ```bash -node node_modules/@lvbt/cli/plugins/lvbt-contributions/scripts/github-create.mjs issue \ +node node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/scripts/github-create.mjs issue \ --type bug|feature --title <title> --body-file <file> -node node_modules/@lvbt/cli/plugins/lvbt-contributions/scripts/github-create.mjs pr \ +node node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/scripts/github-create.mjs pr \ --title <title> --body-file <file> --base main ``` @@ -43,5 +43,5 @@ change crosses boundaries; never invent one for a feature, file, task, or role. ## The repository standard -Lint, format, TypeScript, and test settings extend the `@lvbt/*` packages from +Lint, format, TypeScript, and test settings extend the `@lasvegasfortransit/*` packages from `LasVegasForTransit/repository-tooling`. Change a shared rule there, not here. diff --git a/.lvbt/web-platform/examples/with-vite-react/README.md b/.lvbt/web-platform/examples/with-vite-react/README.md index 4ec3fc9..98140f7 100644 --- a/.lvbt/web-platform/examples/with-vite-react/README.md +++ b/.lvbt/web-platform/examples/with-vite-react/README.md @@ -28,5 +28,5 @@ repository's boundaries. `pnpm run deploy` builds and runs `wrangler deploy` for every app with a wrangler config; `.github/workflows/deploy.yml` does the same on every push to `main`. -Lint, format, TypeScript, and test settings extend the `@lvbt/*` packages from +Lint, format, TypeScript, and test settings extend the `@lasvegasfortransit/*` packages from [`LasVegasForTransit/repository-tooling`](https://github.com/LasVegasForTransit/repository-tooling). diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/eslint.config.js b/.lvbt/web-platform/examples/with-vite-react/apps/app/eslint.config.js index 40b3111..10f3779 100644 --- a/.lvbt/web-platform/examples/with-vite-react/apps/app/eslint.config.js +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/eslint.config.js @@ -1,3 +1,3 @@ -import { config } from '@lvbt/eslint-config/react-internal'; +import { config } from '@lasvegasfortransit/eslint-config/react-internal'; export default config; diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json b/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json index 16c3a41..cf8b1b5 100644 --- a/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json @@ -1,5 +1,5 @@ { - "name": "@lvbt/app", + "name": "@lasvegasfortransit/app", "version": "0.0.0", "private": true, "type": "module", @@ -17,10 +17,10 @@ "react-dom": "catalog:" }, "devDependencies": { - "@lvbt/eslint-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/eslint-config", - "@lvbt/playwright-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/playwright-config", - "@lvbt/typescript-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/typescript-config", - "@lvbt/vitest-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/vitest-config", + "@lasvegasfortransit/eslint-config": "0.3.5", + "@lasvegasfortransit/playwright-config": "0.3.5", + "@lasvegasfortransit/typescript-config": "0.3.5", + "@lasvegasfortransit/vitest-config": "0.3.5", "@playwright/test": "catalog:", "@tailwindcss/vite": "catalog:", "@types/node": "catalog:", diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/playwright.config.ts b/.lvbt/web-platform/examples/with-vite-react/apps/app/playwright.config.ts index c10f111..a4a6fc2 100644 --- a/.lvbt/web-platform/examples/with-vite-react/apps/app/playwright.config.ts +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/playwright.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from '@playwright/test'; -import { sharedConfig } from '@lvbt/playwright-config'; +import { sharedConfig } from '@lasvegasfortransit/playwright-config'; const url = 'http://127.0.0.1:4173'; diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/e2e/app.spec.ts b/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/e2e/app.spec.ts index e98970f..f6f2d85 100644 --- a/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/e2e/app.spec.ts +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/e2e/app.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { expectNoAccessibilityViolations } from '@lvbt/playwright-config/accessibility'; -import { monitorPageHealth } from '@lvbt/playwright-config/page-health'; +import { expectNoAccessibilityViolations } from '@lasvegasfortransit/playwright-config/accessibility'; +import { monitorPageHealth } from '@lasvegasfortransit/playwright-config/page-health'; test('the button counts clicks', async ({ page }) => { const health = monitorPageHealth(page); diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/tsconfig.json b/.lvbt/web-platform/examples/with-vite-react/apps/app/tsconfig.json index 875c263..9a770fe 100644 --- a/.lvbt/web-platform/examples/with-vite-react/apps/app/tsconfig.json +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@lvbt/typescript-config/react-library.json", + "extends": "@lasvegasfortransit/typescript-config/react-library.json", "compilerOptions": { "types": ["vite/client", "node"] }, diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/vitest.config.ts b/.lvbt/web-platform/examples/with-vite-react/apps/app/vitest.config.ts index 2474677..4222831 100644 --- a/.lvbt/web-platform/examples/with-vite-react/apps/app/vitest.config.ts +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/vitest.config.ts @@ -1,5 +1,5 @@ import { defineConfig } from 'vitest/config'; -import { sharedConfig } from '@lvbt/vitest-config'; +import { sharedConfig } from '@lasvegasfortransit/vitest-config'; export default defineConfig({ ...sharedConfig }); diff --git a/.lvbt/web-platform/examples/with-vite-react/docs/development/reference/glossary.md b/.lvbt/web-platform/examples/with-vite-react/docs/development/reference/glossary.md index 6f18f37..39abe60 100644 --- a/.lvbt/web-platform/examples/with-vite-react/docs/development/reference/glossary.md +++ b/.lvbt/web-platform/examples/with-vite-react/docs/development/reference/glossary.md @@ -20,10 +20,10 @@ of every tool for the whole organization. A package writes `"catalog:"` as a ver pinned one. <a id="eslint"></a>**ESLint**: finds likely bugs and enforces code rules. The rules come from -`@lvbt/eslint-config`. +`@lasvegasfortransit/eslint-config`. <a id="prettier"></a>**Prettier**: formats code the same way for everyone, so formatting is never -discussed in review. The settings come from `@lvbt/prettier-config`. +discussed in review. The settings come from `@lasvegasfortransit/prettier-config`. <a id="vitest"></a>**Vitest**: runs unit tests under `tests/`. diff --git a/.lvbt/web-platform/examples/with-vite-react/package.json b/.lvbt/web-platform/examples/with-vite-react/package.json index 41a5519..f33cbcd 100644 --- a/.lvbt/web-platform/examples/with-vite-react/package.json +++ b/.lvbt/web-platform/examples/with-vite-react/package.json @@ -23,8 +23,8 @@ "*": "prettier --write --ignore-unknown" }, "devDependencies": { - "@lvbt/cli": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/cli", - "@lvbt/prettier-config": "github:LasVegasForTransit/repository-tooling#v0.2.8&path:/packages/prettier-config", + "@lasvegasfortransit/cli": "0.3.5", + "@lasvegasfortransit/prettier-config": "0.3.5", "lint-staged": "catalog:", "markdownlint-cli2": "catalog:", "markdownlint-rule-relative-links": "catalog:", diff --git a/.lvbt/web-platform/examples/with-vite-react/prettier.config.js b/.lvbt/web-platform/examples/with-vite-react/prettier.config.js index 9794a17..edf7c71 100644 --- a/.lvbt/web-platform/examples/with-vite-react/prettier.config.js +++ b/.lvbt/web-platform/examples/with-vite-react/prettier.config.js @@ -1,3 +1,3 @@ -import lvbt from '@lvbt/prettier-config'; +import lvbt from '@lasvegasfortransit/prettier-config'; export default lvbt; diff --git a/.lvbt/web-platform/packages/cli/package.json b/.lvbt/web-platform/packages/cli/package.json index f0df639..e14c3c8 100644 --- a/.lvbt/web-platform/packages/cli/package.json +++ b/.lvbt/web-platform/packages/cli/package.json @@ -1,6 +1,6 @@ { - "name": "@lvbt/cli", - "version": "0.2.8", + "name": "@lasvegasfortransit/cli", + "version": "0.3.5", "description": "The lvbt command every LVBT repository runs for bootstrap, preflight, and deploy, plus the shared git hooks and the lvbt-contributions agent plugin.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json index 475f641..7b2144b 100644 --- a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "lvbt-contributions", - "version": "0.2.0", + "version": "0.3.1", "description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.", "author": { "name": "Las Vegans for Better Transit", diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json index 9fa85e5..ee124a1 100644 --- a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "lvbt-contributions", - "version": "0.2.0", + "version": "0.3.1", "description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.", "author": { "name": "Las Vegans for Better Transit", diff --git a/.lvbt/web-platform/packages/cli/src/lib/check/contract.mjs b/.lvbt/web-platform/packages/cli/src/lib/check/contract.mjs index e9c2c65..a59a008 100644 --- a/.lvbt/web-platform/packages/cli/src/lib/check/contract.mjs +++ b/.lvbt/web-platform/packages/cli/src/lib/check/contract.mjs @@ -25,18 +25,22 @@ const IGNORED = new Set([ ]); /** Version specifiers the standard permits besides the catalog. */ -function allowedRange(range) { +function allowedRange(name, range) { return ( range.startsWith('catalog:') || range.startsWith('workspace:') || - range.startsWith('github:LasVegasForTransit/repository-tooling#') || + (/^@lasvegasfortransit\/[a-z0-9-]+$/.test(name) && /^\d+\.\d+\.\d+$/.test(range)) || range.startsWith('link:') ); } function vendoredRange(root, directory, name, range) { - if (!/^@lvbt\/[a-z0-9-]+$/.test(name) || !range.startsWith('file:')) return false; - const expected = path.resolve(root, '.lvbt/web-platform/packages', name.slice('@lvbt/'.length)); + if (!/^@lasvegasfortransit\/[a-z0-9-]+$/.test(name) || !range.startsWith('file:')) return false; + const expected = path.resolve( + root, + '.lvbt/web-platform/packages', + name.slice('@lasvegasfortransit/'.length), + ); if (path.resolve(root, directory, range.slice('file:'.length)) !== expected) return false; try { return JSON.parse(readFileSync(path.join(expected, 'package.json'), 'utf8')).name === name; @@ -130,7 +134,7 @@ function dependencyFailures(root, directory) { const failures = []; for (const field of ['dependencies', 'devDependencies']) { for (const [name, range] of Object.entries(manifest[field] ?? {})) { - if (!allowedRange(range) && !vendoredRange(root, directory, name, range)) { + if (!allowedRange(name, range) && !vendoredRange(root, directory, name, range)) { failures.push( `${directory}/package.json pins "${name}" to "${range}" instead of "catalog:"`, ); diff --git a/.lvbt/web-platform/packages/eslint-config/package.json b/.lvbt/web-platform/packages/eslint-config/package.json index cecb449..0c18d77 100644 --- a/.lvbt/web-platform/packages/eslint-config/package.json +++ b/.lvbt/web-platform/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { - "name": "@lvbt/eslint-config", - "version": "0.2.8", + "name": "@lasvegasfortransit/eslint-config", + "version": "0.3.5", "description": "The ESLint configurations every LVBT repository uses.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/playwright-config/index.js b/.lvbt/web-platform/packages/playwright-config/index.js index bab9e01..6a16a93 100644 --- a/.lvbt/web-platform/packages/playwright-config/index.js +++ b/.lvbt/web-platform/packages/playwright-config/index.js @@ -19,7 +19,7 @@ export const foregroundServerEnvironment = { * Spread it into a package's playwright.config.ts and add the web server: * * import { defineConfig } from '@playwright/test'; - * import { sharedConfig } from '@lvbt/playwright-config'; + * import { sharedConfig } from '@lasvegasfortransit/playwright-config'; * export default defineConfig({ * ...sharedConfig, * webServer: { diff --git a/.lvbt/web-platform/packages/playwright-config/package.json b/.lvbt/web-platform/packages/playwright-config/package.json index 4a64963..bf0cefd 100644 --- a/.lvbt/web-platform/packages/playwright-config/package.json +++ b/.lvbt/web-platform/packages/playwright-config/package.json @@ -1,6 +1,6 @@ { - "name": "@lvbt/playwright-config", - "version": "0.2.8", + "name": "@lasvegasfortransit/playwright-config", + "version": "0.3.5", "description": "The Playwright configuration every LVBT repository spreads into its own: end-to-end tests under tests/e2e, desktop and mobile projects, traces on failure.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/prettier-config/package.json b/.lvbt/web-platform/packages/prettier-config/package.json index e4b7403..5ce39b4 100644 --- a/.lvbt/web-platform/packages/prettier-config/package.json +++ b/.lvbt/web-platform/packages/prettier-config/package.json @@ -1,6 +1,6 @@ { - "name": "@lvbt/prettier-config", - "version": "0.2.8", + "name": "@lasvegasfortransit/prettier-config", + "version": "0.3.5", "description": "The Prettier configuration every LVBT repository uses.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/typescript-config/package.json b/.lvbt/web-platform/packages/typescript-config/package.json index 9798007..7e5592a 100644 --- a/.lvbt/web-platform/packages/typescript-config/package.json +++ b/.lvbt/web-platform/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { - "name": "@lvbt/typescript-config", - "version": "0.2.8", + "name": "@lasvegasfortransit/typescript-config", + "version": "0.3.5", "description": "TypeScript configurations every LVBT repository extends.", "license": "MIT", "repository": { diff --git a/.lvbt/web-platform/packages/vitest-config/index.js b/.lvbt/web-platform/packages/vitest-config/index.js index 13cc7f0..ef6cac9 100644 --- a/.lvbt/web-platform/packages/vitest-config/index.js +++ b/.lvbt/web-platform/packages/vitest-config/index.js @@ -8,7 +8,7 @@ * Spread it into a package's vitest.config.ts: * * import { defineConfig } from 'vitest/config'; - * import { sharedConfig } from '@lvbt/vitest-config'; + * import { sharedConfig } from '@lasvegasfortransit/vitest-config'; * export default defineConfig({ ...sharedConfig }); * * @type {import("vitest/config").UserConfig} diff --git a/.lvbt/web-platform/packages/vitest-config/package.json b/.lvbt/web-platform/packages/vitest-config/package.json index fdafadd..6b85e4a 100644 --- a/.lvbt/web-platform/packages/vitest-config/package.json +++ b/.lvbt/web-platform/packages/vitest-config/package.json @@ -1,6 +1,6 @@ { - "name": "@lvbt/vitest-config", - "version": "0.2.8", + "name": "@lasvegasfortransit/vitest-config", + "version": "0.3.5", "description": "The Vitest configuration every LVBT repository spreads into its own.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/web-platform/README.md b/.lvbt/web-platform/packages/web-platform/README.md index 69937b4..b08dc46 100644 --- a/.lvbt/web-platform/packages/web-platform/README.md +++ b/.lvbt/web-platform/packages/web-platform/README.md @@ -1,7 +1,8 @@ # Web platform operations -`@lvbt/web-platform` provides the provider-neutral operations used to inspect and reconcile LVBT web -infrastructure. Repository packages supply project discovery, naming, routes, and lifecycle policy. +`@lasvegasfortransit/web-platform` provides the provider-neutral operations used to inspect and +reconcile LVBT web infrastructure. Repository packages supply project discovery, naming, routes, and +lifecycle policy. The package reads external state separately from reconciliation. Every resource describes its current and desired values, then `reconcileResources` reports or applies the smallest necessary @@ -9,6 +10,6 @@ change. `reconcileResourceGroups` preserves the same fail-closed planning bounda dependency stages, such as repository creation before ruleset and environment configuration. Doctor checks return structured results without mutating provider state. -Import GitHub operations from `@lvbt/web-platform/github`, Cloudflare operations from -`@lvbt/web-platform/cloudflare`, and the reconciliation contract from -`@lvbt/web-platform/provision`. +Import GitHub operations from `@lasvegasfortransit/web-platform/github`, Cloudflare operations from +`@lasvegasfortransit/web-platform/cloudflare`, and the reconciliation contract from +`@lasvegasfortransit/web-platform/provision`. diff --git a/.lvbt/web-platform/packages/web-platform/package.json b/.lvbt/web-platform/packages/web-platform/package.json index 2d27c41..6fc9c91 100644 --- a/.lvbt/web-platform/packages/web-platform/package.json +++ b/.lvbt/web-platform/packages/web-platform/package.json @@ -1,6 +1,6 @@ { - "name": "@lvbt/web-platform", - "version": "0.2.8", + "name": "@lasvegasfortransit/web-platform", + "version": "0.3.5", "description": "Provider-neutral deployment, provisioning, and validation primitives for LVBT web repositories.", "license": "MIT", "type": "module", @@ -9,6 +9,10 @@ "url": "https://github.com/LasVegasForTransit/repository-tooling.git", "directory": "packages/web-platform" }, + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "restricted" + }, "exports": { ".": "./src/index.ts", "./cloudflare": "./src/cloudflare.ts", diff --git a/.lvbt/web-platform/packages/web-platform/src/doctor-cloudflare.ts b/.lvbt/web-platform/packages/web-platform/src/doctor-cloudflare.ts index 5128321..98d83fb 100644 --- a/.lvbt/web-platform/packages/web-platform/src/doctor-cloudflare.ts +++ b/.lvbt/web-platform/packages/web-platform/src/doctor-cloudflare.ts @@ -6,7 +6,7 @@ export interface CloudflareTarget { zoneId: string; zoneName: string; hostname: string; - workers: { slug: string; name: string }[]; + workers: { slug: string; name: string; previewRequired?: boolean }[]; } export interface CloudflareRead { @@ -71,22 +71,31 @@ function validRoutes(input: unknown, target: CloudflareTarget) { ); } -function validDns(input: unknown, hostname: string) { - return z - .array(z.object({ name: z.string(), type: z.string(), proxied: z.boolean().optional() })) +function validDns(input: unknown, target: CloudflareTarget) { + const domains = z + .array( + z.object({ + hostname: z.string(), + service: z.string(), + zone_id: z.string(), + cert_id: z.string().optional(), + }), + ) .parse(input) - .some( - (record) => - record.name === hostname && - ['A', 'AAAA', 'CNAME'].includes(record.type) && - record.proxied === true, - ); + .filter((domain) => domain.hostname === target.hostname); + return ( + domains.length === 1 && + domains[0]?.service === target.workers.find((worker) => worker.slug === 'home')?.name && + domains[0]?.zone_id === target.zoneId && + (domains[0].cert_id?.length ?? 0) > 0 + ); } function validAnalytics(input: unknown, hostname: string) { const sites = z .array( z.object({ + host: z.string().optional(), site_token: z.string().optional(), rules: z .array( @@ -104,9 +113,10 @@ function validAnalytics(input: unknown, hostname: string) { sites.filter( (site) => site.site_token && - site.rules?.some( - (rule) => rule.host === hostname && rule.inclusive === true && rule.is_paused !== true, - ), + (site.host === hostname || + site.rules?.some( + (rule) => rule.host === hostname && rule.inclusive === true && rule.is_paused !== true, + )), ).length === 1 ); } @@ -114,6 +124,9 @@ function validAnalytics(input: unknown, hostname: string) { export async function cloudflareDoctor(target: CloudflareTarget, read: CloudflareRead) { const account = `accounts/${target.accountId}`; const zone = `zones/${target.zoneId}`; + let domainRecords: Promise<unknown> | undefined; + const domains = () => + (domainRecords ??= read.list(`${account}/workers/domains?hostname=${target.hostname}`)); const check = (id: string, requirement: string, inspect: () => Promise<boolean>) => doctorCheck(`cloudflare.${id}`, requirement, inspect); return [ @@ -123,19 +136,17 @@ export async function cloudflareDoctor(target: CloudflareTarget, read: Cloudflar await check( 'domain', 'The Labs custom domain belongs to the home Worker in the declared zone.', - async () => - validDomain( - await read.list(`${account}/workers/domains?hostname=${target.hostname}`), - target, - ), + async () => validDomain(await domains(), target), ), await check( 'routes', 'Home owns the catchall; every published lab owns its exact and subtree routes without conflicting patterns.', async () => validRoutes(await read.list(`${zone}/workers/routes`), target), ), - await check('dns', 'The hostname has a proxied address or CNAME record.', async () => - validDns(await read.list(`${zone}/dns_records?name=${target.hostname}`), target.hostname), + await check( + 'dns', + 'The Worker custom domain has Cloudflare-managed DNS and a certificate.', + async () => validDns(await domains(), target), ), await check('workers', 'Every published lab has its declared Worker.', async () => { const workers = z @@ -147,12 +158,12 @@ export async function cloudflareDoctor(target: CloudflareTarget, read: Cloudflar }), await check( 'worker-previews', - 'Every published Worker has immutable version preview URLs enabled.', + 'Every Worker requiring immutable version preview URLs has them enabled.', async () => { const settings = await Promise.all( - target.workers.map((worker) => - read.get(`${account}/workers/scripts/${worker.name}/subdomain`), - ), + target.workers + .filter((worker) => worker.previewRequired !== false) + .map((worker) => read.get(`${account}/workers/scripts/${worker.name}/subdomain`)), ); return settings.every( (value) => z.object({ previews_enabled: z.literal(true) }).safeParse(value).success, diff --git a/.lvbt/web-platform/packages/web-platform/src/provision-analytics.ts b/.lvbt/web-platform/packages/web-platform/src/provision-analytics.ts index 487fbba..d6e49bb 100644 --- a/.lvbt/web-platform/packages/web-platform/src/provision-analytics.ts +++ b/.lvbt/web-platform/packages/web-platform/src/provision-analytics.ts @@ -9,6 +9,7 @@ const targetSchema = z.object({ environment: z.string().min(1), }); const siteSchema = z.object({ + host: z.string().optional(), site_tag: z.string().min(1), site_token: z.string().min(1), rules: z @@ -40,11 +41,12 @@ function analyticsToken(input: unknown, hostname: string) { const referenced = z .array(siteSchema) .parse(input) - .filter((site) => site.rules?.some((rule) => rule.host === hostname)); + .filter((site) => site.host === hostname || site.rules?.some((rule) => rule.host === hostname)); if (referenced.length > 1) throw new Error('Duplicate Web Analytics sites require review.'); const site = referenced[0]; if (site === undefined) return null; if ( + site.host !== hostname && !site.rules?.some( (rule) => rule.host === hostname && rule.inclusive === true && rule.is_paused !== true, ) diff --git a/.lvbt/web-platform/packages/web-platform/tests/doctor-cloudflare.test.ts b/.lvbt/web-platform/packages/web-platform/tests/doctor-cloudflare.test.ts index 00103d6..035f7ab 100644 --- a/.lvbt/web-platform/packages/web-platform/tests/doctor-cloudflare.test.ts +++ b/.lvbt/web-platform/packages/web-platform/tests/doctor-cloudflare.test.ts @@ -20,7 +20,12 @@ const fixtures: Record<string, unknown> = { paused: false, }, 'accounts/account/workers/domains?hostname=labs.example.org': [ - { hostname: 'labs.example.org', service: 'lvbt-labs-home', zone_id: 'zone' }, + { + hostname: 'labs.example.org', + service: 'lvbt-labs-home', + zone_id: 'zone', + cert_id: 'issued-certificate', + }, ], 'zones/zone/workers/routes': [ { pattern: 'labs.example.org/*', script: 'lvbt-labs-home' }, @@ -54,6 +59,59 @@ test('checks declared Worker names, exact routes, domain, DNS, and analytics', a expect(JSON.stringify(result)).not.toContain('public-id'); }); +test('recognizes DNS managed by a Worker custom domain without DNS-record access', async () => { + const reader = (endpoint: string) => { + if (endpoint.includes('/dns_records')) + return Promise.reject(new Error('DNS-record listing unavailable')); + return read(endpoint); + }; + + const result = await cloudflareDoctor(target, { get: reader, list: reader }); + expect(result.find((check) => check.id === 'cloudflare.dns')?.status).toBe('pass'); +}); + +test('rejects a Worker custom domain without an issued certificate', async () => { + const reader = (endpoint: string) => { + if (endpoint.includes('/workers/domains')) + return Promise.resolve([ + { hostname: target.hostname, service: 'lvbt-labs-home', zone_id: target.zoneId }, + ]); + return read(endpoint); + }; + + const result = await cloudflareDoctor(target, { get: reader, list: reader }); + expect(result.find((check) => check.id === 'cloudflare.domain')?.status).toBe('pass'); + expect(result.find((check) => check.id === 'cloudflare.dns')?.status).toBe('fail'); +}); + +test('accepts one hostname-created analytics site when rules are omitted', async () => { + const reader = (endpoint: string) => { + if (endpoint.includes('/rum/')) + return Promise.resolve([{ host: target.hostname, site_token: 'public-id' }]); + return read(endpoint); + }; + + const result = await cloudflareDoctor(target, { get: reader, list: reader }); + expect(result.find((check) => check.id === 'cloudflare.analytics')?.status).toBe('pass'); +}); + +test('rejects multiple active analytics sites for the hostname', async () => { + const reader = (endpoint: string) => { + if (endpoint.includes('/rum/')) + return Promise.resolve([ + { host: target.hostname, site_token: 'hostname-site' }, + { + site_token: 'rules-site', + rules: [{ host: target.hostname, inclusive: true, is_paused: false }], + }, + ]); + return read(endpoint); + }; + + const result = await cloudflareDoctor(target, { get: reader, list: reader }); + expect(result.find((check) => check.id === 'cloudflare.analytics')?.status).toBe('fail'); +}); + test('flags route collisions while preserving an unknown analytics result', async () => { const reader = (endpoint: string) => { if (endpoint.includes('/rum/')) return Promise.reject(new Error('No analytics permission')); @@ -76,3 +134,34 @@ test('fails when any published Worker has version previews disabled', async () = const result = await cloudflareDoctor(target, { get: reader, list: reader }); expect(result.find((check) => check.id === 'cloudflare.worker-previews')?.status).toBe('fail'); }); + +test('verifies an external Worker route without requiring version previews', async () => { + const external = { + ...target, + workers: [ + ...target.workers, + { slug: 'transit-mapper', name: 'transitmapper', previewRequired: false }, + ], + }; + const reader = (endpoint: string) => { + if (endpoint.endsWith('/workers/routes')) + return Promise.resolve([ + ...(fixtures['zones/zone/workers/routes'] as object[]), + { pattern: 'labs.example.org/transit-mapper', script: 'transitmapper' }, + { pattern: 'labs.example.org/transit-mapper/*', script: 'transitmapper' }, + ]); + if (endpoint.endsWith('/workers/scripts')) + return Promise.resolve([ + ...(fixtures['accounts/account/workers/scripts'] as object[]), + { id: 'transitmapper' }, + ]); + if (endpoint.endsWith('transitmapper/subdomain')) + return Promise.resolve({ enabled: false, previews_enabled: false }); + return read(endpoint); + }; + + const result = await cloudflareDoctor(external, { get: reader, list: reader }); + expect(result.find((check) => check.id === 'cloudflare.routes')?.status).toBe('pass'); + expect(result.find((check) => check.id === 'cloudflare.workers')?.status).toBe('pass'); + expect(result.find((check) => check.id === 'cloudflare.worker-previews')?.status).toBe('pass'); +}); diff --git a/.lvbt/web-platform/packages/web-platform/tests/provision-analytics.test.ts b/.lvbt/web-platform/packages/web-platform/tests/provision-analytics.test.ts index 2d67dc9..3f27f6a 100644 --- a/.lvbt/web-platform/packages/web-platform/tests/provision-analytics.test.ts +++ b/.lvbt/web-platform/packages/web-platform/tests/provision-analytics.test.ts @@ -82,6 +82,35 @@ test('updates a stale analytics variable without creating another site', async ( expect(variables.get('PUBLIC_LVBT_CWA_TOKEN')).toBe('current-token'); }); +test('recognizes a hostname-created analytics site when rules are omitted', async () => { + const sites = [ + { + host: target.hostname, + site_tag: 'site', + site_token: 'current-token', + }, + ]; + const variables = new Map<string, string>(); + let siteWrites = 0; + const resources = provisionAnalytics(target, { + readSites: () => Promise.resolve(sites), + createSite: () => { + siteWrites += 1; + return Promise.resolve(); + }, + readVariables: () => + Promise.resolve({ variables: [...variables].map(([name, value]) => ({ name, value })) }), + writeVariable: (_method, _endpoint, body) => { + variables.set(body.name, body.value); + return Promise.resolve(); + }, + }); + + expect((await reconcileResources(resources, true)).ok).toBe(true); + expect(siteWrites).toBe(0); + expect(variables.get('PUBLIC_LVBT_CWA_TOKEN')).toBe('current-token'); +}); + test('refuses duplicate or conflicting analytics sites', async () => { for (const sites of [ [ @@ -93,7 +122,7 @@ test('refuses duplicate or conflicting analytics sites', async () => { { site_tag: 'two', site_token: 'two', - rules: [{ host: target.hostname, inclusive: true, is_paused: false }], + host: target.hostname, }, ], [ diff --git a/.lvbt/web-platform/standards/template-publication.ts b/.lvbt/web-platform/standards/template-publication.ts new file mode 100644 index 0000000..b0f9fe5 --- /dev/null +++ b/.lvbt/web-platform/standards/template-publication.ts @@ -0,0 +1,126 @@ +import { execFileSync } from 'node:child_process'; +import { cp, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { parseArgs } from 'node:util'; + +import { applyPreset } from './web-platform.ts'; +import { readRelease } from './web-platform-source.ts'; + +const examples = new Set(['basic', 'with-astro', 'with-vite-react']); +const dependencyFields = [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', +] as const; + +interface Manifest { + scripts?: Record<string, string>; + dependencies?: Record<string, string>; + devDependencies?: Record<string, string>; + optionalDependencies?: Record<string, string>; + peerDependencies?: Record<string, string>; +} + +async function manifestPaths(root: string): Promise<string[]> { + const manifests = [path.join(root, 'package.json')]; + for (const parent of ['apps', 'packages']) { + const directory = path.join(root, parent); + const entries = await readdir(directory, { withFileTypes: true }).catch(() => []); + for (const entry of entries) { + if (entry.isDirectory()) manifests.push(path.join(directory, entry.name, 'package.json')); + } + } + return manifests; +} + +async function rewriteManifest(root: string, file: string): Promise<void> { + const manifest = JSON.parse(await readFile(file, 'utf8')) as Manifest; + for (const field of dependencyFields) { + const dependencies = manifest[field]; + if (!dependencies) continue; + for (const name of Object.keys(dependencies)) { + if (!name.startsWith('@lasvegasfortransit/')) continue; + const packageName = name.slice('@lasvegasfortransit/'.length); + const target = path.join(root, '.lvbt/web-platform/packages', packageName); + const relative = path.relative(path.dirname(file), target).split(path.sep).join('/'); + dependencies[name] = `file:${relative}`; + } + } + if (file === path.join(root, 'package.json')) { + manifest.scripts ??= {}; + manifest.scripts['standards:update'] = + 'node .lvbt/web-platform/standards/web-platform-cli.ts update'; + manifest.scripts['standards:check'] = + 'node .lvbt/web-platform/standards/web-platform-cli.ts check'; + const check = manifest.scripts.check; + if (!check) throw new Error('The template root must define a check script.'); + if (!check.startsWith('pnpm standards:check && ')) { + manifest.scripts.check = `pnpm standards:check && ${check}`; + } + } + await writeFile(file, `${JSON.stringify(manifest, null, 2)}\n`); +} + +export async function materializeTemplate(options: { + source: string; + target: string; + example: string; + release: string; +}): Promise<void> { + const source = path.resolve(options.source); + const target = path.resolve(options.target); + if (!examples.has(options.example)) + throw new Error(`Unknown template example: ${options.example}`); + if (source === target) throw new Error('Source and target directories must differ.'); + + const bundle = readRelease(source, options.release); + const git = (args: string[]) => + execFileSync('git', ['-C', source, ...args], { encoding: 'utf8' }).trim(); + if (git(['rev-parse', 'HEAD']) !== bundle.commit || git(['status', '--porcelain'])) { + throw new Error('Template publication requires a clean checkout of the requested release.'); + } + + const entries = await readdir(target, { withFileTypes: true }).catch(() => []); + await Promise.all( + entries + .filter((entry) => entry.name !== '.git') + .map((entry) => rm(path.join(target, entry.name), { recursive: true, force: true })), + ); + await cp(path.join(source, 'examples', options.example), target, { recursive: true }); + await applyPreset(target, bundle); + for (const file of await manifestPaths(target)) await rewriteManifest(target, file); +} + +export async function main(args: string[]): Promise<void> { + const { values } = parseArgs({ + args, + options: { + source: { type: 'string' }, + target: { type: 'string' }, + example: { type: 'string' }, + release: { type: 'string' }, + }, + }); + if (!values.source || !values.target || !values.example || !values.release) { + throw new Error( + 'Usage: --source <repository> --target <directory> --example <name> --release <tag>', + ); + } + await materializeTemplate({ + source: values.source, + target: values.target, + example: values.example, + release: values.release, + }); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + await main(process.argv.slice(2)); + } catch (error) { + process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); + process.exitCode = 1; + } +} diff --git a/.lvbt/web-platform/standards/web-platform-cli.ts b/.lvbt/web-platform/standards/web-platform-cli.ts index 63f41e7..bbac24e 100644 --- a/.lvbt/web-platform/standards/web-platform-cli.ts +++ b/.lvbt/web-platform/standards/web-platform-cli.ts @@ -1,11 +1,11 @@ import { execFileSync } from 'node:child_process'; -import { mkdtemp, rm } from 'node:fs/promises'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { parseArgs } from 'node:util'; -import { applyPreset, verifyPreset } from './web-platform.ts'; +import { applyPreset, verifyPreset, type WebPreset } from './web-platform.ts'; import { readCommit, readRelease } from './web-platform-source.ts'; const upstream = 'https://github.com/LasVegasForTransit/repository-tooling.git'; @@ -27,13 +27,35 @@ function readSource(repository: string, identity: SourceIdentity) { : readCommit(repository, identity.commit ?? ''); } +/** + * Applies a preset with the updater it carries, so a release's own consumer migrations run in the + * update that installs it rather than in the next one. A preset without an updater uses this one. + */ +async function applyIncoming(root: string, bundle: WebPreset, dryRun: boolean) { + const names = Object.keys(bundle.files).filter((name) => name.startsWith('standards/')); + if (!names.includes('standards/web-platform.ts')) return applyPreset(root, bundle, dryRun); + const directory = await mkdtemp(path.join(os.tmpdir(), 'lvbt-updater-')); + try { + for (const name of names) { + await mkdir(path.dirname(path.join(directory, name)), { recursive: true }); + await writeFile(path.join(directory, name), bundle.files[name] ?? ''); + } + const incoming = (await import( + pathToFileURL(path.join(directory, 'standards/web-platform.ts')).href + )) as { applyPreset: typeof applyPreset }; + return await incoming.applyPreset(root, bundle, dryRun); + } finally { + await rm(directory, { recursive: true, force: true }); + } +} + async function update( root: string, identity: SourceIdentity, source: string | undefined, dryRun: boolean, ) { - if (source) return applyPreset(root, readSource(source, identity), dryRun); + if (source) return applyIncoming(root, readSource(source, identity), dryRun); const directory = await mkdtemp(path.join(os.tmpdir(), 'lvbt-standards-')); try { if (identity.release) { @@ -63,7 +85,7 @@ async function update( { stdio: 'pipe' }, ); } - return await applyPreset(root, readSource(directory, identity), dryRun); + return await applyIncoming(root, readSource(directory, identity), dryRun); } finally { await rm(directory, { recursive: true, force: true }); } diff --git a/.lvbt/web-platform/standards/web-platform-source.ts b/.lvbt/web-platform/standards/web-platform-source.ts index c6dbe26..f993c09 100644 --- a/.lvbt/web-platform/standards/web-platform-source.ts +++ b/.lvbt/web-platform/standards/web-platform-source.ts @@ -35,7 +35,7 @@ function readPreset(repository: string, ref: string, release: string | null): We export function readRelease(repository: string, release: string): WebPreset { if (!/^v\d+\.\d+\.\d+(?:-[a-z0-9.-]+)?$/.test(release)) { - throw new Error('Use an explicit version tag, such as v0.2.8.'); + throw new Error('Use an explicit version tag, such as v0.3.1.'); } return readPreset(repository, `refs/tags/${release}`, release); } diff --git a/.lvbt/web-platform/standards/web-platform.ts b/.lvbt/web-platform/standards/web-platform.ts index 586b567..626e6b5 100644 --- a/.lvbt/web-platform/standards/web-platform.ts +++ b/.lvbt/web-platform/standards/web-platform.ts @@ -145,6 +145,85 @@ export async function applyPreset(root: string, bundle: WebPreset, dryRun = fals .sort() .filter((name) => !(name in bundle.files)), }; + const consumerChanged = [ + ...new Set([ + ...(await migrateLegacyPackageScope(root, dryRun)), + ...(await ignorePlaywrightOutput(root, dryRun)), + ]), + ].sort(); if (!dryRun) await install(root, bundle); - return plan; + return { ...plan, consumerChanged }; +} + +// Playwright writes these beside each app's configuration, such as apps/site/test-results/. +// Every example's .gitignore carries the same rules. A slash inside a pattern anchors it to the +// .gitignore's own directory, so the cache rule needs its leading **/ to reach every app. +export const PLAYWRIGHT_OUTPUT_IGNORES = [ + 'test-results/', + 'playwright-report/', + 'blob-report/', + '**/playwright/.cache/', +]; + +/** Appends the rules a consumer's root .gitignore lacks, leaving its own lines untouched. */ +async function ignorePlaywrightOutput(root: string, dryRun: boolean): Promise<string[]> { + const file = path.join(root, '.gitignore'); + const source = await readFile(file, 'utf8').catch(() => null); + if (source === null) return []; + const present = new Set(source.split(/\r?\n/).map((line) => line.trim())); + const missing = PLAYWRIGHT_OUTPUT_IGNORES.filter((rule) => !present.has(rule)); + if (missing.length === 0) return []; + const separator = source === '' || source.endsWith('\n') ? '' : '\n'; + if (!dryRun) await writeFile(file, `${source}${separator}${missing.join('\n')}\n`); + return ['.gitignore']; +} + +const SKIPPED_DIRECTORIES = new Set([ + '.git', + 'node_modules', + 'dist', + '.turbo', + 'test-results', + 'playwright-report', + 'blob-report', +]); +const LEGACY_PLATFORM_PACKAGES = [ + 'cli', + 'eslint-config', + 'playwright-config', + 'prettier-config', + 'typescript-config', + 'vitest-config', + 'web-platform', +] as const; + +async function consumerFiles(root: string, relative = ''): Promise<string[]> { + const directory = path.join(root, relative); + const files: string[] = []; + for (const entry of await readdir(directory, { withFileTypes: true })) { + if (entry.name === '.lvbt' && relative === '') continue; + if (entry.isDirectory()) { + if (!SKIPPED_DIRECTORIES.has(entry.name)) + files.push(...(await consumerFiles(root, path.join(relative, entry.name)))); + continue; + } + if (entry.isFile()) files.push(path.join(relative, entry.name)); + } + return files; +} + +async function migrateLegacyPackageScope(root: string, dryRun: boolean): Promise<string[]> { + const changed: string[] = []; + for (const relative of await consumerFiles(root)) { + const file = path.join(root, relative); + const source = await readFile(file, 'utf8').catch(() => null); + if (source === null || source.includes('\0')) continue; + let next = source; + for (const name of LEGACY_PLATFORM_PACKAGES) + next = next.replaceAll(`@lvbt/${name}`, `@lasvegasfortransit/${name}`); + if (next === source) continue; + changed.push(relative.split(path.sep).join('/')); + if (!dryRun) await writeFile(file, next); + } + return changed.sort(); } diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..e520ccd --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@lasvegasfortransit:registry=https://npm.pkg.github.com diff --git a/AGENTS.md b/AGENTS.md index a599239..51ae712 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,9 +25,9 @@ authorizes creating an issue or pull request. It carries the organization checkl templates, and the only approved creation helper: ```bash -node node_modules/@lvbt/cli/plugins/lvbt-contributions/scripts/github-create.mjs issue \ +node node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/scripts/github-create.mjs issue \ --type bug|feature --title <title> --body-file <file> -node node_modules/@lvbt/cli/plugins/lvbt-contributions/scripts/github-create.mjs pr \ +node node_modules/@lasvegasfortransit/cli/plugins/lvbt-contributions/scripts/github-create.mjs pr \ --title <title> --body-file <file> --base main ``` @@ -43,5 +43,5 @@ change crosses boundaries; never invent one for a feature, file, task, or role. ## The repository standard -Lint, format, TypeScript, and test settings extend the `@lvbt/*` packages from +Lint, format, TypeScript, and test settings extend the `@lasvegasfortransit/*` packages from `LasVegasForTransit/repository-tooling`. Change a shared rule there, not here. diff --git a/README.md b/README.md index a9f062b..0ff1647 100644 --- a/README.md +++ b/README.md @@ -21,5 +21,5 @@ Then rename the root package and `packages/example`, and replace the scopes in - `apps/` for deployable applications and services - `packages/` for libraries and tools; `packages/example` shows the shape of one -Lint, format, TypeScript, and test settings extend the `@lvbt/*` packages from +Lint, format, TypeScript, and test settings extend the `@lasvegasfortransit/*` packages from [`LasVegasForTransit/repository-tooling`](https://github.com/LasVegasForTransit/repository-tooling). diff --git a/docs/development/reference/glossary.md b/docs/development/reference/glossary.md index 6f18f37..39abe60 100644 --- a/docs/development/reference/glossary.md +++ b/docs/development/reference/glossary.md @@ -20,10 +20,10 @@ of every tool for the whole organization. A package writes `"catalog:"` as a ver pinned one. <a id="eslint"></a>**ESLint**: finds likely bugs and enforces code rules. The rules come from -`@lvbt/eslint-config`. +`@lasvegasfortransit/eslint-config`. <a id="prettier"></a>**Prettier**: formats code the same way for everyone, so formatting is never -discussed in review. The settings come from `@lvbt/prettier-config`. +discussed in review. The settings come from `@lasvegasfortransit/prettier-config`. <a id="vitest"></a>**Vitest**: runs unit tests under `tests/`. diff --git a/package.json b/package.json index 901b853..935cea2 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "*": "prettier --write --ignore-unknown" }, "devDependencies": { - "@lvbt/cli": "file:.lvbt/web-platform/packages/cli", - "@lvbt/prettier-config": "file:.lvbt/web-platform/packages/prettier-config", + "@lasvegasfortransit/cli": "file:.lvbt/web-platform/packages/cli", + "@lasvegasfortransit/prettier-config": "file:.lvbt/web-platform/packages/prettier-config", "lint-staged": "catalog:", "markdownlint-cli2": "catalog:", "markdownlint-rule-relative-links": "catalog:", diff --git a/packages/example/eslint.config.js b/packages/example/eslint.config.js index 50f29f2..fe2b986 100644 --- a/packages/example/eslint.config.js +++ b/packages/example/eslint.config.js @@ -1,3 +1,3 @@ -import { config } from '@lvbt/eslint-config/base'; +import { config } from '@lasvegasfortransit/eslint-config/base'; export default config; diff --git a/packages/example/package.json b/packages/example/package.json index 85f3020..6fac845 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -1,5 +1,5 @@ { - "name": "@lvbt/example", + "name": "@lasvegasfortransit/example", "version": "0.0.0", "private": true, "type": "module", @@ -16,9 +16,9 @@ "test": "vitest run" }, "devDependencies": { - "@lvbt/eslint-config": "file:../../.lvbt/web-platform/packages/eslint-config", - "@lvbt/typescript-config": "file:../../.lvbt/web-platform/packages/typescript-config", - "@lvbt/vitest-config": "file:../../.lvbt/web-platform/packages/vitest-config", + "@lasvegasfortransit/eslint-config": "file:../../.lvbt/web-platform/packages/eslint-config", + "@lasvegasfortransit/typescript-config": "file:../../.lvbt/web-platform/packages/typescript-config", + "@lasvegasfortransit/vitest-config": "file:../../.lvbt/web-platform/packages/vitest-config", "@types/node": "catalog:", "eslint": "catalog:", "typescript": "catalog:", diff --git a/packages/example/tsconfig.json b/packages/example/tsconfig.json index e7ad808..7b03316 100644 --- a/packages/example/tsconfig.json +++ b/packages/example/tsconfig.json @@ -1,4 +1,4 @@ { - "extends": "@lvbt/typescript-config/node.json", + "extends": "@lasvegasfortransit/typescript-config/node.json", "include": ["src", "tests", "vitest.config.ts"] } diff --git a/packages/example/vitest.config.ts b/packages/example/vitest.config.ts index 2474677..4222831 100644 --- a/packages/example/vitest.config.ts +++ b/packages/example/vitest.config.ts @@ -1,5 +1,5 @@ import { defineConfig } from 'vitest/config'; -import { sharedConfig } from '@lvbt/vitest-config'; +import { sharedConfig } from '@lasvegasfortransit/vitest-config'; export default defineConfig({ ...sharedConfig }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a6b2b5..ece4be5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,10 +43,10 @@ importers: .: devDependencies: - '@lvbt/cli': + '@lasvegasfortransit/cli': specifier: file:.lvbt/web-platform/packages/cli version: file:.lvbt/web-platform/packages/cli - '@lvbt/prettier-config': + '@lasvegasfortransit/prettier-config': specifier: file:.lvbt/web-platform/packages/prettier-config version: file:.lvbt/web-platform/packages/prettier-config(prettier@3.9.6) lint-staged: @@ -70,13 +70,13 @@ importers: packages/example: devDependencies: - '@lvbt/eslint-config': + '@lasvegasfortransit/eslint-config': specifier: file:../../.lvbt/web-platform/packages/eslint-config version: file:.lvbt/web-platform/packages/eslint-config(eslint@10.9.1)(turbo@2.10.12)(typescript@6.0.3) - '@lvbt/typescript-config': + '@lasvegasfortransit/typescript-config': specifier: file:../../.lvbt/web-platform/packages/typescript-config version: file:.lvbt/web-platform/packages/typescript-config - '@lvbt/vitest-config': + '@lasvegasfortransit/vitest-config': specifier: file:../../.lvbt/web-platform/packages/vitest-config version: file:.lvbt/web-platform/packages/vitest-config(vitest@4.1.11(@types/node@24.13.3)(vite@8.3.0(@types/node@24.13.3)(yaml@2.9.1))) '@types/node': @@ -242,26 +242,26 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@lvbt/cli@file:.lvbt/web-platform/packages/cli': + '@lasvegasfortransit/cli@file:.lvbt/web-platform/packages/cli': resolution: {directory: .lvbt/web-platform/packages/cli, type: directory} engines: {node: '>=24'} hasBin: true - '@lvbt/eslint-config@file:.lvbt/web-platform/packages/eslint-config': + '@lasvegasfortransit/eslint-config@file:.lvbt/web-platform/packages/eslint-config': resolution: {directory: .lvbt/web-platform/packages/eslint-config, type: directory} peerDependencies: eslint: '>=10' typescript: '>=5.9' - '@lvbt/prettier-config@file:.lvbt/web-platform/packages/prettier-config': + '@lasvegasfortransit/prettier-config@file:.lvbt/web-platform/packages/prettier-config': resolution: {directory: .lvbt/web-platform/packages/prettier-config, type: directory} peerDependencies: prettier: '>=3' - '@lvbt/typescript-config@file:.lvbt/web-platform/packages/typescript-config': + '@lasvegasfortransit/typescript-config@file:.lvbt/web-platform/packages/typescript-config': resolution: {directory: .lvbt/web-platform/packages/typescript-config, type: directory} - '@lvbt/vitest-config@file:.lvbt/web-platform/packages/vitest-config': + '@lasvegasfortransit/vitest-config@file:.lvbt/web-platform/packages/vitest-config': resolution: {directory: .lvbt/web-platform/packages/vitest-config, type: directory} peerDependencies: vitest: '>=4' @@ -646,8 +646,8 @@ packages: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} - electron-to-chromium@1.5.433: - resolution: {integrity: sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==} + electron-to-chromium@1.5.434: + resolution: {integrity: sha512-7EeFW9OLf1NN9NKQP9xdOE/XKqjxv7JDPP6AEAkHli11+Fae1OaLoNuAf13hQv1PPNtuES0pLQSPk2+fS3s8ww==} entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} @@ -822,8 +822,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.9: - resolution: {integrity: sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==} + ignore@7.0.10: + resolution: {integrity: sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==} engines: {node: '>= 4'} imurmurhash@0.1.4: @@ -1564,7 +1564,7 @@ snapshots: dependencies: escape-string-regexp: 4.0.0 eslint: 10.9.1 - ignore: 7.0.9 + ignore: 7.0.10 '@eslint-community/eslint-utils@4.10.1(eslint@10.9.1)': dependencies: @@ -1635,9 +1635,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 - '@lvbt/cli@file:.lvbt/web-platform/packages/cli': {} + '@lasvegasfortransit/cli@file:.lvbt/web-platform/packages/cli': {} - '@lvbt/eslint-config@file:.lvbt/web-platform/packages/eslint-config(eslint@10.9.1)(turbo@2.10.12)(typescript@6.0.3)': + '@lasvegasfortransit/eslint-config@file:.lvbt/web-platform/packages/eslint-config(eslint@10.9.1)(turbo@2.10.12)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-plugin-eslint-comments': 4.7.2(eslint@10.9.1) '@eslint/js': 10.0.1(eslint@10.9.1) @@ -1653,13 +1653,13 @@ snapshots: - supports-color - turbo - '@lvbt/prettier-config@file:.lvbt/web-platform/packages/prettier-config(prettier@3.9.6)': + '@lasvegasfortransit/prettier-config@file:.lvbt/web-platform/packages/prettier-config(prettier@3.9.6)': dependencies: prettier: 3.9.6 - '@lvbt/typescript-config@file:.lvbt/web-platform/packages/typescript-config': {} + '@lasvegasfortransit/typescript-config@file:.lvbt/web-platform/packages/typescript-config': {} - '@lvbt/vitest-config@file:.lvbt/web-platform/packages/vitest-config(vitest@4.1.11(@types/node@24.13.3)(vite@8.3.0(@types/node@24.13.3)(yaml@2.9.1)))': + '@lasvegasfortransit/vitest-config@file:.lvbt/web-platform/packages/vitest-config(vitest@4.1.11(@types/node@24.13.3)(vite@8.3.0(@types/node@24.13.3)(yaml@2.9.1)))': dependencies: vitest: 4.1.11(@types/node@24.13.3)(vite@8.3.0(@types/node@24.13.3)(yaml@2.9.1)) @@ -1782,7 +1782,7 @@ snapshots: '@typescript-eslint/utils': 8.69.0(eslint@10.9.1)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.69.0 eslint: 10.9.1 - ignore: 7.0.9 + ignore: 7.0.10 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 @@ -1940,7 +1940,7 @@ snapshots: dependencies: baseline-browser-mapping: 2.11.25 caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.433 + electron-to-chromium: 1.5.434 node-releases: 2.0.56 update-browserslist-db: 1.3.3(browserslist@4.29.0) @@ -1988,7 +1988,7 @@ snapshots: dotenv@16.0.3: {} - electron-to-chromium@1.5.433: {} + electron-to-chromium@1.5.434: {} entities@4.5.0: {} @@ -2171,7 +2171,7 @@ snapshots: dependencies: '@sindresorhus/merge-streams': 4.0.0 fast-glob: 3.3.3 - ignore: 7.0.9 + ignore: 7.0.10 is-path-inside: 4.0.0 slash: 5.1.0 unicorn-magic: 0.4.0 @@ -2184,7 +2184,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.9: {} + ignore@7.0.10: {} imurmurhash@0.1.4: {} diff --git a/prettier.config.js b/prettier.config.js index 9b4c309..ad51682 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,3 +1,3 @@ -import config from '@lvbt/prettier-config'; +import config from '@lasvegasfortransit/prettier-config'; export default config;