@forinda/kickjs-cli 5.3.2 → 5.4.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.
- package/dist/{builtins-CnQ6lxcV.mjs → builtins-DBzZkJey.mjs} +151 -90
- package/dist/builtins-DBzZkJey.mjs.map +1 -0
- package/dist/{builtins-BxGfcEP6.mjs → builtins-K-nRJcJG.mjs} +409 -214
- package/dist/cli.mjs +2 -2
- package/dist/config-CCNnXhar.mjs +11 -0
- package/dist/config-CQZ6Hppr.mjs +12 -0
- package/dist/config-CQZ6Hppr.mjs.map +1 -0
- package/dist/{generator-extension-BNgcYlom.mjs → generator-extension-Bn2aH7kY.mjs} +228 -94
- package/dist/generator-extension-Bn2aH7kY.mjs.map +1 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{plugin-B56zClEX.mjs → plugin-D0C4ISZA.mjs} +2 -2
- package/dist/{plugin-Ccvf-gn6.mjs → plugin-DasN_2Zr.mjs} +3 -3
- package/dist/{plugin-Ccvf-gn6.mjs.map → plugin-DasN_2Zr.mjs.map} +1 -1
- package/dist/{rolldown-runtime-CP9PNXAB.mjs → rolldown-runtime-BTpMa50s.mjs} +1 -1
- package/dist/{run-plugins-sjeIm8hS.mjs → run-plugins-Dk7KBKON.mjs} +2 -2
- package/dist/{typegen-CYA1y8NJ.mjs → typegen-Bl9kUVNL.mjs} +4 -4
- package/dist/{typegen-CYA1y8NJ.mjs.map → typegen-Bl9kUVNL.mjs.map} +1 -1
- package/dist/{typegen-CFW1vIgv.mjs → typegen-DDQJNnUl.mjs} +3 -3
- package/dist/{types-2ICiQzlQ.mjs → types-kAfWJgh0.mjs} +2 -2
- package/dist/{types-2ICiQzlQ.mjs.map → types-kAfWJgh0.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/builtins-CnQ6lxcV.mjs.map +0 -1
- package/dist/config-B5g_GsV2.mjs +0 -12
- package/dist/config-B5g_GsV2.mjs.map +0 -1
- package/dist/config-DE9Vo6LN.mjs +0 -11
- package/dist/generator-extension-BNgcYlom.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-cli v5.
|
|
2
|
+
* @forinda/kickjs-cli v5.4.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{t as e}from"./rolldown-runtime-
|
|
11
|
+
import{t as e}from"./rolldown-runtime-BTpMa50s.mjs";import{a as t,i as n,r,t as i}from"./config-CCNnXhar.mjs";import{n as a,r as o}from"./plugin-D0C4ISZA.mjs";import{a as s,i as c,o as l,r as u,s as d,t as f}from"./typegen-DDQJNnUl.mjs";import{createRequire as p}from"node:module";import{cpSync as m,existsSync as h,mkdirSync as g,readFileSync as _,readdirSync as ee,rmSync as v,statSync as te,writeFileSync as ne}from"node:fs";import y,{basename as re,dirname as b,extname as ie,isAbsolute as ae,join as x,relative as S,resolve as C,sep as oe}from"node:path";import{fileURLToPath as se,pathToFileURL as w}from"node:url";import{execSync as T,fork as ce,spawn as le,spawnSync as ue}from"node:child_process";import{access as de,copyFile as fe,mkdir as pe,readFile as E,readdir as me,rm as he,stat as ge,writeFile as D}from"node:fs/promises";import*as O from"@clack/prompts";import k from"picocolors";import _e from"pluralize";import{glob as ve}from"glob";import{groupAssetKeys as ye}from"@forinda/kickjs";import{arch as be,platform as xe,release as Se}from"node:os";import{generate as Ce,migrateDown as we,migrateLatest as Te,migrateRollback as Ee,migrateStatus as De,migrateUp as Oe,renderSchemaSource as ke,resolveDbConfig as Ae}from"@forinda/kickjs-db";function je(e,t,n){T(e,{cwd:t,stdio:`inherit`,env:n?{...process.env,...n}:process.env})}function Me(e,t,n){let r=ue(process.execPath,[e],{cwd:n,stdio:`inherit`,env:{...process.env,...t}});r.status!==0&&process.exit(r.status??1)}let Ne=!1;function A(e){Ne=e}const Pe=new Set([`.ts`,`.tsx`,`.js`,`.jsx`,`.mjs`,`.cjs`,`.json`,`.md`]);async function j(e,t){Ne||(await pe(b(e),{recursive:!0}),await D(e,t,`utf-8`),Pe.has(ie(e))&&await Ie(e,t).catch(()=>{}))}let M;async function Fe(e){if(M!==void 0)return M;try{M=await import(p(x(e,`package.json`)).resolve(`oxfmt`))}catch{M=null}return M}async function Ie(e,t){let n=await Fe(process.cwd());if(!n)return;let r=await Le(e);if(r===null)return;let i=await n.format(e,t,r);i.code!==t&&await D(e,i.code,`utf-8`)}const N=new Map;async function Le(e){let t=b(e),n=t;if(N.has(n))return N.get(n);for(;;){let e=x(t,`.oxfmtrc.json`);if(h(e))try{let t=await E(e,`utf-8`),r=JSON.parse(t);return delete r.$schema,delete r.ignorePatterns,N.set(n,r),r}catch{return N.set(n,null),null}let r=b(t);if(r===t)return N.set(n,null),null;t=r}}async function Re(e){try{return await de(e),!0}catch{return!1}}const ze={auth:`@forinda/kickjs-auth`,swagger:`@forinda/kickjs-swagger`,ws:`@forinda/kickjs-ws`,queue:`@forinda/kickjs-queue`,devtools:`@forinda/kickjs-devtools`};function Be(e,t,n,r=[]){let i={"@forinda/kickjs":n,dotenv:`^17.3.1`,express:`^5.1.0`,"reflect-metadata":`^0.2.2`,zod:`^4.3.6`,pino:`^10.3.1`,"pino-pretty":`^13.1.3`};for(let e of r){let t=ze[e];t&&!i[t]&&(i[t]=n)}return JSON.stringify({name:e,version:n.replace(`^`,``),type:`module`,scripts:{dev:`vite`,"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:i,devDependencies:{"@forinda/kickjs-cli":n,"@forinda/kickjs-vite":n,"@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 Ve(){return`import { defineConfig } from 'vite'
|
|
12
12
|
import { resolve } from 'node:path'
|
|
13
13
|
import swc from 'unplugin-swc'
|
|
14
14
|
import { kickjsVitePlugin, envWatchPlugin } from '@forinda/kickjs-vite'
|
|
@@ -43,7 +43,7 @@ export default defineConfig({
|
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
})
|
|
46
|
-
`}function
|
|
46
|
+
`}function He(){return JSON.stringify({compilerOptions:{target:`ES2022`,module:`ESNext`,moduleResolution:`bundler`,lib:[`ES2022`],types:[`node`,`vite/client`],strict:!0,esModuleInterop:!0,skipLibCheck:!0,sourceMap:!0,declaration:!0,experimentalDecorators:!0,emitDecoratorMetadata:!0,outDir:`dist`,paths:{"@/*":[`./src/*`]}},include:[`src`,`.kickjs/types/**/*.d.ts`,`.kickjs/types/**/*.ts`]},null,2)}function Ue(){return JSON.stringify({semi:!1,singleQuote:!0,trailingComma:`all`,printWidth:100,tabWidth:2},null,2)}function We(){return`# https://editorconfig.org
|
|
47
47
|
root = true
|
|
48
48
|
|
|
49
49
|
[*]
|
|
@@ -56,14 +56,14 @@ insert_final_newline = true
|
|
|
56
56
|
|
|
57
57
|
[*.md]
|
|
58
58
|
trim_trailing_whitespace = false
|
|
59
|
-
`}function
|
|
59
|
+
`}function Ge(){return`node_modules/
|
|
60
60
|
dist/
|
|
61
61
|
.env
|
|
62
62
|
coverage/
|
|
63
63
|
.DS_Store
|
|
64
64
|
*.tsbuildinfo
|
|
65
65
|
.kickjs/
|
|
66
|
-
`}function
|
|
66
|
+
`}function Ke(){return`# Auto-detect text files and normalise line endings to LF
|
|
67
67
|
* text=auto eol=lf
|
|
68
68
|
|
|
69
69
|
# Explicitly mark generated / binary files
|
|
@@ -81,11 +81,11 @@ coverage/
|
|
|
81
81
|
pnpm-lock.yaml -diff linguist-generated
|
|
82
82
|
yarn.lock -diff linguist-generated
|
|
83
83
|
package-lock.json -diff linguist-generated
|
|
84
|
-
`}function
|
|
84
|
+
`}function qe(){return`PORT=3000
|
|
85
85
|
NODE_ENV=development
|
|
86
|
-
`}function
|
|
86
|
+
`}function Je(){return`PORT=3000
|
|
87
87
|
NODE_ENV=development
|
|
88
|
-
`}function
|
|
88
|
+
`}function Ye(){return`import { defineConfig } from 'vitest/config'
|
|
89
89
|
import swc from 'unplugin-swc'
|
|
90
90
|
|
|
91
91
|
export default defineConfig({
|
|
@@ -96,7 +96,7 @@ export default defineConfig({
|
|
|
96
96
|
include: ['src/**/*.test.ts'],
|
|
97
97
|
},
|
|
98
98
|
})
|
|
99
|
-
`}function
|
|
99
|
+
`}function Xe(e,t,n,r=[]){switch(t){case`cqrs`:{let t=[],i=[];return r.includes(`devtools`)&&(t.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`),i.push(` DevToolsAdapter(),`)),r.includes(`swagger`)&&(t.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`),i.push(` SwaggerAdapter({\n info: { title: '${e}', version: '${n}' },\n }),`)),`import 'reflect-metadata'
|
|
100
100
|
// Side-effect import — registers the extended env schema with kickjs
|
|
101
101
|
// **before** any controller / service / @Value gets resolved. Without
|
|
102
102
|
// this line ConfigService.get('YOUR_KEY') returns undefined because the
|
|
@@ -166,12 +166,14 @@ export const app = await bootstrap({
|
|
|
166
166
|
express.json(),
|
|
167
167
|
],
|
|
168
168
|
})
|
|
169
|
-
`}}}function
|
|
169
|
+
`}}}function Ze(){return`import type { AppModuleEntry } from '@forinda/kickjs'
|
|
170
170
|
import { HelloModule } from './hello/hello.module'
|
|
171
171
|
|
|
172
172
|
// Remove HelloModule and run: kick g module <name>
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
// Modules built with \`defineModule\` are called as factories — the
|
|
174
|
+
// invocation produces the AppModule instance bootstrap registers.
|
|
175
|
+
export const modules: AppModuleEntry[] = [HelloModule()]
|
|
176
|
+
`}function Qe(){return`import { defineEnv, loadEnv } from '@forinda/kickjs/config'
|
|
175
177
|
import { z } from 'zod'
|
|
176
178
|
|
|
177
179
|
/**
|
|
@@ -206,7 +208,7 @@ const envSchema = defineEnv((base) =>
|
|
|
206
208
|
export const env = loadEnv(envSchema)
|
|
207
209
|
|
|
208
210
|
export default envSchema
|
|
209
|
-
`}function
|
|
211
|
+
`}function $e(){return`import { Service } from '@forinda/kickjs'
|
|
210
212
|
|
|
211
213
|
@Service()
|
|
212
214
|
export class HelloService {
|
|
@@ -218,7 +220,7 @@ export class HelloService {
|
|
|
218
220
|
return { status: 'ok', uptime: process.uptime() }
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
|
-
`}function
|
|
223
|
+
`}function et(){return`import { Controller, Get, Autowired, type Ctx } from '@forinda/kickjs'
|
|
222
224
|
import { HelloService } from './hello.service'
|
|
223
225
|
|
|
224
226
|
// \`Ctx<KickRoutes.HelloController['<method>']>\` is generated by
|
|
@@ -240,26 +242,28 @@ export class HelloController {
|
|
|
240
242
|
ctx.json(this.helloService.healthCheck())
|
|
241
243
|
}
|
|
242
244
|
}
|
|
243
|
-
`}function
|
|
245
|
+
`}function tt(){return`import { defineModule } from '@forinda/kickjs'
|
|
244
246
|
import { HelloController } from './hello.controller'
|
|
245
247
|
|
|
246
|
-
export
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
248
|
+
export const HelloModule = defineModule({
|
|
249
|
+
name: 'HelloModule',
|
|
250
|
+
build: () => ({
|
|
251
|
+
// \`register(container)\` is optional — only implement it when you need
|
|
252
|
+
// to bind a token to a concrete implementation, e.g.
|
|
253
|
+
// register(container) {
|
|
254
|
+
// container.registerFactory(USER_REPOSITORY, () => container.resolve(InMemoryUserRepository))
|
|
255
|
+
// }
|
|
256
|
+
// The HelloService uses @Service() so the decorator handles registration.
|
|
257
|
+
|
|
258
|
+
routes() {
|
|
259
|
+
return {
|
|
260
|
+
path: '/hello',
|
|
261
|
+
controller: HelloController,
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
}),
|
|
265
|
+
})
|
|
266
|
+
`}function nt(e,t=`inmemory`,n=`pnpm`){return`import { defineConfig } from '@forinda/kickjs-cli'
|
|
263
267
|
|
|
264
268
|
export default defineConfig({
|
|
265
269
|
pattern: '${e}',
|
|
@@ -303,7 +307,7 @@ export default defineConfig({
|
|
|
303
307
|
},
|
|
304
308
|
],
|
|
305
309
|
})
|
|
306
|
-
`}function
|
|
310
|
+
`}function rt(e,t,n){let r={rest:`REST API`,ddd:`Domain-Driven Design`,cqrs:`CQRS + Event-Driven`,minimal:`Minimal`},i=[`@forinda/kickjs`,`@forinda/kickjs-vite`];return t!==`minimal`&&i.push(`@forinda/kickjs-swagger`,`@forinda/kickjs-devtools`),t===`cqrs`&&i.push(`@forinda/kickjs-queue`,`@forinda/kickjs-ws`),`# ${e}
|
|
307
311
|
|
|
308
312
|
A **${r[t]??`REST API`}** built with [KickJS](https://forinda.github.io/kick-js/) — a decorator-driven Node.js framework on Express 5 and TypeScript.
|
|
309
313
|
|
|
@@ -366,7 +370,7 @@ Copy \`.env.example\` to \`.env\` and configure:
|
|
|
366
370
|
|
|
367
371
|
- [KickJS Documentation](https://forinda.github.io/kick-js/)
|
|
368
372
|
- [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
|
|
369
|
-
`}function
|
|
373
|
+
`}function it(e,t,n){return`# CLAUDE.md — ${e}
|
|
370
374
|
|
|
371
375
|
**Read \`./AGENTS.md\` first.** It is the canonical, multi-agent
|
|
372
376
|
reference for this project (Claude, Copilot, Codex, Gemini, etc.) —
|
|
@@ -432,7 +436,7 @@ When generating or modifying code in this project, stay aligned with the v4 conv
|
|
|
432
436
|
- **Refresh these files**: \`kick g agents -f\` regenerates \`AGENTS.md\` + \`CLAUDE.md\` from the latest CLI templates. Hand-edited content is overwritten — keep customisation in \`AGENTS.local.md\`.
|
|
433
437
|
|
|
434
438
|
For everything else (controllers, services, modules, RequestContext API, generators, CLI commands, package additions, env wiring, troubleshooting) → \`AGENTS.md\`.
|
|
435
|
-
`}function
|
|
439
|
+
`}function at(e,t,n){return`# AGENTS.md — AI Agent Guide for ${e}
|
|
436
440
|
|
|
437
441
|
This guide is the **canonical, multi-agent reference** for this KickJS
|
|
438
442
|
application — Claude, Copilot, Codex, Gemini, etc. all read it first.
|
|
@@ -537,7 +541,7 @@ mistakes:
|
|
|
537
541
|
|
|
538
542
|
\`\`\`ts
|
|
539
543
|
// src/modules/index.ts
|
|
540
|
-
export const modules:
|
|
544
|
+
export const modules: AppModuleEntry[] = [HelloModule(), UsersModule(), ...]
|
|
541
545
|
|
|
542
546
|
// src/middleware/index.ts
|
|
543
547
|
export const middleware = [helmet(), cors(), requestId(), ...]
|
|
@@ -601,7 +605,7 @@ ${t===`ddd`?`\`\`\`
|
|
|
601
605
|
├── <name>.repository.ts # Data access (@Repository)
|
|
602
606
|
├── <name>.dto.ts # Request/response schemas (Zod)
|
|
603
607
|
├── <name>.entity.ts # Domain entity (optional)
|
|
604
|
-
└── <name>.module.ts # Module definition (
|
|
608
|
+
└── <name>.module.ts # Module definition (defineModule factory)
|
|
605
609
|
\`\`\`
|
|
606
610
|
`:t===`cqrs`?`\`\`\`
|
|
607
611
|
<name>/
|
|
@@ -615,14 +619,14 @@ ${t===`ddd`?`\`\`\`
|
|
|
615
619
|
│ └── <name>-created.event.ts
|
|
616
620
|
├── <name>.controller.ts # HTTP routes
|
|
617
621
|
├── <name>.repository.ts # Data access
|
|
618
|
-
└── <name>.module.ts # Module definition (
|
|
622
|
+
└── <name>.module.ts # Module definition (defineModule factory)
|
|
619
623
|
\`\`\`
|
|
620
624
|
`:t===`rest`?`\`\`\`
|
|
621
625
|
<name>/
|
|
622
626
|
├── <name>.controller.ts # HTTP routes (@Controller)
|
|
623
627
|
├── <name>.service.ts # Business logic (@Service)
|
|
624
628
|
├── <name>.dto.ts # Request/response schemas (Zod)
|
|
625
|
-
└── <name>.module.ts # Module definition (
|
|
629
|
+
└── <name>.module.ts # Module definition (defineModule factory)
|
|
626
630
|
\`\`\`
|
|
627
631
|
`:"```\nsrc/\n├── index.ts # Add routes here\n└── ... # Custom structure\n```\n"}
|
|
628
632
|
|
|
@@ -653,8 +657,8 @@ If not using generators:
|
|
|
653
657
|
- [ ] Create \`src/modules/<name>/<name>.controller.ts\`
|
|
654
658
|
- [ ] Add \`@Controller()\` decorator
|
|
655
659
|
- [ ] Add route handlers with \`@Get()\`, \`@Post()\`, etc.
|
|
656
|
-
- [ ] Create module file
|
|
657
|
-
- [ ] Register module in \`src/modules/index.ts\` (\`
|
|
660
|
+
- [ ] Create module file with \`defineModule({ name, build: () => ({ routes() { return { path, controller } } }) })\` (the framework derives the Express router from the controller)
|
|
661
|
+
- [ ] Register module in \`src/modules/index.ts\` (\`AppModuleEntry[]\` array — call the factory at the registration site: \`[MyModule()]\`)
|
|
658
662
|
- [ ] Test with \`kick dev\`
|
|
659
663
|
|
|
660
664
|
### Manual Service
|
|
@@ -935,7 +939,7 @@ ${t===`cqrs`?`### Background Jobs
|
|
|
935
939
|
- [Decorators Guide](https://forinda.github.io/kick-js/guide/decorators.html)
|
|
936
940
|
- [DI System](https://forinda.github.io/kick-js/guide/dependency-injection.html)
|
|
937
941
|
- [Testing](https://forinda.github.io/kick-js/api/testing.html)
|
|
938
|
-
`}function
|
|
942
|
+
`}function ot(e,t,n){return`# kickjs-skills.md — Task Skills for AI Agents (${e})
|
|
939
943
|
|
|
940
944
|
This file is the agent-facing **skills index** for KickJS work in this
|
|
941
945
|
repo. Each block below is a short, rigid workflow keyed to a specific
|
|
@@ -1182,16 +1186,16 @@ description: Patterns to refuse outright when the user asks for them — they br
|
|
|
1182
1186
|
- [Decorators](https://forinda.github.io/kick-js/guide/decorators.html)
|
|
1183
1187
|
- [Context Decorators](https://forinda.github.io/kick-js/guide/context-decorators.html)
|
|
1184
1188
|
- [Testing](https://forinda.github.io/kick-js/api/testing.html)
|
|
1185
|
-
`}const
|
|
1186
|
-
Dependencies installed successfully!`)}catch{console.log(`\n Warning: ${r} install failed. Run it manually.`)}}try{let{runTypegen:e}=await import(`./typegen-
|
|
1187
|
-
Project scaffolded successfully!`),console.log();let l=s!==process.cwd();c(`Next steps:`),l&&c(` cd ${t}`),e.installDeps||c(` ${r} install`);let u={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`};c(` ${u[i]??u.rest}`),c(` kick dev`),c(``),c(`Commands:`),c(` kick dev Start dev server with Vite HMR`),c(` kick build Production build via Vite`),c(` kick start Run production build`),c(``),c(`Generators:`),c(` kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)`),c(` kick g scaffold <n> <f..> CRUD module from field definitions`),c(` kick g controller <name> Standalone controller`),c(` kick g service <name> @Service() class`),c(` kick g middleware <name> Express middleware`),c(` kick g guard <name> Route guard (auth, roles, etc.)`),c(` kick g adapter <name> AppAdapter with lifecycle hooks`),c(` kick g dto <name> Zod DTO schema`),i===`cqrs`&&c(` kick g job <name> Queue job processor`),c(` kick g config Generate kick.config.ts`),c(``),c(`Add packages:`),c(` kick add <pkg> Install a KickJS package + peers`),c(` kick add --list Show all available packages`),c(``),c(`Available: auth, swagger, drizzle, prisma, ws, queue, devtools, mcp, testing`),c(``)}const
|
|
1189
|
+
`}const st=b(se(import.meta.url)),ct=JSON.parse(_(x(st,`..`,`package.json`),`utf-8`)),lt=`^${ct.version}`;async function ut(e){let{name:t,directory:n,packageManager:r=`pnpm`,template:i=`rest`,defaultRepo:a=`inmemory`,packages:o=[]}=e,s=n,c=e=>console.log(` ${e}`);if(console.log(`\n Creating KickJS project: ${t}\n`),await j(x(s,`package.json`),Be(t,i,lt,o)),await j(x(s,`vite.config.ts`),Ve()),await j(x(s,`tsconfig.json`),He()),await j(x(s,`.prettierrc`),Ue()),await j(x(s,`.editorconfig`),We()),await j(x(s,`.gitignore`),Ge()),await j(x(s,`.gitattributes`),Ke()),await j(x(s,`.env`),qe()),await j(x(s,`.env.example`),Je()),await j(x(s,`src/config/index.ts`),Qe()),await j(x(s,`src/index.ts`),Xe(t,i,ct.version,o)),await j(x(s,`src/modules/index.ts`),Ze()),await j(x(s,`src/modules/hello/hello.service.ts`),$e()),await j(x(s,`src/modules/hello/hello.controller.ts`),et()),await j(x(s,`src/modules/hello/hello.module.ts`),tt()),await j(x(s,`kick.config.ts`),nt(i,a,r)),await j(x(s,`vitest.config.ts`),Ye()),await j(x(s,`README.md`),rt(t,i,r)),await j(x(s,`CLAUDE.md`),it(t,i,r)),await j(x(s,`AGENTS.md`),at(t,i,r)),await j(x(s,`kickjs-skills.md`),ot(t,i,r)),e.installDeps){console.log(`\n Installing dependencies with ${r}...\n`);try{T(`${r} install`,{cwd:s,stdio:`inherit`}),console.log(`
|
|
1190
|
+
Dependencies installed successfully!`)}catch{console.log(`\n Warning: ${r} install failed. Run it manually.`)}}try{let{runTypegen:e}=await import(`./typegen-DDQJNnUl.mjs`).then(e=>e.n);await e({cwd:s,allowDuplicates:!0,silent:!0})}catch{}if(e.initGit)try{T(`git init`,{cwd:s,stdio:`pipe`}),T(`git branch -M main`,{cwd:s,stdio:`pipe`}),T(`git add -A`,{cwd:s,stdio:`pipe`}),T(`git commit -m "chore: initial commit from kick new"`,{cwd:s,stdio:`pipe`}),c(`Git repository initialized`)}catch{c(`Warning: git init failed (git may not be installed)`)}console.log(`
|
|
1191
|
+
Project scaffolded successfully!`),console.log();let l=s!==process.cwd();c(`Next steps:`),l&&c(` cd ${t}`),e.installDeps||c(` ${r} install`);let u={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`};c(` ${u[i]??u.rest}`),c(` kick dev`),c(``),c(`Commands:`),c(` kick dev Start dev server with Vite HMR`),c(` kick build Production build via Vite`),c(` kick start Run production build`),c(``),c(`Generators:`),c(` kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)`),c(` kick g scaffold <n> <f..> CRUD module from field definitions`),c(` kick g controller <name> Standalone controller`),c(` kick g service <name> @Service() class`),c(` kick g middleware <name> Express middleware`),c(` kick g guard <name> Route guard (auth, roles, etc.)`),c(` kick g adapter <name> AppAdapter with lifecycle hooks`),c(` kick g dto <name> Zod DTO schema`),i===`cqrs`&&c(` kick g job <name> Queue job processor`),c(` kick g config Generate kick.config.ts`),c(``),c(`Add packages:`),c(` kick add <pkg> Install a KickJS package + peers`),c(` kick add --list Show all available packages`),c(``),c(`Available: auth, swagger, drizzle, prisma, ws, queue, devtools, mcp, testing`),c(``)}const dt={GET:k.green,POST:k.cyan,PUT:k.yellow,PATCH:k.magenta,DELETE:k.red};function ft(e){return(dt[e]??k.dim)(e.padEnd(7))}function pt(e){let t=`[${e}]`.padEnd(10);switch(e){case`CRITICAL`:return k.red(t);case`WARNING`:return k.yellow(t);case`INFO`:return k.blue(k.dim(t));default:return t}}k.green(`✓`),k.red(`✖`),k.yellow(`⚠`),k.blue(`ℹ`);function mt(e){O.intro(k.bgCyan(k.black(` ${e} `)))}function P(e){O.outro(e)}function ht(e){O.isCancel(e)&&(O.cancel(`Operation cancelled.`),process.exit(0))}async function gt(e){let t=await O.text(e);return ht(t),t}async function _t(e){let t=await O.select(e);return ht(t),t}async function vt(e){let t=await O.multiselect(e);return ht(t),t}async function F(e){let t=await O.confirm(e);return ht(t),t}function yt(){return O.spinner()}const I=O.log,bt={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},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`},"db-pg":{pkg:`@forinda/kickjs-db-pg`,peers:[`pg`],description:`kick/db PostgreSQL dialect + adapter (pgDialect, pgAdapter)`},drizzle:{pkg:`@forinda/kickjs-drizzle`,peers:[`drizzle-orm`],description:`Drizzle ORM adapter + query builder`},prisma:{pkg:`@forinda/kickjs-prisma`,peers:[`@prisma/client`],description:`Prisma adapter + query builder`},ws:{pkg:`@forinda/kickjs-ws`,peers:[`socket.io`],description:`WebSocket with @WsController decorators`},devtools:{pkg:`@forinda/kickjs-devtools`,peers:[],description:`Development dashboard — routes, DI, metrics, health`,dev:!0},auth:{pkg:`@forinda/kickjs-auth`,peers:[`jsonwebtoken`],description:`Authentication — JWT, API key, and custom strategies`},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`},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}};function L(e,t=process.cwd()){let n=t;for(;;){if(h(C(n,e)))return n;let t=b(n);if(t===n)return null;n=t}}function xt(){return L(`pnpm-lock.yaml`)?`pnpm`:L(`yarn.lock`)?`yarn`:L(`bun.lockb`)||L(`bun.lock`)?`bun`:L(`package-lock.json`)?`npm`:null}function St(){let e=process.cwd();for(;e;){let t=C(e,`package.json`);if(h(t))try{let e=JSON.parse(_(t,`utf-8`)).packageManager;if(typeof e==`string`){let t=e.split(`@`)[0];if(i.includes(t))return t}}catch{}let n=b(e);if(n===e)return null;e=n}return null}async function Ct(e){if(e&&i.includes(e))return{pm:e,source:`flag`};let t=await r(process.cwd());if(t?.packageManager&&i.includes(t.packageManager))return{pm:t.packageManager,source:`config`};let n=St();if(n)return{pm:n,source:`package.json`};let a=xt();return a?{pm:a,source:`lockfile`}:{pm:`npm`,source:`default`}}async function wt(e){let{pm:t}=await Ct(e);return t}function Tt(e=!1){let t=Object.entries(bt),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(`, `)})`:``;return` ${r} ${t.description}${i}`};console.log(`
|
|
1188
1192
|
Core packages (always installed by \`kick new\`):
|
|
1189
1193
|
`);for(let e of r)console.log(a(e));if(e){console.log(`
|
|
1190
1194
|
Optional packages (add as needed):
|
|
1191
1195
|
`);for(let e of i)console.log(a(e))}else console.log(`\n Plus ${i.length} optional packages (auth, swagger, db, queue, …).`),console.log(" Run `kick add --list --all` for the full catalog.");console.log(`
|
|
1192
|
-
Usage: kick add auth drizzle swagger`),console.log(` kick add queue:bullmq`),console.log()}function
|
|
1193
|
-
`),a.size===0&&o.size===0))){if(a.size>0){let e=Array.from(a),t=`${n} add ${e.join(` `)}`;console.log(`\n Installing ${e.length} dependency(ies):`);for(let t of e)console.log(` + ${t}`);console.log();try{
|
|
1194
|
-
`)}})}const
|
|
1196
|
+
Usage: kick add auth drizzle swagger`),console.log(` kick add queue:bullmq`),console.log()}function Et(e){e.command(`list`).alias(`ls`).description(`List KickJS packages (core only; pair with --all for the full catalog)`).option(`--all`,`Include the full optional catalog`).action(e=>{Tt(!!e.all)})}function Dt(e){e.command(`add [packages...]`).description(`Add KickJS packages with their required dependencies`).option(`--pm <manager>`,`Package manager override`).option(`-D, --dev`,`Install as dev dependency`).option(`--list`,`List packages (core only by default; pair with --all)`).option(`--all`,`When listing, include the full optional catalog`).action(async(e,t)=>{if(t.list||e.length===0){Tt(!!t.all);return}let{pm:n,source:r}=await Ct(t.pm);console.log(`\n Using ${n} (resolved from ${r})`);let i=t.dev,a=new Set,o=new Set,s=[];for(let t of e){let e=bt[t];if(!e){s.push(t);continue}let n=i||e.dev?o:a;n.add(e.pkg);for(let t of e.peers)n.add(t)}if(!(s.length>0&&(console.log(`\n Unknown packages: ${s.join(`, `)}`),console.log(` Run "kick add --list" to see available packages.
|
|
1197
|
+
`),a.size===0&&o.size===0))){if(a.size>0){let e=Array.from(a),t=`${n} add ${e.join(` `)}`;console.log(`\n Installing ${e.length} dependency(ies):`);for(let t of e)console.log(` + ${t}`);console.log();try{T(t,{stdio:`inherit`})}catch{console.log(`\n Installation failed. Run manually:\n ${t}\n`)}}if(o.size>0){let e=Array.from(o),t=`${n} add -D ${e.join(` `)}`;console.log(`\n Installing ${e.length} dev dependency(ies):`);for(let t of e)console.log(` + ${t} (dev)`);console.log();try{T(t,{stdio:`inherit`})}catch{console.log(`\n Installation failed. Run manually:\n ${t}\n`)}}console.log(` Done!
|
|
1198
|
+
`)}})}const Ot=[{value:`auth`,label:`Auth`,hint:`JWT, OAuth, API keys`},{value:`swagger`,label:`Swagger`,hint:`OpenAPI docs`},{value:`ws`,label:`WebSocket`,hint:`rooms, heartbeat`},{value:`queue`,label:`Queue`,hint:`BullMQ/RabbitMQ/Kafka`},{value:`devtools`,label:`DevTools`,hint:`debug dashboard`}];function kt(e){e.command(`new [name]`).alias(`init`).description(`Create a new KickJS project (use "." for current directory)`).option(`-d, --directory <dir>`,`Target directory (defaults to project name)`).option(`--pm <manager>`,`Package manager: pnpm | npm | yarn | bun`).option(`--git`,`Initialize git repository`).option(`--no-git`,`Skip git initialization`).option(`--install`,`Install dependencies after scaffolding`).option(`--no-install`,`Skip dependency installation`).option(`-f, --force`,`Remove existing files without prompting`).option(`-t, --template <type>`,`Project template: rest | ddd | cqrs | minimal`).option(`-r, --repo <type>`,`Default repository: prisma | drizzle | inmemory | custom`).option(`--packages <packages>`,`Comma-separated packages to include (e.g. auth,swagger,ws,queue)`).option(`-y, --yes`,`Pick safe defaults for every prompt (template=minimal, repo=inmemory, no extras, git+install on)`).option(`--non-interactive`,`alias for --yes`).action(async(e,t)=>{mt(`KickJS — Create a new project`);let n=!!(t.yes||t.nonInteractive);e||=n?`my-api`:await gt({message:`Project name`,placeholder:`my-api`,defaultValue:`my-api`});let r;if(e===`.`?(r=C(`.`),e=re(r)):r=C(t.directory||e),h(r)){let i=ee(r);if(i.length>0){if(t.force)I.warn(`Clearing existing files in ${r}`);else if(n){I.warn(`Directory "${e}" is not empty. Pass --force to clear it.`),P(`Aborted.`);return}else{I.warn(`Directory "${e}" is not empty:`);let t=i.slice(0,5);for(let e of t)I.message(` - ${e}`);if(i.length>5&&I.message(` ... and ${i.length-5} more`),!await F({message:k.red(`Remove all existing files and proceed?`),initialValue:!1})){P(`Aborted.`);return}}for(let e of i)v(C(r,e),{recursive:!0,force:!0})}}let i=t.template;i||=n?`minimal`:await _t({message:`Project template`,options:[{value:`rest`,label:`REST API`,hint:`Express + Swagger`},{value:`ddd`,label:`DDD`,hint:`Domain-Driven Design modules`},{value:`cqrs`,label:`CQRS`,hint:`Commands, Queries, Events + WS/Queue`},{value:`minimal`,label:`Minimal`,hint:`bare Express`}]});let a=t.pm;a||=n?await wt(void 0):await _t({message:`Package manager`,options:[{value:`pnpm`,label:`pnpm`},{value:`npm`,label:`npm`},{value:`yarn`,label:`yarn`},{value:`bun`,label:`bun`}]});let o=t.repo;o||(n?o=`inmemory`:(o=await _t({message:`Default repository/ORM`,options:[{value:`prisma`,label:`Prisma`},{value:`drizzle`,label:`Drizzle`},{value:`inmemory`,label:`In-Memory`},{value:`custom`,label:`Custom`,hint:`specify later`}]}),o===`custom`&&(o=await gt({message:`Custom repository name`,defaultValue:`custom`}))));let s;if(t.packages!==void 0){let e=t.packages.trim().toLowerCase();s=e===``||e===`none`||e===`false`?[]:t.packages.split(`,`).map(e=>e.trim()).filter(Boolean)}else s=n?[]:await vt({message:`Select packages to include`,options:[...Ot],required:!1});let c;c=t.git===void 0?n?!0:await F({message:`Initialize git repository?`,initialValue:!0}):t.git;let l;l=t.install===void 0?n?!0:await F({message:`Install dependencies?`,initialValue:!0}):t.install,await ut({name:e,directory:r,packageManager:a,initGit:c,installDeps:l,template:i,defaultRepo:o,packages:s}),P(`Done! Next steps: ${k.cyan(`cd ${e} && ${a} dev`)}`)})}function R(e){return e.replace(/[-_\s]+(.)?/g,(e,t)=>t?t.toUpperCase():``).replace(/^(.)/,e=>e.toUpperCase())}function z(e){let t=R(e);return t.charAt(0).toLowerCase()+t.slice(1)}function B(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_]+/g,`-`).toLowerCase()}function V(e){return _e.plural(e)}function At(e){return _e.plural(e)}function jt(e){return B(e).replace(/-/g,`_`)}function Mt(e){let t=e.cwd??process.cwd(),n=e.pluralize??!0,r=R(e.name),i=z(e.name),a=B(e.name),o=jt(e.name),s={name:e.name,pascal:r,camel:i,kebab:a,snake:o,modulesDir:e.modulesDir??`src/modules`,cwd:t,args:e.args??[],flags:e.flags??{}};if(n){let e=V(a);s.pluralKebab=e,s.pluralPascal=R(e),s.pluralCamel=z(e)}return s}function Nt(e,t){return C(e.cwd,t)}async function Pt(e){return import(w(e).href)}const Ft=new Map;async function It(e){let t=Ft.get(e);if(t)return t;let n=Lt(e);return Ft.set(e,n),n}async function Lt(e){let t=C(e,`package.json`);if(!h(t))return{generators:[],loaded:[],failed:[]};let n=Rt(JSON.parse(await E(t,`utf-8`))),r=p(C(e,`package.json`)),i=[],a=[],o=[];for(let e of n){let t;try{t=r.resolve(`${e}/package.json`)}catch{continue}let n;try{n=JSON.parse(await E(t,`utf-8`))}catch(t){o.push({source:e,reason:`failed to parse package.json: ${t}`});continue}if(!n.kickjs?.generators)continue;let s=n.kickjs.generators,c=C(b(t),s);if(!h(c)){o.push({source:e,reason:`kickjs.generators points to missing file: ${s}`});continue}let l;try{l=await Pt(c)}catch(t){o.push({source:e,reason:`failed to import manifest: ${t}`});continue}let u=l.default;if(!Array.isArray(u)){o.push({source:e,reason:`manifest's default export is not an array of GeneratorSpec`});continue}for(let t of u){if(!zt(t)){o.push({source:e,reason:`manifest entry is not a valid GeneratorSpec (missing name/files)`});continue}i.push({source:e,spec:t})}a.push(e)}return{generators:i,loaded:a,failed:o}}function Rt(e){let t=new Set;for(let n of[e.dependencies,e.devDependencies,e.peerDependencies])if(n)for(let e of Object.keys(n))t.add(e);return Array.from(t)}function zt(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.name==`string`&&typeof t.files==`function`}async function Bt(e,t=[]){let n=e.cwd??process.cwd(),r=t.find(t=>t.spec.name===e.generatorName);if(r)return Ut(r.spec,r.source,e,n);let i=Ht(await It(n),e.generatorName);return i?Ut(i.spec,i.source,e,n):null}async function Vt(e,t=[]){let n=await It(e),r=new Set(t.map(e=>e.spec.name)),i=n.generators.filter(e=>!r.has(e.spec.name));return{generators:[...t,...i],loaded:n.loaded,failed:n.failed}}function Ht(e,t){return e.generators.find(e=>e.spec.name===t)}async function Ut(e,t,n,r){let i=Mt({name:n.itemName,args:n.args,flags:n.flags,modulesDir:n.modulesDir,pluralize:n.pluralize,cwd:r}),a=await e.files(i),o=[];for(let e of a){let t=Nt(i,e.path);await j(t,e.content),o.push(t)}return{files:o,source:t}}const Wt={inmemory:`in-memory`,drizzle:`Drizzle`,prisma:`Prisma`};function Gt(e){return e.charAt(0).toUpperCase()+e.slice(1).replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function Kt(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).toLowerCase()}function qt(e){return Wt[e]??Gt(e)}function Jt(e,t,n){let r={inmemory:`InMemory${e}Repository`,drizzle:`Drizzle${e}Repository`,prisma:`Prisma${e}Repository`},i={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`};return{repoClass:r[n]??`${Gt(n)}${e}Repository`,repoFile:i[n]??`${Kt(n)}-${t}`}}function Yt(e){return e??`define`}function Xt(e){let{pascal:t,kebab:n,plural:r=``,repo:i,style:a}=e,{repoClass:o,repoFile:s}=Jt(t,n,i),c=Yt(a),l=`/**
|
|
1195
1199
|
* ${t} Module
|
|
1196
1200
|
*
|
|
1197
1201
|
* Self-contained feature module following Domain-Driven Design (DDD).
|
|
@@ -1201,46 +1205,84 @@ description: Patterns to refuse outright when the user asks for them — they br
|
|
|
1201
1205
|
* presentation/ — HTTP controllers (entry points)
|
|
1202
1206
|
* application/ — Use cases (orchestration) and DTOs (validation)
|
|
1203
1207
|
* domain/ — Entities, value objects, repository interfaces, domain services
|
|
1204
|
-
* infrastructure/ — Repository implementations (currently ${
|
|
1205
|
-
|
|
1206
|
-
import {
|
|
1207
|
-
import { buildRoutes } from '@forinda/kickjs'
|
|
1208
|
-
import { ${t.toUpperCase()}_REPOSITORY } from './domain/repositories/${n}.repository'
|
|
1209
|
-
import { ${a} } from './infrastructure/repositories/${o}.repository'
|
|
1208
|
+
* infrastructure/ — Repository implementations (currently ${qt(i)})
|
|
1209
|
+
*/`,u=`import { ${t.toUpperCase()}_REPOSITORY } from './domain/repositories/${n}.repository'
|
|
1210
|
+
import { ${o} } from './infrastructure/repositories/${s}.repository'
|
|
1210
1211
|
import { ${t}Controller } from './presentation/${n}.controller'
|
|
1211
1212
|
|
|
1212
1213
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
1213
1214
|
import.meta.glob(
|
|
1214
1215
|
['./domain/services/**/*.ts', './application/use-cases/**/*.ts', '!./**/*.test.ts'],
|
|
1215
1216
|
{ eager: true },
|
|
1216
|
-
)
|
|
1217
|
+
)`,d=` /**
|
|
1218
|
+
* Declare HTTP routes for this module.
|
|
1219
|
+
*
|
|
1220
|
+
* The path is prefixed with the global apiPrefix and version
|
|
1221
|
+
* (e.g. /api/v1/${r}). The framework derives the Express
|
|
1222
|
+
* Router from the controller via \`buildRoutes()\` and uses the
|
|
1223
|
+
* same controller for OpenAPI spec generation via SwaggerAdapter.
|
|
1224
|
+
*
|
|
1225
|
+
* Return an **array** to mount multiple route sets under the
|
|
1226
|
+
* same module (e.g. side-by-side v1 + v2 controllers). Each
|
|
1227
|
+
* entry can override the API version with a \`version\` field —
|
|
1228
|
+
* the mount path becomes \`/{apiPrefix}/v{version}{path}\`:
|
|
1229
|
+
*
|
|
1230
|
+
* return [
|
|
1231
|
+
* { path: '/${r}', version: 1, controller: ${t}V1Controller },
|
|
1232
|
+
* { path: '/${r}', version: 2, controller: ${t}V2Controller },
|
|
1233
|
+
* ]
|
|
1234
|
+
*/`;return c===`class`?`${l}
|
|
1235
|
+
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
1236
|
+
${u}
|
|
1217
1237
|
|
|
1218
1238
|
export class ${t}Module implements AppModule {
|
|
1219
1239
|
/**
|
|
1220
1240
|
* Register module dependencies in the DI container.
|
|
1221
1241
|
* Bind repository interface tokens to their implementations here.
|
|
1222
|
-
* Currently wired to ${
|
|
1242
|
+
* Currently wired to ${qt(i)}. To swap implementations, change the factory target.
|
|
1223
1243
|
*/
|
|
1224
1244
|
register(container: Container): void {
|
|
1225
1245
|
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
1226
|
-
container.resolve(${
|
|
1246
|
+
container.resolve(${o}),
|
|
1227
1247
|
)
|
|
1228
1248
|
}
|
|
1229
1249
|
|
|
1230
|
-
|
|
1231
|
-
* Declare HTTP routes for this module.
|
|
1232
|
-
* The path is prefixed with the global apiPrefix and version (e.g. /api/v1/${r}).
|
|
1233
|
-
* Passing 'controller' enables automatic OpenAPI spec generation via SwaggerAdapter.
|
|
1234
|
-
*/
|
|
1250
|
+
${d.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
1235
1251
|
routes(): ModuleRoutes {
|
|
1236
1252
|
return {
|
|
1237
1253
|
path: '/${r}',
|
|
1238
|
-
router: buildRoutes(${t}Controller),
|
|
1239
1254
|
controller: ${t}Controller,
|
|
1240
1255
|
}
|
|
1241
1256
|
}
|
|
1242
1257
|
}
|
|
1243
|
-
|
|
1258
|
+
`:`${l}
|
|
1259
|
+
import { defineModule } from '@forinda/kickjs'
|
|
1260
|
+
${u}
|
|
1261
|
+
|
|
1262
|
+
export const ${t}Module = defineModule({
|
|
1263
|
+
name: '${t}Module',
|
|
1264
|
+
build: () => ({
|
|
1265
|
+
/**
|
|
1266
|
+
* Register module dependencies in the DI container.
|
|
1267
|
+
* Bind repository interface tokens to their implementations here.
|
|
1268
|
+
* Currently wired to ${qt(i)}. To swap implementations, change the factory target.
|
|
1269
|
+
*/
|
|
1270
|
+
register(container) {
|
|
1271
|
+
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
1272
|
+
container.resolve(${o}),
|
|
1273
|
+
)
|
|
1274
|
+
},
|
|
1275
|
+
|
|
1276
|
+
${d}
|
|
1277
|
+
routes() {
|
|
1278
|
+
return {
|
|
1279
|
+
path: '/${r}',
|
|
1280
|
+
controller: ${t}Controller,
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
}),
|
|
1284
|
+
})
|
|
1285
|
+
`}function Zt(e){let{pascal:t,kebab:n,plural:r=``,repo:i,style:a}=e,{repoClass:o,repoFile:s}=Jt(t,n,i),c=Yt(a),l=`/**
|
|
1244
1286
|
* ${t} Module
|
|
1245
1287
|
*
|
|
1246
1288
|
* REST module with a flat folder structure.
|
|
@@ -1250,47 +1292,107 @@ export class ${t}Module implements AppModule {
|
|
|
1250
1292
|
* ${n}.controller.ts — HTTP routes (CRUD)
|
|
1251
1293
|
* ${n}.service.ts — Business logic
|
|
1252
1294
|
* ${n}.repository.ts — Repository interface
|
|
1253
|
-
* ${
|
|
1295
|
+
* ${s}.repository.ts — Repository implementation
|
|
1254
1296
|
* dtos/ — Request/response schemas
|
|
1255
|
-
|
|
1256
|
-
import {
|
|
1257
|
-
import { buildRoutes } from '@forinda/kickjs'
|
|
1258
|
-
import { ${t.toUpperCase()}_REPOSITORY } from './${n}.repository'
|
|
1259
|
-
import { ${a} } from './${o}.repository'
|
|
1297
|
+
*/`,u=`import { ${t.toUpperCase()}_REPOSITORY } from './${n}.repository'
|
|
1298
|
+
import { ${o} } from './${s}.repository'
|
|
1260
1299
|
import { ${t}Controller } from './${n}.controller'
|
|
1261
1300
|
|
|
1262
1301
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
1263
|
-
import.meta.glob(['./**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'], { eager: true })
|
|
1302
|
+
import.meta.glob(['./**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'], { eager: true })`,d=` /**
|
|
1303
|
+
* Declare HTTP routes for this module.
|
|
1304
|
+
*
|
|
1305
|
+
* Pass \`controller\` and the framework derives the Express
|
|
1306
|
+
* Router via \`buildRoutes()\` and uses the same controller for
|
|
1307
|
+
* OpenAPI spec generation through SwaggerAdapter.
|
|
1308
|
+
*
|
|
1309
|
+
* Return an **array** to mount multiple route sets under the
|
|
1310
|
+
* same module (side-by-side v1 + v2 controllers, admin surfaces).
|
|
1311
|
+
* Each entry can override the API version with a \`version\` field:
|
|
1312
|
+
*
|
|
1313
|
+
* return [
|
|
1314
|
+
* { path: '/${r}', version: 1, controller: ${t}V1Controller },
|
|
1315
|
+
* { path: '/${r}', version: 2, controller: ${t}V2Controller },
|
|
1316
|
+
* ]
|
|
1317
|
+
*/`;return c===`class`?`${l}
|
|
1318
|
+
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
1319
|
+
${u}
|
|
1264
1320
|
|
|
1265
1321
|
export class ${t}Module implements AppModule {
|
|
1266
1322
|
register(container: Container): void {
|
|
1267
1323
|
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
1268
|
-
container.resolve(${
|
|
1324
|
+
container.resolve(${o}),
|
|
1269
1325
|
)
|
|
1270
1326
|
}
|
|
1271
1327
|
|
|
1328
|
+
${d.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
1272
1329
|
routes(): ModuleRoutes {
|
|
1273
1330
|
return {
|
|
1274
1331
|
path: '/${r}',
|
|
1275
|
-
router: buildRoutes(${t}Controller),
|
|
1276
1332
|
controller: ${t}Controller,
|
|
1277
1333
|
}
|
|
1278
1334
|
}
|
|
1279
1335
|
}
|
|
1280
|
-
|
|
1281
|
-
import {
|
|
1336
|
+
`:`${l}
|
|
1337
|
+
import { defineModule } from '@forinda/kickjs'
|
|
1338
|
+
${u}
|
|
1339
|
+
|
|
1340
|
+
export const ${t}Module = defineModule({
|
|
1341
|
+
name: '${t}Module',
|
|
1342
|
+
build: () => ({
|
|
1343
|
+
register(container) {
|
|
1344
|
+
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
1345
|
+
container.resolve(${o}),
|
|
1346
|
+
)
|
|
1347
|
+
},
|
|
1348
|
+
|
|
1349
|
+
${d}
|
|
1350
|
+
routes() {
|
|
1351
|
+
return {
|
|
1352
|
+
path: '/${r}',
|
|
1353
|
+
controller: ${t}Controller,
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
}),
|
|
1357
|
+
})
|
|
1358
|
+
`}function Qt(e){let{pascal:t,kebab:n,plural:r=``,style:i}=e,a=Yt(i),o=` /**
|
|
1359
|
+
* Pass \`controller\` and the framework derives the Express
|
|
1360
|
+
* Router via \`buildRoutes()\`. Return an array to mount multiple
|
|
1361
|
+
* route sets — each entry can override the API version with a
|
|
1362
|
+
* \`version\` field:
|
|
1363
|
+
*
|
|
1364
|
+
* return [
|
|
1365
|
+
* { path: '/${r}', version: 1, controller: ${t}V1Controller },
|
|
1366
|
+
* { path: '/${r}', version: 2, controller: ${t}V2Controller },
|
|
1367
|
+
* ]
|
|
1368
|
+
*/`;return a===`class`?`import { type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
1282
1369
|
import { ${t}Controller } from './${n}.controller'
|
|
1283
1370
|
|
|
1284
1371
|
export class ${t}Module implements AppModule {
|
|
1372
|
+
${o.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
1285
1373
|
routes(): ModuleRoutes {
|
|
1286
1374
|
return {
|
|
1287
1375
|
path: '/${r}',
|
|
1288
|
-
router: buildRoutes(${t}Controller),
|
|
1289
1376
|
controller: ${t}Controller,
|
|
1290
1377
|
}
|
|
1291
1378
|
}
|
|
1292
1379
|
}
|
|
1293
|
-
|
|
1380
|
+
`:`import { defineModule } from '@forinda/kickjs'
|
|
1381
|
+
import { ${t}Controller } from './${n}.controller'
|
|
1382
|
+
|
|
1383
|
+
export const ${t}Module = defineModule({
|
|
1384
|
+
name: '${t}Module',
|
|
1385
|
+
build: () => ({
|
|
1386
|
+
${o}
|
|
1387
|
+
routes() {
|
|
1388
|
+
return {
|
|
1389
|
+
path: '/${r}',
|
|
1390
|
+
controller: ${t}Controller,
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
}),
|
|
1394
|
+
})
|
|
1395
|
+
`}function $t(e){let{pascal:t,kebab:n,plural:r=``,pluralPascal:i=``}=e;return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams, type Ctx } from '@forinda/kickjs'
|
|
1294
1396
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
1295
1397
|
import { Create${t}UseCase } from '../application/use-cases/create-${n}.use-case'
|
|
1296
1398
|
import { Get${t}UseCase } from '../application/use-cases/get-${n}.use-case'
|
|
@@ -1353,7 +1455,7 @@ export class ${t}Controller {
|
|
|
1353
1455
|
ctx.noContent()
|
|
1354
1456
|
}
|
|
1355
1457
|
}
|
|
1356
|
-
`}function
|
|
1458
|
+
`}function en(e){let{pascal:t,kebab:n}=e,r=t.charAt(0).toLowerCase()+t.slice(1);return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams, type Ctx } from '@forinda/kickjs'
|
|
1357
1459
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
1358
1460
|
import { ${t}Service } from './${n}.service'
|
|
1359
1461
|
import { create${t}Schema } from './dtos/create-${n}.dto'
|
|
@@ -1408,14 +1510,14 @@ export class ${t}Controller {
|
|
|
1408
1510
|
ctx.noContent()
|
|
1409
1511
|
}
|
|
1410
1512
|
}
|
|
1411
|
-
`}function
|
|
1513
|
+
`}function tn(e){let{pascal:t}=e;return`import type { QueryParamsConfig } from '@forinda/kickjs'
|
|
1412
1514
|
|
|
1413
1515
|
export const ${t.toUpperCase()}_QUERY_CONFIG: QueryParamsConfig = {
|
|
1414
1516
|
filterable: ['name'],
|
|
1415
1517
|
sortable: ['name', 'createdAt'],
|
|
1416
1518
|
searchable: ['name'],
|
|
1417
1519
|
}
|
|
1418
|
-
`}function
|
|
1520
|
+
`}function nn(e){let{pascal:t}=e;return`import { z } from 'zod'
|
|
1419
1521
|
|
|
1420
1522
|
/**
|
|
1421
1523
|
* Create ${t} DTO — Zod schema for validating POST request bodies.
|
|
@@ -1431,20 +1533,20 @@ export const create${t}Schema = z.object({
|
|
|
1431
1533
|
})
|
|
1432
1534
|
|
|
1433
1535
|
export type Create${t}DTO = z.infer<typeof create${t}Schema>
|
|
1434
|
-
`}function
|
|
1536
|
+
`}function rn(e){let{pascal:t}=e;return`import { z } from 'zod'
|
|
1435
1537
|
|
|
1436
1538
|
export const update${t}Schema = z.object({
|
|
1437
1539
|
name: z.string().min(1).max(200).optional(),
|
|
1438
1540
|
})
|
|
1439
1541
|
|
|
1440
1542
|
export type Update${t}DTO = z.infer<typeof update${t}Schema>
|
|
1441
|
-
`}function
|
|
1543
|
+
`}function an(e){let{pascal:t}=e;return`export interface ${t}ResponseDTO {
|
|
1442
1544
|
id: string
|
|
1443
1545
|
name: string
|
|
1444
1546
|
createdAt: string
|
|
1445
1547
|
updatedAt: string
|
|
1446
1548
|
}
|
|
1447
|
-
`}function
|
|
1549
|
+
`}function on(e){let{pascal:t,kebab:n,plural:r=``,pluralPascal:i=``}=e;return[{file:`create-${n}.use-case.ts`,content:`/**
|
|
1448
1550
|
* Create ${t} Use Case
|
|
1449
1551
|
*
|
|
1450
1552
|
* Application layer — orchestrates a single business operation.
|
|
@@ -1522,7 +1624,7 @@ export class Delete${t}UseCase {
|
|
|
1522
1624
|
await this.repo.delete(id)
|
|
1523
1625
|
}
|
|
1524
1626
|
}
|
|
1525
|
-
`}]}function
|
|
1627
|
+
`}]}function sn(e){let{pascal:t,kebab:n,dtoPrefix:r=`../../application/dtos`,tokenScope:i=`app`}=e;return`/**
|
|
1526
1628
|
* ${t} Repository Interface
|
|
1527
1629
|
*
|
|
1528
1630
|
* Defines the contract for data access.
|
|
@@ -1617,7 +1719,7 @@ export class InMemory${t}Repository implements I${t}Repository {
|
|
|
1617
1719
|
this.store.delete(id)
|
|
1618
1720
|
}
|
|
1619
1721
|
}
|
|
1620
|
-
`}function
|
|
1722
|
+
`}function cn(e){let{pascal:t,kebab:n,repoType:r=``,repoPrefix:i=`../../domain/repositories`,dtoPrefix:a=`../../application/dtos`}=e,o=r.charAt(0).toUpperCase()+r.slice(1).replace(/-([a-z])/g,(e,t)=>t.toUpperCase());return`/**
|
|
1621
1723
|
* ${o} ${t} Repository
|
|
1622
1724
|
*
|
|
1623
1725
|
* Stub implementation for a custom '${r}' repository.
|
|
@@ -1686,7 +1788,7 @@ export class ${o}${t}Repository implements I${t}Repository {
|
|
|
1686
1788
|
this.store.delete(id)
|
|
1687
1789
|
}
|
|
1688
1790
|
}
|
|
1689
|
-
`}function
|
|
1791
|
+
`}function ln(e){let{pascal:t,kebab:n}=e;return`/**
|
|
1690
1792
|
* ${t} Domain Service
|
|
1691
1793
|
*
|
|
1692
1794
|
* Domain layer — contains business rules that don't belong to a single entity.
|
|
@@ -1709,7 +1811,7 @@ export class ${t}DomainService {
|
|
|
1709
1811
|
}
|
|
1710
1812
|
}
|
|
1711
1813
|
}
|
|
1712
|
-
`}function
|
|
1814
|
+
`}function un(e){let{pascal:t,kebab:n}=e;return`/**
|
|
1713
1815
|
* ${t} Entity
|
|
1714
1816
|
*
|
|
1715
1817
|
* Domain layer — the core business object.
|
|
@@ -1778,7 +1880,7 @@ export class ${t} {
|
|
|
1778
1880
|
}
|
|
1779
1881
|
}
|
|
1780
1882
|
}
|
|
1781
|
-
`}function
|
|
1883
|
+
`}function dn(e){let{pascal:t}=e;return`/**
|
|
1782
1884
|
* ${t} ID Value Object
|
|
1783
1885
|
*
|
|
1784
1886
|
* Domain layer — wraps a primitive ID with type safety and validation.
|
|
@@ -1812,7 +1914,7 @@ export class ${t}Id {
|
|
|
1812
1914
|
return this.value === other.value
|
|
1813
1915
|
}
|
|
1814
1916
|
}
|
|
1815
|
-
`}function
|
|
1917
|
+
`}function fn(e){let{pascal:t,kebab:n,plural:r=``}=e;return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
1816
1918
|
import { Container } from '@forinda/kickjs'
|
|
1817
1919
|
|
|
1818
1920
|
describe('${t}Controller', () => {
|
|
@@ -1864,7 +1966,7 @@ describe('${t}Controller', () => {
|
|
|
1864
1966
|
})
|
|
1865
1967
|
})
|
|
1866
1968
|
})
|
|
1867
|
-
`}function
|
|
1969
|
+
`}function pn(e){let{pascal:t,kebab:n,plural:r=``,repoPrefix:i=`../infrastructure/repositories/in-memory-${n}.repository`}=e;return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
1868
1970
|
import { InMemory${t}Repository } from '${i}'
|
|
1869
1971
|
|
|
1870
1972
|
describe('InMemory${t}Repository', () => {
|
|
@@ -1926,7 +2028,7 @@ describe('InMemory${t}Repository', () => {
|
|
|
1926
2028
|
expect(found).toBeNull()
|
|
1927
2029
|
})
|
|
1928
2030
|
})
|
|
1929
|
-
`}function
|
|
2031
|
+
`}function mn(e){let{pascal:t,kebab:n}=e;return`import { Service, Inject, HttpException } from '@forinda/kickjs'
|
|
1930
2032
|
import type { ParsedQuery } from '@forinda/kickjs'
|
|
1931
2033
|
import { ${t.toUpperCase()}_REPOSITORY, type I${t}Repository } from './${n}.repository'
|
|
1932
2034
|
import type { ${t}ResponseDTO } from './dtos/${n}-response.dto'
|
|
@@ -1963,14 +2065,14 @@ export class ${t}Service {
|
|
|
1963
2065
|
await this.repo.delete(id)
|
|
1964
2066
|
}
|
|
1965
2067
|
}
|
|
1966
|
-
`}function
|
|
2068
|
+
`}function hn(e){let{pascal:t}=e;return`import type { QueryFieldConfig } from '@forinda/kickjs'
|
|
1967
2069
|
|
|
1968
2070
|
export const ${t.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
1969
2071
|
filterable: ['name'],
|
|
1970
2072
|
sortable: ['name', 'createdAt'],
|
|
1971
2073
|
searchable: ['name'],
|
|
1972
2074
|
}
|
|
1973
|
-
`}function
|
|
2075
|
+
`}function gn(e){let{pascal:t,kebab:n,plural:r=``,repo:i,style:a}=e,o={inmemory:`InMemory${t}Repository`,drizzle:`Drizzle${t}Repository`,prisma:`Prisma${t}Repository`},s={inmemory:`in-memory-${n}`,drizzle:`drizzle-${n}`,prisma:`prisma-${n}`},c=o[i]??o.inmemory,l=s[i]??s.inmemory,u=a??`define`,d=`/**
|
|
1974
2076
|
* ${t} Module — CQRS Pattern
|
|
1975
2077
|
*
|
|
1976
2078
|
* Separates read (queries) and write (commands) operations.
|
|
@@ -1982,11 +2084,8 @@ export const ${t.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
|
1982
2084
|
* queries/ — Read operations (get, list)
|
|
1983
2085
|
* events/ — Domain events + handlers (WS broadcast, queue dispatch)
|
|
1984
2086
|
* dtos/ — Request/response schemas
|
|
1985
|
-
|
|
1986
|
-
import {
|
|
1987
|
-
import { buildRoutes } from '@forinda/kickjs'
|
|
1988
|
-
import { ${t.toUpperCase()}_REPOSITORY } from './${n}.repository'
|
|
1989
|
-
import { ${s} } from './${c}.repository'
|
|
2087
|
+
*/`,f=`import { ${t.toUpperCase()}_REPOSITORY } from './${n}.repository'
|
|
2088
|
+
import { ${c} } from './${l}.repository'
|
|
1990
2089
|
import { ${t}Controller } from './${n}.controller'
|
|
1991
2090
|
|
|
1992
2091
|
// Eagerly load decorated classes
|
|
@@ -1998,24 +2097,59 @@ import.meta.glob(
|
|
|
1998
2097
|
'!./**/*.test.ts',
|
|
1999
2098
|
],
|
|
2000
2099
|
{ eager: true },
|
|
2001
|
-
)
|
|
2100
|
+
)`,p=` /**
|
|
2101
|
+
* Declare HTTP routes. Pass \`controller\` and the framework
|
|
2102
|
+
* derives the Express Router via \`buildRoutes()\` and uses the
|
|
2103
|
+
* same controller for OpenAPI spec generation. Return an array
|
|
2104
|
+
* to mount multiple route sets — each entry can override the API
|
|
2105
|
+
* version with a \`version\` field:
|
|
2106
|
+
*
|
|
2107
|
+
* return [
|
|
2108
|
+
* { path: '/${r}', version: 1, controller: ${t}V1Controller },
|
|
2109
|
+
* { path: '/${r}', version: 2, controller: ${t}V2Controller },
|
|
2110
|
+
* ]
|
|
2111
|
+
*/`;return u===`class`?`${d}
|
|
2112
|
+
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
2113
|
+
${f}
|
|
2002
2114
|
|
|
2003
2115
|
export class ${t}Module implements AppModule {
|
|
2004
2116
|
register(container: Container): void {
|
|
2005
2117
|
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
2006
|
-
container.resolve(${
|
|
2118
|
+
container.resolve(${c}),
|
|
2007
2119
|
)
|
|
2008
2120
|
}
|
|
2009
2121
|
|
|
2122
|
+
${p.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
2010
2123
|
routes(): ModuleRoutes {
|
|
2011
2124
|
return {
|
|
2012
2125
|
path: '/${r}',
|
|
2013
|
-
router: buildRoutes(${t}Controller),
|
|
2014
2126
|
controller: ${t}Controller,
|
|
2015
2127
|
}
|
|
2016
2128
|
}
|
|
2017
2129
|
}
|
|
2018
|
-
|
|
2130
|
+
`:`${d}
|
|
2131
|
+
import { defineModule } from '@forinda/kickjs'
|
|
2132
|
+
${f}
|
|
2133
|
+
|
|
2134
|
+
export const ${t}Module = defineModule({
|
|
2135
|
+
name: '${t}Module',
|
|
2136
|
+
build: () => ({
|
|
2137
|
+
register(container) {
|
|
2138
|
+
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
2139
|
+
container.resolve(${c}),
|
|
2140
|
+
)
|
|
2141
|
+
},
|
|
2142
|
+
|
|
2143
|
+
${p}
|
|
2144
|
+
routes() {
|
|
2145
|
+
return {
|
|
2146
|
+
path: '/${r}',
|
|
2147
|
+
controller: ${t}Controller,
|
|
2148
|
+
}
|
|
2149
|
+
},
|
|
2150
|
+
}),
|
|
2151
|
+
})
|
|
2152
|
+
`}function _n(e){let{pascal:t,kebab:n,plural:r=``,pluralPascal:i=``}=e;return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams, type Ctx } from '@forinda/kickjs'
|
|
2019
2153
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
2020
2154
|
import { Create${t}Command } from './commands/create-${n}.command'
|
|
2021
2155
|
import { Update${t}Command } from './commands/update-${n}.command'
|
|
@@ -2078,7 +2212,7 @@ export class ${t}Controller {
|
|
|
2078
2212
|
ctx.noContent()
|
|
2079
2213
|
}
|
|
2080
2214
|
}
|
|
2081
|
-
`}function
|
|
2215
|
+
`}function vn(e){let{pascal:t,kebab:n}=e;return[{file:`create-${n}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs'
|
|
2082
2216
|
import { ${t.toUpperCase()}_REPOSITORY, type I${t}Repository } from '../${n}.repository'
|
|
2083
2217
|
import type { Create${t}DTO } from '../dtos/create-${n}.dto'
|
|
2084
2218
|
import type { ${t}ResponseDTO } from '../dtos/${n}-response.dto'
|
|
@@ -2132,7 +2266,7 @@ export class Delete${t}Command {
|
|
|
2132
2266
|
this.events.emit('${n}.deleted', { id })
|
|
2133
2267
|
}
|
|
2134
2268
|
}
|
|
2135
|
-
`}]}function
|
|
2269
|
+
`}]}function yn(e){let{pascal:t,kebab:n,plural:r=``,pluralPascal:i=``}=e;return[{file:`get-${n}.query.ts`,content:`import { Service, Inject } from '@forinda/kickjs'
|
|
2136
2270
|
import { ${t.toUpperCase()}_REPOSITORY, type I${t}Repository } from '../${n}.repository'
|
|
2137
2271
|
import type { ${t}ResponseDTO } from '../dtos/${n}-response.dto'
|
|
2138
2272
|
|
|
@@ -2160,7 +2294,7 @@ export class List${i}Query {
|
|
|
2160
2294
|
return this.repo.findPaginated(parsed)
|
|
2161
2295
|
}
|
|
2162
2296
|
}
|
|
2163
|
-
`}]}function
|
|
2297
|
+
`}]}function bn(e){let{pascal:t,kebab:n}=e;return[{file:`${n}.events.ts`,content:`import { Service } from '@forinda/kickjs'
|
|
2164
2298
|
import { EventEmitter } from 'node:events'
|
|
2165
2299
|
import type { ${t}ResponseDTO } from '../dtos/${n}-response.dto'
|
|
2166
2300
|
|
|
@@ -2246,7 +2380,7 @@ export class On${t}ChangeHandler {
|
|
|
2246
2380
|
})
|
|
2247
2381
|
}
|
|
2248
2382
|
}
|
|
2249
|
-
`}]}function
|
|
2383
|
+
`}]}function xn(e){let{pascal:t,kebab:n,repoPrefix:r=`../../domain/repositories`,dtoPrefix:i=`../../application/dtos`}=e;return`/**
|
|
2250
2384
|
* Drizzle ${t} Repository
|
|
2251
2385
|
*
|
|
2252
2386
|
* Implements the repository interface using Drizzle ORM.
|
|
@@ -2328,7 +2462,7 @@ export class Drizzle${t}Repository implements I${t}Repository {
|
|
|
2328
2462
|
throw new Error('Drizzle ${t} repository not yet implemented')
|
|
2329
2463
|
}
|
|
2330
2464
|
}
|
|
2331
|
-
`}function
|
|
2465
|
+
`}function Sn(e){let{pascal:t,kebab:n}=e;return`import type { DrizzleQueryParamsConfig } from '@forinda/kickjs-drizzle'
|
|
2332
2466
|
// TODO: Import your schema table and reference actual columns for type safety
|
|
2333
2467
|
// import { ${n}s } from '@/db/schema'
|
|
2334
2468
|
|
|
@@ -2346,7 +2480,7 @@ export const ${t.toUpperCase()}_QUERY_CONFIG: DrizzleQueryParamsConfig = {
|
|
|
2346
2480
|
// ${n}s.name,
|
|
2347
2481
|
],
|
|
2348
2482
|
}
|
|
2349
|
-
`}function
|
|
2483
|
+
`}function Cn(e){let{pascal:t,kebab:n,repoPrefix:r=`../../domain/repositories`,dtoPrefix:i=`../../application/dtos`}=e,a=n.replace(/-([a-z])/g,(e,t)=>t.toUpperCase());return`/**
|
|
2350
2484
|
* Prisma ${t} Repository
|
|
2351
2485
|
*
|
|
2352
2486
|
* Implements the repository interface using Prisma Client.
|
|
@@ -2404,7 +2538,7 @@ export class Prisma${t}Repository implements I${t}Repository {
|
|
|
2404
2538
|
await this.prisma.${a}.deleteMany({ where: { id } })
|
|
2405
2539
|
}
|
|
2406
2540
|
}
|
|
2407
|
-
`}async function
|
|
2541
|
+
`}async function wn(e){let{pascal:t,kebab:n,plural:r,style:i,write:a}=e;await a(`${n}.module.ts`,Qt({pascal:t,kebab:n,plural:r,style:i})),await a(`${n}.controller.ts`,`import { Controller, Get, type Ctx } from '@forinda/kickjs'
|
|
2408
2542
|
|
|
2409
2543
|
// \`Ctx<KickRoutes.${t}Controller['<method>']>\` is generated by
|
|
2410
2544
|
// \`kick typegen\` (auto-run on \`kick dev\`).
|
|
@@ -2416,14 +2550,14 @@ export class ${t}Controller {
|
|
|
2416
2550
|
ctx.json({ message: '${t} list' })
|
|
2417
2551
|
}
|
|
2418
2552
|
}
|
|
2419
|
-
`)}async function
|
|
2420
|
-
import { ${t}Module } from '${
|
|
2553
|
+
`)}async function Tn(e){let{pascal:t,kebab:n,plural:r,pluralPascal:i,repo:a,noTests:o,prismaClientPath:s,tokenScope:c,style:l,write:u}=e;await u(`${n}.module.ts`,Zt({pascal:t,kebab:n,plural:r,repo:a,style:l})),await u(`${n}.constants.ts`,hn({pascal:t,kebab:n})),await u(`${n}.controller.ts`,en({pascal:t,kebab:n,plural:r,pluralPascal:i})),await u(`${n}.service.ts`,mn({pascal:t,kebab:n})),await u(`dtos/create-${n}.dto.ts`,nn({pascal:t,kebab:n})),await u(`dtos/update-${n}.dto.ts`,rn({pascal:t,kebab:n})),await u(`dtos/${n}-response.dto.ts`,an({pascal:t,kebab:n})),await u(`${n}.repository.ts`,sn({pascal:t,kebab:n,dtoPrefix:`./dtos`,tokenScope:c}));let d={inmemory:`in-memory-${n}`,drizzle:`drizzle-${n}`,prisma:`prisma-${n}`},f={inmemory:()=>H({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`}),drizzle:()=>xn({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`}),prisma:()=>Cn({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`,prismaClientPath:s})},p=d[a]??`${B(a)}-${n}`,m=f[a]??(()=>cn({pascal:t,kebab:n,repoType:a,repoPrefix:`.`,dtoPrefix:`./dtos`}));await u(`${p}.repository.ts`,m()),o||(a!==`inmemory`&&await u(`in-memory-${n}.repository.ts`,H({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`})),await u(`__tests__/${n}.controller.test.ts`,fn({pascal:t,kebab:n,plural:r})),await u(`__tests__/${n}.repository.test.ts`,pn({pascal:t,kebab:n,plural:r,repoPrefix:`../${d.inmemory??`in-memory-${n}`}.repository`})))}async function En(e){let{pascal:t,kebab:n,plural:r,pluralPascal:i,repo:a,noTests:o,prismaClientPath:s,tokenScope:c,style:l,write:u}=e;await u(`${n}.module.ts`,gn({pascal:t,kebab:n,plural:r,repo:a,style:l})),await u(`${n}.constants.ts`,hn({pascal:t,kebab:n})),await u(`${n}.controller.ts`,_n({pascal:t,kebab:n,plural:r,pluralPascal:i})),await u(`dtos/create-${n}.dto.ts`,nn({pascal:t,kebab:n})),await u(`dtos/update-${n}.dto.ts`,rn({pascal:t,kebab:n})),await u(`dtos/${n}-response.dto.ts`,an({pascal:t,kebab:n}));let d=vn({pascal:t,kebab:n});for(let e of d)await u(`commands/${e.file}`,e.content);let f=yn({pascal:t,kebab:n,plural:r,pluralPascal:i});for(let e of f)await u(`queries/${e.file}`,e.content);let p=bn({pascal:t,kebab:n});for(let e of p)await u(`events/${e.file}`,e.content);await u(`${n}.repository.ts`,sn({pascal:t,kebab:n,dtoPrefix:`./dtos`,tokenScope:c}));let m={inmemory:`in-memory-${n}`,drizzle:`drizzle-${n}`,prisma:`prisma-${n}`},h={inmemory:()=>H({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`}),drizzle:()=>xn({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`}),prisma:()=>Cn({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`,prismaClientPath:s})},g=m[a]??`${B(a)}-${n}`,_=h[a]??(()=>cn({pascal:t,kebab:n,repoType:a,repoPrefix:`.`,dtoPrefix:`./dtos`}));await u(`${g}.repository.ts`,_()),o||(a!==`inmemory`&&await u(`in-memory-${n}.repository.ts`,H({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`})),await u(`__tests__/${n}.controller.test.ts`,fn({pascal:t,kebab:n,plural:r})),await u(`__tests__/${n}.repository.test.ts`,pn({pascal:t,kebab:n,plural:r,repoPrefix:`../${m.inmemory??`in-memory-${n}`}.repository`})))}async function Dn(e){let{pascal:t,kebab:n,plural:r,pluralPascal:i,repo:a,noEntity:o,noTests:s,prismaClientPath:c,tokenScope:l,style:u,write:d}=e;await d(`${n}.module.ts`,Xt({pascal:t,kebab:n,plural:r,repo:a,style:u})),await d(`constants.ts`,a===`drizzle`?Sn({pascal:t,kebab:n}):tn({pascal:t,kebab:n})),await d(`presentation/${n}.controller.ts`,$t({pascal:t,kebab:n,plural:r,pluralPascal:i})),await d(`application/dtos/create-${n}.dto.ts`,nn({pascal:t,kebab:n})),await d(`application/dtos/update-${n}.dto.ts`,rn({pascal:t,kebab:n})),await d(`application/dtos/${n}-response.dto.ts`,an({pascal:t,kebab:n}));let f=on({pascal:t,kebab:n,plural:r,pluralPascal:i});for(let e of f)await d(`application/use-cases/${e.file}`,e.content);await d(`domain/repositories/${n}.repository.ts`,sn({pascal:t,kebab:n,tokenScope:l})),await d(`domain/services/${n}-domain.service.ts`,ln({pascal:t,kebab:n}));let p={inmemory:`in-memory-${n}`,drizzle:`drizzle-${n}`,prisma:`prisma-${n}`},m={inmemory:()=>H({pascal:t,kebab:n}),drizzle:()=>xn({pascal:t,kebab:n}),prisma:()=>Cn({pascal:t,kebab:n,prismaClientPath:c})},h=p[a]??`${B(a)}-${n}`,g=m[a]??(()=>cn({pascal:t,kebab:n,repoType:a}));await d(`infrastructure/repositories/${h}.repository.ts`,g()),o||(await d(`domain/entities/${n}.entity.ts`,un({pascal:t,kebab:n})),await d(`domain/value-objects/${n}-id.vo.ts`,dn({pascal:t,kebab:n}))),s||(a!==`inmemory`&&await d(`infrastructure/repositories/in-memory-${n}.repository.ts`,H({pascal:t,kebab:n})),await d(`__tests__/${n}.controller.test.ts`,fn({pascal:t,kebab:n,plural:r})),await d(`__tests__/${n}.repository.test.ts`,pn({pascal:t,kebab:n,plural:r})))}function On(e){return e?typeof e==`string`?e:e.name:`inmemory`}async function kn(e){let{name:t,modulesDir:n,noEntity:r,noTests:i,repo:a=`inmemory`,force:o,dryRun:s}=e,c=e.pluralize!==!1,l=e.pattern??`ddd`;e.minimal&&(l=`minimal`);let u=B(t),d=R(t),f=c?V(u):u,p=c?At(d):d,m=x(n,f),h=[],g=o??!1,_={kebab:u,pascal:d,plural:f,pluralPascal:p,moduleDir:m,repo:a,noEntity:r??!1,noTests:i??!1,prismaClientPath:e.prismaClientPath??`@prisma/client`,tokenScope:e.tokenScope??`app`,style:e.style??`define`,write:async(e,t)=>{let n=x(m,e);if(s){h.push(n);return}if(!g&&await Re(n)&&!await F({message:`File exists: ${k.dim(e)}. Overwrite?`,initialValue:!1})){I.warn(`Skipped: ${e}`);return}await j(n,t),h.push(n)},files:h};switch(l){case`minimal`:await wn(_);break;case`rest`:await Tn(_);break;case`cqrs`:await En(_);break;default:await Dn(_);break}return s||await An(n,d,f,u,_.style),h}async function An(e,t,n,r,i=`define`){let a=x(e,`index.ts`),o=await Re(a),s=`./${n}/${r}.module`,c=i===`class`?`${t}Module`:`${t}Module()`;if(!o){await j(a,`import type { AppModuleEntry } from '@forinda/kickjs'
|
|
2554
|
+
import { ${t}Module } from '${s}'
|
|
2421
2555
|
|
|
2422
|
-
export const modules:
|
|
2423
|
-
`);return}let
|
|
2424
|
-
`,e);
|
|
2425
|
-
`+
|
|
2426
|
-
`+
|
|
2556
|
+
export const modules: AppModuleEntry[] = [${c}]
|
|
2557
|
+
`);return}let l=await E(a,`utf-8`),u=`import { ${t}Module } from '${s}'`;if(!l.includes(`${t}Module`)){let e=l.lastIndexOf(`import `);if(e!==-1){let t=l.indexOf(`
|
|
2558
|
+
`,e);l=l.slice(0,t+1)+u+`
|
|
2559
|
+
`+l.slice(t+1)}else l=u+`
|
|
2560
|
+
`+l;l=l.replace(/(=\s*\[)([\s\S]*?)(])/,(e,t,n,r)=>{let i=n.trim();if(!i)return`${t}${c}${r}`;let a=i.endsWith(`,`)?``:`,`;return`${t}${n.trimEnd()}${a} ${c}${r}`})}await D(a,l,`utf-8`)}async function jn(e){let{name:t,outDir:n}=e,r=B(t),i=R(t),a=[],o=x(n,`${r}.adapter.ts`);return await j(o,`import {
|
|
2427
2561
|
defineAdapter,
|
|
2428
2562
|
type AdapterContext,
|
|
2429
2563
|
type AdapterMiddleware,
|
|
@@ -2592,10 +2726,10 @@ export const ${i}Adapter = defineAdapter<${i}AdapterConfig>({
|
|
|
2592
2726
|
}
|
|
2593
2727
|
},
|
|
2594
2728
|
})
|
|
2595
|
-
`),a.push(o),a}async function
|
|
2729
|
+
`),a.push(o),a}async function Mn(e){let{name:t,outDir:n}=e,r=B(t),i=R(t),a=[],o=x(n,`${r}.plugin.ts`);return await j(o,`import {
|
|
2596
2730
|
definePlugin,
|
|
2597
2731
|
type AppAdapter,
|
|
2598
|
-
type
|
|
2732
|
+
type AppModuleEntry,
|
|
2599
2733
|
type Container,
|
|
2600
2734
|
type ContributorRegistrations,
|
|
2601
2735
|
} from '@forinda/kickjs'
|
|
@@ -2661,13 +2795,17 @@ export const ${i}Plugin = definePlugin<${i}PluginConfig>({
|
|
|
2661
2795
|
},
|
|
2662
2796
|
|
|
2663
2797
|
/**
|
|
2664
|
-
* Return
|
|
2665
|
-
*
|
|
2666
|
-
*
|
|
2798
|
+
* Return modules this plugin contributes to the app. These load
|
|
2799
|
+
* before user modules, so plugin controllers and services are
|
|
2800
|
+
* available for user code to \`@Autowired\`.
|
|
2801
|
+
*
|
|
2802
|
+
* Accepts both \`defineModule\`-style instances (call the factory:
|
|
2803
|
+
* \`ExampleModule()\`) and legacy \`class … implements AppModule\`
|
|
2804
|
+
* constructors.
|
|
2667
2805
|
*/
|
|
2668
|
-
modules():
|
|
2806
|
+
modules(): AppModuleEntry[] {
|
|
2669
2807
|
return [
|
|
2670
|
-
// ExampleModule,
|
|
2808
|
+
// ExampleModule(),
|
|
2671
2809
|
]
|
|
2672
2810
|
},
|
|
2673
2811
|
|
|
@@ -2732,7 +2870,7 @@ export const ${i}Plugin = definePlugin<${i}PluginConfig>({
|
|
|
2732
2870
|
},
|
|
2733
2871
|
}),
|
|
2734
2872
|
})
|
|
2735
|
-
`),a.push(o),a}const
|
|
2873
|
+
`),a.push(o),a}const Nn={controller:`presentation`,service:`domain/services`,dto:`application/dtos`,guard:`presentation/guards`,middleware:`middleware`},Pn={controller:``,service:``,dto:`dtos`,guard:`guards`,middleware:`middleware`},Fn={controller:``,service:``,dto:`dtos`,guard:`guards`,middleware:`middleware`,command:`commands`,query:`queries`,event:`events`};function U(e){let{type:t,outDir:n,moduleName:r,modulesDir:i=`src/modules`,defaultDir:a,pattern:o=`ddd`,shouldPluralize:s=!0}=e;if(n)return C(n);if(r){let e=o===`ddd`?Nn:o===`cqrs`?Fn:Pn,n=B(r),a=s?V(n):n,c=e[t]??``,l=x(i,a);return C(c?x(l,c):l)}return C(a)}async function In(e){let{name:t,moduleName:n,modulesDir:r,pattern:i}=e,a=U({type:`middleware`,outDir:e.outDir,moduleName:n,modulesDir:r,defaultDir:`src/middleware`,pattern:i,shouldPluralize:e.pluralize??!0}),o=B(t),s=z(t),c=[],l=x(a,`${o}.middleware.ts`);return await j(l,`import type { Request, Response, NextFunction } from 'express'
|
|
2736
2874
|
|
|
2737
2875
|
export interface ${R(t)}Options {
|
|
2738
2876
|
// Add configuration options here
|
|
@@ -2756,7 +2894,7 @@ export function ${s}(options: ${R(t)}Options = {}) {
|
|
|
2756
2894
|
next()
|
|
2757
2895
|
}
|
|
2758
2896
|
}
|
|
2759
|
-
`),c.push(l),c}async function
|
|
2897
|
+
`),c.push(l),c}async function Ln(e){let{name:t,moduleName:n,modulesDir:r,pattern:i}=e,a=U({type:`guard`,outDir:e.outDir,moduleName:n,modulesDir:r,defaultDir:`src/guards`,pattern:i,shouldPluralize:e.pluralize??!0}),o=B(t),s=z(t),c=R(t),l=[],u=x(a,`${o}.guard.ts`);return await j(u,`import { Container, HttpException } from '@forinda/kickjs'
|
|
2760
2898
|
import type { RequestContext } from '@forinda/kickjs'
|
|
2761
2899
|
|
|
2762
2900
|
/**
|
|
@@ -2792,7 +2930,7 @@ export async function ${s}Guard(ctx: RequestContext, next: () => void): Promise<
|
|
|
2792
2930
|
ctx.res.status(401).json({ message: 'Invalid or expired token' })
|
|
2793
2931
|
}
|
|
2794
2932
|
}
|
|
2795
|
-
`),l.push(u),l}async function
|
|
2933
|
+
`),l.push(u),l}async function Rn(e){let{name:t,moduleName:n,modulesDir:r,pattern:i}=e,a=U({type:`service`,outDir:e.outDir,moduleName:n,modulesDir:r,defaultDir:`src/services`,pattern:i,shouldPluralize:e.pluralize??!0}),o=B(t),s=R(t),c=[],l=x(a,`${o}.service.ts`);return await j(l,`import { Service } from '@forinda/kickjs'
|
|
2796
2934
|
|
|
2797
2935
|
@Service()
|
|
2798
2936
|
export class ${s}Service {
|
|
@@ -2801,7 +2939,7 @@ export class ${s}Service {
|
|
|
2801
2939
|
// @Inject(MY_REPO) private readonly repo: IMyRepository,
|
|
2802
2940
|
// ) {}
|
|
2803
2941
|
}
|
|
2804
|
-
`),c.push(l),c}async function
|
|
2942
|
+
`),c.push(l),c}async function zn(e){let{name:t,moduleName:n,modulesDir:r,pattern:i}=e,a=U({type:`controller`,outDir:e.outDir,moduleName:n,modulesDir:r,defaultDir:`src/controllers`,pattern:i,shouldPluralize:e.pluralize??!0}),o=B(t),s=R(t),c=[],l=x(a,`${o}.controller.ts`);return await j(l,`import { Controller, Get, Post, type Ctx } from '@forinda/kickjs'
|
|
2805
2943
|
|
|
2806
2944
|
// \`Ctx<KickRoutes.${s}Controller['<method>']>\` is generated by
|
|
2807
2945
|
// \`kick typegen\` (auto-run on \`kick dev\`). After the first run, your IDE
|
|
@@ -2822,7 +2960,7 @@ export class ${s}Controller {
|
|
|
2822
2960
|
ctx.created({ message: '${s} created', data: ctx.body })
|
|
2823
2961
|
}
|
|
2824
2962
|
}
|
|
2825
|
-
`),c.push(l),c}async function
|
|
2963
|
+
`),c.push(l),c}async function Bn(e){let{name:t,moduleName:n,modulesDir:r,pattern:i}=e,a=U({type:`dto`,outDir:e.outDir,moduleName:n,modulesDir:r,defaultDir:`src/dtos`,pattern:i,shouldPluralize:e.pluralize??!0}),o=B(t),s=R(t),c=z(t),l=[],u=x(a,`${o}.dto.ts`);return await j(u,`import { z } from 'zod'
|
|
2826
2964
|
|
|
2827
2965
|
export const ${c}Schema = z.object({
|
|
2828
2966
|
// Define your schema fields here
|
|
@@ -2830,8 +2968,8 @@ export const ${c}Schema = z.object({
|
|
|
2830
2968
|
})
|
|
2831
2969
|
|
|
2832
2970
|
export type ${s}DTO = z.infer<typeof ${c}Schema>
|
|
2833
|
-
`),l.push(u),l}async function
|
|
2834
|
-
Skipped — existing kick.config.ts preserved.`),[]):(await
|
|
2971
|
+
`),l.push(u),l}async function Vn(e){let t=x(e.outDir,`kick.config.ts`),n=e.modulesDir??`src/modules`,r=e.defaultRepo??`inmemory`;return h(t)&&!e.force&&!await F({message:`kick.config.ts already exists. Overwrite?`,initialValue:!1})?(console.log(`
|
|
2972
|
+
Skipped — existing kick.config.ts preserved.`),[]):(await j(t,`import { defineConfig } from '@forinda/kickjs-cli'
|
|
2835
2973
|
|
|
2836
2974
|
export default defineConfig({
|
|
2837
2975
|
modules: {
|
|
@@ -2868,7 +3006,18 @@ export default defineConfig({
|
|
|
2868
3006
|
},
|
|
2869
3007
|
],
|
|
2870
3008
|
})
|
|
2871
|
-
`),[t])}const
|
|
3009
|
+
`),[t])}const Hn=new Set([`rest`,`ddd`,`cqrs`,`minimal`]);function Un(e,t){if(t)return t;try{let t=JSON.parse(_(x(e,`package.json`),`utf-8`));if(t.name)return t.name.replace(/^@[^/]+\//,``)}catch{}return e.split(`/`).findLast(Boolean)??`app`}function Wn(e,t){if(t)return t;try{let t=JSON.parse(_(x(e,`package.json`),`utf-8`));if(t.packageManager)return t.packageManager.split(`@`)[0]}catch{}return`pnpm`}async function Gn(e,t){if(t)return t;try{let t=(await r(e))?.pattern;if(t&&Hn.has(t))return t}catch{}return`ddd`}async function Kn(e){let t=e.only??`all`,n=Un(e.outDir,e.name),r=Wn(e.outDir,e.pm),i=await Gn(e.outDir,e.template),a=t===`agents`||t===`both`||t===`all`,o=t===`claude`||t===`both`||t===`all`,s=t===`skills`||t===`all`,c=[];a&&c.push({file:x(e.outDir,`AGENTS.md`),render:()=>at(n,i,r)}),o&&c.push({file:x(e.outDir,`CLAUDE.md`),render:()=>it(n,i,r)}),s&&c.push({file:x(e.outDir,`kickjs-skills.md`),render:()=>ot(n,i,r)});let l=[];for(let{file:t,render:n}of c){if(h(t)&&!e.force&&!await F({message:`${t.replace(e.outDir+`/`,``)} already exists. Overwrite?`,initialValue:!1})){console.log(` Skipped — existing ${t.replace(e.outDir+`/`,``)} preserved.`);continue}await j(t,n()),l.push(t)}return l}function qn(e,t){if(e[t]!==`{`)return-1;let n=1;for(let r=t+1;r<e.length;r++){let t=e[r];if(t===`{`)n++;else if(t===`}`&&(n--,n===0))return r}return-1}function W(e,t){let n=t.exec(e);if(!n)return null;let r=n.index+n[0].length-1,i=qn(e,r);return i===-1?null:e.slice(r+1,i)}function G(e,t,n){let r=` `.repeat(n);return e.split(`
|
|
3010
|
+
`).map(e=>{if(e.trim()===``)return e;let n=RegExp(`^ {0,${t}}`);return r+e.replace(n,``)}).join(`
|
|
3011
|
+
`)}function Jn(e){return e.replaceAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*'@forinda\/kickjs'/g,(e,t)=>{let n=t.split(`,`).map(e=>e.trim()).filter(e=>e&&e!==`Container`&&e!==`type Container`&&e!==`type AppModule`&&e!==`AppModule`&&e!==`type ModuleRoutes`&&e!==`ModuleRoutes`);return n.includes(`defineModule`)||n.push(`defineModule`),`import { ${n.join(`, `)} } from '@forinda/kickjs'`})}function Yn(e,t){return e.replaceAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*'@forinda\/kickjs'/g,(e,n)=>{let r=n.split(`,`).map(e=>e.trim()).filter(e=>e&&e!==`defineModule`);return t.container&&!r.includes(`Container`)&&r.push(`Container`),t.appModule&&!r.some(e=>e===`AppModule`||e===`type AppModule`)&&r.push(`type AppModule`),t.moduleRoutes&&!r.some(e=>e===`ModuleRoutes`||e===`type ModuleRoutes`)&&r.push(`type ModuleRoutes`),t.contributorRegistrations&&!r.some(e=>e===`ContributorRegistrations`||e===`type ContributorRegistrations`)&&r.push(`type ContributorRegistrations`),`import { ${r.join(`, `)} } from '@forinda/kickjs'`})}function Xn(e){if(/\bdefineModule\s*\(/.test(e))return{migrated:null,reason:`already in target form`};let t=[...e.matchAll(/export\s+class\s+(\w+Module)\s+implements\s+AppModule\s*\{/g)];if(t.length===0)return{migrated:null,reason:`no class form detected`};if(t.length>1)return{migrated:null,reason:`multiple module classes in one file — migrate manually`};let n=t[0],r=n[1],i=n.index+n[0].length-1,a=qn(e,i);if(a===-1)return{migrated:null,reason:`unbalanced class braces`};let o=e.slice(i+1,a),s=e.slice(0,n.index),c=e.slice(a+1),l=W(o,/register\s*\(([^)]*)\)\s*:\s*void\s*\{/),u=W(o,/contributors\s*\(\s*\)\s*:\s*ContributorRegistrations\s*\{/),d=W(o,/routes\s*\(\s*\)\s*:\s*[A-Za-z|[\]\s]+\{/);if(!d)return{migrated:null,reason:`routes() method missing or signature unrecognized`};let f=Jn(s),p=``;return l&&(p+=` register(container) {${G(l,4,6)} },\n\n`),u&&(p+=` contributors() {${G(u,4,6)} },\n\n`),p+=` routes() {${G(d,4,6)} },`,{migrated:`${f}${`export const ${r} = defineModule({
|
|
3012
|
+
name: '${r}',
|
|
3013
|
+
build: () => ({
|
|
3014
|
+
${p}
|
|
3015
|
+
}),
|
|
3016
|
+
})`}${c}`}}function Zn(e){if(/export\s+class\s+\w+Module\s+implements\s+AppModule\s*\{/.test(e))return{migrated:null,reason:`already in target form`};let t=[...e.matchAll(/export\s+const\s+(\w+Module)\s*=\s*defineModule\s*\(\s*\{/g)];if(t.length===0)return{migrated:null,reason:`no defineModule form detected`};if(t.length>1)return{migrated:null,reason:`multiple defineModule blocks in one file — migrate manually`};let n=t[0],r=n[1],i=n.index+n[0].length-1,a=qn(e,i);if(a===-1)return{migrated:null,reason:`unbalanced defineModule braces`};let o=e.indexOf(`)`,a);if(o===-1)return{migrated:null,reason:`unbalanced defineModule call parens`};let s=e.slice(i+1,a),c=e.slice(0,n.index),l=o+1;for(;l<e.length&&(e[l]===`
|
|
3017
|
+
`||e[l]===`\r`);)l++;let u=e.slice(l),d=/build\s*:\s*\([^)]*\)\s*=>\s*\(\s*\{/g.exec(s);if(!d)return{migrated:null,reason:`build: () => ({...}) not found in defineModule`};let f=d.index+d[0].length-1,p=qn(s,f);if(p===-1)return{migrated:null,reason:`unbalanced build() braces`};let m=s.slice(f+1,p),h=W(m,/register\s*\(([^)]*)\)\s*\{/),g=W(m,/contributors\s*\(\s*\)\s*\{/),_=W(m,/routes\s*\(\s*\)\s*\{/);if(!_)return{migrated:null,reason:`routes() method missing inside build()`};let ee=Yn(c,{container:h!==null,appModule:!0,moduleRoutes:!0,contributorRegistrations:g!==null}),v=``;return h!==null&&(v+=` register(container: Container): void {${G(h,6,4)} }\n\n`),g!==null&&(v+=` contributors(): ContributorRegistrations {${G(g,6,4)} }\n\n`),v+=` routes(): ModuleRoutes {${G(_,6,4)} }`,{migrated:`${ee}${`export class ${r} implements AppModule {
|
|
3018
|
+
${v}
|
|
3019
|
+
}
|
|
3020
|
+
`}${u}`}}function Qn(e,t){return t===`class`?Zn(e):Xn(e)}function $n(e,t){let n=e,r=!1;if(t===`define`){/\bAppModuleClass\b/.test(n)&&(n=n.replaceAll(/\bAppModuleClass\b/g,`AppModuleEntry`),r=!0);let e=/(=\s*\[)([\s\S]*?)(])/,t=e.exec(n);if(t){let i=t[1],a=t[3],o=t[2],s=o.replaceAll(/(\b\w+Module)(?![(.])/g,`$1()`);s!==o&&(n=n.replace(e,`${i}${s}${a}`),r=!0)}}else{/\bAppModuleEntry\b/.test(n)&&(n=n.replaceAll(/\bAppModuleEntry\b/g,`AppModuleClass`),r=!0);let e=/(=\s*\[)([\s\S]*?)(])/,t=e.exec(n);if(t){let i=t[1],a=t[3],o=t[2],s=o.replaceAll(/(\b\w+Module)\s*\(\s*\)/g,`$1`);s!==o&&(n=n.replace(e,`${i}${s}${a}`),r=!0)}}return r?{migrated:n}:{migrated:null,reason:`no changes needed`}}async function er(e){let t=[];return await n(C(e),0),t;async function n(e,r){let i;try{i=await me(e)}catch{return}for(let a of i){if(a===`node_modules`||a===`dist`||a===`.kickjs`)continue;let i=x(e,a),o;try{o=await ge(i)}catch{continue}o.isDirectory()?await n(i,r+1):(a.endsWith(`.module.ts`)||a===`index.ts`&&r===1)&&t.push(i)}}}async function tr(e,t){let n=0;return await r(e,t),n;async function r(e,t){let i;try{i=await me(e)}catch{return}await pe(t,{recursive:!0});for(let a of i){if(a===`node_modules`||a===`dist`||a===`.kickjs`)continue;let i=x(e,a),o=x(t,a),s;try{s=await ge(i)}catch{continue}s.isDirectory()?await r(i,o):(await fe(i,o),n++)}}}function nr(e){return x(e,`.kickjs`,`codemod-backups`,`${new Date().toISOString().replaceAll(/[:.]/g,`-`)}-modules`)}async function rr(e,t){let{dryRun:n=!1,cwd:r=process.cwd(),target:i}=t,a=t.backup??!n,o=await er(e),s=await E(x(e,`index.ts`),`utf-8`).then(()=>!0,()=>!1),c=null;a&&(o.length>0||s)&&(c=nr(r),await tr(e,c));let l=[];for(let e of o){let t=Qn(await E(e,`utf-8`),i);if(t.migrated==null){l.push({path:e,status:`skipped`,reason:t.reason});continue}n||await D(e,t.migrated,`utf-8`),l.push({path:e,status:`migrated`})}let u=x(e,`index.ts`),d=null;try{d=await E(u,`utf-8`)}catch{return{target:i,files:l,indexStatus:`not-found`,indexPath:u,backupDir:c}}let f=$n(d,i);return f.migrated==null?{target:i,files:l,indexStatus:`skipped`,indexPath:u,indexReason:f.reason,backupDir:c}:(n||await D(u,f.migrated,`utf-8`),{target:i,files:l,indexStatus:`migrated`,indexPath:u,backupDir:c})}async function ir(e,t){let n=await er(e),r=[],i=t===`define`?/export\s+class\s+\w+Module\s+implements\s+AppModule\s*\{/:/export\s+const\s+\w+Module\s*=\s*defineModule\s*\(/;for(let e of n){let t=await E(e,`utf-8`);i.test(t)&&r.push(e)}return r}async function ar(e={}){let t=e.strategy??`jwt`,n=e.outDir??`src/modules/auth`,r=x(n,`dto`),i=[],a=x(n,`auth.module.ts`);await j(a,`import { Module } from '@forinda/kickjs'
|
|
2872
3021
|
import { AuthController } from './auth.controller'
|
|
2873
3022
|
import { AuthService } from './auth.service'
|
|
2874
3023
|
|
|
@@ -2877,7 +3026,7 @@ import { AuthService } from './auth.service'
|
|
|
2877
3026
|
services: [AuthService],
|
|
2878
3027
|
})
|
|
2879
3028
|
export class AuthModule {}
|
|
2880
|
-
`),i.push(a);let o=
|
|
3029
|
+
`),i.push(a);let o=x(n,`auth.controller.ts`);await j(o,t===`jwt`?or():cr()),i.push(o);let s=x(n,`auth.service.ts`);await j(s,t===`jwt`?sr():lr()),i.push(s);let c=x(r,`register.dto.ts`);await j(c,`import { z } from 'zod'
|
|
2881
3030
|
|
|
2882
3031
|
export const RegisterDto = z.object({
|
|
2883
3032
|
email: z.string().email(),
|
|
@@ -2886,7 +3035,7 @@ export const RegisterDto = z.object({
|
|
|
2886
3035
|
})
|
|
2887
3036
|
|
|
2888
3037
|
export type RegisterInput = z.infer<typeof RegisterDto>
|
|
2889
|
-
`),i.push(c);let l=
|
|
3038
|
+
`),i.push(c);let l=x(r,`login.dto.ts`);await j(l,`import { z } from 'zod'
|
|
2890
3039
|
|
|
2891
3040
|
export const LoginDto = z.object({
|
|
2892
3041
|
email: z.string().email(),
|
|
@@ -2894,7 +3043,7 @@ export const LoginDto = z.object({
|
|
|
2894
3043
|
})
|
|
2895
3044
|
|
|
2896
3045
|
export type LoginInput = z.infer<typeof LoginDto>
|
|
2897
|
-
`),i.push(l);let u=
|
|
3046
|
+
`),i.push(l);let u=x(n,`auth.test.ts`);if(await j(u,`import { describe, it, expect } from 'vitest'
|
|
2898
3047
|
|
|
2899
3048
|
describe('Auth Module', () => {
|
|
2900
3049
|
it.todo('POST /register — creates a new user')
|
|
@@ -2903,7 +3052,7 @@ describe('Auth Module', () => {
|
|
|
2903
3052
|
it.todo('POST /logout — invalidates session/token')
|
|
2904
3053
|
it.todo('GET /me — returns authenticated user')
|
|
2905
3054
|
})
|
|
2906
|
-
`),i.push(u),e.roleGuards!==!1){let e=
|
|
3055
|
+
`),i.push(u),e.roleGuards!==!1){let e=x(n,`auth.guard.ts`);await j(e,`import { Roles } from '@forinda/kickjs-auth'
|
|
2907
3056
|
|
|
2908
3057
|
/**
|
|
2909
3058
|
* Role-based access guard.
|
|
@@ -2914,7 +3063,7 @@ describe('Auth Module', () => {
|
|
|
2914
3063
|
*/
|
|
2915
3064
|
export const AdminOnly = Roles('admin')
|
|
2916
3065
|
export const ManagerOnly = Roles('manager')
|
|
2917
|
-
`),i.push(e)}return i}function
|
|
3066
|
+
`),i.push(e)}return i}function or(){return`import { Controller, Post, Get } from '@forinda/kickjs'
|
|
2918
3067
|
import { Authenticated, Public } from '@forinda/kickjs-auth'
|
|
2919
3068
|
import type { RequestContext } from '@forinda/kickjs'
|
|
2920
3069
|
import { Autowired } from '@forinda/kickjs'
|
|
@@ -2950,7 +3099,7 @@ export class AuthController {
|
|
|
2950
3099
|
return ctx.json({ user: ctx.user })
|
|
2951
3100
|
}
|
|
2952
3101
|
}
|
|
2953
|
-
`}function
|
|
3102
|
+
`}function sr(){return`import { Service, Autowired } from '@forinda/kickjs'
|
|
2954
3103
|
import { PasswordService } from '@forinda/kickjs-auth'
|
|
2955
3104
|
import type { RegisterInput } from './dto/register.dto'
|
|
2956
3105
|
import type { LoginInput } from './dto/login.dto'
|
|
@@ -2989,7 +3138,7 @@ export class AuthService {
|
|
|
2989
3138
|
return { user: { id: user.id, email: user.email, name: user.name } }
|
|
2990
3139
|
}
|
|
2991
3140
|
}
|
|
2992
|
-
`}function
|
|
3141
|
+
`}function cr(){return`import { Controller, Post, Get } from '@forinda/kickjs'
|
|
2993
3142
|
import { Authenticated, Public } from '@forinda/kickjs-auth'
|
|
2994
3143
|
import { sessionLogin, sessionLogout } from '@forinda/kickjs-auth'
|
|
2995
3144
|
import type { RequestContext } from '@forinda/kickjs'
|
|
@@ -3028,7 +3177,7 @@ export class AuthController {
|
|
|
3028
3177
|
return ctx.json({ user: ctx.user })
|
|
3029
3178
|
}
|
|
3030
3179
|
}
|
|
3031
|
-
`}function
|
|
3180
|
+
`}function lr(){return`import { Service, Autowired } from '@forinda/kickjs'
|
|
3032
3181
|
import { PasswordService } from '@forinda/kickjs-auth'
|
|
3033
3182
|
import type { RegisterInput } from './dto/register.dto'
|
|
3034
3183
|
import type { LoginInput } from './dto/login.dto'
|
|
@@ -3065,7 +3214,7 @@ export class AuthService {
|
|
|
3065
3214
|
return { id: user.id, email: user.email, name: user.name }
|
|
3066
3215
|
}
|
|
3067
3216
|
}
|
|
3068
|
-
`}async function
|
|
3217
|
+
`}async function ur(e){let{name:t,outDir:n}=e,r=R(t),i=B(t),a=z(t),o=e.queue??`${i}-queue`,s=[];return await(async(e,t)=>{let r=x(n,e);await j(r,t),s.push(r)})(`${i}.job.ts`,`import { Inject } from '@forinda/kickjs'
|
|
3069
3218
|
import { Job, Process, QUEUE_MANAGER, type QueueService } from '@forinda/kickjs-queue'
|
|
3070
3219
|
|
|
3071
3220
|
/**
|
|
@@ -3098,7 +3247,7 @@ export class ${r}Job {
|
|
|
3098
3247
|
// Handle high-priority variant of this job
|
|
3099
3248
|
}
|
|
3100
3249
|
}
|
|
3101
|
-
`),s}const
|
|
3250
|
+
`),s}const dr={string:{ts:`string`,zod:`z.string()`},text:{ts:`string`,zod:`z.string()`},number:{ts:`number`,zod:`z.number()`},int:{ts:`number`,zod:`z.number().int()`},float:{ts:`number`,zod:`z.number()`},boolean:{ts:`boolean`,zod:`z.boolean()`},date:{ts:`string`,zod:`z.string().datetime()`},email:{ts:`string`,zod:`z.string().email()`},url:{ts:`string`,zod:`z.string().url()`},uuid:{ts:`string`,zod:`z.string().uuid()`},json:{ts:`any`,zod:`z.any()`}};function fr(e){return e.map(e=>{let t=e.indexOf(`:`);if(t===-1)throw Error(`Invalid field: "${e}". Use format: name:type (e.g. title:string)`);let n=e.slice(0,t),r=e.slice(t+1);if(!n||!r)throw Error(`Invalid field: "${e}". Use format: name:type (e.g. title:string)`);let i=!1;r.endsWith(`:optional`)&&(r=r.slice(0,-9),i=!0),n.endsWith(`?`)&&(n=n.slice(0,-1),i=!0),r.endsWith(`?`)&&(r=r.slice(0,-1),i=!0);let a=r;if(a.startsWith(`enum:`)){let e=a.slice(5).split(`,`);return{name:n,type:`enum`,tsType:e.map(e=>`'${e}'`).join(` | `),zodType:`z.enum([${e.map(e=>`'${e}'`).join(`, `)}])`,optional:i}}let o=dr[a];if(!o){let e=[...Object.keys(dr),`enum:a,b,c`].join(`, `);throw Error(`Unknown field type: "${a}". Valid types: ${e}`)}return{name:n,type:a,tsType:o.ts,zodType:o.zod,optional:i}})}async function pr(e){let{name:t,fields:n,modulesDir:r,noEntity:i,noTests:a,repo:o=`inmemory`,tokenScope:s=`app`,style:c=`define`}=e,l=e.pluralize!==!1,u=B(t),d=R(t);z(t);let f=l?V(u):u,p=l?At(d):d,m=x(r,f),h=[],g=async(e,t)=>{let n=x(m,e);await j(n,t),h.push(n)};await g(`${u}.module.ts`,xr(d,u,f,c)),await g(`constants.ts`,_r(d,n)),await g(`presentation/${u}.controller.ts`,Sr(d,u,f,p)),await g(`application/dtos/create-${u}.dto.ts`,mr(d,n)),await g(`application/dtos/update-${u}.dto.ts`,hr(d,n)),await g(`application/dtos/${u}-response.dto.ts`,gr(d,n));let _=Tr(d,u,f,p);for(let e of _)await g(`application/use-cases/${e.file}`,e.content);return await g(`domain/repositories/${u}.repository.ts`,Cr(d,u,s)),await g(`domain/services/${u}-domain.service.ts`,wr(d,u)),o===`inmemory`&&await g(`infrastructure/repositories/in-memory-${u}.repository.ts`,vr(d,u,n)),i||(await g(`domain/entities/${u}.entity.ts`,yr(d,u,n)),await g(`domain/value-objects/${u}-id.vo.ts`,br(d))),await Er(r,d,f,u,c),h}function mr(e,t){return`import { z } from 'zod'
|
|
3102
3251
|
|
|
3103
3252
|
export const create${e}Schema = z.object({
|
|
3104
3253
|
${t.map(e=>{let t=e.zodType;return` ${e.name}: ${t}${e.optional?`.optional()`:``},`}).join(`
|
|
@@ -3106,7 +3255,7 @@ ${t.map(e=>{let t=e.zodType;return` ${e.name}: ${t}${e.optional?`.optional()`:`
|
|
|
3106
3255
|
})
|
|
3107
3256
|
|
|
3108
3257
|
export type Create${e}DTO = z.infer<typeof create${e}Schema>
|
|
3109
|
-
`}function
|
|
3258
|
+
`}function hr(e,t){return`import { z } from 'zod'
|
|
3110
3259
|
|
|
3111
3260
|
export const update${e}Schema = z.object({
|
|
3112
3261
|
${t.map(e=>` ${e.name}: ${e.zodType}.optional(),`).join(`
|
|
@@ -3114,21 +3263,21 @@ ${t.map(e=>` ${e.name}: ${e.zodType}.optional(),`).join(`
|
|
|
3114
3263
|
})
|
|
3115
3264
|
|
|
3116
3265
|
export type Update${e}DTO = z.infer<typeof update${e}Schema>
|
|
3117
|
-
`}function
|
|
3266
|
+
`}function gr(e,t){return`export interface ${e}ResponseDTO {
|
|
3118
3267
|
id: string
|
|
3119
3268
|
${t.map(e=>` ${e.name}${e.optional?`?`:``}: ${e.tsType}`).join(`
|
|
3120
3269
|
`)}
|
|
3121
3270
|
createdAt: string
|
|
3122
3271
|
updatedAt: string
|
|
3123
3272
|
}
|
|
3124
|
-
`}function
|
|
3273
|
+
`}function _r(e,t){let n=t.filter(e=>e.tsType===`string`).map(e=>`'${e.name}'`);t.filter(e=>e.tsType===`number`).map(e=>`'${e.name}'`);let r=t.map(e=>`'${e.name}'`),i=[...r].join(`, `),a=[...r,`'createdAt'`,`'updatedAt'`].join(`, `),o=n.length>0?n.join(`, `):`'name'`;return`import type { ApiQueryParamsConfig } from '@forinda/kickjs'
|
|
3125
3274
|
|
|
3126
3275
|
export const ${e.toUpperCase()}_QUERY_CONFIG: ApiQueryParamsConfig = {
|
|
3127
3276
|
filterable: [${i}],
|
|
3128
3277
|
sortable: [${a}],
|
|
3129
3278
|
searchable: [${o}],
|
|
3130
3279
|
}
|
|
3131
|
-
`}function
|
|
3280
|
+
`}function vr(e,t,n){return`import { randomUUID } from 'node:crypto'
|
|
3132
3281
|
import { Repository, HttpException } from '@forinda/kickjs'
|
|
3133
3282
|
import type { ParsedQuery } from '@forinda/kickjs'
|
|
3134
3283
|
import type { I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
@@ -3180,7 +3329,7 @@ ${n.map(e=>` ${e.name}: dto.${e.name},`).join(`
|
|
|
3180
3329
|
this.store.delete(id)
|
|
3181
3330
|
}
|
|
3182
3331
|
}
|
|
3183
|
-
`}function
|
|
3332
|
+
`}function yr(e,t,n){return`import { ${e}Id } from '../value-objects/${t}-id.vo'
|
|
3184
3333
|
|
|
3185
3334
|
interface ${e}Props {
|
|
3186
3335
|
id: ${e}Id
|
|
@@ -3226,7 +3375,7 @@ ${n.map(e=>` ${e.name}: this.props.${e.name},`).join(`
|
|
|
3226
3375
|
}
|
|
3227
3376
|
}
|
|
3228
3377
|
}
|
|
3229
|
-
`}function
|
|
3378
|
+
`}function br(e){return`import { randomUUID } from 'node:crypto'
|
|
3230
3379
|
|
|
3231
3380
|
export class ${e}Id {
|
|
3232
3381
|
private constructor(private readonly value: string) {}
|
|
@@ -3241,8 +3390,7 @@ export class ${e}Id {
|
|
|
3241
3390
|
toString(): string { return this.value }
|
|
3242
3391
|
equals(other: ${e}Id): boolean { return this.value === other.value }
|
|
3243
3392
|
}
|
|
3244
|
-
`}function
|
|
3245
|
-
import { ${e}Controller } from './presentation/${t}.controller'
|
|
3393
|
+
`}function xr(e,t,n,r=`define`){let i=`import { ${e}Controller } from './presentation/${t}.controller'
|
|
3246
3394
|
import { ${e.toUpperCase()}_REPOSITORY } from './domain/repositories/${t}.repository'
|
|
3247
3395
|
import { InMemory${e}Repository } from './infrastructure/repositories/in-memory-${t}.repository'
|
|
3248
3396
|
|
|
@@ -3251,7 +3399,18 @@ import { InMemory${e}Repository } from './infrastructure/repositories/in-memory-
|
|
|
3251
3399
|
import.meta.glob(
|
|
3252
3400
|
['./domain/services/**/*.ts', './application/use-cases/**/*.ts', '!./**/*.test.ts'],
|
|
3253
3401
|
{ eager: true },
|
|
3254
|
-
)
|
|
3402
|
+
)`,a=` /**
|
|
3403
|
+
* Declare HTTP routes. Pass \`controller\` and the framework
|
|
3404
|
+
* derives the Express Router via \`buildRoutes()\`. Return an array
|
|
3405
|
+
* to mount multiple route sets — each entry can override the API
|
|
3406
|
+
* version with a \`version\` field:
|
|
3407
|
+
*
|
|
3408
|
+
* return [
|
|
3409
|
+
* { path: '/${n}', version: 1, controller: ${e}V1Controller },
|
|
3410
|
+
* { path: '/${n}', version: 2, controller: ${e}V2Controller },
|
|
3411
|
+
* ]
|
|
3412
|
+
*/`;return r===`class`?`import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
3413
|
+
${i}
|
|
3255
3414
|
|
|
3256
3415
|
export class ${e}Module implements AppModule {
|
|
3257
3416
|
/**
|
|
@@ -3266,15 +3425,42 @@ export class ${e}Module implements AppModule {
|
|
|
3266
3425
|
)
|
|
3267
3426
|
}
|
|
3268
3427
|
|
|
3428
|
+
${a.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
3269
3429
|
routes(): ModuleRoutes {
|
|
3270
3430
|
return {
|
|
3271
3431
|
path: '/${n}',
|
|
3272
|
-
router: buildRoutes(${e}Controller),
|
|
3273
3432
|
controller: ${e}Controller,
|
|
3274
3433
|
}
|
|
3275
3434
|
}
|
|
3276
3435
|
}
|
|
3277
|
-
|
|
3436
|
+
`:`import { defineModule } from '@forinda/kickjs'
|
|
3437
|
+
${i}
|
|
3438
|
+
|
|
3439
|
+
export const ${e}Module = defineModule({
|
|
3440
|
+
name: '${e}Module',
|
|
3441
|
+
build: () => ({
|
|
3442
|
+
/**
|
|
3443
|
+
* Bind the repository token to its concrete implementation.
|
|
3444
|
+
* Decorator-managed classes (@Service, @Controller, @Repository) are
|
|
3445
|
+
* registered automatically — only token-to-impl bindings need to live here.
|
|
3446
|
+
*/
|
|
3447
|
+
register(container) {
|
|
3448
|
+
container.registerFactory(
|
|
3449
|
+
${e.toUpperCase()}_REPOSITORY,
|
|
3450
|
+
() => container.resolve(InMemory${e}Repository),
|
|
3451
|
+
)
|
|
3452
|
+
},
|
|
3453
|
+
|
|
3454
|
+
${a}
|
|
3455
|
+
routes() {
|
|
3456
|
+
return {
|
|
3457
|
+
path: '/${n}',
|
|
3458
|
+
controller: ${e}Controller,
|
|
3459
|
+
}
|
|
3460
|
+
},
|
|
3461
|
+
}),
|
|
3462
|
+
})
|
|
3463
|
+
`}function Sr(e,t,n,r){return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams, type Ctx } from '@forinda/kickjs'
|
|
3278
3464
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
3279
3465
|
import { Create${e}UseCase } from '../application/use-cases/create-${t}.use-case'
|
|
3280
3466
|
import { Get${e}UseCase } from '../application/use-cases/get-${t}.use-case'
|
|
@@ -3337,7 +3523,7 @@ export class ${e}Controller {
|
|
|
3337
3523
|
ctx.noContent()
|
|
3338
3524
|
}
|
|
3339
3525
|
}
|
|
3340
|
-
`}function
|
|
3526
|
+
`}function Cr(e,t,n){return`import { createToken } from '@forinda/kickjs'
|
|
3341
3527
|
import type { ${e}ResponseDTO } from '../../application/dtos/${t}-response.dto'
|
|
3342
3528
|
import type { Create${e}DTO } from '../../application/dtos/create-${t}.dto'
|
|
3343
3529
|
import type { Update${e}DTO } from '../../application/dtos/update-${t}.dto'
|
|
@@ -3363,7 +3549,7 @@ export interface I${e}Repository {
|
|
|
3363
3549
|
* adopters must NOT use the reserved \`'kick/'\` namespace.
|
|
3364
3550
|
*/
|
|
3365
3551
|
export const ${e.toUpperCase()}_REPOSITORY = createToken<I${e}Repository>('${n}/${e}/repository')
|
|
3366
|
-
`}function
|
|
3552
|
+
`}function wr(e,t){return`import { Service, Inject, HttpException } from '@forinda/kickjs'
|
|
3367
3553
|
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../repositories/${t}.repository'
|
|
3368
3554
|
|
|
3369
3555
|
@Service()
|
|
@@ -3377,7 +3563,7 @@ export class ${e}DomainService {
|
|
|
3377
3563
|
if (!entity) throw HttpException.notFound('${e} not found')
|
|
3378
3564
|
}
|
|
3379
3565
|
}
|
|
3380
|
-
`}function
|
|
3566
|
+
`}function Tr(e,t,n,r){return[{file:`create-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs'
|
|
3381
3567
|
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
3382
3568
|
import type { Create${e}DTO } from '../dtos/create-${t}.dto'
|
|
3383
3569
|
|
|
@@ -3420,10 +3606,10 @@ export class Delete${e}UseCase {
|
|
|
3420
3606
|
constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
|
|
3421
3607
|
async execute(id: string) { return this.repo.delete(id) }
|
|
3422
3608
|
}
|
|
3423
|
-
`}]}async function
|
|
3424
|
-
`,e);
|
|
3425
|
-
`+
|
|
3426
|
-
`+
|
|
3609
|
+
`}]}async function Er(e,t,n,r,i=`define`){let a=x(e,`index.ts`),o=await Re(a),s=`./${n}/${r}.module`,c=i===`class`?`${t}Module`:`${t}Module()`;if(!o){await j(a,`import type { AppModuleEntry } from '@forinda/kickjs'\nimport { ${t}Module } from '${s}'\n\nexport const modules: AppModuleEntry[] = [${c}]\n`);return}let l=await E(a,`utf-8`),u=`import { ${t}Module } from '${s}'`;if(!l.includes(`${t}Module`)){let e=l.lastIndexOf(`import `);if(e!==-1){let t=l.indexOf(`
|
|
3610
|
+
`,e);l=l.slice(0,t+1)+u+`
|
|
3611
|
+
`+l.slice(t+1)}else l=u+`
|
|
3612
|
+
`+l;l=l.replace(/(=\s*\[)([\s\S]*?)(])/,(e,t,n,r)=>{let i=n.trim();if(!i)return`${t}${c}${r}`;let a=i.endsWith(`,`)?``:`,`;return`${t}${n.trimEnd()}${a} ${c}${r}`})}await D(a,l,`utf-8`)}async function Dr(e){let{name:t,moduleName:n,modulesDir:r}=e,i=e.pluralize??!0,a=B(t),o=R(t),s=[],c;if(e.outDir)c=C(e.outDir);else if(n){let e=B(n),t=i?V(e):e;c=C(x(r??`src/modules`,t,`__tests__`))}else c=C(`src/__tests__`);let l=x(c,`${a}.test.ts`);return await j(l,`import { describe, it, expect, beforeEach } from 'vitest'
|
|
3427
3613
|
import { Container } from '@forinda/kickjs'
|
|
3428
3614
|
|
|
3429
3615
|
describe('${o}', () => {
|
|
@@ -3446,59 +3632,59 @@ describe('${o}', () => {
|
|
|
3446
3632
|
expect(true).toBe(true)
|
|
3447
3633
|
})
|
|
3448
3634
|
})
|
|
3449
|
-
`),s.push(l),s}const
|
|
3450
|
-
(dry run — no files were written)`),console.log()}async function
|
|
3635
|
+
`),s.push(l),s}const Or=[`agents`,`claude`,`skills`,`both`,`all`];function K(e){return e.parent?.opts()?.dryRun??!1}function q(e,t=!1){let n=process.cwd();console.log(`\n ${t?`Would generate`:`Generated`} ${e.length} file${e.length===1?``:`s`}:`);for(let t of e)console.log(` ${t.replace(n+`/`,``)}`);t&&console.log(`
|
|
3636
|
+
(dry run — no files were written)`),console.log()}async function kr(e){if(!e)try{let e=await r(process.cwd());await f({cwd:process.cwd(),allowDuplicates:!0,silent:!0,schemaValidator:e?.typegen?.schemaValidator??`zod`,envFile:e?.typegen?.envFile,srcDir:e?.typegen?.srcDir,outDir:e?.typegen?.outDir})}catch{}}const Ar=[{name:`module <name>`,description:`Full DDD module (controller, DTOs, use-cases, repo)`},{name:`scaffold <name> <fields...>`,description:`CRUD module from field definitions`},{name:`controller <name>`,description:`@Controller() class [-m module]`},{name:`service <name>`,description:`@Service() singleton [-m module]`},{name:`middleware <name>`,description:`Express middleware function [-m module]`},{name:`guard <name>`,description:`Route guard (auth, roles, etc.) [-m module]`},{name:`dto <name>`,description:`Zod DTO schema [-m module]`},{name:`adapter <name>`,description:`AppAdapter with lifecycle hooks (app-level only)`},{name:`test <name>`,description:`Vitest test scaffold [-m module]`},{name:`job <name>`,description:`Queue @Job processor`},{name:`config`,description:`Generate kick.config.ts`},{name:`agents`,description:`Regenerate AGENTS.md + CLAUDE.md + kickjs-skills.md from upstream templates`}];async function jr(){console.log(`
|
|
3451
3637
|
Built-in generators:
|
|
3452
|
-
`);let e=Math.max(...
|
|
3638
|
+
`);let e=Math.max(...Ar.map(e=>e.name.length));for(let t of Ar)console.log(` kick g ${t.name.padEnd(e+2)} ${t.description}`);let t=await r(process.cwd()),n=a(t?.plugins??[],t?.commands??[]),i=await Vt(process.cwd(),n.generators);if(i.generators.length>0){console.log(`
|
|
3453
3639
|
Plugin generators:
|
|
3454
3640
|
`);let e=Math.max(...i.generators.map(e=>`${e.spec.name} <name>`.length));for(let{source:t,spec:n}of i.generators){let r=`${n.name} <name>`;console.log(` kick g ${r.padEnd(e+2)} ${n.description} [${t}]`)}}if(i.failed.length>0){console.log(`
|
|
3455
3641
|
Failed to load:
|
|
3456
|
-
`);for(let{source:e,reason:t}of i.failed)console.log(` ${e} — ${t}`)}console.log()}async function
|
|
3457
|
-
Use -m to scope it to a module: kick g middleware auth -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=
|
|
3458
|
-
Use -m to scope it to a module: kick g guard admin -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=
|
|
3459
|
-
Use -m to scope it to a module: kick g service payment -m orders`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=
|
|
3460
|
-
Use -m to scope it to a module: kick g controller auth -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=
|
|
3461
|
-
Use -m to scope it to a module: kick g dto create-user -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=
|
|
3462
|
-
Use -m to scope it to a module: kick g test user-service -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module's __tests__/ folder`).action(async(e,t,i)=>{let a=
|
|
3642
|
+
`);for(let{source:e,reason:t}of i.failed)console.log(` ${e} — ${t}`)}console.log()}async function Mr(e,i,a){let o=await r(process.cwd()),s=n(o),c=i.modulesDir??s.dir??`src/modules`,l=i.repo??On(s.repo),u=i.pattern??o?.pattern??`ddd`,d=i.pluralize===!1?!1:s.pluralize??!0,f=t(o,process.cwd()),p=s.style??`define`;if(!a&&p===`define`){let e=await ir(C(c),`define`);if(e.length>0){console.error(`\n ${k.red(`Error:`)} ${e.length} module file(s) still use the legacy \`class … implements AppModule\` shape.\n ${k.dim(`Project setting:`)} modules.style: 'define' (default)\n\n ${k.bold(`Files needing migration:`)}`);for(let t of e.slice(0,5))console.error(` - ${t}`);e.length>5&&console.error(` … and ${e.length-5} more`),console.error(`\n ${k.bold(`Pick one:`)}\n 1. Migrate everything to defineModule:\n ${k.dim(`$`)} kick codemod modules --experimental --apply\n 2. Keep the class form — pin it in kick.config.ts:\n ${k.dim(`// kick.config.ts`)}\n ${k.dim(`export default defineConfig({ modules: { style: 'class' } })`)}\n`),process.exit(1)}}let m=[];for(let t of e){let e=await kn({name:t,modulesDir:C(c),noEntity:i.entity===!1,noTests:i.tests===!1,repo:l,minimal:i.minimal,force:i.force,pattern:u,dryRun:a,pluralize:d,prismaClientPath:s.prismaClientPath,tokenScope:f,style:s.style});m.push(...e)}q(m,a),await kr(a)}function Nr(e){let i=e.command(`generate [names...]`).alias(`g`).description("Generate code scaffolds — bare form `kick g <name>` is shorthand for `kick g module <name>`").option(`--list`,`List all available generators`).option(`--dry-run`,`Preview files that would be generated without writing them`).option(`--no-entity`,`Skip entity and value object generation (module shortcut)`).option(`--no-tests`,`Skip test file generation (module shortcut)`).option(`--repo <type>`,`Repository implementation: inmemory | drizzle | prisma`).option(`--pattern <pattern>`,`Override project pattern: rest | ddd | cqrs | minimal`).option(`--minimal`,`Shorthand for --pattern minimal`).option(`--modules-dir <dir>`,`Modules directory`).option(`--no-pluralize`,`Use singular names (skip auto-pluralization)`).option(`-f, --force`,`Overwrite existing files without prompting`).action(async(e,t,n)=>{if(t.list){await jr();return}if(!e||e.length===0){i.help();return}let o=K(n);if(A(o),e.length>=2){let[n,i,...s]=e,c=await r(process.cwd()),l=a(c?.plugins??[],c?.commands??[]),u=await Bt({generatorName:n,itemName:i,args:s,flags:t,cwd:process.cwd()},l.generators);if(u){q(u.files,o);return}}await Mr(e,t,o)});i.command(`module <names...>`).description(`Generate one or more modules (e.g. kick g module user task project)`).option(`--no-entity`,`Skip entity and value object generation`).option(`--no-tests`,`Skip test file generation`).option(`--repo <type>`,`Repository implementation: inmemory | drizzle | prisma`).option(`--pattern <pattern>`,`Override project pattern: rest | ddd | cqrs | minimal`).option(`--minimal`,`Shorthand for --pattern minimal`).option(`--modules-dir <dir>`,`Modules directory`).option(`--no-pluralize`,`Use singular names (skip auto-pluralization)`).option(`-f, --force`,`Overwrite existing files without prompting`).action(async(e,t,n)=>{let r=K(n);A(r),await Mr(e,t,r)}),i.command(`adapter <name>`).description(`Generate an AppAdapter with lifecycle hooks and middleware support`).option(`-o, --out <dir>`,`Output directory`,`src/adapters`).action(async(e,t,n)=>{let r=K(n);A(r),q(await jn({name:e,outDir:C(t.out)}),r)}),i.command(`plugin <name>`).description(`Generate a KickPlugin with DI, modules, adapters, middleware, and lifecycle hooks`).option(`-o, --out <dir>`,`Output directory`,`src/plugins`).action(async(e,t,n)=>{let r=K(n);A(r),q(await Mn({name:e,outDir:C(t.out)}),r)}),i.command(`middleware <name>`).description(`Generate an Express middleware function
|
|
3643
|
+
Use -m to scope it to a module: kick g middleware auth -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=K(i);A(a);let o=await r(process.cwd()),s=n(o),c=s.dir??`src/modules`;q(await In({name:e,outDir:t.out,moduleName:t.module,modulesDir:c,pattern:o?.pattern,pluralize:s.pluralize??!0}),a)}),i.command(`guard <name>`).description(`Generate a route guard (auth, roles, etc.)
|
|
3644
|
+
Use -m to scope it to a module: kick g guard admin -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=K(i);A(a);let o=await r(process.cwd()),s=n(o),c=s.dir??`src/modules`;q(await Ln({name:e,outDir:t.out,moduleName:t.module,modulesDir:c,pattern:o?.pattern,pluralize:s.pluralize??!0}),a)}),i.command(`service <name>`).description(`Generate a @Service() class
|
|
3645
|
+
Use -m to scope it to a module: kick g service payment -m orders`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=K(i);A(a);let o=await r(process.cwd()),s=n(o),c=s.dir??`src/modules`;q(await Rn({name:e,outDir:t.out,moduleName:t.module,modulesDir:c,pattern:o?.pattern,pluralize:s.pluralize??!0}),a)}),i.command(`controller <name>`).description(`Generate a @Controller() class with basic routes
|
|
3646
|
+
Use -m to scope it to a module: kick g controller auth -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=K(i);A(a);let o=await r(process.cwd()),s=n(o),c=s.dir??`src/modules`;q(await zn({name:e,outDir:t.out,moduleName:t.module,modulesDir:c,pattern:o?.pattern,pluralize:s.pluralize??!0}),a),await kr(a)}),i.command(`dto <name>`).description(`Generate a Zod DTO schema
|
|
3647
|
+
Use -m to scope it to a module: kick g dto create-user -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module folder`).action(async(e,t,i)=>{let a=K(i);A(a);let o=await r(process.cwd()),s=n(o),c=s.dir??`src/modules`;q(await Bn({name:e,outDir:t.out,moduleName:t.module,modulesDir:c,pattern:o?.pattern,pluralize:s.pluralize??!0}),a)}),i.command(`test <name>`).description(`Generate a Vitest test scaffold
|
|
3648
|
+
Use -m to scope it to a module: kick g test user-service -m users`).option(`-o, --out <dir>`,`Output directory (overrides --module)`).option(`-m, --module <module>`,`Place inside a module's __tests__/ folder`).action(async(e,t,i)=>{let a=K(i);A(a);let o=n(await r(process.cwd())),s=o.dir??`src/modules`;q(await Dr({name:e,outDir:t.out,moduleName:t.module,modulesDir:s,pluralize:o.pluralize??!0}),a)}),i.command(`job <name>`).description(`Generate a @Job queue processor with @Process handlers`).option(`-o, --out <dir>`,`Output directory`,`src/jobs`).option(`-q, --queue <name>`,`Queue name (default: <name>-queue)`).action(async(e,t,n)=>{let r=K(n);A(r),q(await ur({name:e,outDir:C(t.out),queue:t.queue}),r)}),i.command(`scaffold <name> [fields...]`).description(`Generate a full CRUD module from field definitions
|
|
3463
3649
|
Example: kick g scaffold Post title:string body:text:optional published:boolean:optional
|
|
3464
3650
|
Types: string, text, number, int, float, boolean, date, email, url, uuid, json, enum:a,b,c
|
|
3465
3651
|
Optional: append :optional (shell-safe): description:text:optional
|
|
3466
|
-
or use ? with quoting: "description:text?" or "description?:text"`).option(`--no-entity`,`Skip entity and value object generation`).option(`--no-tests`,`Skip test file generation`).option(`--no-pluralize`,`Use singular names (skip auto-pluralization)`).option(`--modules-dir <dir>`,`Modules directory`).action(async(e,i,a,o)=>{let s=
|
|
3652
|
+
or use ? with quoting: "description:text?" or "description?:text"`).option(`--no-entity`,`Skip entity and value object generation`).option(`--no-tests`,`Skip test file generation`).option(`--no-pluralize`,`Use singular names (skip auto-pluralization)`).option(`--modules-dir <dir>`,`Modules directory`).action(async(e,i,a,o)=>{let s=K(o);A(s),i.length===0&&(console.error(`
|
|
3467
3653
|
Error: At least one field is required.
|
|
3468
3654
|
Usage: kick g scaffold <name> <field:type> [field:type...]
|
|
3469
3655
|
Example: kick g scaffold Post title:string body:text:optional published:boolean:optional
|
|
3470
3656
|
Optional: append :optional (shell-safe, no quoting needed)
|
|
3471
|
-
`),process.exit(1));let c=await r(process.cwd()),l=n(c),u=a.modulesDir??l.dir??`src/modules`,d=
|
|
3472
|
-
Includes controller, service, DTOs, and test stubs.`).option(`-s, --strategy <type>`,`Auth strategy: jwt | session`).option(`--role-guards`,`Generate role-based guards (default: true)`).option(`--no-role-guards`,`Skip role-based guard generation`).option(`-o, --out <dir>`,`Output directory`,`src/modules/auth`).action(async(e,t)=>{let n=
|
|
3473
|
-
`,l=``;if(h(s)&&(l=await
|
|
3474
|
-
`,`utf-8`),o(` ✓ wrote manifest → ${
|
|
3657
|
+
`),process.exit(1));let c=await r(process.cwd()),l=n(c),u=a.modulesDir??l.dir??`src/modules`,d=fr(i),f=t(c,process.cwd()),p=c?.pattern??`ddd`;p!==`ddd`&&(console.error(`\n Error: 'kick g scaffold' currently only supports the DDD pattern.\n Detected project pattern: '${p}'.\n Workarounds:\n - Run 'kick g module ${e}' for the ${p} layout (no fields), then add fields manually.\n - Override the pattern for this scaffold by setting kick.config.ts pattern: 'ddd'.\n`),process.exit(1));let m=await pr({name:e,fields:d,modulesDir:C(u),noEntity:a.entity===!1,noTests:a.tests===!1,pluralize:a.pluralize===!1?!1:l.pluralize??!0,tokenScope:f,style:l.style});console.log(`\n Scaffolded ${e} with ${d.length} field(s):`);for(let e of d)console.log(` ${e.name}: ${e.type}${e.optional?` (optional)`:``}`);q(m,s),await kr(s)}),i.command(`auth-scaffold`).description(`Generate a complete auth module (register, login, logout, password hashing)
|
|
3658
|
+
Includes controller, service, DTOs, and test stubs.`).option(`-s, --strategy <type>`,`Auth strategy: jwt | session`).option(`--role-guards`,`Generate role-based guards (default: true)`).option(`--no-role-guards`,`Skip role-based guard generation`).option(`-o, --out <dir>`,`Output directory`,`src/modules/auth`).action(async(e,t)=>{let n=K(t);A(n);let r=e.strategy;r||=await _t({message:`Auth strategy`,options:[{value:`jwt`,label:`JWT`,hint:`stateless token-based auth`},{value:`session`,label:`Session`,hint:`server-side session with cookies`}]});let i=e.roleGuards;i===void 0&&(i=await F({message:`Generate role-based guards?`,initialValue:!0})),q(await ar({strategy:r,outDir:e.out,roleGuards:i}),n)}),i.command(`config`).description(`Generate a kick.config.ts at the project root`).option(`--modules-dir <dir>`,`Modules directory path`,`src/modules`).option(`--repo <type>`,`Default repository type: inmemory | drizzle | prisma`,`inmemory`).option(`-f, --force`,`Overwrite existing kick.config.ts without prompting`).action(async(e,t)=>{let n=K(t);A(n),q(await Vn({outDir:C(`.`),modulesDir:e.modulesDir,defaultRepo:e.repo,force:e.force}),n)}),i.command(`agents`).alias(`agent-docs`).alias(`ai-docs`).description(`Regenerate AGENTS.md + CLAUDE.md + kickjs-skills.md (sync after framework upgrades)`).option(`--only <which>`,`Limit scope: agents | claude | skills | both (agents+claude) | all (default: all)`,`all`).option(`--name <name>`,`Project name (defaults to package.json name)`).option(`--pm <pm>`,`Package manager (defaults to package.json packageManager)`).option(`--template <template>`,`Template: rest | ddd | cqrs | minimal`).option(`-f, --force`,`Overwrite existing files without prompting`).action(async(e,t)=>{let n=K(t);A(n);let r=e.only??`all`;if(!Or.includes(r)){console.error(` Invalid --only value: ${r}. Expected: ${Or.join(` | `)}`),process.exitCode=1;return}q(await Kn({outDir:C(`.`),only:r,name:e.name,pm:e.pm,template:e.template,force:e.force}),n)})}async function Pr(e){let t=y.resolve(e.cwd,`.kickjs/types`);await pe(t,{recursive:!0});let n=new Map,r=e.scan??d,i={cwd:e.cwd,config:e.config,async importTs(e){return await import(w(e).href)},async writeFile(t,n){let r=y.resolve(e.cwd,t);await pe(y.dirname(r),{recursive:!0}),await D(r,n,`utf8`)},getScanResult:e=>{let t=Fr(e),i=n.get(t);return i||(i=r(e),n.set(t,i)),i},log:console},a=[];for(let n of e.plugins){let r=await n.generate(i);if(r===null){a.push({id:n.id,status:`skipped`});continue}let o=n.outExtension??`.d.ts`,s=y.join(t,`${n.id.replace(/\//g,`__`)}${o}`),c=`/* AUTO-GENERATED by kick typegen — do not edit. Plugin: ${n.id} */\n\n`+r+`
|
|
3659
|
+
`,l=``;if(h(s)&&(l=await E(s,`utf8`)),l===c){a.push({id:n.id,status:`unchanged`,outFile:s});continue}if(e.check)throw Error(`kick typegen --check: drift detected for ${n.id} (${s})`);await D(s,c,`utf8`),a.push({id:n.id,status:`written`,outFile:s})}return a}function Fr(e){let t=(e.extensions??[]).slice().toSorted().join(`,`),n=(e.exclude??[]).slice().toSorted().join(`,`);return[`root=${e.root}`,`cwd=${e.cwd}`,`extensions=${t}`,`exclude=${n}`,`envFile=${e.envFile??``}`].join(`|`)}function Ir(e,t){let n=new Set(t),r=[],i=[],a=new Set;for(let t of e)n.has(t.id)?(i.push(t),a.add(t.id)):r.push(t);return{enabled:r,skipped:i,unknown:[...n].filter(e=>!a.has(e))}}var Lr=e({applyDisableFilter:()=>Ir,runAllPluginTypegens:()=>Rr});async function Rr(e){let{enabled:t,skipped:n,unknown:r}=Ir(a([..._a,...e.config?.plugins??[]],e.config?.commands??[]).typegens,e.config?.typegen?.disable??[]);if(!e.silent&&n.length>0)for(let e of n)console.log(` ${e.id}: disabled (typegen.disable)`);if(!e.silent&&r.length>0&&console.warn(` kick typegen: disable list references unknown id(s): ${r.map(e=>`'${e}'`).join(`, `)}. Run \`kick typegen --list\` to see registered ids.`),t.length===0)return[];try{let n=await Pr({cwd:e.cwd,config:e.config??{},plugins:t,check:e.check});if(!e.silent)for(let e of n)console.log(` ${e.id}: ${e.status}`);return n}catch(t){if(!e.silent){let e=t instanceof Error?t.message:String(t);console.warn(` kick typegen plugins: skipped (${e})`)}return[]}}async function zr(e,t){let{cwd:n,silent:r=!1}=t,i=t.distDir??e?.build?.outDir??`dist`,a=e?.assetMap;if(!a||Object.keys(a).length===0)return null;let o=r?()=>{}:console.log,s=C(n,i);g(s,{recursive:!0});let c=[],l={};for(let[e,t]of Object.entries(a)){let r=await Br(e,t,n,s);c.push(r.entrySummary),Object.assign(l,r.manifestSlice),o(` ✓ ${e}: ${r.entrySummary.filesCopied} file(s) → ${r.entrySummary.dest}`)}let u={version:1,entries:l},d=x(s,`.kickjs-assets.json`);return ne(d,JSON.stringify(u,null,2)+`
|
|
3660
|
+
`,`utf-8`),o(` ✓ wrote manifest → ${S(n,d)} (${Object.keys(l).length} entries)`),{manifestPath:d,entries:c,manifest:u}}async function Br(e,t,n,r){let i=C(n,t.src),a=t.dest?C(n,t.dest):x(r,e);if(Hr(a,n))return console.warn(` ⚠ assetMap.${e}.dest ('${t.dest}') resolves outside the project root — skipping copy`),{entrySummary:{namespace:e,src:t.src,dest:S(n,a),filesCopied:0},manifestSlice:{}};if(!h(i)||!Ur(i))return{entrySummary:{namespace:e,src:t.src,dest:S(n,a),filesCopied:0},manifestSlice:{}};let o=await ve(t.glob??`**/*`,{cwd:i,nodir:!0,dot:!1,posix:!0});g(a,{recursive:!0});let s={},{pairs:c,collisionGroupsResolved:l}=ye(e,[...o].toSorted(),{strategy:t.keys??`auto`});for(let{rel:e,key:t}of c){let n=x(i,e),o=x(a,e);g(b(o),{recursive:!0}),m(n,o),s[t]=Vr(r,o)}return l>0&&console.log(` ℹ assetMap.${e}: auto-resolved ${l} 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:t.src,dest:S(n,a),filesCopied:o.length},manifestSlice:s}}function Vr(e,t){return S(e,t).split(/[\\/]/).filter(Boolean).join(`/`)}function Hr(e,t){let n=S(t,e);return n===``?!1:n.startsWith(`..`)||ae(n)}function Ur(e){try{return te(e).isDirectory()}catch{return!1}}function Wr(e){if(typeof e==`boolean`)return e;let t=process.env.KICKJS_WATCH_POLLING;return t===`1`||t===`true`}async function Gr(e,t,n={}){t&&(process.env.PORT=t);let i=Wr(n.polling),a=process.cwd(),o=await r(a),s=o?.typegen?.schemaValidator??`zod`,c=o?.typegen?.envFile;try{await f({cwd:a,allowDuplicates:!0,schemaValidator:s,envFile:c,srcDir:o?.typegen?.srcDir,outDir:o?.typegen?.outDir,assetMap:o?.assetMap,runPlugins:!1})}catch(e){console.warn(` kick typegen: skipped (${e?.message??e})`)}await Rr({cwd:a,config:o});let{createRequire:l}=await import(`node:module`),{createServer:u}=await import(w(l(C(`package.json`)).resolve(`vite`)).href),d=await u({configFile:C(`vite.config.ts`),server:{port:t?parseInt(t,10):void 0,...i?{watch:{usePolling:!0,interval:100}}:{}}}),p=o?.assetMap?Object.values(o.assetMap).map(e=>e?.src).filter(e=>typeof e==`string`&&e.length>0).map(e=>C(a,e)):[],m=e=>p.some(t=>e===t||e.startsWith(`${t}/`)),h=null,g=e=>{if(e.includes(`.kickjs`)||e.endsWith(`.d.ts`))return;let t=/\.(ts|tsx|mts|cts)$/.test(e),n=m(e);!t&&!n||(h&&clearTimeout(h),h=setTimeout(()=>{f({cwd:a,silent:!0,allowDuplicates:!0,schemaValidator:s,envFile:c,srcDir:o?.typegen?.srcDir,outDir:o?.typegen?.outDir,assetMap:o?.assetMap,runPlugins:!1}).catch(()=>{}),Rr({cwd:a,config:o,silent:!0}).catch(()=>{})},100))};d.watcher.on(`add`,g),d.watcher.on(`unlink`,g),d.watcher.on(`change`,g),p.length>0&&d.watcher.add(p),await d.listen(),d.printUrls(),console.log(`
|
|
3475
3661
|
KickJS dev server running (Vite + @forinda/kickjs-vite)
|
|
3476
|
-
`);let _=async()=>{h&&clearTimeout(h),await d.close(),process.exit(0)};process.on(`SIGINT`,_),process.on(`SIGTERM`,_)}function
|
|
3662
|
+
`);let _=async()=>{h&&clearTimeout(h),await d.close(),process.exit(0)};process.on(`SIGINT`,_),process.on(`SIGTERM`,_)}function Kr(e){e.command(`dev`).description(`Start development server with Vite HMR (zero-downtime reload)`).option(`-e, --entry <file>`,`Entry file`,`src/index.ts`).option(`-p, --port <port>`,`Port number`).option(`--polling`,`Force chokidar to poll for file changes (Docker / WSL / NFS / older kernels)`).action(async e=>{try{await Gr(e.entry,e.port,{polling:e.polling})}catch(e){e.code===`ERR_MODULE_NOT_FOUND`&&e.message?.includes(`vite`)?console.error(`
|
|
3477
3663
|
Error: vite is not installed.
|
|
3478
3664
|
Run: pnpm add -D vite unplugin-swc
|
|
3479
3665
|
`):console.error(`
|
|
3480
3666
|
Dev server failed:`,e.message??e),process.exit(1)}}),e.command(`build`).description(`Build for production via Vite`).action(async()=>{console.log(`
|
|
3481
3667
|
Building for production...
|
|
3482
|
-
`);let{createRequire:e}=await import(`node:module`),{build:t}=await import(
|
|
3483
|
-
Copying directories to dist...`);for(let e of i){let t=typeof e==`string`?e:e.src,n=typeof e==`string`?
|
|
3484
|
-
Building asset map...`);try{await
|
|
3668
|
+
`);let{createRequire:e}=await import(`node:module`),{build:t}=await import(w(e(C(`package.json`)).resolve(`vite`)).href);await t({configFile:C(`vite.config.ts`)});let n=await r(process.cwd()),i=n?.copyDirs??[];if(i.length>0){console.log(`
|
|
3669
|
+
Copying directories to dist...`);for(let e of i){let t=typeof e==`string`?e:e.src,n=typeof e==`string`?x(`dist`,e):e.dest??x(`dist`,t),r=C(t),i=C(n);if(!h(r)){console.log(` ⚠ Skipped ${t} (not found)`);continue}g(i,{recursive:!0}),m(r,i,{recursive:!0}),console.log(` ✓ ${t} → ${n}`)}}if(n?.assetMap&&Object.keys(n.assetMap).length>0){console.log(`
|
|
3670
|
+
Building asset map...`);try{await zr(n,{cwd:process.cwd()})}catch(e){console.error(` ✗ asset build failed: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}console.log(`
|
|
3485
3671
|
Build complete.
|
|
3486
3672
|
`)}),e.command(`build:assets`).description(`Rebuild the .kickjs-assets.json manifest under the configured outDir (no JS rebuild)`).action(async()=>{let e=await r(process.cwd());if(!e?.assetMap||Object.keys(e.assetMap).length===0){console.log(` No assetMap entries — nothing to build.`);return}console.log(`
|
|
3487
|
-
Building asset map...`);try{await
|
|
3673
|
+
Building asset map...`);try{await zr(e,{cwd:process.cwd()}),console.log(`
|
|
3488
3674
|
Asset build complete.
|
|
3489
|
-
`)}catch(e){console.error(` ✗ ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}),e.command(`start`).description(`Start production server`).option(`-e, --entry <file>`,`Entry file`,`dist/index.js`).option(`-p, --port <port>`,`Port number`).action(e=>{let t={NODE_ENV:`production`};e.port&&(t.PORT=String(e.port)),
|
|
3490
|
-
Dev server (debug) failed:`,e.message??e),process.exit(1)}})}function
|
|
3675
|
+
`)}catch(e){console.error(` ✗ ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}),e.command(`start`).description(`Start production server`).option(`-e, --entry <file>`,`Entry file`,`dist/index.js`).option(`-p, --port <port>`,`Port number`).action(e=>{let t={NODE_ENV:`production`};e.port&&(t.PORT=String(e.port)),Me(e.entry,t)}),e.command(`dev:debug`).description(`Start dev server with Node.js inspector attached`).option(`-e, --entry <file>`,`Entry file`,`src/index.ts`).option(`-p, --port <port>`,`Port number`).option(`--inspect-port <port>`,`Inspector port`,`9229`).action(async e=>{let t=e.inspectPort??`9229`;process.env.NODE_OPTIONS=`--inspect=0.0.0.0:${t}`,console.log(` Debugger: ws://0.0.0.0:${t}`);try{await Gr(e.entry,e.port)}catch(e){console.error(`
|
|
3676
|
+
Dev server (debug) failed:`,e.message??e),process.exit(1)}})}function qr(e){e.command(`info`).description(`Print system and framework info`).action(()=>{console.log(`
|
|
3491
3677
|
KickJS CLI
|
|
3492
3678
|
|
|
3493
3679
|
System:
|
|
3494
|
-
OS: ${
|
|
3680
|
+
OS: ${xe()} ${Se()} (${be()})
|
|
3495
3681
|
Node: ${process.version}
|
|
3496
3682
|
|
|
3497
3683
|
Packages:
|
|
3498
3684
|
@forinda/kickjs workspace
|
|
3499
3685
|
@forinda/kickjs-vite workspace
|
|
3500
3686
|
@forinda/kickjs-cli workspace
|
|
3501
|
-
`)})}const{bold:
|
|
3687
|
+
`)})}const{bold:J,dim:Y,green:Jr,red:Yr,yellow:Xr,blue:Zr}=k;function Qr(e){let t=Math.floor(e/86400),n=Math.floor(e%86400/3600),r=Math.floor(e%3600/60),i=e%60,a=[];return t&&a.push(`${t}d`),n&&a.push(`${n}h`),r&&a.push(`${r}m`),a.push(`${i}s`),a.join(` `)}async function $r(e){let t=await fetch(e,{signal:AbortSignal.timeout(5e3)});if(!t.ok)throw Error(`${t.status} ${t.statusText}`);return t.json()}async function X(e,t){try{return await $r(`${e}${t}`)}catch{return null}}async function ei(e){let[t,n,r,i,a]=await Promise.all([X(e,`/health`),X(e,`/metrics`),X(e,`/routes`),X(e,`/container`),X(e,`/ws`)]);return{health:t,metrics:n,routes:r,container:i,ws:a}}function ti(e,t){let{health:n,metrics:r,routes:i,container:a,ws:o}=t,s=Y(`─`.repeat(60));if(console.log(),console.log(J(` KickJS Inspector`)+Y(` → ${e}`)),console.log(s),n){let e=n.status===`healthy`?Jr(`● healthy`):Yr(`● `+n.status);console.log(` ${J(`Health:`)} ${e}`)}else console.log(` ${J(`Health:`)} ${Yr(`● unreachable`)}`);if(r){let e=((r.errorRate??0)*100).toFixed(1),t=r.errorRate>.1?Yr:r.errorRate>0?Xr:Jr;console.log(` ${J(`Uptime:`)} ${Qr(r.uptimeSeconds)}`),console.log(` ${J(`Requests:`)} ${r.requests}`),console.log(` ${J(`Errors:`)} ${r.serverErrors} server, ${r.clientErrors??0} client ${Y(`(`)}${t(e+`%`)}${Y(`)`)}`)}if(a&&console.log(` ${J(`DI:`)} ${a.count} bindings`),o&&o.enabled&&console.log(` ${J(`WS:`)} ${o.connections??0} connections, ${o.namespaces??0} namespaces`),i?.routes?.length){console.log(),console.log(J(` Routes`)),console.log(s),console.log(` ${Y(`METHOD`)} ${Y(`PATH`.padEnd(36))} ${Y(`CONTROLLER`)}`);for(let e of i.routes){let t=e.path.length>36?e.path.slice(0,33)+`...`:e.path.padEnd(36);console.log(` ${ft(e.method)} ${t} ${Zr(e.controller)}.${Y(e.handler)}`)}}console.log(s),console.log()}function ni(e){e.command(`inspect [url]`).description(`Connect to a running KickJS app and display debug info`).option(`-p, --port <port>`,`Override port`).option(`-w, --watch`,`Poll every 5 seconds`).option(`-j, --json`,`Output raw JSON`).action(async(e,t)=>{let n=e??`http://localhost:3000`;if(t.port)try{let e=new URL(n);e.port=t.port,n=e.origin}catch{n=`http://localhost:${t.port}`}let r=`${n.replace(/\/$/,``)}/_debug`,i=async()=>{try{let e=await ei(r);t.json?console.log(JSON.stringify(e,null,2)):ti(n,e)}catch(e){t.json?console.log(JSON.stringify({error:String(e)})):(console.error(Yr(` ✖ Could not connect to ${n}`)),console.error(Y(` ${e instanceof Error?e.message:String(e)}`))),t.watch||(process.exitCode=1)}};if(t.watch){let e=async()=>{process.stdout.write(`\x1B[2J\x1B[H`),await i()};await e(),setInterval(e,5e3)}else await i()})}function ri(e,t){let n=e.toLowerCase();return t.every(e=>n.includes(e.toLowerCase()))}function Z(e,t){let n=e.toLowerCase();return t.some(e=>n.includes(e.toLowerCase()))}const ii=[{match(e,t){let n=ri(e,[`config`,`get`])&&Z(e,[`undefined`,`null`]),r=e.includes(`@Value`)&&Z(e,[`undefined`,`is not defined`]);return!n&&!r?null:{confidence:n&&r?90:75,diagnosis:{id:`env-schema-not-registered`,title:`ConfigService.get() returns undefined for user-defined keys`,explanation:`Your src/index.ts is missing \`import "./config"\`. That side-effect import
|
|
3502
3688
|
registers the env schema with kickjs at module-load time. Without it,
|
|
3503
3689
|
ConfigService falls back to the base schema (PORT/NODE_ENV/LOG_LEVEL only)
|
|
3504
3690
|
and every user-defined key reads as undefined. @Value() may *appear* to
|
|
@@ -3510,7 +3696,7 @@ import { modules } from './modules'
|
|
|
3510
3696
|
import './config' // ← add this — registers env schema
|
|
3511
3697
|
import { bootstrap } from '@forinda/kickjs'
|
|
3512
3698
|
import { modules } from './modules'
|
|
3513
|
-
`,docs:`https://forinda.github.io/kick-js/guide/configuration.html#wiring-the-schema-at-startup`}}}},{match(e,t){let n=
|
|
3699
|
+
`,docs:`https://forinda.github.io/kick-js/guide/configuration.html#wiring-the-schema-at-startup`}}}},{match(e,t){let n=Z(e,[`vitest`,`test`,`spec`,`__tests__`,`.test.`]);return Z(e,[`already registered`,`already exists`,`duplicate`,`has been registered`])?{confidence:n?85:60,diagnosis:{id:`container-not-reset-in-tests`,title:`DI container leaks between test cases`,explanation:`KickJS decorators register classes on the global Container at import time.
|
|
3514
3700
|
When vitest re-imports your modules across tests, the same class can be
|
|
3515
3701
|
registered twice and the container throws. The fix is to wipe the
|
|
3516
3702
|
container between tests so each case starts fresh.`,fix:`Add Container.reset() to a beforeEach hook in the failing test file:`,codeAfter:`import { describe, it, beforeEach } from 'vitest'
|
|
@@ -3520,7 +3706,7 @@ describe('UserController', () => {
|
|
|
3520
3706
|
beforeEach(() => Container.reset())
|
|
3521
3707
|
|
|
3522
3708
|
it('does the thing', async () => { /* ... */ })
|
|
3523
|
-
})`,docs:`https://forinda.github.io/kick-js/guide/testing.html`}}:null}},{match(e,t){return e.includes(`@Module`)||
|
|
3709
|
+
})`,docs:`https://forinda.github.io/kick-js/guide/testing.html`}}:null}},{match(e,t){return e.includes(`@Module`)||ri(e,[`Module`,`is not a function`])||ri(e,[`Module`,`no exported member`])?{confidence:80,diagnosis:{id:`module-decorator-not-found`,title:`KickJS does not have a @Module decorator (different pattern from NestJS)`,explanation:`NestJS uses @Module({ controllers, providers }). KickJS uses an interface
|
|
3524
3710
|
pattern instead: a class implements AppModule and exposes routes() that
|
|
3525
3711
|
returns the controller wiring. This was a deliberate choice — modules
|
|
3526
3712
|
become explicit values rather than metadata, which makes them easier to
|
|
@@ -3547,7 +3733,7 @@ KickRoutes["POST /users"]. The new form is per-controller, per-method,
|
|
|
3547
3733
|
and matches the actual class names so refactors propagate via
|
|
3548
3734
|
rename-symbol instead of grep.`,fix:`Update the Ctx<...> type parameter to use the namespace form:`,codeBefore:`@Post('/', { body: createUserSchema })
|
|
3549
3735
|
create(ctx: Ctx<KickRoutes['POST /users']>) { /* ... */ }`,codeAfter:`@Post('/', { body: createUserSchema, name: 'CreateUser' })
|
|
3550
|
-
create(ctx: Ctx<KickRoutes.UserController['create']>) { /* ... */ }`,docs:`https://forinda.github.io/kick-js/guide/typegen.html`}}:null}},{match(e,t){let n=
|
|
3736
|
+
create(ctx: Ctx<KickRoutes.UserController['create']>) { /* ... */ }`,docs:`https://forinda.github.io/kick-js/guide/typegen.html`}}:null}},{match(e,t){let n=Z(e,[`cluster`,`workers`,`two ports`,`duplicate server`]),r=Z(e,[`kick dev`,`vite`,`eaddrinuse`,`5173`,`5174`,`two servers`]);return!n||!r?null:{confidence:85,diagnosis:{id:`cluster-in-vite-dev`,title:"Cluster mode is incompatible with `kick dev` (Vite owns the server)",explanation:`In dev mode, Vite owns the HTTP server. If your bootstrap passes
|
|
3551
3737
|
cluster: { workers: N }, the framework forks N workers, each of which
|
|
3552
3738
|
spins up its own Vite instance on a separate port. The fix landed in
|
|
3553
3739
|
v2.2.5: McpAdapter (and bootstrap()) now detects Vite dev mode and
|
|
@@ -3555,7 +3741,7 @@ silently skips cluster, with a warning. If you see this on an older
|
|
|
3555
3741
|
version, upgrade or guard the cluster option behind NODE_ENV.`,fix:`Either upgrade to v2.2.5+ or gate cluster mode on production:`,codeAfter:`export const app = await bootstrap({
|
|
3556
3742
|
modules,
|
|
3557
3743
|
cluster: process.env.NODE_ENV === 'production' ? { workers: 4 } : false,
|
|
3558
|
-
})`,docs:`https://forinda.github.io/kick-js/guide/cluster.html`}}}},{match(e,t){return
|
|
3744
|
+
})`,docs:`https://forinda.github.io/kick-js/guide/cluster.html`}}}},{match(e,t){return Z(e,[`reflect-metadata`,`Reflect.getMetadata is not a function`,`Reflect.defineMetadata`,`design:type`,`design:paramtypes`])?{confidence:90,diagnosis:{id:`reflect-metadata-missing`,title:`reflect-metadata is not loaded — DI cannot read decorator types`,explanation:`The DI container reads constructor parameter types via the
|
|
3559
3745
|
reflect-metadata polyfill. The polyfill must be imported once,
|
|
3560
3746
|
before any decorator runs. Most projects do this at the top of
|
|
3561
3747
|
src/index.ts; missing the import causes obscure "design:paramtypes"
|
|
@@ -3564,32 +3750,32 @@ import './config'
|
|
|
3564
3750
|
import { bootstrap } from '@forinda/kickjs'
|
|
3565
3751
|
import { modules } from './modules'
|
|
3566
3752
|
|
|
3567
|
-
export const app = await bootstrap({ modules })`,docs:`https://forinda.github.io/kick-js/guide/dependency-injection.html`}}:null}},{match(e,t){return
|
|
3753
|
+
export const app = await bootstrap({ modules })`,docs:`https://forinda.github.io/kick-js/guide/dependency-injection.html`}}:null}},{match(e,t){return Z(e,[`404`,`cannot get`,`cannot post`,`no route`])?{confidence:50,diagnosis:{id:`module-not-registered`,title:`A 404 may indicate a module is not in the modules array`,explanation:`KickJS only mounts modules listed in \`src/modules/index.ts\`. If you
|
|
3568
3754
|
generated a module via \`kick g module foo\` but the routes don't appear,
|
|
3569
3755
|
the most likely cause is that the module is missing from the exported
|
|
3570
3756
|
array. The CLI usually wires this automatically, but a hand-edit can
|
|
3571
|
-
drop the entry.`,fix:`Open src/modules/index.ts and verify the module is in the array:`,codeAfter:`import type {
|
|
3757
|
+
drop the entry.`,fix:`Open src/modules/index.ts and verify the module is in the array:`,codeAfter:`import type { AppModuleEntry } from '@forinda/kickjs'
|
|
3572
3758
|
import { UserModule } from './users/user.module'
|
|
3573
3759
|
import { TaskModule } from './tasks/task.module' // ← was this missing?
|
|
3574
3760
|
|
|
3575
|
-
export const modules:
|
|
3761
|
+
export const modules: AppModuleEntry[] = [UserModule(), TaskModule()]`,docs:`https://forinda.github.io/kick-js/guide/project-structure.html`}}:null}}];function ai(e,t){let n=null;for(let r of ii){let i=null;try{i=r.match(e,t)}catch{continue}!i||i.confidence<40||(!n||i.confidence>n.confidence)&&(n=i)}return n}async function oi(e){let t=e.provider??`openai`,n=process.env.OPENAI_API_KEY;if(t===`openai`&&!n)return{kind:`unavailable`,reason:`OPENAI_API_KEY environment variable is not set`,suggestion:`Set OPENAI_API_KEY in your shell, e.g.
|
|
3576
3762
|
export OPENAI_API_KEY="sk-..."
|
|
3577
3763
|
|
|
3578
3764
|
Then re-run \`kick explain --ai "<your error>"\`.`};let r;try{r=await import(`@forinda/kickjs-ai`)}catch{return{kind:`unavailable`,reason:`@forinda/kickjs-ai is not installed`,suggestion:`Install the AI package to enable the LLM fallback:
|
|
3579
3765
|
kick add ai
|
|
3580
3766
|
|
|
3581
3767
|
Or manually:
|
|
3582
|
-
pnpm add @forinda/kickjs-ai`}}let{OpenAIProvider:i}=r,a=new i({apiKey:n,defaultChatModel:e.model??`gpt-4o-mini`}),o=
|
|
3583
|
-
`)}function
|
|
3768
|
+
pnpm add @forinda/kickjs-ai`}}let{OpenAIProvider:i}=r,a=new i({apiKey:n,defaultChatModel:e.model??`gpt-4o-mini`}),o=si(e.cwd),s=`Error or stack trace:\n\n${e.input.trim()}`;try{let e=ci((await a.chat({messages:[{role:`system`,content:o},{role:`user`,content:s}]})).content);return e?{kind:`ok`,diagnosis:e}:{kind:`error`,message:`The LLM responded but the payload was not valid JSON in the expected shape. Try again, or file an issue with the error text.`}}catch(e){return{kind:`error`,message:`LLM request failed: ${e instanceof Error?e.message:String(e)}`}}}function si(e){return[`You are a diagnostic assistant for KickJS, a decorator-driven Node.js`,`framework built on Express 5 and TypeScript. KickJS projects use:`,` - @Controller, @Get, @Post, @Autowired, @Service, @Value decorators`,` - An AppModule interface with a routes() method (NOT a @Module decorator)`,` - Zod schemas as both runtime validators and OpenAPI sources`,` - Ctx<KickRoutes.ControllerName['method']> for typed request context`,` - src/config/index.ts with defineEnv/loadEnv for env schema`,' - A side-effect `import "./config"` in src/index.ts to register the schema',` - Container.reset() in beforeEach for DI test isolation`,``,`When the user gives you an error message or stack trace, produce a`,`structured diagnosis that helps them fix the bug. You MUST respond`,`with a single JSON object (no surrounding prose, no markdown fences)`,`matching this shape:`,``,`{`,` "id": "<kebab-case-identifier>",`,` "title": "<one-line problem summary>",`,` "explanation": "<multi-line explanation of what is wrong>",`,` "fix": "<multi-line instructions for fixing the problem>",`,` "codeBefore": "<optional: broken code snippet>",`,` "codeAfter": "<optional: corrected code snippet>",`,` "docs": "<optional: KickJS doc URL that discusses this topic>"`,`}`,``,`The KickJS docs live at https://forinda.github.io/kick-js/ — prefer`,`that domain for any doc links you suggest.`,e?`The project is located at ${e}.`:``].filter(e=>e.length>0).join(`
|
|
3769
|
+
`)}function ci(e){let t=[e,li(e),ui(e)].filter(e=>e!==null);for(let e of t)try{let t=JSON.parse(e);if(di(t))return t}catch{continue}return null}function li(e){let t=e.match(/```(?:json)?\s*\n([\s\S]*?)```/);return t?t[1]?.trim()??null:null}function ui(e){let t=e.indexOf(`{`);if(t===-1)return null;let n=0,r=!1,i=!1;for(let a=t;a<e.length;a++){let o=e[a];if(i){i=!1;continue}if(o===`\\`&&r){i=!0;continue}if(o===`"`){r=!r;continue}if(!r&&(o===`{`&&n++,o===`}`&&(n--,n===0)))return e.slice(t,a+1)}return null}function di(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.id==`string`&&typeof t.title==`string`&&typeof t.explanation==`string`&&typeof t.fix==`string`}function fi(e){e.command(`explain [message]`).description(`Explain a KickJS error and suggest a fix`).option(`-m, --message <text>`,`Error message to explain (alternative to positional arg)`).option(`--ai`,`Fall back to LLM if no known-issue matches (requires @forinda/kickjs-ai)`).option(`--model <name>`,`Model name for the --ai fallback`,`gpt-4o-mini`).option(`--json`,`Output the diagnosis as JSON for tooling integration`).action(async(e,t)=>{let n=await hi(e,t.message);(!n||n.trim().length===0)&&(process.stderr.write(`Error: no input provided.
|
|
3584
3770
|
|
|
3585
3771
|
Pass a message as a positional arg, --message flag, or pipe via stdin:
|
|
3586
3772
|
kick explain "config.get returned undefined"
|
|
3587
3773
|
pnpm test 2>&1 | kick explain
|
|
3588
|
-
`),process.exit(1));let r=
|
|
3589
|
-
`);return}if(i){
|
|
3590
|
-
`),process.exit(2)),
|
|
3591
|
-
`),process.exit(a.kind===`ok`?0:2)),
|
|
3592
|
-
`)}function
|
|
3774
|
+
`),process.exit(1));let r=_i(),i=ai(n,r);if(t.json&&i){process.stdout.write(JSON.stringify({matched:!0,...i},null,2)+`
|
|
3775
|
+
`);return}if(i){vi(n,i.diagnosis,i.confidence);return}t.ai||(t.json&&(process.stdout.write(JSON.stringify({matched:!1},null,2)+`
|
|
3776
|
+
`),process.exit(2)),yi(n,!1),process.exit(2));let a=await oi({input:n,model:t.model,cwd:r.cwd});t.json&&(process.stdout.write(JSON.stringify(pi(a),null,2)+`
|
|
3777
|
+
`),process.exit(a.kind===`ok`?0:2)),mi(n,a),process.exit(a.kind===`ok`?0:2)})}function pi(e){return e.kind===`ok`?{matched:!0,source:`ai`,diagnosis:e.diagnosis}:e.kind===`unavailable`?{matched:!1,aiUnavailable:!0,reason:e.reason}:{matched:!1,aiError:!0,error:e.message}}function mi(e,t){if(t.kind===`ok`){vi(e,t.diagnosis,-1,!0);return}if(t.kind===`unavailable`){process.stdout.write(`\n Explaining: ${xi(e.trim(),200)}\n\n`),process.stdout.write(` AI fallback unavailable: ${t.reason}\n\n`),process.stdout.write(`${bi(t.suggestion,` `)}\n\n`);return}process.stdout.write(`\n Explaining: ${xi(e.trim(),200)}\n\n`),process.stdout.write(` AI fallback error: ${t.message}\n\n`)}async function hi(e,t){return e&&e.trim().length>0?e:t&&t.trim().length>0?t:process.stdin.isTTY?``:gi()}function gi(){return new Promise((e,t)=>{let n=``;process.stdin.setEncoding(`utf8`),process.stdin.on(`data`,e=>{n+=e}),process.stdin.on(`end`,()=>e(n)),process.stdin.on(`error`,t)})}function _i(){let e=process.cwd();return{cwd:e,hasFile:t=>h(C(e,t))}}function vi(e,t,n,r=!1){let i=xi(e.trim(),200),a=r?`AI-generated — verify before applying`:Si(n);process.stdout.write(`\n Explaining: ${i}\n`),process.stdout.write(`\n Match: ${t.id} (${a})\n`),process.stdout.write(` Title: ${t.title}\n`),process.stdout.write(`\n Diagnosis:\n${bi(t.explanation,` `)}\n`),process.stdout.write(`\n Fix:\n${bi(t.fix,` `)}\n`),t.codeBefore&&process.stdout.write(`\n Before:\n${bi(t.codeBefore,` `)}\n`),t.codeAfter&&process.stdout.write(`\n After:\n${bi(t.codeAfter,` `)}\n`),t.docs&&process.stdout.write(`\n Docs: ${t.docs}\n`),process.stdout.write(`
|
|
3778
|
+
`)}function yi(e,t){let n=xi(e.trim(),200);process.stdout.write(`\n Explaining: ${n}\n\n`),t?process.stdout.write(` No known-issue matched, and --ai fallback is not yet wired.
|
|
3593
3779
|
When @forinda/kickjs-ai ships its provider implementations,
|
|
3594
3780
|
this command will call the configured LLM with the error +
|
|
3595
3781
|
project context and return a structured fix.
|
|
@@ -3606,12 +3792,12 @@ Pass a message as a positional arg, --message flag, or pipe via stdin:
|
|
|
3606
3792
|
3. File an issue with the error text:
|
|
3607
3793
|
https://github.com/forinda/kick-js/issues/new
|
|
3608
3794
|
|
|
3609
|
-
`)}function
|
|
3795
|
+
`)}function bi(e,t){return e.split(`
|
|
3610
3796
|
`).map(e=>`${t}${e}`).join(`
|
|
3611
|
-
`)}function
|
|
3612
|
-
`,`utf8`),process.stdout.write(`\n ✓ Wrote MCP server entry "${r}" to ${i}\n\n To activate it:\n 1. Build your app: kick build\n 2. Restart your MCP client (Claude Code, Cursor, Zed)\n 3. The server should appear in the client's tool picker\n\n`)}function
|
|
3797
|
+
`)}function xi(e,t){return e.length<=t?e:e.slice(0,t-1)+`…`}function Si(e){return e>=90?`high confidence`:e>=70?`good match`:e>=50?`medium confidence`:`low confidence — verify manually`}function Ci(e){let t=e.command(`mcp`).description(`Model Context Protocol commands (start | init)`);t.command(`start`,{isDefault:!0}).description(`Run the built application as an MCP server over stdio`).option(`-e, --entry <file>`,`Entry file`,`dist/index.js`).option(`--node-arg <arg...>`,`Extra arguments to pass to node`).action(wi),t.command(`init`).description(`Generate .mcp.json for Claude Code / Cursor / Zed`).option(`-n, --name <name>`,`Server name (defaults to package.json name)`).option(`-o, --out <file>`,`Output file`,`.mcp.json`).option(`-f, --force`,`Overwrite an existing entry without prompting`).option(`--global`,`Write to ~/.mcp.json instead of the project root`).action(Ti)}function wi(e){let t=C(e.entry);h(t)||(process.stderr.write(`Error: entry file not found: ${t}\n\nBuild the app first with \`kick build\`, or pass a custom entry:\n kick mcp -e dist/server.js\n`),process.exit(1));let n=[...e.nodeArg??[],t],r=le(process.execPath,n,{stdio:`inherit`,env:{...process.env,KICK_MCP_STDIO:`1`,NODE_ENV:process.env.NODE_ENV??`production`}});r.on(`error`,e=>{process.stderr.write(`Failed to start MCP server: ${e.message}\n`),process.exit(1)}),r.on(`exit`,(e,t)=>{if(t){process.kill(process.pid,t);return}process.exit(e??0)});let i=e=>{r.killed||r.kill(e)};process.on(`SIGINT`,()=>i(`SIGINT`)),process.on(`SIGTERM`,()=>i(`SIGTERM`))}function Ti(e){let t=process.cwd(),n=Ei(t)??re(t),r=e.name??n,i=e.global?C(process.env.HOME??`.`,`.mcp.json`):C(t,e.out),a={command:`kick`,args:[`mcp`],cwd:t},o={mcpServers:{}};if(h(i))try{let e=_(i,`utf8`),t=JSON.parse(e);t&&typeof t==`object`&&t.mcpServers&&(o={mcpServers:{...t.mcpServers}})}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`Error: existing ${i} is not valid JSON (${t}).\nFix the file or pass --force to overwrite the entry.\n`),process.exit(1)}o.mcpServers[r]&&!e.force&&(process.stderr.write(`Error: an entry for "${r}" already exists in ${i}.\nPass --force to overwrite it, or use --name to pick a different key.\n`),process.exit(1)),o.mcpServers[r]=a,ne(i,JSON.stringify(o,null,2)+`
|
|
3798
|
+
`,`utf8`),process.stdout.write(`\n ✓ Wrote MCP server entry "${r}" to ${i}\n\n To activate it:\n 1. Build your app: kick build\n 2. Restart your MCP client (Claude Code, Cursor, Zed)\n 3. The server should appear in the client's tool picker\n\n`)}function Ei(e){let t=C(e,`package.json`);if(!h(t))return null;try{let e=_(t,`utf8`),n=JSON.parse(e);return typeof n.name==`string`?n.name:null}catch{return null}}function Di(e){e.command(`tinker`).description(`Interactive REPL with DI container and services loaded`).option(`-e, --entry <file>`,`Entry file to load`,`src/index.ts`).action(async e=>{let t=process.cwd(),n=C(t,e.entry);h(n)||(console.error(`\n Error: ${e.entry} not found.\n`),process.exit(1));let r=ki(t,`tsx`);r||(console.error(`
|
|
3613
3799
|
Error: tsx not found. Install it: pnpm add -D tsx
|
|
3614
|
-
`),process.exit(1));let i=
|
|
3800
|
+
`),process.exit(1));let i=Oi(n,e.entry),a=x(t,`.kick-tinker.mjs`),{writeFileSync:o,unlinkSync:s}=await import(`node:fs`);o(a,i,`utf-8`);try{let e=ce(a,[],{cwd:t,execPath:r,stdio:`inherit`});await new Promise(t=>{e.on(`exit`,()=>t())})}finally{try{s(a)}catch{}}})}function Oi(e,t){return`
|
|
3615
3801
|
import 'reflect-metadata'
|
|
3616
3802
|
|
|
3617
3803
|
// Prevent bootstrap() from starting the HTTP server
|
|
@@ -3636,7 +3822,7 @@ try {
|
|
|
3636
3822
|
|
|
3637
3823
|
// Load entry to trigger decorator registration
|
|
3638
3824
|
try {
|
|
3639
|
-
await import('${
|
|
3825
|
+
await import('${w(e).href}')
|
|
3640
3826
|
} catch (err) {
|
|
3641
3827
|
console.warn(' Warning: ' + err.message)
|
|
3642
3828
|
console.warn(' Container may be partially initialized.\\n')
|
|
@@ -3665,21 +3851,30 @@ server.on('exit', () => {
|
|
|
3665
3851
|
console.log('\\n Goodbye!\\n')
|
|
3666
3852
|
process.exit(0)
|
|
3667
3853
|
})
|
|
3668
|
-
`}function
|
|
3854
|
+
`}function ki(e,t){let n=e;for(;;){let e=x(n,`node_modules`,`.bin`,t);if(h(e))return e;let r=C(n,`..`);if(r===n)break;n=r}return null}async function Ai(e){let{name:t,modulesDir:n,force:r}=e,i=e.pluralize!==!1,a=B(t),o=R(t),s=i?V(a):a,c=x(n,s);if(!await Re(c)){console.log(`\n Module not found: ${c}\n`);return}if(!r&&!await F({message:k.red(`Delete module '${s}' at ${c}? This cannot be undone.`),initialValue:!1})){console.log(`
|
|
3669
3855
|
Cancelled.
|
|
3670
|
-
`);return}await
|
|
3856
|
+
`);return}await he(c,{recursive:!0,force:!0}),console.log(` Deleted: ${c}`);let l=x(n,`index.ts`);if(await Re(l)){let e=await E(l,`utf-8`),t=e,n=RegExp(`^import\\s*\\{\\s*${o}Module\\s*\\}\\s*from\\s*['"][^'"]*${s}(?:/[^'"]*)?['"].*\\n?`,`gm`);e=e.replace(n,``),e=e.replace(RegExp(`\\s*,?\\s*${o}Module(?:\\s*\\(\\s*\\))?\\s*,?`,`g`),e=>{let t=e.trimStart().startsWith(`,`),n=e.trimEnd().endsWith(`,`);return t&&n?`,`:``}),e=e.replace(/,(\s*])/,`$1`),e=e.replace(/\n{3,}/g,`
|
|
3671
3857
|
|
|
3672
|
-
`),e!==t&&(await
|
|
3858
|
+
`),e!==t&&(await D(l,e,`utf-8`),console.log(` Unregistered: ${o}Module from ${l}`))}console.log(`\n Module '${s}' removed.\n`)}function ji(e){e.command(`remove`).alias(`rm`).description(`Remove generated code`).command(`module <names...>`).description(`Remove one or more modules (e.g. kick rm module user task)`).option(`--modules-dir <dir>`,`Modules directory`).option(`--no-pluralize`,`Use singular module name`).option(`-f, --force`,`Skip confirmation prompt`).action(async(e,t)=>{let i=n(await r(process.cwd())),a=t.modulesDir??i.dir??`src/modules`,o=t.pluralize===!1?!1:i.pluralize??!0;for(let n of e)await Ai({name:n,modulesDir:C(a),force:t.force,pluralize:o})})}function Mi(e){if(e!==void 0){if(e===`false`||e===`off`||e===`none`)return!1;if(e===`zod`)return`zod`;console.warn(` kick typegen: unknown --schema-validator '${e}' (only 'zod' and 'false' are supported). Falling back to project config.`)}}function Ni(e){if(e!==void 0)return e===`false`||e===`off`||e===`none`?!1:e}function Pi(e){e.command(`typegen`).description(`Generate type-safe DI registry and module types into .kickjs/types/`).option(`-w, --watch`,`Watch source files and regenerate on change`).option(`-s, --src <dir>`,`Source directory to scan`,`src`).option(`-o, --out <dir>`,`Output directory`,`.kickjs/types`).option(`--silent`,`Suppress output`).option(`--allow-duplicates`,`Auto-namespace duplicate class names instead of failing (use with caution)`).option(`--schema-validator <name>`,`Schema validator for body/query/params typing (currently 'zod' or 'false')`).option(`--env-file <path>`,`Path to env schema file for KickEnv typing (default 'src/env.ts'; pass 'false' to disable)`).option(`--check`,`CI gate: fail on plugin-typegen drift instead of writing`).option(`--list`,"List every registered typegen plugin id (use to populate `typegen.disable`)").action(async e=>{let t=process.cwd(),n=await r(t);if(e.list){let{mergeCliPlugins:e}=await import(`./plugin-D0C4ISZA.mjs`).then(e=>e.t),{builtinCliPlugins:t}=await Promise.resolve().then(()=>ga),r=e([...t,...n?.plugins??[]],n?.commands??[]),i=new Set(n?.typegen?.disable??[]);if(r.typegens.length===0){console.log(` No typegen plugins registered.`);return}let a=Math.max(...r.typegens.map(e=>e.id.length));console.log(`
|
|
3673
3859
|
Registered typegen plugins:
|
|
3674
|
-
`);for(let e of r.typegens){let t=i.has(e.id)?` (disabled)`:``;console.log(` ${e.id.padEnd(a+2)}inputs: ${e.inputs.join(`, `)||`(none)`}${t}`)}console.log();return}let i=
|
|
3860
|
+
`);for(let e of r.typegens){let t=i.has(e.id)?` (disabled)`:``;console.log(` ${e.id.padEnd(a+2)}inputs: ${e.inputs.join(`, `)||`(none)`}${t}`)}console.log();return}let i=Mi(e.schemaValidator)??n?.typegen?.schemaValidator??`zod`,a=Ni(e.envFile)??n?.typegen?.envFile,o={cwd:t,srcDir:e.src??n?.typegen?.srcDir,outDir:e.out??n?.typegen?.outDir,silent:e.silent,allowDuplicates:e.allowDuplicates,schemaValidator:i,envFile:a,assetMap:n?.assetMap,runPlugins:!1};try{if(e.watch){let t=await u(o);e.silent||console.log(` kick typegen: watching for changes (Ctrl-C to exit)`);let n=()=>{t(),process.exit(0)};process.on(`SIGINT`,n),process.on(`SIGTERM`,n),await new Promise(()=>{})}else{await f(o);let r=await Rr({cwd:t,config:n??null,silent:e.silent,check:e.check});e.check&&r.some(e=>e.status===`written`)&&process.exit(1)}}catch(e){e instanceof c?console.error(`
|
|
3675
3861
|
`+e.message+`
|
|
3676
|
-
`):e instanceof Error?console.error(`\n kick typegen failed: ${e.message}`):console.error(`\n kick typegen failed: ${JSON.stringify(e)}`),process.exit(1)}})}function
|
|
3862
|
+
`):e instanceof Error?console.error(`\n kick typegen failed: ${e.message}`):console.error(`\n kick typegen failed: ${JSON.stringify(e)}`),process.exit(1)}})}function Fi(e){let t=[];if(!h(e))return t;let n=ee(e,{withFileTypes:!0});for(let r of n){let n=x(e,r.name);if(r.isDirectory()){if([`node_modules`,`dist`,`.kickjs`,`.git`].includes(r.name))continue;t.push(...Fi(n))}else r.isFile()&&/\.tsx?$/.test(r.name)&&!r.name.endsWith(`.d.ts`)&&t.push(n)}return t}function Ii(e){try{return _(e,`utf-8`)}catch{return``}}const Li=new Set([`secret`,`changeme`,`password`,`test`,`default`,``]);function Ri(e,t){let n=Ii(x(e,`.env`));if(n){let e=n.match(/^JWT_SECRET\s*=\s*['"]?([^'"\n]*)['"]?/m);if(e){let t=e[1].trim();if(Li.has(t.toLowerCase())||t.length<32)return{severity:`CRITICAL`,message:`JWT_SECRET appears to be a default value or too short (< 32 chars) — change it`}}}for(let e of t)for(let t of[/JWT_SECRET['"]?\s*[:=]\s*['"]?(secret|changeme|password|test|default)['"]?/i,/secret\s*[:=]\s*['"]?(secret|changeme|password|test|default)['"]?/i])if(t.test(e))return{severity:`CRITICAL`,message:`JWT_SECRET appears to be a default value in source code — use an environment variable`};return null}function zi(e){for(let t of e)if(/cors\s*\(/.test(t)&&/origin\s*:\s*['"]\*['"]/.test(t))return{severity:`CRITICAL`,message:`CORS origin is '*' — restrict to your domains`};return null}function Bi(e){for(let t of e)if(/rateLimit/i.test(t)||/@RateLimit/i.test(t))return null;return{severity:`WARNING`,message:`No rate limiting detected — add rateLimit() middleware or @RateLimit decorator`}}function Vi(){return process.env.NODE_ENV===`production`?null:{severity:`WARNING`,message:`NODE_ENV is '${process.env.NODE_ENV??`undefined`}', not 'production'`}}function Hi(e){let t=!1,n=!1;for(let r of e)/tokenStore/i.test(r)&&(t=!0),/MemoryTokenStore/i.test(r)&&(n=!0);return n?{severity:`WARNING`,message:`MemoryTokenStore detected — use a persistent store (Redis, DB) for production deployments`}:t?null:{severity:`WARNING`,message:`No token revocation store detected — consider adding one for auth token management`}}function Ui(e){for(let t of e)if(/helmet\s*\(/.test(t))return/security\s*\.\s*helmet\s*.*false/.test(t)?{severity:`WARNING`,message:`Helmet security headers are disabled — enable them for production`}:{severity:`INFO`,message:`Helmet security headers active`};return{severity:`WARNING`,message:`Helmet not detected — add helmet() middleware for security headers`}}function Wi(e){for(let t of e)if(/AuthAdapter/i.test(t))return{severity:`INFO`,message:`AuthAdapter configured`};return{severity:`INFO`,message:`No AuthAdapter detected — add one if your app requires authentication`}}function Gi(e){let t=Fi(x(e,`src`)).map(e=>Ii(e)),n=[],r=Ri(e,t);r&&n.push(r);let i=zi(t);i&&n.push(i);let a=Bi(t);a&&n.push(a);let o=Vi();o&&n.push(o);let s=Hi(t);return s&&n.push(s),n.push(Ui(t)),n.push(Wi(t)),n}function Ki(e){e.command(`check`).description(`Audit project for common issues`).option(`--deploy`,`Run production readiness checks`).action(e=>{if(!e.deploy){console.log(`
|
|
3677
3863
|
Usage: kick check --deploy
|
|
3678
3864
|
|
|
3679
3865
|
Available checks:
|
|
3680
3866
|
--deploy Audit for production readiness (security, config, best practices)
|
|
3681
|
-
`);return}let t=process.cwd();
|
|
3682
|
-
|
|
3867
|
+
`);return}let t=process.cwd();mt(`KickJS Deploy Check`);let n=yt();n.start(`Scanning project...`);let r=Gi(t);n.stop(`Scan complete`);let i={CRITICAL:0,WARNING:1,INFO:2};r.sort((e,t)=>i[e.severity]-i[t.severity]);for(let e of r)I.message(`${pt(e.severity)} ${e.message}`);let a=r.filter(e=>e.severity===`CRITICAL`).length,o=r.filter(e=>e.severity===`WARNING`).length,s=r.filter(e=>e.severity===`INFO`).length,c=o===1?`warning`:`warnings`,l=[a>0?k.red(`${a} critical`):`${a} critical`,o>0?k.yellow(`${o} ${c}`):`${o} ${c}`,`${s} info`].join(`, `);a>0?(P(k.red(`${l} — fix critical issues before deploying`)),process.exit(1)):P(k.green(`${l} — looking good!`))})}async function Q(e){return Ae({configPath:y.resolve(process.cwd(),e.config)})}async function $(e){if(e.adapter){let t=await e.adapter();return{adapter:t,cleanup:async()=>t.close()}}if(!e.connectionString)throw Error(`kickjs-db: no adapter resolved — set db.connectionString (or DATABASE_URL) in kick.config.ts, or supply db.adapter() factory`);let t=e.dialect??`postgres`;if(t!==`postgres`)throw Error(`kickjs-db: built-in CLI adapter only supports postgres in M1 (dialect=${t}); use db.adapter() factory for other dialects`);let[{pgAdapter:n},r]=await Promise.all([import(`@forinda/kickjs-db-pg`),import(`pg`)]),i=new r.default.Pool({connectionString:e.connectionString}),a=n({pool:i});return{adapter:a,cleanup:async()=>{await a.close(),await i.end()}}}function qi(e){if(e.length===0){console.log(`No migrations.`);return}console.table(e.map(e=>({id:e.id,state:e.state,batch:e.batch??`-`,reviewed:e.reviewed,applied:e.appliedAt??`-`})))}function Ji(e){let t=e.command(`db`).description(`Database commands (kickjs-db)`);t.command(`generate <name>`).description(`Generate a new migration from schema diff`).option(`-c, --config <path>`,`Path to kick.config.ts`,`kick.config.ts`).option(`-e, --empty`,`Skip schema diff and create an empty migration shell (data migration, seed, freeform SQL)`).action(async(e,t)=>{let n=process.cwd(),r=await Ce({name:e,config:await Q(t),cwd:n,empty:t.empty});if(r.status===`no-changes`){console.log(`No schema changes detected.`);return}if(r.empty){console.log(`Created empty migration ${r.migrationDir} (author up.sql + down.sql).`);return}let i=r.changeCount===1?``:`s`;console.log(`Created migration ${r.migrationDir} (${r.changeCount} change${i}).`)});let n=t.command(`migrate`).description(`Migration runner subcommands`);n.command(`latest`).description(`Apply all pending migrations in a new batch`).option(`-c, --config <path>`,`Path to kick.config.ts`,`kick.config.ts`).option(`--confirm-enum-drop`,"Allow migrations carrying the `-- KICK ENUM REMOVE` header to apply",!1).action(async e=>{let t=await Q(e),{adapter:n,cleanup:r}=await $(t);try{let r=await Te({adapter:n,migrationsDir:t.migrationsDir,confirmEnumDrop:e.confirmEnumDrop});r.applied.length===0?console.log(`No pending migrations.`):console.log(`Applied batch ${r.batch}: ${r.applied.join(`, `)}`)}finally{await r()}}),n.command(`up`).description(`Apply the next single pending migration`).option(`-c, --config <path>`,`Path to kick.config.ts`,`kick.config.ts`).option(`--confirm-enum-drop`,"Allow migrations carrying the `-- KICK ENUM REMOVE` header to apply",!1).action(async e=>{let t=await Q(e),{adapter:n,cleanup:r}=await $(t);try{let r=await Oe({adapter:n,migrationsDir:t.migrationsDir,confirmEnumDrop:e.confirmEnumDrop});r.applied.length===0?console.log(`No pending migrations.`):console.log(`Applied ${r.applied[0]} (batch ${r.batch})`)}finally{await r()}}),n.command(`down`).description(`Reverse the most recent applied migration`).option(`-c, --config <path>`,`Path to kick.config.ts`,`kick.config.ts`).action(async e=>{let t=await Q(e),{adapter:n,cleanup:r}=await $(t);try{let e=await we({adapter:n,migrationsDir:t.migrationsDir});e.reversed?console.log(`Reversed ${e.reversed}.`):console.log(`Nothing to reverse.`)}finally{await r()}}),n.command(`rollback`).description(`Reverse the entire last batch as a single unit`).option(`-c, --config <path>`,`Path to kick.config.ts`,`kick.config.ts`).action(async e=>{let t=await Q(e),{adapter:n,cleanup:r}=await $(t);try{let e=await Ee({adapter:n,migrationsDir:t.migrationsDir});e.reversed.length===0?console.log(`Nothing to roll back.`):console.log(`Rolled back batch ${e.batch}: ${e.reversed.join(`, `)}`)}finally{await r()}}),n.command(`status`).description(`Print applied + pending migrations`).option(`-c, --config <path>`,`Path to kick.config.ts`,`kick.config.ts`).action(async e=>{let t=await Q(e),{adapter:n,cleanup:r}=await $(t);try{qi(await De({adapter:n,migrationsDir:t.migrationsDir}))}finally{await r()}}),t.command(`introspect`).description(`Generate a TypeScript schema file from a live database`).option(`-c, --config <path>`,`Path to kick.config.ts`,`kick.config.ts`).option(`--out <path>`,`Output file (defaults to db.schemaPath from config)`).option(`--json`,`Print the raw SchemaSnapshot JSON to stdout instead of writing TS source`).action(async e=>{let t=await Q(e),{adapter:n,cleanup:r}=await $(t);try{let r=await n.introspect();if(e.json){console.log(JSON.stringify(r,null,2));return}let i=e.out??t.schemaPath;await D(i,ke(r),`utf8`);let a=Object.keys(r.tables).length;console.log(`Wrote ${i} (${a} table${a===1?``:`s`}).`)}finally{await r()}})}function Yi(e){return e.optsWithGlobals().dryRun??!1}function Xi(e){e.command(`codemod`).description(`Codebase migration commands (AST-style rewrites — distinct from db migrate)`).command(`modules`).description(`Rewrite module declarations between class form and the defineModule factory.
|
|
3868
|
+
Direction defaults to \`modules.style\` from kick.config (or "define").
|
|
3869
|
+
--target define|class Override the migration direction.
|
|
3870
|
+
--apply Apply the changes (default: dry-run preview).
|
|
3871
|
+
--experimental Acknowledge that AST migration is experimental.`).option(`--modules-dir <dir>`,`Modules directory (default: src/modules from kick.config)`).option(`--apply`,`Apply the migration to disk (default: dry-run)`).option(`--experimental`,`Acknowledge that this command is experimental`).option(`--target <style>`,`Migration direction — 'define' or 'class'`).option(`--no-backup`,`Skip the .kickjs/codemod-backups/ snapshot (default: backup on)`).action(async(e,t)=>{let i=Yi(t)||!e.apply;A(i),e.experimental||(console.error(`
|
|
3872
|
+
`+k.red(`Error:`)+` kick codemod modules is experimental — pass --experimental to acknowledge.
|
|
3873
|
+
The regex-based rewrite handles the shapes our templates produce.
|
|
3874
|
+
Hand-rolled modules with non-standard structures may be skipped.
|
|
3875
|
+
Always commit before running with --apply.
|
|
3876
|
+
`),process.exit(1));let a=n(await r(process.cwd())),o=C(e.modulesDir??a.dir??`src/modules`),s;e.target===`define`||e.target===`class`?s=e.target:e.target===void 0?s=a.style??`define`:(console.error(`\n ${k.red(`Error:`)} --target must be 'define' or 'class' (got '${e.target}').\n`),process.exit(1));let c=k.dim(`→ ${s}`),l=i?k.dim(`(dry-run)`):k.bold(`(applying)`);console.log(`\n ${k.bold(`kick codemod modules`)} ${c} ${l}`),console.log(` modulesDir: ${k.dim(o)}\n`);let u=e.backup!==!1&&!i,d=await rr(o,{dryRun:i,target:s,backup:u});if(d.backupDir){let e=d.backupDir;console.log(` ${k.green(`✓`)} backup: ${k.dim(e)}\n ${k.dim(`(restore: rm -rf <modulesDir> && mv "<backup>" <modulesDir>)`)}\n`)}else !i&&e.backup===!1&&console.log(` ${k.dim(`(--no-backup — skipping snapshot)`)}\n`);let f=0,p=0;for(let e of d.files)if(e.status===`migrated`)f++,console.log(` ${k.green(`✓`)} ${e.path}`);else{p++;let t=k.dim(`(${e.reason??`skipped`})`);console.log(` ${k.dim(`-`)} ${e.path} ${t}`)}if(console.log(),d.indexStatus===`migrated`)console.log(` ${k.green(`✓`)} ${d.indexPath}`);else if(d.indexStatus===`skipped`){let e=k.dim(`(${d.indexReason??`skipped`})`);console.log(` ${k.dim(`-`)} ${d.indexPath} ${e}`)}else console.log(` ${k.dim(`-`)} ${d.indexPath} ${k.dim(`(not found)`)}`);let m=i?k.dim(` (dry-run — pass --apply to write)`):``;console.log(`\n ${k.bold(String(f))} migrated, ${k.bold(String(p))} skipped${m}\n`)})}const Zi=[`src/db/schema.ts`,`src/db/schema/index.ts`,`src/db/schema`],Qi=()=>({id:`kick/db`,inputs:[`src/db/schema.ts`,`src/db/schema/**/*.ts`],async generate(e){let t=$i(e.cwd);if(!t)return null;let n=y.resolve(e.cwd,`.kickjs/types`);return[`import type { SchemaToTypes, SchemaToRelationsRegister, KickDbClient } from '@forinda/kickjs-db'`,`import type * as appSchema from '${ea(y.relative(n,t)).replace(/\.ts$/,``).replace(/\/index$/,``)}'`,``,`declare global {`,` interface KickDbSchema extends SchemaToTypes<typeof appSchema> {}`,`}`,``,`declare module '@forinda/kickjs-db' {`,` interface KickDbRegister {`,` db: KickDbClient<KickDbSchema>`,` }`,``,` interface KickDbRelationsRegister {`,` db: SchemaToRelationsRegister<typeof appSchema>`,` }`,`}`].join(`
|
|
3877
|
+
`)}});function $i(e){for(let t of Zi){let n=y.resolve(e,t);if(t.endsWith(`.ts`)){if(h(n))return n}else{let e=y.join(n,`index.ts`);if(h(e))return e}}return null}function ea(e){return e.replace(/\\/g,`/`)}const ta=()=>({id:`kick/assets`,inputs:[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`],async generate(e){if(!h(y.resolve(e.cwd,`kick.config.ts`)))return null;let t=await r(e.cwd);if(!t?.assetMap)return null;let n=s(t.assetMap,e.cwd);return n.count===0?null:l(n)}}),na="/* eslint-disable */\n// AUTO-GENERATED by `kick typegen`. DO NOT EDIT.\n// Re-run with `kick typegen` or rely on `kick dev` to refresh.\n";function ra(e,t,n){if(e.length===0)return`${na}
|
|
3683
3878
|
// (no routes discovered yet — annotate a controller method with
|
|
3684
3879
|
// @Get/@Post/@Put/@Delete/@Patch and re-run \`kick typegen\`)
|
|
3685
3880
|
declare global {
|
|
@@ -3688,8 +3883,8 @@ declare global {
|
|
|
3688
3883
|
}
|
|
3689
3884
|
|
|
3690
3885
|
export {}
|
|
3691
|
-
`;let r=new Map;for(let t of e){let e=r.get(t.controller)??[];e.push(t),r.set(t.controller,e)}let i=new Map,a=(e,r)=>{let a=
|
|
3692
|
-
`))}return`${
|
|
3886
|
+
`;let r=new Map;for(let t of e){let e=r.get(t.controller)??[];e.push(t),r.set(t.controller,e)}let i=new Map,a=(e,r)=>{let a=oa(e,r,t,n,i);return a?`import('zod').infer<typeof ${a}>`:null},o=[];for(let[e,t]of r){let n=[` interface ${e} {`];for(let e of t){let t=e.pathParams.length>0?`{ ${e.pathParams.map(e=>`${e}: string`).join(`; `)} }`:`{}`,r=a(e.bodySchema,e.filePath),i=a(e.querySchema,e.filePath),o=a(e.paramsSchema,e.filePath)??t,s=r??`unknown`,c=i??ia(e),l=aa(e);n.push(` /**`,` * ${e.httpMethod} ${e.path}`,...l.map(e=>` * ${e}`),` */`,` ${e.method}: {`,` params: ${o}`,` body: ${s}`,` query: ${c}`,` response: unknown`,` }`)}n.push(` }`),o.push(n.join(`
|
|
3887
|
+
`))}return`${na}${sa(i)}
|
|
3693
3888
|
declare global {
|
|
3694
3889
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
3695
3890
|
namespace KickRoutes {
|
|
@@ -3699,9 +3894,9 @@ ${o.join(`
|
|
|
3699
3894
|
}
|
|
3700
3895
|
|
|
3701
3896
|
export {}
|
|
3702
|
-
`}function
|
|
3897
|
+
`}function ia(e){if(e.queryFilterable===null)return`unknown`;let t=e.querySortable??[];return`{ filter?: string | string[]; sort?: ${t.length>0?t.flatMap(e=>[`'${e}'`,`'-${e}'`]).join(` | `):`string`}; q?: string; page?: string; limit?: string }`}function aa(e){let t=[];return e.queryFilterable&&e.queryFilterable.length>0&&t.push(`Filterable: ${e.queryFilterable.join(`, `)}`),e.querySortable&&e.querySortable.length>0&&t.push(`Sortable: ${e.querySortable.join(`, `)}`),e.querySearchable&&e.querySearchable.length>0&&t.push(`Searchable: ${e.querySearchable.join(`, `)}`),t}function oa(e,t,n,r,i){if(!e||r!==`zod`||e.source===null)return null;let a=ca(e.source,t,n);if(a===`unknown`)return null;let o=`${a}::${e.identifier}`,s=i.get(o)?.specifier;return s?s=i.get(o).specifier:(s=`_S${i.size}`,i.set(o,{identifier:e.identifier,specifier:s})),s}function sa(e){if(e.size===0)return``;let t=[];for(let[n,r]of e){let[e]=n.split(`::`);t.push(`import type { ${r.identifier} as ${r.specifier} } from '${e}'`)}return t.join(`
|
|
3703
3898
|
`)+`
|
|
3704
|
-
`}function
|
|
3899
|
+
`}function ca(e,t,n){if(e===null)return`unknown`;let r=b(n);if(e===``){let e=S(r,t).split(oe).join(`/`);return e=e.replace(/\.(ts|tsx|mts|cts)$/i,``),e.startsWith(`.`)||(e=`./`+e),e}if(!e.startsWith(`.`)&&!e.startsWith(`/`))return e;let i=S(r,C(b(t),e)).split(oe).join(`/`);return i=i.replace(/\.(ts|tsx|mts|cts)$/i,``),i.startsWith(`.`)||(i=`./`+i),i}const la=()=>({id:`kick/routes`,outExtension:`.ts`,inputs:[`src/**/*.controller.ts`,`src/**/*.module.ts`],async generate(e){let t=await e.getScanResult({root:ua(e),cwd:e.cwd,envFile:da(e)}),n=e.config?.typegen?.schemaValidator??`zod`,r=y.resolve(e.cwd,`.kickjs/types/kick__routes.ts`);return ra(t.routes,r,n)}});function ua(e){return y.resolve(e.cwd,e.config?.typegen?.srcDir??`src`)}function da(e){let t=e.config?.typegen?.envFile;if(t!==!1)return t}function fa(e,t){if(!e)return null;let n=S(b(t),e.filePath).split(oe).join(`/`);return n=n.replace(/\.(ts|tsx|mts|cts)$/i,``),n.startsWith(`.`)||(n=`./`+n),`/* eslint-disable */
|
|
3705
3900
|
// AUTO-GENERATED by \`kick typegen\`. DO NOT EDIT.
|
|
3706
3901
|
// Re-run with \`kick typegen\` or rely on \`kick dev\` to refresh.
|
|
3707
3902
|
|
|
@@ -3737,4 +3932,4 @@ declare global {
|
|
|
3737
3932
|
}
|
|
3738
3933
|
|
|
3739
3934
|
export {}
|
|
3740
|
-
`}const
|
|
3935
|
+
`}const pa=()=>({id:`kick/env`,outExtension:`.ts`,inputs:[`src/env.ts`,`src/**/env.ts`,`src/**/*.env.ts`],async generate(e){let t=ha(e);if(t===!1)return null;let n=await e.getScanResult({root:ma(e),cwd:e.cwd,envFile:t});if(!n.env)return null;let r=y.resolve(e.cwd,`.kickjs/types/kick__env.ts`);return fa(n.env,r)}});function ma(e){return y.resolve(e.cwd,e.config?.typegen?.srcDir??`src`)}function ha(e){return e.config?.typegen?.envFile}var ga=e({builtinCliPlugins:()=>_a});const _a=[o({name:`kick/init`,register:kt}),o({name:`kick/generate`,register:Nr}),o({name:`kick/run`,register:Kr}),o({name:`kick/info`,register:qr}),o({name:`kick/inspect`,register:ni}),o({name:`kick/add`,register:Dt}),o({name:`kick/list`,register:Et}),o({name:`kick/explain`,register:fi}),o({name:`kick/mcp`,register:Ci}),o({name:`kick/tinker`,register:Di}),o({name:`kick/remove`,register:ji}),o({name:`kick/typegen`,register:Pi}),o({name:`kick/check`,register:Ki}),o({name:`kick/db`,register:Ji,typegens:[Qi()]}),o({name:`kick/codemod`,register:Xi}),o({name:`kick/assets`,typegens:[ta()]}),o({name:`kick/routes`,typegens:[la()]}),o({name:`kick/env`,typegens:[pa()]})];export{je as i,ga as n,Lr as r,_a as t};
|