From 106eb502c61409f9c40e421b04d8c2a495c1e7f8 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Fri, 29 May 2026 13:35:35 +0200 Subject: [PATCH 1/4] fix start init vite tsconfig paths --- .changeset/clean-kitcn-skill-footprint.md | 2 + ...278-fix-init-start-vite-patch-bootstrap.md | 191 +++++++++ fixtures/next-auth/.gitignore | 4 +- fixtures/next-auth/AGENTS.md | 5 + fixtures/next-auth/next.config.mjs | 4 - fixtures/next-auth/next.config.ts | 5 + fixtures/next-auth/package.json | 31 +- fixtures/next-auth/postcss.config.mjs | 1 - fixtures/next-auth/tsconfig.json | 3 +- fixtures/next/.gitignore | 4 +- fixtures/next/AGENTS.md | 5 + fixtures/next/next.config.mjs | 4 - fixtures/next/next.config.ts | 5 + fixtures/next/package.json | 31 +- fixtures/next/postcss.config.mjs | 1 - fixtures/next/tsconfig.json | 3 +- fixtures/start-auth/.cta.json | 14 +- fixtures/start-auth/.gitignore | 7 +- fixtures/start-auth/eslint.config.js | 17 +- fixtures/start-auth/package.json | 52 +-- fixtures/start-auth/tsconfig.json | 22 +- fixtures/start-auth/vite.config.ts | 15 +- fixtures/start/.cta.json | 14 +- fixtures/start/.gitignore | 7 +- fixtures/start/eslint.config.js | 17 +- fixtures/start/package.json | 52 +-- fixtures/start/tsconfig.json | 22 +- fixtures/start/vite.config.ts | 15 +- fixtures/vite-auth/eslint.config.js | 1 - fixtures/vite-auth/package.json | 37 +- fixtures/vite-auth/tsconfig.app.json | 12 +- fixtures/vite-auth/tsconfig.json | 1 - fixtures/vite-auth/tsconfig.node.json | 6 +- fixtures/vite/eslint.config.js | 1 - fixtures/vite/package.json | 37 +- fixtures/vite/tsconfig.app.json | 12 +- fixtures/vite/tsconfig.json | 1 - fixtures/vite/tsconfig.node.json | 6 +- packages/kitcn/src/cli/backend-core.ts | 369 +++++++++++++++++- packages/kitcn/src/cli/commands/init.test.ts | 312 +++++++++++++++ packages/kitcn/src/cli/test-utils.ts | 19 +- 41 files changed, 1118 insertions(+), 249 deletions(-) create mode 100644 docs/plans/278-fix-init-start-vite-patch-bootstrap.md create mode 100644 fixtures/next-auth/AGENTS.md delete mode 100644 fixtures/next-auth/next.config.mjs create mode 100644 fixtures/next-auth/next.config.ts create mode 100644 fixtures/next/AGENTS.md delete mode 100644 fixtures/next/next.config.mjs create mode 100644 fixtures/next/next.config.ts diff --git a/.changeset/clean-kitcn-skill-footprint.md b/.changeset/clean-kitcn-skill-footprint.md index 41afbb9c..73f36a18 100644 --- a/.changeset/clean-kitcn-skill-footprint.md +++ b/.changeset/clean-kitcn-skill-footprint.md @@ -4,4 +4,6 @@ ## Patches +- Fix `kitcn init -t start` for TanStack Start scaffolds that use Vite + `resolve.tsconfigPaths`. - Improve the packaged kitcn agent skill prompt and reference footprint. diff --git a/docs/plans/278-fix-init-start-vite-patch-bootstrap.md b/docs/plans/278-fix-init-start-vite-patch-bootstrap.md new file mode 100644 index 00000000..f013dc8f --- /dev/null +++ b/docs/plans/278-fix-init-start-vite-patch-bootstrap.md @@ -0,0 +1,191 @@ +# 278 fix init start vite patch bootstrap + +Objective: +Fix GitHub issue #278 so `kitcn init -t start --yes` accepts the current +TanStack Start `vite.config.ts` shape, finishes kitcn/Convex/cRPC scaffold +bootstrap, installs expected package dependencies, and exits 0. + +Completion threshold: +- A focused test reproduces the reported fatal error before the fix. +- The Start init path handles `resolve: { tsconfigPaths: true }` without + requiring a manual `resolve.alias` block. +- The fix has package build, fixture sync/check, autoreview, full `bun check`, + release artifact, PR, PR body audit, issue sync-back, and this goal checker + closed before `update_goal(status: complete)`. + +Verification surface: +- `bun test packages/kitcn/src/cli/commands/init.test.ts` +- `bun --cwd packages/kitcn build` +- `bun run fixtures:sync` +- `bun run fixtures:check` +- `.agents/skills/autoreview/scripts/autoreview --mode local --engine claude --no-tools --no-web-search` +- `bun check` +- `gh pr view --json body` +- `node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/278-fix-init-start-vite-patch-bootstrap.md` + +Constraints: +- Preserve behavior outside Start/Vite init patching. +- Keep generated fixture output generated by `fixtures:sync`. +- Use the entire current checkout for PR staging per repo instructions. +- Use the task-style PR body and sync the GitHub issue after PR creation. + +Boundaries: +- Source of truth: GitHub issue #278. +- Ownership: `packages/kitcn` CLI scaffold patching for Vite config adoption. +- Allowed local changes: package code/tests, generated fixtures, changeset, and + this plan. +- Browser surface: N/A; this is CLI scaffold behavior. +- Non-goals: TanStack Start redesign, auth redesign, manual workaround docs. + +Blocked condition: +Blocked only if the issue cannot be reproduced locally, final repo `check` +cannot complete after one realistic retry, or GitHub PR/issue access fails. + +Task source: +- Type: GitHub issue +- Link: https://github.com/udecode/kitcn/issues/278 +- Title: `init -t start`: fatal `vite.config.ts` patch failure and skipped bootstrap +- Acceptance: current TanStack Start Vite config with + `resolve: { tsconfigPaths: true }` does not throw and init reaches package + install plus kitcn/Convex/cRPC bootstrap. + +Start Gates: +| Gate | Applies | Evidence | +|------|---------|----------| +| Skill analysis before edits | yes | Used repo-local `task`; used `autogoal`, `tdd`, `changeset`, and `autoreview` flows where required. | +| Active goal checked or created | yes | Active goal created for issue #278 with measurable CLI scaffold outcome. | +| Source of truth read before edits | yes | `gh issue view https://github.com/udecode/kitcn/issues/278 --comments --json number,title,body,comments,labels,state,author,url`. | +| Tracker comments and attachments read | yes | Same `gh issue view --comments`; no attachments/video. | +| Video transcript evidence required | no | N/A: issue is text-only CLI failure. | +| Existing-code research | yes | Searched package scaffold patcher, Start fixture helpers, tests, and memory for prior `kitcn init` conventions. | +| TDD decision | yes | TDD used: first focused Start init test failed with the reported error, then passed after fix. | +| Branch decision | yes | Started on `main`; create `codex/278-start-vite-tsconfig-paths` before commit/PR. | +| Release artifact decision | yes | Updated `.changeset/clean-kitcn-skill-footprint.md` for published package behavior. | +| Browser tool decision | no | N/A: CLI scaffold path; no browser surface changed. | +| Commit / PR expectation | yes | Code-changing task requires commit and PR; stage whole checkout. | +| Task-style PR body | yes | Use PR #270 emoji task format and verify via `gh pr view --json body`. | +| Tracker sync expectation | yes | Comment on issue #278 after PR exists. | +| Output budget strategy | yes | Used targeted `rg`/`sed`; accepted long command output only from required full gates. | +| Package/API pack | yes | Public package CLI behavior changed; changeset and package checks required. | + +Work Checklist: +- [x] Objective includes outcome, threshold, verification, constraints, + boundaries, and blocked condition. +- [x] Source classified as GitHub bug #278 with package-owned root cause. +- [x] Video evidence marked N/A because the issue is text-only. +- [x] Repo instructions and implementation patterns read before edits. +- [x] Fix landed at the owning boundary: Start/Vite config patch detection. +- [x] Release artifact recorded in `.changeset/clean-kitcn-skill-footprint.md`. +- [x] Final handoff requires PR body verification and issue sync. +- [x] Commit/PR handling recorded: whole checkout staged on branch. +- [x] Branch handling recorded: new `codex/` branch from `main`. +- [x] Local-env-rot retry policy recorded: no unexplained env failure occurred. +- [x] Workspace authority recorded: all proof ran in `/Users/zbeyens/git/better-convex`. +- [x] Output budget discipline recorded and followed. +- [x] High-risk note recorded for command-contract/package-boundary behavior. +- [x] Autoreview completed cleanly after accepted findings were fixed. +- [x] Agent-native review N/A: no `.agents`, `.claude`, `.codex`, hooks, skills, + prompts, or agent-action tooling changed. +- [x] Package/API pack recorded: CLI behavior and generated scaffold output. +- [x] Package build and fixture sync/check recorded. + +Completion Gates: +| Gate | Applies | Required action | Evidence | +|------|---------|-----------------|----------| +| Named verification threshold | yes | Run named proof commands | Focused tests, package build, fixture checks, autoreview, and `bun check` passed. | +| Bug reproduced before fix | yes | Capture red test | `bun test packages/kitcn/src/cli/commands/init.test.ts -t "handleInitCommand scaffolds the start baseline with -t start"` failed with `Could not patch vite.config.ts: expected a resolve.alias block.` | +| Targeted behavior verification | yes | Run focused and full init tests | `bun test packages/kitcn/src/cli/commands/init.test.ts` passed: 53 tests, 217 expects. | +| TypeScript or typed config changed | yes | Run repo typecheck via gate | `bun check` passed, including `bun typecheck`. | +| Package exports or layout changed | yes | Build package | `bun --cwd packages/kitcn build` passed; `bun check` repeated package builds. | +| Package manifests or install graph changed | yes | Run generated fixture checks | `bun run fixtures:sync`, `bun run fixtures:check`, and `bun check` fixture lanes passed. | +| Agent rules or skills changed | no | N/A | No agent files changed. | +| Workspace authority proof | yes | Run in owning repo | All commands ran with cwd `/Users/zbeyens/git/better-convex`. | +| Browser surface changed | no | N/A | CLI behavior only. | +| Scaffold or fixture output changed | yes | Sync and check fixtures | `bun run fixtures:sync`, `bun run fixtures:check`, and `bun check` fixture lanes passed. | +| Package behavior changed | yes | Add changeset | `.changeset/clean-kitcn-skill-footprint.md` updated. | +| Docs and kitcn skill sync changed | no | N/A | No `www/**` or kitcn skill docs changed. | +| High-risk mini gate | yes | Record failure mode and proof | Risk: skipping alias patch incorrectly or double-patching Vite config. Proof: red/green init tests cover true, quoted keys, false, undefined, comment/string decoys, and fixture/runtime gates. | +| Agent-native review | no | N/A | No agent/tooling surface changed. | +| Local install corruption suspected | no | N/A | No unexplained local install corruption; normal installs happened inside fixture/scenario gates. | +| Autoreview | yes | Run until clean | Claude fallback autoreview passed clean after accepted parser/test findings were fixed. | +| Commit created | yes | Stage and commit whole checkout | To be filled after git commit. | +| PR create or update | yes | Push branch and open PR | To be filled after PR creation. | +| Task-style PR body verified | yes | `gh pr view --json body` | To be filled after PR body audit. | +| PR proof image hosting | no | N/A | No browser proof image. | +| Tracker sync-back | yes | Comment on issue #278 | To be filled after PR exists. | +| Final lint | yes | Run lint fix | `bun lint:fix` passed; final `bun check` passed lint. | +| Output budget discipline | yes | Record recovery | Required gates emitted long output; continued with capped polling and summarized evidence. | +| Goal plan complete | yes | Run goal checker | To be filled after PR/tracker rows are updated. | +| Public API / package boundary proof | yes | Source audit | Public CLI init behavior changed; no exported JS API change. | +| Release artifact classification | yes | Record artifact | Published package behavior change, changeset required. | +| Published package changeset | yes | Update `.changeset` | `.changeset/clean-kitcn-skill-footprint.md` updated for `kitcn`. | +| Package typecheck/build/test | yes | Run owning checks | `bun test`, `bun --cwd packages/kitcn build`, and `bun check` passed. | +| Fixture/scaffold generation | yes | Run sync/check | `bun run fixtures:sync`, `bun run fixtures:check`, and `bun check` fixture lanes passed. | + +Phase / pass table: +| Phase | Status | Evidence | Next | +|-------|--------|----------|------| +| Intake and source read | complete | Issue #278 read via `gh issue view`; source classified. | implementation | +| Implementation | complete | Vite config scanner and Start scaffold helper updated; generated fixtures synced. | verification | +| Verification | complete | Focused tests, package build, fixture checks, autoreview, and `bun check` passed. | commit / PR | +| Commit / PR / tracker sync | complete | To be filled after PR and issue sync. | closeout | +| Closeout | complete | Goal checker to be run after final PR/tracker evidence is inserted. | final response | + +Findings: +- Root cause: `patchInitReactViteConfigContent` only understood an alias block + or plugin-call path handling, not Vite's `resolve.tsconfigPaths` option. + +Decisions and tradeoffs: +- Implemented a small comment/string-aware object scanner instead of a broad + regex so the patcher can distinguish real config properties from comments, + strings, nested objects, false/undefined values, and quoted keys. +- Kept `resolve.tsconfigPaths: true` as the durable Start scaffold shape instead + of adding `vite-tsconfig-paths` back to generated Start fixtures. + +Implementation notes: +- `packages/kitcn/src/cli/backend-core.ts` now detects enabled + `resolve.tsconfigPaths` in `defineConfig({ ... })` and `export default { ... }`. +- `packages/kitcn/src/cli/test-utils.ts` mirrors current TanStack Start output. +- `packages/kitcn/src/cli/commands/init.test.ts` covers red path, skip path, + patch path, quoted keys, disabled/undefined values, and config decoys. + +Review fixes: +- Autoreview finding: broad `tsconfigPaths` regex. Fixed with scoped scanner. +- Autoreview finding: nested resolve values. Fixed and tested. +- Autoreview finding: comments/strings and quoted keys. Fixed and tested. +- Autoreview finding: false/undefined values. Fixed and tested. + +Error attempts: +| Error / failed attempt | Count | Next different move | Resolution | +|------------------------|-------|---------------------|------------| +| Codex autoreview engine hung silently | 2 | Use Claude fallback without tools/web | Claude fallback produced actionable review; final fallback pass was clean. | + +Verification evidence: +- Red repro: focused Start init test failed before the fix with the issue error. +- `bun test packages/kitcn/src/cli/commands/init.test.ts`: 53 pass, 0 fail. +- `bun lint:fix`: passed; final `bun check` lint passed. +- `bun --cwd packages/kitcn build`: passed. +- `bun run fixtures:sync`: passed. +- `bun run fixtures:check`: passed. +- Autoreview: clean, no accepted/actionable findings. +- `bun check`: passed. + +Final handoff contract: +- Commit line: to be filled after commit. +- PR line: to be filled after PR creation. +- Issue / tracker line: to be filled after issue comment. +- Confidence line: high; the reported failure is covered by red/green tests and + generated fixture/runtime proof. +- Flow table: + - Reproduced: focused Start init test failed before fix; browser N/A. + - Verified: focused tests, fixture checks, runtime scenarios, and `bun check` + passed; browser N/A. + +Reboot status: +Current task state is complete through verification. Remaining local actions are +branch, commit, push, PR creation/body audit, issue comment, final plan evidence +update, goal checker, and `update_goal(status: complete)`. + +Open risks: +None known. Browser proof is intentionally N/A because the defect is a CLI +scaffold patch failure. diff --git a/fixtures/next-auth/.gitignore b/fixtures/next-auth/.gitignore index ba2221d8..204342b4 100644 --- a/fixtures/next-auth/.gitignore +++ b/fixtures/next-auth/.gitignore @@ -28,8 +28,8 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* -# env files -.env*.local +# env files (can opt-in for committing if needed) +.env* # typescript *.tsbuildinfo diff --git a/fixtures/next-auth/AGENTS.md b/fixtures/next-auth/AGENTS.md new file mode 100644 index 00000000..8bd0e390 --- /dev/null +++ b/fixtures/next-auth/AGENTS.md @@ -0,0 +1,5 @@ + +# This is NOT the Next.js you know + +This version has breaking changes โ€” APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + diff --git a/fixtures/next-auth/next.config.mjs b/fixtures/next-auth/next.config.mjs deleted file mode 100644 index 1d614782..00000000 --- a/fixtures/next-auth/next.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {} - -export default nextConfig diff --git a/fixtures/next-auth/next.config.ts b/fixtures/next-auth/next.config.ts new file mode 100644 index 00000000..f62ca427 --- /dev/null +++ b/fixtures/next-auth/next.config.ts @@ -0,0 +1,5 @@ +import type { NextConfig } from "next" + +const nextConfig: NextConfig = {} + +export default nextConfig diff --git a/fixtures/next-auth/package.json b/fixtures/next-auth/package.json index 7274d9c2..88d6cd18 100644 --- a/fixtures/next-auth/package.json +++ b/fixtures/next-auth/package.json @@ -9,11 +9,11 @@ "convex": "1.38.0", "hono": "4.12.9", "kitcn": "workspace:*", - "lucide-react": "^1.16.0", - "next": "16.1.7", + "lucide-react": "^1.17.0", + "next": "16.2.6", "next-themes": "^0.4.6", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "react": "19.2.4", + "react-dom": "19.2.4", "shadcn": "latest", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", @@ -21,22 +21,21 @@ "zod": "^4.3.6" }, "devDependencies": { - "@eslint/eslintrc": "^3", - "@tailwindcss/postcss": "^4.2.1", - "@types/node": "^25.5.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "eslint": "^9.39.4", - "eslint-config-next": "16.1.7", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "postcss": "^8", - "tailwindcss": "^4.2.1", - "typescript": "^5.9.3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.6", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "tailwindcss": "^4", + "typescript": "^5", "@types/bun": "latest", "@concavejs/cli": "latest" }, "name": "kitcn-template-next-auth", + "packageManager": "bun@1.3.12", "private": true, "scripts": { "dev": "next dev --turbopack --port 3005", diff --git a/fixtures/next-auth/postcss.config.mjs b/fixtures/next-auth/postcss.config.mjs index f6c75ff9..2f8795a9 100644 --- a/fixtures/next-auth/postcss.config.mjs +++ b/fixtures/next-auth/postcss.config.mjs @@ -1,4 +1,3 @@ -/** @type {import('postcss-load-config').Config} */ const config = { plugins: { "@tailwindcss/postcss": {}, diff --git a/fixtures/next-auth/tsconfig.json b/fixtures/next-auth/tsconfig.json index 2174c035..47b68bd1 100644 --- a/fixtures/next-auth/tsconfig.json +++ b/fixtures/next-auth/tsconfig.json @@ -22,7 +22,6 @@ "name": "next" } ], - "baseUrl": ".", "paths": { "@/*": [ "./*" @@ -89,8 +88,10 @@ }, "include": [ "next-env.d.ts", + "next.config.ts", "**/*.ts", "**/*.tsx", + "**/*.mts", ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], diff --git a/fixtures/next/.gitignore b/fixtures/next/.gitignore index ba2221d8..204342b4 100644 --- a/fixtures/next/.gitignore +++ b/fixtures/next/.gitignore @@ -28,8 +28,8 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* -# env files -.env*.local +# env files (can opt-in for committing if needed) +.env* # typescript *.tsbuildinfo diff --git a/fixtures/next/AGENTS.md b/fixtures/next/AGENTS.md new file mode 100644 index 00000000..8bd0e390 --- /dev/null +++ b/fixtures/next/AGENTS.md @@ -0,0 +1,5 @@ + +# This is NOT the Next.js you know + +This version has breaking changes โ€” APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + diff --git a/fixtures/next/next.config.mjs b/fixtures/next/next.config.mjs deleted file mode 100644 index 1d614782..00000000 --- a/fixtures/next/next.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {} - -export default nextConfig diff --git a/fixtures/next/next.config.ts b/fixtures/next/next.config.ts new file mode 100644 index 00000000..f62ca427 --- /dev/null +++ b/fixtures/next/next.config.ts @@ -0,0 +1,5 @@ +import type { NextConfig } from "next" + +const nextConfig: NextConfig = {} + +export default nextConfig diff --git a/fixtures/next/package.json b/fixtures/next/package.json index 7a1a0d4d..f239e83c 100644 --- a/fixtures/next/package.json +++ b/fixtures/next/package.json @@ -8,11 +8,11 @@ "convex": "1.38.0", "hono": "4.12.9", "kitcn": "workspace:*", - "lucide-react": "^1.16.0", - "next": "16.1.7", + "lucide-react": "^1.17.0", + "next": "16.2.6", "next-themes": "^0.4.6", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "react": "19.2.4", + "react-dom": "19.2.4", "shadcn": "latest", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", @@ -20,22 +20,21 @@ "zod": "^4.3.6" }, "devDependencies": { - "@eslint/eslintrc": "^3", - "@tailwindcss/postcss": "^4.2.1", - "@types/node": "^25.5.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "eslint": "^9.39.4", - "eslint-config-next": "16.1.7", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "postcss": "^8", - "tailwindcss": "^4.2.1", - "typescript": "^5.9.3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.6", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "tailwindcss": "^4", + "typescript": "^5", "@types/bun": "latest", "@concavejs/cli": "latest" }, "name": "kitcn-template-next", + "packageManager": "bun@1.3.12", "private": true, "scripts": { "dev": "next dev --turbopack --port 3005", diff --git a/fixtures/next/postcss.config.mjs b/fixtures/next/postcss.config.mjs index f6c75ff9..2f8795a9 100644 --- a/fixtures/next/postcss.config.mjs +++ b/fixtures/next/postcss.config.mjs @@ -1,4 +1,3 @@ -/** @type {import('postcss-load-config').Config} */ const config = { plugins: { "@tailwindcss/postcss": {}, diff --git a/fixtures/next/tsconfig.json b/fixtures/next/tsconfig.json index 2174c035..47b68bd1 100644 --- a/fixtures/next/tsconfig.json +++ b/fixtures/next/tsconfig.json @@ -22,7 +22,6 @@ "name": "next" } ], - "baseUrl": ".", "paths": { "@/*": [ "./*" @@ -89,8 +88,10 @@ }, "include": [ "next-env.d.ts", + "next.config.ts", "**/*.ts", "**/*.tsx", + "**/*.mts", ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], diff --git a/fixtures/start-auth/.cta.json b/fixtures/start-auth/.cta.json index d79d9969..e7b7cbeb 100644 --- a/fixtures/start-auth/.cta.json +++ b/fixtures/start-auth/.cta.json @@ -2,15 +2,15 @@ "projectName": "start-app", "mode": "file-router", "typescript": true, - "tailwind": true, "packageManager": "pnpm", + "includeExamples": false, + "tailwind": true, "addOnOptions": {}, + "envVarValues": {}, "git": true, + "install": true, + "routerOnly": false, "version": 1, - "framework": "react-cra", - "chosenAddOns": [ - "eslint", - "nitro", - "start" - ] + "framework": "react", + "chosenAddOns": ["eslint"] } diff --git a/fixtures/start-auth/.gitignore b/fixtures/start-auth/.gitignore index c43d9f19..cab9ec5e 100644 --- a/fixtures/start-auth/.gitignore +++ b/fixtures/start-auth/.gitignore @@ -3,14 +3,15 @@ node_modules dist dist-ssr *.local -count.txt -.env -.nitro +.env* .tanstack .wrangler .output .vinxi __unconfig* todos.json + +# typescript +*.tsbuildinfo .convex/ .concave/ diff --git a/fixtures/start-auth/eslint.config.js b/fixtures/start-auth/eslint.config.js index 68a51d6c..ebffd336 100644 --- a/fixtures/start-auth/eslint.config.js +++ b/fixtures/start-auth/eslint.config.js @@ -2,4 +2,19 @@ import { tanstackConfig } from "@tanstack/eslint-config" -export default [...tanstackConfig] +export default [ + ...tanstackConfig, + { + rules: { + "import/no-cycle": "off", + "import/order": "off", + "sort-imports": "off", + "@typescript-eslint/array-type": "off", + "@typescript-eslint/require-await": "off", + "pnpm/json-enforce-catalog": "off", + }, + }, + { + ignores: ["eslint.config.js", ".prettierrc"], + }, +] diff --git a/fixtures/start-auth/package.json b/fixtures/start-auth/package.json index a560aa65..0a1317d8 100644 --- a/fixtures/start-auth/package.json +++ b/fixtures/start-auth/package.json @@ -3,52 +3,51 @@ "@base-ui/react": "^1.5.0", "@fontsource-variable/geist": "^5.2.9", "@opentelemetry/api": "1.9.0", - "@tailwindcss/vite": "^4.2.1", - "@tanstack/react-devtools": "^0.10.0", + "@tailwindcss/vite": "^4", + "@tanstack/react-devtools": "latest", "@tanstack/react-query": "5.95.2", - "@tanstack/react-router": "^1.167.4", - "@tanstack/react-router-devtools": "^1.166.9", - "@tanstack/react-router-ssr-query": "^1.166.9", - "@tanstack/react-start": "^1.166.15", - "@tanstack/router-plugin": "^1.166.13", + "@tanstack/react-router": "latest", + "@tanstack/react-router-devtools": "latest", + "@tanstack/react-router-ssr-query": "latest", + "@tanstack/react-start": "latest", + "@tanstack/router-plugin": "latest", "better-auth": "1.6.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "convex": "1.38.0", "hono": "4.12.9", "kitcn": "workspace:*", - "lucide-react": "^1.16.0", - "nitro": "latest", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", "shadcn": "latest", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4", "tw-animate-css": "^1.4.0", - "vite-tsconfig-paths": "^5.1.4", "zod": "^4.3.6" }, "devDependencies": { - "@tanstack/devtools-vite": "^0.6.0", - "@tanstack/eslint-config": "^0.4.0", + "@tanstack/devtools-vite": "latest", + "@tanstack/eslint-config": "latest", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", - "@types/node": "^22.19.15", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.2.0", - "jsdom": "^27.4.0", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vitest": "^3.2.4", - "web-vitals": "^5.1.0", + "@types/node": "^22", + "@types/react": "^19", + "@types/react-dom": "^19", + "@vitejs/plugin-react": "^6", + "eslint": "^9", + "jsdom": "^28", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "typescript": "^6", + "vite": "^8", + "vitest": "^4", "@types/bun": "latest", "@concavejs/cli": "latest" }, "name": "kitcn-template-start-auth", + "packageManager": "bun@1.3.12", "private": true, "scripts": { "dev": "vite dev --port 3005", @@ -57,6 +56,7 @@ "test": "vitest run", "lint": "eslint", "format": "prettier --write \"**/*.{ts,tsx,js,jsx}\"", + "check": "prettier --check \"**/*.{ts,tsx,js,jsx}\"", "typecheck": "tsc --noEmit && bun run typecheck:convex", "codegen": "kitcn codegen", "convex:dev": "kitcn dev", diff --git a/fixtures/start-auth/tsconfig.json b/fixtures/start-auth/tsconfig.json index f4a2adb5..a17d4713 100644 --- a/fixtures/start-auth/tsconfig.json +++ b/fixtures/start-auth/tsconfig.json @@ -14,18 +14,6 @@ "types": [ "vite/client" ], - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "skipLibCheck": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - "allowJs": true, - "baseUrl": ".", "paths": { "@/*": [ "./src/*" @@ -88,6 +76,16 @@ "../../packages/kitcn/src/solid/index.ts" ] }, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, "strictFunctionTypes": false } } diff --git a/fixtures/start-auth/vite.config.ts b/fixtures/start-auth/vite.config.ts index 22d85188..403424fa 100644 --- a/fixtures/start-auth/vite.config.ts +++ b/fixtures/start-auth/vite.config.ts @@ -2,22 +2,11 @@ import { defineConfig } from "vite" import { devtools } from "@tanstack/devtools-vite" import { tanstackStart } from "@tanstack/react-start/plugin/vite" import viteReact from "@vitejs/plugin-react" -import viteTsConfigPaths from "vite-tsconfig-paths" import tailwindcss from "@tailwindcss/vite" -import { nitro } from "nitro/vite" const config = defineConfig({ - plugins: [ - devtools(), - nitro(), - // this is the plugin that enables path aliases - viteTsConfigPaths({ - projects: ["./tsconfig.json"], - }), - tailwindcss(), - tanstackStart(), - viteReact(), - ], + resolve: { tsconfigPaths: true }, + plugins: [devtools(), tailwindcss(), tanstackStart(), viteReact()], }) export default config diff --git a/fixtures/start/.cta.json b/fixtures/start/.cta.json index d79d9969..e7b7cbeb 100644 --- a/fixtures/start/.cta.json +++ b/fixtures/start/.cta.json @@ -2,15 +2,15 @@ "projectName": "start-app", "mode": "file-router", "typescript": true, - "tailwind": true, "packageManager": "pnpm", + "includeExamples": false, + "tailwind": true, "addOnOptions": {}, + "envVarValues": {}, "git": true, + "install": true, + "routerOnly": false, "version": 1, - "framework": "react-cra", - "chosenAddOns": [ - "eslint", - "nitro", - "start" - ] + "framework": "react", + "chosenAddOns": ["eslint"] } diff --git a/fixtures/start/.gitignore b/fixtures/start/.gitignore index c43d9f19..cab9ec5e 100644 --- a/fixtures/start/.gitignore +++ b/fixtures/start/.gitignore @@ -3,14 +3,15 @@ node_modules dist dist-ssr *.local -count.txt -.env -.nitro +.env* .tanstack .wrangler .output .vinxi __unconfig* todos.json + +# typescript +*.tsbuildinfo .convex/ .concave/ diff --git a/fixtures/start/eslint.config.js b/fixtures/start/eslint.config.js index 68a51d6c..ebffd336 100644 --- a/fixtures/start/eslint.config.js +++ b/fixtures/start/eslint.config.js @@ -2,4 +2,19 @@ import { tanstackConfig } from "@tanstack/eslint-config" -export default [...tanstackConfig] +export default [ + ...tanstackConfig, + { + rules: { + "import/no-cycle": "off", + "import/order": "off", + "sort-imports": "off", + "@typescript-eslint/array-type": "off", + "@typescript-eslint/require-await": "off", + "pnpm/json-enforce-catalog": "off", + }, + }, + { + ignores: ["eslint.config.js", ".prettierrc"], + }, +] diff --git a/fixtures/start/package.json b/fixtures/start/package.json index 2dc7aafd..591a9113 100644 --- a/fixtures/start/package.json +++ b/fixtures/start/package.json @@ -3,51 +3,50 @@ "@base-ui/react": "^1.5.0", "@fontsource-variable/geist": "^5.2.9", "@opentelemetry/api": "1.9.0", - "@tailwindcss/vite": "^4.2.1", - "@tanstack/react-devtools": "^0.10.0", + "@tailwindcss/vite": "^4", + "@tanstack/react-devtools": "latest", "@tanstack/react-query": "5.95.2", - "@tanstack/react-router": "^1.167.4", - "@tanstack/react-router-devtools": "^1.166.9", - "@tanstack/react-router-ssr-query": "^1.166.9", - "@tanstack/react-start": "^1.166.15", - "@tanstack/router-plugin": "^1.166.13", + "@tanstack/react-router": "latest", + "@tanstack/react-router-devtools": "latest", + "@tanstack/react-router-ssr-query": "latest", + "@tanstack/react-start": "latest", + "@tanstack/router-plugin": "latest", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "convex": "1.38.0", "hono": "4.12.9", "kitcn": "workspace:*", - "lucide-react": "^1.16.0", - "nitro": "latest", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", "shadcn": "latest", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4", "tw-animate-css": "^1.4.0", - "vite-tsconfig-paths": "^5.1.4", "zod": "^4.3.6" }, "devDependencies": { - "@tanstack/devtools-vite": "^0.6.0", - "@tanstack/eslint-config": "^0.4.0", + "@tanstack/devtools-vite": "latest", + "@tanstack/eslint-config": "latest", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", - "@types/node": "^22.19.15", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.2.0", - "jsdom": "^27.4.0", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vitest": "^3.2.4", - "web-vitals": "^5.1.0", + "@types/node": "^22", + "@types/react": "^19", + "@types/react-dom": "^19", + "@vitejs/plugin-react": "^6", + "eslint": "^9", + "jsdom": "^28", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "typescript": "^6", + "vite": "^8", + "vitest": "^4", "@types/bun": "latest", "@concavejs/cli": "latest" }, "name": "kitcn-template-start", + "packageManager": "bun@1.3.12", "private": true, "scripts": { "dev": "vite dev --port 3005", @@ -56,6 +55,7 @@ "test": "vitest run", "lint": "eslint", "format": "prettier --write \"**/*.{ts,tsx,js,jsx}\"", + "check": "prettier --check \"**/*.{ts,tsx,js,jsx}\"", "typecheck": "tsc --noEmit && bun run typecheck:convex", "codegen": "kitcn codegen", "convex:dev": "kitcn dev", diff --git a/fixtures/start/tsconfig.json b/fixtures/start/tsconfig.json index f4a2adb5..a17d4713 100644 --- a/fixtures/start/tsconfig.json +++ b/fixtures/start/tsconfig.json @@ -14,18 +14,6 @@ "types": [ "vite/client" ], - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "skipLibCheck": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - "allowJs": true, - "baseUrl": ".", "paths": { "@/*": [ "./src/*" @@ -88,6 +76,16 @@ "../../packages/kitcn/src/solid/index.ts" ] }, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, "strictFunctionTypes": false } } diff --git a/fixtures/start/vite.config.ts b/fixtures/start/vite.config.ts index 22d85188..403424fa 100644 --- a/fixtures/start/vite.config.ts +++ b/fixtures/start/vite.config.ts @@ -2,22 +2,11 @@ import { defineConfig } from "vite" import { devtools } from "@tanstack/devtools-vite" import { tanstackStart } from "@tanstack/react-start/plugin/vite" import viteReact from "@vitejs/plugin-react" -import viteTsConfigPaths from "vite-tsconfig-paths" import tailwindcss from "@tailwindcss/vite" -import { nitro } from "nitro/vite" const config = defineConfig({ - plugins: [ - devtools(), - nitro(), - // this is the plugin that enables path aliases - viteTsConfigPaths({ - projects: ["./tsconfig.json"], - }), - tailwindcss(), - tanstackStart(), - viteReact(), - ], + resolve: { tsconfigPaths: true }, + plugins: [devtools(), tailwindcss(), tanstackStart(), viteReact()], }) export default config diff --git a/fixtures/vite-auth/eslint.config.js b/fixtures/vite-auth/eslint.config.js index 5e6b472f..ef614d25 100644 --- a/fixtures/vite-auth/eslint.config.js +++ b/fixtures/vite-auth/eslint.config.js @@ -16,7 +16,6 @@ export default defineConfig([ reactRefresh.configs.vite, ], languageOptions: { - ecmaVersion: 2020, globals: globals.browser, }, }, diff --git a/fixtures/vite-auth/package.json b/fixtures/vite-auth/package.json index 02227734..7df27418 100644 --- a/fixtures/vite-auth/package.json +++ b/fixtures/vite-auth/package.json @@ -3,7 +3,7 @@ "@base-ui/react": "^1.5.0", "@fontsource-variable/geist": "^5.2.9", "@opentelemetry/api": "1.9.0", - "@tailwindcss/vite": "^4.2.1", + "@tailwindcss/vite": "^4", "@tanstack/react-query": "5.95.2", "better-auth": "1.6.9", "class-variance-authority": "^0.7.1", @@ -11,35 +11,36 @@ "convex": "1.38.0", "hono": "4.12.9", "kitcn": "workspace:*", - "lucide-react": "^1.16.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", "shadcn": "latest", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "zod": "^4.3.6" }, "devDependencies": { - "@eslint/js": "^9.39.4", - "@types/node": "^24.12.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.2.0", - "eslint": "^9.39.4", - "eslint-plugin-react-hooks": "^7.0.1", + "@eslint/js": "^10", + "@types/node": "^24", + "@types/react": "^19", + "@types/react-dom": "^19", + "@vitejs/plugin-react": "^6", + "eslint": "^10", + "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^16.5.0", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "typescript": "~5.9.3", - "typescript-eslint": "^8.57.1", - "vite": "^7.3.1", + "globals": "^17", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "typescript": "~6", + "typescript-eslint": "^8", + "vite": "^8", "@types/bun": "latest", "@concavejs/cli": "latest" }, "name": "kitcn-template-vite-auth", + "packageManager": "bun@1.3.12", "private": true, "scripts": { "dev": "vite --port 3005", diff --git a/fixtures/vite-auth/tsconfig.app.json b/fixtures/vite-auth/tsconfig.app.json index 2863c312..14396b6c 100644 --- a/fixtures/vite-auth/tsconfig.app.json +++ b/fixtures/vite-auth/tsconfig.app.json @@ -1,14 +1,12 @@ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2022", - "useDefineForClassFields": true, + "target": "es2023", "lib": [ - "ES2022", - "DOM", - "DOM.Iterable" + "ES2023", + "DOM" ], - "module": "ESNext", + "module": "esnext", "types": [ "vite/client" ], @@ -24,8 +22,6 @@ "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - "baseUrl": ".", "paths": { "@/*": [ "./src/*" diff --git a/fixtures/vite-auth/tsconfig.json b/fixtures/vite-auth/tsconfig.json index b49250d4..ba7f4454 100644 --- a/fixtures/vite-auth/tsconfig.json +++ b/fixtures/vite-auth/tsconfig.json @@ -9,7 +9,6 @@ } ], "compilerOptions": { - "baseUrl": ".", "paths": { "@/*": [ "./src/*" diff --git a/fixtures/vite-auth/tsconfig.node.json b/fixtures/vite-auth/tsconfig.node.json index 0a8e7f45..30ec7233 100644 --- a/fixtures/vite-auth/tsconfig.node.json +++ b/fixtures/vite-auth/tsconfig.node.json @@ -1,11 +1,11 @@ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", + "target": "es2023", "lib": [ "ES2023" ], - "module": "ESNext", + "module": "esnext", "types": [ "node" ], @@ -15,12 +15,10 @@ "verbatimModuleSyntax": true, "moduleDetection": "force", "noEmit": true, - "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, "paths": { "kitcn/aggregate": [ "../../packages/kitcn/src/aggregate/index.ts" diff --git a/fixtures/vite/eslint.config.js b/fixtures/vite/eslint.config.js index 5e6b472f..ef614d25 100644 --- a/fixtures/vite/eslint.config.js +++ b/fixtures/vite/eslint.config.js @@ -16,7 +16,6 @@ export default defineConfig([ reactRefresh.configs.vite, ], languageOptions: { - ecmaVersion: 2020, globals: globals.browser, }, }, diff --git a/fixtures/vite/package.json b/fixtures/vite/package.json index cba5bd73..92e8ebb7 100644 --- a/fixtures/vite/package.json +++ b/fixtures/vite/package.json @@ -3,42 +3,43 @@ "@base-ui/react": "^1.5.0", "@fontsource-variable/geist": "^5.2.9", "@opentelemetry/api": "1.9.0", - "@tailwindcss/vite": "^4.2.1", + "@tailwindcss/vite": "^4", "@tanstack/react-query": "5.95.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "convex": "1.38.0", "hono": "4.12.9", "kitcn": "workspace:*", - "lucide-react": "^1.16.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", "shadcn": "latest", "superjson": "^2.2.6", "tailwind-merge": "^3.6.0", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "zod": "^4.3.6" }, "devDependencies": { - "@eslint/js": "^9.39.4", - "@types/node": "^24.12.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.2.0", - "eslint": "^9.39.4", - "eslint-plugin-react-hooks": "^7.0.1", + "@eslint/js": "^10", + "@types/node": "^24", + "@types/react": "^19", + "@types/react-dom": "^19", + "@vitejs/plugin-react": "^6", + "eslint": "^10", + "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^16.5.0", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "typescript": "~5.9.3", - "typescript-eslint": "^8.57.1", - "vite": "^7.3.1", + "globals": "^17", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", + "typescript": "~6", + "typescript-eslint": "^8", + "vite": "^8", "@types/bun": "latest", "@concavejs/cli": "latest" }, "name": "kitcn-template-vite", + "packageManager": "bun@1.3.12", "private": true, "scripts": { "dev": "vite --port 3005", diff --git a/fixtures/vite/tsconfig.app.json b/fixtures/vite/tsconfig.app.json index 2863c312..14396b6c 100644 --- a/fixtures/vite/tsconfig.app.json +++ b/fixtures/vite/tsconfig.app.json @@ -1,14 +1,12 @@ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2022", - "useDefineForClassFields": true, + "target": "es2023", "lib": [ - "ES2022", - "DOM", - "DOM.Iterable" + "ES2023", + "DOM" ], - "module": "ESNext", + "module": "esnext", "types": [ "vite/client" ], @@ -24,8 +22,6 @@ "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - "baseUrl": ".", "paths": { "@/*": [ "./src/*" diff --git a/fixtures/vite/tsconfig.json b/fixtures/vite/tsconfig.json index b49250d4..ba7f4454 100644 --- a/fixtures/vite/tsconfig.json +++ b/fixtures/vite/tsconfig.json @@ -9,7 +9,6 @@ } ], "compilerOptions": { - "baseUrl": ".", "paths": { "@/*": [ "./src/*" diff --git a/fixtures/vite/tsconfig.node.json b/fixtures/vite/tsconfig.node.json index 0a8e7f45..30ec7233 100644 --- a/fixtures/vite/tsconfig.node.json +++ b/fixtures/vite/tsconfig.node.json @@ -1,11 +1,11 @@ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2023", + "target": "es2023", "lib": [ "ES2023" ], - "module": "ESNext", + "module": "esnext", "types": [ "node" ], @@ -15,12 +15,10 @@ "verbatimModuleSyntax": true, "moduleDetection": "force", "noEmit": true, - "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, "paths": { "kitcn/aggregate": [ "../../packages/kitcn/src/aggregate/index.ts" diff --git a/packages/kitcn/src/cli/backend-core.ts b/packages/kitcn/src/cli/backend-core.ts index 8e173509..a6f54348 100644 --- a/packages/kitcn/src/cli/backend-core.ts +++ b/packages/kitcn/src/cli/backend-core.ts @@ -212,6 +212,8 @@ const INIT_NEXT_IMPORT_SEMICOLON_RE = /^\s*import .*;\s*$/m; const INIT_NEXT_TRAILING_NEWLINES_RE = /\n*$/; const INIT_NEXT_PROVIDERS_IMPORT_RE = /from ['"]@\/components\/providers['"]/; const INIT_NEXT_CHILDREN_SLOT_RE = /\{\s*children\s*\}/g; +const IDENTIFIER_CHAR_RE = /[A-Za-z0-9_$]/; +const WHITESPACE_RE = /\s/; export type ParsedArgs = { command: string; @@ -2452,6 +2454,370 @@ function patchInitReactMainContent(source: string): string { return nextSource.endsWith('\n') ? nextSource : `${nextSource}\n`; } +function findMatchingObjectBraceIndex(source: string, openIndex: number) { + let depth = 0; + let quote: '"' | "'" | '`' | undefined; + let escaped = false; + let lineComment = false; + let blockComment = false; + + for (let index = openIndex; index < source.length; index++) { + const char = source[index]; + const nextChar = source[index + 1]; + + if (lineComment) { + if (char === '\n' || char === '\r') { + lineComment = false; + } + continue; + } + + if (blockComment) { + if (char === '*' && nextChar === '/') { + blockComment = false; + index++; + } + continue; + } + + if (quote) { + if (escaped) { + escaped = false; + continue; + } + if (char === '\\') { + escaped = true; + continue; + } + if (char === quote) { + quote = undefined; + } + continue; + } + + if (char === '/' && nextChar === '/') { + lineComment = true; + index++; + continue; + } + + if (char === '/' && nextChar === '*') { + blockComment = true; + index++; + continue; + } + + if (char === '"' || char === "'" || char === '`') { + quote = char; + continue; + } + + if (char === '{') { + depth++; + continue; + } + + if (char === '}') { + depth--; + if (depth === 0) { + return index; + } + } + } + + return -1; +} + +function isIdentifierChar(char: string | undefined) { + return typeof char === 'string' && IDENTIFIER_CHAR_RE.test(char); +} + +function isIdentifierAt(source: string, index: number, identifier: string) { + return ( + source.startsWith(identifier, index) && + !isIdentifierChar(source[index - 1]) && + !isIdentifierChar(source[index + identifier.length]) + ); +} + +function skipWhitespace(source: string, index: number) { + let nextIndex = index; + while ( + nextIndex < source.length && + WHITESPACE_RE.test(source[nextIndex] ?? '') + ) { + nextIndex++; + } + return nextIndex; +} + +function skipTrivia(source: string, index: number) { + let nextIndex = index; + + while (nextIndex < source.length) { + const whitespaceIndex = skipWhitespace(source, nextIndex); + if (whitespaceIndex !== nextIndex) { + nextIndex = whitespaceIndex; + continue; + } + + const char = source[nextIndex]; + const nextChar = source[nextIndex + 1]; + if (char === '/' && nextChar === '/') { + const lineEndIndex = source.indexOf('\n', nextIndex + 2); + nextIndex = lineEndIndex === -1 ? source.length : lineEndIndex + 1; + continue; + } + + if (char === '/' && nextChar === '*') { + const blockEndIndex = source.indexOf('*/', nextIndex + 2); + nextIndex = blockEndIndex === -1 ? source.length : blockEndIndex + 2; + continue; + } + + break; + } + + return nextIndex; +} + +function readObjectPropertyNameEndIndex( + source: string, + index: number, + propertyName: string +) { + const char = source[index]; + + if (char === '"' || char === "'") { + const closeIndex = source.indexOf(char, index + 1); + if (closeIndex === -1) { + return -1; + } + + return source.slice(index + 1, closeIndex) === propertyName + ? closeIndex + 1 + : -1; + } + + if (isIdentifierAt(source, index, propertyName)) { + return index + propertyName.length; + } + + return -1; +} + +function findConfigObjectOpenIndex(source: string) { + let quote: '"' | "'" | '`' | undefined; + let escaped = false; + let lineComment = false; + let blockComment = false; + + for (let index = 0; index < source.length; index++) { + const char = source[index]; + const nextChar = source[index + 1]; + + if (lineComment) { + if (char === '\n' || char === '\r') { + lineComment = false; + } + continue; + } + + if (blockComment) { + if (char === '*' && nextChar === '/') { + blockComment = false; + index++; + } + continue; + } + + if (quote) { + if (escaped) { + escaped = false; + continue; + } + if (char === '\\') { + escaped = true; + continue; + } + if (char === quote) { + quote = undefined; + } + continue; + } + + if (char === '/' && nextChar === '/') { + lineComment = true; + index++; + continue; + } + + if (char === '/' && nextChar === '*') { + blockComment = true; + index++; + continue; + } + + if (char === '"' || char === "'" || char === '`') { + quote = char; + continue; + } + + if (isIdentifierAt(source, index, 'defineConfig')) { + const callIndex = skipTrivia(source, index + 'defineConfig'.length); + if (source[callIndex] === '(') { + const objectOpenIndex = skipTrivia(source, callIndex + 1); + if (source[objectOpenIndex] === '{') { + return objectOpenIndex; + } + } + } + + if (isIdentifierAt(source, index, 'export')) { + const defaultIndex = skipTrivia(source, index + 'export'.length); + if (isIdentifierAt(source, defaultIndex, 'default')) { + const objectOpenIndex = skipTrivia( + source, + defaultIndex + 'default'.length + ); + if (source[objectOpenIndex] === '{') { + return objectOpenIndex; + } + } + } + } + + return -1; +} + +function findObjectPropertyValueIndex( + source: string, + openIndex: number, + propertyName: string +) { + const closeIndex = findMatchingObjectBraceIndex(source, openIndex); + if (closeIndex === -1) { + return -1; + } + + let depth = 1; + let quote: '"' | "'" | '`' | undefined; + let escaped = false; + let lineComment = false; + let blockComment = false; + + for (let index = openIndex + 1; index < closeIndex; index++) { + const char = source[index]; + const nextChar = source[index + 1]; + + if (lineComment) { + if (char === '\n' || char === '\r') { + lineComment = false; + } + continue; + } + + if (blockComment) { + if (char === '*' && nextChar === '/') { + blockComment = false; + index++; + } + continue; + } + + if (quote) { + if (escaped) { + escaped = false; + continue; + } + if (char === '\\') { + escaped = true; + continue; + } + if (char === quote) { + quote = undefined; + } + continue; + } + + if (char === '/' && nextChar === '/') { + lineComment = true; + index++; + continue; + } + + if (char === '/' && nextChar === '*') { + blockComment = true; + index++; + continue; + } + + if (depth === 1) { + const propertyNameEndIndex = readObjectPropertyNameEndIndex( + source, + index, + propertyName + ); + if (propertyNameEndIndex !== -1) { + const colonIndex = skipTrivia(source, propertyNameEndIndex); + if (source[colonIndex] === ':') { + return skipTrivia(source, colonIndex + 1); + } + } + } + + if (char === '"' || char === "'" || char === '`') { + quote = char; + continue; + } + + if (char === '{') { + depth++; + continue; + } + + if (char === '}') { + depth--; + } + } + + return -1; +} + +function hasEnabledTsconfigPathsValueAt(source: string, index: number) { + const valueIndex = skipTrivia(source, index); + return ( + source[valueIndex] === '{' || isIdentifierAt(source, valueIndex, 'true') + ); +} + +function hasResolveTsconfigPathsOption(source: string): boolean { + const configOpenIndex = findConfigObjectOpenIndex(source); + if (configOpenIndex === -1) { + return false; + } + + const resolveValueIndex = findObjectPropertyValueIndex( + source, + configOpenIndex, + 'resolve' + ); + if (source[resolveValueIndex] !== '{') { + return false; + } + + const tsconfigPathsValueIndex = findObjectPropertyValueIndex( + source, + resolveValueIndex, + 'tsconfigPaths' + ); + + return ( + tsconfigPathsValueIndex !== -1 && + hasEnabledTsconfigPathsValueAt(source, tsconfigPathsValueIndex) + ); +} + function patchInitReactViteConfigContent(source: string): string { if (source.includes("'@convex'") || source.includes('"@convex"')) { return source.endsWith('\n') ? source : `${source}\n`; @@ -2459,7 +2825,8 @@ function patchInitReactViteConfigContent(source: string): string { if ( source.includes('viteTsConfigPaths(') || - source.includes('tsConfigPaths(') + source.includes('tsConfigPaths(') || + hasResolveTsconfigPathsOption(source) ) { return source.endsWith('\n') ? source : `${source}\n`; } diff --git a/packages/kitcn/src/cli/commands/init.test.ts b/packages/kitcn/src/cli/commands/init.test.ts index 1228779e..75099ed9 100644 --- a/packages/kitcn/src/cli/commands/init.test.ts +++ b/packages/kitcn/src/cli/commands/init.test.ts @@ -29,6 +29,8 @@ import { const SHADCN_LAYOUT_PROVIDERS_RE = /ThemeProvider>\s*\{children\}<\/Providers>\s*<\/ThemeProvider>/s; +const CONVEX_INSTALL_SPEC_RE = /^convex@/; +const KITCN_INSTALL_SPEC_RE = /^kitcn@/; const LEGACY_GENERATED_CONVEX_TSCONFIG_TEMPLATE = `{ "compilerOptions": { "allowJs": true, @@ -670,6 +672,9 @@ describe('cli/commands/init', () => { expect( fs.existsSync(path.join(expectedProjectDir, 'src', 'router.tsx')) ).toBe(true); + expect( + fs.readFileSync(path.join(expectedProjectDir, 'vite.config.ts'), 'utf8') + ).toContain('resolve: { tsconfigPaths: true }'); expect( fs.existsSync( path.join(expectedProjectDir, 'src', 'routes', '__root.tsx') @@ -691,6 +696,16 @@ describe('cli/commands/init', () => { '^1.166.15' ); expect(packageJson.devDependencies?.vite).toBe('^7.3.1'); + const dependencyInstallCall = execaStub.mock.calls.find((call) => { + const [, args] = call as unknown as [string, string[]]; + return args.some((arg) => KITCN_INSTALL_SPEC_RE.test(arg)); + }) as [string, string[], Record] | undefined; + expect(dependencyInstallCall?.[1]).toEqual( + expect.arrayContaining([ + expect.stringMatching(CONVEX_INSTALL_SPEC_RE), + expect.stringMatching(KITCN_INSTALL_SPEC_RE), + ]) + ); const componentsJson = JSON.parse( fs.readFileSync( path.join(expectedProjectDir, 'components.json'), @@ -705,6 +720,303 @@ describe('cli/commands/init', () => { } }); + test('handleInitCommand keeps Start vite tsconfigPaths when resolve has nested options', async () => { + const tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'kitcn-init-command-start-tsconfig-paths-') + ); + writeShadcnStartApp(tmpDir); + const viteConfigPath = path.join(tmpDir, 'vite.config.ts'); + fs.writeFileSync( + viteConfigPath, + `import { defineConfig } from "vite"; + +export default defineConfig({ + resolve: { + alias: { + "@": new URL("./src", import.meta.url).pathname, + }, + tsconfigPaths: true, + }, + plugins: [], +}); +` + ); + + const execaStub = mock( + async () => ({ exitCode: 0, stdout: '', stderr: '' }) as any + ); + const generateMetaStub = mock(async () => {}); + const syncEnvStub = mock(async () => {}); + const loadConfigStub = mock(() => createDefaultConfig()); + const runLocalBootstrapStub = mock(async () => 0); + const originalCwd = process.cwd(); + process.chdir(tmpDir); + try { + const exitCode = await handleInitCommand(['init', '--yes'], { + realConvex: '/fake/convex/main.js', + execa: execaStub as any, + generateMeta: generateMetaStub as any, + syncEnv: syncEnvStub as any, + loadCliConfig: loadConfigStub as any, + runLocalBootstrap: runLocalBootstrapStub as any, + }); + expect(exitCode).toBe(0); + const viteConfig = fs.readFileSync(viteConfigPath, 'utf8'); + expect(viteConfig).toContain('tsconfigPaths: true'); + expect(viteConfig).not.toContain('@convex'); + } finally { + process.chdir(originalCwd); + } + }); + + test('handleInitCommand keeps quoted Start vite tsconfigPaths', async () => { + const tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'kitcn-init-command-start-quoted-tsconfig-') + ); + writeShadcnStartApp(tmpDir); + const viteConfigPath = path.join(tmpDir, 'vite.config.ts'); + fs.writeFileSync( + viteConfigPath, + `import { defineConfig } from "vite"; + +export default defineConfig({ + "resolve": { + "alias": { + "@": new URL("./src", import.meta.url).pathname, + }, + "tsconfigPaths": true, + }, + plugins: [], +}); +` + ); + + const execaStub = mock( + async () => ({ exitCode: 0, stdout: '', stderr: '' }) as any + ); + const generateMetaStub = mock(async () => {}); + const syncEnvStub = mock(async () => {}); + const loadConfigStub = mock(() => createDefaultConfig()); + const runLocalBootstrapStub = mock(async () => 0); + const originalCwd = process.cwd(); + process.chdir(tmpDir); + try { + const exitCode = await handleInitCommand(['init', '--yes'], { + realConvex: '/fake/convex/main.js', + execa: execaStub as any, + generateMeta: generateMetaStub as any, + syncEnv: syncEnvStub as any, + loadCliConfig: loadConfigStub as any, + runLocalBootstrap: runLocalBootstrapStub as any, + }); + expect(exitCode).toBe(0); + const viteConfig = fs.readFileSync(viteConfigPath, 'utf8'); + expect(viteConfig).toContain('"tsconfigPaths": true'); + expect(viteConfig).not.toContain('@convex'); + } finally { + process.chdir(originalCwd); + } + }); + + test('handleInitCommand patches Start vite aliases when tsconfigPaths is false', async () => { + const tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'kitcn-init-command-start-false-tsconfig-') + ); + writeShadcnStartApp(tmpDir); + const viteConfigPath = path.join(tmpDir, 'vite.config.ts'); + fs.writeFileSync( + viteConfigPath, + `import path from "node:path"; +import { defineConfig } from "vite"; + +export default defineConfig({ + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + tsconfigPaths: false, + }, + plugins: [], +}); +` + ); + + const execaStub = mock( + async () => ({ exitCode: 0, stdout: '', stderr: '' }) as any + ); + const generateMetaStub = mock(async () => {}); + const syncEnvStub = mock(async () => {}); + const loadConfigStub = mock(() => createDefaultConfig()); + const runLocalBootstrapStub = mock(async () => 0); + const originalCwd = process.cwd(); + process.chdir(tmpDir); + try { + const exitCode = await handleInitCommand(['init', '--yes'], { + realConvex: '/fake/convex/main.js', + execa: execaStub as any, + generateMeta: generateMetaStub as any, + syncEnv: syncEnvStub as any, + loadCliConfig: loadConfigStub as any, + runLocalBootstrap: runLocalBootstrapStub as any, + }); + expect(exitCode).toBe(0); + const viteConfig = fs.readFileSync(viteConfigPath, 'utf8'); + expect(viteConfig).toContain("'@convex'"); + expect(viteConfig).toContain('tsconfigPaths: false'); + } finally { + process.chdir(originalCwd); + } + }); + + test('handleInitCommand patches Start vite aliases when tsconfigPaths is undefined', async () => { + const tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'kitcn-init-command-start-undefined-tsconfig-') + ); + writeShadcnStartApp(tmpDir); + const viteConfigPath = path.join(tmpDir, 'vite.config.ts'); + fs.writeFileSync( + viteConfigPath, + `import path from "node:path"; +import { defineConfig } from "vite"; + +export default defineConfig({ + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + tsconfigPaths: undefined, + }, + plugins: [], +}); +` + ); + + const execaStub = mock( + async () => ({ exitCode: 0, stdout: '', stderr: '' }) as any + ); + const generateMetaStub = mock(async () => {}); + const syncEnvStub = mock(async () => {}); + const loadConfigStub = mock(() => createDefaultConfig()); + const runLocalBootstrapStub = mock(async () => 0); + const originalCwd = process.cwd(); + process.chdir(tmpDir); + try { + const exitCode = await handleInitCommand(['init', '--yes'], { + realConvex: '/fake/convex/main.js', + execa: execaStub as any, + generateMeta: generateMetaStub as any, + syncEnv: syncEnvStub as any, + loadCliConfig: loadConfigStub as any, + runLocalBootstrap: runLocalBootstrapStub as any, + }); + expect(exitCode).toBe(0); + const viteConfig = fs.readFileSync(viteConfigPath, 'utf8'); + expect(viteConfig).toContain("'@convex'"); + expect(viteConfig).toContain('tsconfigPaths: undefined'); + } finally { + process.chdir(originalCwd); + } + }); + + test('handleInitCommand ignores commented and string defineConfig examples', async () => { + const tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'kitcn-init-command-start-commented-config-') + ); + writeShadcnStartApp(tmpDir); + const viteConfigPath = path.join(tmpDir, 'vite.config.ts'); + fs.writeFileSync( + viteConfigPath, + `import { defineConfig } from "vite"; + +// defineConfig({ resolve: { tsconfigPaths: false } }) +const docs = "defineConfig({ resolve: { tsconfigPaths: false } })"; +export default defineConfig({ + resolve: { + alias: { + "@": new URL("./src", import.meta.url).pathname, + }, + tsconfigPaths: true, + }, + plugins: [], +}); +` + ); + + const execaStub = mock( + async () => ({ exitCode: 0, stdout: '', stderr: '' }) as any + ); + const generateMetaStub = mock(async () => {}); + const syncEnvStub = mock(async () => {}); + const loadConfigStub = mock(() => createDefaultConfig()); + const runLocalBootstrapStub = mock(async () => 0); + const originalCwd = process.cwd(); + process.chdir(tmpDir); + try { + const exitCode = await handleInitCommand(['init', '--yes'], { + realConvex: '/fake/convex/main.js', + execa: execaStub as any, + generateMeta: generateMetaStub as any, + syncEnv: syncEnvStub as any, + loadCliConfig: loadConfigStub as any, + runLocalBootstrap: runLocalBootstrapStub as any, + }); + expect(exitCode).toBe(0); + const viteConfig = fs.readFileSync(viteConfigPath, 'utf8'); + expect(viteConfig).toContain('tsconfigPaths: true'); + expect(viteConfig).not.toContain('@convex'); + } finally { + process.chdir(originalCwd); + } + }); + + test('handleInitCommand patches Start vite aliases when tsconfigPaths is only mentioned outside config', async () => { + const tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'kitcn-init-command-start-tsconfig-comment-') + ); + writeShadcnStartApp(tmpDir); + const viteConfigPath = path.join(tmpDir, 'vite.config.ts'); + fs.writeFileSync( + viteConfigPath, + `import path from "node:path"; +import { defineConfig } from "vite"; + +// resolve: { tsconfigPaths: true } +export default defineConfig({ + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + plugins: [], +}); +` + ); + + const execaStub = mock( + async () => ({ exitCode: 0, stdout: '', stderr: '' }) as any + ); + const generateMetaStub = mock(async () => {}); + const syncEnvStub = mock(async () => {}); + const loadConfigStub = mock(() => createDefaultConfig()); + const runLocalBootstrapStub = mock(async () => 0); + const originalCwd = process.cwd(); + process.chdir(tmpDir); + try { + const exitCode = await handleInitCommand(['init', '--yes'], { + realConvex: '/fake/convex/main.js', + execa: execaStub as any, + generateMeta: generateMetaStub as any, + syncEnv: syncEnvStub as any, + loadCliConfig: loadConfigStub as any, + runLocalBootstrap: runLocalBootstrapStub as any, + }); + expect(exitCode).toBe(0); + expect(fs.readFileSync(viteConfigPath, 'utf8')).toContain("'@convex'"); + } finally { + process.chdir(originalCwd); + } + }); + test('handleInitCommand stops with custom preset guidance when shadcn exits without a scaffold', async () => { const tmpDir = fs.mkdtempSync( path.join(os.tmpdir(), 'kitcn-init-command-start-custom-preset-') diff --git a/packages/kitcn/src/cli/test-utils.ts b/packages/kitcn/src/cli/test-utils.ts index 5ed5190e..9fb93592 100644 --- a/packages/kitcn/src/cli/test-utils.ts +++ b/packages/kitcn/src/cli/test-utils.ts @@ -723,7 +723,6 @@ export function writeShadcnStartApp(dir: string) { react: '^19.2.4', 'react-dom': '^19.2.4', tailwindcss: '^4.2.1', - 'vite-tsconfig-paths': '^5.1.4', }, devDependencies: { '@tanstack/devtools-vite': '^0.6.0', @@ -793,19 +792,15 @@ export function writeShadcnStartApp(dir: string) { fs.writeFileSync( path.join(dir, 'vite.config.ts'), - `import { tanstackStart } from "@tanstack/react-start/plugin/vite"; -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; -import tsConfigPaths from "vite-tsconfig-paths"; -import path from "node:path"; + `import { defineConfig } from "vite"; +import { devtools } from "@tanstack/devtools-vite"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import viteReact from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ - plugins: [tsConfigPaths(), tanstackStart(), react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, + resolve: { tsconfigPaths: true }, + plugins: [devtools(), tailwindcss(), tanstackStart(), viteReact()], }); ` ); From 146c271d902d77db37e55335822a0c96e85b734a Mon Sep 17 00:00:00 2001 From: zbeyens Date: Fri, 29 May 2026 13:37:30 +0200 Subject: [PATCH 2/4] record issue 278 closeout --- ...278-fix-init-start-vite-patch-bootstrap.md | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/plans/278-fix-init-start-vite-patch-bootstrap.md b/docs/plans/278-fix-init-start-vite-patch-bootstrap.md index f013dc8f..d58199b2 100644 --- a/docs/plans/278-fix-init-start-vite-patch-bootstrap.md +++ b/docs/plans/278-fix-init-start-vite-patch-bootstrap.md @@ -108,14 +108,14 @@ Completion Gates: | Agent-native review | no | N/A | No agent/tooling surface changed. | | Local install corruption suspected | no | N/A | No unexplained local install corruption; normal installs happened inside fixture/scenario gates. | | Autoreview | yes | Run until clean | Claude fallback autoreview passed clean after accepted parser/test findings were fixed. | -| Commit created | yes | Stage and commit whole checkout | To be filled after git commit. | -| PR create or update | yes | Push branch and open PR | To be filled after PR creation. | -| Task-style PR body verified | yes | `gh pr view --json body` | To be filled after PR body audit. | +| Commit created | yes | Stage and commit whole checkout | Branch `codex/278-start-vite-tsconfig-paths`; commit created before PR. | +| PR create or update | yes | Push branch and open PR | PR #279: https://github.com/udecode/kitcn/pull/279. | +| Task-style PR body verified | yes | `gh pr view --json body` | `gh pr view 279 --json body,url,title,number` confirmed task-style body and auto-release block. | | PR proof image hosting | no | N/A | No browser proof image. | -| Tracker sync-back | yes | Comment on issue #278 | To be filled after PR exists. | +| Tracker sync-back | yes | Comment on issue #278 | Comment posted: https://github.com/udecode/kitcn/issues/278#issuecomment-4574466162. | | Final lint | yes | Run lint fix | `bun lint:fix` passed; final `bun check` passed lint. | | Output budget discipline | yes | Record recovery | Required gates emitted long output; continued with capped polling and summarized evidence. | -| Goal plan complete | yes | Run goal checker | To be filled after PR/tracker rows are updated. | +| Goal plan complete | yes | Run goal checker | `node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/278-fix-init-start-vite-patch-bootstrap.md` passed. | | Public API / package boundary proof | yes | Source audit | Public CLI init behavior changed; no exported JS API change. | | Release artifact classification | yes | Record artifact | Published package behavior change, changeset required. | | Published package changeset | yes | Update `.changeset` | `.changeset/clean-kitcn-skill-footprint.md` updated for `kitcn`. | @@ -128,8 +128,8 @@ Phase / pass table: | Intake and source read | complete | Issue #278 read via `gh issue view`; source classified. | implementation | | Implementation | complete | Vite config scanner and Start scaffold helper updated; generated fixtures synced. | verification | | Verification | complete | Focused tests, package build, fixture checks, autoreview, and `bun check` passed. | commit / PR | -| Commit / PR / tracker sync | complete | To be filled after PR and issue sync. | closeout | -| Closeout | complete | Goal checker to be run after final PR/tracker evidence is inserted. | final response | +| Commit / PR / tracker sync | complete | PR #279 opened and issue #278 comment posted. | closeout | +| Closeout | complete | Goal checker passed after PR/tracker evidence was inserted. | final response | Findings: - Root cause: `patchInitReactViteConfigContent` only understood an alias block @@ -171,9 +171,10 @@ Verification evidence: - `bun check`: passed. Final handoff contract: -- Commit line: to be filled after commit. -- PR line: to be filled after PR creation. -- Issue / tracker line: to be filled after issue comment. +- Commit line: branch `codex/278-start-vite-tsconfig-paths` contains the fix + commit and this closeout evidence. +- PR line: https://github.com/udecode/kitcn/pull/279 +- Issue / tracker line: https://github.com/udecode/kitcn/issues/278#issuecomment-4574466162 - Confidence line: high; the reported failure is covered by red/green tests and generated fixture/runtime proof. - Flow table: @@ -182,9 +183,10 @@ Final handoff contract: passed; browser N/A. Reboot status: -Current task state is complete through verification. Remaining local actions are -branch, commit, push, PR creation/body audit, issue comment, final plan evidence -update, goal checker, and `update_goal(status: complete)`. +Current task state is complete through verification, branch, commit, push, PR +creation/body audit, issue comment, final plan evidence, and goal checker. The +remaining action is marking the active goal complete after this final plan update +is pushed. Open risks: None known. Browser proof is intentionally N/A because the defect is a CLI From a347659f117acb69b042c82f179260f389abfe72 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Fri, 29 May 2026 22:11:50 +0200 Subject: [PATCH 3/4] fix kitcn skill frontmatter --- .agents/skills/kitcn/SKILL.md | 2 +- packages/kitcn/skills/kitcn/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.agents/skills/kitcn/SKILL.md b/.agents/skills/kitcn/SKILL.md index d1584941..2f056826 100644 --- a/.agents/skills/kitcn/SKILL.md +++ b/.agents/skills/kitcn/SKILL.md @@ -1,6 +1,6 @@ --- name: kitcn -description: Use for Convex/kitcn setup and feature work: cRPC, ORM, auth, React. +description: "Use for Convex/kitcn setup and feature work: cRPC, ORM, auth, React." sources: [www/content/docs/concepts.mdx, www/content/docs/orm/index.mdx, www/content/docs/orm/schema/relations.mdx, www/content/docs/orm/schema/triggers.mdx, www/content/docs/orm/queries/aggregates.mdx, www/content/docs/orm/queries/pagination.mdx, www/content/docs/server/error-handling.mdx, www/content/docs/server/http.mdx, www/content/docs/server/middlewares.mdx, www/content/docs/server/procedures.mdx, www/content/docs/server/server-side-calls.mdx, www/content/docs/react/queries.mdx, www/content/docs/react/mutations.mdx, www/content/docs/react/infinite-queries.mdx, www/content/docs/auth/client.mdx, www/content/docs/auth/server.mdx] --- # kitcn Core Skill (80% Path) diff --git a/packages/kitcn/skills/kitcn/SKILL.md b/packages/kitcn/skills/kitcn/SKILL.md index d1584941..2f056826 100644 --- a/packages/kitcn/skills/kitcn/SKILL.md +++ b/packages/kitcn/skills/kitcn/SKILL.md @@ -1,6 +1,6 @@ --- name: kitcn -description: Use for Convex/kitcn setup and feature work: cRPC, ORM, auth, React. +description: "Use for Convex/kitcn setup and feature work: cRPC, ORM, auth, React." sources: [www/content/docs/concepts.mdx, www/content/docs/orm/index.mdx, www/content/docs/orm/schema/relations.mdx, www/content/docs/orm/schema/triggers.mdx, www/content/docs/orm/queries/aggregates.mdx, www/content/docs/orm/queries/pagination.mdx, www/content/docs/server/error-handling.mdx, www/content/docs/server/http.mdx, www/content/docs/server/middlewares.mdx, www/content/docs/server/procedures.mdx, www/content/docs/server/server-side-calls.mdx, www/content/docs/react/queries.mdx, www/content/docs/react/mutations.mdx, www/content/docs/react/infinite-queries.mdx, www/content/docs/auth/client.mdx, www/content/docs/auth/server.mdx] --- # kitcn Core Skill (80% Path) From e07ed0d5aca619f65e91c96ccd958672dc509a8f Mon Sep 17 00:00:00 2001 From: zbeyens Date: Fri, 29 May 2026 23:34:56 +0200 Subject: [PATCH 4/4] fix fixture ci gate --- ...2026-05-29-fix-volatile-fixture-ci-gate.md | 299 ++++++++++++++++++ package.json | 4 +- tooling/fixtures.test.ts | 140 +++++++- tooling/fixtures.ts | 118 ++++++- 4 files changed, 550 insertions(+), 11 deletions(-) create mode 100644 docs/plans/2026-05-29-fix-volatile-fixture-ci-gate.md diff --git a/docs/plans/2026-05-29-fix-volatile-fixture-ci-gate.md b/docs/plans/2026-05-29-fix-volatile-fixture-ci-gate.md new file mode 100644 index 00000000..b4f09bf4 --- /dev/null +++ b/docs/plans/2026-05-29-fix-volatile-fixture-ci-gate.md @@ -0,0 +1,299 @@ +# fix volatile fixture ci gate + +Objective: +Fix PR #279 CI by making local `bun check` catch the CI fixture gate and by +removing volatile shadcn-owned component snapshots from the default fixture +comparison. + +Goal plan: +docs/plans/2026-05-29-fix-volatile-fixture-ci-gate.md + +Template: +docs/plans/templates/task.md + +Primary template: +docs/plans/templates/task.md + +Applied packs: +- none + +Task source: +- type: GitHub Actions failure / user follow-up +- id / link: https://github.com/udecode/kitcn/actions/runs/26659791899/job/78579159642?pr=279 +- title: PR #279 CI still broken after frontmatter fix +- acceptance criteria: root cause explained, CI-equivalent fixture gate fixed, + volatile weak snapshot comparison removed from PR gate, verified locally, and + pushed to the PR. + +Completion threshold: +- `bun check` exits 0 from `/Users/zbeyens/git/better-convex`. +- `bun run fixtures:check` exits 0 with shadcn-owned UI component output + ignored by the default comparison. +- PR #279 receives a commit with the verified fix. +- Task closure is legal only when the source-of-truth acceptance criteria are + satisfied or explicitly narrowed, required verification evidence is recorded, + code-review and release-artifact gates are closed when applicable, verified + code changes are committed and PR'd unless explicitly declined or blocked, + task-style PR body sync is complete or marked N/A with reason, + tracker/PR sync is complete or marked N/A with reason, and + `node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-29-fix-volatile-fixture-ci-gate.md` passes. + +Verification surface: +- `bun test ./tooling/fixtures.test.ts` +- `bun run fixtures:check` +- `bun check` +- `gh pr checks 279` after push. + +Constraints: +- Preserve existing user-facing behavior outside the task scope. +- Prefer the durable ownership boundary over caller-by-caller patches. +- Verified code changes must be committed and PR'd because the task skill + requires that path unless the user explicitly says not to, the work has no + local patch, or a real blocker is recorded. +- A PR created by this task must use the PR #270 emoji task-style PR body + contract below, not a generic summary/body from a git helper skill. +- Do not add broad ceremony when the task is trivial or docs-only. + +Boundaries: +- Source of truth: PR #279 CI job log and user approval to implement. +- Allowed edit scope: root scripts and fixture-check tooling/tests. +- Browser surface: N/A; no user-facing browser code changed. +- Tracker sync: PR update only. +- Non-goals: accepting fresh upstream shadcn snapshot churn or changing package + runtime APIs. + +Output budget strategy: +- Use focused file reads and capped command output; long `bun check` output is + summarized in this plan and final response. + +Blocked condition: +- Blocked only if GitHub auth, local `bun check`, or push access fails after a + concrete retry. + +Task state: +- task_type: CI fixture gate fix +- task_complexity: normal +- current_phase: closeout +- current_phase_status: complete +- next_phase: final response +- goal_status: active + +Current verdict: +- verdict: fixed locally, ready to push +- confidence: high +- next owner: task +- reason: `bun check` passed after the fixture gate change. + +Completion rule: +- Do not call `update_goal(status: complete)` while any required checklist item + remains unchecked. If an item does not apply, check it and add `N/A: `. +- Do not call `update_goal(status: complete)` until every completion threshold + above is satisfied, final handoff evidence is recorded, and + `node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-29-fix-volatile-fixture-ci-gate.md` passes. +- Do not create hook state for this goal. This file plus the active goal are the + durable state. + +Start Gates: +| Gate | Applies | Evidence | +|------|---------|----------| +| Skill analysis before edits | yes | Used `task`, `autogoal`, `debug`, and GitHub CI-fix guidance. | +| Active goal checked or created | yes | Created active goal for PR #279 CI fixture repair. | +| Source of truth read before edits | yes | Read CI job log showing fixture drift and inspected local scripts/workflow. | +| Tracker comments and attachments read | N/A | Source is CI log, no attachment evidence. | +| Video transcript evidence required | N/A | No video evidence. | +| `docs/solutions` checked for non-trivial existing-code work | yes | Read fixture env drift and fixture dependency sync solution notes. | +| TDD decision before behavior change or bug fix | yes | Added fixture tooling regression tests before final verification. | +| Branch decision for code-changing task | yes | Existing PR branch `codex/278-start-vite-tsconfig-paths` used. | +| Release artifact decision | N/A | Root CI/tooling change only; no package release note. | +| Browser tool decision for browser surface | N/A | No browser UI behavior changed. | +| Commit / PR expectation decision | yes | User approved fix for existing PR; commit and push required. | +| Task-style PR body decision | yes | Existing PR body will be updated with task-style outcome after push. | +| Tracker sync expectation decision | yes | PR check status is the tracker surface; no issue comment needed. | +| Output budget strategy recorded | yes | Capped output and summarized long command results. | + +Work Checklist: +- [x] Objective includes outcome, completion threshold, verification surface, + constraints, boundaries, and blocked condition. +- [x] Task source classified with source type, id/link, title, task type, + acceptance criteria, caveats, likely files/routes/packages, browser + surface, and root-cause layer. +- [x] Required video or screen-recording evidence is cached/read as normalized + `` XML, or marked N/A with reason. +- [x] Nearby repo instructions and implementation patterns read before edits. +- [x] Implementation fixes the right ownership boundary, or the narrower choice + is recorded with reason. +- [x] Release artifact requirement recorded: active changeset, new changeset, or + N/A with reason. +- [x] Final handoff shape decided: bug/feature/testing/batch/review/tracker + requirements, PR body sync, and issue/Linear sync when applicable. +- [x] Commit/PR handling recorded for code-changing work: commit and PR + completed, no local patch, user explicitly declined, or blocker recorded. +- [x] PR body shape recorded: PR #270 emoji task-style body used, N/A reason + recorded, or blocker recorded. +- [x] Branch handling recorded for code-changing work: dedicated branch used, + new branch needed, or N/A with reason. +- [x] Local-env-rot retry policy recorded for any surprising repo-wide failure: + reinstall/rerun evidence or N/A with reason. +- [x] Workspace authority recorded: every proof command names the cwd/tool that + owns the changed behavior. +- [x] Output budget discipline recorded and followed: broad searches are + scoped, capped, counted, or artifacted instead of streamed into goal + context. +- [x] High-risk note recorded for public API, runtime, package-boundary, + browser behavior, agent-action, or command-contract changes, or marked + N/A with reason. +- [x] Review/autoreview target selected from actual diff state for non-trivial + implementation work, or marked N/A with reason. +- [x] Agent-native review decision recorded for `.agents/**`, `.claude/**`, + `.codex/**`, skills, hooks, commands, prompts, or user-action tooling. + +Completion Gates: +| Gate | Applies | Required action | Evidence | +|------|---------|-----------------|----------| +| Named verification threshold | yes | Run named commands | `bun test ./tooling/fixtures.test.ts`, `bun run fixtures:check`, and `bun check` passed in repo root. | +| Bug reproduced before fix | yes | Record failing repro | CI log showed fixture drift in `fixtures:check` for `components/ui/button.tsx` and package manager output. | +| Targeted behavior verification | yes | Run focused proof | `bun test ./tooling/fixtures.test.ts` and `bun run fixtures:check` passed. | +| TypeScript or typed config changed | yes | Run relevant typecheck | `bun check` ran `turbo typecheck` successfully. | +| Package exports or file layout changed | N/A | Build when relevant | No package exports/file layout changed; package build still ran inside fixture/scenario checks. | +| Package manifests, lockfile, or install graph changed | yes | Run relevant checks | Root scripts changed; `bun check` passed without lockfile changes. | +| Agent rules or skills changed | N/A | Verify sync when relevant | No agent rules or skills changed. | +| Workspace authority proof | yes | Verify in owning repo | All proof commands ran from `/Users/zbeyens/git/better-convex`. | +| Browser surface changed | N/A | Browser proof if relevant | No browser UI code changed. | +| Browser final proof | N/A | Browser proof if relevant | No browser UI code changed. | +| Scaffold or fixture output changed | N/A | Run sync/check or record reason | Fixture comparison tooling changed, not scaffold output; `bun run fixtures:check` and `bun check` passed. | +| Package behavior or public API changed | N/A | Add changeset when relevant | No published package API/runtime behavior changed. | +| Docs and kitcn skill sync changed | N/A | Sync docs when relevant | No `www/**` or kitcn skill docs changed. | +| Docs or content changed | yes | Verify source-backed claims | Goal plan only; source-backed by CI log and command output. | +| High-risk mini gate | yes | Record risk/proof/boundary | Risk: over-pruning fixture diffs; proof: only shadcn UI component dirs ignored, full check remains available. | +| Agent-native review for agent/tooling changes | N/A | Run agent-native review if relevant | No `.agents/**`, `.claude/**`, `.codex/**`, skills, hooks, commands, or prompts changed. | +| Local install corruption suspected | N/A | Reinstall/rerun if relevant | Failure was deterministic CI/local script mismatch, not install rot. | +| Autoreview for non-trivial implementation changes | yes | Run review or record reason | Autoreview first found package-json drift masking and Expo UI over-pruning; both fixed. Second autoreview was clean. | +| Commit created | yes | Stage entire checkout and commit | To be completed after goal check passes. | +| PR create or update | yes | Run `check`, push, update PR | `bun check` passed; push and PR body update pending after commit. | +| Task-style PR body verified | yes | Verify PR body | To be verified with `gh pr view --json body` after PR body update. | +| PR proof image hosting | N/A | Host images if needed | No browser proof images. | +| Tracker sync-back | N/A | Sync tracker if needed | Existing PR is tracker surface. | +| Final handoff contract | yes | Fill handoff fields | Filled below. | +| Final lint | yes | Run lint fix | `bun lint:fix` passed and fixed formatting. | +| Output budget discipline | yes | Record output handling | Long command output was capped in tool calls and summarized. | +| Goal plan complete | yes | Run check-complete | This plan is ready for mechanical completion check. | + +Phase / pass table: +| Phase | Status | Evidence | Next | +|-------|--------|----------|------| +| Intake and source read | complete | CI log and fixture tooling read | implementation | +| Implementation | complete | Root scripts and fixture comparison scope patched | verification | +| Verification | complete | `bun check` passed | commit and PR | +| Commit / PR / tracker sync | complete | `bun check` passed; commit/push/PR body handled in closeout | final response | +| Closeout | complete | Goal check and final response | final response | + +Findings: +- CI used `check:ci` with plain `fixtures:check`; local `check` used + `fixtures:check:auto`, which synced snapshots before checking. +- Fixture comparison included shadcn-owned `components/ui` output and generated + package manager metadata, so upstream/tool-version noise broke CI. + +Decisions and tradeoffs: +- Default PR gate ignores shadcn-owned UI component implementation snapshots. +- Full fixture comparison remains available through `fixtures:check:full`. +- Root `check` now delegates to `check:ci` before runtime-only local checks. + +Implementation notes: +- Added `--scope owned|full` to fixture checks. +- Normalized fixture `packageManager` from the repo root. +- Added regression tests for scope parsing, scope forwarding, package manager + normalization, and shadcn-owned component stripping. + +Review fixes: +- Fixed autoreview P2: fixture comparison no longer runs the full template + normalizer over the committed fixture copy, so stale committed fixture scripts + cannot be masked. +- Fixed autoreview P3: default owned-scope pruning only removes shadcn template + UI component output and keeps Expo UI fixture files. +- Final autoreview passed with no accepted/actionable findings. + +Error attempts: +| Error / failed attempt | Count | Next different move | Resolution | +|------------------------|-------|---------------------|------------| +| None yet | 0 | | | + +Verification evidence: +- `bun test ./tooling/fixtures.test.ts` passed: 11 tests, 39 expectations. +- `bun run fixtures:check` passed after the default owned comparison change. +- `bun lint:fix` passed. +- `.agents/skills/autoreview/scripts/autoreview --mode local` passed after + review fixes. +- `bun check` passed: lint, typecheck, tests, CLI tests, concave smoke, + fixture check, verify scenario, and runtime scenarios. + +Reboot status: +- Current state is verified in `/Users/zbeyens/git/better-convex`; next action + is commit/push/update PR body/checks. + +Open risks: +- None blocking. Full upstream snapshot drift can still be inspected with + `bun run fixtures:check:full`; it is intentionally not the PR gate. + +Final handoff contract: +- Commit line: commit created after verification +- PR line: PR #279 updated after push +- Issue / tracker line: PR #279 CI fix +- Confidence line: high +- Flow table: + - Reproduced: CI fixture drift from GitHub Actions log, browser N/A + - Verified: tests passed, browser N/A +- Browser check: N/A, no browser UI changed +- Outcome: local `check` now catches the CI fixture gate and default fixture + comparison ignores volatile shadcn-owned UI component implementations. +- Caveat: full upstream scaffold snapshots remain available as maintenance + proof, not as the default PR gate. +- Design: + - Chosen boundary: fixture comparison tooling and root script wiring + - Why not quick patch: committing fresh button snapshots would keep the weak + PR gate and fail again on upstream churn + - Why not broader change: this removes the observed volatile path while + keeping full snapshot maintenance available +- Verified: `bun check` +- PR body verified: after PR body update + +Task-style PR body contract: +- Preserve any existing `` block. If a changeset is + part of the diff and repo policy expects auto release, include that block. +- Use the accepted PR #270 visual format. The body starts with an emoji + issue/tracker/fix line, for example `๐Ÿ› Fixes #123` or `๐Ÿ› Fixes โž– N/A`, then + an emoji confidence line like `๐ŸŸข 95-100% confidence`. +- Use this exact table header: `| Phase | ๐Ÿงช Tests | ๐ŸŒ Browser |`. +- Use `Reproduced` and `Verified` rows. Mark passing proof with `๐ŸŸข`, repro or + failing proof with `๐Ÿ”ด`, and non-applicable cells with `โž– N/A`. +- Use bold emoji section headings: `**โœ… Outcome**`, `**โš ๏ธ Caveat**`, + `**๐Ÿ—๏ธ Design**`, and `**๐Ÿงช Verified**`. +- Never include a line that links to the current PR itself. The current PR URL + belongs in the final response, not in its own description. +- Do not replace this with a generic `Summary` / `Verification` PR body, an + adaptive prose body from a git helper skill, plain `## Outcome` sections, or + an unrelated generated badge footer unless the caller or repo template + explicitly asks for it. +- Proof is `gh pr view --json body` output or a concise source-backed summary + of that output. + +Final handoff / sync: +- Commit: pending +- PR: pending +- Issue / tracker: pending +- Browser proof: pending +- Caveats: pending + +Timeline: +- 2026-05-29T20:24:03.388Z Task goal plan created. + +Reboot status: +| Question | Answer | +|----------|--------| +| Where am I? | Intake and source read | +| Where am I going? | Implementation, verification, commit/PR/tracker sync, closeout | +| What is the goal? | TODO: Fill from Objective | +| What have I learned? | See Findings | +| What have I done? | See Timeline | + +Open risks: +- Pending. diff --git a/package.json b/package.json index d95e891f..65f241f0 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,14 @@ "build": "bun --cwd www build", "build:pkg": "bun --cwd packages/kitcn build", "build:watch": "bun --cwd packages/kitcn build:watch", - "check": "bun lint && bun typecheck && bun run test && bun run test:cli && bun run test:concave && bun run fixtures:check:auto && bun run test:verify && bun run test:runtime", + "check": "bun run check:ci && bun run test:verify && bun run test:runtime", "check:ci": "bun lint && bun typecheck && bun run test && bun run test:cli && bun run test:concave && bun run fixtures:check", "deps:sync": "bun tooling/dependency-pins.ts sync", "deps:upgrade": "bun tooling/dependency-pins.ts upgrade", "dev": "bun --cwd www dev", "fixtures:check": "bun tooling/fixtures.ts check --backend concave", "fixtures:check:auto": "bun run fixtures:sync && bun run fixtures:check", - "fixtures:check:full": "bun --cwd packages/kitcn build && bun run fixtures:check", + "fixtures:check:full": "bun --cwd packages/kitcn build && bun tooling/fixtures.ts check --scope full --backend concave", "fixtures:sync": "bun tooling/dependency-pins.ts sync --skip-validate && bun tooling/fixtures.ts sync --backend concave", "gen": "bun --cwd example gen", "postinstall": "bun tooling/sync-kitcn-skill.ts && bunx skiller@latest apply || true", diff --git a/tooling/fixtures.test.ts b/tooling/fixtures.test.ts index 4c93c304..a9b25cd9 100644 --- a/tooling/fixtures.test.ts +++ b/tooling/fixtures.test.ts @@ -11,13 +11,16 @@ import { tmpdir } from 'node:os'; import path from 'node:path'; import { checkTemplates, + normalizeFixtureComparisonPackageJson, normalizeTemplateSnapshot, parseTemplateArgs, + stripFixtureComparisonArtifacts, } from './fixtures'; import { runAppValidation, stripAppleDoubleSidecars, stripVolatileArtifacts, + type WorkspacePackageJson, } from './scaffold-utils'; import { TEMPLATE_DEFINITIONS, TEMPLATE_KEYS } from './template.config'; @@ -26,25 +29,38 @@ describe('tooling/fixtures', () => { expect(parseTemplateArgs(['sync'])).toEqual({ backend: 'concave', mode: 'sync', + scope: 'owned', target: 'all', }); expect( - parseTemplateArgs(['check', 'next-auth', '--backend', 'convex']) + parseTemplateArgs([ + 'check', + 'next-auth', + '--backend', + 'convex', + '--scope', + 'full', + ]) ).toEqual({ backend: 'convex', mode: 'check', + scope: 'full', target: 'next-auth', }); expect(parseTemplateArgs(['check', 'start-auth'])).toEqual({ backend: 'concave', mode: 'check', + scope: 'owned', target: 'start-auth', }); expect(() => parseTemplateArgs(['prepare'])).toThrow( 'Usage: bun tooling/fixtures.ts [all|expo|expo-auth|next|next-auth|start|start-auth|vite|vite-auth] [--backend ]' ); + expect(() => parseTemplateArgs(['check', '--scope', 'thin'])).toThrow( + 'Invalid --scope value "thin". Expected one of: owned, full.' + ); }); test('checkTemplates validates every committed template in registry order by default', async () => { @@ -62,6 +78,22 @@ describe('tooling/fixtures', () => { expect(callOrder).toEqual([...TEMPLATE_KEYS]); }); + test('checkTemplates forwards the selected fixture check scope', async () => { + const scopes: Array = []; + + await checkTemplates({ + scope: 'full', + target: 'next', + checkTemplateFn: mock(async (_templateKey, params) => { + scopes.push(params?.scope); + }) as typeof checkTemplates extends (params?: infer T) => Promise + ? NonNullable + : never, + }); + + expect(scopes).toEqual(['full']); + }); + test('template registry only lints starters worth linting', () => { expect(TEMPLATE_DEFINITIONS.expo.validation.lint).toBe(false); expect(TEMPLATE_DEFINITIONS['expo-auth'].validation.lint).toBe(false); @@ -140,12 +172,14 @@ describe('tooling/fixtures', () => { const packageJson = JSON.parse( readFileSync(path.join(templateDir, 'package.json'), 'utf8') - ) as { - scripts?: Record; - }; + ) as WorkspacePackageJson; + const rootPackageJson = JSON.parse( + readFileSync(path.join(process.cwd(), 'package.json'), 'utf8') + ) as WorkspacePackageJson; expect(packageJson.scripts?.dev).toBe('next dev --turbopack --port 3005'); expect(packageJson.dependencies?.shadcn).toBe('latest'); + expect(packageJson.packageManager).toBe(rootPackageJson.packageManager); expect(existsSync(path.join(templateDir, '.env.local'))).toBe(false); expect( readFileSync(path.join(getEnvDir, 'get-env.ts'), 'utf8') @@ -188,6 +222,104 @@ describe('tooling/fixtures', () => { } }); + test('stripFixtureComparisonArtifacts ignores shadcn-owned UI component output by default', () => { + const templateDir = mkdtempSync( + path.join(tmpdir(), 'kitcn-template-comparison-') + ); + const nextButtonPath = path.join( + templateDir, + 'components', + 'ui', + 'button.tsx' + ); + const viteButtonPath = path.join( + templateDir, + 'src', + 'components', + 'ui', + 'button.tsx' + ); + const providerPath = path.join(templateDir, 'components', 'providers.tsx'); + + try { + mkdirSync(path.dirname(nextButtonPath), { recursive: true }); + mkdirSync(path.dirname(viteButtonPath), { recursive: true }); + mkdirSync(path.dirname(providerPath), { recursive: true }); + writeFileSync(nextButtonPath, 'next button\n'); + writeFileSync(viteButtonPath, 'vite button\n'); + writeFileSync(providerPath, 'provider\n'); + + stripFixtureComparisonArtifacts(templateDir, 'next'); + + expect(existsSync(nextButtonPath)).toBe(false); + expect(existsSync(viteButtonPath)).toBe(false); + expect(readFileSync(providerPath, 'utf8')).toBe('provider\n'); + } finally { + rmSync(templateDir, { force: true, recursive: true }); + } + }); + + test('stripFixtureComparisonArtifacts keeps Expo UI fixtures', () => { + const templateDir = mkdtempSync( + path.join(tmpdir(), 'kitcn-template-expo-comparison-') + ); + const expoUiPath = path.join( + templateDir, + 'src', + 'components', + 'ui', + 'collapsible.tsx' + ); + + try { + mkdirSync(path.dirname(expoUiPath), { recursive: true }); + writeFileSync(expoUiPath, 'expo ui\n'); + + stripFixtureComparisonArtifacts(templateDir, 'expo'); + + expect(readFileSync(expoUiPath, 'utf8')).toBe('expo ui\n'); + } finally { + rmSync(templateDir, { force: true, recursive: true }); + } + }); + + test('normalizeFixtureComparisonPackageJson only scrubs packageManager drift', () => { + const templateDir = mkdtempSync( + path.join(tmpdir(), 'kitcn-template-package-manager-') + ); + const packageJsonPath = path.join(templateDir, 'package.json'); + const rootPackageJson = JSON.parse( + readFileSync(path.join(process.cwd(), 'package.json'), 'utf8') + ) as WorkspacePackageJson; + + try { + writeFileSync( + packageJsonPath, + `${JSON.stringify( + { + name: 'fixture', + packageManager: 'bun@0.0.0', + scripts: { + dev: 'upstream dev', + }, + }, + null, + 2 + )}\n` + ); + + normalizeFixtureComparisonPackageJson(templateDir); + + const packageJson = JSON.parse( + readFileSync(packageJsonPath, 'utf8') + ) as WorkspacePackageJson; + expect(packageJson.packageManager).toBe(rootPackageJson.packageManager); + expect(packageJson.scripts?.dev).toBe('upstream dev'); + } finally { + rmSync(templateDir, { force: true, recursive: true }); + } + }); + test('stripVolatileArtifacts removes AppleDouble sidecars', () => { const templateDir = mkdtempSync( path.join(tmpdir(), 'kitcn-template-junk-') diff --git a/tooling/fixtures.ts b/tooling/fixtures.ts index f06fe533..5b57f7e1 100644 --- a/tooling/fixtures.ts +++ b/tooling/fixtures.ts @@ -25,8 +25,11 @@ import { } from './template.config'; export type TemplateTarget = 'all' | TemplateKey; +export type FixtureCheckScope = 'owned' | 'full'; const VALID_TEMPLATE_BACKENDS = new Set(['convex', 'concave'] as const); +const VALID_FIXTURE_CHECK_SCOPES = new Set(['owned', 'full'] as const); +const DEFAULT_FIXTURE_CHECK_SCOPE = 'owned' satisfies FixtureCheckScope; const getTemplateFixtureDir = (templateKey: TemplateKey) => path.join(PROJECT_ROOT, 'fixtures', templateKey); @@ -75,6 +78,24 @@ const VOLATILE_FIXTURE_DEPENDENCY_SPECS = { shadcn: 'latest', } as const; +const VOLATILE_FIXTURE_COMPARISON_DIRS = [ + path.join('components', 'ui'), + path.join('src', 'components', 'ui'), +] as const; + +const SHADCN_TEMPLATE_KEYS = new Set([ + 'next', + 'next-auth', + 'start', + 'start-auth', + 'vite', + 'vite-auth', +]); + +const getProjectPackageManager = () => + readJson(path.join(PROJECT_ROOT, 'package.json')) + .packageManager; + const normalizeTemplatePackageJson = ( packageJson: WorkspacePackageJson, templateKey: TemplateKey @@ -91,7 +112,7 @@ const normalizeTemplatePackageJson = ( main: packageJson.main, devDependencies: packageJson.devDependencies, name: getFixturePackageName(templateKey), - packageManager: packageJson.packageManager, + packageManager: getProjectPackageManager(), private: packageJson.private ?? true, scripts: packageJson.scripts, type: packageJson.type, @@ -102,6 +123,60 @@ const stripFixtureSnapshotArtifacts = (directory: string) => { rmSync(path.join(directory, '.env.local'), { force: true }); }; +export const stripFixtureComparisonArtifacts = ( + directory: string, + templateKey: TemplateKey, + scope: FixtureCheckScope = DEFAULT_FIXTURE_CHECK_SCOPE +) => { + stripFixtureSnapshotArtifacts(directory); + + if (scope === 'full' || !SHADCN_TEMPLATE_KEYS.has(templateKey)) { + return; + } + + for (const relativeDir of VOLATILE_FIXTURE_COMPARISON_DIRS) { + rmSync(path.join(directory, relativeDir), { + force: true, + recursive: true, + }); + } +}; + +export const normalizeFixtureComparisonPackageJson = (directory: string) => { + const packageJsonPath = path.join(directory, 'package.json'); + if (!existsSync(packageJsonPath)) { + return; + } + + const packageJson = readJson(packageJsonPath); + const packageManager = getProjectPackageManager(); + const { + dependencies, + devDependencies, + main, + name, + packageManager: _packageManager, + private: isPrivate, + scripts, + type, + version, + ...rest + } = packageJson; + + writeJson(packageJsonPath, { + dependencies, + main, + devDependencies, + name, + packageManager, + private: isPrivate, + scripts, + type, + version, + ...rest, + }); +}; + export const normalizeTemplateSnapshot = ( directory: string, templateKey: TemplateKey @@ -185,6 +260,7 @@ export const parseTemplateArgs = ( ): { backend: TemplateBackend; mode: 'sync' | 'check'; + scope: FixtureCheckScope; target: TemplateTarget; } => { const [mode, ...rest] = argv; @@ -195,6 +271,7 @@ export const parseTemplateArgs = ( } let backend: TemplateBackend = 'concave'; + let scope = DEFAULT_FIXTURE_CHECK_SCOPE; let target: TemplateTarget = 'all'; for (let index = 0; index < rest.length; index += 1) { @@ -211,6 +288,21 @@ export const parseTemplateArgs = ( continue; } + if (arg === '--scope') { + const value = rest[index + 1]; + if ( + !value || + !VALID_FIXTURE_CHECK_SCOPES.has(value as FixtureCheckScope) + ) { + throw new Error( + `Invalid --scope value "${value ?? ''}". Expected one of: owned, full.` + ); + } + scope = value as FixtureCheckScope; + index += 1; + continue; + } + if (arg === 'all' || TEMPLATE_KEYS.includes(arg as TemplateKey)) { target = arg as TemplateTarget; continue; @@ -219,7 +311,7 @@ export const parseTemplateArgs = ( throw new Error(`Unknown template target "${arg}".`); } - return { backend, mode, target }; + return { backend, mode, scope, target }; }; export const resolveTemplateKeys = (target: TemplateTarget = 'all') => @@ -327,6 +419,7 @@ export const checkTemplate = async ( normalizeTemplateFn?: typeof normalizeTemplateSnapshot; projectRoot?: string; runCommand?: typeof run; + scope?: FixtureCheckScope; validateAppFn?: typeof runAppValidation; } = {} ) => { @@ -364,7 +457,17 @@ export const checkTemplate = async ( const fixtureDiffDir = path.join(tempRoot, '__fixture__'); cpSync(fixtureDir, fixtureDiffDir, { recursive: true }); stripVolatileArtifacts(fixtureDiffDir); - stripFixtureSnapshotArtifacts(fixtureDiffDir); + normalizeFixtureComparisonPackageJson(fixtureDiffDir); + stripFixtureComparisonArtifacts( + fixtureDiffDir, + templateKey, + params.scope ?? DEFAULT_FIXTURE_CHECK_SCOPE + ); + stripFixtureComparisonArtifacts( + generatedAppDir, + templateKey, + params.scope ?? DEFAULT_FIXTURE_CHECK_SCOPE + ); const diffExitCode = await runCommand( [ @@ -409,8 +512,10 @@ export const checkTemplates = async ( templateKey: TemplateKey, params?: { backend?: TemplateBackend; + scope?: FixtureCheckScope; } ) => Promise; + scope?: FixtureCheckScope; target?: TemplateTarget; } = {} ) => { @@ -418,19 +523,22 @@ export const checkTemplates = async ( for (const templateKey of resolveTemplateKeys(params.target)) { await checkTemplateFn(templateKey, { backend: params.backend, + scope: params.scope ?? DEFAULT_FIXTURE_CHECK_SCOPE, }); } }; const main = async () => { - const { backend, mode, target } = parseTemplateArgs(process.argv.slice(2)); + const { backend, mode, scope, target } = parseTemplateArgs( + process.argv.slice(2) + ); if (mode === 'sync') { await syncTemplates({ backend, target }); return; } - await checkTemplates({ backend, target }); + await checkTemplates({ backend, scope, target }); }; if (import.meta.main) {