From c2fbeb48e47e5e1b95b6b3520833db471c348592 Mon Sep 17 00:00:00 2001 From: Willie Chalmers III Date: Sat, 19 Sep 2026 21:02:27 -0700 Subject: [PATCH] chore(dx): Vendor the released repository standard Co-Authored-By: Codex --- .claude/settings.json | 2 +- .lvbt/web-platform.json | 22 ++ .lvbt/web-platform/LICENSE | 21 ++ .../.agents/plugins/marketplace.json | 20 ++ .../examples/with-astro/.claude/settings.json | 38 +++ .../examples/with-astro/.codex/hooks.json | 16 ++ .../examples/with-astro/.editorconfig | 12 + .../examples/with-astro/.githooks/commit-msg | 5 + .../examples/with-astro/.githooks/pre-commit | 5 + .../examples/with-astro/.githooks/pre-push | 5 + .../with-astro/.githooks/prepare-commit-msg | 5 + .../examples/with-astro/.github/CODEOWNERS | 3 + .../actions/setup-node-pnpm/action.yml | 20 ++ .../examples/with-astro/.github/renovate.json | 44 ++++ .../with-astro/.github/workflows/ci.yml | 45 ++++ .../with-astro/.github/workflows/deploy.yml | 42 ++++ .../examples/with-astro/.gitignore | 12 + .../examples/with-astro/.gitleaks.toml | 11 + .../with-astro/.lvbt/commit-scopes.txt | 6 + .../with-astro/.markdownlint-cli2.jsonc | 25 ++ .../examples/with-astro/.prettierignore | 5 + .../examples/with-astro/AGENTS.md | 47 ++++ .../examples/with-astro/README.md | 32 +++ .../with-astro/apps/site/astro.config.ts | 13 + .../with-astro/apps/site/eslint.config.js | 3 + .../with-astro/apps/site/package.json | 35 +++ .../with-astro/apps/site/playwright.config.ts | 16 ++ .../with-astro/apps/site/public/robots.txt | 2 + .../apps/site/src/layouts/Base.astro | 22 ++ .../with-astro/apps/site/src/lib/title.ts | 6 + .../with-astro/apps/site/src/pages/404.astro | 10 + .../apps/site/src/pages/index.astro | 13 + .../apps/site/src/styles/global.css | 1 + .../apps/site/tests/e2e/home.spec.ts | 19 ++ .../with-astro/apps/site/tests/title.test.ts | 13 + .../with-astro/apps/site/tsconfig.json | 5 + .../with-astro/apps/site/vitest.config.ts | 5 + .../with-astro/apps/site/wrangler.jsonc | 14 ++ .../examples/with-astro/docs/README.md | 14 ++ .../docs/development/reference/glossary.md | 51 ++++ .../docs/development/tutorials/start-here.md | 81 ++++++ .../examples/with-astro/package.json | 41 +++ .../examples/with-astro/packages/.gitkeep | 0 .../examples/with-astro/pnpm-workspace.yaml | 65 +++++ .../examples/with-astro/prettier.config.js | 7 + .../examples/with-astro/turbo.json | 38 +++ .../.agents/plugins/marketplace.json | 20 ++ .../with-vite-react/.claude/settings.json | 38 +++ .../with-vite-react/.codex/hooks.json | 16 ++ .../examples/with-vite-react/.editorconfig | 12 + .../with-vite-react/.githooks/commit-msg | 5 + .../with-vite-react/.githooks/pre-commit | 5 + .../with-vite-react/.githooks/pre-push | 5 + .../.githooks/prepare-commit-msg | 5 + .../with-vite-react/.github/CODEOWNERS | 3 + .../actions/setup-node-pnpm/action.yml | 20 ++ .../with-vite-react/.github/renovate.json | 44 ++++ .../with-vite-react/.github/workflows/ci.yml | 45 ++++ .../.github/workflows/deploy.yml | 42 ++++ .../examples/with-vite-react/.gitignore | 11 + .../examples/with-vite-react/.gitleaks.toml | 11 + .../with-vite-react/.lvbt/commit-scopes.txt | 6 + .../with-vite-react/.markdownlint-cli2.jsonc | 25 ++ .../examples/with-vite-react/.prettierignore | 5 + .../examples/with-vite-react/AGENTS.md | 47 ++++ .../examples/with-vite-react/README.md | 32 +++ .../with-vite-react/apps/app/eslint.config.js | 3 + .../with-vite-react/apps/app/index.html | 12 + .../with-vite-react/apps/app/package.json | 37 +++ .../apps/app/playwright.config.ts | 11 + .../apps/app/public/robots.txt | 2 + .../with-vite-react/apps/app/src/App.tsx | 26 ++ .../apps/app/src/lib/counter.ts | 4 + .../with-vite-react/apps/app/src/main.tsx | 14 ++ .../apps/app/src/styles/global.css | 1 + .../apps/app/tests/counter.test.ts | 13 + .../apps/app/tests/e2e/app.spec.ts | 14 ++ .../with-vite-react/apps/app/tsconfig.json | 7 + .../with-vite-react/apps/app/vite.config.ts | 7 + .../with-vite-react/apps/app/vitest.config.ts | 5 + .../with-vite-react/apps/app/wrangler.jsonc | 14 ++ .../examples/with-vite-react/docs/README.md | 14 ++ .../docs/development/reference/glossary.md | 51 ++++ .../docs/development/tutorials/start-here.md | 81 ++++++ .../examples/with-vite-react/package.json | 40 +++ .../with-vite-react/packages/.gitkeep | 0 .../with-vite-react/pnpm-workspace.yaml | 65 +++++ .../with-vite-react/prettier.config.js | 3 + .../examples/with-vite-react/turbo.json | 38 +++ .lvbt/web-platform/packages/cli/catalog.json | 44 ++++ .../packages/cli/hooks/commit-msg.sh | 16 ++ .../packages/cli/hooks/pre-commit.sh | 37 +++ .../packages/cli/hooks/pre-push.sh | 13 + .../packages/cli/hooks/prepare-commit-msg.sh | 80 ++++++ .lvbt/web-platform/packages/cli/package.json | 28 +++ .../.claude-plugin/plugin.json | 12 + .../.codex-plugin/plugin.json | 22 ++ .../hooks/block-direct-create.mjs | 43 ++++ .../hooks/claude-pre-tool-use.mjs | 3 + .../lvbt-contributions/hooks/codex-hooks.json | 16 ++ .../hooks/codex-pre-tool-use.mjs | 3 + .../lvbt-contributions/hooks/hooks.json | 14 ++ .../scripts/github-create.mjs | 182 ++++++++++++++ .../scripts/validate-commit-message.mjs | 141 +++++++++++ .../scripts/validate-commit-subject.mjs | 105 ++++++++ .../skills/github-contribution/SKILL.md | 44 ++++ .../skills/github-contribution/assets/bug.md | 15 ++ .../github-contribution/assets/feature.md | 11 + .../assets/pull-request.md | 5 + .../standards/commit-types.txt | 12 + .lvbt/web-platform/packages/cli/src/cli.mjs | 49 ++++ .../packages/cli/src/lib/arguments.mjs | 35 +++ .../packages/cli/src/lib/check/contract.mjs | 155 ++++++++++++ .../packages/cli/src/lib/check/debt.mjs | 183 ++++++++++++++ .../packages/cli/src/lib/check/filenames.mjs | 90 +++++++ .../packages/cli/src/lib/check/index.mjs | 37 +++ .../packages/cli/src/lib/files.mjs | 16 ++ .../packages/cli/src/lib/operate.mjs | 233 ++++++++++++++++++ .../packages/eslint-config/base.js | 162 ++++++++++++ .../packages/eslint-config/browser.js | 22 ++ .../packages/eslint-config/package.json | 40 +++ .../packages/eslint-config/react-internal.js | 28 +++ .../playwright-config/accessibility.d.ts | 4 + .../playwright-config/accessibility.js | 19 ++ .../packages/playwright-config/index.d.ts | 7 + .../packages/playwright-config/index.js | 53 ++++ .../packages/playwright-config/package.json | 45 ++++ .../playwright-config/page-health.d.ts | 8 + .../packages/playwright-config/page-health.js | 31 +++ .../packages/prettier-config/index.js | 15 ++ .../packages/prettier-config/package.json | 25 ++ .../packages/typescript-config/astro.json | 12 + .../packages/typescript-config/base.json | 26 ++ .../packages/typescript-config/browser.json | 8 + .../packages/typescript-config/node.json | 7 + .../packages/typescript-config/package.json | 18 ++ .../typescript-config/react-library.json | 7 + .../packages/typescript-config/worker.json | 8 + .../packages/vitest-config/index.d.ts | 12 + .../packages/vitest-config/index.js | 22 ++ .../packages/vitest-config/package.json | 29 +++ .../packages/web-platform/README.md | 14 ++ .../packages/web-platform/package.json | 34 +++ .../web-platform/src/cloudflare-read.ts | 87 +++++++ .../web-platform/src/cloudflare-release.ts | 54 ++++ .../packages/web-platform/src/cloudflare.ts | 12 + .../web-platform/src/deployment-checkout.ts | 22 ++ .../packages/web-platform/src/doctor-check.ts | 17 ++ .../web-platform/src/doctor-cloudflare.ts | 166 +++++++++++++ .../web-platform/src/doctor-github.ts | 160 ++++++++++++ .../packages/web-platform/src/github-read.ts | 35 +++ .../packages/web-platform/src/github.ts | 12 + .../packages/web-platform/src/index.ts | 4 + .../web-platform/src/pr-preview-config.ts | 107 ++++++++ .../web-platform/src/pr-preview-upload.ts | 178 +++++++++++++ .../packages/web-platform/src/pr-preview.ts | 67 +++++ .../web-platform/src/provision-analytics.ts | 103 ++++++++ .../web-platform/src/provision-domain.ts | 53 ++++ .../web-platform/src/provision-environment.ts | 108 ++++++++ .../web-platform/src/provision-reconcile.ts | 102 ++++++++ .../web-platform/src/provision-repository.ts | 65 +++++ .../web-platform/src/provision-routes.ts | 69 ++++++ .../web-platform/src/provision-ruleset.ts | 69 ++++++ .../web-platform/src/provision-secret.ts | 29 +++ .../web-platform/src/provision-variables.ts | 97 ++++++++ .../web-platform/src/provision-worker.ts | 52 ++++ .../packages/web-platform/src/provision.ts | 11 + .../web-platform/src/release-artifact.ts | 80 ++++++ .../packages/web-platform/src/release.ts | 11 + .../packages/web-platform/src/ruleset.ts | 40 +++ .../tests/cloudflare-read.test.ts | 60 +++++ .../tests/cloudflare-release.test.ts | 73 ++++++ .../tests/doctor-cloudflare.test.ts | 78 ++++++ .../web-platform/tests/doctor-github.test.ts | 111 +++++++++ .../tests/pr-preview-config.test.ts | 132 ++++++++++ .../tests/pr-preview-upload.test.ts | 206 ++++++++++++++++ .../web-platform/tests/pr-preview.test.ts | 73 ++++++ .../tests/provision-analytics.test.ts | 123 +++++++++ .../tests/provision-domain.test.ts | 63 +++++ .../tests/provision-environment.test.ts | 141 +++++++++++ .../tests/provision-reconcile.test.ts | 116 +++++++++ .../tests/provision-repository.test.ts | 64 +++++ .../tests/provision-routes.test.ts | 62 +++++ .../tests/provision-ruleset.test.ts | 83 +++++++ .../tests/provision-secret.test.ts | 27 ++ .../tests/provision-variables.test.ts | 63 +++++ .../tests/provision-worker.test.ts | 65 +++++ .../tests/release-artifact.test.ts | 69 ++++++ .../packages/web-platform/tsconfig.json | 7 + .../web-platform/standards/repositories.json | 46 ++++ .lvbt/web-platform/standards/ruleset.json | 41 +++ .lvbt/web-platform/standards/tsconfig.json | 7 + .../standards/web-platform-cli.ts | 115 +++++++++ .../standards/web-platform-source.ts | 48 ++++ .lvbt/web-platform/standards/web-platform.ts | 150 +++++++++++ .markdownlint-cli2.jsonc | 2 + apps/collector/package.json | 4 +- docs/README.md | 1 + .../how-to/update-the-repository-standard.md | 30 +++ package.json | 8 +- packages/analytics/package.json | 4 +- pnpm-lock.yaml | 60 +++-- tools/report/package.json | 4 +- 203 files changed, 7980 insertions(+), 42 deletions(-) create mode 100644 .lvbt/web-platform.json create mode 100644 .lvbt/web-platform/LICENSE create mode 100644 .lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json create mode 100644 .lvbt/web-platform/examples/with-astro/.claude/settings.json create mode 100644 .lvbt/web-platform/examples/with-astro/.codex/hooks.json create mode 100644 .lvbt/web-platform/examples/with-astro/.editorconfig create mode 100755 .lvbt/web-platform/examples/with-astro/.githooks/commit-msg create mode 100755 .lvbt/web-platform/examples/with-astro/.githooks/pre-commit create mode 100755 .lvbt/web-platform/examples/with-astro/.githooks/pre-push create mode 100755 .lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg create mode 100644 .lvbt/web-platform/examples/with-astro/.github/CODEOWNERS create mode 100644 .lvbt/web-platform/examples/with-astro/.github/actions/setup-node-pnpm/action.yml create mode 100644 .lvbt/web-platform/examples/with-astro/.github/renovate.json create mode 100644 .lvbt/web-platform/examples/with-astro/.github/workflows/ci.yml create mode 100644 .lvbt/web-platform/examples/with-astro/.github/workflows/deploy.yml create mode 100644 .lvbt/web-platform/examples/with-astro/.gitignore create mode 100644 .lvbt/web-platform/examples/with-astro/.gitleaks.toml create mode 100644 .lvbt/web-platform/examples/with-astro/.lvbt/commit-scopes.txt create mode 100644 .lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc create mode 100644 .lvbt/web-platform/examples/with-astro/.prettierignore create mode 100644 .lvbt/web-platform/examples/with-astro/AGENTS.md create mode 100644 .lvbt/web-platform/examples/with-astro/README.md create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/astro.config.ts create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/eslint.config.js create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/package.json create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/playwright.config.ts create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/public/robots.txt create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/src/layouts/Base.astro create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/src/lib/title.ts create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/src/pages/404.astro create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/src/pages/index.astro create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/src/styles/global.css create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/tests/e2e/home.spec.ts create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/tests/title.test.ts create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/vitest.config.ts create mode 100644 .lvbt/web-platform/examples/with-astro/apps/site/wrangler.jsonc create mode 100644 .lvbt/web-platform/examples/with-astro/docs/README.md create mode 100644 .lvbt/web-platform/examples/with-astro/docs/development/reference/glossary.md create mode 100644 .lvbt/web-platform/examples/with-astro/docs/development/tutorials/start-here.md create mode 100644 .lvbt/web-platform/examples/with-astro/package.json create mode 100644 .lvbt/web-platform/examples/with-astro/packages/.gitkeep create mode 100644 .lvbt/web-platform/examples/with-astro/pnpm-workspace.yaml create mode 100644 .lvbt/web-platform/examples/with-astro/prettier.config.js create mode 100644 .lvbt/web-platform/examples/with-astro/turbo.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/.agents/plugins/marketplace.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/.claude/settings.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/.codex/hooks.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/.editorconfig create mode 100755 .lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg create mode 100755 .lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit create mode 100755 .lvbt/web-platform/examples/with-vite-react/.githooks/pre-push create mode 100755 .lvbt/web-platform/examples/with-vite-react/.githooks/prepare-commit-msg create mode 100644 .lvbt/web-platform/examples/with-vite-react/.github/CODEOWNERS create mode 100644 .lvbt/web-platform/examples/with-vite-react/.github/actions/setup-node-pnpm/action.yml create mode 100644 .lvbt/web-platform/examples/with-vite-react/.github/renovate.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/.github/workflows/ci.yml create mode 100644 .lvbt/web-platform/examples/with-vite-react/.github/workflows/deploy.yml create mode 100644 .lvbt/web-platform/examples/with-vite-react/.gitignore create mode 100644 .lvbt/web-platform/examples/with-vite-react/.gitleaks.toml create mode 100644 .lvbt/web-platform/examples/with-vite-react/.lvbt/commit-scopes.txt create mode 100644 .lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc create mode 100644 .lvbt/web-platform/examples/with-vite-react/.prettierignore create mode 100644 .lvbt/web-platform/examples/with-vite-react/AGENTS.md create mode 100644 .lvbt/web-platform/examples/with-vite-react/README.md create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/eslint.config.js create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/index.html create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/package.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/playwright.config.ts create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/public/robots.txt create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/src/App.tsx create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/src/lib/counter.ts create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/src/main.tsx create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/src/styles/global.css create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/tests/counter.test.ts create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/tests/e2e/app.spec.ts create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/tsconfig.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/vite.config.ts create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/vitest.config.ts create mode 100644 .lvbt/web-platform/examples/with-vite-react/apps/app/wrangler.jsonc create mode 100644 .lvbt/web-platform/examples/with-vite-react/docs/README.md create mode 100644 .lvbt/web-platform/examples/with-vite-react/docs/development/reference/glossary.md create mode 100644 .lvbt/web-platform/examples/with-vite-react/docs/development/tutorials/start-here.md create mode 100644 .lvbt/web-platform/examples/with-vite-react/package.json create mode 100644 .lvbt/web-platform/examples/with-vite-react/packages/.gitkeep create mode 100644 .lvbt/web-platform/examples/with-vite-react/pnpm-workspace.yaml create mode 100644 .lvbt/web-platform/examples/with-vite-react/prettier.config.js create mode 100644 .lvbt/web-platform/examples/with-vite-react/turbo.json create mode 100644 .lvbt/web-platform/packages/cli/catalog.json create mode 100755 .lvbt/web-platform/packages/cli/hooks/commit-msg.sh create mode 100755 .lvbt/web-platform/packages/cli/hooks/pre-commit.sh create mode 100755 .lvbt/web-platform/packages/cli/hooks/pre-push.sh create mode 100755 .lvbt/web-platform/packages/cli/hooks/prepare-commit-msg.sh create mode 100644 .lvbt/web-platform/packages/cli/package.json create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/block-direct-create.mjs create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/claude-pre-tool-use.mjs create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-hooks.json create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/hooks.json create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/scripts/github-create.mjs create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/scripts/validate-commit-message.mjs create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/scripts/validate-commit-subject.mjs create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/skills/github-contribution/SKILL.md create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/skills/github-contribution/assets/bug.md create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/skills/github-contribution/assets/feature.md create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/skills/github-contribution/assets/pull-request.md create mode 100644 .lvbt/web-platform/packages/cli/plugins/lvbt-contributions/standards/commit-types.txt create mode 100755 .lvbt/web-platform/packages/cli/src/cli.mjs create mode 100644 .lvbt/web-platform/packages/cli/src/lib/arguments.mjs create mode 100644 .lvbt/web-platform/packages/cli/src/lib/check/contract.mjs create mode 100644 .lvbt/web-platform/packages/cli/src/lib/check/debt.mjs create mode 100644 .lvbt/web-platform/packages/cli/src/lib/check/filenames.mjs create mode 100644 .lvbt/web-platform/packages/cli/src/lib/check/index.mjs create mode 100644 .lvbt/web-platform/packages/cli/src/lib/files.mjs create mode 100644 .lvbt/web-platform/packages/cli/src/lib/operate.mjs create mode 100644 .lvbt/web-platform/packages/eslint-config/base.js create mode 100644 .lvbt/web-platform/packages/eslint-config/browser.js create mode 100644 .lvbt/web-platform/packages/eslint-config/package.json create mode 100644 .lvbt/web-platform/packages/eslint-config/react-internal.js create mode 100644 .lvbt/web-platform/packages/playwright-config/accessibility.d.ts create mode 100644 .lvbt/web-platform/packages/playwright-config/accessibility.js create mode 100644 .lvbt/web-platform/packages/playwright-config/index.d.ts create mode 100644 .lvbt/web-platform/packages/playwright-config/index.js create mode 100644 .lvbt/web-platform/packages/playwright-config/package.json create mode 100644 .lvbt/web-platform/packages/playwright-config/page-health.d.ts create mode 100644 .lvbt/web-platform/packages/playwright-config/page-health.js create mode 100644 .lvbt/web-platform/packages/prettier-config/index.js create mode 100644 .lvbt/web-platform/packages/prettier-config/package.json create mode 100644 .lvbt/web-platform/packages/typescript-config/astro.json create mode 100644 .lvbt/web-platform/packages/typescript-config/base.json create mode 100644 .lvbt/web-platform/packages/typescript-config/browser.json create mode 100644 .lvbt/web-platform/packages/typescript-config/node.json create mode 100644 .lvbt/web-platform/packages/typescript-config/package.json create mode 100644 .lvbt/web-platform/packages/typescript-config/react-library.json create mode 100644 .lvbt/web-platform/packages/typescript-config/worker.json create mode 100644 .lvbt/web-platform/packages/vitest-config/index.d.ts create mode 100644 .lvbt/web-platform/packages/vitest-config/index.js create mode 100644 .lvbt/web-platform/packages/vitest-config/package.json create mode 100644 .lvbt/web-platform/packages/web-platform/README.md create mode 100644 .lvbt/web-platform/packages/web-platform/package.json create mode 100644 .lvbt/web-platform/packages/web-platform/src/cloudflare-read.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/cloudflare-release.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/cloudflare.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/deployment-checkout.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/doctor-check.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/doctor-cloudflare.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/doctor-github.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/github-read.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/github.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/index.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/pr-preview-config.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/pr-preview-upload.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/pr-preview.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-analytics.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-domain.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-environment.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-reconcile.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-repository.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-routes.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-ruleset.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-secret.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-variables.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision-worker.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/provision.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/release-artifact.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/release.ts create mode 100644 .lvbt/web-platform/packages/web-platform/src/ruleset.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/cloudflare-read.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/cloudflare-release.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/doctor-cloudflare.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/doctor-github.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/pr-preview-config.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/pr-preview-upload.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/pr-preview.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-analytics.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-domain.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-environment.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-reconcile.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-repository.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-routes.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-ruleset.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-secret.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-variables.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/provision-worker.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tests/release-artifact.test.ts create mode 100644 .lvbt/web-platform/packages/web-platform/tsconfig.json create mode 100644 .lvbt/web-platform/standards/repositories.json create mode 100644 .lvbt/web-platform/standards/ruleset.json create mode 100644 .lvbt/web-platform/standards/tsconfig.json create mode 100644 .lvbt/web-platform/standards/web-platform-cli.ts create mode 100644 .lvbt/web-platform/standards/web-platform-source.ts create mode 100644 .lvbt/web-platform/standards/web-platform.ts create mode 100644 docs/development/how-to/update-the-repository-standard.md diff --git a/.claude/settings.json b/.claude/settings.json index d41a309..d1c0e32 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -4,7 +4,7 @@ "source": { "source": "github", "repo": "LasVegasForTransit/repository-tooling", - "ref": "v0.2.7" + "ref": "v0.2.8" } } }, diff --git a/.lvbt/web-platform.json b/.lvbt/web-platform.json new file mode 100644 index 0000000..e124081 --- /dev/null +++ b/.lvbt/web-platform.json @@ -0,0 +1,22 @@ +{ + "formatVersion": 1, + "preset": "lvbt-web", + "release": "v0.2.8", + "commit": "8c33f0f2855fb1ce919df0c076d866392b83946e", + "contentHash": "f649fa8382fb42b4c8aafb4681406f75f3baf4e24f97ccb78eb5ce2198f74996", + "executables": [ + "examples/with-astro/.githooks/commit-msg", + "examples/with-astro/.githooks/pre-commit", + "examples/with-astro/.githooks/pre-push", + "examples/with-astro/.githooks/prepare-commit-msg", + "examples/with-vite-react/.githooks/commit-msg", + "examples/with-vite-react/.githooks/pre-commit", + "examples/with-vite-react/.githooks/pre-push", + "examples/with-vite-react/.githooks/prepare-commit-msg", + "packages/cli/hooks/commit-msg.sh", + "packages/cli/hooks/pre-commit.sh", + "packages/cli/hooks/pre-push.sh", + "packages/cli/hooks/prepare-commit-msg.sh", + "packages/cli/src/cli.mjs" + ] +} diff --git a/.lvbt/web-platform/LICENSE b/.lvbt/web-platform/LICENSE new file mode 100644 index 0000000..4149c6b --- /dev/null +++ b/.lvbt/web-platform/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Las Vegans for Better Transit + +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/.lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json b/.lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json new file mode 100644 index 0000000..3109e3f --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "lvbt", + "interface": { + "displayName": "Las Vegans for Better Transit" + }, + "plugins": [ + { + "name": "lvbt-contributions", + "source": { + "source": "local", + "path": "../../node_modules/@lvbt/cli/plugins/lvbt-contributions" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} diff --git a/.lvbt/web-platform/examples/with-astro/.claude/settings.json b/.lvbt/web-platform/examples/with-astro/.claude/settings.json new file mode 100644 index 0000000..d1c0e32 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.claude/settings.json @@ -0,0 +1,38 @@ +{ + "extraKnownMarketplaces": { + "lvbt": { + "source": { + "source": "github", + "repo": "LasVegasForTransit/repository-tooling", + "ref": "v0.2.8" + } + } + }, + "enabledPlugins": { + "lvbt-contributions@lvbt": true + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "pnpm exec prettier --write --ignore-unknown \"$CLAUDE_FILE_PATHS\" 2>/dev/null || true" + } + ] + } + ] + }, + "permissions": { + "deny": [ + "Read(**/.dev.vars)", + "Read(**/.dev.vars.*)", + "Read(**/.env)", + "Read(**/.env.*)", + "Read(**/*.pem)", + "Read(**/*.key)", + "Read(**/id_rsa*)" + ] + } +} diff --git a/.lvbt/web-platform/examples/with-astro/.codex/hooks.json b/.lvbt/web-platform/examples/with-astro/.codex/hooks.json new file mode 100644 index 0000000..e6c5136 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.codex/hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash|mcp__github__create_issue|mcp__github__create_pull_request", + "hooks": [ + { + "type": "command", + "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lvbt/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", + "timeout": 10 + } + ] + } + ] + } +} diff --git a/.lvbt/web-platform/examples/with-astro/.editorconfig b/.lvbt/web-platform/examples/with-astro/.editorconfig new file mode 100644 index 0000000..1014ba7 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/commit-msg b/.lvbt/web-platform/examples/with-astro/.githooks/commit-msg new file mode 100755 index 0000000..f16cc88 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.githooks/commit-msg @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/pre-commit b/.lvbt/web-platform/examples/with-astro/.githooks/pre-commit new file mode 100755 index 0000000..1310c52 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.githooks/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/pre-push b/.lvbt/web-platform/examples/with-astro/.githooks/pre-push new file mode 100755 index 0000000..76aa3a8 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.githooks/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg b/.lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg new file mode 100755 index 0000000..464132c --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.githooks/prepare-commit-msg @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" diff --git a/.lvbt/web-platform/examples/with-astro/.github/CODEOWNERS b/.lvbt/web-platform/examples/with-astro/.github/CODEOWNERS new file mode 100644 index 0000000..176be06 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Every change is reviewed by the maintainers team. Add narrower entries below +# for paths that need a specific reviewer, for example database migrations. +* @LasVegasForTransit/maintainers diff --git a/.lvbt/web-platform/examples/with-astro/.github/actions/setup-node-pnpm/action.yml b/.lvbt/web-platform/examples/with-astro/.github/actions/setup-node-pnpm/action.yml new file mode 100644 index 0000000..82b7cb4 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.github/actions/setup-node-pnpm/action.yml @@ -0,0 +1,20 @@ +name: Setup Node + pnpm +description: Install the repository's pinned pnpm, cache its store, and install from the lockfile. + +runs: + using: composite + steps: + # packageManager in package.json is the one version source; pnpm/action-setup + # reads it when no version input is supplied. + - name: Setup pnpm + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 + + - name: Setup Node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: package.json + cache: pnpm + + - name: Install dependencies + shell: bash + run: pnpm install --frozen-lockfile diff --git a/.lvbt/web-platform/examples/with-astro/.github/renovate.json b/.lvbt/web-platform/examples/with-astro/.github/renovate.json new file mode 100644 index 0000000..d7160ad --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.github/renovate.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", ":semanticCommits"], + "schedule": ["before 6am on monday"], + "timezone": "America/Los_Angeles", + "labels": ["dependencies"], + "prHourlyLimit": 4, + "prConcurrentLimit": 8, + "rangeStrategy": "pin", + "lockFileMaintenance": { + "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"] + }, + { + "groupName": "dev dependencies (non-major)", + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["minor", "patch"] + }, + { + "matchUpdateTypes": ["patch"], + "automerge": true, + "automergeType": "branch" + } + ] +} diff --git a/.lvbt/web-platform/examples/with-astro/.github/workflows/ci.yml b/.lvbt/web-platform/examples/with-astro/.github/workflows/ci.yml new file mode 100644 index 0000000..ca7a0cc --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +# The organization ruleset requires one status check named "Validate" on every +# pull request. Add steps to this job rather than renaming it. +name: CI + +on: + pull_request: + branches: [main] + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + # The secret scan reads history, not only the proposed tree. + fetch-depth: 0 + + - name: Setup Node + pnpm + uses: ./.github/actions/setup-node-pnpm + + - name: Check + run: pnpm check + + - name: Dependency audit + run: pnpm audit --audit-level=high + + - name: Secret scan + run: | + set -euo pipefail + docker run --rm -v "$PWD:/repo" -w /repo \ + ghcr.io/gitleaks/gitleaks@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f \ + git --redact --no-banner --config /repo/.gitleaks.toml diff --git a/.lvbt/web-platform/examples/with-astro/.github/workflows/deploy.yml b/.lvbt/web-platform/examples/with-astro/.github/workflows/deploy.yml new file mode 100644 index 0000000..97d169c --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.github/workflows/deploy.yml @@ -0,0 +1,42 @@ +# Deploys every app with a wrangler config on each push to main, after the +# same check pull requests run. Needs the CLOUDFLARE_API_TOKEN (Workers Scripts +# edit) and CLOUDFLARE_ACCOUNT_ID repository secrets. +name: Deploy + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate: + name: Validate + uses: ./.github/workflows/ci.yml + + deploy: + name: Deploy + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: production + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup Node + pnpm + uses: ./.github/actions/setup-node-pnpm + + - name: Deploy + run: pnpm run deploy + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.lvbt/web-platform/examples/with-astro/.gitignore b/.lvbt/web-platform/examples/with-astro/.gitignore new file mode 100644 index 0000000..18995c8 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +dist/ +coverage/ +.turbo/ +.wrangler/ +.env +.env.* +!.env.example +.dev.vars +.dev.vars.* +.DS_Store +.astro/ diff --git a/.lvbt/web-platform/examples/with-astro/.gitleaks.toml b/.lvbt/web-platform/examples/with-astro/.gitleaks.toml new file mode 100644 index 0000000..adff6ab --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.gitleaks.toml @@ -0,0 +1,11 @@ +# Secret scanning. Extends gitleaks' upstream ruleset; everything below is an +# exemption for paths that carry no secrets but trip generic entropy rules. +[extend] +useDefault = true + +[allowlist] +description = "Paths that carry no secrets but trip generic entropy rules." +paths = [ + '''pnpm-lock\.yaml''', + '''docs/superpowers/.*\.md''', +] diff --git a/.lvbt/web-platform/examples/with-astro/.lvbt/commit-scopes.txt b/.lvbt/web-platform/examples/with-astro/.lvbt/commit-scopes.txt new file mode 100644 index 0000000..7be9d71 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.lvbt/commit-scopes.txt @@ -0,0 +1,6 @@ +# One scope per durable boundary in this repository. Replace these with the +# boundaries of your repository; omit a scope when a change crosses them. +site +docs +ci +dx diff --git a/.lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc b/.lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..14459ad --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.markdownlint-cli2.jsonc @@ -0,0 +1,25 @@ +{ + // Documentation is checked like code: every relative link and anchor must + // resolve, and prose wraps at the same 100 columns Prettier uses. + "customRules": ["markdownlint-rule-relative-links"], + "config": { + "default": true, + "MD013": { "line_length": 100, "code_blocks": false, "tables": false, "headings": false }, + "MD033": false, + "relative-links": true, + }, + "globs": ["**/*.md"], + "ignores": [ + // Playwright output. + "**/test-results", + "**/playwright-report", + ".claude/worktrees", + "node_modules", + "**/node_modules", + "dist", + "**/dist", + ".turbo", + "docs/superpowers", + "CHANGELOG.md", + ], +} diff --git a/.lvbt/web-platform/examples/with-astro/.prettierignore b/.lvbt/web-platform/examples/with-astro/.prettierignore new file mode 100644 index 0000000..4e71e60 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/.prettierignore @@ -0,0 +1,5 @@ +pnpm-lock.yaml +.lvbt/web-platform/ +.claude/worktrees/ +# Generated by `wrangler types`; regenerate it, never hand-edit it. +**/worker-configuration.d.ts diff --git a/.lvbt/web-platform/examples/with-astro/AGENTS.md b/.lvbt/web-platform/examples/with-astro/AGENTS.md new file mode 100644 index 0000000..a599239 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/AGENTS.md @@ -0,0 +1,47 @@ +# Working in this repository + +Run `pnpm check` after every change. It is the same command CI runs, and a failing check names the +command that fixes it (`pnpm check:fix` repairs everything a machine can). + +## Standard commands + +Every LVBT repository answers to the same commands: + +| Command | What it does | +| --------------------- | ---------------------------------------------------------- | +| `pnpm bootstrap` | Install dependencies, wire git hooks, and run preflight | +| `pnpm preflight` | Confirm the machine can build and deploy this repository | +| `pnpm check` | Format, docs, shape rules, lint, types, tests, repo checks | +| `pnpm check:fix` | Apply formatting and lint fixes | +| `pnpm build` | Build every package | +| `pnpm test` | Run every package's tests | +| `pnpm run deploy` | Build, then `wrangler deploy` every app (deployable repos) | +| `turbo gen workspace` | Scaffold a new package or app | + +## Create GitHub issues and pull requests + +Use the mandatory `github-contribution` skill from the `lvbt-contributions` plugin whenever a user +authorizes creating an issue or pull request. It carries the organization checklist, readable +templates, and the only approved creation helper: + +```bash +node node_modules/@lvbt/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 \ + --title <title> --body-file <file> --base main +``` + +Preview with `--dry-run --json` and inspect the complete Markdown before creating anything. Do not +call `gh issue create`, `gh pr create`, equivalent `gh api` routes, or connector creation tools +directly. + +## Commit messages + +Subjects are conventional: `type(scope): description`, at most 72 characters. Scopes are optional +and come only from [`.lvbt/commit-scopes.txt`](.lvbt/commit-scopes.txt). Omit the scope when a +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 +`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 new file mode 100644 index 0000000..4ec3fc9 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/README.md @@ -0,0 +1,32 @@ +# LVBT site repository + +A Turborepo workspace following the LVBT repository standard, with an Astro site under `apps/site` +that deploys to Cloudflare Workers as static assets. It was created with: + +```bash +npx create-turbo@latest --example https://github.com/LasVegasForTransit/repository-tooling/tree/main/examples/with-astro +``` + +## Getting started + +```bash +pnpm bootstrap # install, wire git hooks, run preflight +pnpm check # the same check CI runs +pnpm dev # the site at http://127.0.0.1:4321 +``` + +Then rename the root package and the Worker in `apps/site/wrangler.jsonc`, set `site` in +`apps/site/astro.config.ts`, and replace the scopes in `.lvbt/commit-scopes.txt` with this +repository's boundaries. + +## Layout + +- `apps/site` is the Astro site: pages under `src/pages`, layouts under `src/layouts`, Tailwind in + `src/styles/global.css`, unit tests under `tests/`, end-to-end tests under `tests/e2e/` +- `packages/` for libraries the site shares with other apps + +`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 +[`LasVegasForTransit/repository-tooling`](https://github.com/LasVegasForTransit/repository-tooling). diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/astro.config.ts b/.lvbt/web-platform/examples/with-astro/apps/site/astro.config.ts new file mode 100644 index 0000000..5a8444f --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/astro.config.ts @@ -0,0 +1,13 @@ +import sitemap from '@astrojs/sitemap'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + // The canonical URL. The sitemap and absolute links are built from it. + site: 'https://example.lasvegasfortransit.org', + output: 'static', + integrations: [sitemap()], + vite: { + plugins: [tailwindcss()], + }, +}); 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 new file mode 100644 index 0000000..50f29f2 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/eslint.config.js @@ -0,0 +1,3 @@ +import { config } from '@lvbt/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 new file mode 100644 index 0000000..a062108 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/package.json @@ -0,0 +1,35 @@ +{ + "name": "@lvbt/site", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "astro build", + "dev": "astro dev --host 127.0.0.1 --port 4321", + "preview": "astro preview --host 127.0.0.1 --port 4321", + "lint": "eslint . --max-warnings 0", + "check-types": "astro check", + "test": "vitest run", + "test:e2e": "playwright test" + }, + "dependencies": { + "@astrojs/sitemap": "catalog:", + "@tailwindcss/vite": "catalog:", + "astro": "catalog:", + "tailwindcss": "catalog:" + }, + "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", + "@playwright/test": "catalog:", + "@types/node": "catalog:", + "eslint": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vitest": "catalog:", + "wrangler": "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 new file mode 100644 index 0000000..47d462a --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/playwright.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from '@playwright/test'; + +import { foregroundServerEnvironment, sharedConfig } from '@lvbt/playwright-config'; + +const url = 'http://127.0.0.1:4321'; + +export default defineConfig({ + ...sharedConfig, + use: { ...sharedConfig.use, baseURL: url }, + webServer: { + command: 'pnpm preview', + env: foregroundServerEnvironment, + url, + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/public/robots.txt b/.lvbt/web-platform/examples/with-astro/apps/site/public/robots.txt new file mode 100644 index 0000000..c2a49f4 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/src/layouts/Base.astro b/.lvbt/web-platform/examples/with-astro/apps/site/src/layouts/Base.astro new file mode 100644 index 0000000..50832fa --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/src/layouts/Base.astro @@ -0,0 +1,22 @@ +--- +import { pageTitle } from '../lib/title'; +import '../styles/global.css'; + +interface Props { + page?: string; +} + +const { page } = Astro.props; +--- + +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>{pageTitle(page)} + + + + + diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/src/lib/title.ts b/.lvbt/web-platform/examples/with-astro/apps/site/src/lib/title.ts new file mode 100644 index 0000000..6a7a905 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/src/lib/title.ts @@ -0,0 +1,6 @@ +export const siteName = 'LVBT site'; + +/** The document title: the page name, then the site, or just the site on the home page. */ +export function pageTitle(page?: string): string { + return page === undefined ? siteName : `${page} · ${siteName}`; +} diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/src/pages/404.astro b/.lvbt/web-platform/examples/with-astro/apps/site/src/pages/404.astro new file mode 100644 index 0000000..6bc31b4 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/src/pages/404.astro @@ -0,0 +1,10 @@ +--- +import Base from '../layouts/Base.astro'; +--- + + +
+

Page not found

+

Back to the home page

+
+ diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/src/pages/index.astro b/.lvbt/web-platform/examples/with-astro/apps/site/src/pages/index.astro new file mode 100644 index 0000000..37da398 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/src/pages/index.astro @@ -0,0 +1,13 @@ +--- +import Base from '../layouts/Base.astro'; +--- + + +
+

LVBT site

+

+ An Astro site on Cloudflare Workers, following the LVBT repository standard. Replace this + page. +

+
+ diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/src/styles/global.css b/.lvbt/web-platform/examples/with-astro/apps/site/src/styles/global.css new file mode 100644 index 0000000..d4b5078 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/src/styles/global.css @@ -0,0 +1 @@ +@import 'tailwindcss'; 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 new file mode 100644 index 0000000..a4b97af --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/tests/e2e/home.spec.ts @@ -0,0 +1,19 @@ +import { expect, test } from '@playwright/test'; +import { expectNoAccessibilityViolations } from '@lvbt/playwright-config/accessibility'; +import { monitorPageHealth } from '@lvbt/playwright-config/page-health'; + +test('the home page has one main heading', async ({ page }) => { + const health = monitorPageHealth(page); + await page.goto('/'); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('LVBT site'); + await expectNoAccessibilityViolations(page); + health.assertNoErrors(); +}); + +test('unknown paths get the 404 page', async ({ page }) => { + const health = monitorPageHealth(page); + const response = await page.goto('/nowhere'); + expect(response?.status()).toBe(404); + await expect(page.getByRole('heading', { level: 1 })).toHaveText('Page not found'); + health.assertNoErrors(); +}); diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/tests/title.test.ts b/.lvbt/web-platform/examples/with-astro/apps/site/tests/title.test.ts new file mode 100644 index 0000000..06d8ad5 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/tests/title.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest'; + +import { pageTitle, siteName } from '../src/lib/title'; + +describe('pageTitle', () => { + it('is the site name on the home page', () => { + expect(pageTitle()).toBe(siteName); + }); + + it('puts the page before the site name elsewhere', () => { + expect(pageTitle('About')).toBe('About · LVBT site'); + }); +}); diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json b/.lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json new file mode 100644 index 0000000..898e748 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@lvbt/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 new file mode 100644 index 0000000..2474677 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from 'vitest/config'; + +import { sharedConfig } from '@lvbt/vitest-config'; + +export default defineConfig({ ...sharedConfig }); diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/wrangler.jsonc b/.lvbt/web-platform/examples/with-astro/apps/site/wrangler.jsonc new file mode 100644 index 0000000..a4aab15 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/apps/site/wrangler.jsonc @@ -0,0 +1,14 @@ +{ + "$schema": "./node_modules/wrangler/config-schema.json", + // The Worker name. Rename it before the first deploy. + "name": "lvbt-site", + "compatibility_date": "2026-08-31", + "preview_urls": true, + "assets": { + "directory": "./dist", + "not_found_handling": "404-page", + }, + "observability": { + "enabled": true, + }, +} diff --git a/.lvbt/web-platform/examples/with-astro/docs/README.md b/.lvbt/web-platform/examples/with-astro/docs/README.md new file mode 100644 index 0000000..d832ce5 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/docs/README.md @@ -0,0 +1,14 @@ +# Documentation + +This documentation follows [Diátaxis](https://diataxis.fr) under domain directories: tutorials +teach, how-to guides complete one task, reference records facts, and explanation gives the why. +Domains are `development` (working on the code), `operations` (running it), `product` (what it is +for), and `security`. Every document is listed here so it can be found. + +## Tutorials + +- [Start here](development/tutorials/start-here.md) + +## Reference + +- [Glossary](development/reference/glossary.md) 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 new file mode 100644 index 0000000..6f18f37 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/docs/development/reference/glossary.md @@ -0,0 +1,51 @@ +# Glossary + +Terms used across LVBT repositories, defined once. Docs link here the first time a term appears. + +## Tools + +**pnpm**: the package manager every LVBT repository uses. It installs dependencies +(`pnpm install`) and runs scripts (`pnpm check`). The version is pinned in `package.json` under +`packageManager`. + +**Turborepo**: runs a script (`lint`, `test`, `build`) in every workspace +package in the right order and caches the results. `pnpm check` uses it; you rarely call `turbo` +directly. + +**Workspace**: a repository holding several packages, listed in +`pnpm-workspace.yaml`. Applications live under `apps/`, libraries under `packages/`. + +**Catalog**: the `catalog:` block in `pnpm-workspace.yaml` that pins one version +of every tool for the whole organization. A package writes `"catalog:"` as a version and gets the +pinned one. + +**ESLint**: finds likely bugs and enforces code rules. The rules come from +`@lvbt/eslint-config`. + +**Prettier**: formats code the same way for everyone, so formatting is never +discussed in review. The settings come from `@lvbt/prettier-config`. + +**Vitest**: runs unit tests under `tests/`. + +**Playwright**: runs end-to-end tests in a real browser, under `tests/e2e/`. + +**Wrangler**: Cloudflare's command line for deploying Workers. +`pnpm run deploy` calls it. + +**gh**: the GitHub command line. Signing in with `gh auth login` lets the +repository's helper create issues and pull requests for you. + +## Terms + +**Conventional commit**: a commit subject shaped +`type(scope): description`, for example `fix(worker): reject expired share links`. The hook enforces +it. + +**Scope**: the part of a commit subject in parentheses, naming which durable +boundary of the repository the change belongs to. The allowed list is `.lvbt/commit-scopes.txt`. + +**CI**: continuous integration, the automation that runs `pnpm check` on every pull +request. The required status is named `Validate`. + +**Preflight**: `pnpm preflight`, the check that your machine can build and +deploy this repository, with a fix printed for anything missing. diff --git a/.lvbt/web-platform/examples/with-astro/docs/development/tutorials/start-here.md b/.lvbt/web-platform/examples/with-astro/docs/development/tutorials/start-here.md new file mode 100644 index 0000000..809a0b9 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/docs/development/tutorials/start-here.md @@ -0,0 +1,81 @@ +# Start here + +This tutorial takes you from a fresh clone to a passing check and a first commit. It exists so that +your first hour is spent on the project, not on tooling. You need to be able to run a command in a +terminal; nothing else is assumed. + +## Before you start + +- Node.js 24.20 or newer on the 24 line (the current long-term support release). Check with + `node --version`; install from [nodejs.org](https://nodejs.org) if needed. +- git, and a GitHub account with access to this repository. +- The GitHub CLI (`gh`), signed in with `gh auth login`. Issues and pull requests are created + through it. + +pnpm (the package manager, see the [glossary](../reference/glossary.md#pnpm)) installs itself from +the version pinned in `package.json` the first time you run it, through Corepack. If +`pnpm --version` fails, run `corepack enable` once. + +## 1. Clone and bootstrap + +```bash +git clone git@github.com:LasVegasForTransit/.git +cd +pnpm bootstrap +``` + +`bootstrap` installs dependencies, points git at the repository's hooks, and runs preflight, which +prints one line per check: + +```text + ok Node.js 24.20.0 satisfies ^24.20.0 + ok pnpm 11.25.0 matches packageManager + ok dependencies node_modules is present + ok git hooks core.hooksPath is .githooks + ok commit scopes .lvbt/commit-scopes.txt is present + ok GitHub CLI gh is installed and signed in + ok Cloudflare wrangler is signed in; deployables: apps/site +preflight: all 7 checks passed +``` + +A failing line prints the command that fixes it. Run it, then `pnpm preflight` again. + +## 2. Run the check + +```bash +pnpm check +``` + +This is the same command CI runs: formatting, documentation links, the repository-shape rules, then +lint, typecheck, and tests for every package. On a fresh clone it passes. When it fails, the output +names the package and file; `pnpm check:fix` repairs everything a machine can (formatting and +auto-fixable lint findings). + +## 3. Make a change and commit it + +Edit a file, then: + +```bash +git add +git commit +``` + +The commit hook checks the message. Subjects look like `type(scope): description` where `type` is +one of `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `perf`, `ci`, `build`, `style`, `revert`, +and the optional `scope` is one of the boundaries listed in `.lvbt/commit-scopes.txt`. A `feat` or +`fix` commit also needs a body that says what changed for a person using the product and why. The +hook tells you exactly what to change when it rejects a message. + +## 4. Open a pull request + +Push your branch and open the pull request with the GitHub CLI or the web UI. The pull request +template asks for a TL;DR, an overview of changes, and follow-ups. CI runs `pnpm check` as the +`Validate` status, and a maintainer reviews. + +## Where to go next + +- `pnpm dev` starts the app; `pnpm build` builds it; `pnpm run deploy` publishes it to Cloudflare + (`.github/workflows/deploy.yml` does that on every push to `main`). +- The [glossary](../reference/glossary.md) defines every tool and acronym used here. +- `AGENTS.md` at the repository root is the contract for coding agents; it is a good summary of the + rules for people too. diff --git a/.lvbt/web-platform/examples/with-astro/package.json b/.lvbt/web-platform/examples/with-astro/package.json new file mode 100644 index 0000000..4f9ee51 --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/package.json @@ -0,0 +1,41 @@ +{ + "name": "lvbt-with-astro-example", + "private": true, + "type": "module", + "scripts": { + "bootstrap": "lvbt bootstrap", + "preflight": "lvbt preflight", + "build": "turbo run build", + "dev": "turbo run dev", + "preview": "turbo run preview", + "lint": "turbo run lint", + "check-types": "turbo run check-types", + "test": "turbo run test", + "test:e2e": "turbo run test:e2e --concurrency=1", + "format": "prettier --write .", + "format:check": "prettier --check .", + "check": "pnpm format:check && markdownlint-cli2 && lvbt check && turbo run lint check-types test validate", + "check:fix": "pnpm format && markdownlint-cli2 --fix && turbo run lint -- --fix", + "prepare": "git config --local core.hooksPath .githooks 2>/dev/null || true", + "deploy": "lvbt deploy" + }, + "lint-staged": { + "*": "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", + "lint-staged": "catalog:", + "markdownlint-cli2": "catalog:", + "markdownlint-rule-relative-links": "catalog:", + "prettier": "catalog:", + "prettier-plugin-astro": "catalog:", + "turbo": "catalog:", + "typescript": "catalog:", + "wrangler": "catalog:" + }, + "packageManager": "pnpm@11.25.0", + "engines": { + "node": "^24.20.0" + } +} diff --git a/.lvbt/web-platform/examples/with-astro/packages/.gitkeep b/.lvbt/web-platform/examples/with-astro/packages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.lvbt/web-platform/examples/with-astro/pnpm-workspace.yaml b/.lvbt/web-platform/examples/with-astro/pnpm-workspace.yaml new file mode 100644 index 0000000..c4b29ca --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/pnpm-workspace.yaml @@ -0,0 +1,65 @@ +packages: + - 'apps/*' + - 'packages/*' + +# Dependencies allowed to run install scripts. Everything else installs +# without them, so a compromised package cannot run code at install time. +allowBuilds: + esbuild: true + sharp: true + workerd: true + +# A release must be at least one day old before it installs, so a package +# that is compromised and yanked the same day never reaches a repository. +minimumReleaseAge: 1440 + +# Temporary transitive pins for published security fixes not yet selected by +# their direct dependents. +overrides: + sharp: 0.35.4 + smol-toml: 1.8.0 + svgo: 4.1.0 + +# The organization version baseline. Every LVBT repository carries this same +# catalog, so a package declares "catalog:" and two repositories cannot drift +# onto different versions of one tool without it showing up as a change here. +# Renovate keeps it current. +catalog: + '@axe-core/playwright': 4.13.0 + '@astrojs/check': 0.9.10 + '@astrojs/sitemap': 3.7.4 + '@cloudflare/vitest-pool-workers': 0.22.0 + '@cloudflare/workers-types': 5.20260901.1 + '@eslint-community/eslint-plugin-eslint-comments': 4.7.2 + '@eslint/js': 10.0.1 + '@playwright/test': 1.62.1 + '@tailwindcss/vite': 4.3.3 + '@types/node': 24.13.3 + '@types/react': 19.2.18 + '@types/react-dom': 19.2.5 + '@vitejs/plugin-react': 6.1.1 + astro: 7.2.10 + eslint: 10.9.1 + eslint-config-prettier: 10.1.8 + eslint-plugin-react-hooks: 7.1.1 + eslint-plugin-sonarjs: 4.2.0 + eslint-plugin-turbo: 2.10.12 + globals: 17.11.0 + hono: 4.13.5 + lint-staged: 17.4.1 + markdownlint-cli2: 0.23.2 + markdownlint-rule-relative-links: 5.1.2 + playwright-core: 1.62.1 + prettier: 3.9.6 + prettier-plugin-astro: 0.14.1 + react: 19.2.8 + react-dom: 19.2.8 + tailwindcss: 4.3.3 + tsx: 4.23.13 + turbo: 2.10.12 + typescript: 6.0.3 + typescript-eslint: 8.69.0 + vite: 8.2.2 + vitest: 4.1.11 + wrangler: 4.127.1 + zod: 4.5.4 diff --git a/.lvbt/web-platform/examples/with-astro/prettier.config.js b/.lvbt/web-platform/examples/with-astro/prettier.config.js new file mode 100644 index 0000000..06fd3db --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/prettier.config.js @@ -0,0 +1,7 @@ +import lvbt from '@lvbt/prettier-config'; + +export default { + ...lvbt, + plugins: ['prettier-plugin-astro'], + overrides: [{ files: '*.astro', options: { parser: 'astro' } }], +}; diff --git a/.lvbt/web-platform/examples/with-astro/turbo.json b/.lvbt/web-platform/examples/with-astro/turbo.json new file mode 100644 index 0000000..fdc643b --- /dev/null +++ b/.lvbt/web-platform/examples/with-astro/turbo.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://turborepo.dev/schema.json", + "ui": "tui", + "globalEnv": ["CI"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": ["dist/**"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "check-types": { + "dependsOn": ["^check-types"] + }, + "test": { + "dependsOn": ["^build"] + }, + "validate": { + "dependsOn": ["^build"] + }, + "test:e2e": { + "dependsOn": ["build"], + "cache": false, + "outputs": ["playwright-report/**", "test-results/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "preview": { + "dependsOn": ["build"], + "cache": false, + "persistent": true + } + } +} 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 new file mode 100644 index 0000000..3109e3f --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "lvbt", + "interface": { + "displayName": "Las Vegans for Better Transit" + }, + "plugins": [ + { + "name": "lvbt-contributions", + "source": { + "source": "local", + "path": "../../node_modules/@lvbt/cli/plugins/lvbt-contributions" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} diff --git a/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json b/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json new file mode 100644 index 0000000..d1c0e32 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json @@ -0,0 +1,38 @@ +{ + "extraKnownMarketplaces": { + "lvbt": { + "source": { + "source": "github", + "repo": "LasVegasForTransit/repository-tooling", + "ref": "v0.2.8" + } + } + }, + "enabledPlugins": { + "lvbt-contributions@lvbt": true + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "pnpm exec prettier --write --ignore-unknown \"$CLAUDE_FILE_PATHS\" 2>/dev/null || true" + } + ] + } + ] + }, + "permissions": { + "deny": [ + "Read(**/.dev.vars)", + "Read(**/.dev.vars.*)", + "Read(**/.env)", + "Read(**/.env.*)", + "Read(**/*.pem)", + "Read(**/*.key)", + "Read(**/id_rsa*)" + ] + } +} diff --git a/.lvbt/web-platform/examples/with-vite-react/.codex/hooks.json b/.lvbt/web-platform/examples/with-vite-react/.codex/hooks.json new file mode 100644 index 0000000..e6c5136 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.codex/hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash|mcp__github__create_issue|mcp__github__create_pull_request", + "hooks": [ + { + "type": "command", + "command": "node \"$(git rev-parse --show-toplevel)/node_modules/@lvbt/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs\"", + "timeout": 10 + } + ] + } + ] + } +} diff --git a/.lvbt/web-platform/examples/with-vite-react/.editorconfig b/.lvbt/web-platform/examples/with-vite-react/.editorconfig new file mode 100644 index 0000000..1014ba7 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg b/.lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg new file mode 100755 index 0000000..f16cc88 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/commit-msg @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" diff --git a/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit new file mode 100755 index 0000000..1310c52 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" diff --git a/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-push b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-push new file mode 100755 index 0000000..76aa3a8 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" 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 new file mode 100755 index 0000000..464132c --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.githooks/prepare-commit-msg @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +# The shared hook lives in @lvbt/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" "$@" diff --git a/.lvbt/web-platform/examples/with-vite-react/.github/CODEOWNERS b/.lvbt/web-platform/examples/with-vite-react/.github/CODEOWNERS new file mode 100644 index 0000000..176be06 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Every change is reviewed by the maintainers team. Add narrower entries below +# for paths that need a specific reviewer, for example database migrations. +* @LasVegasForTransit/maintainers diff --git a/.lvbt/web-platform/examples/with-vite-react/.github/actions/setup-node-pnpm/action.yml b/.lvbt/web-platform/examples/with-vite-react/.github/actions/setup-node-pnpm/action.yml new file mode 100644 index 0000000..82b7cb4 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.github/actions/setup-node-pnpm/action.yml @@ -0,0 +1,20 @@ +name: Setup Node + pnpm +description: Install the repository's pinned pnpm, cache its store, and install from the lockfile. + +runs: + using: composite + steps: + # packageManager in package.json is the one version source; pnpm/action-setup + # reads it when no version input is supplied. + - name: Setup pnpm + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 + + - name: Setup Node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: package.json + cache: pnpm + + - name: Install dependencies + shell: bash + run: pnpm install --frozen-lockfile diff --git a/.lvbt/web-platform/examples/with-vite-react/.github/renovate.json b/.lvbt/web-platform/examples/with-vite-react/.github/renovate.json new file mode 100644 index 0000000..d7160ad --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.github/renovate.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", ":semanticCommits"], + "schedule": ["before 6am on monday"], + "timezone": "America/Los_Angeles", + "labels": ["dependencies"], + "prHourlyLimit": 4, + "prConcurrentLimit": 8, + "rangeStrategy": "pin", + "lockFileMaintenance": { + "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"] + }, + { + "groupName": "dev dependencies (non-major)", + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["minor", "patch"] + }, + { + "matchUpdateTypes": ["patch"], + "automerge": true, + "automergeType": "branch" + } + ] +} diff --git a/.lvbt/web-platform/examples/with-vite-react/.github/workflows/ci.yml b/.lvbt/web-platform/examples/with-vite-react/.github/workflows/ci.yml new file mode 100644 index 0000000..ca7a0cc --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +# The organization ruleset requires one status check named "Validate" on every +# pull request. Add steps to this job rather than renaming it. +name: CI + +on: + pull_request: + branches: [main] + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + # The secret scan reads history, not only the proposed tree. + fetch-depth: 0 + + - name: Setup Node + pnpm + uses: ./.github/actions/setup-node-pnpm + + - name: Check + run: pnpm check + + - name: Dependency audit + run: pnpm audit --audit-level=high + + - name: Secret scan + run: | + set -euo pipefail + docker run --rm -v "$PWD:/repo" -w /repo \ + ghcr.io/gitleaks/gitleaks@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f \ + git --redact --no-banner --config /repo/.gitleaks.toml diff --git a/.lvbt/web-platform/examples/with-vite-react/.github/workflows/deploy.yml b/.lvbt/web-platform/examples/with-vite-react/.github/workflows/deploy.yml new file mode 100644 index 0000000..97d169c --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.github/workflows/deploy.yml @@ -0,0 +1,42 @@ +# Deploys every app with a wrangler config on each push to main, after the +# same check pull requests run. Needs the CLOUDFLARE_API_TOKEN (Workers Scripts +# edit) and CLOUDFLARE_ACCOUNT_ID repository secrets. +name: Deploy + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate: + name: Validate + uses: ./.github/workflows/ci.yml + + deploy: + name: Deploy + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: production + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup Node + pnpm + uses: ./.github/actions/setup-node-pnpm + + - name: Deploy + run: pnpm run deploy + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.lvbt/web-platform/examples/with-vite-react/.gitignore b/.lvbt/web-platform/examples/with-vite-react/.gitignore new file mode 100644 index 0000000..cff9ebb --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +dist/ +coverage/ +.turbo/ +.wrangler/ +.env +.env.* +!.env.example +.dev.vars +.dev.vars.* +.DS_Store diff --git a/.lvbt/web-platform/examples/with-vite-react/.gitleaks.toml b/.lvbt/web-platform/examples/with-vite-react/.gitleaks.toml new file mode 100644 index 0000000..adff6ab --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.gitleaks.toml @@ -0,0 +1,11 @@ +# Secret scanning. Extends gitleaks' upstream ruleset; everything below is an +# exemption for paths that carry no secrets but trip generic entropy rules. +[extend] +useDefault = true + +[allowlist] +description = "Paths that carry no secrets but trip generic entropy rules." +paths = [ + '''pnpm-lock\.yaml''', + '''docs/superpowers/.*\.md''', +] diff --git a/.lvbt/web-platform/examples/with-vite-react/.lvbt/commit-scopes.txt b/.lvbt/web-platform/examples/with-vite-react/.lvbt/commit-scopes.txt new file mode 100644 index 0000000..36c4e52 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.lvbt/commit-scopes.txt @@ -0,0 +1,6 @@ +# One scope per durable boundary in this repository. Replace these with the +# boundaries of your repository; omit a scope when a change crosses them. +app +docs +ci +dx diff --git a/.lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc b/.lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..14459ad --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.markdownlint-cli2.jsonc @@ -0,0 +1,25 @@ +{ + // Documentation is checked like code: every relative link and anchor must + // resolve, and prose wraps at the same 100 columns Prettier uses. + "customRules": ["markdownlint-rule-relative-links"], + "config": { + "default": true, + "MD013": { "line_length": 100, "code_blocks": false, "tables": false, "headings": false }, + "MD033": false, + "relative-links": true, + }, + "globs": ["**/*.md"], + "ignores": [ + // Playwright output. + "**/test-results", + "**/playwright-report", + ".claude/worktrees", + "node_modules", + "**/node_modules", + "dist", + "**/dist", + ".turbo", + "docs/superpowers", + "CHANGELOG.md", + ], +} diff --git a/.lvbt/web-platform/examples/with-vite-react/.prettierignore b/.lvbt/web-platform/examples/with-vite-react/.prettierignore new file mode 100644 index 0000000..4e71e60 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/.prettierignore @@ -0,0 +1,5 @@ +pnpm-lock.yaml +.lvbt/web-platform/ +.claude/worktrees/ +# Generated by `wrangler types`; regenerate it, never hand-edit it. +**/worker-configuration.d.ts diff --git a/.lvbt/web-platform/examples/with-vite-react/AGENTS.md b/.lvbt/web-platform/examples/with-vite-react/AGENTS.md new file mode 100644 index 0000000..a599239 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/AGENTS.md @@ -0,0 +1,47 @@ +# Working in this repository + +Run `pnpm check` after every change. It is the same command CI runs, and a failing check names the +command that fixes it (`pnpm check:fix` repairs everything a machine can). + +## Standard commands + +Every LVBT repository answers to the same commands: + +| Command | What it does | +| --------------------- | ---------------------------------------------------------- | +| `pnpm bootstrap` | Install dependencies, wire git hooks, and run preflight | +| `pnpm preflight` | Confirm the machine can build and deploy this repository | +| `pnpm check` | Format, docs, shape rules, lint, types, tests, repo checks | +| `pnpm check:fix` | Apply formatting and lint fixes | +| `pnpm build` | Build every package | +| `pnpm test` | Run every package's tests | +| `pnpm run deploy` | Build, then `wrangler deploy` every app (deployable repos) | +| `turbo gen workspace` | Scaffold a new package or app | + +## Create GitHub issues and pull requests + +Use the mandatory `github-contribution` skill from the `lvbt-contributions` plugin whenever a user +authorizes creating an issue or pull request. It carries the organization checklist, readable +templates, and the only approved creation helper: + +```bash +node node_modules/@lvbt/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 \ + --title <title> --body-file <file> --base main +``` + +Preview with `--dry-run --json` and inspect the complete Markdown before creating anything. Do not +call `gh issue create`, `gh pr create`, equivalent `gh api` routes, or connector creation tools +directly. + +## Commit messages + +Subjects are conventional: `type(scope): description`, at most 72 characters. Scopes are optional +and come only from [`.lvbt/commit-scopes.txt`](.lvbt/commit-scopes.txt). Omit the scope when a +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 +`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 new file mode 100644 index 0000000..4ec3fc9 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/README.md @@ -0,0 +1,32 @@ +# LVBT site repository + +A Turborepo workspace following the LVBT repository standard, with an Astro site under `apps/site` +that deploys to Cloudflare Workers as static assets. It was created with: + +```bash +npx create-turbo@latest --example https://github.com/LasVegasForTransit/repository-tooling/tree/main/examples/with-astro +``` + +## Getting started + +```bash +pnpm bootstrap # install, wire git hooks, run preflight +pnpm check # the same check CI runs +pnpm dev # the site at http://127.0.0.1:4321 +``` + +Then rename the root package and the Worker in `apps/site/wrangler.jsonc`, set `site` in +`apps/site/astro.config.ts`, and replace the scopes in `.lvbt/commit-scopes.txt` with this +repository's boundaries. + +## Layout + +- `apps/site` is the Astro site: pages under `src/pages`, layouts under `src/layouts`, Tailwind in + `src/styles/global.css`, unit tests under `tests/`, end-to-end tests under `tests/e2e/` +- `packages/` for libraries the site shares with other apps + +`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 +[`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 new file mode 100644 index 0000000..40b3111 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/eslint.config.js @@ -0,0 +1,3 @@ +import { config } from '@lvbt/eslint-config/react-internal'; + +export default config; diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/index.html b/.lvbt/web-platform/examples/with-vite-react/apps/app/index.html new file mode 100644 index 0000000..8a614da --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/index.html @@ -0,0 +1,12 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>LVBT app + + +
+ + + 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 new file mode 100644 index 0000000..16c3a41 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json @@ -0,0 +1,37 @@ +{ + "name": "@lvbt/app", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite --host 127.0.0.1 --port 5173", + "preview": "vite preview --host 127.0.0.1 --port 4173", + "lint": "eslint . --max-warnings 0", + "check-types": "tsc --noEmit", + "test": "vitest run", + "test:e2e": "playwright test" + }, + "dependencies": { + "react": "catalog:", + "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", + "@playwright/test": "catalog:", + "@tailwindcss/vite": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@vitejs/plugin-react": "catalog:", + "eslint": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vitest": "catalog:", + "wrangler": "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 new file mode 100644 index 0000000..c10f111 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/playwright.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from '@playwright/test'; + +import { sharedConfig } from '@lvbt/playwright-config'; + +const url = 'http://127.0.0.1:4173'; + +export default defineConfig({ + ...sharedConfig, + use: { ...sharedConfig.use, baseURL: url }, + webServer: { command: 'pnpm preview', url, reuseExistingServer: !process.env.CI }, +}); diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/public/robots.txt b/.lvbt/web-platform/examples/with-vite-react/apps/app/public/robots.txt new file mode 100644 index 0000000..c2a49f4 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/src/App.tsx b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/App.tsx new file mode 100644 index 0000000..efd17e2 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/App.tsx @@ -0,0 +1,26 @@ +import { useState } from 'react'; + +import { nextCount } from './lib/counter'; + +export function App() { + const [count, setCount] = useState(0); + + return ( +
+

LVBT app

+

+ A Vite and React app on Cloudflare Workers, following the LVBT repository standard. Replace + this page. +

+ +
+ ); +} diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/src/lib/counter.ts b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/lib/counter.ts new file mode 100644 index 0000000..02eb9b7 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/lib/counter.ts @@ -0,0 +1,4 @@ +/** The next count after a click. Counts never go below one. */ +export function nextCount(current: number): number { + return Math.max(0, current) + 1; +} diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/src/main.tsx b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/main.tsx new file mode 100644 index 0000000..6f4db87 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { App } from './App'; +import './styles/global.css'; + +const root = document.getElementById('root'); +if (root === null) throw new Error('index.html has no #root element'); + +createRoot(root).render( + + + , +); diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/src/styles/global.css b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/styles/global.css new file mode 100644 index 0000000..d4b5078 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/src/styles/global.css @@ -0,0 +1 @@ +@import 'tailwindcss'; diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/counter.test.ts b/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/counter.test.ts new file mode 100644 index 0000000..f9da01c --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/counter.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest'; + +import { nextCount } from '../src/lib/counter'; + +describe('nextCount', () => { + it('adds one', () => { + expect(nextCount(2)).toBe(3); + }); + + it('never goes below one', () => { + expect(nextCount(-5)).toBe(1); + }); +}); 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 new file mode 100644 index 0000000..e98970f --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/tests/e2e/app.spec.ts @@ -0,0 +1,14 @@ +import { expect, test } from '@playwright/test'; +import { expectNoAccessibilityViolations } from '@lvbt/playwright-config/accessibility'; +import { monitorPageHealth } from '@lvbt/playwright-config/page-health'; + +test('the button counts clicks', async ({ page }) => { + const health = monitorPageHealth(page); + await page.goto('/'); + const button = page.getByRole('button'); + await expect(button).toHaveText('Clicked 0 times'); + await button.click(); + await expect(button).toHaveText('Clicked 1 times'); + await expectNoAccessibilityViolations(page); + health.assertNoErrors(); +}); 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 new file mode 100644 index 0000000..875c263 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@lvbt/typescript-config/react-library.json", + "compilerOptions": { + "types": ["vite/client", "node"] + }, + "include": ["src", "tests", "*.config.ts"] +} diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/vite.config.ts b/.lvbt/web-platform/examples/with-vite-react/apps/app/vite.config.ts new file mode 100644 index 0000000..63be2a0 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/vite.config.ts @@ -0,0 +1,7 @@ +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react(), tailwindcss()], +}); 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 new file mode 100644 index 0000000..2474677 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from 'vitest/config'; + +import { sharedConfig } from '@lvbt/vitest-config'; + +export default defineConfig({ ...sharedConfig }); diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/wrangler.jsonc b/.lvbt/web-platform/examples/with-vite-react/apps/app/wrangler.jsonc new file mode 100644 index 0000000..9c876a5 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/wrangler.jsonc @@ -0,0 +1,14 @@ +{ + "$schema": "./node_modules/wrangler/config-schema.json", + // The Worker name. Rename it before the first deploy. + "name": "lvbt-app", + "compatibility_date": "2026-08-31", + "preview_urls": true, + "assets": { + "directory": "./dist", + "not_found_handling": "single-page-application", + }, + "observability": { + "enabled": true, + }, +} diff --git a/.lvbt/web-platform/examples/with-vite-react/docs/README.md b/.lvbt/web-platform/examples/with-vite-react/docs/README.md new file mode 100644 index 0000000..d832ce5 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/docs/README.md @@ -0,0 +1,14 @@ +# Documentation + +This documentation follows [Diátaxis](https://diataxis.fr) under domain directories: tutorials +teach, how-to guides complete one task, reference records facts, and explanation gives the why. +Domains are `development` (working on the code), `operations` (running it), `product` (what it is +for), and `security`. Every document is listed here so it can be found. + +## Tutorials + +- [Start here](development/tutorials/start-here.md) + +## Reference + +- [Glossary](development/reference/glossary.md) 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 new file mode 100644 index 0000000..6f18f37 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/docs/development/reference/glossary.md @@ -0,0 +1,51 @@ +# Glossary + +Terms used across LVBT repositories, defined once. Docs link here the first time a term appears. + +## Tools + +**pnpm**: the package manager every LVBT repository uses. It installs dependencies +(`pnpm install`) and runs scripts (`pnpm check`). The version is pinned in `package.json` under +`packageManager`. + +**Turborepo**: runs a script (`lint`, `test`, `build`) in every workspace +package in the right order and caches the results. `pnpm check` uses it; you rarely call `turbo` +directly. + +**Workspace**: a repository holding several packages, listed in +`pnpm-workspace.yaml`. Applications live under `apps/`, libraries under `packages/`. + +**Catalog**: the `catalog:` block in `pnpm-workspace.yaml` that pins one version +of every tool for the whole organization. A package writes `"catalog:"` as a version and gets the +pinned one. + +**ESLint**: finds likely bugs and enforces code rules. The rules come from +`@lvbt/eslint-config`. + +**Prettier**: formats code the same way for everyone, so formatting is never +discussed in review. The settings come from `@lvbt/prettier-config`. + +**Vitest**: runs unit tests under `tests/`. + +**Playwright**: runs end-to-end tests in a real browser, under `tests/e2e/`. + +**Wrangler**: Cloudflare's command line for deploying Workers. +`pnpm run deploy` calls it. + +**gh**: the GitHub command line. Signing in with `gh auth login` lets the +repository's helper create issues and pull requests for you. + +## Terms + +**Conventional commit**: a commit subject shaped +`type(scope): description`, for example `fix(worker): reject expired share links`. The hook enforces +it. + +**Scope**: the part of a commit subject in parentheses, naming which durable +boundary of the repository the change belongs to. The allowed list is `.lvbt/commit-scopes.txt`. + +**CI**: continuous integration, the automation that runs `pnpm check` on every pull +request. The required status is named `Validate`. + +**Preflight**: `pnpm preflight`, the check that your machine can build and +deploy this repository, with a fix printed for anything missing. diff --git a/.lvbt/web-platform/examples/with-vite-react/docs/development/tutorials/start-here.md b/.lvbt/web-platform/examples/with-vite-react/docs/development/tutorials/start-here.md new file mode 100644 index 0000000..c129eb2 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/docs/development/tutorials/start-here.md @@ -0,0 +1,81 @@ +# Start here + +This tutorial takes you from a fresh clone to a passing check and a first commit. It exists so that +your first hour is spent on the project, not on tooling. You need to be able to run a command in a +terminal; nothing else is assumed. + +## Before you start + +- Node.js 24.20 or newer on the 24 line (the current long-term support release). Check with + `node --version`; install from [nodejs.org](https://nodejs.org) if needed. +- git, and a GitHub account with access to this repository. +- The GitHub CLI (`gh`), signed in with `gh auth login`. Issues and pull requests are created + through it. + +pnpm (the package manager, see the [glossary](../reference/glossary.md#pnpm)) installs itself from +the version pinned in `package.json` the first time you run it, through Corepack. If +`pnpm --version` fails, run `corepack enable` once. + +## 1. Clone and bootstrap + +```bash +git clone git@github.com:LasVegasForTransit/.git +cd +pnpm bootstrap +``` + +`bootstrap` installs dependencies, points git at the repository's hooks, and runs preflight, which +prints one line per check: + +```text + ok Node.js 24.20.0 satisfies ^24.20.0 + ok pnpm 11.25.0 matches packageManager + ok dependencies node_modules is present + ok git hooks core.hooksPath is .githooks + ok commit scopes .lvbt/commit-scopes.txt is present + ok GitHub CLI gh is installed and signed in + ok Cloudflare wrangler is signed in; deployables: apps/app +preflight: all 7 checks passed +``` + +A failing line prints the command that fixes it. Run it, then `pnpm preflight` again. + +## 2. Run the check + +```bash +pnpm check +``` + +This is the same command CI runs: formatting, documentation links, the repository-shape rules, then +lint, typecheck, and tests for every package. On a fresh clone it passes. When it fails, the output +names the package and file; `pnpm check:fix` repairs everything a machine can (formatting and +auto-fixable lint findings). + +## 3. Make a change and commit it + +Edit a file, then: + +```bash +git add +git commit +``` + +The commit hook checks the message. Subjects look like `type(scope): description` where `type` is +one of `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `perf`, `ci`, `build`, `style`, `revert`, +and the optional `scope` is one of the boundaries listed in `.lvbt/commit-scopes.txt`. A `feat` or +`fix` commit also needs a body that says what changed for a person using the product and why. The +hook tells you exactly what to change when it rejects a message. + +## 4. Open a pull request + +Push your branch and open the pull request with the GitHub CLI or the web UI. The pull request +template asks for a TL;DR, an overview of changes, and follow-ups. CI runs `pnpm check` as the +`Validate` status, and a maintainer reviews. + +## Where to go next + +- `pnpm dev` starts the app; `pnpm build` builds it; `pnpm run deploy` publishes it to Cloudflare + (`.github/workflows/deploy.yml` does that on every push to `main`). +- The [glossary](../reference/glossary.md) defines every tool and acronym used here. +- `AGENTS.md` at the repository root is the contract for coding agents; it is a good summary of the + rules for people too. diff --git a/.lvbt/web-platform/examples/with-vite-react/package.json b/.lvbt/web-platform/examples/with-vite-react/package.json new file mode 100644 index 0000000..41a5519 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/package.json @@ -0,0 +1,40 @@ +{ + "name": "lvbt-with-vite-react-example", + "private": true, + "type": "module", + "scripts": { + "bootstrap": "lvbt bootstrap", + "preflight": "lvbt preflight", + "build": "turbo run build", + "dev": "turbo run dev", + "preview": "turbo run preview", + "lint": "turbo run lint", + "check-types": "turbo run check-types", + "test": "turbo run test", + "test:e2e": "turbo run test:e2e --concurrency=1", + "format": "prettier --write .", + "format:check": "prettier --check .", + "check": "pnpm format:check && markdownlint-cli2 && lvbt check && turbo run lint check-types test validate", + "check:fix": "pnpm format && markdownlint-cli2 --fix && turbo run lint -- --fix", + "prepare": "git config --local core.hooksPath .githooks 2>/dev/null || true", + "deploy": "lvbt deploy" + }, + "lint-staged": { + "*": "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", + "lint-staged": "catalog:", + "markdownlint-cli2": "catalog:", + "markdownlint-rule-relative-links": "catalog:", + "prettier": "catalog:", + "turbo": "catalog:", + "typescript": "catalog:", + "wrangler": "catalog:" + }, + "packageManager": "pnpm@11.25.0", + "engines": { + "node": "^24.20.0" + } +} diff --git a/.lvbt/web-platform/examples/with-vite-react/packages/.gitkeep b/.lvbt/web-platform/examples/with-vite-react/packages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.lvbt/web-platform/examples/with-vite-react/pnpm-workspace.yaml b/.lvbt/web-platform/examples/with-vite-react/pnpm-workspace.yaml new file mode 100644 index 0000000..c4b29ca --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/pnpm-workspace.yaml @@ -0,0 +1,65 @@ +packages: + - 'apps/*' + - 'packages/*' + +# Dependencies allowed to run install scripts. Everything else installs +# without them, so a compromised package cannot run code at install time. +allowBuilds: + esbuild: true + sharp: true + workerd: true + +# A release must be at least one day old before it installs, so a package +# that is compromised and yanked the same day never reaches a repository. +minimumReleaseAge: 1440 + +# Temporary transitive pins for published security fixes not yet selected by +# their direct dependents. +overrides: + sharp: 0.35.4 + smol-toml: 1.8.0 + svgo: 4.1.0 + +# The organization version baseline. Every LVBT repository carries this same +# catalog, so a package declares "catalog:" and two repositories cannot drift +# onto different versions of one tool without it showing up as a change here. +# Renovate keeps it current. +catalog: + '@axe-core/playwright': 4.13.0 + '@astrojs/check': 0.9.10 + '@astrojs/sitemap': 3.7.4 + '@cloudflare/vitest-pool-workers': 0.22.0 + '@cloudflare/workers-types': 5.20260901.1 + '@eslint-community/eslint-plugin-eslint-comments': 4.7.2 + '@eslint/js': 10.0.1 + '@playwright/test': 1.62.1 + '@tailwindcss/vite': 4.3.3 + '@types/node': 24.13.3 + '@types/react': 19.2.18 + '@types/react-dom': 19.2.5 + '@vitejs/plugin-react': 6.1.1 + astro: 7.2.10 + eslint: 10.9.1 + eslint-config-prettier: 10.1.8 + eslint-plugin-react-hooks: 7.1.1 + eslint-plugin-sonarjs: 4.2.0 + eslint-plugin-turbo: 2.10.12 + globals: 17.11.0 + hono: 4.13.5 + lint-staged: 17.4.1 + markdownlint-cli2: 0.23.2 + markdownlint-rule-relative-links: 5.1.2 + playwright-core: 1.62.1 + prettier: 3.9.6 + prettier-plugin-astro: 0.14.1 + react: 19.2.8 + react-dom: 19.2.8 + tailwindcss: 4.3.3 + tsx: 4.23.13 + turbo: 2.10.12 + typescript: 6.0.3 + typescript-eslint: 8.69.0 + vite: 8.2.2 + vitest: 4.1.11 + wrangler: 4.127.1 + zod: 4.5.4 diff --git a/.lvbt/web-platform/examples/with-vite-react/prettier.config.js b/.lvbt/web-platform/examples/with-vite-react/prettier.config.js new file mode 100644 index 0000000..9794a17 --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/prettier.config.js @@ -0,0 +1,3 @@ +import lvbt from '@lvbt/prettier-config'; + +export default lvbt; diff --git a/.lvbt/web-platform/examples/with-vite-react/turbo.json b/.lvbt/web-platform/examples/with-vite-react/turbo.json new file mode 100644 index 0000000..fdc643b --- /dev/null +++ b/.lvbt/web-platform/examples/with-vite-react/turbo.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://turborepo.dev/schema.json", + "ui": "tui", + "globalEnv": ["CI"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": ["dist/**"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "check-types": { + "dependsOn": ["^check-types"] + }, + "test": { + "dependsOn": ["^build"] + }, + "validate": { + "dependsOn": ["^build"] + }, + "test:e2e": { + "dependsOn": ["build"], + "cache": false, + "outputs": ["playwright-report/**", "test-results/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "preview": { + "dependsOn": ["build"], + "cache": false, + "persistent": true + } + } +} diff --git a/.lvbt/web-platform/packages/cli/catalog.json b/.lvbt/web-platform/packages/cli/catalog.json new file mode 100644 index 0000000..4940880 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/catalog.json @@ -0,0 +1,44 @@ +{ + "packageManager": "pnpm@11.25.0", + "node": "^24.20.0", + "catalog": { + "@axe-core/playwright": "4.13.0", + "@astrojs/check": "0.9.10", + "@astrojs/sitemap": "3.7.4", + "@cloudflare/vitest-pool-workers": "0.22.0", + "@cloudflare/workers-types": "5.20260901.1", + "@eslint-community/eslint-plugin-eslint-comments": "4.7.2", + "@eslint/js": "10.0.1", + "@playwright/test": "1.62.1", + "@tailwindcss/vite": "4.3.3", + "@types/node": "24.13.3", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.5", + "@vitejs/plugin-react": "6.1.1", + "astro": "7.2.10", + "eslint": "10.9.1", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-sonarjs": "4.2.0", + "eslint-plugin-turbo": "2.10.12", + "globals": "17.11.0", + "hono": "4.13.5", + "lint-staged": "17.4.1", + "markdownlint-cli2": "0.23.2", + "markdownlint-rule-relative-links": "5.1.2", + "playwright-core": "1.62.1", + "prettier": "3.9.6", + "prettier-plugin-astro": "0.14.1", + "react": "19.2.8", + "react-dom": "19.2.8", + "tailwindcss": "4.3.3", + "tsx": "4.23.13", + "turbo": "2.10.12", + "typescript": "6.0.3", + "typescript-eslint": "8.69.0", + "vite": "8.2.2", + "vitest": "4.1.11", + "wrangler": "4.127.1", + "zod": "4.5.4" + } +} diff --git a/.lvbt/web-platform/packages/cli/hooks/commit-msg.sh b/.lvbt/web-platform/packages/cli/hooks/commit-msg.sh new file mode 100755 index 0000000..f7200d4 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/hooks/commit-msg.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh +# Validates the whole commit message against the organization grammar and the +# repository's own scope list (.lvbt/commit-scopes.txt, read from the current +# directory, which git sets to the repository root for hooks). +set -eu + +MESSAGE_FILE="$1" +SUBJECT=$(head -n 1 "$MESSAGE_FILE") + +# Git writes these subjects itself; they do not name an authored change. +case "$SUBJECT" in + "Merge "*|"Revert "*|"fixup!"*|"squash!"*|"amend!"*) exit 0 ;; +esac + +HOOKS_DIR=$(cd "$(dirname "$0")" && pwd) +exec node "$HOOKS_DIR/../plugins/lvbt-contributions/scripts/validate-commit-message.mjs" "$MESSAGE_FILE" diff --git a/.lvbt/web-platform/packages/cli/hooks/pre-commit.sh b/.lvbt/web-platform/packages/cli/hooks/pre-commit.sh new file mode 100755 index 0000000..bfab1f1 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/hooks/pre-commit.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env sh +# Formats the staged files and scans them for secrets. Fast by design: a slow +# hook gets bypassed with --no-verify, and a bypassed hook enforces nothing. +# CI is the guarantee; this exists so CI is rarely the thing that tells you. +set -eu +ROOT=$(git rev-parse --show-toplevel) +cd "$ROOT" + +STAGED="$(git diff --cached --name-only --diff-filter=ACMR)" +[ -z "$STAGED" ] && exit 0 + +if ! pnpm --silent exec lint-staged --config "$ROOT/package.json"; then + printf '\n Commit blocked: lint-staged could not format everything.\n' >&2 + printf ' fix: pnpm check:fix\n\n' >&2 + exit 1 +fi + +# Filenames are checked against the staged tree, so a misnamed file is caught +# at the commit rather than at push or in CI. +if ! pnpm --silent exec lvbt check filenames --staged; then + printf '\n Commit blocked: a source or test filename is out of contract.\n' >&2 + printf ' fix: rename the file and update its imports\n\n' >&2 + exit 1 +fi + +# Secret scanning, net 1 of 3. CI and GitHub push protection are the others, +# because this one is bypassable by design. +if command -v gitleaks >/dev/null 2>&1; then + if ! gitleaks git --staged --redact --no-banner 2>/dev/null; then + printf '\n Commit blocked: a secret was detected in the staged changes.\n' >&2 + printf ' Remove it, then rotate it. Assume anything written down is burned.\n\n' >&2 + exit 1 + fi +else + printf ' note: gitleaks is not installed, skipping the local secret scan.\n' >&2 + printf ' CI still scans. Install it with: brew install gitleaks\n' >&2 +fi diff --git a/.lvbt/web-platform/packages/cli/hooks/pre-push.sh b/.lvbt/web-platform/packages/cli/hooks/pre-push.sh new file mode 100755 index 0000000..bb3a6e3 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/hooks/pre-push.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env sh +# Runs the same check CI runs before anything leaves the laptop, so a red CI +# run is rare rather than routine. CI is still the authority. +set -eu +ROOT=$(git rev-parse --show-toplevel) +LOCAL_ENV_VARS=$(git rev-parse --local-env-vars) +while IFS= read -r variable; do + unset "$variable" +done < <…>` survives and only a missing one gets the +# tool-level fallback. +set -e +MSG_FILE="$1" +COMMIT_SOURCE="${2:-}" + +# Nothing to attribute for messages git composes itself. `squash` covers the +# rebase/fixup path, where the footers already belong to the commits being +# combined. +case "$COMMIT_SOURCE" in + merge | squash) exit 0 ;; +esac + +# Which agent, if any. CODEX_SESSION_ID is present in every Codex session; +# AI_AGENT is the cross-vendor spelling and CLAUDECODE is Claude Code's own +# boolean. None says which model, which is why the fallback names the tool. +if [ -n "${CODEX_SESSION_ID:-}" ]; then + AUTHOR='Codex ' +elif [ -n "${AI_AGENT:-}" ]; then + AUTHOR='Claude Code ' +elif [ "${CLAUDECODE:-}" = "1" ]; then + AUTHOR='Claude Code ' +else + exit 0 +fi + +# Already attributed, by the agent itself or by an earlier run of this hook on +# the commit being amended. +if grep -qi '^Co-Authored-By:' "$MSG_FILE"; then + exit 0 +fi + +# An empty message means the commit is about to be aborted anyway. +if ! grep -qv -e '^#' -e '^[[:space:]]*$' "$MSG_FILE"; then + exit 0 +fi + +# Append inside the existing footer block when there is one, so a +# `BREAKING CHANGE:` footer keeps its new neighbour rather than being split +# into a paragraph of its own. Comments stay where they are: git strips them. +awk -v footer="Co-Authored-By: $AUTHOR" ' + { lines[NR] = $0 } + END { + last = 0 + for (i = 1; i <= NR; i++) { + if (lines[i] !~ /^#/ && lines[i] !~ /^[[:space:]]*$/) last = i + } + start = 1 + for (i = last; i >= 1; i--) { + if (lines[i] ~ /^[[:space:]]*$/) { start = i + 1; break } + } + joinable = (last > 0) + for (i = start; i <= last; i++) { + if (lines[i] !~ /^[A-Za-z][A-Za-z-]*: / && + lines[i] !~ /^BREAKING[ -]CHANGE: / && + lines[i] !~ /^[[:space:]]+[^[:space:]]/) { joinable = 0; break } + } + for (i = 1; i <= NR; i++) { + if (i == last + 1) { + if (!joinable) print "" + print footer + } + print lines[i] + } + if (last == NR) { + if (!joinable) print "" + print footer + } + } +' "$MSG_FILE" >"$MSG_FILE.tmp" +mv "$MSG_FILE.tmp" "$MSG_FILE" diff --git a/.lvbt/web-platform/packages/cli/package.json b/.lvbt/web-platform/packages/cli/package.json new file mode 100644 index 0000000..f0df639 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/package.json @@ -0,0 +1,28 @@ +{ + "name": "@lvbt/cli", + "version": "0.2.8", + "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", + "repository": { + "type": "git", + "url": "https://github.com/LasVegasForTransit/repository-tooling.git", + "directory": "packages/cli" + }, + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "restricted" + }, + "bin": { + "lvbt": "src/cli.mjs" + }, + "files": [ + "src", + "hooks", + "plugins", + "catalog.json" + ], + "engines": { + "node": ">=24" + } +} 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 new file mode 100644 index 0000000..475f641 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "lvbt-contributions", + "version": "0.2.0", + "description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.", + "author": { + "name": "Las Vegans for Better Transit", + "url": "https://lasvegasfortransit.org" + }, + "repository": "https://github.com/LasVegasForTransit/repository-tooling", + "license": "MIT", + "keywords": ["github", "issues", "pull-requests"] +} 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 new file mode 100644 index 0000000..9fa85e5 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "lvbt-contributions", + "version": "0.2.0", + "description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.", + "author": { + "name": "Las Vegans for Better Transit", + "url": "https://lasvegasfortransit.org" + }, + "repository": "https://github.com/LasVegasForTransit/repository-tooling", + "license": "MIT", + "keywords": ["github", "issues", "pull-requests"], + "hooks": "./hooks/codex-hooks.json", + "interface": { + "displayName": "LVBT Contributions", + "shortDescription": "Create readable issues and pull requests", + "longDescription": "Guides agents through the LVBT issue and pull request workflow and blocks direct creation that bypasses the shared templates.", + "developerName": "Las Vegans for Better Transit", + "category": "Developer Tools", + "capabilities": ["Write"], + "websiteURL": "https://github.com/LasVegasForTransit/repository-tooling" + } +} diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/block-direct-create.mjs b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/block-direct-create.mjs new file mode 100644 index 0000000..32e6efd --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/block-direct-create.mjs @@ -0,0 +1,43 @@ +const reason = 'Use the mandatory github-contribution skill and its github-create helper.'; + +function commandFrom(payload) { + const input = payload.tool_input ?? payload.toolInput ?? {}; + return [input.command, input.cmd, input.script] + .filter((value) => typeof value === 'string') + .join('\n'); +} + +function isCreateTool(toolName) { + return /(?:^|__|_)(?:create_issue|create_pull_request|create_pr)$/i.test(toolName); +} + +function isCreateCommand(command) { + if (/\bgh\s+(?:issue|pr)\s+create\b/i.test(command)) return true; + + const writesIssues = + /\bgh\s+api\b[\s\S]*--method\s+(?:POST|PUT|PATCH)\b/i.test(command) && + /(?:repos\/[^\s'"]+\/[^\s'"]+\/(?:issues|pulls))\b/i.test(command); + return writesIssues; +} + +export function decision(payload) { + const toolName = String(payload.tool_name ?? payload.toolName ?? ''); + if (!isCreateTool(toolName) && !isCreateCommand(commandFrom(payload))) { + return {}; + } + + return { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision: 'deny', + permissionDecisionReason: reason, + }, + }; +} + +export async function run() { + let input = ''; + for await (const chunk of process.stdin) input += chunk; + const payload = input.trim() ? JSON.parse(input) : {}; + process.stdout.write(`${JSON.stringify(decision(payload))}\n`); +} diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/claude-pre-tool-use.mjs b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/claude-pre-tool-use.mjs new file mode 100644 index 0000000..61131dd --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/claude-pre-tool-use.mjs @@ -0,0 +1,3 @@ +import { run } from './block-direct-create.mjs'; + +await run(); diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-hooks.json b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-hooks.json new file mode 100644 index 0000000..e0ce866 --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash|mcp__github__create_issue|mcp__github__create_pull_request", + "hooks": [ + { + "type": "command", + "command": "node $PLUGIN_ROOT/hooks/codex-pre-tool-use.mjs", + "timeout": 10 + } + ] + } + ] + } +} diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs new file mode 100644 index 0000000..61131dd --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/codex-pre-tool-use.mjs @@ -0,0 +1,3 @@ +import { run } from './block-direct-create.mjs'; + +await run(); diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/hooks.json b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/hooks.json new file mode 100644 index 0000000..d79c16d --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/hooks/hooks.json @@ -0,0 +1,14 @@ +{ + "PreToolUse": [ + { + "matcher": "Bash|mcp__github__create_issue|mcp__github__create_pull_request", + "hooks": [ + { + "type": "command", + "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/claude-pre-tool-use.mjs", + "timeout": 10 + } + ] + } + ] +} diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/scripts/github-create.mjs b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/scripts/github-create.mjs new file mode 100644 index 0000000..4ad064c --- /dev/null +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/scripts/github-create.mjs @@ -0,0 +1,182 @@ +#!/usr/bin/env node + +import { readFile } from 'node:fs/promises'; +import { spawnSync } from 'node:child_process'; +import { commitSubjectError } from './validate-commit-subject.mjs'; + +const placeholderPattern = + /\[(?:describe|optional|subheading|more subheadings|future issue title)[^\]]*\]/i; + +function fail(message, code = 1) { + process.stderr.write(`${message}\n`); + process.exit(code); +} + +function parseArguments(argv) { + const [kind, ...rest] = argv; + const options = { kind, dryRun: false, json: false, draft: false }; + for (let index = 0; index < rest.length; index += 1) { + const argument = rest[index]; + if (argument === '--dry-run') options.dryRun = true; + else if (argument === '--json') options.json = true; + else if (argument === '--draft') options.draft = true; + else if (argument?.startsWith('--')) { + const value = rest[index + 1]; + if (!value || value.startsWith('--')) fail(`Missing value for ${argument}.`, 2); + options[argument.slice(2).replaceAll('-', '_')] = value; + index += 1; + } else fail(`Unexpected argument: ${argument}`, 2); + } + return options; +} + +function headings(body) { + const matches = [...body.matchAll(/^#{1,6}\s+(.+?)\s*$/gm)]; + return matches.map((match, index) => ({ + title: match[1], + level: match[0].indexOf(' '), + start: match.index + match[0].length, + end: + matches + .slice(index + 1) + .find((candidate) => candidate[0].indexOf(' ') <= match[0].indexOf(' '))?.index ?? + body.length, + })); +} + +function validateSections(body, required, optional = []) { + const found = headings(body); + const allowed = [...required, ...optional]; + for (const title of allowed) { + const matches = found.filter((heading) => heading.title === title); + if (matches.length > 1) fail(`Section "${title}" must appear exactly once.`); + } + + let previous = -1; + for (const title of required) { + const heading = found.find((candidate) => candidate.title === title); + if (!heading) fail(`Missing required section "${title}".`); + const position = found.indexOf(heading); + if (position <= previous) fail('Required sections are out of order.'); + previous = position; + const content = body.slice(heading.start, heading.end).trim(); + if (!content) fail(`Section "${title}" cannot be empty.`); + } +} + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + encoding: 'utf8', + ...options, + }); + if (result.error) fail(result.error.message, 2); + if (result.status !== 0) fail(result.stderr.trim() || `${command} failed.`, 2); + return result.stdout.trim(); +} + +function normalize(value) { + return value.replaceAll('\r\n', '\n').trimEnd(); +} + +function verifyStored(expected, stored) { + if (stored.title !== expected.title || normalize(stored.body) !== normalize(expected.body)) { + fail(`GitHub stored metadata that differs from the verified preview for ${stored.url}.`, 2); + } +} + +const options = parseArguments(process.argv.slice(2)); +if (!['issue', 'pr'].includes(options.kind)) { + fail('Usage: github-create issue|pr [options]', 2); +} +if (!options.title?.trim()) fail('A non-empty title is required.'); +if (options.title !== options.title.trim()) fail('The title must be trimmed.'); +if (placeholderPattern.test(options.title)) fail('The title contains a placeholder.'); +if (!options.body_file) fail('--body-file is required.', 2); + +const body = await readFile(options.body_file, 'utf8').catch((error) => + fail(`Could not read body file: ${error.message}`, 2), +); +if (placeholderPattern.test(body)) fail('The body contains an untouched placeholder.'); +if (/