@forinda/kickjs-cli 7.1.0 → 8.0.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/README.md +4 -3
- package/dist/agent-docs-B-HVS3nj.mjs +11 -0
- package/dist/agent-docs-C0JyMGzA.mjs +12 -0
- package/dist/agent-docs-C0JyMGzA.mjs.map +1 -0
- package/dist/{build-CYsWGD_U.mjs → build-7MjB7FQe.mjs} +2 -2
- package/dist/{build-Cp43RjiH.mjs → build-BPVxGl5d.mjs} +3 -3
- package/dist/{build-Cp43RjiH.mjs.map → build-BPVxGl5d.mjs.map} +1 -1
- package/dist/{builtins-iA3XGsqz.mjs → builtins-BDNTBw86.mjs} +2 -2
- package/dist/{builtins-DxdTZCr8.mjs → builtins-C5IYbrWs.mjs} +329 -312
- package/dist/cli.mjs +2 -2
- package/dist/{config-CdkYLlv2.mjs → config-CCjq5XrS.mjs} +4 -4
- package/dist/config-CCjq5XrS.mjs.map +1 -0
- package/dist/{config-BCTidBAX.mjs → config-CTshIQFs.mjs} +3 -3
- package/dist/{doctor-B2xA4478.mjs → doctor-CabZwXIc.mjs} +244 -227
- package/dist/doctor-CabZwXIc.mjs.map +1 -0
- package/dist/{fullstack-Bv08AwzK.mjs → fullstack-CpsuJwAC.mjs} +4 -4
- package/dist/{fullstack-Bv08AwzK.mjs.map → fullstack-CpsuJwAC.mjs.map} +1 -1
- package/dist/{fullstack-DP9x-ljd.mjs → fullstack-U0G1tXX-.mjs} +3 -3
- package/dist/index.d.mts +30 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{plugin-CVI4MJH6.mjs → plugin-CLk1hucT.mjs} +2 -2
- package/dist/{plugin-Fhc8Qv93.mjs → plugin-CgNrMHOr.mjs} +3 -3
- package/dist/{plugin-Fhc8Qv93.mjs.map → plugin-CgNrMHOr.mjs.map} +1 -1
- package/dist/{project-CNkmaH1A.mjs → project-BDmhnlq7.mjs} +12 -8
- package/dist/project-BDmhnlq7.mjs.map +1 -0
- package/dist/{project-CG3MprDt.mjs → project-N9-eInMo.mjs} +11 -7
- package/dist/{project-docs-D237QtHO.mjs → project-docs-BAkYVe21.mjs} +72 -16
- package/dist/project-docs-BAkYVe21.mjs.map +1 -0
- package/dist/{project-docs-Byh4JGRk.mjs → project-docs-Ce8bB21w.mjs} +71 -15
- package/dist/{project-root-BHwObi8C.mjs → project-root-C_w_fm3Z.mjs} +2 -2
- package/dist/{project-root-B_LSN3Fl.mjs → project-root-k6lK049j.mjs} +3 -3
- package/dist/{project-root-B_LSN3Fl.mjs.map → project-root-k6lK049j.mjs.map} +1 -1
- package/dist/{prompts-xjN6jSpm.mjs → prompts-DgwJdGWX.mjs} +2 -2
- package/dist/{prompts-xjN6jSpm.mjs.map → prompts-DgwJdGWX.mjs.map} +1 -1
- package/dist/{rolldown-runtime-Bqs5QSod.mjs → rolldown-runtime-CEoUVQPP.mjs} +1 -1
- package/dist/{run-plugins-Cw_O6W49.mjs → run-plugins-CA8eTAtB.mjs} +59 -59
- package/dist/run-plugins-CA8eTAtB.mjs.map +1 -0
- package/dist/{typegen-DKoIb8Uc.mjs → typegen-XbrxqReA.mjs} +5 -5
- package/dist/{typegen-DKoIb8Uc.mjs.map → typegen-XbrxqReA.mjs.map} +1 -1
- package/dist/{typegen-BibwRoz2.mjs → typegen-n6mBEphY.mjs} +4 -4
- package/dist/{types-B2gBwglF.mjs → types-qTFFy76T.mjs} +1 -1
- package/package.json +5 -5
- package/dist/agent-docs-Btt3h9P7.mjs +0 -12
- package/dist/agent-docs-Btt3h9P7.mjs.map +0 -1
- package/dist/agent-docs-DWvJTBPO.mjs +0 -11
- package/dist/config-CdkYLlv2.mjs.map +0 -1
- package/dist/doctor-B2xA4478.mjs.map +0 -1
- package/dist/project-CNkmaH1A.mjs.map +0 -1
- package/dist/project-docs-D237QtHO.mjs.map +0 -1
- package/dist/run-plugins-Cw_O6W49.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-cli
|
|
2
|
+
* @forinda/kickjs-cli v8.0.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{l as e,s as t}from"./project-docs-
|
|
11
|
+
import{l as e,s as t}from"./project-docs-BAkYVe21.mjs";import{a as n,n as r,r as i,t as a,u as o}from"./prompts-DgwJdGWX.mjs";import{i as s,s as c}from"./project-BDmhnlq7.mjs";import{o as l}from"./config-CCjq5XrS.mjs";import{t as u}from"./project-root-k6lK049j.mjs";import{createRequire as d}from"node:module";import f,{dirname as p,join as m,relative as h,resolve as g}from"node:path";import{existsSync as _,readFileSync as v,readdirSync as y,statSync as b}from"node:fs";import{readFile as x,writeFile as S}from"node:fs/promises";import C from"pluralize";import{pathToFileURL as w}from"node:url";import{defineGenerator as T}from"@forinda/kickjs-cli-kit";function E(e){return e.replace(/[-_\s]+(.)?/g,(e,t)=>t?t.toUpperCase():``).replace(/^(.)/,e=>e.toUpperCase())}function D(e){let t=E(e);return t.charAt(0).toLowerCase()+t.slice(1)}function O(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_]+/g,`-`).toLowerCase()}function k(e){return C.plural(e)}function A(e){return C.plural(e)}function j(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}const ee={inmemory:`in-memory`,drizzle:`Drizzle`,prisma:`Prisma`};function te(e){return e.charAt(0).toUpperCase()+e.slice(1).replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function ne(e){return ee[e]??te(e)}function re(e){return`create${e}Repository`}function M(e){return e??`define`}function N(e){let{pascal:t,kebab:n,plural:r=``,repo:i,style:a}=e,o=re(t),s=M(a),c=`/**
|
|
12
12
|
* ${t} Module
|
|
13
13
|
*
|
|
14
14
|
* REST module with a flat folder structure.
|
|
@@ -17,11 +17,14 @@ import{l as e,s as t}from"./project-docs-D237QtHO.mjs";import{a as n,n as r,r as
|
|
|
17
17
|
* Structure:
|
|
18
18
|
* ${n}.controller.ts — HTTP routes (CRUD)
|
|
19
19
|
* ${n}.service.ts — Business logic
|
|
20
|
-
* ${n}.repository.ts — Repository
|
|
21
|
-
* ${s}.repository.ts — Repository implementation
|
|
20
|
+
* ${n}.repository.ts — Repository: factory, contract, token
|
|
22
21
|
* dtos/ — Request/response schemas
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
*
|
|
23
|
+
* The repository is backed by an in-memory Map so this module works as
|
|
24
|
+
* generated. Swap in ${ne(i)} by replacing the factory body in
|
|
25
|
+
* ${n}.repository.ts — the contract is whatever that factory returns, so
|
|
26
|
+
* nothing else has to change.
|
|
27
|
+
*/`,l=`import { ${t.toUpperCase()}_REPOSITORY, ${o} } from './${n}.repository'
|
|
25
28
|
import { ${t}Controller } from './${n}.controller'
|
|
26
29
|
|
|
27
30
|
// Eagerly load decorated classes so @Controller()/@Service()/@Repository() decorators
|
|
@@ -30,7 +33,7 @@ import { ${t}Controller } from './${n}.controller'
|
|
|
30
33
|
import.meta.glob(
|
|
31
34
|
['./**/*.controller.ts', './**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'],
|
|
32
35
|
{ eager: true },
|
|
33
|
-
)`,
|
|
36
|
+
)`,u=` /**
|
|
34
37
|
* Declare HTTP routes for this module. Return value shape:
|
|
35
38
|
*
|
|
36
39
|
* - \`path\` — URL prefix for this route set.
|
|
@@ -44,18 +47,16 @@ import.meta.glob(
|
|
|
44
47
|
* { path: '/${r}', version: 1, controller: ${t}V1Controller },
|
|
45
48
|
* { path: '/${r}', version: 2, controller: ${t}V2Controller },
|
|
46
49
|
* ]
|
|
47
|
-
*/`;return
|
|
50
|
+
*/`;return s===`class`?`${c}
|
|
48
51
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
49
|
-
${
|
|
52
|
+
${l}
|
|
50
53
|
|
|
51
54
|
export class ${t}Module implements AppModule {
|
|
52
55
|
register(container: Container): void {
|
|
53
|
-
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
54
|
-
container.resolve(${o}),
|
|
55
|
-
)
|
|
56
|
+
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () => ${o}())
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
${
|
|
59
|
+
${u.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
59
60
|
routes(): ModuleRoutes {
|
|
60
61
|
return {
|
|
61
62
|
path: '/${r}',
|
|
@@ -63,20 +64,20 @@ ${d.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
`:`${
|
|
67
|
+
`:`${c}
|
|
67
68
|
import { defineModule } from '@forinda/kickjs'
|
|
68
|
-
${
|
|
69
|
+
${l}
|
|
69
70
|
|
|
70
71
|
export const ${t}Module = defineModule({
|
|
71
72
|
name: '${t}Module',
|
|
72
73
|
build: () => ({
|
|
73
74
|
register(container) {
|
|
74
75
|
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
75
|
-
|
|
76
|
+
${o}(),
|
|
76
77
|
)
|
|
77
78
|
},
|
|
78
79
|
|
|
79
|
-
${
|
|
80
|
+
${u}
|
|
80
81
|
routes() {
|
|
81
82
|
return {
|
|
82
83
|
path: '/${r}',
|
|
@@ -85,7 +86,7 @@ ${d}
|
|
|
85
86
|
},
|
|
86
87
|
}),
|
|
87
88
|
})
|
|
88
|
-
`}function
|
|
89
|
+
`}function P(e){let{pascal:t,kebab:n,plural:r=``,style:i}=e,a=M(i),o=` /**
|
|
89
90
|
* Declare HTTP routes. Return value shape:
|
|
90
91
|
*
|
|
91
92
|
* - \`path\` — URL prefix for this route set.
|
|
@@ -125,7 +126,7 @@ ${o}
|
|
|
125
126
|
},
|
|
126
127
|
}),
|
|
127
128
|
})
|
|
128
|
-
`}function
|
|
129
|
+
`}function F(e){let{pascal:t,kebab:n}=e,r=e.swagger?`import { ApiTags } from '@forinda/kickjs-swagger'
|
|
129
130
|
`:``,i=e.swagger?` @ApiTags('${e.pascal}')\n`:``,a=t.charAt(0).toLowerCase()+t.slice(1);return`import {
|
|
130
131
|
Controller,
|
|
131
132
|
Get,
|
|
@@ -197,7 +198,7 @@ ${i} async remove(ctx: Ctx<KickRoutes.${t}Controller['remove']>) {
|
|
|
197
198
|
return reply.noContent()
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
|
-
`}function
|
|
201
|
+
`}function ie(e){let{pascal:t}=e;return`import { z } from 'zod'
|
|
201
202
|
|
|
202
203
|
/**
|
|
203
204
|
* Create ${t} DTO — Zod schema for validating POST request bodies.
|
|
@@ -213,243 +214,196 @@ export const create${t}Schema = z.object({
|
|
|
213
214
|
})
|
|
214
215
|
|
|
215
216
|
export type Create${t}DTO = z.infer<typeof create${t}Schema>
|
|
216
|
-
`}function
|
|
217
|
+
`}function ae(e){let{pascal:t}=e;return`import { z } from 'zod'
|
|
217
218
|
|
|
218
219
|
export const update${t}Schema = z.object({
|
|
219
220
|
name: z.string().min(1).max(200).optional(),
|
|
220
221
|
})
|
|
221
222
|
|
|
222
223
|
export type Update${t}DTO = z.infer<typeof update${t}Schema>
|
|
223
|
-
`}function
|
|
224
|
+
`}function oe(e){let{pascal:t}=e;return`export interface ${t}ResponseDTO {
|
|
224
225
|
id: string
|
|
225
226
|
name: string
|
|
226
227
|
createdAt: string
|
|
227
228
|
updatedAt: string
|
|
228
229
|
}
|
|
229
|
-
`}function
|
|
230
|
-
|
|
230
|
+
`}function I(e){let{pascal:t,kebab:n,repoType:r,dtoPrefix:i=`./dtos`,tokenScope:a=`app`}=e,o=r&&r!==`inmemory`?r:``,s=`create${t}Repository`,c=` const store = new Map<string, ${t}ResponseDTO>()
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
async findById(id: string): Promise<${t}ResponseDTO | null> {
|
|
234
|
+
return store.get(id) ?? null
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
async findAll(): Promise<${t}ResponseDTO[]> {
|
|
238
|
+
return [...store.values()]
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
async findPaginated(
|
|
242
|
+
parsed: ParsedQuery,
|
|
243
|
+
): Promise<{ data: ${t}ResponseDTO[]; total: number }> {
|
|
244
|
+
const all = [...store.values()]
|
|
245
|
+
const { offset, limit } = parsed.pagination
|
|
246
|
+
return { data: all.slice(offset, offset + limit), total: all.length }
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async create(dto: Create${t}DTO): Promise<${t}ResponseDTO> {
|
|
250
|
+
const now = new Date().toISOString()
|
|
251
|
+
const entity = { id: randomUUID(), ...dto, createdAt: now, updatedAt: now } as ${t}ResponseDTO
|
|
252
|
+
store.set(entity.id, entity)
|
|
253
|
+
return entity
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
async update(id: string, dto: Update${t}DTO): Promise<${t}ResponseDTO> {
|
|
257
|
+
const existing = store.get(id)
|
|
258
|
+
if (!existing) throw HttpException.notFound('${t} not found')
|
|
259
|
+
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
260
|
+
store.set(id, updated)
|
|
261
|
+
return updated
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
async delete(id: string): Promise<void> {
|
|
265
|
+
if (!store.has(id)) throw HttpException.notFound('${t} not found')
|
|
266
|
+
store.delete(id)
|
|
267
|
+
},
|
|
268
|
+
}`;return`/**
|
|
269
|
+
* ${t} repository.
|
|
231
270
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
271
|
+
* ${o?`Backed by an in-memory Map so the module works as generated. Replace the
|
|
272
|
+
* body with your ${o} queries — the contract stays whatever this factory
|
|
273
|
+
* returns.`:`Backed by an in-memory Map: a working store with no dependencies.`}
|
|
235
274
|
*
|
|
236
|
-
*
|
|
275
|
+
* The factory's return type IS the contract: \`${t}Repository\` is derived
|
|
276
|
+
* from it, so the implementation and its interface cannot drift apart. To swap
|
|
277
|
+
* stores, write another factory returning a compatible shape and bind that one
|
|
278
|
+
* in the module — nothing else has to change.
|
|
237
279
|
*/
|
|
238
|
-
import {
|
|
239
|
-
import
|
|
240
|
-
import type { Create${t}DTO } from '${r}/create-${n}.dto'
|
|
241
|
-
import type { Update${t}DTO } from '${r}/update-${n}.dto'
|
|
280
|
+
import { randomUUID } from 'node:crypto'
|
|
281
|
+
import { createToken, HttpException } from '@forinda/kickjs'
|
|
242
282
|
import type { ParsedQuery } from '@forinda/kickjs'
|
|
283
|
+
import type { ${t}ResponseDTO } from '${i}/${n}-response.dto'
|
|
284
|
+
import type { Create${t}DTO } from '${i}/create-${n}.dto'
|
|
285
|
+
import type { Update${t}DTO } from '${i}/update-${n}.dto'
|
|
243
286
|
|
|
244
|
-
export
|
|
245
|
-
|
|
246
|
-
findAll(): Promise<${t}ResponseDTO[]>
|
|
247
|
-
findPaginated(parsed: ParsedQuery): Promise<{ data: ${t}ResponseDTO[]; total: number }>
|
|
248
|
-
create(dto: Create${t}DTO): Promise<${t}ResponseDTO>
|
|
249
|
-
update(id: string, dto: Update${t}DTO): Promise<${t}ResponseDTO>
|
|
250
|
-
delete(id: string): Promise<void>
|
|
287
|
+
export function ${s}() {
|
|
288
|
+
${c}
|
|
251
289
|
}
|
|
252
290
|
|
|
291
|
+
/** The contract, derived from the factory rather than declared beside it. */
|
|
292
|
+
export type ${t}Repository = ReturnType<typeof ${s}>
|
|
293
|
+
|
|
253
294
|
/**
|
|
254
|
-
* Collision-safe DI token bound to
|
|
295
|
+
* Collision-safe DI token bound to \`${t}Repository\`.
|
|
255
296
|
* \`container.resolve(${t.toUpperCase()}_REPOSITORY)\` and
|
|
256
297
|
* \`@Inject(${t.toUpperCase()}_REPOSITORY)\` both return the typed
|
|
257
|
-
*
|
|
298
|
+
* contract — no manual generic, no \`any\` cast.
|
|
258
299
|
*
|
|
259
|
-
* The \`'${
|
|
300
|
+
* The \`'${a}/'\` prefix matches the project scope so
|
|
260
301
|
* \`kick-lint\`'s \`token-reserved-prefix\` rule never fires —
|
|
261
302
|
* adopters must NOT use the reserved \`'kick/'\` namespace.
|
|
262
303
|
*/
|
|
263
|
-
export const ${t.toUpperCase()}_REPOSITORY = createToken
|
|
264
|
-
`}function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
* Useful for prototyping and testing. Replace with a database implementation
|
|
269
|
-
* (Drizzle, Prisma, etc.) for production use.
|
|
270
|
-
*
|
|
271
|
-
* @Repository() registers this class in the DI container as a singleton.
|
|
272
|
-
*/
|
|
273
|
-
import { randomUUID } from 'node:crypto'
|
|
274
|
-
import { Repository, HttpException } from '@forinda/kickjs'
|
|
275
|
-
import type { ParsedQuery } from '@forinda/kickjs'
|
|
276
|
-
import type { I${t}Repository } from '${r}/${n}.repository'
|
|
277
|
-
import type { ${t}ResponseDTO } from '${i}/${n}-response.dto'
|
|
278
|
-
import type { Create${t}DTO } from '${i}/create-${n}.dto'
|
|
279
|
-
import type { Update${t}DTO } from '${i}/update-${n}.dto'
|
|
280
|
-
|
|
281
|
-
@Repository()
|
|
282
|
-
export class InMemory${t}Repository implements I${t}Repository {
|
|
283
|
-
private store = new Map<string, ${t}ResponseDTO>()
|
|
284
|
-
|
|
285
|
-
async findById(id: string): Promise<${t}ResponseDTO | null> {
|
|
286
|
-
return this.store.get(id) ?? null
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async findAll(): Promise<${t}ResponseDTO[]> {
|
|
290
|
-
return Array.from(this.store.values())
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${t}ResponseDTO[]; total: number }> {
|
|
294
|
-
const all = Array.from(this.store.values())
|
|
295
|
-
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
296
|
-
return { data, total: all.length }
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async create(dto: Create${t}DTO): Promise<${t}ResponseDTO> {
|
|
300
|
-
const now = new Date().toISOString()
|
|
301
|
-
const entity: ${t}ResponseDTO = {
|
|
302
|
-
id: randomUUID(),
|
|
303
|
-
...dto,
|
|
304
|
-
createdAt: now,
|
|
305
|
-
updatedAt: now,
|
|
306
|
-
}
|
|
307
|
-
this.store.set(entity.id, entity)
|
|
308
|
-
return entity
|
|
309
|
-
}
|
|
304
|
+
export const ${t.toUpperCase()}_REPOSITORY = createToken<${t}Repository>('${a}/${t}/repository')
|
|
305
|
+
`}function se(e){let{pascal:t,kebab:n,plural:r=``,style:i=`define`,testHarness:a=!1}=e,o=i===`define`?`${t}Module()`:`${t}Module`;return`${a?`import { describe, it, expect, beforeEach } from 'vitest'
|
|
306
|
+
import request from 'supertest'
|
|
307
|
+
import { Container } from '@forinda/kickjs'
|
|
308
|
+
import { createTestApp } from '@forinda/kickjs-testing'
|
|
310
309
|
|
|
311
|
-
|
|
312
|
-
const existing = this.store.get(id)
|
|
313
|
-
if (!existing) throw HttpException.notFound('${t} not found')
|
|
314
|
-
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
315
|
-
this.store.set(id, updated)
|
|
316
|
-
return updated
|
|
317
|
-
}
|
|
310
|
+
import { ${t}Module } from '../${n}.module'
|
|
318
311
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
this.store.delete(id)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
`}function I(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`/**
|
|
325
|
-
* ${o} ${t} Repository
|
|
326
|
-
*
|
|
327
|
-
* Stub implementation for a custom '${r}' repository.
|
|
328
|
-
* Implements the repository interface using an in-memory Map as a placeholder.
|
|
312
|
+
/**
|
|
313
|
+
* Where this module is mounted.
|
|
329
314
|
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
315
|
+
* \`createTestApp\` builds its own Application with the framework defaults —
|
|
316
|
+
* apiPrefix \`/api\` and defaultVersion \`1\` — whatever your \`bootstrap()\` uses,
|
|
317
|
+
* so this is correct as generated. If your app configures them differently,
|
|
318
|
+
* pass the same values in \`boot()\` below and update this to match, so the test
|
|
319
|
+
* exercises the paths production actually serves:
|
|
332
320
|
*
|
|
333
|
-
*
|
|
321
|
+
* defaultVersion: false → '/api/${r}'
|
|
322
|
+
* apiPrefix: '/v1' → '/v1/v1/${r}'
|
|
334
323
|
*/
|
|
335
|
-
|
|
336
|
-
import {
|
|
337
|
-
import type { ParsedQuery } from '@forinda/kickjs'
|
|
338
|
-
import type { I${t}Repository } from '${i}/${n}.repository'
|
|
339
|
-
import type { ${t}ResponseDTO } from '${a}/${n}-response.dto'
|
|
340
|
-
import type { Create${t}DTO } from '${a}/create-${n}.dto'
|
|
341
|
-
import type { Update${t}DTO } from '${a}/update-${n}.dto'
|
|
342
|
-
|
|
343
|
-
@Repository()
|
|
344
|
-
export class ${o}${t}Repository implements I${t}Repository {
|
|
345
|
-
// TODO: Replace with your ${r} client/connection
|
|
346
|
-
private store = new Map<string, ${t}ResponseDTO>()
|
|
347
|
-
|
|
348
|
-
async findById(id: string): Promise<${t}ResponseDTO | null> {
|
|
349
|
-
// TODO: Implement with ${r}
|
|
350
|
-
return this.store.get(id) ?? null
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
async findAll(): Promise<${t}ResponseDTO[]> {
|
|
354
|
-
// TODO: Implement with ${r}
|
|
355
|
-
return Array.from(this.store.values())
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${t}ResponseDTO[]; total: number }> {
|
|
359
|
-
// TODO: Implement with ${r}
|
|
360
|
-
const all = Array.from(this.store.values())
|
|
361
|
-
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
362
|
-
return { data, total: all.length }
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
async create(dto: Create${t}DTO): Promise<${t}ResponseDTO> {
|
|
366
|
-
// TODO: Implement with ${r}
|
|
367
|
-
const now = new Date().toISOString()
|
|
368
|
-
const entity: ${t}ResponseDTO = {
|
|
369
|
-
id: randomUUID(),
|
|
370
|
-
...dto,
|
|
371
|
-
createdAt: now,
|
|
372
|
-
updatedAt: now,
|
|
373
|
-
}
|
|
374
|
-
this.store.set(entity.id, entity)
|
|
375
|
-
return entity
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
async update(id: string, dto: Update${t}DTO): Promise<${t}ResponseDTO> {
|
|
379
|
-
// TODO: Implement with ${r}
|
|
380
|
-
const existing = this.store.get(id)
|
|
381
|
-
if (!existing) throw HttpException.notFound('${t} not found')
|
|
382
|
-
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
383
|
-
this.store.set(id, updated)
|
|
384
|
-
return updated
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async delete(id: string): Promise<void> {
|
|
388
|
-
// TODO: Implement with ${r}
|
|
389
|
-
if (!this.store.has(id)) throw HttpException.notFound('${t} not found')
|
|
390
|
-
this.store.delete(id)
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
`}function ce(e){let{pascal:t,kebab:n,plural:r=``}=e;return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
324
|
+
const BASE = '/api/v1/${r}'
|
|
325
|
+
`:`import { describe, it, beforeEach } from 'vitest'
|
|
394
326
|
import { Container } from '@forinda/kickjs'
|
|
395
327
|
|
|
328
|
+
// Install \`@forinda/kickjs-testing\` and \`supertest\` to boot this module in a
|
|
329
|
+
// test: \`kick add @forinda/kickjs-testing\`. Until then every case below is a
|
|
330
|
+
// todo — the reporter lists them as outstanding rather than counting them as
|
|
331
|
+
// coverage.
|
|
332
|
+
`}
|
|
396
333
|
describe('${t}Controller', () => {
|
|
397
334
|
beforeEach(() => {
|
|
398
335
|
Container.reset()
|
|
399
336
|
})
|
|
400
337
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
338
|
+
${a?` /**
|
|
339
|
+
* Boot the module through the real pipeline.
|
|
340
|
+
*
|
|
341
|
+
* Drive \`app.handle\` rather than an Express app: it is the Application's own
|
|
342
|
+
* Node listener, so this test runs on whichever runtime the app is
|
|
343
|
+
* configured with.
|
|
344
|
+
*/
|
|
345
|
+
async function boot() {
|
|
346
|
+
const { app, container } = await createTestApp({
|
|
347
|
+
modules: [${o}],
|
|
348
|
+
// Mirror your bootstrap() here if it overrides either, and update BASE:
|
|
349
|
+
// apiPrefix: '/api',
|
|
350
|
+
// defaultVersion: 1,
|
|
409
351
|
})
|
|
410
|
-
|
|
352
|
+
return { app, container, agent: request(app.handle.bind(app)) }
|
|
353
|
+
}
|
|
411
354
|
|
|
412
355
|
describe('GET /${r}', () => {
|
|
413
|
-
it('
|
|
414
|
-
|
|
415
|
-
|
|
356
|
+
it('returns an empty page before anything is created', async () => {
|
|
357
|
+
const { agent } = await boot()
|
|
358
|
+
const res = await agent.get(BASE)
|
|
359
|
+
|
|
360
|
+
expect(res.status).toBe(200)
|
|
361
|
+
expect(res.body).toMatchObject({ data: [], meta: { page: 1 } })
|
|
416
362
|
})
|
|
363
|
+
|
|
364
|
+
it.todo('returns created ${r}, paginated')
|
|
365
|
+
})`:` describe('GET /${r}', () => {
|
|
366
|
+
// Boot the module with createTestApp and assert { data, meta }.
|
|
367
|
+
it.todo('returns an empty page before anything is created')
|
|
368
|
+
it.todo('returns created ${r}, paginated')
|
|
369
|
+
})`}
|
|
370
|
+
|
|
371
|
+
// The cases below are scaffolding, not coverage. Each is \`it.todo\` so the
|
|
372
|
+
// reporter lists it as outstanding instead of counting it as a pass. Replace
|
|
373
|
+
// the todo with a real test as you implement each endpoint.
|
|
374
|
+
|
|
375
|
+
describe('POST /${r}', () => {
|
|
376
|
+
// Post a valid body, assert 201 and the created ${n} in the response.
|
|
377
|
+
it.todo('creates a ${n}')
|
|
378
|
+
// Post an invalid body, assert 422 and the validation detail.
|
|
379
|
+
it.todo('rejects an invalid body')
|
|
417
380
|
})
|
|
418
381
|
|
|
419
382
|
describe('GET /${r}/:id', () => {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
it('should return 404 for non-existent ${n}', async () => {
|
|
426
|
-
// TODO: Fetch non-existent id, assert 404
|
|
427
|
-
expect(true).toBe(true)
|
|
428
|
-
})
|
|
383
|
+
// Create one, fetch it by id, assert the payload matches.
|
|
384
|
+
it.todo('returns a ${n} by id')
|
|
385
|
+
// Fetch an id that does not exist, assert 404.
|
|
386
|
+
it.todo('returns 404 for an unknown id')
|
|
429
387
|
})
|
|
430
388
|
|
|
431
389
|
describe('PUT /${r}/:id', () => {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
expect(true).toBe(true)
|
|
435
|
-
})
|
|
390
|
+
// Create, update, assert the change is reflected on a subsequent read.
|
|
391
|
+
it.todo('updates an existing ${n}')
|
|
436
392
|
})
|
|
437
393
|
|
|
438
394
|
describe('DELETE /${r}/:id', () => {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
expect(true).toBe(true)
|
|
442
|
-
})
|
|
395
|
+
// Create, delete, assert a subsequent read is 404.
|
|
396
|
+
it.todo('deletes a ${n}')
|
|
443
397
|
})
|
|
444
398
|
})
|
|
445
|
-
`}function
|
|
446
|
-
import {
|
|
399
|
+
`}function ce(e){let{pascal:t,kebab:n,plural:r=``,repoPrefix:i=`../${n}.repository`}=e,a=`create${t}Repository`;return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
400
|
+
import { ${a}, type ${t}Repository } from '${i}'
|
|
447
401
|
|
|
448
|
-
describe('
|
|
449
|
-
let repo:
|
|
402
|
+
describe('${t} repository', () => {
|
|
403
|
+
let repo: ${t}Repository
|
|
450
404
|
|
|
451
405
|
beforeEach(() => {
|
|
452
|
-
repo =
|
|
406
|
+
repo = ${a}()
|
|
453
407
|
})
|
|
454
408
|
|
|
455
409
|
it('should create and retrieve a ${n}', async () => {
|
|
@@ -506,7 +460,7 @@ describe('InMemory${t}Repository', () => {
|
|
|
506
460
|
})
|
|
507
461
|
`}function L(e){let{pascal:t,kebab:n}=e;return`import { Service, Inject, HttpException } from '@forinda/kickjs'
|
|
508
462
|
import type { ParsedQuery } from '@forinda/kickjs'
|
|
509
|
-
import { ${t.toUpperCase()}_REPOSITORY, type
|
|
463
|
+
import { ${t.toUpperCase()}_REPOSITORY, type ${t}Repository } from './${n}.repository'
|
|
510
464
|
import type { ${t}ResponseDTO } from './dtos/${n}-response.dto'
|
|
511
465
|
import type { Create${t}DTO } from './dtos/create-${n}.dto'
|
|
512
466
|
import type { Update${t}DTO } from './dtos/update-${n}.dto'
|
|
@@ -514,7 +468,7 @@ import type { Update${t}DTO } from './dtos/update-${n}.dto'
|
|
|
514
468
|
@Service()
|
|
515
469
|
export class ${t}Service {
|
|
516
470
|
constructor(
|
|
517
|
-
@Inject(${t.toUpperCase()}_REPOSITORY) private readonly repo:
|
|
471
|
+
@Inject(${t.toUpperCase()}_REPOSITORY) private readonly repo: ${t}Repository,
|
|
518
472
|
) {}
|
|
519
473
|
|
|
520
474
|
async findById(id: string): Promise<${t}ResponseDTO | null> {
|
|
@@ -548,7 +502,7 @@ export const ${t.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
|
548
502
|
sortable: ['name', 'createdAt'],
|
|
549
503
|
searchable: ['name'],
|
|
550
504
|
}
|
|
551
|
-
`}async function
|
|
505
|
+
`}async function le(e){let{pascal:t,kebab:n,plural:r,style:i,write:a}=e;await a(`${n}.module.ts`,P({pascal:t,kebab:n,plural:r,style:i})),await a(`${n}.controller.ts`,`import { Controller, Get, Post, Put, Delete, reply, type Ctx } from '@forinda/kickjs'
|
|
552
506
|
|
|
553
507
|
// \`Ctx<KickRoutes.${t}Controller['<method>']>\` is generated by
|
|
554
508
|
// \`kick typegen\` (auto-run on \`kick dev\`).
|
|
@@ -588,7 +542,7 @@ export class ${t}Controller {
|
|
|
588
542
|
return reply.noContent()
|
|
589
543
|
}
|
|
590
544
|
}
|
|
591
|
-
`)}async function
|
|
545
|
+
`)}async function ue(e){let{pascal:t,kebab:n,plural:r,pluralPascal:i,repo:a,noTests:o,tokenScope:s,style:c,testHarness:l,write:u}=e,d=e.swagger??!1;await u(`${n}.module.ts`,N({pascal:t,kebab:n,plural:r,repo:a,style:c})),await u(`${n}.constants.ts`,R({pascal:t,kebab:n})),await u(`${n}.controller.ts`,F({pascal:t,kebab:n,plural:r,pluralPascal:i,swagger:d})),await u(`${n}.service.ts`,L({pascal:t,kebab:n})),await u(`dtos/create-${n}.dto.ts`,ie({pascal:t,kebab:n})),await u(`dtos/update-${n}.dto.ts`,ae({pascal:t,kebab:n})),await u(`dtos/${n}-response.dto.ts`,oe({pascal:t,kebab:n})),await u(`${n}.repository.ts`,I({pascal:t,kebab:n,repoType:a,dtoPrefix:`./dtos`,tokenScope:s})),o||(await u(`__tests__/${n}.controller.test.ts`,se({pascal:t,kebab:n,plural:r,style:c,testHarness:l})),await u(`__tests__/${n}.repository.test.ts`,ce({pascal:t,kebab:n,plural:r,repoPrefix:`../${n}.repository`})))}function de(e){return e?typeof e==`string`?e:e.name:`inmemory`}async function fe(n){let{name:r,modulesDir:s,noEntity:c,noTests:l,repo:u=`inmemory`,force:d,dryRun:f}=n,p=n.pluralize!==!1,h=n.pattern??`rest`;n.minimal&&(h=`minimal`);let g=O(r),_=E(r),v=p?k(g):g,y=p?A(_):_,b=m(s,v),x=[],S=d??!1,C={kebab:g,pascal:_,plural:v,pluralPascal:y,moduleDir:b,repo:u,noEntity:c??!1,noTests:l??!1,tokenScope:n.tokenScope??`app`,swagger:n.swagger??!1,testHarness:n.testHarness??!1,style:n.style??`define`,write:async(n,r)=>{let s=m(b,n);if(f){x.push(s);return}if(!S&&await t(s)&&!await a({message:`File exists: ${o.dim(n)}. Overwrite?`,initialValue:!1})){i.warn(`Skipped: ${n}`);return}await e(s,r),x.push(s)},files:x};switch(h){case`minimal`:await le(C);break;default:await ue(C);break}return f||await z(s,_,v,g,C.style),x}async function z(n,r,i,a,o=`define`){let s=m(n,`index.ts`),c=await t(s),l=`./${i}/${a}.module`,u=o===`class`?`${r}Module`:`${r}Module()`;if(!c){await e(s,o===`class`?`import type { AppModuleEntry } from '@forinda/kickjs'
|
|
592
546
|
import { ${r}Module } from '${l}'
|
|
593
547
|
|
|
594
548
|
export const modules: AppModuleEntry[] = [${u}]
|
|
@@ -596,11 +550,11 @@ export const modules: AppModuleEntry[] = [${u}]
|
|
|
596
550
|
import { ${r}Module } from '${l}'
|
|
597
551
|
|
|
598
552
|
export const modules = defineModules().mount(${u})
|
|
599
|
-
`);return}let d=await x(s,`utf-8`),f=`import { ${r}Module } from '${l}'`,p=
|
|
553
|
+
`);return}let d=await x(s,`utf-8`),f=`import { ${r}Module } from '${l}'`,p=j(l);if(!RegExp(`^import\\s*\\{[^}]*\\b${j(r)}Module\\b[^}]*\\}\\s*from\\s*['"]${p}['"]`,`m`).test(d)){let e=d.lastIndexOf(`import `);if(e!==-1){let t=d.indexOf(`
|
|
600
554
|
`,e);d=d.slice(0,t+1)+f+`
|
|
601
555
|
`+d.slice(t+1)}else d=f+`
|
|
602
|
-
`+d}let h=V(d);if(h){let e=d.slice(h.rhsStart,h.rhsEnd+1);RegExp(`\\b${
|
|
603
|
-
`;)t++;return t}if(n===`/*`){for(t+=2;t+1<e.length&&(e[t]!==`*`||e[t+1]!==`/`);)t++;return t+2}return t}function
|
|
556
|
+
`+d}let h=V(d);if(h){let e=d.slice(h.rhsStart,h.rhsEnd+1);RegExp(`\\b${j(r)}Module\\b`).test(e)||(d=B(d,u))}else d=B(d,u);await S(s,d,`utf-8`)}function B(e,t){let n=V(e);if(!n)return e;if(n.shape===`array`){let r=e.slice(n.rhsStart+1,n.rhsEnd),i=r.trim(),a;if(!i)a=`[${t}]`;else{let e=i.endsWith(`,`)?``:`,`;a=`[${r.trimEnd()}${e} ${t}]`}return e.slice(0,n.rhsStart)+a+e.slice(n.rhsEnd+1)}return`${e.slice(0,n.chainEnd)}\n .mount(${t})${e.slice(n.chainEnd)}`}function V(e){let t=/export\s+const\s+modules\b[^=]*=/.exec(e);if(!t)return null;let n=t.index+t[0].length;for(;n<e.length&&/\s/.test(e[n]??``);)n++;if(e[n]===`[`){let t=me(e,n);return t===-1?null:{shape:`array`,rhsStart:n,rhsEnd:t}}if(e.slice(n,n+13)===`defineModules`){let t=pe(e,n);return t===-1?null:{shape:`chain`,rhsStart:n,rhsEnd:t-1,chainEnd:t}}return null}function pe(e,t=0){let n=/defineModules\s*\(/g;n.lastIndex=t;let r=n.exec(e);if(!r)return-1;let i=r.index+r[0].length-1;if(e[i]!==`(`||(i=U(e,i),i===-1))return-1;for(i++;;){let t=i;for(;t<e.length&&/\s/.test(e[t]??``);)t++;if(e[t]!==`.`||e.slice(t,t+6)!==`.mount`)break;for(t+=6;t<e.length&&/\s/.test(e[t]??``);)t++;if(e[t]!==`(`)break;let n=U(e,t);if(n===-1)break;i=n+1}return i}function H(e,t){let n=e.slice(t,t+2);if(n===`//`){for(t+=2;t<e.length&&e[t]!==`
|
|
557
|
+
`;)t++;return t}if(n===`/*`){for(t+=2;t+1<e.length&&(e[t]!==`*`||e[t+1]!==`/`);)t++;return t+2}return t}function me(e,t){if(e[t]!==`[`)return-1;let n=1,r=t+1;for(;r<e.length;){let t=e.slice(r,r+2);if(t===`//`||t===`/*`){r=H(e,r);continue}let i=e[r]??``;if(i===`'`||i===`"`||i==="`"){let t=i;for(r++;r<e.length&&e[r]!==t;)e[r]===`\\`&&r++,r++;r<e.length&&r++;continue}if(i===`[`)n++;else if(i===`]`&&(n--,n===0))return r;r++}return-1}function U(e,t){if(e[t]!==`(`)return-1;let n=1,r=t+1;for(;r<e.length;){let t=e.slice(r,r+2);if(t===`//`||t===`/*`){r=H(e,r);continue}let i=e[r]??``;if(i===`'`||i===`"`||i==="`"){let t=i;for(r++;r<e.length&&e[r]!==t;)e[r]===`\\`&&r++,r++;r<e.length&&r++;continue}if(i===`(`)n++;else if(i===`)`&&(n--,n===0))return r;r++}return-1}async function he(t){let{name:n,outDir:r}=t,i=O(n),a=E(n),o=[],s=m(r,`${i}.adapter.ts`);return await e(s,`import {
|
|
604
558
|
defineAdapter,
|
|
605
559
|
type AdapterContext,
|
|
606
560
|
type AdapterMiddleware,
|
|
@@ -658,7 +612,9 @@ export const ${a}Adapter = defineAdapter<${a}AdapterConfig>({
|
|
|
658
612
|
|
|
659
613
|
return {
|
|
660
614
|
/**
|
|
661
|
-
*
|
|
615
|
+
* Connect-style middleware entries the Application mounts at named
|
|
616
|
+
* phases. Works on every runtime — Express, Fastify and h3 all accept
|
|
617
|
+
* the connect signature through the runtime seam.
|
|
662
618
|
*
|
|
663
619
|
* \`phase\` controls where each handler sits in the pipeline:
|
|
664
620
|
* 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.
|
|
@@ -773,13 +729,74 @@ export const ${a}Adapter = defineAdapter<${a}AdapterConfig>({
|
|
|
773
729
|
// Example: await this.pool.end()
|
|
774
730
|
// Example: clearInterval(this.heartbeatTimer)
|
|
775
731
|
},
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Reports this adapter's backing service to \`GET /health/ready\`.
|
|
735
|
+
*
|
|
736
|
+
* The Application aggregates every adapter's check through
|
|
737
|
+
* \`Promise.allSettled\` and serves the combined result, so contributing
|
|
738
|
+
* one here is what makes your dependency visible to the built-in
|
|
739
|
+
* readiness endpoint — no route of your own required.
|
|
740
|
+
*
|
|
741
|
+
* Keep it cheap and time-boxed: readiness is polled by orchestrators.
|
|
742
|
+
*
|
|
743
|
+
* Delete this hook if your adapter has nothing to report.
|
|
744
|
+
*/
|
|
745
|
+
async onHealthCheck(): Promise<{ name: string; status: 'up' | 'down' }> {
|
|
746
|
+
// Example:
|
|
747
|
+
// try {
|
|
748
|
+
// await _config.client.ping()
|
|
749
|
+
// return { name: '${i}', status: 'up' }
|
|
750
|
+
// } catch {
|
|
751
|
+
// return { name: '${i}', status: 'down' }
|
|
752
|
+
// }
|
|
753
|
+
return { name: '${i}', status: 'up' }
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Snapshot for the DevTools topology view (\`/_debug\`).
|
|
758
|
+
*
|
|
759
|
+
* Must be CHEAP — the topology endpoint polls on a short interval, so
|
|
760
|
+
* \`state\` and \`metrics\` should be counters and flags already in memory,
|
|
761
|
+
* never a database round trip. Async is allowed for that reason, not as
|
|
762
|
+
* an invitation to do work.
|
|
763
|
+
*
|
|
764
|
+
* Delete this hook if the adapter has nothing worth showing.
|
|
765
|
+
*/
|
|
766
|
+
// introspect(): IntrospectionSnapshot {
|
|
767
|
+
// return {
|
|
768
|
+
// protocolVersion: 1,
|
|
769
|
+
// state: { connected: true },
|
|
770
|
+
// metrics: { handled: 0 },
|
|
771
|
+
// tokens: [],
|
|
772
|
+
// }
|
|
773
|
+
// },
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* DevTools panels this adapter contributes.
|
|
777
|
+
*
|
|
778
|
+
* Import \`defineDevtoolsTab\` from \`@forinda/kickjs-devtools-kit\`;
|
|
779
|
+
* \`@forinda/kickjs\` deliberately does not depend on the kit at runtime.
|
|
780
|
+
*
|
|
781
|
+
* Delete this hook unless the adapter ships a panel.
|
|
782
|
+
*/
|
|
783
|
+
// devtoolsTabs() {
|
|
784
|
+
// return [
|
|
785
|
+
// defineDevtoolsTab({
|
|
786
|
+
// id: '${i}',
|
|
787
|
+
// title: '${a}',
|
|
788
|
+
// kind: 'html',
|
|
789
|
+
// render: () => '<p>${a} adapter</p>',
|
|
790
|
+
// }),
|
|
791
|
+
// ]
|
|
792
|
+
// },
|
|
776
793
|
}
|
|
777
794
|
},
|
|
778
795
|
})
|
|
779
|
-
`),o.push(s),o}const
|
|
780
|
-
// \`express\``:"this project declares no `runtime` in kick.config.ts, so it\n // may have no `express`",c=W({type:`middleware`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/middleware`,pattern:a,shouldPluralize:t.pluralize??!0}),l=
|
|
796
|
+
`),o.push(s),o}const ge={controller:``,service:``,dto:`dtos`,guard:`guards`,middleware:`middleware`,contributor:`contributors`};function W(e){let{type:t,outDir:n,moduleName:r,modulesDir:i=`src/modules`,defaultDir:a,shouldPluralize:o=!0}=e;if(n)return g(n);if(r){let e=ge,n=O(r),a=o?k(n):n,s=e[t]??``,c=m(i,a);return g(s?m(c,s):c)}return g(a)}async function _e(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=t.runtime===`express`,s=t.runtime?`this project runs on ${t.runtime} and has no
|
|
797
|
+
// \`express\``:"this project declares no `runtime` in kick.config.ts, so it\n // may have no `express`",c=W({type:`middleware`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/middleware`,pattern:a,shouldPluralize:t.pluralize??!0}),l=O(n),u=D(n),d=[],f=m(c,`${l}.middleware.ts`);return await e(f,`${o?`import type { Request, Response, NextFunction } from 'express'`:`import type { IncomingMessage, ServerResponse } from 'node:http'`}
|
|
781
798
|
|
|
782
|
-
export interface ${
|
|
799
|
+
export interface ${E(n)}Options {
|
|
783
800
|
// Add configuration options here. The factory below closes over the
|
|
784
801
|
// resolved options object; pass them at the call site —
|
|
785
802
|
// \`${u}({ foo: 'bar' })\` — and the closure preserves them across
|
|
@@ -787,10 +804,10 @@ export interface ${w(n)}Options {
|
|
|
787
804
|
}
|
|
788
805
|
|
|
789
806
|
/**
|
|
790
|
-
* ${
|
|
807
|
+
* ${E(n)} middleware.
|
|
791
808
|
*
|
|
792
809
|
* Usage in bootstrap (fires on every request):
|
|
793
|
-
*
|
|
810
|
+
* middlewares: [${u}()]
|
|
794
811
|
*
|
|
795
812
|
* Usage with adapter — phase controls *when* the handler runs:
|
|
796
813
|
*
|
|
@@ -819,7 +836,7 @@ export interface ${w(n)}Options {
|
|
|
819
836
|
* Usage with @Middleware decorator:
|
|
820
837
|
* @Middleware(${u}())
|
|
821
838
|
*/
|
|
822
|
-
export function ${u}(options: ${
|
|
839
|
+
export function ${u}(options: ${E(n)}Options = {}) {
|
|
823
840
|
${o?" // Typed from `express` because that is this project's runtime\n // (`runtime: 'express'` in kick.config.ts). Global middleware is\n // connect-style on every engine, but only Express hands the handler its own\n // request / response objects — so `req.originalUrl`, `req.query` and\n // `res.json()` are all available here. Switching `runtime` later means\n // re-typing this signature from `node:http`.\n return (req: Request, res: Response, next: NextFunction) => {":` // Typed from \`node:http\`, not \`express\`. Global middleware is connect-style
|
|
824
841
|
// on every engine, but ${s} dependency to import types from. Under Fastify the handler
|
|
825
842
|
// receives \`request.raw\` / a reply driver, and under h3 the node objects, so
|
|
@@ -832,7 +849,7 @@ ${o?" // Typed from `express` because that is this project's runtime\n // (`ru
|
|
|
832
849
|
next()
|
|
833
850
|
}
|
|
834
851
|
}
|
|
835
|
-
`),d.push(f),d}async function
|
|
852
|
+
`),d.push(f),d}async function ve(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=W({type:`guard`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/guards`,pattern:a,shouldPluralize:t.pluralize??!0}),s=O(n),c=D(n),l=E(n),u=[],d=m(o,`${s}.guard.ts`);return await e(d,`import { Container, HttpException } from '@forinda/kickjs'
|
|
836
853
|
import type { RequestContext } from '@forinda/kickjs'
|
|
837
854
|
|
|
838
855
|
/**
|
|
@@ -872,7 +889,7 @@ export async function ${c}Guard(ctx: RequestContext, next: () => void): Promise<
|
|
|
872
889
|
ctx.problem.unauthorized({ detail: 'Invalid or expired token' })
|
|
873
890
|
}
|
|
874
891
|
}
|
|
875
|
-
`),u.push(d),u}async function
|
|
892
|
+
`),u.push(d),u}async function ye(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=W({type:`service`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/services`,pattern:a,shouldPluralize:t.pluralize??!0}),s=O(n),c=E(n),l=[],u=m(o,`${s}.service.ts`);return await e(u,`import { Service } from '@forinda/kickjs'
|
|
876
893
|
|
|
877
894
|
@Service()
|
|
878
895
|
export class ${c}Service {
|
|
@@ -881,7 +898,7 @@ export class ${c}Service {
|
|
|
881
898
|
// @Inject(MY_REPO) private readonly repo: IMyRepository,
|
|
882
899
|
// ) {}
|
|
883
900
|
}
|
|
884
|
-
`),l.push(u),l}async function
|
|
901
|
+
`),l.push(u),l}async function be(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=W({type:`controller`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/controllers`,pattern:a,shouldPluralize:t.pluralize??!0}),s=O(n),c=E(n),l=[],u=m(o,`${s}.controller.ts`);return await e(u,`import { Controller, Get, Post, Put, Delete, reply, type Ctx } from '@forinda/kickjs'
|
|
885
902
|
|
|
886
903
|
// \`Ctx<KickRoutes.${c}Controller['<method>']>\` is generated by
|
|
887
904
|
// \`kick typegen\` (auto-run on \`kick dev\`). After the first run, your IDE
|
|
@@ -923,7 +940,7 @@ export class ${c}Controller {
|
|
|
923
940
|
return reply.noContent()
|
|
924
941
|
}
|
|
925
942
|
}
|
|
926
|
-
`),l.push(u),l}async function
|
|
943
|
+
`),l.push(u),l}async function xe(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=W({type:`dto`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/dtos`,pattern:a,shouldPluralize:t.pluralize??!0}),s=O(n),c=E(n),l=D(n),u=[],d=m(o,`${s}.dto.ts`);return await e(d,`import { z } from 'zod'
|
|
927
944
|
|
|
928
945
|
export const ${l}Schema = z.object({
|
|
929
946
|
// Define your schema fields here
|
|
@@ -931,20 +948,20 @@ export const ${l}Schema = z.object({
|
|
|
931
948
|
})
|
|
932
949
|
|
|
933
950
|
export type ${c}DTO = z.infer<typeof ${l}Schema>
|
|
934
|
-
`),u.push(d),u}function
|
|
935
|
-
`);super(`kick typegen --check: ${e.length} generated file(s) are out of date:\n${t}\n Run \`kick typegen\` and commit the result.`),this.name=`TypegenDriftError`,this.drifted=e}};function
|
|
936
|
-
Install a supported version via nvm / fnm / volta.`}:{name:`Node version`,status:`pass`,message:e}}function
|
|
951
|
+
`),u.push(d),u}function Se(){let e=(process.env.LOG_LEVEL??process.env.KICKJS_LOG_LEVEL??``).toLowerCase();return e===`debug`||e===`trace`}var Ce=class extends Error{drifted;constructor(e){let t=e.map(e=>` ${e.id} → ${e.outFile}`).join(`
|
|
952
|
+
`);super(`kick typegen --check: ${e.length} generated file(s) are out of date:\n${t}\n Run \`kick typegen\` and commit the result.`),this.name=`TypegenDriftError`,this.drifted=e}};function we(e){return e}function Te(e){let t=new Map;return{report(n,r){let i=r instanceof Error?r.message:String(r);t.get(n)!==i&&(t.set(n,i),e(` kick typegen: ${n} pass failed (${i}) — types in .kickjs/types may be stale`))},clear(e){t.delete(e)}}}const Ee=`__kickjs_typegen_owner`;function De(e){let{cwd:t,config:n}=e,r=e.debounceMs??100,i=e.pipeline??{runTypegen:async e=>(await import(`./typegen-XbrxqReA.mjs`).then(e=>e.n)).runTypegen(e),runAllPluginTypegens:async e=>(await import(`./run-plugins-CA8eTAtB.mjs`)).runAllPluginTypegens(e),writeTypegenArtifacts:async(e,t,n)=>(await import(`./typegen-XbrxqReA.mjs`).then(e=>e.n)).writeTypegenArtifacts(e,t,n),buildAssets:async(e,t)=>(await import(`./build-BPVxGl5d.mjs`).then(e=>e.n)).buildAssets(e,t)},a=n?.typegen?.schemaValidator??`zod`,o=n?.typegen?.envFile,s=f.resolve(t,n?.typegen?.outDir??`.kickjs/types`),c=n?.assetMap?Object.values(n.assetMap).map(e=>e?.src).filter(e=>typeof e==`string`&&e.length>0).map(e=>f.resolve(t,e)):[],l=!!n?.assetMap&&Object.keys(n.assetMap).length>0,u=e=>e.replaceAll(`\\`,`/`),d=c.map(u),p=e=>{let t=u(e);return d.some(e=>t===e||t.startsWith(`${e}/`))},m=Te(e.emitWarning),h=null,g=!1,_=new Set,v=new Set,y=!1,b=!1;function x(r,c){i.runTypegen({cwd:t,silent:!0,allowDuplicates:!0,schemaValidator:a,envFile:o,srcDir:n?.typegen?.srcDir,outDir:n?.typegen?.outDir,assetMap:n?.assetMap,changedFiles:r,runPlugins:!1}).then(()=>m.clear(`scan`)).catch(e=>m.report(`scan`,e)),i.runAllPluginTypegens({cwd:t,config:n,silent:!0,changedFiles:r,watch:!0}).then(e=>i.writeTypegenArtifacts(s,e,!0)).then(()=>m.clear(`plugins`)).catch(e=>m.report(`plugins`,e)).finally(()=>e.onPassComplete?.()),c&&n&&i.buildAssets(n,{cwd:t,silent:!0}).catch(()=>{})}function S(){let e=y?void 0:{changed:[..._],removed:[...v]},t=b;_.clear(),v.clear(),y=!1,b=!1,x(e,t)}return{assetSrcRoots:c,handleWatchEvent(e,t){if(!g&&!u(t).includes(`/.kickjs/`)){if(e===`unlinkDir`)y=!0,l&&(b=!0);else{if(t.endsWith(`.d.ts`))return;let n=/\.(ts|tsx|mts|cts)$/.test(t),r=p(t);if(!n&&!r)return;r&&l&&(b=!0),n&&(e===`unlink`?(v.add(t),_.delete(t)):(_.add(t),v.delete(t)))}h&&clearTimeout(h),h=setTimeout(S,r)}},runOnce(){g||x(void 0,l)},dispose(){g=!0,h&&clearTimeout(h),h=null}}}function Oe(e){return O(e).replace(/-/g,`_`)}function G(e){let t=e.cwd??process.cwd(),n=e.projectRoot??u(t),r=e.pluralize??!0,i=E(e.name),a=D(e.name),o=O(e.name),s=Oe(e.name),c={name:e.name,pascal:i,camel:a,kebab:o,snake:s,modulesDir:e.modulesDir??`src/modules`,cwd:t,projectRoot:n,args:e.args??[],flags:e.flags??{}};if(r){let e=k(o);c.pluralKebab=e,c.pluralPascal=E(e),c.pluralCamel=D(e)}return c}function ke(e,t){return g(e.cwd,t)}async function Ae(e){return import(w(e).href)}const K=new Map;async function q(e){let t=K.get(e);if(t)return t;let n=je(e);return K.set(e,n),n}async function je(e){let t=g(e,`package.json`);if(!_(t))return{generators:[],loaded:[],failed:[]};let n=Me(JSON.parse(await x(t,`utf-8`))),r=d(g(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 x(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=g(p(t),s);if(!_(c)){o.push({source:e,reason:`kickjs.generators points to missing file: ${s}`});continue}let l;try{l=await Ae(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(!Ne(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 Me(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 Ne(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.name==`string`&&typeof t.files==`function`}async function Pe(e,t=[]){let n=e.cwd??process.cwd(),r=t.find(t=>t.spec.name===e.generatorName);if(r)return J(r.spec,r.source,e,n);let i=Ie(await q(n),e.generatorName);return i?J(i.spec,i.source,e,n):null}async function Fe(e,t=[]){let n=await q(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 Ie(e,t){return e.generators.find(e=>e.spec.name===t)}async function J(t,n,r,i){let a=G({name:r.itemName,args:r.args,flags:r.flags,modulesDir:r.modulesDir,pluralize:r.pluralize,cwd:i,projectRoot:r.projectRoot}),o=await t.files(a),s=[];for(let t of o){let n=ke(a,t.path);await e(n,t.content),s.push(n)}return{files:s,source:n}}function Le(e){return e}function Re(e){return e}function ze(e){try{return JSON.parse(v(e,`utf-8`))}catch{return null}}function Y(e){try{return v(e,`utf-8`)}catch{return null}}function Be(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|[^:"'\\])\/\/.*$/gm,`$1`).replace(/,(\s*[}\]])/g,`$1`);try{return JSON.parse(t)}catch{return null}}function Ve(e){let t=m(e,`tsconfig.json`);if(_(t))return X(t,new Set)}function X(e,t){if(t.has(e)||t.size>=16)return null;t.add(e);let n=Y(e);if(n===null)return null;let r=Be(n);if(typeof r!=`object`||!r)return null;let i=Array.isArray(r.extends)?r.extends:typeof r.extends==`string`?[r.extends]:[],a={};for(let n of i){if(typeof n!=`string`)continue;let r=He(p(e),n);if(!r)continue;let i=X(r,t);i&&Object.assign(a,i.compilerOptions)}return r.compilerOptions={...a,...r.compilerOptions},r}function He(e,t){if(t.startsWith(`.`)||t.startsWith(`/`))return Z(g(e,t));let n=e;for(;;){let e=Z(m(n,`node_modules`,t));if(e)return e;let r=p(n);if(r===n)break;n=r}return null}function Z(e){if(_(e)){try{if(b(e).isDirectory()){let t=m(e,`tsconfig.json`);return _(t)?t:null}}catch{return null}return e}let t=`${e}.json`;return _(t)?t:null}function Ue(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function We(){let e=process.version,t=Number.parseInt(e.replace(/^v/,``).split(`.`)[0],10);return Number.isNaN(t)||t<20?{name:`Node version`,status:`fail`,message:e,fix:`KickJS requires Node 20 or newer.
|
|
953
|
+
Install a supported version via nvm / fnm / volta.`}:{name:`Node version`,status:`pass`,message:e}}function Ge(e){if(!e.pkg)return{name:`@forinda/kickjs installed`,status:`warn`,message:`no package.json`};let t={...e.pkg.dependencies,...e.pkg.peerDependencies};return t[`@forinda/kickjs`]?{name:`@forinda/kickjs installed`,status:`pass`,message:t[`@forinda/kickjs`]}:{name:`@forinda/kickjs installed`,status:`fail`,fix:"This directory does not look like a KickJS project — `@forinda/kickjs` is not in your package.json. Run `kick doctor` from the project root, or scaffold a fresh project with `kick new <name>`."}}function Ke(e){if(!e.pkg)return null;let t={...e.pkg.dependencies,...e.pkg.peerDependencies};return t[`@forinda/kickjs`]&&!t.express?{name:`express installed`,status:`fail`,fix:"`@forinda/kickjs` declares `express` as a required peer dependency, but your package.json does not include it. Install: pnpm add express"}:t.express?{name:`express installed`,status:`pass`,message:t.express}:null}const qe={express:[],fastify:[`fastify`,`@fastify/middie`],h3:[`h3`]};function Je(e){if(!e.pkg||e.runtime===`express`)return null;let t={...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies},n=qe[e.runtime].filter(e=>!t[e]),r=`runtime engine (${e.runtime})`;return n.length>0?{name:r,status:`fail`,fix:`Resolved runtime '${e.runtime}' is missing engine peer(s): ${n.join(`, `)}.\nInstall: pnpm add ${n.join(` `)}`}:{name:r,status:`pass`}}function Ye(e){if(!e.pkg||!Xe(e.cwd))return null;let t=s[e.runtime],n=`upload driver (${e.runtime})`;return t.prod?{...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies}[t.prod]?{name:n,status:`pass`,message:t.prod}:{name:n,status:`fail`,fix:`This project uses file uploads on the '${e.runtime}' runtime, which needs '${t.prod}'.\nInstall it: kick add upload (or pnpm add ${t.prod})`}:{name:n,status:`pass`,message:`native multipart`}}const Q=2e3;function Xe(e){let t=m(e,`src`);if(!_(t))return!1;let n=/@FileUpload\b|\bupload\.(single|array|none)\s*\(/,r=[t],i=0;for(;r.length>0&&i<Q;){let e=r.pop(),t;try{t=y(e,{withFileTypes:!0})}catch{continue}for(let a of t){if(i>=Q)break;let t=m(e,a.name);if(a.isDirectory()){a.name!==`node_modules`&&r.push(t);continue}if(/\.(ts|tsx|mts|cts)$/.test(a.name)&&(i++,n.test(Y(t)??``)))return!0}}return!1}function Ze(e){if(!e.pkg)return{name:`reflect-metadata installed`,status:`warn`,message:`no package.json`};let t={...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies};return t[`reflect-metadata`]?{name:`reflect-metadata installed`,status:`pass`,message:t[`reflect-metadata`]}:{name:`reflect-metadata installed`,status:`fail`,fix:`KickJS decorators require the reflect-metadata polyfill.
|
|
937
954
|
Install it: pnpm add reflect-metadata
|
|
938
955
|
Then import it at the top of src/index.ts:
|
|
939
956
|
|
|
940
957
|
import 'reflect-metadata'
|
|
941
|
-
// ... rest of bootstrap`}}function
|
|
958
|
+
// ... rest of bootstrap`}}function Qe(e){if(e.tsconfig===void 0)return[{name:`tsconfig.json present`,status:`fail`,fix:"Create a tsconfig.json with `experimentalDecorators: true` and `emitDecoratorMetadata: true`. `kick new` scaffolds one automatically."}];if(e.tsconfig===null)return[{name:`tsconfig.json readable`,status:`warn`,message:`could not parse tsconfig.json (or a config it extends)`,fix:"Check tsconfig.json for a syntax error, and that every path in its `extends` chain resolves. Decorator options could not be verified."}];let t=e.tsconfig.compilerOptions??{},n=[];return n.push(t.experimentalDecorators===!0?{name:`tsconfig: experimentalDecorators`,status:`pass`}:{name:`tsconfig: experimentalDecorators`,status:`fail`,fix:'Add `"experimentalDecorators": true` to compilerOptions in tsconfig.json. Without it, @Service / @Controller / @Get etc. don\'t register any metadata at compile time.'}),n.push(t.emitDecoratorMetadata===!0?{name:`tsconfig: emitDecoratorMetadata`,status:`pass`}:{name:`tsconfig: emitDecoratorMetadata`,status:`fail`,fix:'Add `"emitDecoratorMetadata": true` to compilerOptions in tsconfig.json. The DI container uses this metadata for constructor-parameter injection.'}),n}function $e(e){let t=[`src/env.ts`,`src/env/index.ts`,`src/config/env.ts`,`src/config/index.ts`].map(t=>m(e.cwd,t)).filter(e=>_(e)).filter(e=>/\bloadEnv\s*\(/.test(Y(e)??``));if(t.length===0)return null;let n=[`src/index.ts`,`src/main.ts`].map(t=>m(e.cwd,t)).find(e=>_(e));if(!n)return{name:`env wiring`,status:`warn`,message:`env-init file exists but no src/index.ts or src/main.ts found`};let r=Y(n)??``,i=p(n),a=[];for(let e of t){let t=h(i,e).replace(/\\/g,`/`).replace(/\.ts$/,``),n=t.startsWith(`.`)?t:`./`+t,r=n.replace(/\/index$/,``);a.push(n,r);let o=e.replace(/\\/g,`/`).match(/\/src\/(.+?)(?:\.ts)?$/);if(o){let e=`@/`+o[1],t=e.replace(/\/index$/,``);a.push(e,t)}}let o=-1;for(let e of new Set(a)){let t=RegExp(`^import\\s+(?:.*?from\\s+)?['"]${Ue(e)}['"]`,`m`),n=r.match(t);n&&n.index!==void 0&&(o===-1||n.index<o)&&(o=n.index)}let s=r.search(/\bbootstrap\s*\(/),c=t.map(t=>h(e.cwd,t).replace(/\\/g,`/`)).join(`, `);return o===-1?{name:`env wiring`,status:`fail`,message:c,fix:`An env-init file (${c}) calls \`loadEnv(...)\` but \`${h(e.cwd,n).replace(/\\/g,`/`)}\` doesn't import it.\nWithout this, ConfigService.get('X') returns undefined while @Value('X') works via process.env fallback — a half-broken config you won't notice until something is missing.\n\nFix: add a side-effect import at the top of ${h(e.cwd,n).replace(/\\/g,`/`)} (above bootstrap()), pointing at one of the detected files. For example:\n\n import './env'\n // or\n import './config'\n // or, with the @/ alias:\n import '@/config/env'`}:s!==-1&&o>s?{name:`env wiring`,status:`warn`,message:`env-init imported AFTER bootstrap() — should be before`,fix:`Move the env import above the bootstrap() call so the schema runs before any service reads from ConfigService.`}:{name:`env wiring`,status:`pass`}}function et(e,t=tt){let n=0,r=0,i=[e];for(;i.length>0&&r<t;){let e=i.pop(),a;try{a=y(e,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=t)break;r++;let a=m(e,o.name);if(o.isDirectory()){i.push(a);continue}try{let e=b(a).mtimeMs;e>n&&(n=e)}catch{}}}return n}const tt=2e3;function $(e){let t=m(e.cwd,`.kickjs`,`types`);if(!_(t))return null;let n=et(t);if(n===0)return null;let r=Date.now()-n,i=Math.floor(r/6e4);return i>60?{name:`typegen freshness`,status:`warn`,message:`last updated ${i} minutes ago`,fix:"Re-run `kick typegen` (or `kick dev`, which runs it on every reload) so generated types match the current code."}:{name:`typegen freshness`,status:`pass`,message:i===0?`just now`:`${i}m ago`}}const nt=[`vitest.config.ts`,`vitest.config.js`,`vitest.config.mts`,`vite.config.ts`,`jest.config.ts`,`jest.config.js`],rt=[`.env.test`,`.env.test.local`],it=[`.env`,`.env.local`];function at(e){let t=e.pkg?.scripts;if(t&&typeof t==`object`)for(let[e,n]of Object.entries(t)){if(!e.startsWith(`test`)||typeof n!=`string`)continue;let t=/KICKJS_ENV_FILE=(\S+)/.exec(n);if(t?.[1])return t[1]}return process.env.KICKJS_ENV_FILE??null}function ot(e){if(!nt.some(t=>_(m(e.cwd,t))))return null;let t=it.filter(t=>_(m(e.cwd,t)));if(t.length===0)return null;let n=rt.filter(t=>_(m(e.cwd,t)));if(n.length>0){let t=at(e);return t&&t!==`off`&&t!==`none`?{name:`test env isolation`,status:`warn`,message:`${n.join(`, `)} present, but KICKJS_ENV_FILE=${t} overrides it`,fix:`KICKJS_ENV_FILE is checked before test-mode isolation, so ${n[0]} is\nignored and "${t}" is read instead. Drop the override to use the\ntest files, or set KICKJS_ENV_FILE=off if env comes from the runner.`}:{name:`test env isolation`,status:`pass`,message:`${n.join(`, `)} present`}}return{name:`test env isolation`,status:`warn`,message:`${t.join(`, `)} present, no .env.test`,fix:`Under NODE_ENV=test (or vitest's VITEST) KickJS reads .env.test / .env.test.local
|
|
942
959
|
INSTEAD of ${t.join(` and `)} — no layering, no fallback. Without one of\nthose files, every var your test config does not pin resolves from your
|
|
943
960
|
development env — including DATABASE_URL and any third-party credentials.
|
|
944
961
|
|
|
945
962
|
Create .env.test holding the whole environment the suite should run against
|
|
946
963
|
(a var you leave out is then missing, not inherited), or set KICKJS_ENV_FILE=off
|
|
947
|
-
and supply env from the test runner.`}}const
|
|
964
|
+
and supply env from the test runner.`}}const st=[()=>We(),Ge,Ke,Je,Ye,Ze,Qe,$e,ot,$];async function ct(e,t={}){let n={cwd:e,pkg:ze(m(e,`package.json`)),tsconfig:Ve(e),runtime:t.runtime??`express`},r=[...st,...t.extraChecks??[]],i=[];for(let e of r){let t;try{t=await e(n)}catch(t){i.push({name:e.name||`doctor check`,status:`fail`,message:t instanceof Error?t.message:String(t)});continue}t!=null&&(Array.isArray(t)?i.push(...t):i.push(t))}return i}function lt(e){switch(e){case`pass`:return o.green(`✔`);case`warn`:return o.yellow(`⚠`);case`fail`:return o.red(`✖`)}}function ut(e){let t=lt(e.status),n=e.message?` ${o.dim(`(${e.message})`)}`:``;return`${t} ${e.name}${n}`}function dt(e){return e.split(`
|
|
948
965
|
`).map(e=>` ${o.dim(`→`)} ${e}`).join(`
|
|
949
|
-
`)}function
|
|
950
|
-
//# sourceMappingURL=doctor-
|
|
966
|
+
`)}function ft(e){return e?.doctor?.checks??[]}function pt(e){e.command(`doctor`).description(`Pre-flight checks for your KickJS project (dev environment health)`).action(async()=>{let e=process.cwd(),t=ft(await l(e)),a=await c(e);r(`KickJS Doctor`);let s=await ct(e,{extraChecks:t,runtime:a});for(let e of s)i.message(ut(e)),e.fix&&e.status!==`pass`&&i.message(dt(e.fix));let u=s.filter(e=>e.status===`pass`).length,d=s.filter(e=>e.status===`warn`).length,f=s.filter(e=>e.status===`fail`).length,p=[o.green(`${u} passed`),d>0?o.yellow(`${d} warning${d===1?``:`s`}`):`${d} warnings`,f>0?o.red(`${f} error${f===1?``:`s`}`):`${f} errors`].join(`, `);f>0?(n(`${p} — fix the errors above before running the app`),process.exit(1)):n(d>0?`${p} — review the warnings`:o.green(`${p} — your environment looks good`))})}export{k as A,de as C,F as D,I as E,D as M,O as N,N as O,E as P,fe as S,L as T,_e as _,Pe as a,z as b,Ee as c,we as d,Se as f,ve as g,ye as h,Fe as i,A as j,j as k,De as l,be as m,Le as n,G as o,xe as p,pt as r,T as s,Re as t,Ce as u,W as v,R as w,V as x,he as y};
|
|
967
|
+
//# sourceMappingURL=doctor-CabZwXIc.mjs.map
|