@forinda/kickjs-cli 6.4.0 → 6.6.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/agent-docs-CqRWMHZ-.mjs +12 -0
- package/dist/agent-docs-CqRWMHZ-.mjs.map +1 -0
- package/dist/{build-CBRWV98i.mjs → build-C6c_h-27.mjs} +3 -3
- package/dist/{build-CBRWV98i.mjs.map → build-C6c_h-27.mjs.map} +1 -1
- package/dist/{builtins-1-t8nFZe.mjs → builtins-BvKA9kzi.mjs} +2 -2
- package/dist/cli.mjs +60 -24
- package/dist/{config-C6Na808n.mjs → config-BOArOJm-.mjs} +3 -3
- package/dist/{config-C6Na808n.mjs.map → config-BOArOJm-.mjs.map} +1 -1
- package/dist/doctor-BJWjI_wt.mjs +860 -0
- package/dist/doctor-BJWjI_wt.mjs.map +1 -0
- package/dist/fullstack-BgwfRTCN.mjs +138 -0
- package/dist/fullstack-BgwfRTCN.mjs.map +1 -0
- package/dist/fullstack-CHO-Us7Y.mjs +137 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{plugin-DlIRWEfP.mjs → plugin-IyD9yXBm.mjs} +3 -3
- package/dist/{plugin-DlIRWEfP.mjs.map → plugin-IyD9yXBm.mjs.map} +1 -1
- package/dist/project-QWNGOwFX.mjs +390 -0
- package/dist/project-QWNGOwFX.mjs.map +1 -0
- package/dist/{project-docs-Dr75Phev.mjs → project-docs-BSNKPfLT.mjs} +37 -11
- package/dist/project-docs-BSNKPfLT.mjs.map +1 -0
- package/dist/{project-root-BKYv-vPf.mjs → project-root-2PIpqSzM.mjs} +3 -3
- package/dist/{project-root-BKYv-vPf.mjs.map → project-root-2PIpqSzM.mjs.map} +1 -1
- package/dist/prompts-CeLdg6XF.mjs +12 -0
- package/dist/prompts-CeLdg6XF.mjs.map +1 -0
- package/dist/{rolldown-runtime-DLwMHNw-.mjs → rolldown-runtime-CCuSuZBc.mjs} +1 -1
- package/dist/{run-plugins-DaN0MHlX.mjs → run-plugins-B-_RJ-JV.mjs} +60 -54
- package/dist/run-plugins-B-_RJ-JV.mjs.map +1 -0
- package/dist/{typegen-DJGvcWVf.mjs → typegen-BTfDyerr.mjs} +17 -17
- package/dist/typegen-BTfDyerr.mjs.map +1 -0
- package/dist/{types-CmiWgylI.mjs → types-SeFJ8umm.mjs} +1 -1
- package/package.json +2 -2
- package/dist/agent-docs-lDM1Nx6J.mjs +0 -12
- package/dist/agent-docs-lDM1Nx6J.mjs.map +0 -1
- package/dist/doctor-DlWMC_2O.mjs +0 -1234
- package/dist/doctor-DlWMC_2O.mjs.map +0 -1
- package/dist/project-docs-Dr75Phev.mjs.map +0 -1
- package/dist/run-plugins-DaN0MHlX.mjs.map +0 -1
- package/dist/typegen-DJGvcWVf.mjs.map +0 -1
package/dist/doctor-DlWMC_2O.mjs
DELETED
|
@@ -1,1234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @forinda/kickjs-cli v6.4.0
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) Felix Orinda
|
|
5
|
-
*
|
|
6
|
-
* This source code is licensed under the MIT license found in the
|
|
7
|
-
* LICENSE file in the root directory of this source tree.
|
|
8
|
-
*
|
|
9
|
-
* @license MIT
|
|
10
|
-
*/
|
|
11
|
-
import{b as e,c as t,d as n,g as r,l as i,o as a,s as o,v as s}from"./project-docs-Dr75Phev.mjs";import{i as c,t as l}from"./config-C6Na808n.mjs";import{t as u}from"./project-root-BKYv-vPf.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{execFileSync as w,execSync as T}from"node:child_process";import{fileURLToPath as ee,pathToFileURL as te}from"node:url";import{defineGenerator as ne}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,`\\$&`)}function re(e){return e.charAt(0).toUpperCase()+e.slice(1).replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function ie(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).toLowerCase()}function ae(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]??`${re(n)}${e}Repository`,repoFile:i[n]??`${ie(n)}-${t}`}}function M(e){return e??`define`}function N(e){let{pascal:t,kebab:n,plural:r=``,repo:i,style:a}=e,{repoClass:o,repoFile:s}=ae(t,n,i),c=M(a),l=`/**
|
|
12
|
-
* ${t} Module
|
|
13
|
-
*
|
|
14
|
-
* REST module with a flat folder structure.
|
|
15
|
-
* Controller delegates to service, service wraps the repository.
|
|
16
|
-
*
|
|
17
|
-
* Structure:
|
|
18
|
-
* ${n}.controller.ts — HTTP routes (CRUD)
|
|
19
|
-
* ${n}.service.ts — Business logic
|
|
20
|
-
* ${n}.repository.ts — Repository interface
|
|
21
|
-
* ${s}.repository.ts — Repository implementation
|
|
22
|
-
* dtos/ — Request/response schemas
|
|
23
|
-
*/`,u=`import { ${t.toUpperCase()}_REPOSITORY } from './${n}.repository'
|
|
24
|
-
import { ${o} } from './${s}.repository'
|
|
25
|
-
import { ${t}Controller } from './${n}.controller'
|
|
26
|
-
|
|
27
|
-
// Eagerly load decorated classes so @Controller()/@Service()/@Repository() decorators
|
|
28
|
-
// register in the DI container. Recursive globs (./**/) so the module keeps working
|
|
29
|
-
// however you nest files (e.g. moving controllers into a controllers/ sub-folder).
|
|
30
|
-
import.meta.glob(
|
|
31
|
-
['./**/*.controller.ts', './**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'],
|
|
32
|
-
{ eager: true },
|
|
33
|
-
)`,d=` /**
|
|
34
|
-
* Declare HTTP routes for this module. Return value shape:
|
|
35
|
-
*
|
|
36
|
-
* - \`path\` — URL prefix for this route set.
|
|
37
|
-
* - \`controller\` — Controller class (also drives OpenAPI).
|
|
38
|
-
* - \`version\` — Optional. Overrides the app-wide API version.
|
|
39
|
-
*
|
|
40
|
-
* Return an **array** to mount multiple route sets — admin
|
|
41
|
-
* surfaces, side-by-side v1 + v2 controllers, etc:
|
|
42
|
-
*
|
|
43
|
-
* return [
|
|
44
|
-
* { path: '/${r}', version: 1, controller: ${t}V1Controller },
|
|
45
|
-
* { path: '/${r}', version: 2, controller: ${t}V2Controller },
|
|
46
|
-
* ]
|
|
47
|
-
*/`;return c===`class`?`${l}
|
|
48
|
-
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
49
|
-
${u}
|
|
50
|
-
|
|
51
|
-
export class ${t}Module implements AppModule {
|
|
52
|
-
register(container: Container): void {
|
|
53
|
-
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
54
|
-
container.resolve(${o}),
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
${d.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
59
|
-
routes(): ModuleRoutes {
|
|
60
|
-
return {
|
|
61
|
-
path: '/${r}',
|
|
62
|
-
controller: ${t}Controller,
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
`:`${l}
|
|
67
|
-
import { defineModule } from '@forinda/kickjs'
|
|
68
|
-
${u}
|
|
69
|
-
|
|
70
|
-
export const ${t}Module = defineModule({
|
|
71
|
-
name: '${t}Module',
|
|
72
|
-
build: () => ({
|
|
73
|
-
register(container) {
|
|
74
|
-
container.registerFactory(${t.toUpperCase()}_REPOSITORY, () =>
|
|
75
|
-
container.resolve(${o}),
|
|
76
|
-
)
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
${d}
|
|
80
|
-
routes() {
|
|
81
|
-
return {
|
|
82
|
-
path: '/${r}',
|
|
83
|
-
controller: ${t}Controller,
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
}),
|
|
87
|
-
})
|
|
88
|
-
`}function oe(e){let{pascal:t,kebab:n,plural:r=``,style:i}=e,a=M(i),o=` /**
|
|
89
|
-
* Declare HTTP routes. Return value shape:
|
|
90
|
-
*
|
|
91
|
-
* - \`path\` — URL prefix for this route set.
|
|
92
|
-
* - \`controller\` — Controller class (also drives OpenAPI).
|
|
93
|
-
* - \`version\` — Optional. Overrides the app-wide API version.
|
|
94
|
-
*
|
|
95
|
-
* Return an array to mount multiple route sets:
|
|
96
|
-
*
|
|
97
|
-
* return [
|
|
98
|
-
* { path: '/${r}', version: 1, controller: ${t}V1Controller },
|
|
99
|
-
* { path: '/${r}', version: 2, controller: ${t}V2Controller },
|
|
100
|
-
* ]
|
|
101
|
-
*/`;return a===`class`?`import { type AppModule, type ModuleRoutes } from '@forinda/kickjs'
|
|
102
|
-
import { ${t}Controller } from './${n}.controller'
|
|
103
|
-
|
|
104
|
-
export class ${t}Module implements AppModule {
|
|
105
|
-
${o.replace(/^ {4}/gm,` `).replace(/^ {6}/gm,` `)}
|
|
106
|
-
routes(): ModuleRoutes {
|
|
107
|
-
return {
|
|
108
|
-
path: '/${r}',
|
|
109
|
-
controller: ${t}Controller,
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
`:`import { defineModule } from '@forinda/kickjs'
|
|
114
|
-
import { ${t}Controller } from './${n}.controller'
|
|
115
|
-
|
|
116
|
-
export const ${t}Module = defineModule({
|
|
117
|
-
name: '${t}Module',
|
|
118
|
-
build: () => ({
|
|
119
|
-
${o}
|
|
120
|
-
routes() {
|
|
121
|
-
return {
|
|
122
|
-
path: '/${r}',
|
|
123
|
-
controller: ${t}Controller,
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
}),
|
|
127
|
-
})
|
|
128
|
-
`}function se(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'
|
|
129
|
-
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
130
|
-
import { ${t}Service } from './${n}.service'
|
|
131
|
-
import { create${t}Schema } from './dtos/create-${n}.dto'
|
|
132
|
-
import { update${t}Schema } from './dtos/update-${n}.dto'
|
|
133
|
-
import { ${t.toUpperCase()}_QUERY_CONFIG } from './${n}.constants'
|
|
134
|
-
|
|
135
|
-
// Each handler annotates its \`ctx\` with \`Ctx<KickRoutes.${t}Controller['<method>']>\`
|
|
136
|
-
// so \`ctx.params\`, \`ctx.body\`, and \`ctx.query\` are typed end-to-end.
|
|
137
|
-
// The \`KickRoutes\` namespace is generated by \`kick typegen\` (auto-run on
|
|
138
|
-
// \`kick dev\`) — see https://kickjs.app/guide/typegen.
|
|
139
|
-
|
|
140
|
-
@Controller()
|
|
141
|
-
export class ${t}Controller {
|
|
142
|
-
@Autowired() private readonly ${r}Service!: ${t}Service
|
|
143
|
-
|
|
144
|
-
@Get('/')
|
|
145
|
-
@ApiTags('${t}')
|
|
146
|
-
@ApiQueryParams(${t.toUpperCase()}_QUERY_CONFIG)
|
|
147
|
-
async list(ctx: Ctx<KickRoutes.${t}Controller['list']>) {
|
|
148
|
-
return ctx.paginate(
|
|
149
|
-
(parsed) => this.${r}Service.findPaginated(parsed),
|
|
150
|
-
${t.toUpperCase()}_QUERY_CONFIG,
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
@Get('/:id')
|
|
155
|
-
@ApiTags('${t}')
|
|
156
|
-
async getById(ctx: Ctx<KickRoutes.${t}Controller['getById']>) {
|
|
157
|
-
const result = await this.${r}Service.findById(ctx.params.id)
|
|
158
|
-
if (!result) return ctx.notFound('${t} not found')
|
|
159
|
-
ctx.json(result)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
@Post('/', { body: create${t}Schema, name: 'Create${t}' })
|
|
163
|
-
@ApiTags('${t}')
|
|
164
|
-
async create(ctx: Ctx<KickRoutes.${t}Controller['create']>) {
|
|
165
|
-
const result = await this.${r}Service.create(ctx.body)
|
|
166
|
-
ctx.created(result)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
@Put('/:id', { body: update${t}Schema, name: 'Update${t}' })
|
|
170
|
-
@ApiTags('${t}')
|
|
171
|
-
async update(ctx: Ctx<KickRoutes.${t}Controller['update']>) {
|
|
172
|
-
const result = await this.${r}Service.update(ctx.params.id, ctx.body)
|
|
173
|
-
ctx.json(result)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
@Delete('/:id')
|
|
177
|
-
@ApiTags('${t}')
|
|
178
|
-
async remove(ctx: Ctx<KickRoutes.${t}Controller['remove']>) {
|
|
179
|
-
await this.${r}Service.delete(ctx.params.id)
|
|
180
|
-
ctx.noContent()
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
`}function ce(e){let{pascal:t}=e;return`import { z } from 'zod'
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Create ${t} DTO — Zod schema for validating POST request bodies.
|
|
187
|
-
* This schema is passed to @Post('/', { body: create${t}Schema }) for automatic validation.
|
|
188
|
-
* It also generates OpenAPI request body docs when SwaggerAdapter is used.
|
|
189
|
-
*
|
|
190
|
-
* Add more fields as needed. Supported Zod types:
|
|
191
|
-
* z.string(), z.number(), z.boolean(), z.enum([...]),
|
|
192
|
-
* z.array(), z.object(), .optional(), .default(), .transform()
|
|
193
|
-
*/
|
|
194
|
-
export const create${t}Schema = z.object({
|
|
195
|
-
name: z.string().min(1, 'Name is required').max(200),
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
export type Create${t}DTO = z.infer<typeof create${t}Schema>
|
|
199
|
-
`}function le(e){let{pascal:t}=e;return`import { z } from 'zod'
|
|
200
|
-
|
|
201
|
-
export const update${t}Schema = z.object({
|
|
202
|
-
name: z.string().min(1).max(200).optional(),
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
export type Update${t}DTO = z.infer<typeof update${t}Schema>
|
|
206
|
-
`}function ue(e){let{pascal:t}=e;return`export interface ${t}ResponseDTO {
|
|
207
|
-
id: string
|
|
208
|
-
name: string
|
|
209
|
-
createdAt: string
|
|
210
|
-
updatedAt: string
|
|
211
|
-
}
|
|
212
|
-
`}function de(e){let{pascal:t,kebab:n,dtoPrefix:r=`../../application/dtos`,tokenScope:i=`app`}=e;return`/**
|
|
213
|
-
* ${t} Repository Interface
|
|
214
|
-
*
|
|
215
|
-
* Defines the contract for data access.
|
|
216
|
-
* The interface declares what operations are available;
|
|
217
|
-
* implementations (in-memory, Drizzle, Prisma) fulfill the contract.
|
|
218
|
-
*
|
|
219
|
-
* To swap implementations, change the factory in the module's register() method.
|
|
220
|
-
*/
|
|
221
|
-
import { createToken } from '@forinda/kickjs'
|
|
222
|
-
import type { ${t}ResponseDTO } from '${r}/${n}-response.dto'
|
|
223
|
-
import type { Create${t}DTO } from '${r}/create-${n}.dto'
|
|
224
|
-
import type { Update${t}DTO } from '${r}/update-${n}.dto'
|
|
225
|
-
import type { ParsedQuery } from '@forinda/kickjs'
|
|
226
|
-
|
|
227
|
-
export interface I${t}Repository {
|
|
228
|
-
findById(id: string): Promise<${t}ResponseDTO | null>
|
|
229
|
-
findAll(): Promise<${t}ResponseDTO[]>
|
|
230
|
-
findPaginated(parsed: ParsedQuery): Promise<{ data: ${t}ResponseDTO[]; total: number }>
|
|
231
|
-
create(dto: Create${t}DTO): Promise<${t}ResponseDTO>
|
|
232
|
-
update(id: string, dto: Update${t}DTO): Promise<${t}ResponseDTO>
|
|
233
|
-
delete(id: string): Promise<void>
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Collision-safe DI token bound to \`I${t}Repository\`.
|
|
238
|
-
* \`container.resolve(${t.toUpperCase()}_REPOSITORY)\` and
|
|
239
|
-
* \`@Inject(${t.toUpperCase()}_REPOSITORY)\` both return the typed
|
|
240
|
-
* interface — no manual generic, no \`any\` cast.
|
|
241
|
-
*
|
|
242
|
-
* The \`'${i}/'\` prefix matches the project scope so
|
|
243
|
-
* \`kick-lint\`'s \`token-reserved-prefix\` rule never fires —
|
|
244
|
-
* adopters must NOT use the reserved \`'kick/'\` namespace.
|
|
245
|
-
*/
|
|
246
|
-
export const ${t.toUpperCase()}_REPOSITORY = createToken<I${t}Repository>('${i}/${t}/repository')
|
|
247
|
-
`}function P(e){let{pascal:t,kebab:n,repoPrefix:r=`../../domain/repositories`,dtoPrefix:i=`../../application/dtos`}=e;return`/**
|
|
248
|
-
* In-Memory ${t} Repository
|
|
249
|
-
*
|
|
250
|
-
* Implements the repository interface using a Map.
|
|
251
|
-
* Useful for prototyping and testing. Replace with a database implementation
|
|
252
|
-
* (Drizzle, Prisma, etc.) for production use.
|
|
253
|
-
*
|
|
254
|
-
* @Repository() registers this class in the DI container as a singleton.
|
|
255
|
-
*/
|
|
256
|
-
import { randomUUID } from 'node:crypto'
|
|
257
|
-
import { Repository, HttpException } from '@forinda/kickjs'
|
|
258
|
-
import type { ParsedQuery } from '@forinda/kickjs'
|
|
259
|
-
import type { I${t}Repository } from '${r}/${n}.repository'
|
|
260
|
-
import type { ${t}ResponseDTO } from '${i}/${n}-response.dto'
|
|
261
|
-
import type { Create${t}DTO } from '${i}/create-${n}.dto'
|
|
262
|
-
import type { Update${t}DTO } from '${i}/update-${n}.dto'
|
|
263
|
-
|
|
264
|
-
@Repository()
|
|
265
|
-
export class InMemory${t}Repository implements I${t}Repository {
|
|
266
|
-
private store = new Map<string, ${t}ResponseDTO>()
|
|
267
|
-
|
|
268
|
-
async findById(id: string): Promise<${t}ResponseDTO | null> {
|
|
269
|
-
return this.store.get(id) ?? null
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async findAll(): Promise<${t}ResponseDTO[]> {
|
|
273
|
-
return Array.from(this.store.values())
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${t}ResponseDTO[]; total: number }> {
|
|
277
|
-
const all = Array.from(this.store.values())
|
|
278
|
-
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
279
|
-
return { data, total: all.length }
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async create(dto: Create${t}DTO): Promise<${t}ResponseDTO> {
|
|
283
|
-
const now = new Date().toISOString()
|
|
284
|
-
const entity: ${t}ResponseDTO = {
|
|
285
|
-
id: randomUUID(),
|
|
286
|
-
...dto,
|
|
287
|
-
createdAt: now,
|
|
288
|
-
updatedAt: now,
|
|
289
|
-
}
|
|
290
|
-
this.store.set(entity.id, entity)
|
|
291
|
-
return entity
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async update(id: string, dto: Update${t}DTO): Promise<${t}ResponseDTO> {
|
|
295
|
-
const existing = this.store.get(id)
|
|
296
|
-
if (!existing) throw HttpException.notFound('${t} not found')
|
|
297
|
-
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
298
|
-
this.store.set(id, updated)
|
|
299
|
-
return updated
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async delete(id: string): Promise<void> {
|
|
303
|
-
if (!this.store.has(id)) throw HttpException.notFound('${t} not found')
|
|
304
|
-
this.store.delete(id)
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
`}function F(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`/**
|
|
308
|
-
* ${o} ${t} Repository
|
|
309
|
-
*
|
|
310
|
-
* Stub implementation for a custom '${r}' repository.
|
|
311
|
-
* Implements the repository interface using an in-memory Map as a placeholder.
|
|
312
|
-
*
|
|
313
|
-
* TODO: Replace the in-memory Map with your ${r} data-access logic.
|
|
314
|
-
* See I${t}Repository for the interface contract.
|
|
315
|
-
*
|
|
316
|
-
* @Repository() registers this class in the DI container as a singleton.
|
|
317
|
-
*/
|
|
318
|
-
import { randomUUID } from 'node:crypto'
|
|
319
|
-
import { Repository, HttpException } from '@forinda/kickjs'
|
|
320
|
-
import type { ParsedQuery } from '@forinda/kickjs'
|
|
321
|
-
import type { I${t}Repository } from '${i}/${n}.repository'
|
|
322
|
-
import type { ${t}ResponseDTO } from '${a}/${n}-response.dto'
|
|
323
|
-
import type { Create${t}DTO } from '${a}/create-${n}.dto'
|
|
324
|
-
import type { Update${t}DTO } from '${a}/update-${n}.dto'
|
|
325
|
-
|
|
326
|
-
@Repository()
|
|
327
|
-
export class ${o}${t}Repository implements I${t}Repository {
|
|
328
|
-
// TODO: Replace with your ${r} client/connection
|
|
329
|
-
private store = new Map<string, ${t}ResponseDTO>()
|
|
330
|
-
|
|
331
|
-
async findById(id: string): Promise<${t}ResponseDTO | null> {
|
|
332
|
-
// TODO: Implement with ${r}
|
|
333
|
-
return this.store.get(id) ?? null
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async findAll(): Promise<${t}ResponseDTO[]> {
|
|
337
|
-
// TODO: Implement with ${r}
|
|
338
|
-
return Array.from(this.store.values())
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${t}ResponseDTO[]; total: number }> {
|
|
342
|
-
// TODO: Implement with ${r}
|
|
343
|
-
const all = Array.from(this.store.values())
|
|
344
|
-
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
345
|
-
return { data, total: all.length }
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async create(dto: Create${t}DTO): Promise<${t}ResponseDTO> {
|
|
349
|
-
// TODO: Implement with ${r}
|
|
350
|
-
const now = new Date().toISOString()
|
|
351
|
-
const entity: ${t}ResponseDTO = {
|
|
352
|
-
id: randomUUID(),
|
|
353
|
-
...dto,
|
|
354
|
-
createdAt: now,
|
|
355
|
-
updatedAt: now,
|
|
356
|
-
}
|
|
357
|
-
this.store.set(entity.id, entity)
|
|
358
|
-
return entity
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async update(id: string, dto: Update${t}DTO): Promise<${t}ResponseDTO> {
|
|
362
|
-
// TODO: Implement with ${r}
|
|
363
|
-
const existing = this.store.get(id)
|
|
364
|
-
if (!existing) throw HttpException.notFound('${t} not found')
|
|
365
|
-
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
366
|
-
this.store.set(id, updated)
|
|
367
|
-
return updated
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async delete(id: string): Promise<void> {
|
|
371
|
-
// TODO: Implement with ${r}
|
|
372
|
-
if (!this.store.has(id)) throw HttpException.notFound('${t} not found')
|
|
373
|
-
this.store.delete(id)
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
`}function fe(e){let{pascal:t,kebab:n,plural:r=``}=e;return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
377
|
-
import { Container } from '@forinda/kickjs'
|
|
378
|
-
|
|
379
|
-
describe('${t}Controller', () => {
|
|
380
|
-
beforeEach(() => {
|
|
381
|
-
Container.reset()
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
it('should be defined', () => {
|
|
385
|
-
expect(true).toBe(true)
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
describe('POST /${r}', () => {
|
|
389
|
-
it('should create a new ${n}', async () => {
|
|
390
|
-
// TODO: Set up test module, call create endpoint, assert 201
|
|
391
|
-
expect(true).toBe(true)
|
|
392
|
-
})
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
describe('GET /${r}', () => {
|
|
396
|
-
it('should return paginated ${r}', async () => {
|
|
397
|
-
// TODO: Set up test module, call list endpoint, assert { data, meta }
|
|
398
|
-
expect(true).toBe(true)
|
|
399
|
-
})
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
describe('GET /${r}/:id', () => {
|
|
403
|
-
it('should return a ${n} by id', async () => {
|
|
404
|
-
// TODO: Create a ${n}, then fetch by id, assert match
|
|
405
|
-
expect(true).toBe(true)
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
it('should return 404 for non-existent ${n}', async () => {
|
|
409
|
-
// TODO: Fetch non-existent id, assert 404
|
|
410
|
-
expect(true).toBe(true)
|
|
411
|
-
})
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
describe('PUT /${r}/:id', () => {
|
|
415
|
-
it('should update an existing ${n}', async () => {
|
|
416
|
-
// TODO: Create, update, assert changes
|
|
417
|
-
expect(true).toBe(true)
|
|
418
|
-
})
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
describe('DELETE /${r}/:id', () => {
|
|
422
|
-
it('should delete a ${n}', async () => {
|
|
423
|
-
// TODO: Create, delete, assert gone
|
|
424
|
-
expect(true).toBe(true)
|
|
425
|
-
})
|
|
426
|
-
})
|
|
427
|
-
})
|
|
428
|
-
`}function pe(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'
|
|
429
|
-
import { InMemory${t}Repository } from '${i}'
|
|
430
|
-
|
|
431
|
-
describe('InMemory${t}Repository', () => {
|
|
432
|
-
let repo: InMemory${t}Repository
|
|
433
|
-
|
|
434
|
-
beforeEach(() => {
|
|
435
|
-
repo = new InMemory${t}Repository()
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
it('should create and retrieve a ${n}', async () => {
|
|
439
|
-
const created = await repo.create({ name: 'Test ${t}' })
|
|
440
|
-
expect(created).toBeDefined()
|
|
441
|
-
expect(created.name).toBe('Test ${t}')
|
|
442
|
-
expect(created.id).toBeDefined()
|
|
443
|
-
|
|
444
|
-
const found = await repo.findById(created.id)
|
|
445
|
-
expect(found).toEqual(created)
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
it('should return null for non-existent id', async () => {
|
|
449
|
-
const found = await repo.findById('non-existent')
|
|
450
|
-
expect(found).toBeNull()
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
it('should list all ${r}', async () => {
|
|
454
|
-
await repo.create({ name: '${t} 1' })
|
|
455
|
-
await repo.create({ name: '${t} 2' })
|
|
456
|
-
|
|
457
|
-
const all = await repo.findAll()
|
|
458
|
-
expect(all).toHaveLength(2)
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
it('should return paginated results', async () => {
|
|
462
|
-
await repo.create({ name: '${t} 1' })
|
|
463
|
-
await repo.create({ name: '${t} 2' })
|
|
464
|
-
await repo.create({ name: '${t} 3' })
|
|
465
|
-
|
|
466
|
-
const result = await repo.findPaginated({
|
|
467
|
-
filters: [],
|
|
468
|
-
sort: [],
|
|
469
|
-
search: '',
|
|
470
|
-
pagination: { page: 1, limit: 2, offset: 0 },
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
expect(result.data).toHaveLength(2)
|
|
474
|
-
expect(result.total).toBe(3)
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
it('should update a ${n}', async () => {
|
|
478
|
-
const created = await repo.create({ name: 'Original' })
|
|
479
|
-
const updated = await repo.update(created.id, { name: 'Updated' })
|
|
480
|
-
expect(updated.name).toBe('Updated')
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
it('should delete a ${n}', async () => {
|
|
484
|
-
const created = await repo.create({ name: 'To Delete' })
|
|
485
|
-
await repo.delete(created.id)
|
|
486
|
-
const found = await repo.findById(created.id)
|
|
487
|
-
expect(found).toBeNull()
|
|
488
|
-
})
|
|
489
|
-
})
|
|
490
|
-
`}function I(e){let{pascal:t,kebab:n}=e;return`import { Service, Inject, HttpException } from '@forinda/kickjs'
|
|
491
|
-
import type { ParsedQuery } from '@forinda/kickjs'
|
|
492
|
-
import { ${t.toUpperCase()}_REPOSITORY, type I${t}Repository } from './${n}.repository'
|
|
493
|
-
import type { ${t}ResponseDTO } from './dtos/${n}-response.dto'
|
|
494
|
-
import type { Create${t}DTO } from './dtos/create-${n}.dto'
|
|
495
|
-
import type { Update${t}DTO } from './dtos/update-${n}.dto'
|
|
496
|
-
|
|
497
|
-
@Service()
|
|
498
|
-
export class ${t}Service {
|
|
499
|
-
constructor(
|
|
500
|
-
@Inject(${t.toUpperCase()}_REPOSITORY) private readonly repo: I${t}Repository,
|
|
501
|
-
) {}
|
|
502
|
-
|
|
503
|
-
async findById(id: string): Promise<${t}ResponseDTO | null> {
|
|
504
|
-
return this.repo.findById(id)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
async findAll(): Promise<${t}ResponseDTO[]> {
|
|
508
|
-
return this.repo.findAll()
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
async findPaginated(parsed: ParsedQuery) {
|
|
512
|
-
return this.repo.findPaginated(parsed)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async create(dto: Create${t}DTO): Promise<${t}ResponseDTO> {
|
|
516
|
-
return this.repo.create(dto)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
async update(id: string, dto: Update${t}DTO): Promise<${t}ResponseDTO> {
|
|
520
|
-
return this.repo.update(id, dto)
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
async delete(id: string): Promise<void> {
|
|
524
|
-
await this.repo.delete(id)
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
`}function L(e){let{pascal:t}=e;return`import type { QueryFieldConfig } from '@forinda/kickjs'
|
|
528
|
-
|
|
529
|
-
export const ${t.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
530
|
-
filterable: ['name'],
|
|
531
|
-
sortable: ['name', 'createdAt'],
|
|
532
|
-
searchable: ['name'],
|
|
533
|
-
}
|
|
534
|
-
`}const me={express:{from:`@forinda/kickjs`,name:`expressRuntime`},fastify:{from:`@forinda/kickjs/fastify`,name:`fastifyRuntime`},h3:{from:`@forinda/kickjs/h3`,name:`h3Runtime`}};function he(e,t,n,r=[],i=`express`){let a=me[i],o=i===`express`;switch(t){case`minimal`:{let t=[],i=[],s=o?`import { bootstrap, ${a.name} } from '@forinda/kickjs'`:`import { bootstrap } from '@forinda/kickjs'\nimport { ${a.name} } from '${a.from}'`;r.includes(`swagger`)&&(t.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`),i.push(` SwaggerAdapter({ info: { title: '${e}', version: '${n}' } }),`)),r.includes(`devtools`)&&(t.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`),i.push(` DevToolsAdapter(),`));let c=t.length?t.join(`
|
|
535
|
-
`)+`
|
|
536
|
-
`:``,l=i.length?`,\n adapters: [\n${i.join(`
|
|
537
|
-
`)}\n ]`:``;return`import 'reflect-metadata'
|
|
538
|
-
// Side-effect import — registers the extended env schema with kickjs
|
|
539
|
-
// **before** any controller / service / @Value gets resolved. Without
|
|
540
|
-
// this line ConfigService.get('YOUR_KEY') returns undefined because the
|
|
541
|
-
// cached schema would still be the base shape. See guide/configuration.
|
|
542
|
-
import './config'
|
|
543
|
-
${s}
|
|
544
|
-
${c}import { modules } from './modules'
|
|
545
|
-
|
|
546
|
-
// Export the app for the Vite plugin (dev mode)
|
|
547
|
-
export const app = await bootstrap({ modules, runtime: ${a.name}()${l} })
|
|
548
|
-
`}default:{let t=[],i=[];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 }),`));let s=t.length?t.join(`
|
|
549
|
-
`)+`
|
|
550
|
-
`:``,c=i.length?`\n adapters: [\n${i.join(`
|
|
551
|
-
`)}\n ],`:``,l=[`bootstrap`,`requestId`,`requestLogger`,`helmet`,`cors`];o&&l.push(a.name);let u=o?`import express from 'express'\nimport {\n ${l.join(`,
|
|
552
|
-
`)},\n} from '@forinda/kickjs'`:`import {\n ${l.join(`,
|
|
553
|
-
`)},\n} from '@forinda/kickjs'\nimport { ${a.name} } from '${a.from}'`,d=o?`
|
|
554
|
-
express.json(),`:``;return`import 'reflect-metadata'
|
|
555
|
-
// Side-effect import — registers the extended env schema with kickjs
|
|
556
|
-
// **before** any controller / service / @Value gets resolved. Without
|
|
557
|
-
// this line ConfigService.get('YOUR_KEY') returns undefined because the
|
|
558
|
-
// cached schema would still be the base shape. See guide/configuration.
|
|
559
|
-
import './config'
|
|
560
|
-
${u}
|
|
561
|
-
${s}import { modules } from './modules'
|
|
562
|
-
|
|
563
|
-
// Export the app for the Vite plugin (dev mode)
|
|
564
|
-
export const app = await bootstrap({
|
|
565
|
-
modules,
|
|
566
|
-
runtime: ${a.name}(),${c}
|
|
567
|
-
middleware: [
|
|
568
|
-
helmet(),
|
|
569
|
-
cors({ origin: '*' }),
|
|
570
|
-
requestId(),
|
|
571
|
-
requestLogger(),${d}
|
|
572
|
-
],
|
|
573
|
-
})
|
|
574
|
-
`}}}function ge(){return`import { defineModules } from '@forinda/kickjs'
|
|
575
|
-
import { HelloModule } from './hello/hello.module'
|
|
576
|
-
|
|
577
|
-
// Remove HelloModule and run: kick g module <name>
|
|
578
|
-
// \`defineModules()\` returns a chainable list — \`kick g module\` appends
|
|
579
|
-
// \`.mount(NewModule())\` to the chain on every generation.
|
|
580
|
-
export const modules = defineModules().mount(HelloModule())
|
|
581
|
-
`}function _e(e=`zod`){return e===`valibot`?`import { loadEnvFromSchema } from '@forinda/kickjs/config'
|
|
582
|
-
import { fromValibot } from '@forinda/kickjs-schema/valibot'
|
|
583
|
-
import * as v from 'valibot'
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Project environment schema (Valibot).
|
|
587
|
-
*
|
|
588
|
-
* \`fromValibot\` wraps the Valibot schema as a \`KickSchema\` so the
|
|
589
|
-
* env loader, validate middleware, and swagger spec generator all see
|
|
590
|
-
* the same shape. The default export is the contract \`kick typegen\`
|
|
591
|
-
* reads to populate \`KickEnv\` via \`InferSchemaOutput<typeof _envSchema>\`
|
|
592
|
-
* — that's what makes \`@Value('FOO')\` autocomplete and
|
|
593
|
-
* \`process.env.FOO\` typed.
|
|
594
|
-
*
|
|
595
|
-
* @example
|
|
596
|
-
* DATABASE_URL: v.pipe(v.string(), v.url()),
|
|
597
|
-
* JWT_SECRET: v.pipe(v.string(), v.minLength(32)),
|
|
598
|
-
* REDIS_URL: v.optional(v.pipe(v.string(), v.url())),
|
|
599
|
-
*/
|
|
600
|
-
const envSchema = fromValibot(
|
|
601
|
-
v.object({
|
|
602
|
-
PORT: v.optional(v.pipe(v.string(), v.transform(Number)), '3000'),
|
|
603
|
-
NODE_ENV: v.optional(v.picklist(['development', 'production', 'test']), 'development'),
|
|
604
|
-
LOG_LEVEL: v.optional(v.string(), 'info'),
|
|
605
|
-
// DATABASE_URL: v.pipe(v.string(), v.url()),
|
|
606
|
-
}),
|
|
607
|
-
)
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* IMPORTANT — side effect: register the schema with kickjs's env cache
|
|
611
|
-
* **at module-load time**. \`ConfigService\` and \`@Value()\` both consume
|
|
612
|
-
* this cache, and they will fall back to the base schema (or undefined)
|
|
613
|
-
* if no extended schema has been registered before they're resolved.
|
|
614
|
-
*
|
|
615
|
-
* As long as \`src/index.ts\` imports this file (\`import './config'\`) at
|
|
616
|
-
* the top — before \`bootstrap()\` runs — every controller and service
|
|
617
|
-
* in the app sees the typed extended values.
|
|
618
|
-
*/
|
|
619
|
-
export const env = loadEnvFromSchema(envSchema)
|
|
620
|
-
|
|
621
|
-
export default envSchema
|
|
622
|
-
`:e===`yup`?`import { loadEnvFromSchema } from '@forinda/kickjs/config'
|
|
623
|
-
import { fromYup } from '@forinda/kickjs-schema/yup'
|
|
624
|
-
import * as yup from 'yup'
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Project environment schema (Yup).
|
|
628
|
-
*
|
|
629
|
-
* \`fromYup\` wraps the Yup schema as a \`KickSchema\` so the env loader,
|
|
630
|
-
* validate middleware, and swagger spec generator all see the same
|
|
631
|
-
* shape. The default export is the contract \`kick typegen\` reads to
|
|
632
|
-
* populate \`KickEnv\` via \`InferSchemaOutput<typeof _envSchema>\`.
|
|
633
|
-
*
|
|
634
|
-
* Note: Yup's \`.url()\` defaults to http/https; database connection
|
|
635
|
-
* strings like \`postgres://\` use \`.matches(/^[a-z]+:\\/\\/.+/i)\` or
|
|
636
|
-
* a plain \`.string().required()\`.
|
|
637
|
-
*
|
|
638
|
-
* @example
|
|
639
|
-
* DATABASE_URL: yup.string().required(),
|
|
640
|
-
* JWT_SECRET: yup.string().min(32).required(),
|
|
641
|
-
* REDIS_URL: yup.string().url().optional(),
|
|
642
|
-
*/
|
|
643
|
-
const envSchema = fromYup(
|
|
644
|
-
yup.object({
|
|
645
|
-
PORT: yup.number().default(3000),
|
|
646
|
-
NODE_ENV: yup
|
|
647
|
-
.string()
|
|
648
|
-
.oneOf(['development', 'production', 'test'])
|
|
649
|
-
.default('development'),
|
|
650
|
-
LOG_LEVEL: yup.string().default('info'),
|
|
651
|
-
// DATABASE_URL: yup.string().required(),
|
|
652
|
-
}),
|
|
653
|
-
)
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* IMPORTANT — side effect: register the schema with kickjs's env cache
|
|
657
|
-
* **at module-load time**. \`ConfigService\` and \`@Value()\` both consume
|
|
658
|
-
* this cache, and they will fall back to the base schema (or undefined)
|
|
659
|
-
* if no extended schema has been registered before they're resolved.
|
|
660
|
-
*
|
|
661
|
-
* As long as \`src/index.ts\` imports this file (\`import './config'\`) at
|
|
662
|
-
* the top — before \`bootstrap()\` runs — every controller and service
|
|
663
|
-
* in the app sees the typed extended values.
|
|
664
|
-
*/
|
|
665
|
-
export const env = loadEnvFromSchema(envSchema)
|
|
666
|
-
|
|
667
|
-
export default envSchema
|
|
668
|
-
`:`import { loadEnvFromSchema } from '@forinda/kickjs/config'
|
|
669
|
-
import { fromZod } from '@forinda/kickjs-schema/zod'
|
|
670
|
-
import { z } from 'zod'
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* Project environment schema (Zod).
|
|
674
|
-
*
|
|
675
|
-
* \`fromZod\` wraps the Zod schema as a \`KickSchema\` so the env loader,
|
|
676
|
-
* validate middleware, and swagger spec generator all see the same
|
|
677
|
-
* shape. The default export is the contract \`kick typegen\` reads to
|
|
678
|
-
* populate \`KickEnv\` via \`InferSchemaOutput<typeof _envSchema>\` —
|
|
679
|
-
* that's what makes \`@Value('FOO')\` autocomplete and
|
|
680
|
-
* \`process.env.FOO\` typed.
|
|
681
|
-
*
|
|
682
|
-
* @example
|
|
683
|
-
* DATABASE_URL: z.string().url(),
|
|
684
|
-
* JWT_SECRET: z.string().min(32),
|
|
685
|
-
* REDIS_URL: z.string().url().optional(),
|
|
686
|
-
*/
|
|
687
|
-
const envSchema = fromZod(
|
|
688
|
-
z.object({
|
|
689
|
-
PORT: z.coerce.number().default(3000),
|
|
690
|
-
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
691
|
-
LOG_LEVEL: z.string().default('info'),
|
|
692
|
-
// DATABASE_URL: z.string().url(),
|
|
693
|
-
}),
|
|
694
|
-
)
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* IMPORTANT — side effect: register the schema with kickjs's env cache
|
|
698
|
-
* **at module-load time**. \`ConfigService\` and \`@Value()\` both consume
|
|
699
|
-
* this cache, and they will fall back to the base schema (or undefined)
|
|
700
|
-
* if no extended schema has been registered before they're resolved.
|
|
701
|
-
*
|
|
702
|
-
* As long as \`src/index.ts\` imports this file (\`import './config'\`) at
|
|
703
|
-
* the top — before \`bootstrap()\` runs — every controller and service
|
|
704
|
-
* in the app sees the typed extended values.
|
|
705
|
-
*/
|
|
706
|
-
export const env = loadEnvFromSchema(envSchema)
|
|
707
|
-
|
|
708
|
-
export default envSchema
|
|
709
|
-
`}function ve(){return`import { Service } from '@forinda/kickjs'
|
|
710
|
-
|
|
711
|
-
@Service()
|
|
712
|
-
export class HelloService {
|
|
713
|
-
greet(name: string) {
|
|
714
|
-
return { message: \`Hello \${name} from KickJS!\`, timestamp: new Date().toISOString() }
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
healthCheck() {
|
|
718
|
-
return { status: 'ok', uptime: process.uptime() }
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
`}function ye(){return`import { Controller, Get, Autowired, type Ctx } from '@forinda/kickjs'
|
|
722
|
-
import { HelloService } from './hello.service'
|
|
723
|
-
|
|
724
|
-
// \`Ctx<KickRoutes.HelloController['<method>']>\` is generated by
|
|
725
|
-
// \`kick typegen\` (auto-run on \`kick dev\`). The first run after a fresh
|
|
726
|
-
// scaffold creates \`.kickjs/types/routes.ts\` so this file typechecks.
|
|
727
|
-
// See https://kickjs.app/guide/typegen.
|
|
728
|
-
|
|
729
|
-
@Controller()
|
|
730
|
-
export class HelloController {
|
|
731
|
-
@Autowired() private readonly helloService!: HelloService
|
|
732
|
-
|
|
733
|
-
@Get('/')
|
|
734
|
-
index(ctx: Ctx<KickRoutes.HelloController['index']>) {
|
|
735
|
-
ctx.json(this.helloService.greet('World'))
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
@Get('/health')
|
|
739
|
-
health(ctx: Ctx<KickRoutes.HelloController['health']>) {
|
|
740
|
-
ctx.json(this.helloService.healthCheck())
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
`}function be(){return`import { defineModule } from '@forinda/kickjs'
|
|
744
|
-
import { HelloController } from './hello.controller'
|
|
745
|
-
|
|
746
|
-
export const HelloModule = defineModule({
|
|
747
|
-
name: 'HelloModule',
|
|
748
|
-
build: () => ({
|
|
749
|
-
// \`register(container)\` is optional — only implement it when you need
|
|
750
|
-
// to bind a token to a concrete implementation, e.g.
|
|
751
|
-
// register(container) {
|
|
752
|
-
// container.registerFactory(USER_REPOSITORY, () => container.resolve(InMemoryUserRepository))
|
|
753
|
-
// }
|
|
754
|
-
// The HelloService uses @Service() so the decorator handles registration.
|
|
755
|
-
|
|
756
|
-
routes() {
|
|
757
|
-
return {
|
|
758
|
-
path: '/hello',
|
|
759
|
-
controller: HelloController,
|
|
760
|
-
}
|
|
761
|
-
},
|
|
762
|
-
}),
|
|
763
|
-
})
|
|
764
|
-
`}function xe(e,t=`inmemory`,n=`pnpm`,r=`express`){return`import { defineConfig } from '@forinda/kickjs-cli'
|
|
765
|
-
|
|
766
|
-
export default defineConfig({
|
|
767
|
-
pattern: '${e}',
|
|
768
|
-
// The HTTP engine this app boots on (matches \`bootstrap({ runtime })\` in
|
|
769
|
-
// src/index.ts). Dep-aware commands read it: \`kick add upload\` installs the
|
|
770
|
-
// engine's multipart driver, \`kick doctor\` checks the engine peers, and
|
|
771
|
-
// \`kick typegen\` flips the runtime escape-hatch types to this engine.
|
|
772
|
-
runtime: '${r}',
|
|
773
|
-
// Pinned so \`kick add\` and other dep-installing commands always use the
|
|
774
|
-
// project's intended package manager, regardless of which lockfile exists.
|
|
775
|
-
packageManager: '${n}',
|
|
776
|
-
modules: {
|
|
777
|
-
dir: 'src/modules',
|
|
778
|
-
repo: ${t===`inmemory`?`'inmemory'`:`{ name: '${t}' }`},
|
|
779
|
-
pluralize: true,
|
|
780
|
-
},
|
|
781
|
-
|
|
782
|
-
// \`kick typegen\` populates \`.kickjs/types/\` so \`Ctx<KickRoutes.X['method']>\`
|
|
783
|
-
// resolves to fully-typed params/body/query. Auto-runs on \`kick dev\`.
|
|
784
|
-
// \`'kickjs-schema'\` routes inference through \`InferSchemaOutput\` so the
|
|
785
|
-
// typegen works for any wrapped schema (Zod / Valibot / Yup). Switch
|
|
786
|
-
// to \`'zod'\` if you ship Zod schemas without \`fromZod()\` wrapping, or
|
|
787
|
-
// set \`schemaValidator: false\` to skip schema-driven body typing.
|
|
788
|
-
typegen: {
|
|
789
|
-
schemaValidator: 'kickjs-schema',
|
|
790
|
-
},
|
|
791
|
-
|
|
792
|
-
commands: [
|
|
793
|
-
{
|
|
794
|
-
name: 'test',
|
|
795
|
-
description: 'Run tests with Vitest',
|
|
796
|
-
steps: 'npx vitest run',
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
name: 'format',
|
|
800
|
-
description: 'Format code with Prettier',
|
|
801
|
-
steps: 'npx prettier --write src/',
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
name: 'format:check',
|
|
805
|
-
description: 'Check formatting without writing',
|
|
806
|
-
steps: 'npx prettier --check src/',
|
|
807
|
-
},
|
|
808
|
-
{
|
|
809
|
-
name: 'ci:check',
|
|
810
|
-
description: 'Run typecheck + format check',
|
|
811
|
-
steps: ['npx tsc --noEmit', 'npx prettier --check src/'],
|
|
812
|
-
aliases: ['verify'],
|
|
813
|
-
},
|
|
814
|
-
],
|
|
815
|
-
})
|
|
816
|
-
`}async function Se(e){let{pascal:t,kebab:n,plural:r,style:i,write:a}=e;await a(`${n}.module.ts`,oe({pascal:t,kebab:n,plural:r,style:i})),await a(`${n}.controller.ts`,`import { Controller, Get, type Ctx } from '@forinda/kickjs'
|
|
817
|
-
|
|
818
|
-
// \`Ctx<KickRoutes.${t}Controller['<method>']>\` is generated by
|
|
819
|
-
// \`kick typegen\` (auto-run on \`kick dev\`).
|
|
820
|
-
|
|
821
|
-
@Controller()
|
|
822
|
-
export class ${t}Controller {
|
|
823
|
-
@Get('/')
|
|
824
|
-
async list(ctx: Ctx<KickRoutes.${t}Controller['list']>) {
|
|
825
|
-
ctx.json({ message: '${t} list' })
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
`)}async function Ce(e){let{pascal:t,kebab:n,plural:r,pluralPascal:i,repo:a,noTests:o,tokenScope:s,style:c,write:l}=e;await l(`${n}.module.ts`,N({pascal:t,kebab:n,plural:r,repo:a,style:c})),await l(`${n}.constants.ts`,L({pascal:t,kebab:n})),await l(`${n}.controller.ts`,se({pascal:t,kebab:n,plural:r,pluralPascal:i})),await l(`${n}.service.ts`,I({pascal:t,kebab:n})),await l(`dtos/create-${n}.dto.ts`,ce({pascal:t,kebab:n})),await l(`dtos/update-${n}.dto.ts`,le({pascal:t,kebab:n})),await l(`dtos/${n}-response.dto.ts`,ue({pascal:t,kebab:n})),await l(`${n}.repository.ts`,de({pascal:t,kebab:n,dtoPrefix:`./dtos`,tokenScope:s}));let u=a===`inmemory`,d=u?`in-memory-${n}`:`${O(a)}-${n}`,f=u?P({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`}):F({pascal:t,kebab:n,repoType:a,repoPrefix:`.`,dtoPrefix:`./dtos`});await l(`${d}.repository.ts`,f),o||(a!==`inmemory`&&await l(`in-memory-${n}.repository.ts`,P({pascal:t,kebab:n,repoPrefix:`.`,dtoPrefix:`./dtos`})),await l(`__tests__/${n}.controller.test.ts`,fe({pascal:t,kebab:n,plural:r})),await l(`__tests__/${n}.repository.test.ts`,pe({pascal:t,kebab:n,plural:r,repoPrefix:`../in-memory-${n}.repository`})))}function we(e){return e?typeof e==`string`?e:e.name:`inmemory`}async function Te(t){let{name:n,modulesDir:a,noEntity:c,noTests:l,repo:u=`inmemory`,force:d,dryRun:f}=t,p=t.pluralize!==!1,h=t.pattern??`rest`;t.minimal&&(h=`minimal`);let g=O(n),_=E(n),v=p?k(g):g,y=p?A(_):_,b=m(a,v),x=[],S=d??!1,C={kebab:g,pascal:_,plural:v,pluralPascal:y,moduleDir:b,repo:u,noEntity:c??!1,noTests:l??!1,prismaClientPath:t.prismaClientPath??`@prisma/client`,tokenScope:t.tokenScope??`app`,style:t.style??`define`,write:async(t,n)=>{let a=m(b,t);if(f){x.push(a);return}if(!S&&await s(a)&&!await o({message:`File exists: ${r.dim(t)}. Overwrite?`,initialValue:!1})){i.warn(`Skipped: ${t}`);return}await e(a,n),x.push(a)},files:x};switch(h){case`minimal`:await Se(C);break;default:await Ce(C);break}return f||await R(a,_,v,g,C.style),x}async function R(t,n,r,i,a=`define`){let o=m(t,`index.ts`),c=await s(o),l=`./${r}/${i}.module`,u=a===`class`?`${n}Module`:`${n}Module()`;if(!c){await e(o,a===`class`?`import type { AppModuleEntry } from '@forinda/kickjs'
|
|
829
|
-
import { ${n}Module } from '${l}'
|
|
830
|
-
|
|
831
|
-
export const modules: AppModuleEntry[] = [${u}]
|
|
832
|
-
`:`import { defineModules } from '@forinda/kickjs'
|
|
833
|
-
import { ${n}Module } from '${l}'
|
|
834
|
-
|
|
835
|
-
export const modules = defineModules().mount(${u})
|
|
836
|
-
`);return}let d=await x(o,`utf-8`),f=`import { ${n}Module } from '${l}'`,p=j(l);if(!RegExp(`^import\\s*\\{[^}]*\\b${j(n)}Module\\b[^}]*\\}\\s*from\\s*['"]${p}['"]`,`m`).test(d)){let e=d.lastIndexOf(`import `);if(e!==-1){let t=d.indexOf(`
|
|
837
|
-
`,e);d=d.slice(0,t+1)+f+`
|
|
838
|
-
`+d.slice(t+1)}else d=f+`
|
|
839
|
-
`+d}let h=B(d);if(h){let e=d.slice(h.rhsStart,h.rhsEnd+1);RegExp(`\\b${j(n)}Module\\b`).test(e)||(d=z(d,u))}else d=z(d,u);await S(o,d,`utf-8`)}function z(e,t){let n=B(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 B(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=De(e,n);return t===-1?null:{shape:`array`,rhsStart:n,rhsEnd:t}}if(e.slice(n,n+13)===`defineModules`){let t=Ee(e,n);return t===-1?null:{shape:`chain`,rhsStart:n,rhsEnd:t-1,chainEnd:t}}return null}function Ee(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=H(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=H(e,t);if(n===-1)break;i=n+1}return i}function V(e,t){let n=e.slice(t,t+2);if(n===`//`){for(t+=2;t<e.length&&e[t]!==`
|
|
840
|
-
`;)t++;return t}if(n===`/*`){for(t+=2;t+1<e.length&&!(e[t]===`*`&&e[t+1]===`/`);)t++;return t+2}return t}function De(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=V(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 H(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=V(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 Oe(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 {
|
|
841
|
-
defineAdapter,
|
|
842
|
-
type AdapterContext,
|
|
843
|
-
type AdapterMiddleware,
|
|
844
|
-
type ContributorRegistrations,
|
|
845
|
-
type Constructor,
|
|
846
|
-
} from '@forinda/kickjs'
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* Configuration for the ${a} adapter.
|
|
850
|
-
*
|
|
851
|
-
* Adapters typically take a small config object so callers can tune
|
|
852
|
-
* behaviour at bootstrap time. Keep the shape narrow — anything
|
|
853
|
-
* derived from the environment should be read inside the build
|
|
854
|
-
* function via getEnv(), not forced onto the caller.
|
|
855
|
-
*/
|
|
856
|
-
export interface ${a}AdapterConfig {
|
|
857
|
-
// Add your adapter configuration here, e.g.:
|
|
858
|
-
// enabled?: boolean
|
|
859
|
-
// apiKey?: string
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
/**
|
|
863
|
-
* ${a} adapter — built via \`defineAdapter()\` so callers get the
|
|
864
|
-
* factory's call / \`.scoped()\` / \`.async()\` surfaces for free.
|
|
865
|
-
*
|
|
866
|
-
* Hooks into the Application lifecycle to add middleware, routes,
|
|
867
|
-
* Context Contributors, or external service connections.
|
|
868
|
-
*
|
|
869
|
-
* Every lifecycle hook below is OPTIONAL. The scaffold emits all of
|
|
870
|
-
* them so adopters can browse what's available and delete what they
|
|
871
|
-
* don't need — \`build()\` returning \`{}\` is also valid for an adapter
|
|
872
|
-
* that only contributes config defaults.
|
|
873
|
-
*
|
|
874
|
-
* @example
|
|
875
|
-
* \`\`\`ts
|
|
876
|
-
* import { bootstrap } from '@forinda/kickjs'
|
|
877
|
-
* import { ${a}Adapter } from './adapters/${i}.adapter'
|
|
878
|
-
*
|
|
879
|
-
* bootstrap({
|
|
880
|
-
* modules,
|
|
881
|
-
* adapters: [${a}Adapter({ /* config overrides *\\/ })],
|
|
882
|
-
* })
|
|
883
|
-
* \`\`\`
|
|
884
|
-
*/
|
|
885
|
-
export const ${a}Adapter = defineAdapter<${a}AdapterConfig>({
|
|
886
|
-
name: '${a}Adapter',
|
|
887
|
-
defaults: {
|
|
888
|
-
// Default config values go here. The adopter's overrides shallow-merge
|
|
889
|
-
// on top of these before \`build()\` runs.
|
|
890
|
-
},
|
|
891
|
-
build: (_config, { name: _name }) => {
|
|
892
|
-
// Closures inside \`build()\` are how each adapter instance owns its
|
|
893
|
-
// own state (database client, Map, timer handle, …). The same
|
|
894
|
-
// \`_config\` is visible to every hook below.
|
|
895
|
-
|
|
896
|
-
return {
|
|
897
|
-
/**
|
|
898
|
-
* Express middleware entries the Application mounts at named phases.
|
|
899
|
-
*
|
|
900
|
-
* \`phase\` controls where each handler sits in the pipeline:
|
|
901
|
-
* 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.
|
|
902
|
-
*
|
|
903
|
-
* \`path\` (optional) scopes the entry to a path prefix.
|
|
904
|
-
*
|
|
905
|
-
* Delete this hook entirely if you don't add middleware.
|
|
906
|
-
*/
|
|
907
|
-
middleware(): AdapterMiddleware[] {
|
|
908
|
-
return [
|
|
909
|
-
// Example: add a custom header to all responses
|
|
910
|
-
// {
|
|
911
|
-
// phase: 'beforeGlobal',
|
|
912
|
-
// handler: (_req, res, next) => {
|
|
913
|
-
// res.setHeader('X-${a}', 'true')
|
|
914
|
-
// next()
|
|
915
|
-
// },
|
|
916
|
-
// },
|
|
917
|
-
// Example: scope a rate limiter to one path prefix
|
|
918
|
-
// {
|
|
919
|
-
// phase: 'beforeRoutes',
|
|
920
|
-
// path: '/api/v1/auth',
|
|
921
|
-
// handler: rateLimit({ max: 10 }),
|
|
922
|
-
// },
|
|
923
|
-
]
|
|
924
|
-
},
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Runs BEFORE global middleware. Mount routes that should bypass the
|
|
928
|
-
* middleware stack — health checks, docs UI, static assets, OAuth
|
|
929
|
-
* callbacks. Anything you want reachable even if a global middleware
|
|
930
|
-
* later in the chain rejects requests.
|
|
931
|
-
*
|
|
932
|
-
* Delete this hook if you have no early routes.
|
|
933
|
-
*/
|
|
934
|
-
beforeMount(_ctx: AdapterContext): void {
|
|
935
|
-
// Example:
|
|
936
|
-
// _ctx.app.get('/${i}/status', (_req, res) => res.json({ status: 'ok' }))
|
|
937
|
-
},
|
|
938
|
-
|
|
939
|
-
/**
|
|
940
|
-
* Fires once per controller class as the router mounts. Use this to
|
|
941
|
-
* collect route metadata for OpenAPI specs, dependency graphs, route
|
|
942
|
-
* inventories, devtools dashboards.
|
|
943
|
-
*
|
|
944
|
-
* Delete this hook unless your adapter introspects the route registry.
|
|
945
|
-
*/
|
|
946
|
-
onRouteMount(_controllerClass: Constructor, _mountPath: string): void {
|
|
947
|
-
// Example (Swagger-style): collect routes for the spec.
|
|
948
|
-
// openApiSpec.addController(_controllerClass, _mountPath)
|
|
949
|
-
},
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Runs AFTER modules + routes are wired, BEFORE the server starts.
|
|
953
|
-
* Right place for late-stage DI registrations or final config validation.
|
|
954
|
-
*
|
|
955
|
-
* Delete this hook if there's nothing to wire post-modules.
|
|
956
|
-
*/
|
|
957
|
-
beforeStart(_ctx: AdapterContext): void {
|
|
958
|
-
// Example: _ctx.container.registerInstance(MY_TOKEN, new MyService(_config))
|
|
959
|
-
},
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* Runs AFTER the HTTP server is listening. The raw \`http.Server\` is
|
|
963
|
-
* available on \`ctx.server\` — attach upgrade handlers (Socket.IO,
|
|
964
|
-
* gRPC, GraphQL subscriptions), warm caches, log a banner.
|
|
965
|
-
*
|
|
966
|
-
* Delete this hook if you don't need the running server reference.
|
|
967
|
-
*/
|
|
968
|
-
afterStart(_ctx: AdapterContext): void {
|
|
969
|
-
// Example: const io = new Server(_ctx.server)
|
|
970
|
-
},
|
|
971
|
-
|
|
972
|
-
/**
|
|
973
|
-
* Returns Context Contributors to merge into every route's pipeline
|
|
974
|
-
* at the \`'adapter'\` precedence level. Per-route handlers can
|
|
975
|
-
* override the value at the method / class / module level.
|
|
976
|
-
*
|
|
977
|
-
* Delete this hook unless your adapter ships typed per-request values
|
|
978
|
-
* (auth user, tenant, locale, feature flags, geo, etc).
|
|
979
|
-
*/
|
|
980
|
-
contributors(): ContributorRegistrations {
|
|
981
|
-
return [
|
|
982
|
-
// Example:
|
|
983
|
-
// import { defineHttpContextDecorator } from '@forinda/kickjs'
|
|
984
|
-
// declare module '@forinda/kickjs' { interface ContextMeta { ${i}: { id: string } } }
|
|
985
|
-
// const Load${a} = defineHttpContextDecorator({
|
|
986
|
-
// key: '${i}',
|
|
987
|
-
// resolve: (ctx) => ({ id: ctx.req.headers['x-${i}-id'] as string }),
|
|
988
|
-
// })
|
|
989
|
-
// return [Load${a}.registration]
|
|
990
|
-
]
|
|
991
|
-
},
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Runs on graceful shutdown (SIGINT/SIGTERM). Clean up long-lived
|
|
995
|
-
* resources the adapter owns: close connections, flush buffers,
|
|
996
|
-
* cancel timers. The framework runs every adapter's \`shutdown\`
|
|
997
|
-
* concurrently via \`Promise.allSettled\` — one failure won't block
|
|
998
|
-
* sibling adapters.
|
|
999
|
-
*
|
|
1000
|
-
* Delete this hook if your adapter holds no resources.
|
|
1001
|
-
*/
|
|
1002
|
-
async shutdown(): Promise<void> {
|
|
1003
|
-
// Example: await this.pool.end()
|
|
1004
|
-
// Example: clearInterval(this.heartbeatTimer)
|
|
1005
|
-
},
|
|
1006
|
-
}
|
|
1007
|
-
},
|
|
1008
|
-
})
|
|
1009
|
-
`),o.push(s),o}const ke={controller:``,service:``,dto:`dtos`,guard:`guards`,middleware:`middleware`,contributor:`contributors`};function U(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=ke,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 Ae(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=U({type:`middleware`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/middleware`,pattern:a,shouldPluralize:t.pluralize??!0}),s=O(n),c=D(n),l=[],u=m(o,`${s}.middleware.ts`);return await e(u,`import type { Request, Response, NextFunction } from 'express'
|
|
1010
|
-
|
|
1011
|
-
export interface ${E(n)}Options {
|
|
1012
|
-
// Add configuration options here. The factory below closes over the
|
|
1013
|
-
// resolved options object; pass them at the call site —
|
|
1014
|
-
// \`${c}({ foo: 'bar' })\` — and the closure preserves them across
|
|
1015
|
-
// every request.
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* ${E(n)} middleware.
|
|
1020
|
-
*
|
|
1021
|
-
* Usage in bootstrap (fires on every request):
|
|
1022
|
-
* middleware: [${c}()]
|
|
1023
|
-
*
|
|
1024
|
-
* Usage with adapter — phase controls *when* the handler runs:
|
|
1025
|
-
*
|
|
1026
|
-
* middleware() {
|
|
1027
|
-
* return [{ handler: ${c}(), phase: 'afterGlobal' }]
|
|
1028
|
-
* }
|
|
1029
|
-
*
|
|
1030
|
-
* Phase semantics (see \`MiddlewarePhase\` JSDoc for the full contract):
|
|
1031
|
-
* - 'beforeGlobal' / 'afterGlobal' / 'beforeRoutes' — fire on every
|
|
1032
|
-
* request, before module routes run.
|
|
1033
|
-
* - 'afterRoutes' — fires ONLY when no route matched (404 fall-through)
|
|
1034
|
-
* OR a route handler called \`next()\` without ending the response.
|
|
1035
|
-
* Controllers that call \`ctx.json(…)\` end the chain and skip this
|
|
1036
|
-
* phase. For per-response work (logging, metrics) attach to
|
|
1037
|
-
* \`res.on('finish', …)\` from an earlier-phase middleware instead.
|
|
1038
|
-
*
|
|
1039
|
-
* Optional path scope — string, RegExp, or array of either:
|
|
1040
|
-
* middleware() {
|
|
1041
|
-
* return [{
|
|
1042
|
-
* handler: ${c}({ region: 'eu' }),
|
|
1043
|
-
* phase: 'afterGlobal',
|
|
1044
|
-
* path: ['/api', /^\\/admin/],
|
|
1045
|
-
* }]
|
|
1046
|
-
* }
|
|
1047
|
-
*
|
|
1048
|
-
* Usage with @Middleware decorator:
|
|
1049
|
-
* @Middleware(${c}())
|
|
1050
|
-
*/
|
|
1051
|
-
export function ${c}(options: ${E(n)}Options = {}) {
|
|
1052
|
-
return (req: Request, res: Response, next: NextFunction) => {
|
|
1053
|
-
// Implement your middleware logic here. \`options\` is captured by
|
|
1054
|
-
// closure — log or read it anywhere in this handler body.
|
|
1055
|
-
void options
|
|
1056
|
-
next()
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
`),l.push(u),l}async function je(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=U({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'
|
|
1060
|
-
import type { RequestContext } from '@forinda/kickjs'
|
|
1061
|
-
|
|
1062
|
-
/**
|
|
1063
|
-
* ${l} guard.
|
|
1064
|
-
*
|
|
1065
|
-
* Guards protect routes by checking conditions before the handler runs.
|
|
1066
|
-
* Return early with an error response to block access.
|
|
1067
|
-
*
|
|
1068
|
-
* Usage:
|
|
1069
|
-
* @Middleware(${c}Guard)
|
|
1070
|
-
* @Get('/protected')
|
|
1071
|
-
* async handler(ctx: RequestContext) { ... }
|
|
1072
|
-
*/
|
|
1073
|
-
export async function ${c}Guard(ctx: RequestContext, next: () => void): Promise<void> {
|
|
1074
|
-
// Example: check for an authorization header
|
|
1075
|
-
const header = ctx.headers.authorization
|
|
1076
|
-
if (!header?.startsWith('Bearer ')) {
|
|
1077
|
-
ctx.res.status(401).json({ message: 'Missing or invalid authorization header' })
|
|
1078
|
-
return
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
const token = header.slice(7)
|
|
1082
|
-
|
|
1083
|
-
try {
|
|
1084
|
-
// Verify the token using a service from the DI container
|
|
1085
|
-
// const container = Container.getInstance()
|
|
1086
|
-
// const authService = container.resolve(AuthService)
|
|
1087
|
-
// const payload = authService.verifyToken(token)
|
|
1088
|
-
// ctx.set('auth', payload)
|
|
1089
|
-
|
|
1090
|
-
next()
|
|
1091
|
-
} catch {
|
|
1092
|
-
ctx.res.status(401).json({ message: 'Invalid or expired token' })
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
`),u.push(d),u}async function Me(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=U({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'
|
|
1096
|
-
|
|
1097
|
-
@Service()
|
|
1098
|
-
export class ${c}Service {
|
|
1099
|
-
// Inject dependencies via constructor
|
|
1100
|
-
// constructor(
|
|
1101
|
-
// @Inject(MY_REPO) private readonly repo: IMyRepository,
|
|
1102
|
-
// ) {}
|
|
1103
|
-
}
|
|
1104
|
-
`),l.push(u),l}async function Ne(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=U({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, type Ctx } from '@forinda/kickjs'
|
|
1105
|
-
|
|
1106
|
-
// \`Ctx<KickRoutes.${c}Controller['<method>']>\` is generated by
|
|
1107
|
-
// \`kick typegen\` (auto-run on \`kick dev\`). After the first run, your IDE
|
|
1108
|
-
// will autocomplete \`ctx.params\`, \`ctx.body\`, and \`ctx.query\`.
|
|
1109
|
-
// See https://kickjs.app/guide/typegen for details.
|
|
1110
|
-
|
|
1111
|
-
@Controller()
|
|
1112
|
-
export class ${c}Controller {
|
|
1113
|
-
// @Autowired() private readonly myService!: MyService
|
|
1114
|
-
|
|
1115
|
-
@Get('/')
|
|
1116
|
-
async list(ctx: Ctx<KickRoutes.${c}Controller['list']>) {
|
|
1117
|
-
ctx.json({ message: '${c} list' })
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
@Post('/')
|
|
1121
|
-
async create(ctx: Ctx<KickRoutes.${c}Controller['create']>) {
|
|
1122
|
-
ctx.created({ message: '${c} created', data: ctx.body })
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
`),l.push(u),l}async function Pe(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=U({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'
|
|
1126
|
-
|
|
1127
|
-
export const ${l}Schema = z.object({
|
|
1128
|
-
// Define your schema fields here
|
|
1129
|
-
name: z.string().min(1).max(200),
|
|
1130
|
-
})
|
|
1131
|
-
|
|
1132
|
-
export type ${c}DTO = z.infer<typeof ${l}Schema>
|
|
1133
|
-
`),u.push(d),u}const Fe={swagger:`@forinda/kickjs-swagger`,ws:`@forinda/kickjs-ws`,queue:`@forinda/kickjs-queue`,devtools:`@forinda/kickjs-devtools`},Ie={zod:{name:`zod`,range:`^4.3.6`},valibot:{name:`valibot`,range:`^1.4.1`},yup:{name:`yup`,range:`^1.7.1`}};function W(e,t){let n=e[t];if(!n)throw Error(`generatePackageJson: missing resolved version for ${t}. Add it to SIBLING_PACKAGES in generators/project.ts.`);return n}function Le(e,t,n,r=[],i=`zod`,a=`express`){let o=Ie[i],s={"@forinda/kickjs":W(n,`@forinda/kickjs`),"@forinda/kickjs-schema":W(n,`@forinda/kickjs-schema`),dotenv:`^17.3.1`,"reflect-metadata":`^0.2.2`,[o.name]:o.range};a===`express`?s.express=`^5.1.0`:a===`fastify`?(s.fastify=`^5.0.0`,s[`@fastify/middie`]=`^9.0.0`,s[`serve-static`]=`^2.2.0`):a===`h3`&&(s.h3=`^1.0.0`,s[`serve-static`]=`^2.2.0`);for(let e of r){let t=Fe[e];t&&!s[t]&&(s[t]=W(n,t))}return JSON.stringify({name:e,version:`0.0.0`,type:`module`,scripts:{dev:`kick dev`,"dev:debug":`kick dev:debug`,build:`kick build`,start:`kick start`,test:`vitest run`,"test:watch":`vitest`,typecheck:`tsc --noEmit`,typegen:`kick typegen`,lint:`eslint src/`,format:`prettier --write src/`},dependencies:s,devDependencies:{"@forinda/kickjs-cli":W(n,`@forinda/kickjs-cli`),"@forinda/kickjs-vite":W(n,`@forinda/kickjs-vite`),"@swc/core":`^1.15.21`,...a===`express`?{"@types/express":`^5.0.6`}:{},"@types/node":`^25.0.0`,"unplugin-swc":`^1.5.9`,vite:`^8.0.3`,vitest:`^4.1.2`,typescript:`^6.0.3`,prettier:`^3.8.1`}},null,2)}function Re(){return`import { defineConfig } from 'vite'
|
|
1134
|
-
import { resolve } from 'node:path'
|
|
1135
|
-
import swc from 'unplugin-swc'
|
|
1136
|
-
import { kickjsVitePlugin, envWatchPlugin } from '@forinda/kickjs-vite'
|
|
1137
|
-
|
|
1138
|
-
export default defineConfig({
|
|
1139
|
-
oxc: false,
|
|
1140
|
-
plugins: [
|
|
1141
|
-
swc.vite(),
|
|
1142
|
-
kickjsVitePlugin({ entry: 'src/index.ts' }),
|
|
1143
|
-
// Watches .env files and triggers a full reload on change so the
|
|
1144
|
-
// dev server picks up env tweaks without a manual restart.
|
|
1145
|
-
envWatchPlugin(),
|
|
1146
|
-
],
|
|
1147
|
-
resolve: {
|
|
1148
|
-
alias: {
|
|
1149
|
-
'@': resolve(__dirname, 'src'),
|
|
1150
|
-
},
|
|
1151
|
-
},
|
|
1152
|
-
build: {
|
|
1153
|
-
target: 'node20',
|
|
1154
|
-
ssr: true,
|
|
1155
|
-
outDir: 'dist',
|
|
1156
|
-
sourcemap: true,
|
|
1157
|
-
rollupOptions: {
|
|
1158
|
-
input: resolve(__dirname, 'src/index.ts'),
|
|
1159
|
-
output: { format: 'esm' },
|
|
1160
|
-
},
|
|
1161
|
-
},
|
|
1162
|
-
})
|
|
1163
|
-
`}function ze(){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 Be(){return JSON.stringify({semi:!1,singleQuote:!0,trailingComma:`all`,printWidth:100,tabWidth:2},null,2)}function Ve(){return`# https://editorconfig.org
|
|
1164
|
-
root = true
|
|
1165
|
-
|
|
1166
|
-
[*]
|
|
1167
|
-
indent_style = space
|
|
1168
|
-
indent_size = 2
|
|
1169
|
-
end_of_line = lf
|
|
1170
|
-
charset = utf-8
|
|
1171
|
-
trim_trailing_whitespace = true
|
|
1172
|
-
insert_final_newline = true
|
|
1173
|
-
|
|
1174
|
-
[*.md]
|
|
1175
|
-
trim_trailing_whitespace = false
|
|
1176
|
-
`}function He(){return`node_modules/
|
|
1177
|
-
dist/
|
|
1178
|
-
.env
|
|
1179
|
-
coverage/
|
|
1180
|
-
.DS_Store
|
|
1181
|
-
*.tsbuildinfo
|
|
1182
|
-
.kickjs/
|
|
1183
|
-
`}function Ue(){return`# Auto-detect text files and normalise line endings to LF
|
|
1184
|
-
* text=auto eol=lf
|
|
1185
|
-
|
|
1186
|
-
# Explicitly mark generated / binary files
|
|
1187
|
-
*.png binary
|
|
1188
|
-
*.jpg binary
|
|
1189
|
-
*.jpeg binary
|
|
1190
|
-
*.gif binary
|
|
1191
|
-
*.ico binary
|
|
1192
|
-
*.woff binary
|
|
1193
|
-
*.woff2 binary
|
|
1194
|
-
*.ttf binary
|
|
1195
|
-
*.eot binary
|
|
1196
|
-
|
|
1197
|
-
# Lock files — treat as generated
|
|
1198
|
-
pnpm-lock.yaml -diff linguist-generated
|
|
1199
|
-
yarn.lock -diff linguist-generated
|
|
1200
|
-
package-lock.json -diff linguist-generated
|
|
1201
|
-
`}function We(){return`PORT=3000
|
|
1202
|
-
NODE_ENV=development
|
|
1203
|
-
`}function Ge(){return`PORT=3000
|
|
1204
|
-
NODE_ENV=development
|
|
1205
|
-
`}function Ke(){return`import { defineConfig } from 'vitest/config'
|
|
1206
|
-
import swc from 'unplugin-swc'
|
|
1207
|
-
|
|
1208
|
-
export default defineConfig({
|
|
1209
|
-
plugins: [swc.vite()],
|
|
1210
|
-
test: {
|
|
1211
|
-
globals: true,
|
|
1212
|
-
environment: 'node',
|
|
1213
|
-
include: ['src/**/*.test.ts'],
|
|
1214
|
-
},
|
|
1215
|
-
})
|
|
1216
|
-
`}const G={kickjs:{pkg:`@forinda/kickjs`,peers:[`express`],description:`Unified framework: DI, decorators, routing, middleware`,core:!0},vite:{pkg:`@forinda/kickjs-vite`,peers:[`vite`],description:`Vite plugin: dev server, HMR, module discovery`,dev:!0,core:!0},cli:{pkg:`@forinda/kickjs-cli`,peers:[],description:`CLI tool and code generators`,dev:!0,core:!0},zod:{pkg:`zod`,peers:[],description:`Zod schema validation (env, DTOs, OpenAPI) — wrap with fromZod()`},valibot:{pkg:`valibot`,peers:[],description:`Valibot schema validation — wrap with fromValibot()`},yup:{pkg:`yup`,peers:[],description:`Yup schema validation — wrap with fromYup()`},auth:{pkg:`@forinda/kickjs-auth`,peers:[`jsonwebtoken`],description:`JWT, API key, OAuth strategies, @Public, @Roles (+ optional argon2/bcryptjs)`,deprecated:`auth is moving to BYO — compose @LoadAuthUser/@RequireRole/@Public from defineContextDecorator (see the BYO Auth recipe in the docs)`},ai:{pkg:`@forinda/kickjs-ai`,peers:[`zod`],description:`AI toolkit — LLM providers, tool definitions from controllers`},swagger:{pkg:`@forinda/kickjs-swagger`,peers:[],description:`OpenAPI spec + Swagger UI + ReDoc`},db:{pkg:`@forinda/kickjs-db`,peers:[],description:`kick/db core — schema DSL, migrations, KickDbClient, customType`},pg:{pkg:`@forinda/kickjs-db`,peers:[`pg`],description:`kick/db + PostgreSQL driver (use @forinda/kickjs-db/pg)`},sqlite:{pkg:`@forinda/kickjs-db`,peers:[`better-sqlite3`],description:`kick/db + SQLite driver (use @forinda/kickjs-db/sqlite)`},mysql:{pkg:`@forinda/kickjs-db`,peers:[`mysql2`],description:`kick/db + MySQL driver (use @forinda/kickjs-db/mysql)`},drizzle:{pkg:`@forinda/kickjs-drizzle`,peers:[`drizzle-orm`],description:`Drizzle ORM adapter + query builder`,deprecated:"early-adoption adapter, no longer maintained — wire Drizzle directly (BYO), or use @forinda/kickjs-db, the built-in Kick ORM (`kick add db` / pg / sqlite / mysql)"},prisma:{pkg:`@forinda/kickjs-prisma`,peers:[`@prisma/client`],description:`Prisma adapter + query builder`,deprecated:"early-adoption adapter, no longer maintained — wire Prisma directly (BYO), or use @forinda/kickjs-db, the built-in Kick ORM (`kick add db` / pg / sqlite / mysql)"},ws:{pkg:`@forinda/kickjs-ws`,peers:[`ws`],description:`WebSocket with @WsController decorators`},devtools:{pkg:`@forinda/kickjs-devtools`,peers:[],description:`Development dashboard — routes, DI, metrics, health`,dev:!0},queue:{pkg:`@forinda/kickjs-queue`,peers:[],description:`Queue adapter (BullMQ/RabbitMQ/Kafka)`},"queue:bullmq":{pkg:`@forinda/kickjs-queue`,peers:[`bullmq`,`ioredis`],description:`Queue with BullMQ + Redis`},"queue:rabbitmq":{pkg:`@forinda/kickjs-queue`,peers:[`amqplib`],description:`Queue with RabbitMQ`},"queue:kafka":{pkg:`@forinda/kickjs-queue`,peers:[`kafkajs`],description:`Queue with Kafka`},"queue:redis-pubsub":{pkg:`@forinda/kickjs-queue`,peers:[`ioredis`],description:`Lightweight pub/sub via Redis (no persistence)`},mcp:{pkg:`@forinda/kickjs-mcp`,peers:[`@modelcontextprotocol/sdk`],description:`Model Context Protocol server — expose @Controller endpoints as AI tools`},testing:{pkg:`@forinda/kickjs-testing`,peers:[],description:`Test utilities and TestModule builder`,dev:!0}},qe=Object.entries(G).filter(([e,t])=>!t.core&&!t.deprecated&&!e.includes(`:`)&&![`pg`,`sqlite`,`mysql`,`zod`,`valibot`,`yup`].includes(e)).map(([e])=>e).join(`, `),K={express:{prod:`multer`,dev:`@types/multer`,note:`Express uploads use multer (memory/disk storage, ctx.file / ctx.files).`},fastify:{prod:`@fastify/multipart`,note:`Fastify uploads use @fastify/multipart (buffered into ctx.file / ctx.files).`},h3:{note:`h3 parses multipart natively (readMultipartFormData) — no driver to install.`}};async function q(e=process.cwd()){let t=(await c(e))?.runtime;return t===`express`||t===`fastify`||t===`h3`?t:Je(e)}function Je(e=process.cwd()){let t=J(`package.json`,e);if(t)try{let e=JSON.parse(v(g(t,`package.json`),`utf-8`)),n={...e.dependencies,...e.devDependencies};if(`fastify`in n)return`fastify`;if(`h3`in n)return`h3`}catch{}return`express`}function J(e,t=process.cwd()){let n=t;for(;;){if(_(g(n,e)))return n;let t=p(n);if(t===n)return null;n=t}}function Ye(){return J(`pnpm-lock.yaml`)?`pnpm`:J(`yarn.lock`)?`yarn`:J(`bun.lockb`)||J(`bun.lock`)?`bun`:J(`package-lock.json`)?`npm`:null}function Xe(){let e=process.cwd();for(;e;){let t=g(e,`package.json`);if(_(t))try{let e=JSON.parse(v(t,`utf-8`)).packageManager;if(typeof e==`string`){let t=e.split(`@`)[0];if(l.includes(t))return t}}catch{}let n=p(e);if(n===e)return null;e=n}return null}async function Y(e){if(e&&l.includes(e))return{pm:e,source:`flag`};let t=await c(process.cwd());if(t?.packageManager&&l.includes(t.packageManager))return{pm:t.packageManager,source:`config`};let n=Xe();if(n)return{pm:n,source:`package.json`};let r=Ye();return r?{pm:r,source:`lockfile`}:{pm:`npm`,source:`default`}}async function Ze(e){let{pm:t}=await Y(e);return t}function X(e=!1){let t=Object.entries(G),n=Math.max(...t.map(([e])=>e.length)),r=t.filter(([,e])=>e.core),i=t.filter(([,e])=>!e.core),a=([e,t])=>{let r=e.padEnd(n+2),i=t.peers.length?` (+ ${t.peers.join(`, `)})`:``,a=t.deprecated?` [DEPRECATED — ${t.deprecated}]`:``;return` ${r} ${t.description}${i}${a}`};console.log(`
|
|
1217
|
-
Core packages (always installed by \`kick new\`):
|
|
1218
|
-
`);for(let e of r)console.log(a(e));if(e){console.log(`
|
|
1219
|
-
Optional packages (add as needed):
|
|
1220
|
-
`);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(`
|
|
1221
|
-
Usage: kick add ai db swagger`),console.log(` kick add queue:bullmq`),console.log(` kick add upload # installs the multipart driver for your runtime`),console.log()}function Qe(e,t,n=`express`){let r=new Set,i=new Set,a=[],o=[],s=[];for(let c of e){if(c===`upload`){let e=K[n];s.push(`upload (${n}): ${e.note}`),e.prod&&(t?i:r).add(e.prod),e.dev&&i.add(e.dev);continue}let e=G[c];if(!e){a.push(c);continue}e.deprecated&&o.push(`'${c}' (${e.pkg}) is deprecated — ${e.deprecated}`);let l=t||e.dev?i:r;l.add(e.pkg);for(let t of e.peers)l.add(t)}return{prodDeps:[...r],devDeps:[...i],unknown:a,warnings:o,notices:s}}function $e(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=>{X(!!e.all)})}function et(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){X(!!t.all);return}let{pm:n,source:r}=await Y(t.pm);console.log(`\n Using ${n} (resolved from ${r})`);let i=await q(process.cwd()),{prodDeps:a,devDeps:o,unknown:s,warnings:c,notices:l}=Qe(e,!!t.dev,i);for(let e of c)console.warn(`\n WARNING: ${e}`);for(let e of l)console.log(`\n ${e}`);if(!(s.length>0&&(console.log(`\n Unknown packages: ${s.join(`, `)}`),console.log(` Run "kick add --list" to see available packages.
|
|
1222
|
-
`),a.length===0&&o.length===0))){if(a.length>0){let e=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.length>0){let e=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!
|
|
1223
|
-
`)}})}const tt=p(ee(import.meta.url)),Z=JSON.parse(v(m(tt,`..`,`package.json`),`utf-8`)),nt=`^${Z.version}`,rt=[`@forinda/kickjs`,`@forinda/kickjs-cli`,`@forinda/kickjs-schema`,`@forinda/kickjs-vite`,`@forinda/kickjs-swagger`,`@forinda/kickjs-ws`,`@forinda/kickjs-queue`,`@forinda/kickjs-devtools`,`@forinda/kickjs-testing`];async function it(){let e=await Promise.all(rt.map(async e=>{try{let t=w(`npm`,[`view`,e,`version`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();if(t&&/^\d+\.\d+\.\d+/.test(t))return[e,`^${t}`]}catch{}return[e,nt]}));return Object.fromEntries(e)}function at(e,t){try{let n=w(`npm`,[`view`,`${e}@${t}`,`version`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();return n&&/^\d+\.\d+\.\d+/.test(n)?n:null}catch{return null}}function Q(e){return(e??``).replace(/^[\^~>=<\s]+/,``)}function ot(e,t){let n=e=>Q(e).split(`-`)[0].split(`.`).map(e=>Number.parseInt(e,10)||0),[r=0,i=0,a=0]=n(e),[o=0,s=0,c=0]=n(t);return r===o?i===s?a>=c:i>s:r>o}function st(e,t,n){try{let r=w(`npm`,[`view`,`${e}@${t}`,`exports`,`--json`],{encoding:`utf-8`,timeout:5e3,stdio:[`ignore`,`pipe`,`ignore`]}).toString().trim();if(!r)return!1;let i=JSON.parse(r);return Object.prototype.hasOwnProperty.call(i,n)}catch{return!1}}async function ct(t){let{name:n,directory:r,packageManager:i=`pnpm`,template:o=`rest`,defaultRepo:s=`inmemory`,packages:c=[],schemaLib:l=`zod`,runtime:u=`express`}=t,d=r,f=e=>console.log(` ${e}`);console.log(`\n Creating KickJS project: ${n}\n`),f(`Resolving package versions...`);let p=await it();if(u!==`express`)if(st(`@forinda/kickjs`,`latest`,`./${u}`))f(`Using @forinda/kickjs@latest (stable ships the ${u} runtime)`);else{let e=[`@forinda/kickjs`,`@forinda/kickjs-cli`,`@forinda/kickjs-vite`],t=[],n=!1;for(let r of e){let e=at(r,`alpha`);e&&ot(e,Q(p[r]))&&(p[r]=`^${e}`,t.push(`${r}@^${e}`),r===`@forinda/kickjs`&&(n=!0))}f(n?`Using the alpha channel for the ${u} runtime: ${t.join(`, `)}`:`WARNING: could not resolve @forinda/kickjs@alpha — the ${u} runtime subpath may be missing. After install, run: ${i} add @forinda/kickjs@alpha`)}await e(m(d,`package.json`),Le(n,o,p,c,l,u)),await e(m(d,`vite.config.ts`),Re()),await e(m(d,`tsconfig.json`),ze()),await e(m(d,`.prettierrc`),Be()),await e(m(d,`.editorconfig`),Ve()),await e(m(d,`.gitignore`),He()),await e(m(d,`.gitattributes`),Ue()),await e(m(d,`.env`),We()),await e(m(d,`.env.example`),Ge()),await e(m(d,`src/config/index.ts`),_e(l)),await e(m(d,`src/index.ts`),he(n,o,Z.version,c,u)),await e(m(d,`src/modules/index.ts`),ge()),await e(m(d,`src/modules/hello/hello.service.ts`),ve()),await e(m(d,`src/modules/hello/hello.controller.ts`),ye()),await e(m(d,`src/modules/hello/hello.module.ts`),be()),await e(m(d,`kick.config.ts`),xe(o,s,i,u)),await e(m(d,`vitest.config.ts`),Ke()),await e(m(d,`README.md`),a(n,o,i));let{generateAgentDocs:h}=await import(`./agent-docs-lDM1Nx6J.mjs`).then(e=>e.t);if(await h({outDir:d,name:n,pm:i,template:o,only:`all`,force:!0}),t.installDeps){console.log(`\n Installing dependencies with ${i}...\n`);try{T(`${i} install`,{cwd:d,stdio:`inherit`}),console.log(`
|
|
1224
|
-
Dependencies installed successfully!`)}catch{console.log(`\n Warning: ${i} install failed. Run it manually.`)}}try{let{runTypegen:e}=await import(`./typegen-DJGvcWVf.mjs`).then(e=>e.n);await e({cwd:d,allowDuplicates:!0,silent:!0})}catch{}if(t.initGit)try{T(`git init`,{cwd:d,stdio:`pipe`}),T(`git branch -M main`,{cwd:d,stdio:`pipe`}),T(`git add -A`,{cwd:d,stdio:`pipe`}),T(`git commit -m "chore: initial commit from kick new"`,{cwd:d,stdio:`pipe`}),f(`Git repository initialized`)}catch{f(`Warning: git init failed (git may not be installed)`)}console.log(`
|
|
1225
|
-
Project scaffolded successfully!`),console.log();let g=d!==process.cwd();f(`Next steps:`),g&&f(` cd ${n}`),t.installDeps||f(` ${i} install`);let _={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`};f(` ${_[o]??_.rest}`),f(` kick dev`),f(``),f(`Commands:`),f(` kick dev Start dev server with Vite HMR`),f(` kick build Production build via Vite`),f(` kick start Run production build`),f(``),f(`Generators:`),f(` kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)`),f(` kick g scaffold <n> <f..> CRUD module from field definitions`),f(` kick g controller <name> Standalone controller`),f(` kick g service <name> @Service() class`),f(` kick g middleware <name> Express middleware`),f(` kick g guard <name> Route guard (auth, roles, etc.)`),f(` kick g adapter <name> AppAdapter with lifecycle hooks`),f(` kick g dto <name> Zod DTO schema`),f(` kick g config Generate kick.config.ts`),f(``),f(`Add packages:`),f(` kick add <pkg> Install a KickJS package + peers`),f(` kick add --list Show all available packages`),f(``),f(`Available: ${qe}`),f(``)}function lt(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 ut=`__kickjs_typegen_owner`;function dt(e){let{cwd:t,config:n}=e,r=e.debounceMs??100,i=e.pipeline??{runTypegen:async e=>(await import(`./typegen-DJGvcWVf.mjs`).then(e=>e.n)).runTypegen(e),runAllPluginTypegens:async e=>(await import(`./run-plugins-DaN0MHlX.mjs`)).runAllPluginTypegens(e),writeTypegenArtifacts:async(e,t,n)=>(await import(`./typegen-DJGvcWVf.mjs`).then(e=>e.n)).writeTypegenArtifacts(e,t,n),buildAssets:async(e,t)=>(await import(`./build-CBRWV98i.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=lt(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}).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 ft(e){return O(e).replace(/-/g,`_`)}function pt(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=ft(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 mt(e,t){return g(e.cwd,t)}async function ht(e){return import(te(e).href)}const gt=new Map;async function _t(e){let t=gt.get(e);if(t)return t;let n=vt(e);return gt.set(e,n),n}async function vt(e){let t=g(e,`package.json`);if(!_(t))return{generators:[],loaded:[],failed:[]};let n=yt(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 ht(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(!bt(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 yt(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 bt(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.name==`string`&&typeof t.files==`function`}async function xt(e,t=[]){let n=e.cwd??process.cwd(),r=t.find(t=>t.spec.name===e.generatorName);if(r)return wt(r.spec,r.source,e,n);let i=Ct(await _t(n),e.generatorName);return i?wt(i.spec,i.source,e,n):null}async function St(e,t=[]){let n=await _t(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 Ct(e,t){return e.generators.find(e=>e.spec.name===t)}async function wt(t,n,r,i){let a=pt({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=mt(a,t.path);await e(n,t.content),s.push(n)}return{files:s,source:n}}function Tt(e){return e}function Et(e){return e}function Dt(e){try{return JSON.parse(v(e,`utf-8`))}catch{return null}}function $(e){try{return v(e,`utf-8`)}catch{return null}}function Ot(e){let t=$(m(e,`tsconfig.json`));if(!t)return null;let n=t.replace(/\/\*[\s\S]*?\*\//g,``).replace(/\/\/.*$/gm,``),r;try{r=JSON.parse(n)}catch{return null}if(typeof r?.extends==`string`){let t=kt(e,r.extends);if(t){let e=Dt(t)??{};r.compilerOptions={...e.compilerOptions,...r.compilerOptions}}}return r}function kt(e,t){if(t.startsWith(`.`)){let n=g(e,t);return _(n)?n:null}let n=m(e,`node_modules`,t);return _(n)?n:null}function At(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function jt(){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.
|
|
1226
|
-
Install a supported version via nvm / fnm / volta.`}:{name:`Node version`,status:`pass`,message:e}}function Mt(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 Nt(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 Pt={express:[],fastify:[`fastify`,`@fastify/middie`],h3:[`h3`]};function Ft(e){if(!e.pkg||e.runtime===`express`)return null;let t={...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies},n=Pt[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 It(e){if(!e.pkg||!Rt(e.cwd))return null;let t=K[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 Lt=2e3;function Rt(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<Lt;){let e=r.pop(),t;try{t=y(e,{withFileTypes:!0})}catch{continue}for(let a of t){if(i>=Lt)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($(t)??``)))return!0}}return!1}function zt(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.
|
|
1227
|
-
Install it: pnpm add reflect-metadata
|
|
1228
|
-
Then import it at the top of src/index.ts:
|
|
1229
|
-
|
|
1230
|
-
import 'reflect-metadata'
|
|
1231
|
-
// ... rest of bootstrap`}}function Bt(e){if(!e.tsconfig)return[{name:`tsconfig.json present`,status:`fail`,fix:"Create a tsconfig.json with `experimentalDecorators: true` and `emitDecoratorMetadata: true`. `kick new` scaffolds one automatically."}];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 Vt(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($(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=$(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+)?['"]${At(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 Ht(e,t=Ut){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 Ut=2e3;function Wt(e){let t=m(e.cwd,`.kickjs`,`types`);if(!_(t))return null;let n=Ht(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 Gt=[()=>jt(),Mt,Nt,Ft,It,zt,Bt,Vt,Wt];async function Kt(e,t={}){let n={cwd:e,pkg:Dt(m(e,`package.json`)),tsconfig:Ot(e),runtime:t.runtime??`express`},r=[...Gt,...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 qt(e){switch(e){case`pass`:return r.green(`✔`);case`warn`:return r.yellow(`⚠`);case`fail`:return r.red(`✖`)}}function Jt(e){let t=qt(e.status),n=e.message?` ${r.dim(`(${e.message})`)}`:``;return`${t} ${e.name}${n}`}function Yt(e){return e.split(`
|
|
1232
|
-
`).map(e=>` ${r.dim(`→`)} ${e}`).join(`
|
|
1233
|
-
`)}function Xt(e){return e?.doctor?.checks??[]}function Zt(e){e.command(`doctor`).description(`Pre-flight checks for your KickJS project (dev environment health)`).action(async()=>{let e=process.cwd(),a=Xt(await c(e)),o=await q(e);t(`KickJS Doctor`);let s=await Kt(e,{extraChecks:a,runtime:o});for(let e of s)i.message(Jt(e)),e.fix&&e.status!==`pass`&&i.message(Yt(e.fix));let l=s.filter(e=>e.status===`pass`).length,u=s.filter(e=>e.status===`warn`).length,d=s.filter(e=>e.status===`fail`).length,f=[r.green(`${l} passed`),u>0?r.yellow(`${u} warning${u===1?``:`s`}`):`${u} warnings`,d>0?r.red(`${d} error${d===1?``:`s`}`):`${d} errors`].join(`, `);d>0?(n(`${f} — fix the errors above before running the app`),process.exit(1)):n(u>0?`${f} — review the warnings`:r.green(`${f} — your environment looks good`))})}export{de as A,B as C,I as D,L as E,A as F,D as I,O as L,N as M,j as N,F as O,k as P,E as R,R as S,we as T,Me as _,xt as a,U as b,ut as c,G as d,et as f,Ne as g,Pe as h,St as i,se as j,P as k,dt as l,Ze as m,Tt as n,pt as o,$e as p,Zt as r,ne as s,Et as t,ct as u,je as v,Te as w,Oe as x,Ae as y};
|
|
1234
|
-
//# sourceMappingURL=doctor-DlWMC_2O.mjs.map
|