@forinda/kickjs-cli 6.2.0-alpha.1 → 6.2.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 (26) hide show
  1. package/dist/{agent-docs-DKEQiq25.mjs → agent-docs-Di96qILM.mjs} +3 -3
  2. package/dist/{agent-docs-DKEQiq25.mjs.map → agent-docs-Di96qILM.mjs.map} +1 -1
  3. package/dist/{build-I8Yhoqj-.mjs → build-C_RWnIv1.mjs} +3 -3
  4. package/dist/{build-I8Yhoqj-.mjs.map → build-C_RWnIv1.mjs.map} +1 -1
  5. package/dist/{builtins-7IQexhj7.mjs → builtins-BdJFdAsP.mjs} +2 -2
  6. package/dist/cli.mjs +3 -3
  7. package/dist/{config-DXJWJLD4.mjs → config-G8kmxDyZ.mjs} +3 -3
  8. package/dist/{config-DXJWJLD4.mjs.map → config-G8kmxDyZ.mjs.map} +1 -1
  9. package/dist/{doctor-414bnUd8.mjs → doctor-KBy5WFZJ.mjs} +7 -7
  10. package/dist/doctor-KBy5WFZJ.mjs.map +1 -0
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +2 -2
  13. package/dist/{plugin-C03BaYpV.mjs → plugin-BH1mZC2X.mjs} +3 -3
  14. package/dist/{plugin-C03BaYpV.mjs.map → plugin-BH1mZC2X.mjs.map} +1 -1
  15. package/dist/{project-docs-gnsQyilx.mjs → project-docs-DKOLHfqu.mjs} +2 -2
  16. package/dist/{project-docs-gnsQyilx.mjs.map → project-docs-DKOLHfqu.mjs.map} +1 -1
  17. package/dist/{project-root-gmupeTuu.mjs → project-root-DsnEUmMN.mjs} +3 -3
  18. package/dist/{project-root-gmupeTuu.mjs.map → project-root-DsnEUmMN.mjs.map} +1 -1
  19. package/dist/{rolldown-runtime-BKjf5NYQ.mjs → rolldown-runtime-BoqBCbMv.mjs} +1 -1
  20. package/dist/{run-plugins-A7mE5YjC.mjs → run-plugins-Bql3IbaE.mjs} +4 -4
  21. package/dist/{run-plugins-A7mE5YjC.mjs.map → run-plugins-Bql3IbaE.mjs.map} +1 -1
  22. package/dist/{typegen-C4nEHdxn.mjs → typegen-C7AgyPIB.mjs} +5 -5
  23. package/dist/{typegen-C4nEHdxn.mjs.map → typegen-C7AgyPIB.mjs.map} +1 -1
  24. package/dist/{types-3XZpaE0p.mjs → types-xMa5uCri.mjs} +1 -1
  25. package/package.json +4 -4
  26. package/dist/doctor-414bnUd8.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v6.2.0-alpha.1
2
+ * @forinda/kickjs-cli v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as e}from"./rolldown-runtime-BKjf5NYQ.mjs";import{a as t,b as n,i as r,n as i,r as a,s as o,t as s}from"./project-docs-gnsQyilx.mjs";import{i as c}from"./config-DXJWJLD4.mjs";import{join as l}from"node:path";import{existsSync as u,readFileSync as d}from"node:fs";var f=e({generateAgentDocs:()=>v});const p=`.agents`,m=new Set([`rest`,`minimal`]);function h(e,t){if(t)return t;try{let t=JSON.parse(d(l(e,`package.json`),`utf-8`));if(t.name)return t.name.replace(/^@[^/]+\//,``)}catch{}return e.split(`/`).findLast(Boolean)??`app`}function g(e,t){if(t)return t;try{let t=JSON.parse(d(l(e,`package.json`),`utf-8`));if(t.packageManager)return t.packageManager.split(`@`)[0]}catch{}return`pnpm`}async function _(e,t){if(t)return t;try{let t=(await c(e))?.pattern;if(t&&m.has(t))return t}catch{}return`rest`}async function v(e){let c=e.only??`all`,d=h(e.outDir,e.name),f=g(e.outDir,e.pm),m=await _(e.outDir,e.template),v=c===`agents`||c===`both`||c===`all`,y=c===`claude`||c===`both`||c===`all`,b=c===`skills`||c===`all`,x=c===`gemini`||c===`all`,S=c===`copilot`||c===`all`,C=[];if(v&&C.push({file:l(e.outDir,p,`AGENTS.md`),render:()=>s(d,m,f)}),y&&C.push({file:l(e.outDir,`CLAUDE.md`),render:()=>i(d,m,f)}),b)for(let n of t(d,m,f))C.push({file:l(e.outDir,p,`skills`,n.slug,`SKILL.md`),render:()=>n.content});x&&C.push({file:l(e.outDir,p,`GEMINI.md`),render:()=>r(d,m,f)}),S&&C.push({file:l(e.outDir,p,`COPILOT.md`),render:()=>a(d,m,f)});let w=[];for(let{file:t,render:r}of C){if(u(t)&&!e.force&&!await o({message:`${t.replace(e.outDir+`/`,``)} already exists. Overwrite?`,initialValue:!1})){console.log(` Skipped — existing ${t.replace(e.outDir+`/`,``)} preserved.`);continue}await n(t,r()),w.push(t)}return w}export{v as n,f as t};
12
- //# sourceMappingURL=agent-docs-DKEQiq25.mjs.map
11
+ import{t as e}from"./rolldown-runtime-BoqBCbMv.mjs";import{a as t,b as n,i as r,n as i,r as a,s as o,t as s}from"./project-docs-DKOLHfqu.mjs";import{i as c}from"./config-G8kmxDyZ.mjs";import{join as l}from"node:path";import{existsSync as u,readFileSync as d}from"node:fs";var f=e({generateAgentDocs:()=>v});const p=`.agents`,m=new Set([`rest`,`minimal`]);function h(e,t){if(t)return t;try{let t=JSON.parse(d(l(e,`package.json`),`utf-8`));if(t.name)return t.name.replace(/^@[^/]+\//,``)}catch{}return e.split(`/`).findLast(Boolean)??`app`}function g(e,t){if(t)return t;try{let t=JSON.parse(d(l(e,`package.json`),`utf-8`));if(t.packageManager)return t.packageManager.split(`@`)[0]}catch{}return`pnpm`}async function _(e,t){if(t)return t;try{let t=(await c(e))?.pattern;if(t&&m.has(t))return t}catch{}return`rest`}async function v(e){let c=e.only??`all`,d=h(e.outDir,e.name),f=g(e.outDir,e.pm),m=await _(e.outDir,e.template),v=c===`agents`||c===`both`||c===`all`,y=c===`claude`||c===`both`||c===`all`,b=c===`skills`||c===`all`,x=c===`gemini`||c===`all`,S=c===`copilot`||c===`all`,C=[];if(v&&C.push({file:l(e.outDir,p,`AGENTS.md`),render:()=>s(d,m,f)}),y&&C.push({file:l(e.outDir,`CLAUDE.md`),render:()=>i(d,m,f)}),b)for(let n of t(d,m,f))C.push({file:l(e.outDir,p,`skills`,n.slug,`SKILL.md`),render:()=>n.content});x&&C.push({file:l(e.outDir,p,`GEMINI.md`),render:()=>r(d,m,f)}),S&&C.push({file:l(e.outDir,p,`COPILOT.md`),render:()=>a(d,m,f)});let w=[];for(let{file:t,render:r}of C){if(u(t)&&!e.force&&!await o({message:`${t.replace(e.outDir+`/`,``)} already exists. Overwrite?`,initialValue:!1})){console.log(` Skipped — existing ${t.replace(e.outDir+`/`,``)} preserved.`);continue}await n(t,r()),w.push(t)}return w}export{v as n,f as t};
12
+ //# sourceMappingURL=agent-docs-Di96qILM.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-docs-DKEQiq25.mjs","names":[],"sources":["../src/generators/agent-docs.ts"],"sourcesContent":["import { join } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { writeFileSafe } from '../utils/fs'\nimport { confirm } from '../utils/prompts'\nimport {\n generateClaude,\n generateAgents,\n generateKickJsSkillFiles,\n generateGemini,\n generateCopilot,\n} from './templates/project-docs'\nimport { loadKickConfig } from '../config'\n\ntype ProjectTemplate = 'rest' | 'minimal'\n\n/**\n * Subdirectory (relative to the project root) where every shared\n * agent-context file lands. CLAUDE.md is the only exception — it stays\n * at the project root because Claude Code auto-loads CLAUDE.md from\n * there. CLAUDE.md is generated as a thin pointer that tells Claude\n * to read `.agents/AGENTS.md` first.\n *\n * Existing root-level AGENTS.md / kickjs-skills.md files (from older\n * scaffolds before this restructure) are left untouched — the\n * generator emits the new layout alongside them and leaves migration\n * to the adopter.\n */\nconst AGENTS_DIR = '.agents'\n\nexport interface GenerateAgentDocsOptions {\n outDir: string\n /** Override project name (defaults to package.json `name`). */\n name?: string\n /** Override package manager (defaults to package.json `packageManager` field, then 'pnpm'). */\n pm?: string\n /** Override template (defaults to kick.config.ts `pattern`, then 'ddd'). */\n template?: ProjectTemplate\n /**\n * Which file(s) to (re)generate. All `.agents/`-bound files land in\n * the project's `.agents/` subdirectory; `claude` is the only file\n * that stays at the project root.\n *\n * - `agents` → `.agents/AGENTS.md`\n * - `claude` → `CLAUDE.md` (root; thin pointer to .agents/)\n * - `skills` → `.agents/kickjs-skills.md`\n * - `gemini` → `.agents/GEMINI.md`\n * - `copilot` → `.agents/COPILOT.md`\n * - `both` → `.agents/AGENTS.md` + `CLAUDE.md` (legacy alias)\n * - `all` → every file above\n */\n only?: 'agents' | 'claude' | 'skills' | 'gemini' | 'copilot' | 'both' | 'all'\n /** Skip the overwrite prompt. */\n force?: boolean\n}\n\nconst VALID_TEMPLATES = new Set<ProjectTemplate>(['rest', 'minimal'])\n\nfunction detectName(outDir: string, override?: string): string {\n if (override) return override\n try {\n const pkg = JSON.parse(readFileSync(join(outDir, 'package.json'), 'utf-8')) as { name?: string }\n if (pkg.name) return pkg.name.replace(/^@[^/]+\\//, '')\n } catch {\n // No package.json — fall back to folder name\n }\n return outDir.split('/').findLast(Boolean) ?? 'app'\n}\n\nfunction detectPm(outDir: string, override?: string): string {\n if (override) return override\n try {\n const pkg = JSON.parse(readFileSync(join(outDir, 'package.json'), 'utf-8')) as {\n packageManager?: string\n }\n if (pkg.packageManager) return pkg.packageManager.split('@')[0]\n } catch {\n // ignore\n }\n return 'pnpm'\n}\n\nasync function detectTemplate(\n outDir: string,\n override?: ProjectTemplate,\n): Promise<ProjectTemplate> {\n if (override) return override\n try {\n const cfg = await loadKickConfig(outDir)\n const pattern = cfg?.pattern as ProjectTemplate | undefined\n if (pattern && VALID_TEMPLATES.has(pattern)) return pattern\n } catch {\n // ignore\n }\n return 'rest'\n}\n\nexport async function generateAgentDocs(options: GenerateAgentDocsOptions): Promise<string[]> {\n const only = options.only ?? 'all'\n const name = detectName(options.outDir, options.name)\n const pm = detectPm(options.outDir, options.pm)\n const template = await detectTemplate(options.outDir, options.template)\n\n const wantsAgents = only === 'agents' || only === 'both' || only === 'all'\n const wantsClaude = only === 'claude' || only === 'both' || only === 'all'\n const wantsSkills = only === 'skills' || only === 'all'\n const wantsGemini = only === 'gemini' || only === 'all'\n const wantsCopilot = only === 'copilot' || only === 'all'\n\n // CLAUDE.md stays at the project root because Claude Code auto-loads\n // it from there. Every other shared-context file lands under\n // `.agents/` so the root stays uncluttered. `writeFileSafe` creates\n // parent directories automatically — no need to pre-mkdir.\n const targets: { file: string; render: () => string }[] = []\n if (wantsAgents) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'AGENTS.md'),\n render: () => generateAgents(name, template, pm),\n })\n }\n if (wantsClaude) {\n targets.push({\n file: join(options.outDir, 'CLAUDE.md'),\n render: () => generateClaude(name, template, pm),\n })\n }\n if (wantsSkills) {\n // Per-skill SKILL.md under `.agents/skills/<slug>/` so agents that\n // auto-discover skills (Claude Code, Copilot CLI plugins, Gemini's\n // activate_skill) pick each up by its frontmatter without needing\n // a separate index file.\n for (const skill of generateKickJsSkillFiles(name, template, pm)) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'skills', skill.slug, 'SKILL.md'),\n render: () => skill.content,\n })\n }\n }\n if (wantsGemini) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'GEMINI.md'),\n render: () => generateGemini(name, template, pm),\n })\n }\n if (wantsCopilot) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'COPILOT.md'),\n render: () => generateCopilot(name, template, pm),\n })\n }\n\n const written: string[] = []\n for (const { file, render } of targets) {\n if (existsSync(file) && !options.force) {\n const overwrite = await confirm({\n message: `${file.replace(options.outDir + '/', '')} already exists. Overwrite?`,\n initialValue: false,\n })\n if (!overwrite) {\n console.log(` Skipped — existing ${file.replace(options.outDir + '/', '')} preserved.`)\n continue\n }\n }\n await writeFileSafe(file, render())\n written.push(file)\n }\n return written\n}\n"],"mappings":";;;;;;;;;;mTA2BA,MAAM,EAAa,UA4Bb,EAAkB,IAAI,IAAqB,CAAC,OAAQ,SAAS,CAAC,EAEpE,SAAS,EAAW,EAAgB,EAA2B,CAC7D,GAAI,EAAU,OAAO,EACrB,GAAI,CACF,IAAM,EAAM,KAAK,MAAM,EAAa,EAAK,EAAQ,cAAc,EAAG,OAAO,CAAC,EAC1E,GAAI,EAAI,KAAM,OAAO,EAAI,KAAK,QAAQ,YAAa,EAAE,CACvD,MAAQ,CAER,CACA,OAAO,EAAO,MAAM,GAAG,CAAC,CAAC,SAAS,OAAO,GAAK,KAChD,CAEA,SAAS,EAAS,EAAgB,EAA2B,CAC3D,GAAI,EAAU,OAAO,EACrB,GAAI,CACF,IAAM,EAAM,KAAK,MAAM,EAAa,EAAK,EAAQ,cAAc,EAAG,OAAO,CAAC,EAG1E,GAAI,EAAI,eAAgB,OAAO,EAAI,eAAe,MAAM,GAAG,CAAC,CAAC,EAC/D,MAAQ,CAER,CACA,MAAO,MACT,CAEA,eAAe,EACb,EACA,EAC0B,CAC1B,GAAI,EAAU,OAAO,EACrB,GAAI,CAEF,IAAM,GAAU,MADE,EAAe,CAAM,EAAA,EAClB,QACrB,GAAI,GAAW,EAAgB,IAAI,CAAO,EAAG,OAAO,CACtD,MAAQ,CAER,CACA,MAAO,MACT,CAEA,eAAsB,EAAkB,EAAsD,CAC5F,IAAM,EAAO,EAAQ,MAAQ,MACvB,EAAO,EAAW,EAAQ,OAAQ,EAAQ,IAAI,EAC9C,EAAK,EAAS,EAAQ,OAAQ,EAAQ,EAAE,EACxC,EAAW,MAAM,EAAe,EAAQ,OAAQ,EAAQ,QAAQ,EAEhE,EAAc,IAAS,UAAY,IAAS,QAAU,IAAS,MAC/D,EAAc,IAAS,UAAY,IAAS,QAAU,IAAS,MAC/D,EAAc,IAAS,UAAY,IAAS,MAC5C,EAAc,IAAS,UAAY,IAAS,MAC5C,EAAe,IAAS,WAAa,IAAS,MAM9C,EAAoD,CAAC,EAa3D,GAZI,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,WAAW,EAClD,WAAc,EAAe,EAAM,EAAU,CAAE,CACjD,CAAC,EAEC,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,WAAW,EACtC,WAAc,EAAe,EAAM,EAAU,CAAE,CACjD,CAAC,EAEC,EAKF,IAAK,IAAM,KAAS,EAAyB,EAAM,EAAU,CAAE,EAC7D,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,SAAU,EAAM,KAAM,UAAU,EACvE,WAAc,EAAM,OACtB,CAAC,EAGD,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,WAAW,EAClD,WAAc,EAAe,EAAM,EAAU,CAAE,CACjD,CAAC,EAEC,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,YAAY,EACnD,WAAc,EAAgB,EAAM,EAAU,CAAE,CAClD,CAAC,EAGH,IAAM,EAAoB,CAAC,EAC3B,IAAK,GAAM,CAAE,OAAM,YAAY,EAAS,CACtC,GAAI,EAAW,CAAI,GAAK,CAAC,EAAQ,OAK3B,CAAC,MAJmB,EAAQ,CAC9B,QAAS,GAAG,EAAK,QAAQ,EAAQ,OAAS,IAAK,EAAE,EAAE,6BACnD,aAAc,EAChB,CAAC,EACe,CACd,QAAQ,IAAI,wBAAwB,EAAK,QAAQ,EAAQ,OAAS,IAAK,EAAE,EAAE,YAAY,EACvF,QACF,CAEF,MAAM,EAAc,EAAM,EAAO,CAAC,EAClC,EAAQ,KAAK,CAAI,CACnB,CACA,OAAO,CACT"}
1
+ {"version":3,"file":"agent-docs-Di96qILM.mjs","names":[],"sources":["../src/generators/agent-docs.ts"],"sourcesContent":["import { join } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { writeFileSafe } from '../utils/fs'\nimport { confirm } from '../utils/prompts'\nimport {\n generateClaude,\n generateAgents,\n generateKickJsSkillFiles,\n generateGemini,\n generateCopilot,\n} from './templates/project-docs'\nimport { loadKickConfig } from '../config'\n\ntype ProjectTemplate = 'rest' | 'minimal'\n\n/**\n * Subdirectory (relative to the project root) where every shared\n * agent-context file lands. CLAUDE.md is the only exception — it stays\n * at the project root because Claude Code auto-loads CLAUDE.md from\n * there. CLAUDE.md is generated as a thin pointer that tells Claude\n * to read `.agents/AGENTS.md` first.\n *\n * Existing root-level AGENTS.md / kickjs-skills.md files (from older\n * scaffolds before this restructure) are left untouched — the\n * generator emits the new layout alongside them and leaves migration\n * to the adopter.\n */\nconst AGENTS_DIR = '.agents'\n\nexport interface GenerateAgentDocsOptions {\n outDir: string\n /** Override project name (defaults to package.json `name`). */\n name?: string\n /** Override package manager (defaults to package.json `packageManager` field, then 'pnpm'). */\n pm?: string\n /** Override template (defaults to kick.config.ts `pattern`, then 'ddd'). */\n template?: ProjectTemplate\n /**\n * Which file(s) to (re)generate. All `.agents/`-bound files land in\n * the project's `.agents/` subdirectory; `claude` is the only file\n * that stays at the project root.\n *\n * - `agents` → `.agents/AGENTS.md`\n * - `claude` → `CLAUDE.md` (root; thin pointer to .agents/)\n * - `skills` → `.agents/kickjs-skills.md`\n * - `gemini` → `.agents/GEMINI.md`\n * - `copilot` → `.agents/COPILOT.md`\n * - `both` → `.agents/AGENTS.md` + `CLAUDE.md` (legacy alias)\n * - `all` → every file above\n */\n only?: 'agents' | 'claude' | 'skills' | 'gemini' | 'copilot' | 'both' | 'all'\n /** Skip the overwrite prompt. */\n force?: boolean\n}\n\nconst VALID_TEMPLATES = new Set<ProjectTemplate>(['rest', 'minimal'])\n\nfunction detectName(outDir: string, override?: string): string {\n if (override) return override\n try {\n const pkg = JSON.parse(readFileSync(join(outDir, 'package.json'), 'utf-8')) as { name?: string }\n if (pkg.name) return pkg.name.replace(/^@[^/]+\\//, '')\n } catch {\n // No package.json — fall back to folder name\n }\n return outDir.split('/').findLast(Boolean) ?? 'app'\n}\n\nfunction detectPm(outDir: string, override?: string): string {\n if (override) return override\n try {\n const pkg = JSON.parse(readFileSync(join(outDir, 'package.json'), 'utf-8')) as {\n packageManager?: string\n }\n if (pkg.packageManager) return pkg.packageManager.split('@')[0]\n } catch {\n // ignore\n }\n return 'pnpm'\n}\n\nasync function detectTemplate(\n outDir: string,\n override?: ProjectTemplate,\n): Promise<ProjectTemplate> {\n if (override) return override\n try {\n const cfg = await loadKickConfig(outDir)\n const pattern = cfg?.pattern as ProjectTemplate | undefined\n if (pattern && VALID_TEMPLATES.has(pattern)) return pattern\n } catch {\n // ignore\n }\n return 'rest'\n}\n\nexport async function generateAgentDocs(options: GenerateAgentDocsOptions): Promise<string[]> {\n const only = options.only ?? 'all'\n const name = detectName(options.outDir, options.name)\n const pm = detectPm(options.outDir, options.pm)\n const template = await detectTemplate(options.outDir, options.template)\n\n const wantsAgents = only === 'agents' || only === 'both' || only === 'all'\n const wantsClaude = only === 'claude' || only === 'both' || only === 'all'\n const wantsSkills = only === 'skills' || only === 'all'\n const wantsGemini = only === 'gemini' || only === 'all'\n const wantsCopilot = only === 'copilot' || only === 'all'\n\n // CLAUDE.md stays at the project root because Claude Code auto-loads\n // it from there. Every other shared-context file lands under\n // `.agents/` so the root stays uncluttered. `writeFileSafe` creates\n // parent directories automatically — no need to pre-mkdir.\n const targets: { file: string; render: () => string }[] = []\n if (wantsAgents) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'AGENTS.md'),\n render: () => generateAgents(name, template, pm),\n })\n }\n if (wantsClaude) {\n targets.push({\n file: join(options.outDir, 'CLAUDE.md'),\n render: () => generateClaude(name, template, pm),\n })\n }\n if (wantsSkills) {\n // Per-skill SKILL.md under `.agents/skills/<slug>/` so agents that\n // auto-discover skills (Claude Code, Copilot CLI plugins, Gemini's\n // activate_skill) pick each up by its frontmatter without needing\n // a separate index file.\n for (const skill of generateKickJsSkillFiles(name, template, pm)) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'skills', skill.slug, 'SKILL.md'),\n render: () => skill.content,\n })\n }\n }\n if (wantsGemini) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'GEMINI.md'),\n render: () => generateGemini(name, template, pm),\n })\n }\n if (wantsCopilot) {\n targets.push({\n file: join(options.outDir, AGENTS_DIR, 'COPILOT.md'),\n render: () => generateCopilot(name, template, pm),\n })\n }\n\n const written: string[] = []\n for (const { file, render } of targets) {\n if (existsSync(file) && !options.force) {\n const overwrite = await confirm({\n message: `${file.replace(options.outDir + '/', '')} already exists. Overwrite?`,\n initialValue: false,\n })\n if (!overwrite) {\n console.log(` Skipped — existing ${file.replace(options.outDir + '/', '')} preserved.`)\n continue\n }\n }\n await writeFileSafe(file, render())\n written.push(file)\n }\n return written\n}\n"],"mappings":";;;;;;;;;;mTA2BA,MAAM,EAAa,UA4Bb,EAAkB,IAAI,IAAqB,CAAC,OAAQ,SAAS,CAAC,EAEpE,SAAS,EAAW,EAAgB,EAA2B,CAC7D,GAAI,EAAU,OAAO,EACrB,GAAI,CACF,IAAM,EAAM,KAAK,MAAM,EAAa,EAAK,EAAQ,cAAc,EAAG,OAAO,CAAC,EAC1E,GAAI,EAAI,KAAM,OAAO,EAAI,KAAK,QAAQ,YAAa,EAAE,CACvD,MAAQ,CAER,CACA,OAAO,EAAO,MAAM,GAAG,CAAC,CAAC,SAAS,OAAO,GAAK,KAChD,CAEA,SAAS,EAAS,EAAgB,EAA2B,CAC3D,GAAI,EAAU,OAAO,EACrB,GAAI,CACF,IAAM,EAAM,KAAK,MAAM,EAAa,EAAK,EAAQ,cAAc,EAAG,OAAO,CAAC,EAG1E,GAAI,EAAI,eAAgB,OAAO,EAAI,eAAe,MAAM,GAAG,CAAC,CAAC,EAC/D,MAAQ,CAER,CACA,MAAO,MACT,CAEA,eAAe,EACb,EACA,EAC0B,CAC1B,GAAI,EAAU,OAAO,EACrB,GAAI,CAEF,IAAM,GAAU,MADE,EAAe,CAAM,EAAA,EAClB,QACrB,GAAI,GAAW,EAAgB,IAAI,CAAO,EAAG,OAAO,CACtD,MAAQ,CAER,CACA,MAAO,MACT,CAEA,eAAsB,EAAkB,EAAsD,CAC5F,IAAM,EAAO,EAAQ,MAAQ,MACvB,EAAO,EAAW,EAAQ,OAAQ,EAAQ,IAAI,EAC9C,EAAK,EAAS,EAAQ,OAAQ,EAAQ,EAAE,EACxC,EAAW,MAAM,EAAe,EAAQ,OAAQ,EAAQ,QAAQ,EAEhE,EAAc,IAAS,UAAY,IAAS,QAAU,IAAS,MAC/D,EAAc,IAAS,UAAY,IAAS,QAAU,IAAS,MAC/D,EAAc,IAAS,UAAY,IAAS,MAC5C,EAAc,IAAS,UAAY,IAAS,MAC5C,EAAe,IAAS,WAAa,IAAS,MAM9C,EAAoD,CAAC,EAa3D,GAZI,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,WAAW,EAClD,WAAc,EAAe,EAAM,EAAU,CAAE,CACjD,CAAC,EAEC,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,WAAW,EACtC,WAAc,EAAe,EAAM,EAAU,CAAE,CACjD,CAAC,EAEC,EAKF,IAAK,IAAM,KAAS,EAAyB,EAAM,EAAU,CAAE,EAC7D,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,SAAU,EAAM,KAAM,UAAU,EACvE,WAAc,EAAM,OACtB,CAAC,EAGD,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,WAAW,EAClD,WAAc,EAAe,EAAM,EAAU,CAAE,CACjD,CAAC,EAEC,GACF,EAAQ,KAAK,CACX,KAAM,EAAK,EAAQ,OAAQ,EAAY,YAAY,EACnD,WAAc,EAAgB,EAAM,EAAU,CAAE,CAClD,CAAC,EAGH,IAAM,EAAoB,CAAC,EAC3B,IAAK,GAAM,CAAE,OAAM,YAAY,EAAS,CACtC,GAAI,EAAW,CAAI,GAAK,CAAC,EAAQ,OAK3B,CAAC,MAJmB,EAAQ,CAC9B,QAAS,GAAG,EAAK,QAAQ,EAAQ,OAAS,IAAK,EAAE,EAAE,6BACnD,aAAc,EAChB,CAAC,EACe,CACd,QAAQ,IAAI,wBAAwB,EAAK,QAAQ,EAAQ,OAAS,IAAK,EAAE,EAAE,YAAY,EACvF,QACF,CAEF,MAAM,EAAc,EAAM,EAAO,CAAC,EAClC,EAAQ,KAAK,CAAI,CACnB,CACA,OAAO,CACT"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v6.2.0-alpha.1
2
+ * @forinda/kickjs-cli v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,6 +8,6 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as e}from"./rolldown-runtime-BKjf5NYQ.mjs";import{dirname as t,isAbsolute as n,join as r,relative as i,resolve as a}from"node:path";import{cpSync as o,existsSync as s,mkdirSync as c,statSync as l,writeFileSync as u}from"node:fs";import{glob as d}from"glob";import{groupAssetKeys as f}from"@forinda/kickjs";var p=e({ASSET_MANIFEST_VERSION:()=>1,buildAssets:()=>m});async function m(e,t){let{cwd:n,silent:o=!1}=t,s=t.distDir??e?.build?.outDir??`dist`,l=e?.assetMap;if(!l||Object.keys(l).length===0)return null;let d=o?()=>{}:console.log,f=a(n,s);c(f,{recursive:!0});let p=[],m={};for(let[e,t]of Object.entries(l)){let r=await h(e,t,n,f);p.push(r.entrySummary),Object.assign(m,r.manifestSlice),d(` ✓ ${e}: ${r.entrySummary.filesCopied} file(s) → ${r.entrySummary.dest}`)}let g={version:1,entries:m},_=r(f,`.kickjs-assets.json`);return u(_,JSON.stringify(g,null,2)+`
11
+ import{t as e}from"./rolldown-runtime-BoqBCbMv.mjs";import{dirname as t,isAbsolute as n,join as r,relative as i,resolve as a}from"node:path";import{cpSync as o,existsSync as s,mkdirSync as c,statSync as l,writeFileSync as u}from"node:fs";import{glob as d}from"glob";import{groupAssetKeys as f}from"@forinda/kickjs";var p=e({ASSET_MANIFEST_VERSION:()=>1,buildAssets:()=>m});async function m(e,t){let{cwd:n,silent:o=!1}=t,s=t.distDir??e?.build?.outDir??`dist`,l=e?.assetMap;if(!l||Object.keys(l).length===0)return null;let d=o?()=>{}:console.log,f=a(n,s);c(f,{recursive:!0});let p=[],m={};for(let[e,t]of Object.entries(l)){let r=await h(e,t,n,f);p.push(r.entrySummary),Object.assign(m,r.manifestSlice),d(` ✓ ${e}: ${r.entrySummary.filesCopied} file(s) → ${r.entrySummary.dest}`)}let g={version:1,entries:m},_=r(f,`.kickjs-assets.json`);return u(_,JSON.stringify(g,null,2)+`
12
12
  `,`utf-8`),d(` ✓ wrote manifest → ${i(n,_)} (${Object.keys(m).length} entries)`),{manifestPath:_,entries:p,manifest:g}}async function h(e,n,l,u){let p=a(l,n.src),m=n.dest?a(l,n.dest):r(u,e);if(v(m,l))return console.warn(` ⚠ assetMap.${e}.dest ('${n.dest}') resolves outside the project root — skipping copy`),{entrySummary:{namespace:e,src:n.src,dest:i(l,m),filesCopied:0},manifestSlice:{}};if(!s(p)||!y(p))return{entrySummary:{namespace:e,src:n.src,dest:i(l,m),filesCopied:0},manifestSlice:{}};let h=await d(n.glob??`**/*`,{cwd:p,nodir:!0,dot:!1,posix:!0});c(m,{recursive:!0});let b={},{pairs:x,collisionGroupsResolved:S}=f(e,[...h].toSorted(),{strategy:n.keys??`auto`}),C=0;for(let{rel:e,key:n}of x){let i=r(p,e),a=r(m,e);b[n]=_(u,a),!g(i,a)&&(c(t(a),{recursive:!0}),o(i,a),C++)}return S>0&&console.log(` ℹ assetMap.${e}: auto-resolved ${S} basename collision(s) by keeping extensions (set 'keys: "strip"' to opt back into legacy last-write-wins behaviour, or 'keys: "with-extension"' to keep all keys verbose).`),{entrySummary:{namespace:e,src:n.src,dest:i(l,m),filesCopied:C},manifestSlice:b}}function g(e,t){if(!s(t))return!1;try{let n=l(e),r=l(t);return r.size===n.size&&r.mtimeMs>=n.mtimeMs}catch{return!1}}function _(e,t){return i(e,t).split(/[\\/]/).filter(Boolean).join(`/`)}function v(e,t){let r=i(t,e);return r===``?!1:r.startsWith(`..`)||n(r)}function y(e){try{return l(e).isDirectory()}catch{return!1}}export{p as n,m as t};
13
- //# sourceMappingURL=build-I8Yhoqj-.mjs.map
13
+ //# sourceMappingURL=build-C_RWnIv1.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-I8Yhoqj-.mjs","names":[],"sources":["../src/asset-manager/build.ts"],"sourcesContent":["/**\n * Build pipeline for `assetMap` entries (asset-manager PR 2).\n *\n * For each entry, walks `src/<...>` matching the configured `glob`,\n * copies matches into `dest` (default `dist/<name>/`), then emits a\n * `dist/.kickjs-assets.json` manifest mapping logical\n * `<namespace>/<key>` keys to repo-relative paths inside `dist/`.\n *\n * Pure function on top of `node:fs` + `glob` — no shell, no side\n * effects beyond the configured directory writes. The build entry-\n * point in `commands/run.ts` calls `buildAssets` after the existing\n * `copyDirs` step.\n *\n * @module @forinda/kickjs-cli/asset-manager/build\n */\n\nimport { cpSync, existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { glob } from 'glob'\nimport { groupAssetKeys } from '@forinda/kickjs'\nimport type { AssetMapEntry, KickConfig } from '../config'\n\n/** Wire-format version for `dist/.kickjs-assets.json`. Bump on shape change. */\nexport const ASSET_MANIFEST_VERSION = 1 as const\n\n/** On-disk manifest format (`dist/.kickjs-assets.json`). */\nexport interface AssetManifest {\n version: typeof ASSET_MANIFEST_VERSION\n /**\n * Logical key → manifest-relative path. Logical key is\n * `<namespace>/<key>` where `<key>` is the file path under `src`\n * with the extension stripped + path separators normalised.\n *\n * Path values are relative to the manifest file's directory so the\n * runtime can resolve them with a single `path.resolve(manifestDir,\n * entry)` regardless of where dist/ lives.\n */\n entries: Record<string, string>\n}\n\nexport interface BuildAssetsOptions {\n /** Project root — resolved for every relative path in the entry. */\n cwd: string\n /**\n * Output dir for the manifest + per-namespace asset copies. When\n * omitted, falls back to `config.build?.outDir` (resolved against\n * `cwd`), then to `dist/` under cwd. Adopters with a custom Vite\n * `build.outDir` should set `kick.config.ts.build.outDir` to match.\n */\n distDir?: string\n /** Suppress per-entry log lines. Default: false. */\n silent?: boolean\n}\n\n/** One entry in the per-build summary returned by `buildAssets`. */\nexport interface BuildAssetsEntryResult {\n namespace: string\n src: string\n dest: string\n /**\n * Number of files actually written this run. On an incremental\n * rebuild where nothing changed this is 0 even though the manifest\n * still lists every matched file.\n */\n filesCopied: number\n}\n\n/** Aggregated outcome of `buildAssets`. */\nexport interface BuildAssetsResult {\n manifestPath: string\n entries: BuildAssetsEntryResult[]\n /** `entries` merged into a single record — useful for tests + tooling. */\n manifest: AssetManifest\n}\n\n/**\n * Run the full asset build for a loaded config:\n *\n * 1. For each `assetMap` entry, glob → copy → manifest stub.\n * 2. Write `dist/.kickjs-assets.json`.\n *\n * Returns a summary including the manifest contents. No-op (and no\n * manifest written) when `assetMap` is empty / missing — the build\n * pipeline shouldn't litter `dist/` with empty manifests for\n * adopters who don't use the feature.\n */\nexport async function buildAssets(\n config: KickConfig | null,\n opts: BuildAssetsOptions,\n): Promise<BuildAssetsResult | null> {\n const { cwd, silent = false } = opts\n // Resolution order: explicit opts.distDir → config.build.outDir → 'dist'.\n // The CLI build command passes nothing explicit, so adopters control\n // the output via kick.config.ts.build.outDir alone.\n const distDir = opts.distDir ?? config?.build?.outDir ?? 'dist'\n const map = config?.assetMap\n if (!map || Object.keys(map).length === 0) return null\n\n const log = silent ? () => {} : console.log\n\n const distAbs = resolve(cwd, distDir)\n mkdirSync(distAbs, { recursive: true })\n\n const summary: BuildAssetsEntryResult[] = []\n const manifestEntries: Record<string, string> = {}\n\n for (const [namespace, entry] of Object.entries(map)) {\n const result = await processEntry(namespace, entry, cwd, distAbs)\n summary.push(result.entrySummary)\n Object.assign(manifestEntries, result.manifestSlice)\n log(\n ` ✓ ${namespace}: ${result.entrySummary.filesCopied} file(s) → ${result.entrySummary.dest}`,\n )\n }\n\n const manifest: AssetManifest = {\n version: ASSET_MANIFEST_VERSION,\n entries: manifestEntries,\n }\n const manifestPath = join(distAbs, '.kickjs-assets.json')\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\\n', 'utf-8')\n log(\n ` ✓ wrote manifest → ${relative(cwd, manifestPath)} (${Object.keys(manifestEntries).length} entries)`,\n )\n\n return { manifestPath, entries: summary, manifest }\n}\n\n/** Per-entry inner pipeline — extracted for unit-test reuse. */\nasync function processEntry(\n namespace: string,\n entry: AssetMapEntry,\n cwd: string,\n distAbs: string,\n): Promise<{\n entrySummary: BuildAssetsEntryResult\n manifestSlice: Record<string, string>\n}> {\n const srcAbs = resolve(cwd, entry.src)\n const destAbs = entry.dest ? resolve(cwd, entry.dest) : join(distAbs, namespace)\n\n // Defensive: refuse to write outside the project root (cwd) even\n // though validateAssetMap warned about it at config-load time. The\n // build step shouldn't trust upstream warnings — a typo like\n // `dest: '../../'` would otherwise sprinkle files outside the\n // workspace despite the warning being printed.\n if (escapesRoot(destAbs, cwd)) {\n console.warn(\n ` ⚠ assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — skipping copy`,\n )\n return {\n entrySummary: { namespace, src: entry.src, dest: relative(cwd, destAbs), filesCopied: 0 },\n manifestSlice: {},\n }\n }\n\n // Treat src-not-a-directory the same as src-missing — `glob` would\n // throw if pointed at a file, surfacing as a generic build failure\n // instead of a clean 0-files entry. Matches the validator's warning\n // shape (already emitted at config-load time for the missing case).\n if (!existsSync(srcAbs) || !isDirectorySync(srcAbs)) {\n return {\n entrySummary: { namespace, src: entry.src, dest: relative(cwd, destAbs), filesCopied: 0 },\n manifestSlice: {},\n }\n }\n\n const pattern = entry.glob ?? '**/*'\n // `glob` returns paths relative to `cwd` when `cwd` is set —\n // exactly the slugs we want for the manifest keys.\n const matches = await glob(pattern, {\n cwd: srcAbs,\n nodir: true,\n dot: false,\n posix: true,\n })\n\n mkdirSync(destAbs, { recursive: true })\n\n const manifestSlice: Record<string, string> = {}\n // Sort the walk so the manifest is byte-stable across platforms.\n // The `groupAssetKeys` helper preserves input order, so a stable\n // sort here makes the resulting manifest stable too.\n const sorted = [...matches].toSorted()\n const { pairs, collisionGroupsResolved } = groupAssetKeys(namespace, sorted, {\n strategy: entry.keys ?? 'auto',\n })\n\n // Copy the files and write the keyed manifest slice. Both come from\n // the same `pairs` order so the on-disk layout matches the\n // manifest's iteration order — easier to grep in cold-start\n // debugging.\n //\n // Incremental: skip the copy when the destination is already\n // up-to-date (exists, same size, mtime ≥ source). `cpSync` stamps the\n // copy with the current time, so an unchanged source always satisfies\n // `dest.mtime ≥ src.mtime` on the next run — turning a re-build into a\n // pure stat sweep instead of re-copying every asset. The manifest\n // slice is written unconditionally so the manifest stays complete.\n let filesCopied = 0\n for (const { rel: relPath, key } of pairs) {\n const srcFile = join(srcAbs, relPath)\n const destFile = join(destAbs, relPath)\n manifestSlice[key] = toManifestRelative(distAbs, destFile)\n if (isUpToDate(srcFile, destFile)) continue\n mkdirSync(dirname(destFile), { recursive: true })\n cpSync(srcFile, destFile)\n filesCopied++\n }\n\n if (collisionGroupsResolved > 0) {\n console.log(\n ` ℹ assetMap.${namespace}: auto-resolved ${collisionGroupsResolved} basename collision(s) by keeping extensions ` +\n `(set 'keys: \"strip\"' to opt back into legacy last-write-wins behaviour, or 'keys: \"with-extension\"' to keep all keys verbose).`,\n )\n }\n\n return {\n entrySummary: {\n namespace,\n src: entry.src,\n dest: relative(cwd, destAbs),\n filesCopied,\n },\n manifestSlice,\n }\n}\n\n/**\n * Is `destFile` already a current copy of `srcFile`? True when the\n * destination exists with the same byte size and an mtime no older than\n * the source. Used to skip redundant copies on an incremental rebuild.\n */\nfunction isUpToDate(srcFile: string, destFile: string): boolean {\n if (!existsSync(destFile)) return false\n try {\n const s = statSync(srcFile)\n const d = statSync(destFile)\n return d.size === s.size && d.mtimeMs >= s.mtimeMs\n } catch {\n return false\n }\n}\n\n/**\n * Make `destFile` relative to the manifest's directory + force POSIX\n * separators so the manifest is byte-stable across platforms.\n */\nfunction toManifestRelative(manifestDir: string, destFile: string): string {\n const rel = relative(manifestDir, destFile)\n // path.relative returns OS-native separators; the manifest is JSON\n // and the runtime uses path.resolve which handles either, but a\n // forward-slash manifest is grep-friendly + diff-stable.\n return rel.split(/[\\\\/]/).filter(Boolean).join('/')\n}\n\n/**\n * Pure manifest writer — handy for tests that want to assert against\n * a hand-crafted manifest without exercising the full pipeline.\n */\nexport function writeAssetManifest(distDir: string, manifest: AssetManifest): string {\n const path = join(distDir, '.kickjs-assets.json')\n mkdirSync(distDir, { recursive: true })\n writeFileSync(path, JSON.stringify(manifest, null, 2) + '\\n', 'utf-8')\n return path\n}\n\n/**\n * Read + parse a manifest from disk. Returns `null` on missing or\n * malformed file rather than throwing — the runtime resolver wants\n * to fall through to dev-mode lookup in that case.\n */\nexport function readAssetManifest(distDir: string): AssetManifest | null {\n const path = join(distDir, '.kickjs-assets.json')\n if (!existsSync(path)) return null\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require('node:fs') as typeof import('node:fs')\n const raw = fs.readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<AssetManifest>\n if (parsed.version !== ASSET_MANIFEST_VERSION) return null\n if (!parsed.entries || typeof parsed.entries !== 'object') return null\n return parsed as AssetManifest\n } catch {\n return null\n }\n}\n\n/**\n * Project-root escape check that's safe across symlinks + drive letters.\n * `path.relative` returns `..` segments when the target sits above root,\n * and an absolute path when the two live on different roots (Windows).\n * `startsWith(root)` would miss both cases.\n */\nfunction escapesRoot(path: string, root: string): boolean {\n const rel = relative(root, path)\n if (rel === '') return false\n return rel.startsWith('..') || isAbsolute(rel)\n}\n\n/** Pure helper — `false` for missing, non-dir, or unreadable paths. */\nfunction isDirectorySync(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"build-C_RWnIv1.mjs","names":[],"sources":["../src/asset-manager/build.ts"],"sourcesContent":["/**\n * Build pipeline for `assetMap` entries (asset-manager PR 2).\n *\n * For each entry, walks `src/<...>` matching the configured `glob`,\n * copies matches into `dest` (default `dist/<name>/`), then emits a\n * `dist/.kickjs-assets.json` manifest mapping logical\n * `<namespace>/<key>` keys to repo-relative paths inside `dist/`.\n *\n * Pure function on top of `node:fs` + `glob` — no shell, no side\n * effects beyond the configured directory writes. The build entry-\n * point in `commands/run.ts` calls `buildAssets` after the existing\n * `copyDirs` step.\n *\n * @module @forinda/kickjs-cli/asset-manager/build\n */\n\nimport { cpSync, existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { glob } from 'glob'\nimport { groupAssetKeys } from '@forinda/kickjs'\nimport type { AssetMapEntry, KickConfig } from '../config'\n\n/** Wire-format version for `dist/.kickjs-assets.json`. Bump on shape change. */\nexport const ASSET_MANIFEST_VERSION = 1 as const\n\n/** On-disk manifest format (`dist/.kickjs-assets.json`). */\nexport interface AssetManifest {\n version: typeof ASSET_MANIFEST_VERSION\n /**\n * Logical key → manifest-relative path. Logical key is\n * `<namespace>/<key>` where `<key>` is the file path under `src`\n * with the extension stripped + path separators normalised.\n *\n * Path values are relative to the manifest file's directory so the\n * runtime can resolve them with a single `path.resolve(manifestDir,\n * entry)` regardless of where dist/ lives.\n */\n entries: Record<string, string>\n}\n\nexport interface BuildAssetsOptions {\n /** Project root — resolved for every relative path in the entry. */\n cwd: string\n /**\n * Output dir for the manifest + per-namespace asset copies. When\n * omitted, falls back to `config.build?.outDir` (resolved against\n * `cwd`), then to `dist/` under cwd. Adopters with a custom Vite\n * `build.outDir` should set `kick.config.ts.build.outDir` to match.\n */\n distDir?: string\n /** Suppress per-entry log lines. Default: false. */\n silent?: boolean\n}\n\n/** One entry in the per-build summary returned by `buildAssets`. */\nexport interface BuildAssetsEntryResult {\n namespace: string\n src: string\n dest: string\n /**\n * Number of files actually written this run. On an incremental\n * rebuild where nothing changed this is 0 even though the manifest\n * still lists every matched file.\n */\n filesCopied: number\n}\n\n/** Aggregated outcome of `buildAssets`. */\nexport interface BuildAssetsResult {\n manifestPath: string\n entries: BuildAssetsEntryResult[]\n /** `entries` merged into a single record — useful for tests + tooling. */\n manifest: AssetManifest\n}\n\n/**\n * Run the full asset build for a loaded config:\n *\n * 1. For each `assetMap` entry, glob → copy → manifest stub.\n * 2. Write `dist/.kickjs-assets.json`.\n *\n * Returns a summary including the manifest contents. No-op (and no\n * manifest written) when `assetMap` is empty / missing — the build\n * pipeline shouldn't litter `dist/` with empty manifests for\n * adopters who don't use the feature.\n */\nexport async function buildAssets(\n config: KickConfig | null,\n opts: BuildAssetsOptions,\n): Promise<BuildAssetsResult | null> {\n const { cwd, silent = false } = opts\n // Resolution order: explicit opts.distDir → config.build.outDir → 'dist'.\n // The CLI build command passes nothing explicit, so adopters control\n // the output via kick.config.ts.build.outDir alone.\n const distDir = opts.distDir ?? config?.build?.outDir ?? 'dist'\n const map = config?.assetMap\n if (!map || Object.keys(map).length === 0) return null\n\n const log = silent ? () => {} : console.log\n\n const distAbs = resolve(cwd, distDir)\n mkdirSync(distAbs, { recursive: true })\n\n const summary: BuildAssetsEntryResult[] = []\n const manifestEntries: Record<string, string> = {}\n\n for (const [namespace, entry] of Object.entries(map)) {\n const result = await processEntry(namespace, entry, cwd, distAbs)\n summary.push(result.entrySummary)\n Object.assign(manifestEntries, result.manifestSlice)\n log(\n ` ✓ ${namespace}: ${result.entrySummary.filesCopied} file(s) → ${result.entrySummary.dest}`,\n )\n }\n\n const manifest: AssetManifest = {\n version: ASSET_MANIFEST_VERSION,\n entries: manifestEntries,\n }\n const manifestPath = join(distAbs, '.kickjs-assets.json')\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\\n', 'utf-8')\n log(\n ` ✓ wrote manifest → ${relative(cwd, manifestPath)} (${Object.keys(manifestEntries).length} entries)`,\n )\n\n return { manifestPath, entries: summary, manifest }\n}\n\n/** Per-entry inner pipeline — extracted for unit-test reuse. */\nasync function processEntry(\n namespace: string,\n entry: AssetMapEntry,\n cwd: string,\n distAbs: string,\n): Promise<{\n entrySummary: BuildAssetsEntryResult\n manifestSlice: Record<string, string>\n}> {\n const srcAbs = resolve(cwd, entry.src)\n const destAbs = entry.dest ? resolve(cwd, entry.dest) : join(distAbs, namespace)\n\n // Defensive: refuse to write outside the project root (cwd) even\n // though validateAssetMap warned about it at config-load time. The\n // build step shouldn't trust upstream warnings — a typo like\n // `dest: '../../'` would otherwise sprinkle files outside the\n // workspace despite the warning being printed.\n if (escapesRoot(destAbs, cwd)) {\n console.warn(\n ` ⚠ assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — skipping copy`,\n )\n return {\n entrySummary: { namespace, src: entry.src, dest: relative(cwd, destAbs), filesCopied: 0 },\n manifestSlice: {},\n }\n }\n\n // Treat src-not-a-directory the same as src-missing — `glob` would\n // throw if pointed at a file, surfacing as a generic build failure\n // instead of a clean 0-files entry. Matches the validator's warning\n // shape (already emitted at config-load time for the missing case).\n if (!existsSync(srcAbs) || !isDirectorySync(srcAbs)) {\n return {\n entrySummary: { namespace, src: entry.src, dest: relative(cwd, destAbs), filesCopied: 0 },\n manifestSlice: {},\n }\n }\n\n const pattern = entry.glob ?? '**/*'\n // `glob` returns paths relative to `cwd` when `cwd` is set —\n // exactly the slugs we want for the manifest keys.\n const matches = await glob(pattern, {\n cwd: srcAbs,\n nodir: true,\n dot: false,\n posix: true,\n })\n\n mkdirSync(destAbs, { recursive: true })\n\n const manifestSlice: Record<string, string> = {}\n // Sort the walk so the manifest is byte-stable across platforms.\n // The `groupAssetKeys` helper preserves input order, so a stable\n // sort here makes the resulting manifest stable too.\n const sorted = [...matches].toSorted()\n const { pairs, collisionGroupsResolved } = groupAssetKeys(namespace, sorted, {\n strategy: entry.keys ?? 'auto',\n })\n\n // Copy the files and write the keyed manifest slice. Both come from\n // the same `pairs` order so the on-disk layout matches the\n // manifest's iteration order — easier to grep in cold-start\n // debugging.\n //\n // Incremental: skip the copy when the destination is already\n // up-to-date (exists, same size, mtime ≥ source). `cpSync` stamps the\n // copy with the current time, so an unchanged source always satisfies\n // `dest.mtime ≥ src.mtime` on the next run — turning a re-build into a\n // pure stat sweep instead of re-copying every asset. The manifest\n // slice is written unconditionally so the manifest stays complete.\n let filesCopied = 0\n for (const { rel: relPath, key } of pairs) {\n const srcFile = join(srcAbs, relPath)\n const destFile = join(destAbs, relPath)\n manifestSlice[key] = toManifestRelative(distAbs, destFile)\n if (isUpToDate(srcFile, destFile)) continue\n mkdirSync(dirname(destFile), { recursive: true })\n cpSync(srcFile, destFile)\n filesCopied++\n }\n\n if (collisionGroupsResolved > 0) {\n console.log(\n ` ℹ assetMap.${namespace}: auto-resolved ${collisionGroupsResolved} basename collision(s) by keeping extensions ` +\n `(set 'keys: \"strip\"' to opt back into legacy last-write-wins behaviour, or 'keys: \"with-extension\"' to keep all keys verbose).`,\n )\n }\n\n return {\n entrySummary: {\n namespace,\n src: entry.src,\n dest: relative(cwd, destAbs),\n filesCopied,\n },\n manifestSlice,\n }\n}\n\n/**\n * Is `destFile` already a current copy of `srcFile`? True when the\n * destination exists with the same byte size and an mtime no older than\n * the source. Used to skip redundant copies on an incremental rebuild.\n */\nfunction isUpToDate(srcFile: string, destFile: string): boolean {\n if (!existsSync(destFile)) return false\n try {\n const s = statSync(srcFile)\n const d = statSync(destFile)\n return d.size === s.size && d.mtimeMs >= s.mtimeMs\n } catch {\n return false\n }\n}\n\n/**\n * Make `destFile` relative to the manifest's directory + force POSIX\n * separators so the manifest is byte-stable across platforms.\n */\nfunction toManifestRelative(manifestDir: string, destFile: string): string {\n const rel = relative(manifestDir, destFile)\n // path.relative returns OS-native separators; the manifest is JSON\n // and the runtime uses path.resolve which handles either, but a\n // forward-slash manifest is grep-friendly + diff-stable.\n return rel.split(/[\\\\/]/).filter(Boolean).join('/')\n}\n\n/**\n * Pure manifest writer — handy for tests that want to assert against\n * a hand-crafted manifest without exercising the full pipeline.\n */\nexport function writeAssetManifest(distDir: string, manifest: AssetManifest): string {\n const path = join(distDir, '.kickjs-assets.json')\n mkdirSync(distDir, { recursive: true })\n writeFileSync(path, JSON.stringify(manifest, null, 2) + '\\n', 'utf-8')\n return path\n}\n\n/**\n * Read + parse a manifest from disk. Returns `null` on missing or\n * malformed file rather than throwing — the runtime resolver wants\n * to fall through to dev-mode lookup in that case.\n */\nexport function readAssetManifest(distDir: string): AssetManifest | null {\n const path = join(distDir, '.kickjs-assets.json')\n if (!existsSync(path)) return null\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require('node:fs') as typeof import('node:fs')\n const raw = fs.readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<AssetManifest>\n if (parsed.version !== ASSET_MANIFEST_VERSION) return null\n if (!parsed.entries || typeof parsed.entries !== 'object') return null\n return parsed as AssetManifest\n } catch {\n return null\n }\n}\n\n/**\n * Project-root escape check that's safe across symlinks + drive letters.\n * `path.relative` returns `..` segments when the target sits above root,\n * and an absolute path when the two live on different roots (Windows).\n * `startsWith(root)` would miss both cases.\n */\nfunction escapesRoot(path: string, root: string): boolean {\n const rel = relative(root, path)\n if (rel === '') return false\n return rel.startsWith('..') || isAbsolute(rel)\n}\n\n/** Pure helper — `false` for missing, non-dir, or unreadable paths. */\nfunction isDirectorySync(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v6.2.0-alpha.1
2
+ * @forinda/kickjs-cli v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as e}from"./run-plugins-A7mE5YjC.mjs";export{e as builtinCliPlugins};
11
+ import{t as e}from"./run-plugins-Bql3IbaE.mjs";export{e as builtinCliPlugins};
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v6.2.0-alpha.1
2
+ * @forinda/kickjs-cli v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,7 +9,7 @@
9
9
  * @license MIT
10
10
  */
11
11
  import{createRequire as e}from"node:module";import{Command as t}from"commander";import{cpSync as n,existsSync as r,mkdirSync as i,readFileSync as a,readdirSync as o,rmSync as s,statSync as c,writeFileSync as l}from"node:fs";import u,{basename as d,dirname as f,extname as p,isAbsolute as m,join as h,parse as g,relative as _,resolve as v,sep as y}from"node:path";import{fileURLToPath as b,pathToFileURL as x}from"node:url";import{execFileSync as ee,execSync as S,fork as te,spawn as ne,spawnSync as re}from"node:child_process";import{access as ie,copyFile as ae,mkdir as C,readFile as w,readdir as oe,rm as se,stat as ce,unlink as le,writeFile as T}from"node:fs/promises";import{KickPluginConflictError as ue,defineCliPlugin as E}from"@forinda/kickjs-cli-kit";import*as D from"@clack/prompts";import O from"picocolors";import de from"pluralize";import{parseSync as fe}from"oxc-parser";import{glob as pe,globSync as me}from"glob";import{groupAssetKeys as he}from"@forinda/kickjs";import{arch as ge,platform as _e,release as ve}from"node:os";var ye=Object.defineProperty,k=(e,t)=>{let n={};for(var r in e)ye(n,r,{get:e[r],enumerable:!0});return t||ye(n,Symbol.toStringTag,{value:`Module`}),n};function be(e,t,n){S(e,{cwd:t,stdio:`inherit`,env:n?{...process.env,...n}:process.env})}function xe(e,t,n){let r=re(process.execPath,[e],{cwd:n,stdio:`inherit`,env:{...process.env,...t}});r.status!==0&&process.exit(r.status??1)}function Se(e,t){if(!t?.commands?.length)return;let n=new Set(e.commands.map(e=>e.name()));for(let r of t.commands){if(n.has(r.name)){console.warn(` Warning: custom command '${r.name}' skipped — conflicts with a built-in command`);continue}Ce(e,r)}}function Ce(e,t){let n=e.command(t.name).description(t.description);if(t.aliases)for(let e of t.aliases)n.alias(e);n.allowUnknownOption(!0),n.argument(`[args...]`,`Additional arguments passed to the command`),n.action(e=>{let n=e.join(` `),r=Array.isArray(t.steps)?t.steps:[t.steps];for(let e of r){let r=n?`${e} ${n}`:e;console.log(` $ ${r}`);try{be(r)}catch{console.error(` Command failed: ${t.name}`),process.exitCode=1;return}}})}var we=k({BUILTIN_REPO_TYPES:()=>Ee,DEPRECATED_REPO_TYPES:()=>De,PACKAGE_MANAGERS:()=>Te,defineConfig:()=>ke,loadKickConfig:()=>j,resolveModuleConfig:()=>A,resolveTokenScope:()=>Ae,validateAssetMap:()=>Pe,warnIfDeprecatedRepo:()=>Oe,writeAssetConfigSnapshot:()=>Ne});const Te=[`pnpm`,`npm`,`yarn`,`bun`],Ee=[`inmemory`],De=[`prisma`,`drizzle`];function Oe(e){return De.includes(e)?(console.warn(` Note: the '${e}' repository preset is deprecated. Generating a generic custom repository named '${e}' instead — wire it to your DB by hand. Pass any name via \`--repo <name>\` or \`modules.repo: { name: '<name>' }\`.`),!0):!1}function ke(e){return e}function Ae(e,t){if(e?.tokenScope&&typeof e.tokenScope==`string`&&e.tokenScope.length>0){let t=je(e.tokenScope);if(t.length>0)return t}try{let e=h(t,`package.json`);if(r(e)){let t=JSON.parse(a(e,`utf-8`));if(typeof t.name==`string`&&t.name.length>0){let e=t.name.match(/^@([^/]+)\//),n=je(e?e[1]:t.name);if(n.length>0)return n}}}catch{}return`app`}function je(e){return e.toLowerCase().replace(/[^a-z0-9-]/g,`-`).replace(/^-+|-+$/g,``).replace(/-{2,}/g,`-`)}function A(e){if(!e)return{};let t={dir:e.modules?.dir,repo:e.modules?.repo,schemaDir:e.modules?.schemaDir,pluralize:e.modules?.pluralize,prismaClientPath:e.modules?.prismaClientPath,style:e.modules?.style};return t.style!==void 0&&t.style!==`define`&&t.style!==`class`&&(console.warn(` Warning: modules.style '${t.style}' is not a valid value (expected 'define' or 'class'). Falling back to 'define'.`),t.style=`define`),t.repo&&typeof t.repo==`string`&&!Ee.includes(t.repo)&&(Oe(t.repo)||console.warn(` Warning: modules.repo '${t.repo}' is not a built-in type (${Ee.join(`, `)}). It will generate a stub repository. Use { name: '${t.repo}' } to silence this warning.`)),t}const Me=[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`,`kick.config.json`];async function j(e){let{findProjectRoot:t}=await Promise.resolve().then(()=>en),n=t(e);for(let e of Me){let t=h(n,e);try{await ie(t)}catch{continue}if(e.endsWith(`.json`)){let e=await w(t,`utf-8`);return JSON.parse(e)}if(e.endsWith(`.ts`)){let r;try{r=await import(`jiti`)}catch(t){let n=t instanceof Error?t.message:String(t);n.includes(`Cannot find package 'jiti'`)||n.includes(`ERR_MODULE_NOT_FOUND`)?console.warn(`Warning: Failed to load ${e} — 'jiti' is required for TypeScript configs. Run \`pnpm add -D jiti\` (or your package manager's equivalent), or rename the file to kick.config.js / kick.config.mjs / kick.config.json.`):console.warn(`Warning: Failed to initialize jiti for ${e}: ${n}`);continue}try{let e=await r.createJiti(n,{interopDefault:!0,fsCache:!1}).import(t,{default:!0}),i=Pe(e,n);for(let e of i)console.warn(` Warning: ${e}`);return Ne(n,e),e}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}try{let{pathToFileURL:e}=await import(`node:url`),r=await import(e(t).href),i=r.default??r,a=Pe(i,n);for(let e of a)console.warn(` Warning: ${e}`);return Ne(n,i),i}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}return null}function Ne(e,t){if(!(!t?.assetMap||Object.keys(t.assetMap).length===0))try{let n=h(e,`.kickjs`);i(n,{recursive:!0});let r={version:1,assetMap:t.assetMap,...t.build?.outDir?{build:{outDir:t.build.outDir}}:{}};l(h(n,`kick.config.json`),JSON.stringify(r,null,2)+`
12
- `,`utf-8`)}catch{}}function Pe(e,t){let n=[];if(!e?.assetMap)return n;let i=v(t);for(let[a,o]of Object.entries(e.assetMap)){if(!a||a.includes(`/`)){n.push(`assetMap key '${a}' is invalid — must be a non-empty string without '/'`);continue}if(typeof o?.src!=`string`||o.src.length===0){n.push(`assetMap.${a} is missing a non-empty 'src' field`);continue}r(v(t,o.src))||n.push(`assetMap.${a}.src ('${o.src}') does not exist — typegen + build will fail`),o.dest&&Fe(v(t,o.dest),i)&&n.push(`assetMap.${a}.dest ('${o.dest}') resolves outside the project root — refusing to copy`)}return n}function Fe(e,t){let n=_(t,e);return n===``?!1:n.startsWith(`..`)||m(n)}function Ie(e,t=[]){let n=new Map;for(let t of e){let e=(n.get(t.name)??0)+1;if(n.set(t.name,e),e===2)throw new ue(`plugin`,t.name,[t.name,t.name])}let r=new Map,i=[];for(let t of e)for(let e of t.commands??[]){let n=r.get(e.name);if(n)throw new ue(`command`,e.name,[n,t.name]);r.set(e.name,t.name),i.push(e)}let a=new Set(t.map(e=>e.name)),o=[...i.filter(e=>!a.has(e.name)),...t],s=new Map,c=[];for(let t of e)for(let e of t.typegens??[]){let n=s.get(e.id);if(n)throw new ue(`typegen`,e.id,[n,t.name]);s.set(e.id,t.name),c.push(e)}let l=new Map,u=[];for(let t of e)for(let e of t.generators??[]){let n=l.get(e.name);if(n)throw new ue(`generator`,e.name,[n,t.name]);l.set(e.name,t.name),u.push({source:t.name,spec:e})}return{commands:o,typegens:c,generators:u,register:async(t,n)=>{let r;if(n)r={generators:u,...n};else{let{findProjectRoot:e}=await Promise.resolve().then(()=>en),t=process.cwd();r={cwd:t,projectRoot:e(t),config:null,log:()=>{},generators:u}}for(let n of e)n.register&&await n.register(t,r)}}}var Le=k({mergeCliPlugins:()=>Ie});let Re=!1;function M(e){Re=e}const ze=new Set([`.ts`,`.tsx`,`.js`,`.jsx`,`.mjs`,`.cjs`,`.json`,`.md`]);async function N(e,t){Re||(await C(f(e),{recursive:!0}),await T(e,t,`utf-8`),ze.has(p(e))&&await He(e,t).catch(()=>{}))}let Be;async function Ve(t){if(Be!==void 0)return Be;try{Be=await import(e(h(t,`package.json`)).resolve(`oxfmt`))}catch{Be=null}return Be}async function He(e,t){let n=await Ve(process.cwd());if(!n)return;let r=await We(e);if(r===null)return;let i=await n.format(e,t,r);i.code!==t&&await T(e,i.code,`utf-8`)}const Ue=new Map;async function We(e){let t=f(e),n=t;if(Ue.has(n))return Ue.get(n);for(;;){let e=h(t,`.oxfmtrc.json`);if(r(e))try{let t=await w(e,`utf-8`),r=JSON.parse(t);return delete r.$schema,delete r.ignorePatterns,Ue.set(n,r),r}catch{return Ue.set(n,null),null}let i=f(t);if(i===t)return Ue.set(n,null),null;t=i}}async function Ge(e){try{return await ie(e),!0}catch{return!1}}const Ke={swagger:`@forinda/kickjs-swagger`,ws:`@forinda/kickjs-ws`,queue:`@forinda/kickjs-queue`,devtools:`@forinda/kickjs-devtools`},qe={zod:{name:`zod`,range:`^4.3.6`},valibot:{name:`valibot`,range:`^1.4.1`},yup:{name:`yup`,range:`^1.7.1`}};function Je(e,t){let n=e[t];if(!n)throw Error(`generatePackageJson: missing resolved version for ${t}. Add it to SIBLING_PACKAGES in generators/project.ts.`);return n}function Ye(e,t,n,r=[],i=`zod`,a=`express`){let o=qe[i],s={"@forinda/kickjs":Je(n,`@forinda/kickjs`),"@forinda/kickjs-schema":Je(n,`@forinda/kickjs-schema`),dotenv:`^17.3.1`,express:`^5.1.0`,"reflect-metadata":`^0.2.2`,[o.name]:o.range};a===`fastify`?(s.fastify=`^5.0.0`,s[`@fastify/middie`]=`^9.0.0`):a===`h3`&&(s.h3=`^1.0.0`);for(let e of r){let t=Ke[e];t&&!s[t]&&(s[t]=Je(n,t))}return JSON.stringify({name:e,version:`0.0.0`,type:`module`,scripts:{dev:`kick dev`,"dev:debug":`kick dev:debug`,build:`kick build`,start:`kick start`,test:`vitest run`,"test:watch":`vitest`,typecheck:`tsc --noEmit`,typegen:`kick typegen`,lint:`eslint src/`,format:`prettier --write src/`},dependencies:s,devDependencies:{"@forinda/kickjs-cli":Je(n,`@forinda/kickjs-cli`),"@forinda/kickjs-vite":Je(n,`@forinda/kickjs-vite`),"@swc/core":`^1.15.21`,"@types/express":`^5.0.6`,"@types/node":`^25.0.0`,"unplugin-swc":`^1.5.9`,vite:`^8.0.3`,vitest:`^4.1.2`,typescript:`^6.0.3`,prettier:`^3.8.1`}},null,2)}function Xe(){return`import { defineConfig } from 'vite'
12
+ `,`utf-8`)}catch{}}function Pe(e,t){let n=[];if(!e?.assetMap)return n;let i=v(t);for(let[a,o]of Object.entries(e.assetMap)){if(!a||a.includes(`/`)){n.push(`assetMap key '${a}' is invalid — must be a non-empty string without '/'`);continue}if(typeof o?.src!=`string`||o.src.length===0){n.push(`assetMap.${a} is missing a non-empty 'src' field`);continue}r(v(t,o.src))||n.push(`assetMap.${a}.src ('${o.src}') does not exist — typegen + build will fail`),o.dest&&Fe(v(t,o.dest),i)&&n.push(`assetMap.${a}.dest ('${o.dest}') resolves outside the project root — refusing to copy`)}return n}function Fe(e,t){let n=_(t,e);return n===``?!1:n.startsWith(`..`)||m(n)}function Ie(e,t=[]){let n=new Map;for(let t of e){let e=(n.get(t.name)??0)+1;if(n.set(t.name,e),e===2)throw new ue(`plugin`,t.name,[t.name,t.name])}let r=new Map,i=[];for(let t of e)for(let e of t.commands??[]){let n=r.get(e.name);if(n)throw new ue(`command`,e.name,[n,t.name]);r.set(e.name,t.name),i.push(e)}let a=new Set(t.map(e=>e.name)),o=[...i.filter(e=>!a.has(e.name)),...t],s=new Map,c=[];for(let t of e)for(let e of t.typegens??[]){let n=s.get(e.id);if(n)throw new ue(`typegen`,e.id,[n,t.name]);s.set(e.id,t.name),c.push(e)}let l=new Map,u=[];for(let t of e)for(let e of t.generators??[]){let n=l.get(e.name);if(n)throw new ue(`generator`,e.name,[n,t.name]);l.set(e.name,t.name),u.push({source:t.name,spec:e})}return{commands:o,typegens:c,generators:u,register:async(t,n)=>{let r;if(n)r={generators:u,...n};else{let{findProjectRoot:e}=await Promise.resolve().then(()=>en),t=process.cwd();r={cwd:t,projectRoot:e(t),config:null,log:()=>{},generators:u}}for(let n of e)n.register&&await n.register(t,r)}}}var Le=k({mergeCliPlugins:()=>Ie});let Re=!1;function M(e){Re=e}const ze=new Set([`.ts`,`.tsx`,`.js`,`.jsx`,`.mjs`,`.cjs`,`.json`,`.md`]);async function N(e,t){Re||(await C(f(e),{recursive:!0}),await T(e,t,`utf-8`),ze.has(p(e))&&await He(e,t).catch(()=>{}))}let Be;async function Ve(t){if(Be!==void 0)return Be;try{Be=await import(e(h(t,`package.json`)).resolve(`oxfmt`))}catch{Be=null}return Be}async function He(e,t){let n=await Ve(process.cwd());if(!n)return;let r=await We(e);if(r===null)return;let i=await n.format(e,t,r);i.code!==t&&await T(e,i.code,`utf-8`)}const Ue=new Map;async function We(e){let t=f(e),n=t;if(Ue.has(n))return Ue.get(n);for(;;){let e=h(t,`.oxfmtrc.json`);if(r(e))try{let t=await w(e,`utf-8`),r=JSON.parse(t);return delete r.$schema,delete r.ignorePatterns,Ue.set(n,r),r}catch{return Ue.set(n,null),null}let i=f(t);if(i===t)return Ue.set(n,null),null;t=i}}async function Ge(e){try{return await ie(e),!0}catch{return!1}}const Ke={swagger:`@forinda/kickjs-swagger`,ws:`@forinda/kickjs-ws`,queue:`@forinda/kickjs-queue`,devtools:`@forinda/kickjs-devtools`},qe={zod:{name:`zod`,range:`^4.3.6`},valibot:{name:`valibot`,range:`^1.4.1`},yup:{name:`yup`,range:`^1.7.1`}};function Je(e,t){let n=e[t];if(!n)throw Error(`generatePackageJson: missing resolved version for ${t}. Add it to SIBLING_PACKAGES in generators/project.ts.`);return n}function Ye(e,t,n,r=[],i=`zod`,a=`express`){let o=qe[i],s={"@forinda/kickjs":Je(n,`@forinda/kickjs`),"@forinda/kickjs-schema":Je(n,`@forinda/kickjs-schema`),dotenv:`^17.3.1`,"reflect-metadata":`^0.2.2`,[o.name]:o.range};a===`express`?s.express=`^5.1.0`:a===`fastify`?(s.fastify=`^5.0.0`,s[`@fastify/middie`]=`^9.0.0`,s[`serve-static`]=`^2.2.0`):a===`h3`&&(s.h3=`^1.0.0`,s[`serve-static`]=`^2.2.0`);for(let e of r){let t=Ke[e];t&&!s[t]&&(s[t]=Je(n,t))}return JSON.stringify({name:e,version:`0.0.0`,type:`module`,scripts:{dev:`kick dev`,"dev:debug":`kick dev:debug`,build:`kick build`,start:`kick start`,test:`vitest run`,"test:watch":`vitest`,typecheck:`tsc --noEmit`,typegen:`kick typegen`,lint:`eslint src/`,format:`prettier --write src/`},dependencies:s,devDependencies:{"@forinda/kickjs-cli":Je(n,`@forinda/kickjs-cli`),"@forinda/kickjs-vite":Je(n,`@forinda/kickjs-vite`),"@swc/core":`^1.15.21`,...a===`express`?{"@types/express":`^5.0.6`}:{},"@types/node":`^25.0.0`,"unplugin-swc":`^1.5.9`,vite:`^8.0.3`,vitest:`^4.1.2`,typescript:`^6.0.3`,prettier:`^3.8.1`}},null,2)}function Xe(){return`import { defineConfig } from 'vite'
13
13
  import { resolve } from 'node:path'
14
14
  import swc from 'unplugin-swc'
15
15
  import { kickjsVitePlugin, envWatchPlugin } from '@forinda/kickjs-vite'
@@ -1229,7 +1229,7 @@ Codex / Cursor / Gemini / Claude Code without copy-pasting.
1229
1229
  \`kick g agents --only copilot -f\` regenerates this file from the
1230
1230
  CLI template. Hand-edited content is overwritten — keep customisation
1231
1231
  in \`.agents/COPILOT.local.md\`.
1232
- `}const yt=f(b(import.meta.url)),bt=JSON.parse(a(h(yt,`..`,`package.json`),`utf-8`)),xt=`^${bt.version}`,St=[`@forinda/kickjs`,`@forinda/kickjs-cli`,`@forinda/kickjs-schema`,`@forinda/kickjs-vite`,`@forinda/kickjs-swagger`,`@forinda/kickjs-ws`,`@forinda/kickjs-queue`,`@forinda/kickjs-devtools`,`@forinda/kickjs-testing`];async function Ct(){let e=await Promise.all(St.map(async e=>{try{let t=ee(`npm`,[`view`,e,`version`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();if(t&&/^\d+\.\d+\.\d+/.test(t))return[e,`^${t}`]}catch{}return[e,xt]}));return Object.fromEntries(e)}function wt(e,t){try{let n=ee(`npm`,[`view`,`${e}@${t}`,`version`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();return n&&/^\d+\.\d+\.\d+/.test(n)?n:null}catch{return null}}function Tt(e){return(e??``).replace(/^[\^~>=<\s]+/,``)}function Et(e,t){let n=e=>Tt(e).split(`-`)[0].split(`.`).map(e=>Number.parseInt(e,10)||0),[r=0,i=0,a=0]=n(e),[o=0,s=0,c=0]=n(t);return r===o?i===s?a>=c:i>s:r>o}function Dt(e,t,n){try{let r=ee(`npm`,[`view`,`${e}@${t}`,`exports`,`--json`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();if(!r)return!1;let i=JSON.parse(r);return Object.prototype.hasOwnProperty.call(i,n)}catch{return!1}}async function Ot(e){let{name:t,directory:n,packageManager:r=`pnpm`,template:i=`rest`,defaultRepo:a=`inmemory`,packages:o=[],schemaLib:s=`zod`,runtime:c=`express`}=e,l=n,u=e=>console.log(` ${e}`);console.log(`\n Creating KickJS project: ${t}\n`),u(`Resolving package versions...`);let d=await Ct();if(c!==`express`)if(Dt(`@forinda/kickjs`,`latest`,`./${c}`))u(`Using @forinda/kickjs@latest (stable ships the ${c} runtime)`);else{let e=[`@forinda/kickjs`,`@forinda/kickjs-cli`,`@forinda/kickjs-vite`],t=[],n=!1;for(let r of e){let e=wt(r,`alpha`);e&&Et(e,Tt(d[r]))&&(d[r]=e,t.push(`${r}@${e}`),r===`@forinda/kickjs`&&(n=!0))}u(n?`Using the alpha channel for the ${c} runtime: ${t.join(`, `)}`:`WARNING: could not resolve @forinda/kickjs@alpha — the ${c} runtime subpath may be missing. After install, run: ${r} add @forinda/kickjs@alpha`)}await N(h(l,`package.json`),Ye(t,i,d,o,s,c)),await N(h(l,`vite.config.ts`),Xe()),await N(h(l,`tsconfig.json`),Ze()),await N(h(l,`.prettierrc`),Qe()),await N(h(l,`.editorconfig`),$e()),await N(h(l,`.gitignore`),et()),await N(h(l,`.gitattributes`),tt()),await N(h(l,`.env`),nt()),await N(h(l,`.env.example`),rt()),await N(h(l,`src/config/index.ts`),ct(s)),await N(h(l,`src/index.ts`),ot(t,i,bt.version,o,c)),await N(h(l,`src/modules/index.ts`),st()),await N(h(l,`src/modules/hello/hello.service.ts`),lt()),await N(h(l,`src/modules/hello/hello.controller.ts`),ut()),await N(h(l,`src/modules/hello/hello.module.ts`),dt()),await N(h(l,`kick.config.ts`),ft(i,a,r,c)),await N(h(l,`vitest.config.ts`),it()),await N(h(l,`README.md`),pt(t,i,r));let{generateAgentDocs:f}=await Promise.resolve().then(()=>ir);if(await f({outDir:l,name:t,pm:r,template:i,only:`all`,force:!0}),e.installDeps){console.log(`\n Installing dependencies with ${r}...\n`);try{S(`${r} install`,{cwd:l,stdio:`inherit`}),console.log(`
1232
+ `}const yt=f(b(import.meta.url)),bt=JSON.parse(a(h(yt,`..`,`package.json`),`utf-8`)),xt=`^${bt.version}`,St=[`@forinda/kickjs`,`@forinda/kickjs-cli`,`@forinda/kickjs-schema`,`@forinda/kickjs-vite`,`@forinda/kickjs-swagger`,`@forinda/kickjs-ws`,`@forinda/kickjs-queue`,`@forinda/kickjs-devtools`,`@forinda/kickjs-testing`];async function Ct(){let e=await Promise.all(St.map(async e=>{try{let t=ee(`npm`,[`view`,e,`version`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();if(t&&/^\d+\.\d+\.\d+/.test(t))return[e,`^${t}`]}catch{}return[e,xt]}));return Object.fromEntries(e)}function wt(e,t){try{let n=ee(`npm`,[`view`,`${e}@${t}`,`version`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();return n&&/^\d+\.\d+\.\d+/.test(n)?n:null}catch{return null}}function Tt(e){return(e??``).replace(/^[\^~>=<\s]+/,``)}function Et(e,t){let n=e=>Tt(e).split(`-`)[0].split(`.`).map(e=>Number.parseInt(e,10)||0),[r=0,i=0,a=0]=n(e),[o=0,s=0,c=0]=n(t);return r===o?i===s?a>=c:i>s:r>o}function Dt(e,t,n){try{let r=ee(`npm`,[`view`,`${e}@${t}`,`exports`,`--json`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();if(!r)return!1;let i=JSON.parse(r);return Object.prototype.hasOwnProperty.call(i,n)}catch{return!1}}async function Ot(e){let{name:t,directory:n,packageManager:r=`pnpm`,template:i=`rest`,defaultRepo:a=`inmemory`,packages:o=[],schemaLib:s=`zod`,runtime:c=`express`}=e,l=n,u=e=>console.log(` ${e}`);console.log(`\n Creating KickJS project: ${t}\n`),u(`Resolving package versions...`);let d=await Ct();if(c!==`express`)if(Dt(`@forinda/kickjs`,`latest`,`./${c}`))u(`Using @forinda/kickjs@latest (stable ships the ${c} runtime)`);else{let e=[`@forinda/kickjs`,`@forinda/kickjs-cli`,`@forinda/kickjs-vite`],t=[],n=!1;for(let r of e){let e=wt(r,`alpha`);e&&Et(e,Tt(d[r]))&&(d[r]=`^${e}`,t.push(`${r}@^${e}`),r===`@forinda/kickjs`&&(n=!0))}u(n?`Using the alpha channel for the ${c} runtime: ${t.join(`, `)}`:`WARNING: could not resolve @forinda/kickjs@alpha — the ${c} runtime subpath may be missing. After install, run: ${r} add @forinda/kickjs@alpha`)}await N(h(l,`package.json`),Ye(t,i,d,o,s,c)),await N(h(l,`vite.config.ts`),Xe()),await N(h(l,`tsconfig.json`),Ze()),await N(h(l,`.prettierrc`),Qe()),await N(h(l,`.editorconfig`),$e()),await N(h(l,`.gitignore`),et()),await N(h(l,`.gitattributes`),tt()),await N(h(l,`.env`),nt()),await N(h(l,`.env.example`),rt()),await N(h(l,`src/config/index.ts`),ct(s)),await N(h(l,`src/index.ts`),ot(t,i,bt.version,o,c)),await N(h(l,`src/modules/index.ts`),st()),await N(h(l,`src/modules/hello/hello.service.ts`),lt()),await N(h(l,`src/modules/hello/hello.controller.ts`),ut()),await N(h(l,`src/modules/hello/hello.module.ts`),dt()),await N(h(l,`kick.config.ts`),ft(i,a,r,c)),await N(h(l,`vitest.config.ts`),it()),await N(h(l,`README.md`),pt(t,i,r));let{generateAgentDocs:f}=await Promise.resolve().then(()=>ir);if(await f({outDir:l,name:t,pm:r,template:i,only:`all`,force:!0}),e.installDeps){console.log(`\n Installing dependencies with ${r}...\n`);try{S(`${r} install`,{cwd:l,stdio:`inherit`}),console.log(`
1233
1233
  Dependencies installed successfully!`)}catch{console.log(`\n Warning: ${r} install failed. Run it manually.`)}}try{let{runTypegen:e}=await Promise.resolve().then(()=>xa);await e({cwd:l,allowDuplicates:!0,silent:!0})}catch{}if(e.initGit)try{S(`git init`,{cwd:l,stdio:`pipe`}),S(`git branch -M main`,{cwd:l,stdio:`pipe`}),S(`git add -A`,{cwd:l,stdio:`pipe`}),S(`git commit -m "chore: initial commit from kick new"`,{cwd:l,stdio:`pipe`}),u(`Git repository initialized`)}catch{u(`Warning: git init failed (git may not be installed)`)}console.log(`
1234
1234
  Project scaffolded successfully!`),console.log();let p=l!==process.cwd();u(`Next steps:`),p&&u(` cd ${t}`),e.installDeps||u(` ${r} install`);let m={rest:`kick g module user`,ddd:`kick g module user --repo drizzle`,cqrs:`kick g module user --pattern cqrs`,minimal:`# add your routes to src/index.ts`};u(` ${m[i]??m.rest}`),u(` kick dev`),u(``),u(`Commands:`),u(` kick dev Start dev server with Vite HMR`),u(` kick build Production build via Vite`),u(` kick start Run production build`),u(``),u(`Generators:`),u(` kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)`),u(` kick g scaffold <n> <f..> CRUD module from field definitions`),u(` kick g controller <name> Standalone controller`),u(` kick g service <name> @Service() class`),u(` kick g middleware <name> Express middleware`),u(` kick g guard <name> Route guard (auth, roles, etc.)`),u(` kick g adapter <name> AppAdapter with lifecycle hooks`),u(` kick g dto <name> Zod DTO schema`),u(` kick g config Generate kick.config.ts`),u(``),u(`Add packages:`),u(` kick add <pkg> Install a KickJS package + peers`),u(` kick add --list Show all available packages`),u(``),u(`Available: auth, swagger, drizzle, prisma, ws, queue, devtools, mcp, testing`),u(``)}const kt={GET:O.green,POST:O.cyan,PUT:O.yellow,PATCH:O.magenta,DELETE:O.red};function At(e){return(kt[e]??O.dim)(e.padEnd(7))}function jt(e){let t=`[${e}]`.padEnd(10);switch(e){case`CRITICAL`:return O.red(t);case`WARNING`:return O.yellow(t);case`INFO`:return O.blue(O.dim(t));default:return t}}O.green(`✓`),O.red(`✖`),O.yellow(`⚠`),O.blue(`ℹ`);function Mt(e){D.intro(O.bgCyan(O.black(` ${e} `)))}function P(e){D.outro(e)}function Nt(e){D.isCancel(e)&&(D.cancel(`Operation cancelled.`),process.exit(0))}async function Pt(e){let t=await D.text(e);return Nt(t),t}async function Ft(e){let t=await D.select(e);return Nt(t),t}async function It(e){let t=await D.multiselect(e);return Nt(t),t}async function F(e){let t=await D.confirm(e);return Nt(t),t}function Lt(){return D.spinner()}const I=D.log,Rt={kickjs:{pkg:`@forinda/kickjs`,peers:[`express`],description:`Unified framework: DI, decorators, routing, middleware`,core:!0},vite:{pkg:`@forinda/kickjs-vite`,peers:[`vite`],description:`Vite plugin: dev server, HMR, module discovery`,dev:!0,core:!0},cli:{pkg:`@forinda/kickjs-cli`,peers:[],description:`CLI tool and code generators`,dev:!0,core:!0},zod:{pkg:`zod`,peers:[],description:`Zod schema validation (env, DTOs, OpenAPI) — wrap with fromZod()`},valibot:{pkg:`valibot`,peers:[],description:`Valibot schema validation — wrap with fromValibot()`},yup:{pkg:`yup`,peers:[],description:`Yup schema validation — wrap with fromYup()`},auth:{pkg:`@forinda/kickjs-auth`,peers:[`jsonwebtoken`],description:`JWT, API key, OAuth strategies, @Public, @Roles (+ optional argon2/bcryptjs)`,deprecated:`auth is moving to BYO — compose @LoadAuthUser/@RequireRole/@Public from defineContextDecorator (see the BYO Auth recipe in the docs)`},ai:{pkg:`@forinda/kickjs-ai`,peers:[`zod`],description:`AI toolkit — LLM providers, tool definitions from controllers`},swagger:{pkg:`@forinda/kickjs-swagger`,peers:[],description:`OpenAPI spec + Swagger UI + ReDoc`},db:{pkg:`@forinda/kickjs-db`,peers:[],description:`kick/db core — schema DSL, migrations, KickDbClient, customType`},pg:{pkg:`@forinda/kickjs-db`,peers:[`pg`],description:`kick/db + PostgreSQL driver (use @forinda/kickjs-db/pg)`},sqlite:{pkg:`@forinda/kickjs-db`,peers:[`better-sqlite3`],description:`kick/db + SQLite driver (use @forinda/kickjs-db/sqlite)`},mysql:{pkg:`@forinda/kickjs-db`,peers:[`mysql2`],description:`kick/db + MySQL driver (use @forinda/kickjs-db/mysql)`},drizzle:{pkg:`@forinda/kickjs-drizzle`,peers:[`drizzle-orm`],description:`Drizzle ORM adapter + query builder`,deprecated:"early-adoption adapter, no longer maintained — wire Drizzle directly (BYO), or use @forinda/kickjs-db, the built-in Kick ORM (`kick add db` / pg / sqlite / mysql)"},prisma:{pkg:`@forinda/kickjs-prisma`,peers:[`@prisma/client`],description:`Prisma adapter + query builder`,deprecated:"early-adoption adapter, no longer maintained — wire Prisma directly (BYO), or use @forinda/kickjs-db, the built-in Kick ORM (`kick add db` / pg / sqlite / mysql)"},ws:{pkg:`@forinda/kickjs-ws`,peers:[`ws`],description:`WebSocket with @WsController decorators`},devtools:{pkg:`@forinda/kickjs-devtools`,peers:[],description:`Development dashboard — routes, DI, metrics, health`,dev:!0},queue:{pkg:`@forinda/kickjs-queue`,peers:[],description:`Queue adapter (BullMQ/RabbitMQ/Kafka)`},"queue:bullmq":{pkg:`@forinda/kickjs-queue`,peers:[`bullmq`,`ioredis`],description:`Queue with BullMQ + Redis`},"queue:rabbitmq":{pkg:`@forinda/kickjs-queue`,peers:[`amqplib`],description:`Queue with RabbitMQ`},"queue:kafka":{pkg:`@forinda/kickjs-queue`,peers:[`kafkajs`],description:`Queue with Kafka`},"queue:redis-pubsub":{pkg:`@forinda/kickjs-queue`,peers:[`ioredis`],description:`Lightweight pub/sub via Redis (no persistence)`},mcp:{pkg:`@forinda/kickjs-mcp`,peers:[`@modelcontextprotocol/sdk`],description:`Model Context Protocol server — expose @Controller endpoints as AI tools`},testing:{pkg:`@forinda/kickjs-testing`,peers:[],description:`Test utilities and TestModule builder`,dev:!0}},zt={express:{prod:`multer`,dev:`@types/multer`,note:`Express uploads use multer (memory/disk storage, ctx.file / ctx.files).`},fastify:{prod:`@fastify/multipart`,note:`Fastify uploads use @fastify/multipart (buffered into ctx.file / ctx.files).`},h3:{note:`h3 parses multipart natively (readMultipartFormData) — no driver to install.`}};async function Bt(e=process.cwd()){let t=(await j(e))?.runtime;return t===`express`||t===`fastify`||t===`h3`?t:Vt(e)}function Vt(e=process.cwd()){let t=L(`package.json`,e);if(t)try{let e=JSON.parse(a(v(t,`package.json`),`utf-8`)),n={...e.dependencies,...e.devDependencies};if(`fastify`in n)return`fastify`;if(`h3`in n)return`h3`}catch{}return`express`}function L(e,t=process.cwd()){let n=t;for(;;){if(r(v(n,e)))return n;let t=f(n);if(t===n)return null;n=t}}function Ht(){return L(`pnpm-lock.yaml`)?`pnpm`:L(`yarn.lock`)?`yarn`:L(`bun.lockb`)||L(`bun.lock`)?`bun`:L(`package-lock.json`)?`npm`:null}function Ut(){let e=process.cwd();for(;e;){let t=v(e,`package.json`);if(r(t))try{let e=JSON.parse(a(t,`utf-8`)).packageManager;if(typeof e==`string`){let t=e.split(`@`)[0];if(Te.includes(t))return t}}catch{}let n=f(e);if(n===e)return null;e=n}return null}async function Wt(e){if(e&&Te.includes(e))return{pm:e,source:`flag`};let t=await j(process.cwd());if(t?.packageManager&&Te.includes(t.packageManager))return{pm:t.packageManager,source:`config`};let n=Ut();if(n)return{pm:n,source:`package.json`};let r=Ht();return r?{pm:r,source:`lockfile`}:{pm:`npm`,source:`default`}}async function Gt(e){let{pm:t}=await Wt(e);return t}function Kt(e=!1){let t=Object.entries(Rt),n=Math.max(...t.map(([e])=>e.length)),r=t.filter(([,e])=>e.core),i=t.filter(([,e])=>!e.core),a=([e,t])=>{let r=e.padEnd(n+2),i=t.peers.length?` (+ ${t.peers.join(`, `)})`:``,a=t.deprecated?` [DEPRECATED — ${t.deprecated}]`:``;return` ${r} ${t.description}${i}${a}`};console.log(`
1235
1235
  Core packages (always installed by \`kick new\`):
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v6.2.0-alpha.1
2
+ * @forinda/kickjs-cli v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,6 +8,6 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as e}from"./rolldown-runtime-BKjf5NYQ.mjs";import{isAbsolute as t,join as n,relative as r,resolve as i}from"node:path";import{existsSync as a,mkdirSync as o,readFileSync as s,writeFileSync as c}from"node:fs";import{access as l,readFile as u}from"node:fs/promises";var d=e({BUILTIN_REPO_TYPES:()=>p,DEPRECATED_REPO_TYPES:()=>m,PACKAGE_MANAGERS:()=>f,defineConfig:()=>g,loadKickConfig:()=>x,resolveModuleConfig:()=>y,resolveTokenScope:()=>_,validateAssetMap:()=>C,warnIfDeprecatedRepo:()=>h,writeAssetConfigSnapshot:()=>S});const f=[`pnpm`,`npm`,`yarn`,`bun`],p=[`inmemory`],m=[`prisma`,`drizzle`];function h(e){return m.includes(e)?(console.warn(` Note: the '${e}' repository preset is deprecated. Generating a generic custom repository named '${e}' instead — wire it to your DB by hand. Pass any name via \`--repo <name>\` or \`modules.repo: { name: '<name>' }\`.`),!0):!1}function g(e){return e}function _(e,t){if(e?.tokenScope&&typeof e.tokenScope==`string`&&e.tokenScope.length>0){let t=v(e.tokenScope);if(t.length>0)return t}try{let e=n(t,`package.json`);if(a(e)){let t=JSON.parse(s(e,`utf-8`));if(typeof t.name==`string`&&t.name.length>0){let e=t.name.match(/^@([^/]+)\//),n=v(e?e[1]:t.name);if(n.length>0)return n}}}catch{}return`app`}function v(e){return e.toLowerCase().replace(/[^a-z0-9-]/g,`-`).replace(/^-+|-+$/g,``).replace(/-{2,}/g,`-`)}function y(e){if(!e)return{};let t={dir:e.modules?.dir,repo:e.modules?.repo,schemaDir:e.modules?.schemaDir,pluralize:e.modules?.pluralize,prismaClientPath:e.modules?.prismaClientPath,style:e.modules?.style};return t.style!==void 0&&t.style!==`define`&&t.style!==`class`&&(console.warn(` Warning: modules.style '${t.style}' is not a valid value (expected 'define' or 'class'). Falling back to 'define'.`),t.style=`define`),t.repo&&typeof t.repo==`string`&&!p.includes(t.repo)&&(h(t.repo)||console.warn(` Warning: modules.repo '${t.repo}' is not a built-in type (${p.join(`, `)}). It will generate a stub repository. Use { name: '${t.repo}' } to silence this warning.`)),t}const b=[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`,`kick.config.json`];async function x(e){let{findProjectRoot:t}=await import(`./project-root-gmupeTuu.mjs`).then(e=>e.n),r=t(e);for(let e of b){let t=n(r,e);try{await l(t)}catch{continue}if(e.endsWith(`.json`)){let e=await u(t,`utf-8`);return JSON.parse(e)}if(e.endsWith(`.ts`)){let n;try{n=await import(`jiti`)}catch(t){let n=t instanceof Error?t.message:String(t);n.includes(`Cannot find package 'jiti'`)||n.includes(`ERR_MODULE_NOT_FOUND`)?console.warn(`Warning: Failed to load ${e} — 'jiti' is required for TypeScript configs. Run \`pnpm add -D jiti\` (or your package manager's equivalent), or rename the file to kick.config.js / kick.config.mjs / kick.config.json.`):console.warn(`Warning: Failed to initialize jiti for ${e}: ${n}`);continue}try{let e=await n.createJiti(r,{interopDefault:!0,fsCache:!1}).import(t,{default:!0}),i=C(e,r);for(let e of i)console.warn(` Warning: ${e}`);return S(r,e),e}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}try{let{pathToFileURL:e}=await import(`node:url`),n=await import(e(t).href),i=n.default??n,a=C(i,r);for(let e of a)console.warn(` Warning: ${e}`);return S(r,i),i}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}return null}function S(e,t){if(!(!t?.assetMap||Object.keys(t.assetMap).length===0))try{let r=n(e,`.kickjs`);o(r,{recursive:!0});let i={version:1,assetMap:t.assetMap,...t.build?.outDir?{build:{outDir:t.build.outDir}}:{}};c(n(r,`kick.config.json`),JSON.stringify(i,null,2)+`
11
+ import{t as e}from"./rolldown-runtime-BoqBCbMv.mjs";import{isAbsolute as t,join as n,relative as r,resolve as i}from"node:path";import{existsSync as a,mkdirSync as o,readFileSync as s,writeFileSync as c}from"node:fs";import{access as l,readFile as u}from"node:fs/promises";var d=e({BUILTIN_REPO_TYPES:()=>p,DEPRECATED_REPO_TYPES:()=>m,PACKAGE_MANAGERS:()=>f,defineConfig:()=>g,loadKickConfig:()=>x,resolveModuleConfig:()=>y,resolveTokenScope:()=>_,validateAssetMap:()=>C,warnIfDeprecatedRepo:()=>h,writeAssetConfigSnapshot:()=>S});const f=[`pnpm`,`npm`,`yarn`,`bun`],p=[`inmemory`],m=[`prisma`,`drizzle`];function h(e){return m.includes(e)?(console.warn(` Note: the '${e}' repository preset is deprecated. Generating a generic custom repository named '${e}' instead — wire it to your DB by hand. Pass any name via \`--repo <name>\` or \`modules.repo: { name: '<name>' }\`.`),!0):!1}function g(e){return e}function _(e,t){if(e?.tokenScope&&typeof e.tokenScope==`string`&&e.tokenScope.length>0){let t=v(e.tokenScope);if(t.length>0)return t}try{let e=n(t,`package.json`);if(a(e)){let t=JSON.parse(s(e,`utf-8`));if(typeof t.name==`string`&&t.name.length>0){let e=t.name.match(/^@([^/]+)\//),n=v(e?e[1]:t.name);if(n.length>0)return n}}}catch{}return`app`}function v(e){return e.toLowerCase().replace(/[^a-z0-9-]/g,`-`).replace(/^-+|-+$/g,``).replace(/-{2,}/g,`-`)}function y(e){if(!e)return{};let t={dir:e.modules?.dir,repo:e.modules?.repo,schemaDir:e.modules?.schemaDir,pluralize:e.modules?.pluralize,prismaClientPath:e.modules?.prismaClientPath,style:e.modules?.style};return t.style!==void 0&&t.style!==`define`&&t.style!==`class`&&(console.warn(` Warning: modules.style '${t.style}' is not a valid value (expected 'define' or 'class'). Falling back to 'define'.`),t.style=`define`),t.repo&&typeof t.repo==`string`&&!p.includes(t.repo)&&(h(t.repo)||console.warn(` Warning: modules.repo '${t.repo}' is not a built-in type (${p.join(`, `)}). It will generate a stub repository. Use { name: '${t.repo}' } to silence this warning.`)),t}const b=[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`,`kick.config.json`];async function x(e){let{findProjectRoot:t}=await import(`./project-root-DsnEUmMN.mjs`).then(e=>e.n),r=t(e);for(let e of b){let t=n(r,e);try{await l(t)}catch{continue}if(e.endsWith(`.json`)){let e=await u(t,`utf-8`);return JSON.parse(e)}if(e.endsWith(`.ts`)){let n;try{n=await import(`jiti`)}catch(t){let n=t instanceof Error?t.message:String(t);n.includes(`Cannot find package 'jiti'`)||n.includes(`ERR_MODULE_NOT_FOUND`)?console.warn(`Warning: Failed to load ${e} — 'jiti' is required for TypeScript configs. Run \`pnpm add -D jiti\` (or your package manager's equivalent), or rename the file to kick.config.js / kick.config.mjs / kick.config.json.`):console.warn(`Warning: Failed to initialize jiti for ${e}: ${n}`);continue}try{let e=await n.createJiti(r,{interopDefault:!0,fsCache:!1}).import(t,{default:!0}),i=C(e,r);for(let e of i)console.warn(` Warning: ${e}`);return S(r,e),e}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}try{let{pathToFileURL:e}=await import(`node:url`),n=await import(e(t).href),i=n.default??n,a=C(i,r);for(let e of a)console.warn(` Warning: ${e}`);return S(r,i),i}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}return null}function S(e,t){if(!(!t?.assetMap||Object.keys(t.assetMap).length===0))try{let r=n(e,`.kickjs`);o(r,{recursive:!0});let i={version:1,assetMap:t.assetMap,...t.build?.outDir?{build:{outDir:t.build.outDir}}:{}};c(n(r,`kick.config.json`),JSON.stringify(i,null,2)+`
12
12
  `,`utf-8`)}catch{}}function C(e,t){let n=[];if(!e?.assetMap)return n;let r=i(t);for(let[o,s]of Object.entries(e.assetMap)){if(!o||o.includes(`/`)){n.push(`assetMap key '${o}' is invalid — must be a non-empty string without '/'`);continue}if(typeof s?.src!=`string`||s.src.length===0){n.push(`assetMap.${o} is missing a non-empty 'src' field`);continue}a(i(t,s.src))||n.push(`assetMap.${o}.src ('${s.src}') does not exist — typegen + build will fail`),s.dest&&w(i(t,s.dest),r)&&n.push(`assetMap.${o}.dest ('${s.dest}') resolves outside the project root — refusing to copy`)}return n}function w(e,n){let i=r(n,e);return i===``?!1:i.startsWith(`..`)||t(i)}export{y as a,x as i,d as n,_ as o,g as r,h as s,f as t};
13
- //# sourceMappingURL=config-DXJWJLD4.mjs.map
13
+ //# sourceMappingURL=config-G8kmxDyZ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-DXJWJLD4.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` remains built-in (zero-dep, framework-owned). The\n * `prisma` and `drizzle` ORM presets are **deprecated** — they now\n * scaffold a generic custom-repository stub like any other name (see\n * {@link DEPRECATED_REPO_TYPES}). Bring your own DB by passing a name:\n * `repo: { name: 'postgres' }`.\n */\nexport type BuiltinRepoType = 'inmemory'\n\nexport const BUILTIN_REPO_TYPES: readonly string[] = ['inmemory']\n\n/**\n * Repo names that used to have dedicated ORM generators. They still\n * work — they just produce the same generic stub every other custom\n * name does — but the CLI prints a one-line deprecation note.\n */\nexport const DEPRECATED_REPO_TYPES: readonly string[] = ['prisma', 'drizzle']\n\n/**\n * Print a deprecation note when a generator is asked for a\n * `prisma`/`drizzle` repo. No-op for every other name. Returns whether\n * a note was printed (handy for tests).\n */\nexport function warnIfDeprecatedRepo(repo: string): boolean {\n if (!DEPRECATED_REPO_TYPES.includes(repo)) return false\n console.warn(\n ` Note: the '${repo}' repository preset is deprecated. Generating a generic ` +\n `custom repository named '${repo}' instead — wire it to your DB by hand. ` +\n `Pass any name via \\`--repo <name>\\` or \\`modules.repo: { name: '<name>' }\\`.`,\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/** 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 * Built-in types (string): `'drizzle'`, `'inmemory'`, `'prisma'`\n * — generate fully working repository code.\n *\n * Custom types (object): `{ name: 'typeorm' }`\n * — generate a stub repository with TODO markers.\n *\n * @example\n * repo: 'prisma' // built-in\n * repo: { name: 'typeorm' } // custom\n */\n repo?: RepoTypeConfig\n /** Schema output directory (e.g. 'src/db/schema' for Drizzle, 'prisma/' for Prisma) */\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 * Import path for the Prisma generated client in `--repo prisma` templates.\n * Must resolve within `src/` for path alias compatibility.\n *\n * @default '@prisma/client' (Prisma 5/6)\n * @example\n * prismaClientPath: '@/generated/prisma/client' // Prisma 7+\n * prismaClientPath: './generated/prisma/client' // relative\n */\n prismaClientPath?: string\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 — controls default generator behavior.\n * - 'rest' — Express + Swagger (default)\n * - 'ddd' — Full DDD modules with use cases, entities, value objects\n * - 'cqrs' — CQRS with commands, queries, events, WebSocket + queue\n * - 'minimal' — Bare Express with no scaffolding\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: 'prisma',\n * pluralize: false,\n * schemaDir: 'prisma/',\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 */\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 prismaClientPath: config.modules?.prismaClientPath,\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
+ {"version":3,"file":"config-G8kmxDyZ.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` remains built-in (zero-dep, framework-owned). The\n * `prisma` and `drizzle` ORM presets are **deprecated** — they now\n * scaffold a generic custom-repository stub like any other name (see\n * {@link DEPRECATED_REPO_TYPES}). Bring your own DB by passing a name:\n * `repo: { name: 'postgres' }`.\n */\nexport type BuiltinRepoType = 'inmemory'\n\nexport const BUILTIN_REPO_TYPES: readonly string[] = ['inmemory']\n\n/**\n * Repo names that used to have dedicated ORM generators. They still\n * work — they just produce the same generic stub every other custom\n * name does — but the CLI prints a one-line deprecation note.\n */\nexport const DEPRECATED_REPO_TYPES: readonly string[] = ['prisma', 'drizzle']\n\n/**\n * Print a deprecation note when a generator is asked for a\n * `prisma`/`drizzle` repo. No-op for every other name. Returns whether\n * a note was printed (handy for tests).\n */\nexport function warnIfDeprecatedRepo(repo: string): boolean {\n if (!DEPRECATED_REPO_TYPES.includes(repo)) return false\n console.warn(\n ` Note: the '${repo}' repository preset is deprecated. Generating a generic ` +\n `custom repository named '${repo}' instead — wire it to your DB by hand. ` +\n `Pass any name via \\`--repo <name>\\` or \\`modules.repo: { name: '<name>' }\\`.`,\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/** 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 * Built-in types (string): `'drizzle'`, `'inmemory'`, `'prisma'`\n * — generate fully working repository code.\n *\n * Custom types (object): `{ name: 'typeorm' }`\n * — generate a stub repository with TODO markers.\n *\n * @example\n * repo: 'prisma' // built-in\n * repo: { name: 'typeorm' } // custom\n */\n repo?: RepoTypeConfig\n /** Schema output directory (e.g. 'src/db/schema' for Drizzle, 'prisma/' for Prisma) */\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 * Import path for the Prisma generated client in `--repo prisma` templates.\n * Must resolve within `src/` for path alias compatibility.\n *\n * @default '@prisma/client' (Prisma 5/6)\n * @example\n * prismaClientPath: '@/generated/prisma/client' // Prisma 7+\n * prismaClientPath: './generated/prisma/client' // relative\n */\n prismaClientPath?: string\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 — controls default generator behavior.\n * - 'rest' — Express + Swagger (default)\n * - 'ddd' — Full DDD modules with use cases, entities, value objects\n * - 'cqrs' — CQRS with commands, queries, events, WebSocket + queue\n * - 'minimal' — Bare Express with no scaffolding\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: 'prisma',\n * pluralize: false,\n * schemaDir: 'prisma/',\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 */\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 prismaClientPath: config.modules?.prismaClientPath,\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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"}