@forinda/kickjs-cli 8.2.4 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{agent-docs-C6ZYsFJV.mjs → agent-docs-CpoUa3NG.mjs} +3 -3
  2. package/dist/{agent-docs-C6ZYsFJV.mjs.map → agent-docs-CpoUa3NG.mjs.map} +1 -1
  3. package/dist/{agent-docs-GCMhcDHy.mjs → agent-docs-VPtM_Kgj.mjs} +2 -2
  4. package/dist/{build-hdxD_kEK.mjs → build-BKNvIOBh.mjs} +3 -3
  5. package/dist/{build-hdxD_kEK.mjs.map → build-BKNvIOBh.mjs.map} +1 -1
  6. package/dist/{build-DTs2SHKS.mjs → build-BhoYGIVD.mjs} +2 -2
  7. package/dist/{builtins-Ci7vBC42.mjs → builtins-6FCYMic-.mjs} +34 -25
  8. package/dist/{builtins-BZGJgOyW.mjs → builtins-BvdsVFXh.mjs} +2 -2
  9. package/dist/cli.mjs +2 -2
  10. package/dist/{config-Bsokk8bV.mjs → config-3zv5pWqb.mjs} +2 -2
  11. package/dist/{config-Dgap2WmJ.mjs → config-LkaeajTr.mjs} +3 -3
  12. package/dist/config-LkaeajTr.mjs.map +1 -0
  13. package/dist/{doctor-Crq8eZsF.mjs → doctor-3-n9AElz.mjs} +4 -4
  14. package/dist/{doctor-Crq8eZsF.mjs.map → doctor-3-n9AElz.mjs.map} +1 -1
  15. package/dist/{fullstack-C7Q-BJcj.mjs → fullstack-0D2Dgia_.mjs} +14 -11
  16. package/dist/{fullstack-onQslndx.mjs → fullstack-D_OHhyZX.mjs} +15 -12
  17. package/dist/fullstack-D_OHhyZX.mjs.map +1 -0
  18. package/dist/index.d.mts +54 -0
  19. package/dist/index.d.mts.map +1 -1
  20. package/dist/index.mjs +2 -2
  21. package/dist/{plugin-BYqbu6uO.mjs → plugin-3V8ZP2h3.mjs} +2 -2
  22. package/dist/{plugin-BuSzbyf2.mjs → plugin-MfCs1kvG.mjs} +2 -2
  23. package/dist/{project-BnXVCMd6.mjs → project-D-8rOQYi.mjs} +36 -20
  24. package/dist/project-D-8rOQYi.mjs.map +1 -0
  25. package/dist/{project-4kpuS1ud.mjs → project-Evcbbdbq.mjs} +33 -17
  26. package/dist/{project-docs-Dn60gTjG.mjs → project-docs-B-qcYAX9.mjs} +1 -1
  27. package/dist/{project-docs-CDr1HNPD.mjs → project-docs-B6XKHBMO.mjs} +2 -2
  28. package/dist/{project-docs-CDr1HNPD.mjs.map → project-docs-B6XKHBMO.mjs.map} +1 -1
  29. package/dist/{project-root-B1lyUoe8.mjs → project-root-6mdsMXwA.mjs} +3 -3
  30. package/dist/{project-root-B1lyUoe8.mjs.map → project-root-6mdsMXwA.mjs.map} +1 -1
  31. package/dist/{project-root-B5MNmlIn.mjs → project-root-CxadsnDd.mjs} +2 -2
  32. package/dist/{prompts-8ssKQ4Kn.mjs → prompts-BtUBi1ob.mjs} +2 -2
  33. package/dist/{prompts-8ssKQ4Kn.mjs.map → prompts-BtUBi1ob.mjs.map} +1 -1
  34. package/dist/{rolldown-runtime-DtbiLYsN.mjs → rolldown-runtime-BeIFeoWC.mjs} +1 -1
  35. package/dist/{run-plugins-CxuPMW4H.mjs → run-plugins-lLfcDAbT.mjs} +77 -68
  36. package/dist/run-plugins-lLfcDAbT.mjs.map +1 -0
  37. package/dist/{typegen-ClJqftm9.mjs → typegen-DLdD6SMm.mjs} +4 -4
  38. package/dist/{typegen-Ca46MGcE.mjs → typegen-yk3CJH18.mjs} +5 -5
  39. package/dist/{typegen-Ca46MGcE.mjs.map → typegen-yk3CJH18.mjs.map} +1 -1
  40. package/dist/{types-CJlHwJb4.mjs → types-HQhLYWkB.mjs} +1 -1
  41. package/package.json +7 -7
  42. package/dist/config-Dgap2WmJ.mjs.map +0 -1
  43. package/dist/fullstack-onQslndx.mjs.map +0 -1
  44. package/dist/project-BnXVCMd6.mjs.map +0 -1
  45. package/dist/run-plugins-CxuPMW4H.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v8.2.4
2
+ * @forinda/kickjs-cli v8.3.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-cli",
3
- "version": "8.2.4",
3
+ "version": "8.3.0",
4
4
  "description": "CLI for KickJS — project scaffolding, DDD module generation, dev/build/start",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -36,24 +36,24 @@
36
36
  "commander": "^15.0.0",
37
37
  "glob": "^13.0.6",
38
38
  "jiti": "^2.7.0",
39
- "oxc-parser": "^0.149.0",
40
- "oxfmt": "^0.67.0",
39
+ "oxc-parser": "^0.150.0",
40
+ "oxfmt": "^0.68.0",
41
41
  "picocolors": "^1.1.1",
42
42
  "pluralize": "^8.0.0",
43
- "@forinda/kickjs-cli-kit": "0.1.2",
43
+ "@forinda/kickjs": "8.6.0",
44
44
  "@forinda/kickjs-db": "7.3.0",
45
- "@forinda/kickjs": "8.6.0"
45
+ "@forinda/kickjs-cli-kit": "0.1.2"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@swc/core": "^1.16.2",
49
- "@types/node": "^26.5.1",
49
+ "@types/node": "^26.6.1",
50
50
  "@types/pg": "^8.23.1",
51
51
  "@types/pluralize": "^0.0.33",
52
52
  "@typescript/typescript6": "^6.0.2",
53
53
  "pg": "^8.23.0",
54
54
  "typescript": "^7.0.2",
55
55
  "vite": "^8.3.0",
56
- "vitest": "^5.0.0",
56
+ "vitest": "^5.0.1",
57
57
  "@forinda/kickjs-ai": "8.0.0"
58
58
  },
59
59
  "publishConfig": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-Dgap2WmJ.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { readFile, access } from 'node:fs/promises'\nimport { isAbsolute, join, relative, resolve } from 'node:path'\n\nimport type { KickCliPlugin } from './plugin/types'\n\n/** A custom command that developers can register via kick.config.ts */\nexport interface KickCommandDefinition {\n /** The command name (e.g. 'db:migrate', 'seed', 'proto:gen') */\n name: string\n /** Description shown in --help */\n description: string\n /**\n * Shell command(s) to run. Can be a single string or an array of\n * sequential steps. Use {args} as a placeholder for CLI arguments.\n *\n * @example\n * 'npx drizzle-kit migrate'\n * ['npx drizzle-kit generate', 'npx drizzle-kit migrate']\n */\n steps: string | string[]\n /** Optional aliases (e.g. ['migrate'] for 'db:migrate') */\n aliases?: string[]\n}\n\n/** Project pattern — controls what generators produce and which deps are installed */\nexport type ProjectPattern = 'rest' | 'minimal'\n\n/** Package manager used for `kick add` and other dep-installing commands */\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport const PACKAGE_MANAGERS: readonly PackageManager[] = ['pnpm', 'npm', 'yarn', 'bun']\n\n/**\n * Built-in repository type with first-class code generation support.\n *\n * Only `inmemory` is built in — zero-dependency and framework-owned.\n *\n * There are deliberately no ORM presets. A repository shaped to Prisma or\n * Drizzle is that library's interface, not KickJS's, and a generator for it is\n * a promise to track someone else's API across versions. Any other name\n * scaffolds a generic custom-repository stub you own, passed as an object\n * because `RepoTypeConfig` is `BuiltinRepoType | CustomRepoType` and the\n * builtin side is `'inmemory'` alone: `repo: { name: 'postgres' }`.\n */\nexport type BuiltinRepoType = 'inmemory'\n\nexport const BUILTIN_REPO_TYPES: readonly string[] = ['inmemory']\n\n/**\n * Repo names that once had dedicated ORM generators. They still scaffold — as\n * the same generic stub any other custom name produces — and the CLI says so\n * once, because silently changing what a flag generates is worse than a note.\n */\nexport const DEPRECATED_REPO_TYPES: readonly string[] = ['prisma', 'drizzle']\n\n/**\n * Print a note when a generator is asked for a repo that used to be a preset.\n * No-op for every other name. Returns whether a note was printed.\n */\nexport function warnIfDeprecatedRepo(repo: string): boolean {\n if (!DEPRECATED_REPO_TYPES.includes(repo)) return false\n console.warn(\n ` Note: '${repo}' is no longer a built-in repository preset — a repository ` +\n `shaped to it is that library's interface, not KickJS's.\\n` +\n ` Scaffolding a generic custom-repository stub instead; wire it to ${repo} yourself.`,\n )\n return true\n}\n\n/** Custom repository type — generates a stub with TODO markers */\nexport interface CustomRepoType {\n name: string\n}\n\n/** Repository type — built-in string or custom object */\nexport type RepoTypeConfig = BuiltinRepoType | CustomRepoType\n\n/**\n * Supported schema validators for `kick typegen` body/query/params\n * type extraction.\n *\n * - `'zod'` — emits `import('zod').infer<typeof schema>` (default)\n * - `'kickjs-schema'` — emits `InferSchemaOutput<typeof schema>` from\n * `@forinda/kickjs-schema`, works with Zod, Valibot, Yup, and any\n * Standard Schema v1 or KickSchema adapter\n * - `false` — disables schema-driven body typing (fields stay `unknown`)\n */\nexport type SchemaValidator = 'zod' | 'kickjs-schema' | false\n\n/**\n * One entry in the typed `assetMap` config record (`assets-plan.md`).\n * Each entry names a source directory whose files become addressable\n * via the `assets.<name>.*` typed accessor at runtime.\n */\nexport interface AssetMapEntry {\n /**\n * Source directory, relative to project root. Required. The directory\n * must exist when `kick build` runs — `loadKickConfig` warns when an\n * entry points at a missing directory but doesn't fail the load\n * (the typegen + build steps surface the error in context instead).\n */\n src: string\n /**\n * Destination directory inside `dist/`. Defaults to `dist/<name>/`\n * where `<name>` is the assetMap key. Override when the consumer of\n * the assets expects a non-standard layout (e.g. an existing\n * downstream tool reads from `dist/templates/...`).\n */\n dest?: string\n /**\n * Glob pattern for which files to include. Defaults to `**\\/*` (all\n * files). Files that don't match are NOT copied — `assetMap` is\n * selective by design (unlike `copyDirs` which copies everything).\n */\n glob?: string\n /**\n * How file extensions feed into manifest keys. Default `'auto'`.\n *\n * - `'strip'` — drop the extension. `pages/index.pug` →\n * `'pages/index'`. Two siblings with the same basename collide;\n * last-walk-order wins, others are silently dropped. Opt-in\n * only — kept for backward compatibility with projects that\n * relied on this contract.\n * - `'with-extension'` — keep every extension on every key. Best\n * when the namespace holds extension siblings (`index.pug` +\n * `index.html` + `index.css` in `src/pages/`); every file\n * reaches the manifest under its full path.\n * - `'auto'` — strip when basenames are unique, keep extensions\n * on collision groups. Singleton files keep their short key;\n * `pages/index.{pug,html,css}` becomes\n * `pages/index.pug` / `pages/index.html` / `pages/index.css`.\n * No data loss; non-colliding namespaces stay on the short\n * keys they had before.\n *\n * @default 'auto'\n */\n keys?: 'auto' | 'strip' | 'with-extension'\n}\n\n/**\n * Database settings consumed by `kick db generate`, `kick db migrate`,\n * and the M4.C composite-type detection gate. Mirrors the runtime\n * `DbConfig` shape from `@forinda/kickjs-db` — duplicated here so\n * adopters who only install `@forinda/kickjs-cli` can set the block\n * without pulling `@forinda/kickjs-db` types into their kick.config.ts\n * resolution. The CLI loads kick.config.ts through `loadKickConfig`,\n * then `resolveDbConfig` re-reads this block from the same module and\n * normalises defaults (`schemaPath` / `migrationsDir` / `dialect`).\n *\n * @example\n * ```ts\n * defineConfig({\n * db: {\n * schemaPath: 'src/db/schema.ts',\n * migrationsDir: 'db/migrations',\n * dialect: 'postgres',\n * connectionString: process.env.DATABASE_URL,\n * },\n * })\n * ```\n */\nexport interface KickDbConfigBlock {\n /**\n * Path to the schema module (`pgEnum` / `table` declarations).\n * Defaults to `'src/db/schema.ts'`.\n */\n schemaPath?: string\n /**\n * Where `kick db generate` writes migration directories. Defaults\n * to `'db/migrations'`.\n */\n migrationsDir?: string\n /** SQL dialect. Defaults to `'postgres'`. */\n dialect?: 'postgres' | 'sqlite' | 'mysql'\n /**\n * Postgres connection string for the built-in pgAdapter path. Read\n * from the `DATABASE_URL` env var when omitted. Used by\n * `kick db migrate*` and the M4.C composite-type gate at\n * `kick db generate`.\n */\n connectionString?: string\n /**\n * Escape hatch: a factory returning a fully-constructed\n * MigrationAdapter (typed loosely here so the CLI types don't pull\n * `@forinda/kickjs-db` into adopter projects that don't import it).\n * Takes precedence over `connectionString` when both are set.\n */\n adapter?: () => unknown | Promise<unknown>\n}\n\n/** Typegen settings — controls .kickjs/types/* generation */\nexport interface TypegenConfig {\n /**\n * Source directory to scan for controllers and decorators.\n * Defaults to `'src'`.\n */\n srcDir?: string\n /**\n * Output directory for generated `.d.ts` files.\n * Defaults to `'.kickjs/types'`.\n */\n outDir?: string\n /**\n * Schema validator used to derive `body` types from route metadata.\n *\n * - `'zod'` — emit `z.infer<typeof <importedSchema>>` for any schema\n * referenced as a named identifier in `@Get/@Post/...({ body, query, params })`.\n * - `false` — disable schema-driven body typing.\n *\n * Future: `'joi' | 'yup' | 'json-schema'` plus a `{ name; module }`\n * escape hatch for custom adapters.\n *\n * @default 'zod'\n */\n schemaValidator?: SchemaValidator\n /**\n * Path to the project's env schema file (relative to project root).\n * Must default-export a `defineEnv(...)` schema for typegen to emit\n * the typed `KickEnv` global registry.\n *\n * Set to `false` to disable env typing entirely.\n *\n * @default 'src/env.ts'\n */\n envFile?: string | false\n /**\n * Built-in or user typegen plugin ids to skip during `kick typegen`,\n * `kick dev`, and `kick typegen --watch`.\n *\n * The plugin still loads and merge-time conflict detection still\n * runs — only the `generate()` invocation is skipped — so adopters\n * who want to hand-write `KickDbRegister` (manual typeof-schema\n * augmentation) can disable `'kick/db'` and keep the rest:\n *\n * @example\n * typegen: {\n * disable: ['kick/db'], // hand-written register.ts owns the type\n * }\n *\n * Unrecognised ids are ignored — the list is treated as a wishlist,\n * not a strict registry.\n */\n disable?: string[]\n\n /**\n * Generate `.kickjs/types/kick__client.d.ts`, the self-contained route map\n * a frontend consumes without compiling the server.\n *\n * Producing it means building a whole TypeScript program over the server —\n * measured at 1.1 GB and ~7.6s on a 1,940-route app, against ~130ms for\n * every other typegen plugin combined. An API with no frontend should not\n * pay that for a file nothing reads, so this is not on for everyone.\n *\n * Off unless set. `kick new --template fullstack` writes `true`, since its\n * web app reads the map; anything else opts in here.\n *\n * A map already on disk does NOT turn this on — that would put the switch\n * somewhere nobody looks. It does get a warning, since a map left to rot is\n * how a frontend ends up type-checking against routes the server no longer\n * serves.\n *\n * Pass an object to tune the expansion. `{ maxDepth }` raises the limit on\n * how deeply a response type is expanded inline before it is emitted as\n * `unknown`:\n *\n * ```ts\n * typegen: { client: { maxDepth: 24 } }\n * ```\n *\n * The default of 12 truncates nothing on a 1,940-route app — recursion and\n * named types both hoist, which costs one level rather than the whole\n * budget, so only deep *anonymous* nesting spends it. Raise it if a route\n * warns that it exceeded the limit; the warning names the route.\n */\n client?: boolean | ClientTypegenConfig\n}\n\n/** Tuning for the client route map — see `TypegenConfig.client`. */\nexport interface ClientTypegenConfig {\n /** On, unless explicitly false. Lets `{ maxDepth }` alone mean \"on\". */\n enabled?: boolean\n /**\n * Levels of inline nesting expanded before a type is emitted as `unknown`.\n *\n * Truncation is loud and safe — it warns naming the route, and `unknown`\n * cannot be mistaken for a wrong type — so this is a knob for fidelity, not\n * correctness. Default 12.\n */\n maxDepth?: number\n}\n\n/** Module generation settings — controls how `kick g module` produces code */\nexport interface ModuleConfig {\n /** Where modules live (default: 'src/modules') */\n dir?: string\n /**\n * Default repository implementation for generators.\n *\n * @deprecated The name no longer changes the generated code. Only\n * `'inmemory'` produces a distinct file — the one built-in with a real\n * working implementation. Every other value scaffolds the SAME\n * unimplemented stub, named for the module rather than the store, since a\n * `PostgresUserRepository` that is not yet Postgres asserts something the\n * file does not honour.\n *\n * The value survives only as prose: the store appears in the stub's comments\n * and in its \"not implemented\" errors, as a note to the person writing the\n * query. Set it for that hint, or leave it unset and get the in-memory\n * implementation.\n *\n * Kept working, and there is no plan to remove it without a replacement.\n *\n * @example\n * // unset → InMemoryUserRepository, a working store\n * repo: { name: 'postgres' } // → UserRepository, unimplemented, \"write the postgres query\"\n */\n repo?: RepoTypeConfig\n /** Schema output directory (e.g. 'src/db/schema') */\n schemaDir?: string\n /**\n * Whether to pluralize module names in generated code.\n * When true (default), `kick g module user` creates `src/modules/users/`.\n * When false, it creates `src/modules/user/` and uses singular names throughout.\n */\n pluralize?: boolean\n /**\n * Module declaration style emitted by `kick g module` and the\n * project scaffold.\n *\n * - `'define'` (default) — `defineModule({ name, build: () => ({...}) })`\n * factory form. Mirrors `defineAdapter` / `definePlugin` /\n * `defineContextDecorator`.\n * - `'class'` — legacy `class FooModule implements AppModule { ... }`\n * form. Still fully supported by the framework loader; pin to this\n * value for projects that prefer the class shape (existing-codebase\n * consistency, class-decorator setups, etc).\n *\n * The framework runtime accepts both shapes regardless of this\n * setting — the flag controls codegen output only. `kick g module`\n * inserts the matching call form into `src/modules/index.ts`\n * (`Module()` vs `Module`); `kick rm module` matches both.\n *\n * @default 'define'\n */\n style?: 'define' | 'class'\n}\n\n/** Configuration for the kick.config.ts file */\nexport interface KickConfig {\n /**\n * Project pattern — the module shape `kick g module` produces by default.\n * `--pattern` overrides it per call.\n * - 'rest' — flat module: controller, service, DTOs, repository (default)\n * - 'minimal' — controller + module only\n */\n pattern?: ProjectPattern\n /**\n * Module generation settings — directory, repo type, pluralization, schema dir.\n *\n * @example\n * modules: {\n * dir: 'src/modules',\n * repo: 'postgres',\n * pluralize: false,\n * schemaDir: 'src/db/schema',\n * }\n */\n modules?: ModuleConfig\n /**\n * Package manager used by `kick add` (and any future dep-installing command)\n * to install dependencies. When set, overrides lockfile auto-detection so\n * commands always use the project's intended package manager.\n *\n * Priority (highest first):\n * 1. `--pm` flag on the CLI\n * 2. `packageManager` in kick.config\n * 3. `packageManager` field in package.json (corepack convention)\n * 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)\n * 5. `'npm'`\n *\n * @example\n * packageManager: 'pnpm'\n */\n packageManager?: PackageManager\n\n /**\n * The HTTP runtime the app boots on — the engine passed to\n * `bootstrap({ runtime })`. Written by `kick new --runtime`, and read by\n * dep-aware commands so they install / validate the engine-correct peers:\n * - `kick add upload` picks the multipart driver (express → `multer`,\n * fastify → `@fastify/multipart`, h3 → built-in, no driver)\n * - `kick doctor` checks the engine peers + upload driver are present\n * - the `kick/runtime` typegen emits the `KickRuntimeRegister` augmentation\n * that flips the runtime-typed escape hatches (`AdapterContext.app`,\n * `getRuntimeApp()`) to the engine's native types\n *\n * Defaults to `'express'` when unset (the default engine).\n *\n * @example\n * runtime: 'fastify'\n */\n runtime?: 'express' | 'fastify' | 'h3'\n\n /**\n * DI token scope prefix used by code generators. Every scaffolded\n * `createToken<T>('<scope>/<area>/<key>')` substitutes this string\n * for `<scope>`. Generators emit org-scoped tokens out of the box\n * so adopter projects pass `kick-lint`'s `token-reserved-prefix`\n * rule (which forbids the reserved `kick/` prefix on third-party\n * code) without manual rename.\n *\n * Resolution order (highest first):\n * 1. This field, when set\n * 2. `package.json` `name` field — `@scope/pkg` → `'scope'`,\n * bare `pkg` → `'pkg'`\n * 3. Fallback `'app'`\n *\n * @example\n * tokenScope: 'mycorp'\n * // → createToken<...>('mycorp/users/repository')\n */\n tokenScope?: string\n\n /**\n * Directories to copy to dist/ after build.\n * Useful for EJS templates, email templates, static assets, etc.\n *\n * @example\n * ```ts\n * copyDirs: [\n * 'src/views', // copies to dist/src/views\n * { src: 'src/views', dest: 'dist/views' }, // custom dest\n * 'src/emails',\n * ]\n * ```\n */\n copyDirs?: Array<string | { src: string; dest?: string }>\n /**\n * Build output settings. The asset manager + `kick build`'s copy\n * steps honour these — adopters who use Vite's `build.outDir =\n * 'out'` (or any non-default) should mirror the value here so\n * `assets.x.y()` paths line up with where Vite actually wrote.\n *\n * @example\n * ```ts\n * build: { outDir: 'out' }\n * ```\n */\n build?: {\n /**\n * Output directory, relative to project root. Defaults to\n * `'dist'`. The asset manager emits its manifest + copies\n * assetMap entries into this directory (under a per-namespace\n * subdirectory by default; override per-entry via `dest`).\n */\n outDir?: string\n }\n /**\n * Typed, addressable assets — see `assets-plan.md`. Each entry maps\n * a logical namespace name to a source directory. The build pipeline\n * auto-derives the necessary copy step + emits a manifest at\n * `dist/.kickjs-assets.json`; the runtime exposes\n * `import { assets } from '@forinda/kickjs'` so adopters can resolve\n * paths without dev/prod branching.\n *\n * `copyDirs` is unchanged — `assetMap` is a separate, opt-in surface.\n * Adopters who want raw directory copies keep using `copyDirs`; those\n * who want typed addressable assets add `assetMap` entries.\n *\n * @example\n * ```ts\n * assetMap: {\n * mails: { src: 'src/templates/mails' },\n * reports: { src: 'src/templates/reports', glob: '**\\/*.{ejs,html}' },\n * schemas: { src: 'src/schemas', glob: '**\\/*.json' },\n * }\n * ```\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Typegen settings — controls `.kickjs/types/*` generation including\n * the schema validator used for body type extraction.\n *\n * @example\n * ```ts\n * typegen: {\n * schemaValidator: 'zod',\n * }\n * ```\n */\n typegen?: TypegenConfig\n /**\n * Dev-server (`kick dev`) settings.\n */\n dev?: {\n /**\n * Run the project's TypeScript checker (`tsgo --noEmit`, falling\n * back to `tsc --noEmit`) after each debounced change and surface\n * diagnostics in the dev console + a `kickjs:typecheck` HMR event.\n * Equivalent to the `kick dev --typecheck` flag.\n *\n * @default false\n */\n typecheck?: boolean\n }\n /**\n * Database settings — schema path, migrations dir, dialect,\n * connection string, optional adapter factory. Consumed by\n * `kick db generate` / `kick db migrate*`. See {@link KickDbConfigBlock}.\n */\n db?: KickDbConfigBlock\n /** Custom commands that extend the CLI */\n commands?: KickCommandDefinition[]\n /**\n * CLI plugins — bundled commands + typegens contributed by external\n * packages (e.g. `@forinda/kickjs-cli-drizzle`). Plugin commands\n * appear first; adopter `commands` overrides plugin commands of the\n * same name. Duplicate commands or typegen ids across two plugins\n * fail-fast at CLI startup.\n *\n * @example\n * import { drizzlePlugin } from '@forinda/kickjs-cli-drizzle'\n * export default defineConfig({\n * plugins: [drizzlePlugin({ schemaPath: 'src/db/schema' })],\n * })\n */\n plugins?: KickCliPlugin[]\n /** Code style overrides (auto-detected from prettier when possible) */\n style?: {\n semicolons?: boolean\n quotes?: 'single' | 'double'\n trailingComma?: 'all' | 'es5' | 'none'\n indent?: number\n }\n\n /**\n * Extensibility hook for `kick doctor`. Adopters add their own\n * environment / project-shape checks here; each function receives\n * the same context the built-in checks see and returns a result (or\n * `null` to skip).\n *\n * The framework stays ORM- and stack-agnostic — Prisma-specific,\n * Drizzle-specific, deploy-target-specific checks belong in adopter\n * config (or in adapter packages that ship doctor extensions),\n * never in core.\n *\n * @example\n * ```ts\n * import { existsSync } from 'node:fs'\n * import { join } from 'node:path'\n * import { defineConfig } from '@forinda/kickjs-cli'\n *\n * export default defineConfig({\n * doctor: {\n * checks: [\n * (ctx) => {\n * if (!existsSync(join(ctx.cwd, 'prisma/schema.prisma'))) return null\n * const generated = join(ctx.cwd, 'node_modules/@prisma/client/default.js')\n * return existsSync(generated)\n * ? { name: 'Prisma client generated', status: 'pass' }\n * : {\n * name: 'Prisma client generated',\n * status: 'fail',\n * fix: 'Run: pnpm exec prisma generate',\n * }\n * },\n * ],\n * },\n * })\n * ```\n */\n doctor?: import('./commands/doctor').DoctorExtension\n}\n\n/** Helper to define a type-safe kick.config.ts */\nexport function defineConfig(config: KickConfig): KickConfig {\n return config\n}\n\n/** Resolve module config from `modules.*` block. */\n/**\n * Resolve the project's DI token scope for code generators.\n * Falls back through kick.config.ts → package.json → `'app'`.\n *\n * @param config Loaded `kick.config.ts` (null when not present)\n * @param cwd Project root — used to read package.json\n */\n/**\n * Whether this project actually depends on `@forinda/kickjs-swagger`.\n *\n * The REST generator decorates controllers with `@ApiTags`, which comes from\n * that package. Emitting it unconditionally put an import of a package the\n * project does not have into every generated controller — five decorators and\n * a broken build, in a file the adopter did not write.\n *\n * Reading package.json rather than resolving the module: a generator should\n * emit what the project DECLARES it uses. A transitively-present copy in\n * node_modules is not a dependency this code may import.\n */\nexport function hasDependency(\n cwd: string,\n name: string,\n /**\n * Where the dependency must be declared. Default `'runtime'`: generated\n * code that *imports a value* — `@ApiTags` is a decorator, evaluated at\n * boot — needs the package present in a production install, and\n * `npm install --omit=dev` drops devDependencies. Pass `'any'` only for\n * something used at build or test time.\n */\n scope: 'runtime' | 'any' = 'runtime',\n): boolean {\n try {\n const pkgPath = join(cwd, 'package.json')\n if (!existsSync(pkgPath)) return false\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }\n if (pkg.dependencies?.[name]) return true\n return scope === 'any' ? Boolean(pkg.devDependencies?.[name]) : false\n } catch {\n // An unreadable package.json means we cannot prove the dependency —\n // and emitting an import we cannot justify is the failure being fixed.\n return false\n }\n}\n\n/**\n * Whether the project declares the swagger package `@ApiTags` comes from.\n *\n * Runtime scope: the decorator is evaluated when the controller class is\n * defined, so a devDependency-only install would emit controllers that import\n * a package a production install does not have.\n */\nexport function hasSwagger(cwd: string): boolean {\n return hasDependency(cwd, '@forinda/kickjs-swagger', 'runtime')\n}\n\n/** Schema library a generated schema file is written against. */\nexport type SchemaLib = 'zod' | 'valibot' | 'yup'\n\n/**\n * Which validation library the project actually depends on, so generated\n * schema files import something that is installed.\n *\n * `kick new --schema valibot|yup` installs exactly one of these — the chosen\n * library and no other — so an emitted `import { z } from 'zod'` is a file that\n * cannot resolve. Read from package.json rather than `kick.config.ts` because\n * the dependency IS the fact; nothing has to be declared twice, and projects\n * scaffolded before this existed are covered without a config migration.\n *\n * Valibot and Yup are checked first: `kick new` installs one deliberately,\n * whereas `zod` is common enough to appear alongside it for unrelated reasons.\n * A project that genuinely needs to override the pick can gain a config field\n * when someone hits that; until then the dependency answers it.\n *\n * Runtime scope, same reasoning as {@link hasSwagger}: a DTO schema is\n * evaluated when the module loads, so a devDependency-only install would break\n * in production.\n */\nexport function resolveSchemaLib(cwd: string): SchemaLib {\n if (hasDependency(cwd, 'valibot', 'runtime')) return 'valibot'\n if (hasDependency(cwd, 'yup', 'runtime')) return 'yup'\n return 'zod'\n}\n\nexport function resolveTokenScope(config: KickConfig | null, cwd: string): string {\n if (config?.tokenScope && typeof config.tokenScope === 'string' && config.tokenScope.length > 0) {\n const sanitised = sanitizeScope(config.tokenScope)\n // Configured tokenScope can sanitise down to an empty string (e.g.\n // '___' or '!!') — falling through to the package.json chain is\n // safer than emitting an invalid `'/users/repository'` token.\n if (sanitised.length > 0) return sanitised\n }\n\n // Read package.json synchronously — this runs once per generator\n // invocation, so a sync read is cheaper than the async dance + lets\n // the call sites (template builders) stay synchronous.\n try {\n const pkgPath = join(cwd, 'package.json')\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { name?: unknown }\n if (typeof pkg.name === 'string' && pkg.name.length > 0) {\n const scoped = pkg.name.match(/^@([^/]+)\\//)\n const candidate = scoped ? sanitizeScope(scoped[1]) : sanitizeScope(pkg.name)\n if (candidate.length > 0) return candidate\n // Same empty-after-sanitize guard.\n }\n }\n } catch {\n // package.json missing or malformed — fall through to default\n }\n\n return 'app'\n}\n\n/** Lowercase + strip characters that would break a token literal. */\nfunction sanitizeScope(raw: string): string {\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-') // collapse anything weird to a hyphen\n .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens\n .replace(/-{2,}/g, '-') // collapse runs of hyphens\n}\n\nexport function resolveModuleConfig(config: KickConfig | null): ModuleConfig {\n if (!config) return {}\n const mc: ModuleConfig = {\n dir: config.modules?.dir,\n repo: config.modules?.repo,\n schemaDir: config.modules?.schemaDir,\n pluralize: config.modules?.pluralize,\n style: config.modules?.style,\n }\n // Validate `style` — silently coerce unknown values to the default.\n // The CLI surface only documents 'define' and 'class'; anything else\n // is a typo (e.g. 'defineModule') we'd rather see than silently\n // emit class form.\n if (mc.style !== undefined && mc.style !== 'define' && mc.style !== 'class') {\n console.warn(\n ` Warning: modules.style '${mc.style as string}' is not a valid value ` +\n `(expected 'define' or 'class'). Falling back to 'define'.`,\n )\n mc.style = 'define'\n }\n\n // Warn if a string repo value isn't a known built-in. Deprecated ORM\n // presets get their own note; any other bare string suggests the\n // `{ name }` form to silence the stub-repo warning.\n if (mc.repo && typeof mc.repo === 'string' && !BUILTIN_REPO_TYPES.includes(mc.repo)) {\n if (!warnIfDeprecatedRepo(mc.repo)) {\n console.warn(\n ` Warning: modules.repo '${mc.repo}' is not a built-in type (${BUILTIN_REPO_TYPES.join(', ')}).` +\n ` It will generate a stub repository. Use { name: '${mc.repo}' } to silence this warning.`,\n )\n }\n }\n\n return mc\n}\n\nconst CONFIG_FILES = ['kick.config.ts', 'kick.config.js', 'kick.config.mjs', 'kick.config.json']\n\n/**\n * Load `kick.config.*` starting from `startDir` and walking up toward\n * the filesystem root until a config file is found. Returns `null`\n * when no config exists anywhere on the way up.\n *\n * Walking up means adopters can run `kick <cmd>` from any subdirectory\n * (e.g. `src/modules/users/`) and still pick up the project's config —\n * before this change a nested-cwd invocation silently saw `null` and\n * fell back to framework defaults.\n *\n * TypeScript configs (`.ts`) are loaded via `jiti` when available;\n * `.js` / `.mjs` use native `import()`; `.json` uses `JSON.parse`. The\n * jiti import is dynamic + best-effort: if the dep is missing we\n * surface a warning telling the adopter how to install it, instead of\n * silently dropping the config (which is what the previous bare-catch\n * did).\n */\nexport async function loadKickConfig(startDir: string): Promise<KickConfig | null> {\n const { findProjectRoot } = await import('./utils/project-root')\n const root = findProjectRoot(startDir)\n\n for (const filename of CONFIG_FILES) {\n const filepath = join(root, filename)\n try {\n await access(filepath)\n } catch {\n continue\n }\n\n if (filename.endsWith('.json')) {\n const content = await readFile(filepath, 'utf-8')\n return JSON.parse(content)\n }\n\n const isTs = filename.endsWith('.ts')\n\n if (isTs) {\n // Split the import-jiti step from the load-user-config step so the\n // diagnostic blames the right thing. Both surface as Node module-\n // resolution errors with similar shapes (`Cannot find package …`,\n // `ERR_MODULE_NOT_FOUND`), but the remedies differ:\n //\n // - Missing jiti → adopter needs to install jiti.\n // - Missing dep referenced from kick.config.ts → adopter needs to\n // install THAT package; telling them to install jiti would send\n // them to the wrong fix and bury the real missing module.\n let jitiModule: typeof import('jiti')\n try {\n jitiModule = await import('jiti')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"Cannot find package 'jiti'\") || msg.includes('ERR_MODULE_NOT_FOUND')) {\n console.warn(\n `Warning: Failed to load ${filename} — 'jiti' is required for TypeScript configs. ` +\n \"Run `pnpm add -D jiti` (or your package manager's equivalent), or rename the file \" +\n 'to kick.config.js / kick.config.mjs / kick.config.json.',\n )\n } else {\n console.warn(`Warning: Failed to initialize jiti for ${filename}: ${msg}`)\n }\n continue\n }\n\n try {\n const jiti = jitiModule.createJiti(root, { interopDefault: true, fsCache: false })\n const config = (await jiti.import(filepath, { default: true })) as KickConfig\n const warnings = validateAssetMap(config, root)\n for (const warning of warnings) console.warn(` Warning: ${warning}`)\n writeAssetConfigSnapshot(root, config)\n return config\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n continue\n }\n }\n\n try {\n const { pathToFileURL } = await import('node:url')\n const mod = await import(pathToFileURL(filepath).href)\n const config = (mod.default ?? mod) as KickConfig\n const warnings = validateAssetMap(config, root)\n for (const warning of warnings) console.warn(` Warning: ${warning}`)\n writeAssetConfigSnapshot(root, config)\n return config\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n continue\n }\n }\n return null\n}\n\n/**\n * Mirror the JSON-serialisable slice of a TS/JS `kick.config` that the\n * runtime asset resolver needs (`assetMap` + `build.outDir`) into\n * `.kickjs/kick.config.json`.\n *\n * Why: the runtime resolver in `@forinda/kickjs` reads its config\n * *synchronously* (it sits on the hot path of `assets.x.y()` /\n * `resolveAsset()`), so it can only parse `kick.config.{json,js,cjs}` —\n * it deliberately can't transpile `kick.config.ts`. For a `.ts`-config\n * project in dev (no built `dist/.kickjs-assets.json` yet), that left\n * the resolver with no config to synthesise a manifest from, so\n * `assets.x.y()` threw `UnknownAssetError` until the first build.\n *\n * The CLI already transpiles the `.ts` config here, so it drops this\n * tiny snapshot the runtime can read with a plain `JSON.parse`. Best\n * effort: any failure (read-only fs, etc.) is swallowed — a missing\n * snapshot just falls back to the previous behaviour.\n */\nexport function writeAssetConfigSnapshot(root: string, config: KickConfig | null): void {\n // Only meaningful for asset-manager users — don't litter `.kickjs/`\n // for projects that never declare an assetMap.\n if (!config?.assetMap || Object.keys(config.assetMap).length === 0) return\n try {\n const dir = join(root, '.kickjs')\n mkdirSync(dir, { recursive: true })\n const snapshot = {\n // Marker so a future shape change can be detected/migrated.\n version: 1 as const,\n assetMap: config.assetMap,\n ...(config.build?.outDir ? { build: { outDir: config.build.outDir } } : {}),\n }\n writeFileSync(join(dir, 'kick.config.json'), JSON.stringify(snapshot, null, 2) + '\\n', 'utf-8')\n } catch {\n // Best effort — snapshot is an optimisation, not a correctness\n // requirement. Production reads the real dist manifest.\n }\n}\n\n/**\n * Validate `assetMap` entries on a loaded config. Returns a list of\n * human-readable warnings; the caller decides how to surface them\n * (typically `console.warn`). Never throws — `kick g` and other\n * unrelated commands should keep working even when the assetMap is\n * misconfigured.\n *\n * Checks:\n *\n * - Each entry's `src` is a non-empty string.\n * - The `src` directory exists on disk (otherwise the typegen + build\n * steps will fail later with cryptic errors).\n * - `dest` doesn't escape the project root (defensive — a `dest:\n * '../../etc'` typo could write files outside the workspace).\n * - The namespace key is a non-empty string and doesn't include a\n * `/` (would conflict with the `<namespace>/<key>` manifest format).\n */\nexport function validateAssetMap(config: KickConfig | null, cwd: string): string[] {\n const warnings: string[] = []\n if (!config?.assetMap) return warnings\n\n const root = resolve(cwd)\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!namespace || namespace.includes('/')) {\n warnings.push(\n `assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`,\n )\n continue\n }\n if (typeof entry?.src !== 'string' || entry.src.length === 0) {\n warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`)\n continue\n }\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) {\n warnings.push(\n `assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`,\n )\n }\n if (entry.dest) {\n const destAbs = resolve(cwd, entry.dest)\n // path.relative is the right primitive for \"is X inside Y?\" —\n // a raw startsWith() prefix match has two failure modes the\n // earlier version hit: (a) `/app` is a prefix of `/app2/...`\n // even though they're different directories, and (b) it's\n // case-sensitive on filesystems that aren't (macOS default,\n // Windows). path.relative handles both correctly + accounts\n // for `..` traversal in the destination.\n if (escapesRoot(destAbs, root)) {\n warnings.push(\n `assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`,\n )\n }\n }\n }\n return warnings\n}\n\n/**\n * Returns true when `path` (absolute) resolves outside of `root`\n * (also absolute). Uses `path.relative` for accuracy:\n *\n * - The result is empty when paths are identical (inside).\n * - It starts with `..` when the path traverses outside the root.\n * - It's absolute (Windows: cross-drive) when there's no relative\n * path between them.\n *\n * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`\n * matching `/app2/...`, or case-mismatches on macOS / Windows).\n */\nfunction escapesRoot(path: string, root: string): boolean {\n const rel = relative(root, path)\n return rel === '' ? false : rel.startsWith('..') || isAbsolute(rel)\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fullstack-onQslndx.mjs","names":[],"sources":["../src/generators/fullstack.ts"],"sourcesContent":["// `kick new --template fullstack` — a pnpm-workspace monorepo:\n//\n// my-app/\n// server/ KickJS API (the standard scaffold, minus its own git/install)\n// web/ Vite + React frontend typed end-to-end via\n// @forinda/kickjs-client + the server's generated KickRoutes.Api\n//\n// The type loop: server controllers use return-value handlers → `kick\n// typegen` (run here once, re-run by `kick dev`) emits\n// server/.kickjs/types/kick__client.d.ts → web tsconfig `types` (ambient)\n// side-effect-imports that file (type-only, erased at runtime) → the web\n// client calls `api.get('/hello')` with the handler's actual response type.\n\nimport { join } from 'node:path'\nimport { execFileSync } from 'node:child_process'\n\nimport { writeFileSafe } from '../utils/fs'\nimport { runCommand } from '../utils/shell'\nimport { initProject, resolveSiblingVersions } from './project'\nimport { TEMPLATE_BUILDS, approveInstallScripts } from '../commands/add'\n\nexport interface InitFullstackOptions {\n name: string\n directory: string\n packageManager?: 'pnpm' | 'npm' | 'yarn' | 'bun'\n initGit?: boolean\n installDeps?: boolean\n schemaLib?: 'zod' | 'valibot' | 'yup'\n runtime?: 'express' | 'fastify' | 'h3'\n}\n\nexport async function initFullstackProject(options: InitFullstackOptions): Promise<void> {\n const { name, directory, schemaLib = 'zod', runtime = 'express' } = options\n // `--pm` arrives as a free CLI string — allowlist before it reaches a\n // process invocation (execFileSync takes an argv array, no shell, but a\n // bogus binary name is still a confusing failure).\n const pmValid = (['pnpm', 'npm', 'yarn', 'bun'] as const).includes(\n options.packageManager as never,\n )\n const packageManager = pmValid\n ? (options.packageManager as 'pnpm' | 'npm' | 'yarn' | 'bun')\n : 'pnpm'\n const dir = directory\n const log = (msg: string) => console.log(` ${msg}`)\n if (options.packageManager !== undefined && !pmValid) {\n log(\n `Warning: unknown package manager '${String(options.packageManager)}' — falling back to pnpm.`,\n )\n }\n\n console.log(`\\n Creating fullstack KickJS workspace: ${name}\\n`)\n\n // ── server/ — the standard scaffold, deferred install/git ──────────\n await initProject({\n name: `${name}-server`,\n directory: join(dir, 'server'),\n packageManager,\n template: 'minimal',\n schemaLib,\n runtime,\n // web/ reads the resolved route map from the ambient KickClientApi\n // namespace, which needs the TS 7 compiler API to produce.\n withClientMap: true,\n // web/vite.config.ts proxies /api to the server's port; if kick dev moved\n // to another port the proxy would silently reach the wrong process.\n strictPort: true,\n // server/ is a workspace member; the root records the approvals.\n approveInstallScripts: false,\n // Root owns install + git so the lockfile/commit cover the workspace.\n initGit: false,\n installDeps: false,\n // Production single-origin serving. `web/` builds to `web/dist`, and\n // nothing served it before — `pnpm build` produced a frontend the API had\n // no way to hand out, so every deploy needed a hand-wired static host or\n // a second process. The adapter is inert until `../web/dist` exists, so\n // `dev` (Vite serves the client and proxies /api here) is untouched.\n spaClientDir: '../web/dist',\n })\n\n // ── web/ — Vite + React, typed client ──────────────────────────────\n const versions = await resolveSiblingVersions()\n const clientVersion = versions['@forinda/kickjs-client'] ?? '^0.1.0'\n\n await writeFileSafe(join(dir, 'web/package.json'), webPackageJson(name, clientVersion))\n await writeFileSafe(join(dir, 'web/vite.config.ts'), webViteConfig())\n await writeFileSafe(join(dir, 'web/tsconfig.json'), webTsConfig())\n await writeFileSafe(join(dir, 'web/index.html'), webIndexHtml(name))\n await writeFileSafe(join(dir, 'web/src/main.tsx'), webMain())\n await writeFileSafe(join(dir, 'web/src/App.tsx'), webApp())\n await writeFileSafe(join(dir, 'web/src/api.ts'), webApi())\n\n // ── workspace root ──────────────────────────────────────────────────\n await writeFileSafe(\n join(dir, 'package.json'),\n rootPackageJson(name, packageManager, versions['@forinda/kickjs-cli']),\n )\n // Only pnpm reads this file. npm / yarn / bun declare workspaces via the\n // `workspaces` field in the root package.json instead — see rootPackageJson.\n if (packageManager === 'pnpm') {\n await writeFileSafe(join(dir, 'pnpm-workspace.yaml'), 'packages:\\n - server\\n - web\\n')\n }\n // Before install, at the root every package manager reads them from.\n approveInstallScripts(packageManager, dir, TEMPLATE_BUILDS)\n await writeFileSafe(join(dir, '.gitignore'), rootGitignore())\n await writeFileSafe(join(dir, 'README.md'), rootReadme(name, packageManager))\n\n // Workspace-root agent docs (CLAUDE.md + .agents/) flavored for the\n // fullstack layout — the server/ subdir keeps its own generated set.\n const { generateAgentDocs } = await import('./agent-docs')\n await generateAgentDocs({\n outDir: dir,\n name,\n pm: packageManager,\n template: 'fullstack',\n only: 'all',\n force: true,\n })\n\n // ── install (root — covers both workspace packages) ────────────────\n if (options.installDeps) {\n console.log(`\\n Installing workspace dependencies with ${packageManager}...\\n`)\n try {\n // `runCommand`, not bare execFileSync — on Windows every package\n // manager is a `.cmd` shim that execFileSync cannot spawn.\n runCommand(packageManager, ['install'], { cwd: dir })\n } catch {\n console.log(`\\n Warning: ${packageManager} install failed. Run it manually.`)\n }\n }\n\n // ── typegen (server) so web's KickRoutes.Api resolves immediately ──\n try {\n const { runTypegen } = await import('../typegen')\n await runTypegen({ cwd: join(dir, 'server'), allowDuplicates: true, silent: true })\n } catch {\n // Non-fatal — `kick dev` in server/ retries on boot.\n }\n\n // ── git (root) ──────────────────────────────────────────────────────\n if (options.initGit) {\n try {\n execFileSync('git', ['init'], { cwd: dir, stdio: 'pipe' })\n execFileSync('git', ['branch', '-M', 'main'], { cwd: dir, stdio: 'pipe' })\n execFileSync('git', ['add', '-A'], { cwd: dir, stdio: 'pipe' })\n execFileSync('git', ['commit', '-m', 'chore: initial commit from kick new (fullstack)'], {\n cwd: dir,\n stdio: 'pipe',\n })\n log('Git repository initialized')\n } catch {\n log('Warning: git init failed (git may not be installed)')\n }\n }\n\n console.log('\\n Fullstack workspace scaffolded!')\n console.log()\n log('Next steps:')\n log(` cd ${name}`)\n if (!options.installDeps) log(` ${packageManager} install`)\n if (packageManager === 'pnpm') {\n log(' pnpm dev # runs server (kick dev) + web (vite) together')\n } else {\n log(` ${packageManager} run dev:server # terminal 1`)\n log(` ${packageManager} run dev:web # terminal 2`)\n }\n log('')\n log('The web app calls the API through @forinda/kickjs-client —')\n log(\"edit server/src/modules/hello and watch web/src/App.tsx's types follow.\")\n log('')\n}\n\n// ── web templates ─────────────────────────────────────────────────────\n\nfunction webPackageJson(name: string, clientVersion: string): string {\n return `${JSON.stringify(\n {\n name: `${name}-web`,\n private: true,\n version: '0.0.0',\n type: 'module',\n scripts: {\n dev: 'vite',\n build: 'tsc --noEmit && vite build',\n preview: 'vite preview',\n typecheck: 'tsc --noEmit',\n },\n dependencies: {\n '@forinda/kickjs-client': clientVersion,\n react: '^19.0.0',\n 'react-dom': '^19.0.0',\n },\n devDependencies: {\n '@types/react': '^19.0.0',\n '@types/react-dom': '^19.0.0',\n '@vitejs/plugin-react': '^5.0.0',\n typescript: '^5.9.0',\n vite: '^7.0.0',\n },\n },\n null,\n 2,\n )}\\n`\n}\n\nfunction webViteConfig(): string {\n return `import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({\n plugins: [react()],\n server: {\n // The KickJS server (kick dev) listens on 3000; the client's baseUrl is\n // the relative '/api/v1', so the browser hits Vite and Vite forwards.\n proxy: {\n '/api': 'http://localhost:3000',\n },\n },\n})\n`\n}\n\nexport function webTsConfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n lib: ['ES2022', 'DOM', 'DOM.Iterable'],\n module: 'ESNext',\n moduleResolution: 'bundler',\n jsx: 'react-jsx',\n strict: true,\n skipLibCheck: true,\n noEmit: true,\n isolatedModules: true,\n // The resolved client route map, as an ambient global type package —\n // the same mechanism as \"node\" or \"vitest/globals\". It makes\n // `KickClientApi.Api` available with no import and no bridge file.\n //\n // Note this is a `types` ENTRY, not an `include`: the map is a global\n // type package, not source of this app. It also means the file must\n // exist — `kick new` runs typegen for you, and `kick typegen` in\n // server/ refreshes it. (An `include` entry tolerates the file being\n // absent; a `types` entry reports TS2688, which is the louder and\n // more useful failure for something the app depends on.)\n //\n // No `experimentalDecorators` here: unlike the ambient\n // `KickRoutes.Api` bridge, this map carries no reference to the\n // server's decorated controller sources.\n types: ['../server/.kickjs/types/kick__client'],\n },\n include: ['src'],\n },\n null,\n 2,\n )}\\n`\n}\n\nfunction webIndexHtml(name: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${name}</title>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/src/main.tsx\"></script>\n </body>\n</html>\n`\n}\n\nfunction webMain(): string {\n return `import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { App } from './App'\n\ncreateRoot(document.getElementById('root')!).render(\n <StrictMode>\n <App />\n </StrictMode>,\n)\n`\n}\n\nfunction webApp(): string {\n return `import { useEffect, useState } from 'react'\nimport { api } from './api'\n\n// The response types below are INFERRED from the server's handlers —\n// change server/src/modules/hello/hello.service.ts and these types follow on\n// the next \\`kick typegen\\`. Not under \\`kick dev\\`: resolving the client map\n// builds a whole TypeScript program, so it is a build step, not a per-save one.\ntype Greeting = Awaited<ReturnType<typeof fetchGreeting>>\n\nfunction fetchGreeting() {\n return api.get('/hello')\n}\n\nexport function App() {\n const [greeting, setGreeting] = useState<Greeting | null>(null)\n const [health, setHealth] = useState<string>('checking…')\n\n useEffect(() => {\n fetchGreeting().then(setGreeting).catch(console.error)\n api\n .get('/hello/health')\n .then((h) => setHealth(h.status))\n .catch(() => setHealth('down'))\n }, [])\n\n return (\n <main style={{ fontFamily: 'system-ui', maxWidth: 640, margin: '4rem auto', padding: '0 1rem' }}>\n <h1>KickJS fullstack</h1>\n <p>\n <strong>{greeting?.message ?? 'loading…'}</strong>\n </p>\n <p>\n Server said hello at <code>{greeting?.timestamp ?? '…'}</code> — health:{' '}\n <code>{health}</code>\n </p>\n <p style={{ color: '#666' }}>\n This call is typed end to end: <code>api.get('/hello')</code> returns the exact shape\n <code> HelloService.greet()</code> produces. Rename a field on the server and this file\n stops compiling.\n </p>\n </main>\n )\n}\n`\n}\n\nexport function webApi(): string {\n return `import { createClient } from '@forinda/kickjs-client'\n\n// KickClientApi is ambient — the resolved route map from\n// server/.kickjs/types/kick__client.d.ts, wired in tsconfig's \\`types\\`. Every\n// response type is a literal shape, so nothing from the server's source graph\n// enters this program.\n//\n// Keys are module-mount-relative paths; the bootstrap-level '/api/v1' prefix\n// lives here in baseUrl, and the Vite dev proxy forwards it to the KickJS\n// server.\n//\n// Prefer an explicit import? The same file exports the type:\n// import type { Api } from '../../server/.kickjs/types/kick__client'\nexport const api = createClient<KickClientApi.Api>({ baseUrl: '/api/v1' })\n`\n}\n\n// ── root templates ────────────────────────────────────────────────────\n\nfunction rootPackageJson(name: string, pm: string, cliVersion: string): string {\n const scripts: Record<string, string> =\n pm === 'pnpm'\n ? {\n dev: 'pnpm --parallel -r run dev',\n // One origin: the server serves `web/dist` through SpaAdapter.\n start: 'pnpm --filter ./server run start',\n 'dev:server': 'pnpm --filter ./server dev',\n 'dev:web': 'pnpm --filter ./web dev',\n build: 'pnpm -r run build',\n // No `npx`: the binary is `kick` but the package is\n // `@forinda/kickjs-cli`, so npx cannot map one to the other and\n // falls back to the REGISTRY — where `kick` is an unrelated\n // AngularJS scaffolder. It runs, prints its own help, and exits 0,\n // so this script would pass without type-checking anything.\n //\n // Every package manager puts `node_modules/.bin` on PATH for\n // scripts, so the bare name resolves locally, and a missing install\n // fails loudly instead of fetching a stranger.\n typecheck: 'kick typecheck --cwd server && kick typecheck --cwd web',\n }\n : {\n // cd-based scripts — the one form npm, yarn (classic AND berry),\n // and bun all run identically; workspace-filter flags differ per\n // manager (--workspace vs `yarn workspace <name>` vs --filter).\n start: `cd server && ${pm} run start`,\n 'dev:server': `cd server && ${pm} run dev`,\n 'dev:web': `cd web && ${pm} run dev`,\n build: `${pm} run build:server && ${pm} run build:web`,\n 'build:server': `cd server && ${pm} run build`,\n 'build:web': `cd web && ${pm} run build`,\n // No `npx`: the binary is `kick` but the package is\n // `@forinda/kickjs-cli`, so npx cannot map one to the other and\n // falls back to the REGISTRY — where `kick` is an unrelated\n // AngularJS scaffolder. It runs, prints its own help, and exits 0,\n // so this script would pass without type-checking anything.\n //\n // Every package manager puts `node_modules/.bin` on PATH for\n // scripts, so the bare name resolves locally, and a missing install\n // fails loudly instead of fetching a stranger.\n typecheck: 'kick typecheck --cwd server && kick typecheck --cwd web',\n }\n return `${JSON.stringify(\n {\n name,\n private: true,\n version: '0.0.0',\n type: 'module',\n scripts,\n // The CLI is a ROOT dependency so the bare `kick` in the scripts above\n // resolves: package managers put `node_modules/.bin` on PATH for\n // scripts, and the CLI otherwise lives only in `server/node_modules`.\n devDependencies: { '@forinda/kickjs-cli': cliVersion },\n workspaces: ['server', 'web'],\n },\n null,\n 2,\n )}\\n`\n}\n\nfunction rootGitignore(): string {\n return `node_modules/\ndist/\n.env\n*.log\n.DS_Store\n`\n}\n\nfunction rootReadme(name: string, pm: string): string {\n return `# ${name}\n\nFullstack KickJS workspace — typed end to end.\n\n| Package | What |\n| --------- | ----------------------------------------------------------------- |\n| \\`server/\\` | KickJS API (decorators, DI, \\`kick dev\\` with typegen watch) |\n| \\`web/\\` | Vite + React, typed against the API via \\`@forinda/kickjs-client\\` |\n\n## Develop\n\n\\`\\`\\`bash\n${pm} install\n${pm === 'pnpm' ? 'pnpm dev # server (kick dev) + web (vite), in parallel' : `${pm} run dev:server # terminal 1\\n${pm} run dev:web # terminal 2`}\n\\`\\`\\`\n\nServer: http://localhost:3000 · Web: http://localhost:5173 (Vite proxies \\`/api\\`).\n\n## The type loop\n\n1. Server handlers **return** their payloads (\\`return this.service.greet(...)\\`).\n2. \\`kick typegen\\` emits \\`server/.kickjs/types/kick__client.d.ts\\` — the flat\n route map with every response type resolved to a literal shape.\n3. \\`web/tsconfig.json\\` lists that file in \\`types\\`, so \\`KickClientApi\\` is\n ambient — no import, no bridge file.\n4. \\`web/src/api.ts\\`'s \\`createClient<KickClientApi.Api>\\` types every call site.\n\nBecause the map holds resolved types rather than references to controllers,\n\\`web\\` never compiles server source: no \\`experimentalDecorators\\`, no path\naliases into \\`server/src\\`.\n\nRename a field in \\`server/src/modules/hello/hello.service.ts\\` → \\`web/src/App.tsx\\`\nstops compiling. That's the point.\n\n### Keeping the map fresh\n\n\\`server/.kickjs/types/kick__client.d.ts\\` is generated, and \\`web\\` reads it as\nan ambient type package. Two things follow.\n\n**It is not refreshed by \\`kick dev\\`.** Resolving it builds a whole TypeScript\nprogram over the server, which is a build-step cost rather than a per-save one,\nso a renamed response field surfaces on the next \\`kick typegen\\` rather than on\nsave. Everything else in \\`.kickjs/types\\` still updates on save. Add\n\\`kick typegen --check\\` to CI and a stale map fails the build.\n\n**It needs a compiler API.** TypeScript 7 ships none, so \\`server\\` depends on\n\\`@typescript/typescript6\\`. Remove it and typegen skips this one file, saying\nso; \\`web\\` then reports \\`TS2688\\` because the file it lists in \\`types\\` is gone.\n\n### The other way to wire it\n\nA \\`types\\` entry says \"this is a global type package\", which is what the map is,\nand it fails loudly when the file is missing. If you would rather it be quiet\nwhen absent — say, a repo where the map is not always generated — use\n\\`include\\` instead:\n\n\\`\\`\\`json\n{ \"include\": [\"src\", \"../server/.kickjs/types/kick__client.d.ts\"] }\n\\`\\`\\`\n\nThat tolerates the file not existing, at the cost of \\`KickClientApi\\` silently\nnot resolving. Note \\`types\\` replaces TypeScript's automatic \\`@types\\`\ninclusion, so if you add entries there, list the ones you rely on too\n(\\`\"types\": [\"node\", \"../server/.kickjs/types/kick__client\"]\\`).\n\nOr skip the global entirely — the same file exports the type:\n\n\\`\\`\\`ts\nimport type { Api } from '../../server/.kickjs/types/kick__client'\n\nexport const api = createClient<Api>({ baseUrl: '/api/v1' })\n\\`\\`\\`\n\nDocs: https://kickjs.app/guide/typed-client.html\n`\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"project-BnXVCMd6.mjs","names":[],"sources":["../src/generators/templates/project-app.ts","../src/utils/shell.ts","../src/generators/templates/project-config.ts","../src/commands/add.ts","../src/generators/project.ts"],"sourcesContent":["type ProjectTemplate = 'rest' | 'minimal'\nexport type ProjectRuntime = 'express' | 'fastify' | 'h3'\n\n/** Per-runtime import source + factory name for the scaffolded `runtime:` option. */\nconst RUNTIME_FACTORY: Record<ProjectRuntime, { from: string; name: string }> = {\n express: { from: '@forinda/kickjs', name: 'expressRuntime' },\n fastify: { from: '@forinda/kickjs/fastify', name: 'fastifyRuntime' },\n h3: { from: '@forinda/kickjs/h3', name: 'h3Runtime' },\n}\n\n/**\n * Generate src/index.ts entry file with template-specific bootstrap.\n *\n * The runtime is always emitted explicitly (`runtime: expressRuntime()` etc.)\n * so the entry file is self-documenting and switching engines is a one-line\n * edit. Fastify / h3 parse bodies natively, so the REST template skips the\n * `express.json()` middleware (and the `express` import) under those engines.\n *\n * All templates export the app for the Vite plugin (dev mode).\n */\nexport function generateEntryFile(\n name: string,\n template: ProjectTemplate,\n version: string,\n packages: string[] = [],\n runtime: ProjectRuntime = 'express',\n /**\n * When set, wire `SpaAdapter` at this `clientDir`. Used by the fullstack\n * template so `web/`'s build is served from the API's origin in\n * production. The adapter no-ops while the directory does not exist, so a\n * dev run (where the SPA is served by Vite) is unaffected.\n */\n spaClientDir?: string,\n): string {\n const factory = RUNTIME_FACTORY[runtime]\n const isExpress = runtime === 'express'\n\n switch (template) {\n case 'minimal': {\n const imports: string[] = []\n const adapters: string[] = []\n\n // The runtime factory comes from the core package for Express, or a\n // subpath for Fastify / h3.\n const kickImport = isExpress\n ? `import { bootstrap, ${factory.name} } from '@forinda/kickjs'`\n : `import { bootstrap } from '@forinda/kickjs'\\nimport { ${factory.name} } from '${factory.from}'`\n\n if (packages.includes('swagger')) {\n imports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`)\n adapters.push(` SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`)\n }\n if (packages.includes('devtools')) {\n imports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`)\n adapters.push(` DevToolsAdapter(),`)\n }\n if (spaClientDir) {\n imports.push(`import { SpaAdapter } from '@forinda/kickjs/spa'`)\n // JSON.stringify, not a quoted template hole: this is arbitrary\n // caller-supplied path text being written into a TypeScript file, and\n // a quote, backslash, or newline in it would either break the emitted\n // module or quietly change the path it resolves. The raw value stays\n // out of the comment for the same reason.\n adapters.push(\n ` // Serves the built frontend from this origin in production.\\n` +\n ` // Inert until the client build exists, so \\`kick dev\\` (where Vite\\n` +\n ` // serves the client and proxies /api here) is unaffected.\\n` +\n ` SpaAdapter({ clientDir: ${JSON.stringify(spaClientDir)} }),`,\n )\n }\n const importsBlock = imports.length ? imports.join('\\n') + '\\n' : ''\n const adaptersBlock = adapters.length ? `,\\n adapters: [\\n${adapters.join('\\n')}\\n ]` : ''\n\n return `import 'reflect-metadata'\n// Side-effect import — registers the extended env schema with kickjs\n// **before** any controller / service / @Value gets resolved. Without\n// this line ConfigService.get('YOUR_KEY') returns undefined because the\n// cached schema would still be the base shape. See guide/configuration.\nimport './config'\n${kickImport}\n${importsBlock}import { modules } from './modules'\n\n// Export the app for the Vite plugin (dev mode)\nexport const app = await bootstrap({ modules, runtime: ${factory.name}()${adaptersBlock} })\n`\n }\n\n case 'rest':\n default: {\n // Build adapters based on user-selected packages\n const restImports: string[] = []\n const restAdapters: string[] = []\n\n if (packages.includes('devtools')) {\n restImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`)\n restAdapters.push(` DevToolsAdapter(),`)\n }\n if (packages.includes('swagger')) {\n restImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`)\n restAdapters.push(\n ` SwaggerAdapter({\\n info: { title: '${name}', version: '${version}' },\\n }),`,\n )\n }\n const restImportsBlock = restImports.length ? restImports.join('\\n') + '\\n' : ''\n const restAdaptersBlock = restAdapters.length\n ? `\\n adapters: [\\n${restAdapters.join('\\n')}\\n ],`\n : ''\n\n // Express needs `express.json()` for body parsing; Fastify / h3 parse\n // bodies natively, so adding it would consume the body stream twice.\n const kickNamed = ['bootstrap', 'requestId', 'requestLogger', 'helmet', 'cors']\n if (isExpress) kickNamed.push(factory.name)\n const kickImport = isExpress\n ? `import express from 'express'\\nimport {\\n ${kickNamed.join(',\\n ')},\\n} from '@forinda/kickjs'`\n : `import {\\n ${kickNamed.join(',\\n ')},\\n} from '@forinda/kickjs'\\nimport { ${factory.name} } from '${factory.from}'`\n const bodyParserLine = isExpress ? `\\n express.json(),` : ''\n\n return `import 'reflect-metadata'\n// Side-effect import — registers the extended env schema with kickjs\n// **before** any controller / service / @Value gets resolved. Without\n// this line ConfigService.get('YOUR_KEY') returns undefined because the\n// cached schema would still be the base shape. See guide/configuration.\nimport './config'\n${kickImport}\n${restImportsBlock}import { modules } from './modules'\n\n// Export the app for the Vite plugin (dev mode)\nexport const app = await bootstrap({\n modules,\n runtime: ${factory.name}(),${restAdaptersBlock}\n middlewares: [\n // Security headers. The framework injects helmet() with these defaults\n // anyway — declaring it here is what lets you CHANGE them, e.g.\n // helmet({ frameguard: 'SAMEORIGIN' }) or helmet({ hsts: false }).\n // Auto-injection stands down as soon as this line is present.\n helmet(),\n cors({ origin: '*' }),\n requestId(),\n requestLogger(),${bodyParserLine}\n ],\n})\n`\n }\n }\n}\n\n/** Generate src/modules/index.ts module registry */\nexport function generateModulesIndex(): string {\n return `import { defineModules } from '@forinda/kickjs'\nimport { HelloModule } from './hello/hello.module'\n\n// Remove HelloModule and run: kick g module <name>\n// \\`defineModules()\\` returns a chainable list — \\`kick g module\\` appends\n// \\`.mount(NewModule())\\` to the chain on every generation.\nexport const modules = defineModules().mount(HelloModule())\n`\n}\n\n/**\n * Generate `src/config/index.ts` — the project's typed env schema.\n *\n * Default-exports a `defineEnv(...)` schema so `kick typegen` can\n * infer it into the global `KickEnv` registry, and *also* calls\n * `loadEnvFromSchema(envSchema)` as a module-load side effect so `ConfigService`\n * and `@Value()` see the extended shape from the very first DI\n * resolution. The companion `src/index.ts` template adds\n * `import './config'` immediately after `reflect-metadata` so the\n * registration runs before `bootstrap()` constructs anything.\n *\n * After typegen runs:\n *\n * @Value('DATABASE_URL') private url!: Env<'DATABASE_URL'>\n * process.env.DATABASE_URL // typed as string\n *\n * Both autocomplete and type-check at compile time.\n */\nexport function generateEnvFile(schemaLib: 'zod' | 'valibot' | 'yup' = 'zod'): string {\n if (schemaLib === 'valibot') {\n return `import { loadEnvFromSchema } from '@forinda/kickjs/config'\nimport { fromValibot } from '@forinda/kickjs-schema/valibot'\nimport * as v from 'valibot'\n\n/**\n * Project environment schema (Valibot).\n *\n * \\`fromValibot\\` wraps the Valibot schema as a \\`KickSchema\\` so the\n * env loader, validate middleware, and swagger spec generator all see\n * the same shape. The default export is the contract \\`kick typegen\\`\n * reads to populate \\`KickEnv\\` via \\`InferSchemaOutput<typeof _envSchema>\\`\n * — that's what makes \\`@Value('FOO')\\` autocomplete and\n * \\`process.env.FOO\\` typed.\n *\n * @example\n * DATABASE_URL: v.pipe(v.string(), v.url()),\n * JWT_SECRET: v.pipe(v.string(), v.minLength(32)),\n * REDIS_URL: v.optional(v.pipe(v.string(), v.url())),\n */\nconst envSchema = fromValibot(\n v.object({\n PORT: v.optional(v.pipe(v.string(), v.transform(Number)), '3000'),\n NODE_ENV: v.optional(v.picklist(['development', 'production', 'test']), 'development'),\n LOG_LEVEL: v.optional(v.string(), 'info'),\n // DATABASE_URL: v.pipe(v.string(), v.url()),\n }),\n)\n\n/**\n * IMPORTANT — side effect: register the schema with kickjs's env cache\n * **at module-load time**. \\`ConfigService\\` and \\`@Value()\\` both consume\n * this cache, and they will fall back to the base schema (or undefined)\n * if no extended schema has been registered before they're resolved.\n *\n * As long as \\`src/index.ts\\` imports this file (\\`import './config'\\`) at\n * the top — before \\`bootstrap()\\` runs — every controller and service\n * in the app sees the typed extended values.\n */\nexport const env = loadEnvFromSchema(envSchema)\n\nexport default envSchema\n`\n }\n\n if (schemaLib === 'yup') {\n return `import { loadEnvFromSchema } from '@forinda/kickjs/config'\nimport { fromYup } from '@forinda/kickjs-schema/yup'\nimport * as yup from 'yup'\n\n/**\n * Project environment schema (Yup).\n *\n * \\`fromYup\\` wraps the Yup schema as a \\`KickSchema\\` so the env loader,\n * validate middleware, and swagger spec generator all see the same\n * shape. The default export is the contract \\`kick typegen\\` reads to\n * populate \\`KickEnv\\` via \\`InferSchemaOutput<typeof _envSchema>\\`.\n *\n * Note: Yup's \\`.url()\\` defaults to http/https; database connection\n * strings like \\`postgres://\\` use \\`.matches(/^[a-z]+:\\\\/\\\\/.+/i)\\` or\n * a plain \\`.string().required()\\`.\n *\n * @example\n * DATABASE_URL: yup.string().required(),\n * JWT_SECRET: yup.string().min(32).required(),\n * REDIS_URL: yup.string().url().optional(),\n */\nconst envSchema = fromYup(\n yup.object({\n PORT: yup.number().default(3000),\n NODE_ENV: yup\n .string()\n .oneOf(['development', 'production', 'test'])\n .default('development'),\n LOG_LEVEL: yup.string().default('info'),\n // DATABASE_URL: yup.string().required(),\n }),\n)\n\n/**\n * IMPORTANT — side effect: register the schema with kickjs's env cache\n * **at module-load time**. \\`ConfigService\\` and \\`@Value()\\` both consume\n * this cache, and they will fall back to the base schema (or undefined)\n * if no extended schema has been registered before they're resolved.\n *\n * As long as \\`src/index.ts\\` imports this file (\\`import './config'\\`) at\n * the top — before \\`bootstrap()\\` runs — every controller and service\n * in the app sees the typed extended values.\n */\nexport const env = loadEnvFromSchema(envSchema)\n\nexport default envSchema\n`\n }\n\n // zod (default)\n return `import { loadEnvFromSchema } from '@forinda/kickjs/config'\nimport { fromZod } from '@forinda/kickjs-schema/zod'\nimport { z } from 'zod'\n\n/**\n * Project environment schema (Zod).\n *\n * \\`fromZod\\` wraps the Zod schema as a \\`KickSchema\\` so the env loader,\n * validate middleware, and swagger spec generator all see the same\n * shape. The default export is the contract \\`kick typegen\\` reads to\n * populate \\`KickEnv\\` via \\`InferSchemaOutput<typeof _envSchema>\\` —\n * that's what makes \\`@Value('FOO')\\` autocomplete and\n * \\`process.env.FOO\\` typed.\n *\n * @example\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * REDIS_URL: z.string().url().optional(),\n */\nconst envSchema = fromZod(\n z.object({\n PORT: z.coerce.number().default(3000),\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n LOG_LEVEL: z.string().default('info'),\n // DATABASE_URL: z.string().url(),\n }),\n)\n\n/**\n * IMPORTANT — side effect: register the schema with kickjs's env cache\n * **at module-load time**. \\`ConfigService\\` and \\`@Value()\\` both consume\n * this cache, and they will fall back to the base schema (or undefined)\n * if no extended schema has been registered before they're resolved.\n *\n * As long as \\`src/index.ts\\` imports this file (\\`import './config'\\`) at\n * the top — before \\`bootstrap()\\` runs — every controller and service\n * in the app sees the typed extended values.\n */\nexport const env = loadEnvFromSchema(envSchema)\n\nexport default envSchema\n`\n}\n\n/** Generate src/modules/hello/hello.service.ts */\nexport function generateHelloService(): string {\n return `import { Service } from '@forinda/kickjs'\n\n@Service()\nexport class HelloService {\n greet(name: string) {\n return { message: \\`Hello \\${name} from KickJS!\\`, timestamp: new Date().toISOString() }\n }\n\n healthCheck() {\n return { status: 'ok', uptime: process.uptime() }\n }\n}\n`\n}\n\n/** Generate src/modules/hello/hello.controller.ts */\nexport function generateHelloController(): string {\n return `import { Controller, Get, Autowired, type Ctx } from '@forinda/kickjs'\nimport { HelloService } from './hello.service'\n\n// \\`Ctx<KickRoutes.HelloController['<method>']>\\` is generated by\n// \\`kick typegen\\` (auto-run on \\`kick dev\\`). The first run after a fresh\n// scaffold creates \\`.kickjs/types/routes.ts\\` so this file typechecks.\n// See https://kickjs.app/guide/typegen.\n\n@Controller()\nexport class HelloController {\n @Autowired() private readonly helloService!: HelloService\n\n // Return-value handlers: the runtime sends the returned payload as\n // 200 json, and \\`kick typegen\\` infers the response type into\n // \\`KickRoutes.Api\\` — which is what makes the typed client\n // (@forinda/kickjs-client) end-to-end type-safe.\n @Get('/')\n index(_ctx: Ctx<KickRoutes.HelloController['index']>) {\n return this.helloService.greet('World')\n }\n\n @Get('/health')\n health(_ctx: Ctx<KickRoutes.HelloController['health']>) {\n return this.helloService.healthCheck()\n }\n}\n`\n}\n\n/** Generate src/modules/hello/hello.module.ts */\nexport function generateHelloModule(): string {\n return `import { defineModule } from '@forinda/kickjs'\nimport { HelloController } from './hello.controller'\n\nexport const HelloModule = defineModule({\n name: 'HelloModule',\n build: () => ({\n // \\`register(container)\\` is optional — only implement it when you need\n // to bind a token to a concrete implementation, e.g.\n // register(container) {\n // container.registerFactory(USER_REPOSITORY, () => container.resolve(InMemoryUserRepository))\n // }\n // The HelloService uses @Service() so the decorator handles registration.\n\n routes() {\n return {\n path: '/hello',\n controller: HelloController,\n }\n },\n }),\n})\n`\n}\n\n/** Generate kick.config.ts CLI configuration */\nexport function generateKickConfig(\n template: ProjectTemplate,\n defaultRepo: string = 'inmemory',\n packageManager: 'pnpm' | 'npm' | 'yarn' | 'bun' = 'pnpm',\n runtime: 'express' | 'fastify' | 'h3' = 'express',\n /** Emit the client route map — set for scaffolds whose frontend reads it. */\n withClientMap = false,\n): string {\n // `inmemory` is the only built-in; every other name (incl. the\n // deprecated prisma/drizzle) is emitted as a `{ name }` custom repo.\n const repoValue = defaultRepo === 'inmemory' ? `'inmemory'` : `{ name: '${defaultRepo}' }`\n\n return `import { defineConfig } from '@forinda/kickjs-cli'\n\nexport default defineConfig({\n pattern: '${template}',\n // The HTTP engine this app boots on (matches \\`bootstrap({ runtime })\\` in\n // src/index.ts). Dep-aware commands read it: \\`kick add upload\\` installs the\n // engine's multipart driver, \\`kick doctor\\` checks the engine peers, and\n // \\`kick typegen\\` flips the runtime escape-hatch types to this engine.\n runtime: '${runtime}',\n // Pinned so \\`kick add\\` and other dep-installing commands always use the\n // project's intended package manager, regardless of which lockfile exists.\n packageManager: '${packageManager}',\n modules: {\n dir: 'src/modules',\n repo: ${repoValue},\n pluralize: true,\n },\n\n // \\`kick typegen\\` populates \\`.kickjs/types/\\` so \\`Ctx<KickRoutes.X['method']>\\`\n // resolves to fully-typed params/body/query. Auto-runs on \\`kick dev\\`.\n // \\`'kickjs-schema'\\` routes inference through \\`InferSchemaOutput\\` so the\n // typegen works for any wrapped schema (Zod / Valibot / Yup). Switch\n // to \\`'zod'\\` if you ship Zod schemas without \\`fromZod()\\` wrapping, or\n // set \\`schemaValidator: false\\` to skip schema-driven body typing.\n typegen: {\n schemaValidator: 'kickjs-schema',${\n withClientMap\n ? `\n // web/ reads this map from the ambient KickClientApi namespace. Producing\n // it builds a TypeScript program over the server, so it stays off unless a\n // project actually consumes it.\n client: true,`\n : ''\n }\n },\n\n commands: [\n {\n name: 'test',\n description: 'Run tests with Vitest',\n steps: 'vitest run',\n },\n {\n name: 'lint',\n description: 'Lint with oxlint',\n steps: 'oxlint src/',\n },\n {\n name: 'format',\n description: 'Format code with oxfmt',\n steps: 'oxfmt src/',\n },\n {\n name: 'format:check',\n description: 'Check formatting without writing',\n steps: 'oxfmt --check src/',\n },\n {\n name: 'ci:check',\n description: 'Run typecheck + lint + format check',\n steps: ['kick typecheck', 'oxlint src/', 'oxfmt --check src/'],\n aliases: ['verify'],\n },\n ],\n})\n`\n}\n","import { execFile, execFileSync, execSync, spawnSync } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\n/**\n * Characters an argument may not contain when it has to travel through\n * `cmd.exe`.\n *\n * The first group is shell syntax — `cmd.exe` would interpret these rather\n * than pass them through. The trailing `\\\\` is different in kind: a backslash\n * is not shell syntax, but Windows command-line quoting mangles it. Node's\n * argv encoder doubles trailing backslashes, and a backslash immediately\n * before a closing quote escapes that quote, so a single argument silently\n * splits in two:\n *\n * ['C:\\\\some path\\\\'] → cmd receives ['\"C:\\\\some', 'path\\\\\"']\n * ['no-space\\\\'] → cmd receives ['no-space\\\\\\\\']\n *\n * Correct escaping is possible but fiddly, and no caller in this CLI needs it\n * — arguments here are package names, dist-tags, subcommands and flags, and\n * working directories travel via the `cwd` option rather than as arguments.\n * So a backslash is rejected outright: a loud `null` beats a silently\n * corrupted argument, which is the exact failure mode this module exists to\n * eliminate.\n */\nconst CMD_UNSAFE = /[&|<>^\"'`(){}[\\];!%\\r\\n\\\\]/\n\n/**\n * Build the `[file, args]` pair to hand to `execFileSync`, routing through\n * `cmd.exe` on Windows so `.cmd` shims are launchable.\n *\n * On Windows, `npm` / `pnpm` / `yarn` / `bun` are `.cmd` batch shims, not\n * `.exe`s, and `execFileSync` cannot launch either spelling:\n *\n * - `execFileSync('npm', …)` → ENOENT (there is no `npm.exe` on PATH)\n * - `execFileSync('npm.cmd', …)` → EINVAL (Node >= 18.20 / 20.12 refuses to\n * spawn batch files without a\n * shell — CVE-2024-27980)\n *\n * Both failures are silent wherever the caller swallows the error, so the\n * command looks like it \"returned nothing\" rather than \"never ran\".\n *\n * `/d` skips AutoRun registry commands, `/s` makes cmd treat everything after\n * `/c` as one verbatim string (it strips only the outer quote pair Node adds).\n * Returns `null` when an argument is unsafe to pass through — refusing to run\n * beats running something the shell rewrote.\n *\n * `platform` is injectable so both branches are unit-testable from any host\n * OS; production callers use the default. It is read per call rather than\n * captured at import time for the same reason.\n */\nexport function shellSafeInvocation(\n file: string,\n args: string[],\n platform: NodeJS.Platform = process.platform,\n): [string, string[]] | null {\n if (platform !== 'win32') return [file, args]\n if (CMD_UNSAFE.test(file) || args.some((a) => CMD_UNSAFE.test(a))) return null\n const command = [file, ...args].map((a) => (a.includes(' ') ? `\"${a}\"` : a)).join(' ')\n return [process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', command]]\n}\n\n/**\n * Run a command and capture its trimmed stdout, cross-platform.\n *\n * Returns `null` if the command is missing, exits non-zero, times out, or\n * carries arguments unsafe to pass through `cmd.exe`. Callers treat `null` as\n * \"unknown\" and fall back — the point of this helper is that a Windows `.cmd`\n * shim no longer masquerades as a failed command.\n */\nexport function captureCommand(\n file: string,\n args: string[],\n opts: { timeout?: number; cwd?: string } = {},\n): string | null {\n const invocation = shellSafeInvocation(file, args)\n if (!invocation) return null\n try {\n const out = execFileSync(invocation[0], invocation[1], {\n encoding: 'utf-8',\n timeout: opts.timeout ?? 5000,\n cwd: opts.cwd,\n stdio: ['ignore', 'pipe', 'ignore'],\n windowsHide: true,\n })\n const trimmed = out.toString().trim()\n return trimmed || null\n } catch {\n return null\n }\n}\n\n/**\n * Run a command with inherited stdio, cross-platform. Throws on failure so\n * callers can decide whether a missing package manager is fatal.\n */\nexport function runCommand(file: string, args: string[], opts: { cwd?: string } = {}): void {\n const invocation = shellSafeInvocation(file, args)\n if (!invocation) throw new Error(`Refusing to run '${file}': unsafe arguments`)\n execFileSync(invocation[0], invocation[1], {\n cwd: opts.cwd,\n stdio: 'inherit',\n windowsHide: true,\n })\n}\n\n/**\n * Run a shell command synchronously, printing output.\n *\n * On Windows, `execSync` spawns via `cmd.exe` by default, which means\n * POSIX-style inline env prefixes like `FOO=bar node app.js` do NOT work.\n * Callers that need environment variables should pass them in the `env`\n * option instead of prepending them to the command string — see\n * `runNodeWithEnv` for the cross-platform helper that avoids a shell\n * entirely.\n */\nexport function runShellCommand(command: string, cwd?: string, env?: NodeJS.ProcessEnv): void {\n execSync(command, {\n cwd,\n stdio: 'inherit',\n env: env ? { ...process.env, ...env } : process.env,\n })\n}\n\n/**\n * Cross-platform way to launch a Node.js process with a set of\n * environment variables. Uses `spawnSync` with an argument array so no\n * shell is involved — the `VAR=value node ...` POSIX prefix syntax that\n * `runShellCommand` relied on breaks on cmd.exe and PowerShell.\n */\nexport function runNodeWithEnv(entry: string, env: NodeJS.ProcessEnv, cwd?: string): void {\n const result = spawnSync(process.execPath, [entry], {\n cwd,\n stdio: 'inherit',\n env: { ...process.env, ...env },\n })\n if (result.status !== 0) {\n process.exit(result.status ?? 1)\n }\n}\n\n/**\n * Async twin of `captureCommand` — same Windows `.cmd` routing, same\n * `null`-on-any-failure contract, but genuinely concurrent.\n *\n * `captureCommand` is `execFileSync`, so `Promise.all` over it runs the\n * commands one after another. Callers that fan out over a list of network\n * queries (`npm view` per package) need real concurrency, otherwise the\n * per-call timeout multiplies by the list length.\n */\nexport async function captureCommandAsync(\n file: string,\n args: string[],\n opts: { timeout?: number; cwd?: string } = {},\n): Promise<string | null> {\n const invocation = shellSafeInvocation(file, args)\n if (!invocation) return null\n try {\n const { stdout } = await execFileAsync(invocation[0], invocation[1], {\n encoding: 'utf-8',\n timeout: opts.timeout ?? 5000,\n cwd: opts.cwd,\n windowsHide: true,\n })\n return stdout.toString().trim() || null\n } catch {\n return null\n }\n}\n","type ProjectTemplate = 'rest' | 'minimal'\n\n// `SchemaLib` is defined next to `resolveSchemaLib` in ../../config, so the\n// scaffold that installs the library and the generators that import from it\n// read the same list. Re-exported here for existing importers.\nexport type { SchemaLib } from '../../config'\nimport type { SchemaLib } from '../../config'\n\n/** Map of optional package names to their npm package identifiers */\nconst PACKAGE_DEPS: Record<string, string> = {\n swagger: '@forinda/kickjs-swagger',\n ws: '@forinda/kickjs-ws',\n queue: '@forinda/kickjs-queue',\n devtools: '@forinda/kickjs-devtools',\n}\n\n/**\n * Schema-lib runtime dependency ranges. Pinned to a recent release.\n *\n * Exactly one of these is installed, chosen by `--schema`; `zod` is the `--yes`\n * default for its ecosystem reach (OpenAPI generation, the Standard Schema\n * brand `kick typegen` reads). Whichever lands here is what `resolveSchemaLib`\n * later detects when generating DTO schemas.\n */\nconst SCHEMA_LIB_DEPS: Record<SchemaLib, { name: string; range: string }> = {\n zod: { name: 'zod', range: '^4.3.6' },\n valibot: { name: 'valibot', range: '^1.4.1' },\n yup: { name: 'yup', range: '^1.7.1' },\n}\n\n/**\n * Map of package name → semver range string (`^x.y.z`). Resolved\n * from `npm view <name> version` upstream so per-package independent\n * versioning is honoured at scaffold time. Every sibling\n * `@forinda/kickjs-*` package we might add to the new project must\n * appear here; missing keys throw during package.json generation\n * (loud failure beats silently shipping `^undefined`).\n */\nexport type SiblingVersions = Record<string, string>\n\nfunction take(versions: SiblingVersions, name: string): string {\n const v = versions[name]\n if (!v) {\n throw new Error(\n `generatePackageJson: missing resolved version for ${name}. ` +\n `Add it to SIBLING_PACKAGES in generators/project.ts.`,\n )\n }\n return v\n}\n\n/** Generate package.json with template-aware dependencies */\nexport function generatePackageJson(\n name: string,\n template: ProjectTemplate,\n versions: SiblingVersions,\n packages: string[] = [],\n schemaLib: SchemaLib = 'zod',\n runtime: 'express' | 'fastify' | 'h3' = 'express',\n /**\n * Pin the TypeScript 7 compiler API, needed to resolve the client route map\n * (`.kickjs/types/kick__client.d.ts`). Set by the fullstack generator, whose\n * web app reads that map from the ambient `KickClientApi` namespace.\n *\n * Deliberately a flag rather than `template === 'fullstack'`: the fullstack\n * workspace scaffolds its SERVER with `template: 'minimal'`, so keying off\n * the template name silently pinned nothing.\n */\n withClientMap = false,\n): string {\n const schemaDep = SCHEMA_LIB_DEPS[schemaLib]\n const baseDeps: Record<string, string> = {\n '@forinda/kickjs': take(versions, '@forinda/kickjs'),\n // The schema-agnostic abstraction kickjs-schema wraps zod / valibot\n // / yup behind a single `KickSchema` interface — env validation,\n // body validation, and swagger spec generation all flow through\n // `detectSchema()`. Shipping it as a direct dep (rather than a peer)\n // keeps the new-project install one-step.\n '@forinda/kickjs-schema': take(versions, '@forinda/kickjs-schema'),\n // `dotenv` is an optional peer of @forinda/kickjs — scaffolded apps\n // get it pre-installed so `.env` files Just Work. Apps that load\n // env from the shell or a secret manager can drop this safely.\n dotenv: '^17.3.1',\n 'reflect-metadata': '^0.2.2',\n [schemaDep.name]: schemaDep.range,\n }\n\n // Engine peers for the chosen runtime (optional peers of @forinda/kickjs).\n if (runtime === 'express') {\n // Express is the engine itself.\n baseDeps.express = '^5.1.0'\n } else if (runtime === 'fastify') {\n baseDeps.fastify = '^5.0.0'\n baseDeps['@fastify/middie'] = '^9.0.0'\n // Static serving uses `serve-static` (no express dependency).\n baseDeps['serve-static'] = '^2.2.0'\n } else if (runtime === 'h3') {\n baseDeps.h3 = '^1.0.0'\n baseDeps['serve-static'] = '^2.2.0'\n }\n\n // Add user-selected optional packages — each looked up against\n // the resolved version map so they're independently up-to-date.\n for (const pkg of packages) {\n const dep = PACKAGE_DEPS[pkg]\n if (dep && !baseDeps[dep]) {\n baseDeps[dep] = take(versions, dep)\n }\n }\n\n return JSON.stringify(\n {\n name,\n // Project starts at 0.0.0 — adopters bump as they ship. Tying\n // the project version to the CLI version (the previous\n // behaviour) made every scaffolded app `5.4.0` on day one,\n // which broke npm publishing for adopters trying their first\n // release.\n version: '0.0.0',\n type: 'module',\n scripts: {\n // `kick dev` (not bare `vite`): it boots Vite itself AND owns the\n // typegen-on-save watcher. Plain `vite` gives working HMR but\n // frozen `.kickjs/types` — new routes silently lose their typing\n // until a manual `kick typegen`.\n // Four scripts, not ten. Everything dropped from here is still one\n // command away — `kick dev:debug`, `kick typegen`, `pnpm exec vitest`,\n // `pnpm exec tsc --noEmit` — and a scaffold that opens with a wall of\n // aliases teaches less than one that shows the binary.\n //\n // `lint: 'eslint src/'` used to be here without eslint ever being a\n // dependency, so `lint` failed with \"command not found\" in every\n // generated project.\n dev: 'kick dev',\n build: 'kick build',\n start: 'kick start',\n test: 'vitest run',\n },\n dependencies: baseDeps,\n devDependencies: {\n '@forinda/kickjs-cli': take(versions, '@forinda/kickjs-cli'),\n // The generated AGENTS.md and the `write-controller-test` skill both\n // tell you to test with `createTestApp` + supertest. Shipping those\n // instructions without the packages means the first test a reader\n // writes fails on a missing import.\n '@forinda/kickjs-testing': take(versions, '@forinda/kickjs-testing'),\n '@forinda/kickjs-vite': take(versions, '@forinda/kickjs-vite'),\n '@types/supertest': '^7.2.1',\n '@swc/core': '^1.15.21',\n // Express types only when Express is the engine (it's the only runtime\n // that imports `express` in src/index.ts).\n ...(runtime === 'express' ? { '@types/express': '^5.0.6' } : {}),\n '@types/node': '^25.0.0',\n 'unplugin-swc': '^1.5.9',\n vite: '^8.0.3',\n supertest: '^7.2.2',\n vitest: '^4.1.2',\n typescript: '^7.0.2',\n // Only scaffolds that consume the client route map pin the TypeScript 7\n // compiler API: it is a 10 kB shim over a 24 MB `typescript@6`, which is\n // not something to put in every project. Today that means fullstack,\n // whose web app reads the map from the ambient `KickClientApi`\n // namespace. rest/minimal have no frontend and stay lean.\n ...(withClientMap ? { '@typescript/typescript6': '^6.0.2' } : {}),\n oxfmt: '^0.65.0',\n oxlint: '^1.80.0',\n },\n },\n null,\n 2,\n )\n}\n\n/**\n * Generate vite.config.ts with the KickJS Vite plugin.\n *\n * The plugin handles:\n * - SSR environment setup for backend Node.js code\n * - Virtual module generation (virtual:kickjs/app)\n * - Module auto-discovery (scans *.module.ts files)\n * - HMR with selective container invalidation\n * - Express mounting via configureServer() post-hook\n * - httpServer piping to adapters (WsAdapter, Socket.IO, etc.)\n */\nexport function generateViteConfig(options: { strictPort?: boolean } = {}): string {\n // The fullstack web app proxies /api to a fixed port. Without strictPort,\n // Vite moves to the next free port when that one is taken and the proxy\n // silently talks to whatever else holds it; with it, `kick dev` fails loudly.\n const server = options.strictPort\n ? ` server: {\n // web/vite.config.ts proxies /api to this port — fail instead of moving\n // to another port when it's taken, or the proxy would reach the wrong process.\n strictPort: true,\n },\n`\n : ''\n return `import { defineConfig } from 'vite'\nimport { fileURLToPath } from 'node:url'\nimport swc from 'unplugin-swc'\nimport { kickjsVitePlugin, envWatchPlugin } from '@forinda/kickjs-vite'\n\nexport default defineConfig({\n oxc: false,\n plugins: [\n swc.vite(),\n kickjsVitePlugin({ entry: 'src/index.ts' }),\n // Watches .env files and triggers a full reload on change so the\n // dev server picks up env tweaks without a manual restart.\n envWatchPlugin(),\n ],\n${server} resolve: {\n alias: {\n '@': fileURLToPath(new URL('./src', import.meta.url)),\n },\n },\n build: {\n target: 'node20',\n ssr: true,\n outDir: 'dist',\n sourcemap: true,\n rollupOptions: {\n input: fileURLToPath(new URL('./src/index.ts', import.meta.url)),\n output: { format: 'esm' },\n },\n },\n})\n`\n}\n\n/**\n * Answer pnpm's build-script approvals for `builds` in pnpm-workspace.yaml text,\n * in both formats pnpm has used, so any pnpm 10+ reads them:\n *\n * - `allowBuilds` (pnpm 10.26+; pnpm 11 reads only this)\n * - `onlyBuiltDependencies` / `ignoredBuiltDependencies` (pnpm before 10.26)\n *\n * Each version ignores the format it doesn't read. An existing answer wins: an\n * `allowBuilds` key set to true/false, or a name already in either list, is left\n * alone. A missing key or pnpm's `set this to true or false` placeholder is filled in.\n */\nexport function setAllowBuilds(yaml: string, builds: Record<string, boolean>): string {\n const entries = Object.entries(builds)\n if (entries.length === 0) return yaml\n const lines = yaml === '' ? [] : yaml.replace(/\\n$/, '').split('\\n')\n const unquote = (text: string) => text.trim().replace(/^['\"]|['\"]$/g, '')\n const quote = (name: string) => (/^[\\w-]+$/.test(name) ? name : `'${name}'`)\n\n /** Line range of a top-level block's indented entries; appended when missing and `create`. */\n const block = (key: string, create: boolean): { start: number; end: number } | undefined => {\n let start = lines.findIndex((line) => line.replace(/\\s+$/, '') === `${key}:`)\n if (start === -1) {\n if (!create) return undefined\n if (lines.length > 0 && lines[lines.length - 1] !== '') lines.push('')\n lines.push(`${key}:`)\n start = lines.length - 1\n }\n // The block runs to the next unindented line; blank lines inside it don't end it.\n let end = start + 1\n for (let i = start + 1; i < lines.length && /^(\\s|$)/.test(lines[i]); i++) {\n if (lines[i].trim() !== '') end = i + 1\n }\n return { start, end }\n }\n\n const answers: Array<[string, boolean]> = []\n for (const [name, allow] of entries) {\n const { start, end } = block('allowBuilds', true)!\n const index = lines\n .slice(start + 1, end)\n .findIndex((line) => unquote(line.split(':')[0]) === name)\n const answered =\n index === -1 ? undefined : /:\\s*(true|false)\\s*$/.exec(lines[start + 1 + index])\n if (answered) {\n answers.push([name, answered[1] === 'true'])\n continue\n }\n const entry = ` ${quote(name)}: ${allow}`\n if (index === -1) lines.splice(end, 0, entry)\n else lines[start + 1 + index] = entry\n answers.push([name, allow])\n }\n\n const lists = ['onlyBuiltDependencies', 'ignoredBuiltDependencies']\n for (const [name, allow] of answers) {\n const listed = lists.some((key) => {\n const range = block(key, false)\n return (\n range !== undefined &&\n lines\n .slice(range.start + 1, range.end)\n .some((line) => unquote(line.replace(/^\\s*-/, '')) === name)\n )\n })\n if (listed) continue\n const { end } = block(allow ? lists[0] : lists[1], true)!\n lines.splice(end, 0, ` - ${quote(name)}`)\n }\n return `${lines.join('\\n')}\\n`\n}\n\n/** Generate tsconfig.json with decorator support */\nexport function generateTsConfig(): string {\n return JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'bundler',\n lib: ['ES2022'],\n types: ['node', 'vite/client'],\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n sourceMap: true,\n declaration: true,\n experimentalDecorators: true,\n emitDecoratorMetadata: true,\n outDir: 'dist',\n // rootDir omitted so .kickjs/types/*.d.ts can sit outside src/\n paths: { '@/*': ['./src/*'] },\n },\n // .kickjs/types is generated by `kick typegen` and refreshed\n // automatically on `kick dev`. Including it here makes\n // `container.resolve()` and module discovery type-safe.\n // Both .d.ts and .ts are matched: registry/services/modules are\n // declarations, but routes.ts holds resolvable imports from your\n // controllers' Zod schemas (TS silently degrades inline `import('...')`\n // inside `.d.ts` files under `moduleResolution: 'bundler'`).\n include: ['src', '.kickjs/types/**/*.d.ts', '.kickjs/types/**/*.ts'],\n },\n null,\n 2,\n )\n}\n\n/**\n * Generate `.oxfmtrc.json`.\n *\n * oxfmt over prettier: same options, same output for these settings, one\n * binary instead of a package plus plugins — and it is what the framework\n * itself is formatted with, so a scaffold no longer arrives holding a\n * different tool than the project it came from.\n *\n * There is no `format` script. `pnpm exec oxfmt` is the command, and a\n * scaffold that opens with ten aliases teaches less than one that shows the\n * tools.\n */\nexport function generateFormatterConfig(): string {\n return JSON.stringify(\n {\n $schema: './node_modules/oxfmt/configuration_schema.json',\n semi: false,\n singleQuote: true,\n trailingComma: 'all',\n printWidth: 100,\n tabWidth: 2,\n ignorePatterns: ['**/dist/', '**/.kickjs/', '**/node_modules/'],\n },\n null,\n 2,\n )\n}\n\n/** Generate .editorconfig for consistent editor settings */\nexport function generateEditorConfig(): string {\n return `# https://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n`\n}\n\n/** Generate .gitignore with common Node.js patterns */\nexport function generateGitIgnore(): string {\n return `node_modules/\ndist/\n.env\n# Machine-specific: each developer's test env differs (database names, ports).\n# The shared template is the committed \\`.env.test.example\\`.\n.env.test\n*.local\ncoverage/\n.DS_Store\n*.tsbuildinfo\n.kickjs/\n`\n}\n\n/** Generate .gitattributes for consistent line endings */\nexport function generateGitAttributes(): string {\n return `# Auto-detect text files and normalise line endings to LF\n* text=auto eol=lf\n\n# Explicitly mark generated / binary files\n*.png binary\n*.jpg binary\n*.jpeg binary\n*.gif binary\n*.ico binary\n*.woff binary\n*.woff2 binary\n*.ttf binary\n*.eot binary\n\n# Lock files — treat as generated\npnpm-lock.yaml -diff linguist-generated\nyarn.lock -diff linguist-generated\npackage-lock.json -diff linguist-generated\n`\n}\n\n/** Generate .env file with default environment variables */\nexport function generateEnv(): string {\n return `PORT=3000\nNODE_ENV=development\n`\n}\n\n/** Generate .env.example file as a template */\nexport function generateEnvExample(): string {\n return `PORT=3000\nNODE_ENV=development\n`\n}\n\n/**\n * Generate `.env.test` — the environment the test suite runs against.\n *\n * Under a test run KickJS reads this file INSTEAD of `.env` (no layering,\n * no fallback), so scaffolding it is what makes a new project isolated by\n * default rather than inheriting the developer's environment.\n *\n * Deliberately NOT a copy of `.env.example`. The point of the file is that\n * a var it omits is *missing* rather than quietly inherited, so the suite\n * fails loudly on something it forgot to declare. Copying every key back\n * in would rebuild the exact trap this closes.\n *\n * `PORT=0` asks the OS for a free port, so a test run cannot collide with\n * a dev server already on 3000.\n *\n * Gitignored, like `.env`: values differ per machine, and a committed copy\n * turns every local tweak into a diff for the whole team. The shared\n * template is {@link generateEnvTestExample}.\n */\nexport function generateEnvTest(): string {\n return `# Read INSTEAD of .env when NODE_ENV=test (or under vitest).\n# No fallback to .env — declare here everything the suite needs, so a\n# missing var fails the run instead of silently resolving to your dev value.\n#\n# Not committed. Add new keys to .env.test.example too, with placeholder\n# values — never real endpoints or credentials.\nNODE_ENV=test\nPORT=0\nLOG_LEVEL=silent\n`\n}\n\n/** Generate `.env.test.example` — the committed template a fresh clone copies to `.env.test`. */\nexport function generateEnvTestExample(): string {\n return `# Template for .env.test. Copy it and fill in your machine's values:\n# cp .env.test.example .env.test\n${generateEnvTest()}`\n}\n\n/** Generate vitest.config.ts for test configuration */\nexport function generateVitestConfig(): string {\n return `import { defineConfig, mergeConfig } from 'vitest/config'\nimport viteConfig from './vite.config.ts'\n\n// A \\`vitest.config.ts\\` OVERRIDES \\`vite.config.ts\\` outright — vitest does not\n// merge the two, and it never reads tsconfig \\`paths\\`. Restating settings here\n// would mean the \\`@\\` alias lives in three files and drifts in two of them, so\n// merge the real config instead: the alias, plugins, and ssr externals all come\n// from one place.\nexport default mergeConfig(\n viteConfig,\n defineConfig({\n test: {\n globals: true,\n environment: 'node',\n include: ['src/**/*.test.ts'],\n },\n }),\n)\n`\n}\n","import { execSync } from 'node:child_process'\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport type { Command } from 'commander'\nimport { loadKickConfig, PACKAGE_MANAGERS, type PackageManager } from '../config'\nimport { setAllowBuilds } from '../generators/templates/project-config'\n\ninterface PackageEntry {\n pkg: string\n peers: string[]\n /**\n * Peers depend on the project's HTTP engine, so they are resolved from\n * {@link resolveAppRuntime} rather than listed statically. Set on the\n * framework package itself: the engine is chosen at `bootstrap({ runtime })`,\n * so a Fastify project must not be handed `express`.\n */\n enginePeers?: boolean\n description: string\n dev?: boolean\n /**\n * `true` for packages every project needs (framework + Vite plugin +\n * CLI). `kick new` installs these regardless of options chosen, and\n * future package-removal flows refuse to drop them.\n */\n core?: boolean\n /**\n * Set when the package still installs but should no longer be the\n * default choice. The string is the migration hint shown both in\n * `kick add --list --all` and as a warning when the package is added.\n */\n deprecated?: string\n /**\n * Install-script answers for dependencies this package pulls in.\n * Package managers block or skip unanswered scripts (see approveInstallScripts),\n * so `kick add` and `kick new` record these answers before installing.\n */\n builds?: Record<string, boolean>\n}\n\n/** Registry of KickJS packages and their required peer dependencies */\nexport const PACKAGE_REGISTRY: Record<string, PackageEntry> = {\n // Core (always installed by kick new — required for the framework to run)\n kickjs: {\n pkg: '@forinda/kickjs',\n // Resolved per runtime — see `enginePeers` / ENGINE_PEERS.\n peers: [],\n enginePeers: true,\n description: 'Unified framework: DI, decorators, routing, middleware',\n core: true,\n },\n vite: {\n pkg: '@forinda/kickjs-vite',\n peers: ['vite'],\n description: 'Vite plugin: dev server, HMR, module discovery',\n dev: true,\n core: true,\n },\n cli: {\n pkg: '@forinda/kickjs-cli',\n peers: [],\n description: 'CLI tool and code generators',\n dev: true,\n core: true,\n },\n\n // Schema validation — the validator backing env + DTO + OpenAPI\n // schemas. `@forinda/kickjs-schema` (a core dep) wraps whichever one\n // you pick behind `KickSchema`, but the validator itself is an\n // optional peer of `@forinda/kickjs`, so it must be installed\n // explicitly or the app errors at startup (\"Cannot find module\n // 'zod'\"). `kick new` installs the chosen one; `kick add` lets an\n // existing project add/switch.\n zod: {\n pkg: 'zod',\n peers: [],\n description: 'Zod schema validation (env, DTOs, OpenAPI) — wrap with fromZod()',\n },\n valibot: {\n pkg: 'valibot',\n peers: [],\n description: 'Valibot schema validation — wrap with fromValibot()',\n },\n yup: {\n pkg: 'yup',\n peers: [],\n description: 'Yup schema validation — wrap with fromYup()',\n },\n\n // AI — requires zod (^4) for tool/schema definitions.\n ai: {\n pkg: '@forinda/kickjs-ai',\n peers: ['zod'],\n description: 'AI toolkit — LLM providers, tool definitions from controllers',\n },\n\n // API\n swagger: {\n pkg: '@forinda/kickjs-swagger',\n peers: [],\n description: 'OpenAPI spec + Swagger UI + ReDoc',\n // swagger-ui-dist depends on @scarf/scarf, which has a postinstall script.\n builds: { '@scarf/scarf': true },\n },\n // Database — the dialect adapters now ship as subpaths of\n // `@forinda/kickjs-db` (`/pg`, `/sqlite`, `/mysql`), so each `kick add`\n // pulls the core package plus the one driver you need.\n db: {\n pkg: '@forinda/kickjs-db',\n peers: [],\n description: 'kick/db core — schema DSL, migrations, KickDbClient, customType',\n },\n pg: {\n pkg: '@forinda/kickjs-db',\n peers: ['pg'],\n description: 'kick/db + PostgreSQL driver (use @forinda/kickjs-db/pg)',\n },\n sqlite: {\n pkg: '@forinda/kickjs-db',\n peers: ['better-sqlite3'],\n description: 'kick/db + SQLite driver (use @forinda/kickjs-db/sqlite)',\n },\n mysql: {\n pkg: '@forinda/kickjs-db',\n peers: ['mysql2'],\n description: 'kick/db + MySQL driver (use @forinda/kickjs-db/mysql)',\n },\n\n // Real-time\n ws: {\n pkg: '@forinda/kickjs-ws',\n peers: ['ws'],\n description: 'WebSocket with @WsController decorators',\n },\n\n // DevTools\n devtools: {\n pkg: '@forinda/kickjs-devtools',\n peers: [],\n description: 'Development dashboard — routes, DI, metrics, health',\n dev: true,\n },\n\n // Queue\n queue: {\n pkg: '@forinda/kickjs-queue',\n peers: [],\n description: 'Queue adapter (BullMQ/RabbitMQ/Kafka)',\n },\n 'queue:bullmq': {\n pkg: '@forinda/kickjs-queue',\n peers: ['bullmq', 'ioredis'],\n description: 'Queue with BullMQ + Redis',\n },\n 'queue:rabbitmq': {\n pkg: '@forinda/kickjs-queue',\n peers: ['amqplib'],\n description: 'Queue with RabbitMQ',\n },\n 'queue:kafka': {\n pkg: '@forinda/kickjs-queue',\n peers: ['kafkajs'],\n description: 'Queue with Kafka',\n },\n 'queue:redis-pubsub': {\n pkg: '@forinda/kickjs-queue',\n peers: ['ioredis'],\n description: 'Lightweight pub/sub via Redis (no persistence)',\n },\n\n // MCP — Model Context Protocol server\n mcp: {\n pkg: '@forinda/kickjs-mcp',\n peers: ['@modelcontextprotocol/sdk'],\n description: 'Model Context Protocol server — expose @Controller endpoints as AI tools',\n },\n\n // Testing\n testing: {\n pkg: '@forinda/kickjs-testing',\n peers: [],\n description: 'Test utilities and TestModule builder',\n dev: true,\n },\n}\n\n/**\n * Headline `kick add` packages shown after scaffolding — derived from\n * {@link PACKAGE_REGISTRY} so it can never advertise a deprecated package (the\n * old hardcoded list included auth / drizzle / prisma, all three since removed).\n * Excludes core packages\n * (already installed), deprecated ones, `:` sub-variants (e.g. `queue:bullmq`),\n * and the db-dialect / schema-lib duplicates that clutter a one-line summary.\n * `kick add --list` shows the full catalog.\n */\nexport const AVAILABLE_ADD_PACKAGES = Object.entries(PACKAGE_REGISTRY)\n .filter(\n ([name, entry]) =>\n !entry.core &&\n !entry.deprecated &&\n !name.includes(':') &&\n !['pg', 'sqlite', 'mysql', 'zod', 'valibot', 'yup'].includes(name),\n )\n .map(([name]) => name)\n .join(', ')\n\n/**\n * The `upload` catalog name is special — file uploads ship inside\n * `@forinda/kickjs` itself, so there's no package to install. What an app\n * needs is the multipart DRIVER for its HTTP runtime, and that differs per\n * engine. `planAddPackages` resolves `upload` against the configured runtime\n * (see {@link KickConfig.runtime}); `kick doctor` validates the same mapping.\n */\n/**\n * Engine peers per runtime — the same set `kick new` scaffolds for the chosen\n * engine (`generators/templates/project-config.ts`). `@forinda/kickjs` declares\n * all three as optional peers; which ones a project actually needs is decided\n * by `bootstrap({ runtime })`, so installing them is a runtime-dependent\n * question, not a static dependency.\n */\nexport const ENGINE_PEERS: Record<'express' | 'fastify' | 'h3', readonly string[]> = {\n express: ['express'],\n // `serve-static` rather than express for static files — no express dependency.\n fastify: ['fastify', '@fastify/middie', 'serve-static'],\n h3: ['h3', 'serve-static'],\n}\n\nexport const UPLOAD_DRIVERS: Record<\n 'express' | 'fastify' | 'h3',\n { prod?: string; dev?: string; note: string }\n> = {\n express: {\n prod: 'multer',\n dev: '@types/multer',\n note: 'Express uploads use multer (memory/disk storage, ctx.file / ctx.files).',\n },\n fastify: {\n prod: '@fastify/multipart',\n note: 'Fastify uploads use @fastify/multipart (buffered into ctx.file / ctx.files).',\n },\n h3: {\n note: 'h3 parses multipart natively (readMultipartFormData) — no driver to install.',\n },\n}\n\nexport type AppRuntime = 'express' | 'fastify' | 'h3'\n\n/**\n * Resolve the project's HTTP runtime: the `runtime` field in kick.config\n * (authoritative — `kick new` writes it), falling back to sniffing installed\n * deps in the nearest package.json (`fastify` → fastify, `h3` → h3), else\n * `express` (the default engine). Lets `kick add upload` / `kick doctor` pick\n * the engine-correct multipart driver even in projects scaffolded before the\n * `runtime` field existed.\n */\n/** Validate `--runtime`, returning undefined when absent and exiting on a bad value. */\nexport function parseRuntimeFlag(value: unknown): AppRuntime | undefined {\n if (value === undefined) return undefined\n if (value === 'express' || value === 'fastify' || value === 'h3') return value\n throw new Error(`Invalid --runtime '${String(value)}'. Expected express, fastify or h3.`)\n}\n\nexport async function resolveAppRuntime(cwd = process.cwd()): Promise<AppRuntime> {\n return (await resolveAppRuntimeDetailed(cwd)).runtime\n}\n\n/**\n * As {@link resolveAppRuntime}, but says whether the answer was a guess.\n *\n * `kick.config.ts`'s `runtime` is authoritative and never ambiguous. Sniffing\n * is, so callers that act on the result — installing engine peers — surface the\n * ambiguity instead of silently picking.\n */\nexport async function resolveAppRuntimeDetailed(\n cwd = process.cwd(),\n): Promise<RuntimeDetection & { fromConfig: boolean }> {\n const config = await loadKickConfig(cwd)\n const fromConfig = (config as { runtime?: AppRuntime } | null)?.runtime\n if (fromConfig === 'express' || fromConfig === 'fastify' || fromConfig === 'h3') {\n return { runtime: fromConfig, candidates: [fromConfig], ambiguous: false, fromConfig: true }\n }\n return { ...detectRuntimeFromDepsDetailed(cwd), fromConfig: false }\n}\n\n/** Sniff the runtime from installed deps when kick.config has no `runtime`. */\n/**\n * Sniff result, with enough detail for a caller to say when it is guessing.\n *\n * `ambiguous` means more than one engine is installed and nothing settles which\n * one the app boots on. That happens legitimately — a project on Express with\n * Fastify in devDependencies for a benchmark — and the old precedence order\n * (fastify first, whether prod or dev) answered `fastify` there, which installed\n * the wrong engine peers.\n */\nexport interface RuntimeDetection {\n runtime: AppRuntime\n /** Engines found, in `dependencies` then `devDependencies`. */\n candidates: AppRuntime[]\n ambiguous: boolean\n}\n\nconst ENGINE_BY_PACKAGE: ReadonlyArray<readonly [string, AppRuntime]> = [\n ['express', 'express'],\n ['fastify', 'fastify'],\n ['h3', 'h3'],\n]\n\n/** Sniff the runtime from installed deps when kick.config has no `runtime`. */\nexport function detectRuntimeFromDepsDetailed(cwd = process.cwd()): RuntimeDetection {\n const dir = findUp('package.json', cwd)\n if (!dir) return { runtime: 'express', candidates: [], ambiguous: false }\n\n let prod: Record<string, unknown> = {}\n let dev: Record<string, unknown> = {}\n try {\n const pkg = JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf-8'))\n prod = (pkg.dependencies ?? {}) as Record<string, unknown>\n dev = (pkg.devDependencies ?? {}) as Record<string, unknown>\n } catch {\n return { runtime: 'express', candidates: [], ambiguous: false }\n }\n\n const inProd = ENGINE_BY_PACKAGE.filter(([p]) => p in prod).map(([, r]) => r)\n const inDev = ENGINE_BY_PACKAGE.filter(([p]) => p in dev && !(p in prod)).map(([, r]) => r)\n const candidates = [...inProd, ...inDev]\n\n // A runtime dependency outranks a dev one: you deploy on what you ship. That\n // IS a resolution, so it is not ambiguous — reporting otherwise made the\n // refusal below block the exact case this rule exists to allow (an Express\n // app keeping fastify in devDependencies for a benchmark).\n if (inProd.length === 1) return { runtime: inProd[0], candidates, ambiguous: false }\n if (inProd.length === 0 && inDev.length === 1) {\n return { runtime: inDev[0], candidates, ambiguous: false }\n }\n if (candidates.length === 0) return { runtime: 'express', candidates, ambiguous: false }\n\n // Two or more engines at the same level — nothing here can tell them apart.\n // Default to the framework default and let the caller say it is guessing.\n return { runtime: 'express', candidates, ambiguous: true }\n}\n\n/** Back-compat wrapper — the runtime only. */\nexport function detectRuntimeFromDeps(cwd = process.cwd()): AppRuntime {\n return detectRuntimeFromDepsDetailed(cwd).runtime\n}\n\n/**\n * Walk up from `fromDir` to filesystem root, returning the first\n * directory that contains `name`. Lets monorepo sub-packages pick up\n * lockfiles and `packageManager` fields living at the workspace root.\n */\nfunction findUp(name: string, fromDir = process.cwd()): string | null {\n let current = fromDir\n while (true) {\n if (existsSync(resolve(current, name))) return current\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nfunction detectFromLockfile(): PackageManager | null {\n if (findUp('pnpm-lock.yaml')) return 'pnpm'\n if (findUp('yarn.lock')) return 'yarn'\n if (findUp('bun.lockb') || findUp('bun.lock')) return 'bun'\n if (findUp('package-lock.json')) return 'npm'\n return null\n}\n\n/**\n * Read `packageManager` from the nearest ancestor `package.json` that\n * declares the field (corepack convention: `\"pnpm@10.0.0\"`). Climbs so\n * monorepo sub-packages inherit the workspace pm even when their own\n * package.json omits the field.\n */\nfunction packageManagerFromPackageJson(): PackageManager | null {\n let dir: string | null = process.cwd()\n while (dir) {\n const pkgPath = resolve(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n const field: unknown = pkg.packageManager\n if (typeof field === 'string') {\n const name = field.split('@')[0] as PackageManager\n if (PACKAGE_MANAGERS.includes(name)) return name\n }\n } catch {\n // ignore — keep climbing\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n return null\n}\n\nexport type PackageManagerSource = 'flag' | 'config' | 'package.json' | 'lockfile' | 'default'\n\n/**\n * Resolve which package manager to use, in priority order:\n * 1. `--pm` CLI flag\n * 2. `packageManager` in kick.config\n * 3. `packageManager` in nearest ancestor package.json (corepack)\n * 4. Nearest ancestor lockfile (pnpm-lock.yaml → yarn.lock → bun.lock → package-lock.json)\n * 5. `'npm'` fallback\n *\n * Returns the chosen pm plus the source for callers that want to log\n * the resolution path.\n */\nexport async function resolvePackageManagerWithSource(\n flagPm: string | undefined,\n): Promise<{ pm: PackageManager; source: PackageManagerSource }> {\n if (flagPm && PACKAGE_MANAGERS.includes(flagPm as PackageManager)) {\n return { pm: flagPm as PackageManager, source: 'flag' }\n }\n\n const config = await loadKickConfig(process.cwd())\n if (config?.packageManager && PACKAGE_MANAGERS.includes(config.packageManager)) {\n return { pm: config.packageManager, source: 'config' }\n }\n\n const fromPkg = packageManagerFromPackageJson()\n if (fromPkg) return { pm: fromPkg, source: 'package.json' }\n\n const fromLock = detectFromLockfile()\n if (fromLock) return { pm: fromLock, source: 'lockfile' }\n\n return { pm: 'npm', source: 'default' }\n}\n\n/** Convenience wrapper for callers that don't care about the source. */\nexport async function resolvePackageManager(flagPm: string | undefined): Promise<PackageManager> {\n const { pm } = await resolvePackageManagerWithSource(flagPm)\n return pm\n}\n\n/**\n * Print the package catalog. By default shows just the three core\n * packages every project always has — the optional list churns\n * (packages added, deprecated, removed) and a long enumeration in CLI\n * output / docs goes stale within a release. Pass `all = true` to dump\n * everything; that's what `kick add --list --all` triggers when an\n * adopter genuinely wants the live catalog.\n */\nexport function printPackageList(all = false, runtime?: 'express' | 'fastify' | 'h3'): void {\n const entries = Object.entries(PACKAGE_REGISTRY)\n const maxName = Math.max(...entries.map(([k]) => k.length))\n const core = entries.filter(([, info]) => info.core)\n const optional = entries.filter(([, info]) => !info.core)\n\n const formatRow = ([name, info]: [string, PackageEntry]): string => {\n const padded = name.padEnd(maxName + 2)\n // Engine peers depend on the runtime, so name it rather than printing a\n // list that would be wrong for two projects out of three.\n const resolved = info.enginePeers\n ? [...info.peers, ...ENGINE_PEERS[runtime ?? 'express']]\n : info.peers\n const engineNote = info.enginePeers ? ` [${runtime ?? 'express'} engine]` : ''\n const peers = resolved.length ? ` (+ ${resolved.join(', ')})${engineNote}` : ''\n const deprecated = info.deprecated ? ` [DEPRECATED — ${info.deprecated}]` : ''\n return ` ${padded} ${info.description}${peers}${deprecated}`\n }\n\n console.log('\\n Core packages (always installed by `kick new`):\\n')\n for (const row of core) console.log(formatRow(row))\n\n if (all) {\n console.log('\\n Optional packages (add as needed):\\n')\n for (const row of optional) console.log(formatRow(row))\n } else {\n console.log(`\\n Plus ${optional.length} optional packages (swagger, db, queue, …).`)\n console.log(' Run `kick add --list --all` for the full catalog.')\n }\n\n console.log('\\n Usage: kick add ai db swagger')\n console.log(' kick add queue:bullmq')\n console.log(' kick add upload # installs the multipart driver for your runtime')\n console.log()\n}\n\n/** Build tools every template installs: swc compiles the decorators, esbuild is Vite's. */\nexport const TEMPLATE_BUILDS: Record<string, boolean> = { '@swc/core': true, esbuild: true }\n\n/**\n * Record install-script answers where `pm` reads them, in `dir`:\n *\n * - pnpm 10+: pnpm-workspace.yaml, in both approval formats (see setAllowBuilds).\n * Unanswered scripts are skipped with a warning before pnpm 11; pnpm 11 fails\n * the install and every later `pnpm exec` with ERR_PNPM_IGNORED_BUILDS.\n * - npm 11.19+: `allowScripts` in package.json. Unanswered scripts are skipped\n * with a warning.\n * - bun: `trustedDependencies` in package.json. Untrusted scripts are skipped.\n * Only approvals are written; bun has no deny entry.\n * - yarn runs dependency scripts, so there is nothing to write.\n *\n * Existing answers win: a scaffold or `kick add` never overrides a decision\n * already in the file. Returns the file it changed, if any.\n */\nexport function approveInstallScripts(\n pm: PackageManager,\n dir: string,\n builds: Record<string, boolean>,\n): string | undefined {\n if (pm === 'pnpm') {\n const file = resolve(dir, 'pnpm-workspace.yaml')\n const before = existsSync(file) ? readFileSync(file, 'utf-8') : ''\n const after = setAllowBuilds(before, builds)\n if (after === before) return undefined\n writeFileSync(file, after)\n return file\n }\n if (pm !== 'npm' && pm !== 'bun') return undefined\n\n const file = resolve(dir, 'package.json')\n if (!existsSync(file)) return undefined\n const pkg = JSON.parse(readFileSync(file, 'utf-8'))\n let changed = false\n if (pm === 'npm') {\n const allow: Record<string, boolean> = pkg.allowScripts ?? {}\n for (const [name, value] of Object.entries(builds)) {\n // npm also accepts version-pinned keys (`esbuild@0.28.2`).\n if (Object.keys(allow).some((key) => key === name || key.startsWith(`${name}@`))) continue\n allow[name] = value\n changed = true\n }\n pkg.allowScripts = allow\n } else {\n const trusted: string[] = pkg.trustedDependencies ?? []\n for (const [name, value] of Object.entries(builds)) {\n if (!value || trusted.includes(name)) continue\n trusted.push(name)\n changed = true\n }\n pkg.trustedDependencies = trusted\n }\n if (!changed) return undefined\n writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\\n`)\n return file\n}\n\n/**\n * Where `pm` reads install-script answers from: the workspace root when the\n * project is a workspace member (fullstack server/), else the current directory.\n */\nfunction installRoot(pm: PackageManager): string {\n if (pm === 'pnpm') return findUp('pnpm-workspace.yaml') ?? process.cwd()\n for (let dir = process.cwd(); ; dir = dirname(dir)) {\n try {\n if (JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf-8')).workspaces) return dir\n } catch {\n // no package.json here, or not JSON — keep walking\n }\n if (dirname(dir) === dir) return process.cwd()\n }\n}\n\n/** Merged install-script answers for catalog entries (unknown names skipped). */\nexport function buildsFor(packages: string[]): Record<string, boolean> {\n return Object.assign({}, ...packages.map((name) => PACKAGE_REGISTRY[name]?.builds ?? {}))\n}\n\nexport interface AddPlan {\n prodDeps: string[]\n devDeps: string[]\n unknown: string[]\n /** Deprecation notices for requested entries — print, then install anyway. */\n warnings: string[]\n /** Informational notes (e.g. the upload driver chosen for the runtime). */\n notices: string[]\n}\n\n/**\n * Pure resolution step for `kick add` — maps requested catalog names to\n * the npm packages (plus peers) to install, split prod/dev. Kept free\n * of I/O so the catalog rules (dev defaults, deprecations, unknown\n * handling) are unit-testable without spawning a package manager.\n */\nexport function planAddPackages(\n packages: string[],\n forceDev: boolean,\n runtime: AppRuntime = 'express',\n): AddPlan {\n const prodDeps = new Set<string>()\n const devDeps = new Set<string>()\n const unknown: string[] = []\n const warnings: string[] = []\n const notices: string[] = []\n\n for (const name of packages) {\n // `upload` isn't a package — it's the runtime's multipart driver. File\n // uploads ship in @forinda/kickjs; only the engine backend needs adding.\n if (name === 'upload') {\n const driver = UPLOAD_DRIVERS[runtime]\n notices.push(`upload (${runtime}): ${driver.note}`)\n if (driver.prod) (forceDev ? devDeps : prodDeps).add(driver.prod)\n if (driver.dev) devDeps.add(driver.dev)\n continue\n }\n\n const entry = PACKAGE_REGISTRY[name]\n if (!entry) {\n unknown.push(name)\n continue\n }\n if (entry.deprecated) {\n warnings.push(`'${name}' (${entry.pkg}) is deprecated — ${entry.deprecated}`)\n }\n const target = forceDev || entry.dev ? devDeps : prodDeps\n target.add(entry.pkg)\n for (const peer of entry.peers) {\n target.add(peer)\n }\n if (entry.enginePeers) {\n const enginePeers = ENGINE_PEERS[runtime]\n for (const peer of enginePeers) target.add(peer)\n notices.push(`${name} (${runtime}): engine peers ${enginePeers.join(', ')}`)\n }\n }\n\n return { prodDeps: [...prodDeps], devDeps: [...devDeps], unknown, warnings, notices }\n}\n\nexport function registerListCommand(program: Command): void {\n program\n .command('list')\n .alias('ls')\n .description('List KickJS packages (core only; pair with --all for the full catalog)')\n .option('--all', 'Include the full optional catalog')\n .option('--runtime <engine>', 'HTTP engine to show peers for: express | fastify | h3')\n .action(async (opts: { all?: boolean; runtime?: string }) => {\n printPackageList(\n Boolean(opts.all),\n parseRuntimeFlag(opts.runtime) ?? (await resolveAppRuntime()),\n )\n })\n}\n\nexport function registerAddCommand(program: Command): void {\n program\n .command('add [packages...]')\n .description('Add KickJS packages with their required dependencies')\n .option('--pm <manager>', 'Package manager override')\n .option('-D, --dev', 'Install as dev dependency')\n .option('--list', 'List packages (core only by default; pair with --all)')\n .option(\n '--runtime <engine>',\n 'HTTP engine for peer resolution: express | fastify | h3 (overrides kick.config.ts)',\n )\n .option('--all', 'When listing, include the full optional catalog')\n .action(async (packages: string[], opts: any) => {\n // List mode\n if (opts.list || packages.length === 0) {\n printPackageList(\n Boolean(opts.all),\n parseRuntimeFlag(opts.runtime) ?? (await resolveAppRuntime()),\n )\n return\n }\n\n const { pm, source } = await resolvePackageManagerWithSource(opts.pm)\n console.log(`\\n Using ${pm} (resolved from ${source})`)\n // Resolve the runtime so `kick add upload` installs the right multipart\n // driver (express → multer, fastify → @fastify/multipart, h3 → none) and\n // the framework gets the engine peers it actually runs on.\n const fromFlag = parseRuntimeFlag(opts.runtime)\n const detection = await resolveAppRuntimeDetailed(process.cwd())\n const runtime = fromFlag ?? detection.runtime\n\n // Two engines at the same dependency level and nothing to break the tie.\n // Installing writes package.json and node_modules, so guessing here\n // leaves an adopter to undo it by hand — refuse instead, with both ways\n // to say what the app actually boots on.\n if (!fromFlag && detection.ambiguous) {\n console.error(\n `\\n Cannot tell which HTTP engine this app boots on: found ` +\n `${detection.candidates.join(', ')} in package.json.\\n` +\n `\\n Installing engine peers for the wrong one is worse than not installing them, so:\\n` +\n ` • set \\`runtime: '<engine>'\\` in kick.config.ts (permanent, also fixes kick doctor), or\\n` +\n ` • pass --runtime <${detection.candidates.join('|')}> for this command\\n`,\n )\n process.exitCode = 1\n return\n }\n const { prodDeps, devDeps, unknown, warnings, notices } = planAddPackages(\n packages,\n Boolean(opts.dev),\n runtime,\n )\n\n for (const warning of warnings) {\n console.warn(`\\n WARNING: ${warning}`)\n }\n\n for (const notice of notices) {\n console.log(`\\n ${notice}`)\n }\n\n if (unknown.length > 0) {\n console.log(`\\n Unknown packages: ${unknown.join(', ')}`)\n console.log(' Run \"kick add --list\" to see available packages.\\n')\n if (prodDeps.length === 0 && devDeps.length === 0) return\n }\n\n // Answer install-script approvals before installing, so the package\n // manager doesn't block the scripts these packages bring in.\n const builds = buildsFor(packages)\n if (Object.keys(builds).length > 0) {\n const file = approveInstallScripts(pm, installRoot(pm), builds)\n if (file) console.log(`\\n Approved install scripts in ${file}`)\n }\n\n // Install production dependencies\n if (prodDeps.length > 0) {\n const deps = prodDeps\n const cmd = `${pm} add ${deps.join(' ')}`\n console.log(`\\n Installing ${deps.length} dependency(ies):`)\n for (const dep of deps) console.log(` + ${dep}`)\n console.log()\n try {\n execSync(cmd, { stdio: 'inherit' })\n } catch {\n console.log(`\\n Installation failed. Run manually:\\n ${cmd}\\n`)\n }\n }\n\n // Install dev dependencies\n if (devDeps.length > 0) {\n const deps = devDeps\n const cmd = `${pm} add -D ${deps.join(' ')}`\n console.log(`\\n Installing ${deps.length} dev dependency(ies):`)\n for (const dep of deps) console.log(` + ${dep} (dev)`)\n console.log()\n try {\n execSync(cmd, { stdio: 'inherit' })\n } catch {\n console.log(`\\n Installation failed. Run manually:\\n ${cmd}\\n`)\n }\n }\n\n console.log(' Done!\\n')\n })\n}\n","import { join, dirname } from 'node:path'\nimport { execSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { writeFileSafe } from '../utils/fs'\nimport { captureCommand, captureCommandAsync } from '../utils/shell'\nimport {\n generatePackageJson,\n generateViteConfig,\n generateTsConfig,\n generateFormatterConfig,\n generateEditorConfig,\n generateGitIgnore,\n generateGitAttributes,\n generateEnv,\n generateEnvExample,\n generateEnvTest,\n generateEnvTestExample,\n generateVitestConfig,\n} from './templates/project-config'\nimport {\n generateEntryFile,\n generateEnvFile,\n generateModulesIndex,\n generateKickConfig,\n generateHelloService,\n generateHelloController,\n generateHelloModule,\n} from './templates/project-app'\nimport { generateReadme } from './templates/project-docs'\nimport {\n AVAILABLE_ADD_PACKAGES,\n TEMPLATE_BUILDS,\n approveInstallScripts,\n buildsFor,\n} from '../commands/add'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n/**\n * The CLI's own package.json sits one level up from the bundle\n * (`dist/project-*.mjs`) but two levels up from this source file\n * (`src/generators/project.ts`). Checking both is what lets this module be\n * imported from a test at all — reading only the bundle-relative path threw\n * ENOENT the moment anything imported it from source, which is why the\n * version-resolution path shipped untested.\n */\nconst cliPkg = JSON.parse(\n readFileSync(\n [join(__dirname, '..', 'package.json'), join(__dirname, '..', '..', 'package.json')].find(\n existsSync,\n )!,\n 'utf-8',\n ),\n)\n\n/**\n * Sibling `@forinda/kickjs-*` packages whose versions are resolved\n * independently when scaffolding a new project. Each entry is queried\n * via `npm view <name> version`; failure falls back to `latest`\n * (see `fallbackRange`).\n *\n * Per-package independent versioning landed with changesets — before\n * that, every sibling shipped in lockstep with the CLI so a single\n * pin was correct. Now `@forinda/kickjs@5.5.0` may pair with\n * `@forinda/kickjs-cli@5.4.2` and `@forinda/kickjs-swagger@5.3.1`;\n * pinning them all to the CLI's version under-installs adopters.\n */\nconst SIBLING_PACKAGES = [\n '@forinda/kickjs',\n '@forinda/kickjs-cli',\n '@forinda/kickjs-schema',\n '@forinda/kickjs-vite',\n '@forinda/kickjs-swagger',\n '@forinda/kickjs-ws',\n '@forinda/kickjs-queue',\n '@forinda/kickjs-devtools',\n '@forinda/kickjs-testing',\n '@forinda/kickjs-client',\n] as const\n\n/**\n * Range to write when `npm view <name> version` gives us nothing.\n *\n * It must never be the CLI's own version. Sibling packages version\n * independently, so `@forinda/kickjs-vite@^${cliVersion}` names a release\n * that does not exist — the scaffold then dies at install time with\n * `npm error 404 '@forinda/kickjs-vite@^6.14.1' is not in this registry`,\n * which is worse than the under-install this fallback was meant to avoid.\n * `latest` is the one range that is always resolvable and always current.\n *\n * The CLI itself is the exception: its version IS the CLI's version, and\n * pinning it keeps a scaffold reproducible against the CLI that made it.\n */\nfunction fallbackRange(name: string): string {\n return name === '@forinda/kickjs-cli' ? `^${cliPkg.version}` : 'latest'\n}\n\n/**\n * Resolve the latest published version of every sibling package via\n * `npm view <name> version` (through `captureCommandAsync`, which routes\n * around Windows' `.cmd` shims — see utils/shell.ts).\n *\n * The timeout is generous and the queries are genuinely concurrent: a\n * warm `npm view` against the public registry measures 0.6–3.6s per\n * package, so the old 5s budget was one slow response away from expiring,\n * and the sync `captureCommand` under `Promise.all` ran all ten serially\n * — making the per-call budget the only thing standing between a scaffold\n * and a package.json full of fallbacks.\n */\nexport async function resolveSiblingVersions(): Promise<Record<string, string>> {\n const results = await Promise.all(\n SIBLING_PACKAGES.map(async (name) => {\n // Network failure / package not yet published / npm unavailable\n // all surface as null → fall back to a range that always resolves.\n const out = await captureCommandAsync('npm', ['view', name, 'version'], { timeout: 20_000 })\n if (out && /^\\d+\\.\\d+\\.\\d+/.test(out)) {\n return [name, `^${out}`] as const\n }\n return [name, fallbackRange(name)] as const\n }),\n )\n return Object.fromEntries(results)\n}\n\n/**\n * Resolve the published version of a package at a given dist-tag\n * (`npm view <name>@<tag> version`). Returns `null` on any failure. Used\n * to pin `@forinda/kickjs` to the `alpha` channel when scaffolding a\n * Fastify / h3 app — the engine subpaths (`@forinda/kickjs/fastify`,\n * `/h3`) ship only on the alpha until the runtimes land in a stable\n * release, so the default `latest` resolution would install a kickjs\n * that doesn't export them (→ Vite \"./h3 is not exported\" at boot).\n * Returns the bare version; the caller applies a `^` range so the project\n * floats to newer alphas and auto-graduates to stable (a caret over a\n * prerelease matches same-tuple prereleases ≥ it, plus later stables `< next\n * major`).\n */\nfunction resolveVersionAtTag(name: string, tag: string): string | null {\n const out = captureCommand('npm', ['view', `${name}@${tag}`, 'version'], { timeout: 20_000 })\n return out && /^\\d+\\.\\d+\\.\\d+/.test(out) ? out : null\n}\n\n/**\n * Whether the package at a given dist-tag exports a subpath (e.g. `./h3`).\n * Reads the `exports` map via `npm view <name>@<tag> exports --json`. Used to\n * gate the alpha-pin: if `latest` already ships the engine subpath, the runtime\n * has graduated to stable and we should NOT downgrade to an older alpha.\n * Returns `false` on any failure (missing field / network / unparseable) so the\n * caller treats \"unknown\" as \"not present\" and falls through to the alpha path.\n */\n/** Strip a leading range operator (`^1.2.3` / `~1.2.3` → `1.2.3`). */\nfunction stripRange(range: string | undefined): string {\n return (range ?? '').replace(/^[\\^~>=<\\s]+/, '')\n}\n\n/**\n * Compare the release cores (major.minor.patch, ignoring any `-prerelease`\n * suffix) of two versions: is `a` >= `b`? Used to guard the alpha-pin so a\n * package is never downgraded onto a stale prerelease whose stable line has\n * already moved past it. A coarse compare is enough here — we only need\n * \"is this alpha at least as new as the stable we'd otherwise install\".\n */\nfunction baseVersionGte(a: string, b: string): boolean {\n const core = (v: string): number[] =>\n stripRange(v)\n .split('-')[0]!\n .split('.')\n .map((n) => Number.parseInt(n, 10) || 0)\n const [a0 = 0, a1 = 0, a2 = 0] = core(a)\n const [b0 = 0, b1 = 0, b2 = 0] = core(b)\n if (a0 !== b0) return a0 > b0\n if (a1 !== b1) return a1 > b1\n return a2 >= b2\n}\n\nfunction tagExportsSubpath(name: string, tag: string, subpath: string): boolean {\n const out = captureCommand('npm', ['view', `${name}@${tag}`, 'exports', '--json'], {\n timeout: 20_000,\n })\n if (!out) return false\n try {\n const exportsMap = JSON.parse(out) as Record<string, unknown>\n return Object.prototype.hasOwnProperty.call(exportsMap, subpath)\n } catch {\n return false\n }\n}\n\ntype ProjectTemplate = 'rest' | 'minimal'\ntype SchemaLib = 'zod' | 'valibot' | 'yup'\n\ninterface InitProjectOptions {\n name: string\n directory: string\n packageManager?: 'pnpm' | 'npm' | 'yarn' | 'bun'\n initGit?: boolean\n installDeps?: boolean\n template?: ProjectTemplate\n defaultRepo?: string\n packages?: string[]\n /** Schema library to scaffold env / DTOs with. Defaults to `zod`. */\n schemaLib?: SchemaLib\n /** HTTP engine to scaffold. Defaults to `express`. */\n runtime?: 'express' | 'fastify' | 'h3'\n /** Wire `SpaAdapter` at this clientDir (fullstack template). */\n spaClientDir?: string\n /** Pin the compiler API + generate the client route map (fullstack). */\n withClientMap?: boolean\n /** Fail `kick dev` when its port is taken instead of moving (fullstack's /api proxy needs the port). */\n strictPort?: boolean\n /**\n * Record install-script approvals for the package manager (pnpm-workspace.yaml,\n * or package.json for npm/bun). Default true; false when the project is a\n * member of a workspace whose root records them.\n */\n approveInstallScripts?: boolean\n}\n\n/** Scaffold a new KickJS project */\nexport async function initProject(options: InitProjectOptions): Promise<void> {\n const {\n name,\n directory,\n packageManager = 'pnpm',\n template = 'rest',\n defaultRepo = 'inmemory',\n packages = [],\n schemaLib = 'zod',\n runtime = 'express',\n } = options\n const dir = directory\n\n const log = (msg: string) => console.log(` ${msg}`)\n\n console.log(`\\n Creating KickJS project: ${name}\\n`)\n\n // Resolve published version of every sibling kickjs package in\n // parallel. Per-package independent versioning means\n // `@forinda/kickjs@5.5.0` may pair with `@forinda/kickjs-cli@5.4.2`\n // and `@forinda/kickjs-swagger@5.3.1`; pinning every dep to the\n // CLI's own version under-installs adopters whenever a sibling\n // bumps independently. `npm view` fallback keeps the scaffold\n // working offline.\n log('Resolving package versions...')\n const versions = await resolveSiblingVersions()\n\n // A 'latest' range means the registry query failed for that package. The\n // install still works, but the pin is looser than intended and the user\n // should know why their package.json does not read like the docs.\n const unresolved = Object.keys(versions).filter((name) => versions[name] === 'latest')\n if (unresolved.length > 0) {\n log(`WARNING: could not reach npm for ${unresolved.join(', ')} — pinned to 'latest'`)\n }\n\n // The pluggable-runtimes work (Fastify / h3 engine subpaths, the\n // `kick/runtime` typegen, `kick add upload`, `kick doctor` runtime checks)\n // ships only on the `alpha` channel until it lands in a stable release. So a\n // non-Express scaffold needs the alpha of every package that carries runtime\n // behavior, not just `@forinda/kickjs`:\n // - `@forinda/kickjs` — the `./fastify` / `./h3` export subpaths the\n // app imports (stable lacks them → Vite boot\n // error `\"./h3\" is not exported`).\n // - `@forinda/kickjs-cli` — `--runtime`, `kick add upload`, `kick doctor`,\n // the `kick/runtime` typegen plugin.\n // - `@forinda/kickjs-vite` — the dev loop co-versioned with the above.\n // Gated on whether `@forinda/kickjs@latest` already exports the chosen engine\n // subpath: once that's true the runtimes are stable and we keep `latest` for\n // everything (self-retiring — no code change needed at graduation). Each pin\n // is guarded so it never DOWNGRADES (an alpha can be older than latest — e.g.\n // a package whose stable moved on past an old prerelease). Express is exempt.\n if (runtime !== 'express') {\n const subpath = `./${runtime}` // './fastify' | './h3'\n if (tagExportsSubpath('@forinda/kickjs', 'latest', subpath)) {\n log(`Using @forinda/kickjs@latest (stable ships the ${runtime} runtime)`)\n } else {\n const RUNTIME_PKGS = ['@forinda/kickjs', '@forinda/kickjs-cli', '@forinda/kickjs-vite']\n const pinned: string[] = []\n let kickjsPinned = false\n for (const pkg of RUNTIME_PKGS) {\n const alpha = resolveVersionAtTag(pkg, 'alpha')\n // Only switch when the alpha is newer-or-equal to the stable we'd\n // otherwise install — never downgrade onto a stale prerelease. Use a\n // `^` range (not an exact pin) so the project picks up newer alphas and\n // auto-graduates to the stable release once it ships.\n if (alpha && baseVersionGte(alpha, stripRange(versions[pkg]))) {\n versions[pkg] = `^${alpha}`\n pinned.push(`${pkg}@^${alpha}`)\n if (pkg === '@forinda/kickjs') kickjsPinned = true\n }\n }\n if (kickjsPinned) {\n log(`Using the alpha channel for the ${runtime} runtime: ${pinned.join(', ')}`)\n } else {\n log(\n `WARNING: could not resolve @forinda/kickjs@alpha — the ${runtime} runtime subpath ` +\n `may be missing. After install, run: ${packageManager} add @forinda/kickjs@alpha`,\n )\n }\n }\n }\n\n // ── package.json — template-aware deps ────────────────────────────\n await writeFileSafe(\n join(dir, 'package.json'),\n generatePackageJson(\n name,\n template,\n versions,\n packages,\n schemaLib,\n runtime,\n options.withClientMap,\n ),\n )\n\n // ── vite.config.ts — enables HMR + SWC for decorators ──────────────\n await writeFileSafe(\n join(dir, 'vite.config.ts'),\n generateViteConfig({ strictPort: options.strictPort }),\n )\n\n // ── tsconfig.json ───────────────────────────────────────────────────\n await writeFileSafe(join(dir, 'tsconfig.json'), generateTsConfig())\n\n // ── .oxfmtrc.json ───────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.oxfmtrc.json'), generateFormatterConfig())\n\n // ── .editorconfig ─────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.editorconfig'), generateEditorConfig())\n\n // ── .gitignore ──────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.gitignore'), generateGitIgnore())\n\n // ── .gitattributes ────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.gitattributes'), generateGitAttributes())\n\n // ── .env ────────────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.env'), generateEnv())\n\n await writeFileSafe(join(dir, '.env.example'), generateEnvExample())\n\n // `.env.test` is read INSTEAD of `.env` under a test run, so scaffolding\n // it is what makes a new project isolated by default. Without it the\n // generated app ships the exact shape `kick doctor` warns about — a\n // `.env` plus a test runner — and its first test run prints the backfill\n // warning rather than being isolated. It is gitignored like `.env` —\n // values differ per machine — so the committed template is the example.\n await writeFileSafe(join(dir, '.env.test'), generateEnvTest())\n await writeFileSafe(join(dir, '.env.test.example'), generateEnvTestExample())\n\n // ── src/config/index.ts — typed env schema (read by `kick typegen`) ─\n // Lives under `src/config/` so the framework's \"config\" concept has a\n // single, conventional home. Old projects with `src/env.ts` still\n // work — `detectEnvFile()` searches both locations.\n await writeFileSafe(join(dir, 'src/config/index.ts'), generateEnvFile(schemaLib))\n\n // ── src/index.ts — template-aware entry point ─────────────────────\n await writeFileSafe(\n join(dir, 'src/index.ts'),\n generateEntryFile(name, template, cliPkg.version, packages, runtime, options.spaClientDir),\n )\n\n // ── src/modules/index.ts ────────────────────────────────────────────\n await writeFileSafe(join(dir, 'src/modules/index.ts'), generateModulesIndex())\n\n // ── src/modules/hello/ — sample module ─────────────────────────────\n await writeFileSafe(join(dir, 'src/modules/hello/hello.service.ts'), generateHelloService())\n await writeFileSafe(join(dir, 'src/modules/hello/hello.controller.ts'), generateHelloController())\n await writeFileSafe(join(dir, 'src/modules/hello/hello.module.ts'), generateHelloModule())\n\n // ── kick.config.ts — CLI configuration ─────────────────────────────\n await writeFileSafe(\n join(dir, 'kick.config.ts'),\n generateKickConfig(template, defaultRepo, packageManager, runtime, options.withClientMap),\n )\n\n // ── vitest.config.ts ────────────────────────────────────────────────\n await writeFileSafe(join(dir, 'vitest.config.ts'), generateVitestConfig())\n\n // ── README.md ────────────────────────────────────────────────────────\n await writeFileSafe(join(dir, 'README.md'), generateReadme(name, template, packageManager))\n\n // ── Agent docs ──────────────────────────────────────────────────────\n // Delegate to `generateAgentDocs()` so `kick new` emits the same\n // `.agents/` subfolder layout as `kick g agents -f`. Otherwise the\n // two paths drifted: kick new was writing the legacy flat layout\n // (root-level AGENTS.md + kickjs-skills.md) while kick g agents\n // emits the per-skill SKILL.md format under .agents/. `force: true`\n // because the project directory is fresh — no overwrite prompts\n // make sense during init.\n const { generateAgentDocs } = await import('./agent-docs')\n await generateAgentDocs({\n outDir: dir,\n name,\n pm: packageManager,\n template,\n only: 'all',\n force: true,\n })\n\n // ── Install-script approvals ─────────────────────────────────────────\n // Before install; see approveInstallScripts.\n if (options.approveInstallScripts !== false) {\n approveInstallScripts(packageManager, dir, { ...TEMPLATE_BUILDS, ...buildsFor(packages) })\n }\n\n // ── Install Dependencies ────────────────────────────────────────────\n // Install BEFORE git init so the lockfile is included in the first commit.\n if (options.installDeps) {\n console.log(`\\n Installing dependencies with ${packageManager}...\\n`)\n try {\n execSync(`${packageManager} install`, { cwd: dir, stdio: 'inherit' })\n console.log('\\n Dependencies installed successfully!')\n } catch {\n console.log(`\\n Warning: ${packageManager} install failed. Run it manually.`)\n }\n }\n\n // ── Initial typegen ────────────────────────────────────────────────\n // Run typegen once so the freshly-scaffolded HelloController's\n // `Ctx<KickRoutes.HelloController['index']>` references resolve in\n // the user's editor immediately. Failures are non-fatal.\n try {\n const { runTypegen } = await import('../typegen')\n await runTypegen({ cwd: dir, allowDuplicates: true, silent: true })\n } catch {\n // First-run typegen errors are non-fatal — `kick dev` will retry.\n }\n\n // ── Git Init ─────────────────────────────────────────────────────────\n // Runs after install + typegen so lockfile and generated types are\n // included in the initial commit.\n if (options.initGit) {\n try {\n execSync('git init', { cwd: dir, stdio: 'pipe' })\n execSync('git branch -M main', { cwd: dir, stdio: 'pipe' })\n execSync('git add -A', { cwd: dir, stdio: 'pipe' })\n execSync('git commit -m \"chore: initial commit from kick new\"', {\n cwd: dir,\n stdio: 'pipe',\n })\n log('Git repository initialized')\n } catch {\n log('Warning: git init failed (git may not be installed)')\n }\n }\n\n console.log('\\n Project scaffolded successfully!')\n console.log()\n\n const needsCd = dir !== process.cwd()\n log('Next steps:')\n if (needsCd) log(` cd ${name}`)\n if (!options.installDeps) log(` ${packageManager} install`)\n\n const genHint: Record<string, string> = {\n rest: 'kick g module user',\n minimal: '# add your routes to src/index.ts',\n }\n log(` ${genHint[template] ?? genHint.rest}`)\n log(' kick dev')\n log('')\n log('Commands:')\n log(' kick dev Start dev server with Vite HMR')\n log(' kick build Production build via Vite')\n log(' kick start Run production build')\n log('')\n log('Generators:')\n log(' kick g module <name> REST module (controller, service, DTOs, repository)')\n log(' kick g scaffold <n> <f..> CRUD module from field definitions')\n log(' kick g controller <name> Standalone controller')\n log(' kick g service <name> @Service() class')\n log(' kick g middleware <name> Global connect-style middleware')\n log(' kick g guard <name> Route guard (auth, roles, etc.)')\n log(' kick g adapter <name> AppAdapter with lifecycle hooks')\n log(' kick g dto <name> DTO schema (Zod / Valibot / Yup)')\n log(' kick g config Generate kick.config.ts')\n log('')\n log('Add packages:')\n log(' kick add <pkg> Install a KickJS package + peers')\n log(' kick add --list Show all available packages')\n log('')\n log(`Available: ${AVAILABLE_ADD_PACKAGES}`)\n log('')\n}\n"],"mappings":"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"}