@forinda/kickjs-cli 1.2.11 → 1.2.12
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/cli.js +695 -2912
- package/dist/index.js +470 -1410
- package/package.json +1 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,116 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// src/cli.ts
|
|
6
|
-
import { Command } from "commander";
|
|
7
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
8
|
-
import { dirname as dirname3, join as join18 } from "path";
|
|
9
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10
|
-
|
|
11
|
-
// src/commands/init.ts
|
|
12
|
-
import { resolve, basename } from "path";
|
|
13
|
-
import { createInterface } from "readline";
|
|
14
|
-
import { existsSync, readdirSync, rmSync } from "fs";
|
|
15
|
-
|
|
16
|
-
// src/generators/project.ts
|
|
17
|
-
import { join, dirname as dirname2 } from "path";
|
|
18
|
-
import { execSync } from "child_process";
|
|
19
|
-
import { readFileSync } from "fs";
|
|
20
|
-
import { fileURLToPath } from "url";
|
|
21
|
-
|
|
22
|
-
// src/utils/fs.ts
|
|
23
|
-
import { writeFile, mkdir, access, readFile } from "fs/promises";
|
|
24
|
-
import { dirname } from "path";
|
|
25
|
-
var _dryRun = false;
|
|
26
|
-
function setDryRun(enabled) {
|
|
27
|
-
_dryRun = enabled;
|
|
28
|
-
}
|
|
29
|
-
__name(setDryRun, "setDryRun");
|
|
30
|
-
async function writeFileSafe(filePath, content) {
|
|
31
|
-
if (_dryRun) return;
|
|
32
|
-
await mkdir(dirname(filePath), {
|
|
33
|
-
recursive: true
|
|
34
|
-
});
|
|
35
|
-
await writeFile(filePath, content, "utf-8");
|
|
36
|
-
}
|
|
37
|
-
__name(writeFileSafe, "writeFileSafe");
|
|
38
|
-
async function fileExists(filePath) {
|
|
39
|
-
try {
|
|
40
|
-
await access(filePath);
|
|
41
|
-
return true;
|
|
42
|
-
} catch {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
__name(fileExists, "fileExists");
|
|
47
|
-
|
|
48
|
-
// src/generators/project.ts
|
|
49
|
-
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
50
|
-
var cliPkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
51
|
-
var KICKJS_VERSION = `^${cliPkg.version}`;
|
|
52
|
-
async function initProject(options) {
|
|
53
|
-
const { name, directory, packageManager = "pnpm", template = "rest" } = options;
|
|
54
|
-
const dir = directory;
|
|
55
|
-
console.log(`
|
|
56
|
-
Creating KickJS project: ${name}
|
|
57
|
-
`);
|
|
58
|
-
const baseDeps = {
|
|
59
|
-
"@forinda/kickjs-core": KICKJS_VERSION,
|
|
60
|
-
"@forinda/kickjs-http": KICKJS_VERSION,
|
|
61
|
-
"@forinda/kickjs-config": KICKJS_VERSION,
|
|
62
|
-
express: "^5.1.0",
|
|
63
|
-
"reflect-metadata": "^0.2.2",
|
|
64
|
-
zod: "^4.3.6",
|
|
65
|
-
pino: "^10.3.1",
|
|
66
|
-
"pino-pretty": "^13.1.3"
|
|
67
|
-
};
|
|
68
|
-
if (template !== "minimal") {
|
|
69
|
-
baseDeps["@forinda/kickjs-swagger"] = KICKJS_VERSION;
|
|
70
|
-
baseDeps["@forinda/kickjs-devtools"] = KICKJS_VERSION;
|
|
71
|
-
}
|
|
72
|
-
if (template === "graphql") {
|
|
73
|
-
baseDeps["@forinda/kickjs-graphql"] = KICKJS_VERSION;
|
|
74
|
-
baseDeps["graphql"] = "^16.11.0";
|
|
75
|
-
}
|
|
76
|
-
if (template === "cqrs") {
|
|
77
|
-
baseDeps["@forinda/kickjs-queue"] = KICKJS_VERSION;
|
|
78
|
-
baseDeps["@forinda/kickjs-ws"] = KICKJS_VERSION;
|
|
79
|
-
baseDeps["@forinda/kickjs-otel"] = KICKJS_VERSION;
|
|
80
|
-
}
|
|
81
|
-
if (template === "ddd") {
|
|
82
|
-
baseDeps["@forinda/kickjs-swagger"] = KICKJS_VERSION;
|
|
83
|
-
}
|
|
84
|
-
await writeFileSafe(join(dir, "package.json"), JSON.stringify({
|
|
85
|
-
name,
|
|
86
|
-
version: cliPkg.version,
|
|
87
|
-
type: "module",
|
|
88
|
-
scripts: {
|
|
89
|
-
dev: "kick dev",
|
|
90
|
-
"dev:debug": "kick dev:debug",
|
|
91
|
-
build: "kick build",
|
|
92
|
-
start: "kick start",
|
|
93
|
-
test: "vitest run",
|
|
94
|
-
"test:watch": "vitest",
|
|
95
|
-
typecheck: "tsc --noEmit",
|
|
96
|
-
lint: "eslint src/",
|
|
97
|
-
format: "prettier --write src/"
|
|
98
|
-
},
|
|
99
|
-
dependencies: baseDeps,
|
|
100
|
-
devDependencies: {
|
|
101
|
-
"@forinda/kickjs-cli": KICKJS_VERSION,
|
|
102
|
-
"@swc/core": "^1.7.28",
|
|
103
|
-
"@types/express": "^5.0.6",
|
|
104
|
-
"@types/node": "^24.5.2",
|
|
105
|
-
"unplugin-swc": "^1.5.9",
|
|
106
|
-
vite: "^7.3.1",
|
|
107
|
-
"vite-node": "^5.3.0",
|
|
108
|
-
vitest: "^3.2.4",
|
|
109
|
-
typescript: "^5.9.2",
|
|
110
|
-
prettier: "^3.8.1"
|
|
111
|
-
}
|
|
112
|
-
}, null, 2));
|
|
113
|
-
await writeFileSafe(join(dir, "vite.config.ts"), `import { defineConfig } from 'vite'
|
|
2
|
+
var $t=Object.defineProperty;var s=(e,t)=>$t(e,"name",{value:t,configurable:!0});import{Command as No}from"commander";import{readFileSync as Yo}from"fs";import{dirname as Bo,join as Ho}from"path";import{fileURLToPath as Ko}from"url";import{resolve as te,basename as Ot}from"path";import{createInterface as It}from"readline";import{existsSync as St,readdirSync as Tt,rmSync as jt}from"fs";import{join as h,dirname as vt}from"path";import{execSync as V}from"child_process";import{readFileSync as Ct}from"fs";import{fileURLToPath as xt}from"url";import{writeFile as gt,mkdir as yt,access as ht,readFile as er}from"fs/promises";import{dirname as wt}from"path";var Se=!1;function v(e){Se=e}s(v,"setDryRun");async function l(e,t){Se||(await yt(wt(e),{recursive:!0}),await gt(e,t,"utf-8"))}s(l,"writeFileSafe");async function _(e){try{return await ht(e),!0}catch{return!1}}s(_,"fileExists");var kt=vt(xt(import.meta.url)),Z=JSON.parse(Ct(h(kt,"..","package.json"),"utf-8")),D=`^${Z.version}`;async function Te(e){let{name:t,directory:o,packageManager:r="pnpm",template:n="rest"}=e,i=o;console.log(`
|
|
3
|
+
Creating KickJS project: ${t}
|
|
4
|
+
`);let d={"@forinda/kickjs-core":D,"@forinda/kickjs-http":D,"@forinda/kickjs-config":D,express:"^5.1.0","reflect-metadata":"^0.2.2",zod:"^4.3.6",pino:"^10.3.1","pino-pretty":"^13.1.3"};if(n!=="minimal"&&(d["@forinda/kickjs-swagger"]=D,d["@forinda/kickjs-devtools"]=D),n==="graphql"&&(d["@forinda/kickjs-graphql"]=D,d.graphql="^16.11.0"),n==="cqrs"&&(d["@forinda/kickjs-queue"]=D,d["@forinda/kickjs-ws"]=D,d["@forinda/kickjs-otel"]=D),n==="ddd"&&(d["@forinda/kickjs-swagger"]=D),await l(h(i,"package.json"),JSON.stringify({name:t,version:Z.version,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",lint:"eslint src/",format:"prettier --write src/"},dependencies:d,devDependencies:{"@forinda/kickjs-cli":D,"@swc/core":"^1.7.28","@types/express":"^5.0.6","@types/node":"^24.5.2","unplugin-swc":"^1.5.9",vite:"^7.3.1","vite-node":"^5.3.0",vitest:"^3.2.4",typescript:"^5.9.2",prettier:"^3.8.1"}},null,2)),await l(h(i,"vite.config.ts"),`import { defineConfig } from 'vite'
|
|
114
5
|
import { resolve } from 'path'
|
|
115
6
|
import swc from 'unplugin-swc'
|
|
116
7
|
|
|
@@ -136,46 +27,7 @@ export default defineConfig({
|
|
|
136
27
|
},
|
|
137
28
|
},
|
|
138
29
|
})
|
|
139
|
-
`)
|
|
140
|
-
await writeFileSafe(join(dir, "tsconfig.json"), JSON.stringify({
|
|
141
|
-
compilerOptions: {
|
|
142
|
-
target: "ES2022",
|
|
143
|
-
module: "ESNext",
|
|
144
|
-
moduleResolution: "bundler",
|
|
145
|
-
lib: [
|
|
146
|
-
"ES2022"
|
|
147
|
-
],
|
|
148
|
-
types: [
|
|
149
|
-
"node",
|
|
150
|
-
"vite/client"
|
|
151
|
-
],
|
|
152
|
-
strict: true,
|
|
153
|
-
esModuleInterop: true,
|
|
154
|
-
skipLibCheck: true,
|
|
155
|
-
sourceMap: true,
|
|
156
|
-
declaration: true,
|
|
157
|
-
experimentalDecorators: true,
|
|
158
|
-
emitDecoratorMetadata: true,
|
|
159
|
-
outDir: "dist",
|
|
160
|
-
rootDir: "src",
|
|
161
|
-
paths: {
|
|
162
|
-
"@/*": [
|
|
163
|
-
"./src/*"
|
|
164
|
-
]
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
include: [
|
|
168
|
-
"src"
|
|
169
|
-
]
|
|
170
|
-
}, null, 2));
|
|
171
|
-
await writeFileSafe(join(dir, ".prettierrc"), JSON.stringify({
|
|
172
|
-
semi: false,
|
|
173
|
-
singleQuote: true,
|
|
174
|
-
trailingComma: "all",
|
|
175
|
-
printWidth: 100,
|
|
176
|
-
tabWidth: 2
|
|
177
|
-
}, null, 2));
|
|
178
|
-
await writeFileSafe(join(dir, ".editorconfig"), `# https://editorconfig.org
|
|
30
|
+
`),await l(h(i,"tsconfig.json"),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",rootDir:"src",paths:{"@/*":["./src/*"]}},include:["src"]},null,2)),await l(h(i,".prettierrc"),JSON.stringify({semi:!1,singleQuote:!0,trailingComma:"all",printWidth:100,tabWidth:2},null,2)),await l(h(i,".editorconfig"),`# https://editorconfig.org
|
|
179
31
|
root = true
|
|
180
32
|
|
|
181
33
|
[*]
|
|
@@ -188,15 +40,13 @@ insert_final_newline = true
|
|
|
188
40
|
|
|
189
41
|
[*.md]
|
|
190
42
|
trim_trailing_whitespace = false
|
|
191
|
-
`)
|
|
192
|
-
await writeFileSafe(join(dir, ".gitignore"), `node_modules/
|
|
43
|
+
`),await l(h(i,".gitignore"),`node_modules/
|
|
193
44
|
dist/
|
|
194
45
|
.env
|
|
195
46
|
coverage/
|
|
196
47
|
.DS_Store
|
|
197
48
|
*.tsbuildinfo
|
|
198
|
-
`)
|
|
199
|
-
await writeFileSafe(join(dir, ".gitattributes"), `# Auto-detect text files and normalise line endings to LF
|
|
49
|
+
`),await l(h(i,".gitattributes"),`# Auto-detect text files and normalise line endings to LF
|
|
200
50
|
* text=auto eol=lf
|
|
201
51
|
|
|
202
52
|
# Explicitly mark generated / binary files
|
|
@@ -214,25 +64,17 @@ coverage/
|
|
|
214
64
|
pnpm-lock.yaml -diff linguist-generated
|
|
215
65
|
yarn.lock -diff linguist-generated
|
|
216
66
|
package-lock.json -diff linguist-generated
|
|
217
|
-
`)
|
|
218
|
-
await writeFileSafe(join(dir, ".env"), `PORT=3000
|
|
67
|
+
`),await l(h(i,".env"),`PORT=3000
|
|
219
68
|
NODE_ENV=development
|
|
220
|
-
`)
|
|
221
|
-
await writeFileSafe(join(dir, ".env.example"), `PORT=3000
|
|
69
|
+
`),await l(h(i,".env.example"),`PORT=3000
|
|
222
70
|
NODE_ENV=development
|
|
223
|
-
`)
|
|
224
|
-
await writeFileSafe(join(dir, "src/index.ts"), getEntryFile(name, template));
|
|
225
|
-
await writeFileSafe(join(dir, "src/modules/index.ts"), `import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
71
|
+
`),await l(h(i,"src/index.ts"),Rt(t,n)),await l(h(i,"src/modules/index.ts"),`import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
226
72
|
|
|
227
73
|
export const modules: AppModuleClass[] = []
|
|
228
|
-
`)
|
|
229
|
-
if (template === "graphql") {
|
|
230
|
-
await writeFileSafe(join(dir, "src/resolvers/.gitkeep"), "");
|
|
231
|
-
}
|
|
232
|
-
await writeFileSafe(join(dir, "kick.config.ts"), `import { defineConfig } from '@forinda/kickjs-cli'
|
|
74
|
+
`),n==="graphql"&&await l(h(i,"src/resolvers/.gitkeep"),""),await l(h(i,"kick.config.ts"),`import { defineConfig } from '@forinda/kickjs-cli'
|
|
233
75
|
|
|
234
76
|
export default defineConfig({
|
|
235
|
-
pattern: '${
|
|
77
|
+
pattern: '${n}',
|
|
236
78
|
modulesDir: 'src/modules',
|
|
237
79
|
defaultRepo: 'inmemory',
|
|
238
80
|
|
|
@@ -260,8 +102,7 @@ export default defineConfig({
|
|
|
260
102
|
},
|
|
261
103
|
],
|
|
262
104
|
})
|
|
263
|
-
`)
|
|
264
|
-
await writeFileSafe(join(dir, "vitest.config.ts"), `import { defineConfig } from 'vitest/config'
|
|
105
|
+
`),await l(h(i,"vitest.config.ts"),`import { defineConfig } from 'vitest/config'
|
|
265
106
|
import swc from 'unplugin-swc'
|
|
266
107
|
|
|
267
108
|
export default defineConfig({
|
|
@@ -272,89 +113,12 @@ export default defineConfig({
|
|
|
272
113
|
include: ['src/**/*.test.ts'],
|
|
273
114
|
},
|
|
274
115
|
})
|
|
275
|
-
`)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
stdio: "pipe"
|
|
282
|
-
});
|
|
283
|
-
execSync("git add -A", {
|
|
284
|
-
cwd: dir,
|
|
285
|
-
stdio: "pipe"
|
|
286
|
-
});
|
|
287
|
-
execSync('git commit -m "chore: initial commit from kick new"', {
|
|
288
|
-
cwd: dir,
|
|
289
|
-
stdio: "pipe"
|
|
290
|
-
});
|
|
291
|
-
console.log(" Git repository initialized");
|
|
292
|
-
} catch {
|
|
293
|
-
console.log(" Warning: git init failed (git may not be installed)");
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
if (options.installDeps) {
|
|
297
|
-
console.log(`
|
|
298
|
-
Installing dependencies with ${packageManager}...
|
|
299
|
-
`);
|
|
300
|
-
try {
|
|
301
|
-
execSync(`${packageManager} install`, {
|
|
302
|
-
cwd: dir,
|
|
303
|
-
stdio: "inherit"
|
|
304
|
-
});
|
|
305
|
-
console.log("\n Dependencies installed successfully!");
|
|
306
|
-
} catch {
|
|
307
|
-
console.log(`
|
|
308
|
-
Warning: ${packageManager} install failed. Run it manually.`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
console.log("\n Project scaffolded successfully!");
|
|
312
|
-
console.log();
|
|
313
|
-
const needsCd = dir !== process.cwd();
|
|
314
|
-
console.log(" Next steps:");
|
|
315
|
-
if (needsCd) console.log(` cd ${name}`);
|
|
316
|
-
if (!options.installDeps) console.log(` ${packageManager} install`);
|
|
317
|
-
const genHint = {
|
|
318
|
-
rest: "kick g module user",
|
|
319
|
-
graphql: "kick g resolver user",
|
|
320
|
-
ddd: "kick g module user --repo drizzle",
|
|
321
|
-
cqrs: "kick g module user --pattern cqrs",
|
|
322
|
-
minimal: "# add your routes to src/index.ts"
|
|
323
|
-
};
|
|
324
|
-
console.log(` ${genHint[template] ?? genHint.rest}`);
|
|
325
|
-
console.log(" kick dev");
|
|
326
|
-
console.log();
|
|
327
|
-
console.log(" Commands:");
|
|
328
|
-
console.log(" kick dev Start dev server with Vite HMR");
|
|
329
|
-
console.log(" kick build Production build via Vite");
|
|
330
|
-
console.log(" kick start Run production build");
|
|
331
|
-
console.log();
|
|
332
|
-
console.log(" Generators:");
|
|
333
|
-
console.log(" kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)");
|
|
334
|
-
console.log(" kick g scaffold <n> <f..> CRUD module from field definitions");
|
|
335
|
-
console.log(" kick g controller <name> Standalone controller");
|
|
336
|
-
console.log(" kick g service <name> @Service() class");
|
|
337
|
-
console.log(" kick g middleware <name> Express middleware");
|
|
338
|
-
console.log(" kick g guard <name> Route guard (auth, roles, etc.)");
|
|
339
|
-
console.log(" kick g adapter <name> AppAdapter with lifecycle hooks");
|
|
340
|
-
console.log(" kick g dto <name> Zod DTO schema");
|
|
341
|
-
if (template === "graphql") console.log(" kick g resolver <name> GraphQL resolver");
|
|
342
|
-
if (template === "cqrs") console.log(" kick g job <name> Queue job processor");
|
|
343
|
-
console.log(" kick g config Generate kick.config.ts");
|
|
344
|
-
console.log();
|
|
345
|
-
console.log(" Add packages:");
|
|
346
|
-
console.log(" kick add <pkg> Install a KickJS package + peers");
|
|
347
|
-
console.log(" kick add --list Show all available packages");
|
|
348
|
-
console.log();
|
|
349
|
-
console.log(" Available: auth, swagger, graphql, drizzle, prisma, ws,");
|
|
350
|
-
console.log(" cron, queue, mailer, otel, multi-tenant, notifications, testing");
|
|
351
|
-
console.log();
|
|
352
|
-
}
|
|
353
|
-
__name(initProject, "initProject");
|
|
354
|
-
function getEntryFile(name, template) {
|
|
355
|
-
switch (template) {
|
|
356
|
-
case "graphql":
|
|
357
|
-
return `import 'reflect-metadata'
|
|
116
|
+
`),await l(h(i,"README.md"),Dt(t,n,r)),e.initGit)try{V("git init",{cwd:i,stdio:"pipe"}),V("git add -A",{cwd:i,stdio:"pipe"}),V('git commit -m "chore: initial commit from kick new"',{cwd:i,stdio:"pipe"}),console.log(" Git repository initialized")}catch{console.log(" Warning: git init failed (git may not be installed)")}if(e.installDeps){console.log(`
|
|
117
|
+
Installing dependencies with ${r}...
|
|
118
|
+
`);try{V(`${r} install`,{cwd:i,stdio:"inherit"}),console.log(`
|
|
119
|
+
Dependencies installed successfully!`)}catch{console.log(`
|
|
120
|
+
Warning: ${r} install failed. Run it manually.`)}}console.log(`
|
|
121
|
+
Project scaffolded successfully!`),console.log();let c=i!==process.cwd();console.log(" Next steps:"),c&&console.log(` cd ${t}`),e.installDeps||console.log(` ${r} install`);let a={rest:"kick g module user",graphql:"kick g resolver user",ddd:"kick g module user --repo drizzle",cqrs:"kick g module user --pattern cqrs",minimal:"# add your routes to src/index.ts"};console.log(` ${a[n]??a.rest}`),console.log(" kick dev"),console.log(),console.log(" Commands:"),console.log(" kick dev Start dev server with Vite HMR"),console.log(" kick build Production build via Vite"),console.log(" kick start Run production build"),console.log(),console.log(" Generators:"),console.log(" kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)"),console.log(" kick g scaffold <n> <f..> CRUD module from field definitions"),console.log(" kick g controller <name> Standalone controller"),console.log(" kick g service <name> @Service() class"),console.log(" kick g middleware <name> Express middleware"),console.log(" kick g guard <name> Route guard (auth, roles, etc.)"),console.log(" kick g adapter <name> AppAdapter with lifecycle hooks"),console.log(" kick g dto <name> Zod DTO schema"),n==="graphql"&&console.log(" kick g resolver <name> GraphQL resolver"),n==="cqrs"&&console.log(" kick g job <name> Queue job processor"),console.log(" kick g config Generate kick.config.ts"),console.log(),console.log(" Add packages:"),console.log(" kick add <pkg> Install a KickJS package + peers"),console.log(" kick add --list Show all available packages"),console.log(),console.log(" Available: auth, swagger, graphql, drizzle, prisma, ws,"),console.log(" cron, queue, mailer, otel, multi-tenant, notifications, testing"),console.log()}s(Te,"initProject");function Rt(e,t){switch(t){case"graphql":return`import 'reflect-metadata'
|
|
358
122
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
359
123
|
import { DevToolsAdapter } from '@forinda/kickjs-devtools'
|
|
360
124
|
import { GraphQLAdapter } from '@forinda/kickjs-graphql'
|
|
@@ -374,9 +138,7 @@ bootstrap({
|
|
|
374
138
|
}),
|
|
375
139
|
],
|
|
376
140
|
})
|
|
377
|
-
`;
|
|
378
|
-
case "cqrs":
|
|
379
|
-
return `import 'reflect-metadata'
|
|
141
|
+
`;case"cqrs":return`import 'reflect-metadata'
|
|
380
142
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
381
143
|
import { DevToolsAdapter } from '@forinda/kickjs-devtools'
|
|
382
144
|
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
@@ -388,10 +150,10 @@ import { modules } from './modules'
|
|
|
388
150
|
bootstrap({
|
|
389
151
|
modules,
|
|
390
152
|
adapters: [
|
|
391
|
-
new OtelAdapter({ serviceName: '${
|
|
153
|
+
new OtelAdapter({ serviceName: '${e}' }),
|
|
392
154
|
new DevToolsAdapter(),
|
|
393
155
|
new SwaggerAdapter({
|
|
394
|
-
info: { title: '${
|
|
156
|
+
info: { title: '${e}', version: '${Z.version}' },
|
|
395
157
|
}),
|
|
396
158
|
// Uncomment for WebSocket support:
|
|
397
159
|
// new WsAdapter(),
|
|
@@ -401,18 +163,12 @@ bootstrap({
|
|
|
401
163
|
// }),
|
|
402
164
|
],
|
|
403
165
|
})
|
|
404
|
-
`;
|
|
405
|
-
case "minimal":
|
|
406
|
-
return `import 'reflect-metadata'
|
|
166
|
+
`;case"minimal":return`import 'reflect-metadata'
|
|
407
167
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
408
168
|
import { modules } from './modules'
|
|
409
169
|
|
|
410
170
|
bootstrap({ modules })
|
|
411
|
-
`;
|
|
412
|
-
case "ddd":
|
|
413
|
-
case "rest":
|
|
414
|
-
default:
|
|
415
|
-
return `import 'reflect-metadata'
|
|
171
|
+
`;default:return`import 'reflect-metadata'
|
|
416
172
|
import { bootstrap } from '@forinda/kickjs-http'
|
|
417
173
|
import { DevToolsAdapter } from '@forinda/kickjs-devtools'
|
|
418
174
|
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
@@ -423,42 +179,18 @@ bootstrap({
|
|
|
423
179
|
adapters: [
|
|
424
180
|
new DevToolsAdapter(),
|
|
425
181
|
new SwaggerAdapter({
|
|
426
|
-
info: { title: '${
|
|
182
|
+
info: { title: '${e}', version: '${Z.version}' },
|
|
427
183
|
}),
|
|
428
184
|
],
|
|
429
185
|
})
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
__name(getEntryFile, "getEntryFile");
|
|
434
|
-
function generateReadme(name, template, pm) {
|
|
435
|
-
const templateLabels = {
|
|
436
|
-
rest: "REST API",
|
|
437
|
-
graphql: "GraphQL API",
|
|
438
|
-
ddd: "Domain-Driven Design",
|
|
439
|
-
cqrs: "CQRS + Event-Driven",
|
|
440
|
-
minimal: "Minimal"
|
|
441
|
-
};
|
|
442
|
-
const packages = [
|
|
443
|
-
"@forinda/kickjs-core",
|
|
444
|
-
"@forinda/kickjs-http",
|
|
445
|
-
"@forinda/kickjs-config"
|
|
446
|
-
];
|
|
447
|
-
if (template !== "minimal") {
|
|
448
|
-
packages.push("@forinda/kickjs-swagger", "@forinda/kickjs-devtools");
|
|
449
|
-
}
|
|
450
|
-
if (template === "graphql") packages.push("@forinda/kickjs-graphql");
|
|
451
|
-
if (template === "cqrs") {
|
|
452
|
-
packages.push("@forinda/kickjs-queue", "@forinda/kickjs-ws", "@forinda/kickjs-otel");
|
|
453
|
-
}
|
|
454
|
-
return `# ${name}
|
|
186
|
+
`}}s(Rt,"getEntryFile");function Dt(e,t,o){let r={rest:"REST API",graphql:"GraphQL API",ddd:"Domain-Driven Design",cqrs:"CQRS + Event-Driven",minimal:"Minimal"},n=["@forinda/kickjs-core","@forinda/kickjs-http","@forinda/kickjs-config"];return t!=="minimal"&&n.push("@forinda/kickjs-swagger","@forinda/kickjs-devtools"),t==="graphql"&&n.push("@forinda/kickjs-graphql"),t==="cqrs"&&n.push("@forinda/kickjs-queue","@forinda/kickjs-ws","@forinda/kickjs-otel"),`# ${e}
|
|
455
187
|
|
|
456
|
-
A **${
|
|
188
|
+
A **${r[t]??"REST API"}** built with [KickJS](https://forinda.github.io/kick-js/) \u2014 a decorator-driven Node.js framework on Express 5 and TypeScript.
|
|
457
189
|
|
|
458
190
|
## Getting Started
|
|
459
191
|
|
|
460
192
|
\`\`\`bash
|
|
461
|
-
${
|
|
193
|
+
${o} install
|
|
462
194
|
kick dev
|
|
463
195
|
\`\`\`
|
|
464
196
|
|
|
@@ -469,7 +201,7 @@ kick dev
|
|
|
469
201
|
| \`kick dev\` | Start dev server with Vite HMR |
|
|
470
202
|
| \`kick build\` | Production build |
|
|
471
203
|
| \`kick start\` | Run production build |
|
|
472
|
-
| \`${
|
|
204
|
+
| \`${o} run test\` | Run tests with Vitest |
|
|
473
205
|
| \`kick g module <name>\` | Generate a DDD module |
|
|
474
206
|
| \`kick g scaffold <name> <fields...>\` | Generate CRUD from field definitions |
|
|
475
207
|
| \`kick add <package>\` | Add a KickJS package |
|
|
@@ -486,7 +218,8 @@ src/
|
|
|
486
218
|
|
|
487
219
|
## Packages
|
|
488
220
|
|
|
489
|
-
${
|
|
221
|
+
${n.map(i=>`- \`${i}\``).join(`
|
|
222
|
+
`)}
|
|
490
223
|
|
|
491
224
|
## Adding Features
|
|
492
225
|
|
|
@@ -513,199 +246,10 @@ Copy \`.env.example\` to \`.env\` and configure:
|
|
|
513
246
|
|
|
514
247
|
- [KickJS Documentation](https://forinda.github.io/kick-js/)
|
|
515
248
|
- [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
// src/commands/init.ts
|
|
521
|
-
function ask(question, defaultValue) {
|
|
522
|
-
const rl = createInterface({
|
|
523
|
-
input: process.stdin,
|
|
524
|
-
output: process.stdout
|
|
525
|
-
});
|
|
526
|
-
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
527
|
-
return new Promise((res) => {
|
|
528
|
-
rl.question(` ${question}${suffix}: `, (answer) => {
|
|
529
|
-
rl.close();
|
|
530
|
-
res(answer.trim() || defaultValue || "");
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
__name(ask, "ask");
|
|
535
|
-
async function choose(question, options, defaultIdx = 0) {
|
|
536
|
-
console.log(` ${question}`);
|
|
537
|
-
for (let i = 0; i < options.length; i++) {
|
|
538
|
-
const marker = i === defaultIdx ? ">" : " ";
|
|
539
|
-
console.log(` ${marker} ${i + 1}. ${options[i]}`);
|
|
540
|
-
}
|
|
541
|
-
const answer = await ask("Choose", String(defaultIdx + 1));
|
|
542
|
-
const idx = parseInt(answer, 10) - 1;
|
|
543
|
-
return options[idx] ?? options[defaultIdx];
|
|
544
|
-
}
|
|
545
|
-
__name(choose, "choose");
|
|
546
|
-
async function confirm(question, defaultYes = true) {
|
|
547
|
-
const hint = defaultYes ? "Y/n" : "y/N";
|
|
548
|
-
const answer = await ask(`${question} (${hint})`);
|
|
549
|
-
if (!answer) return defaultYes;
|
|
550
|
-
return answer.toLowerCase().startsWith("y");
|
|
551
|
-
}
|
|
552
|
-
__name(confirm, "confirm");
|
|
553
|
-
function registerInitCommand(program) {
|
|
554
|
-
program.command("new [name]").alias("init").description('Create a new KickJS project (use "." for current directory)').option("-d, --directory <dir>", "Target directory (defaults to project name)").option("--pm <manager>", "Package manager: pnpm | npm | yarn").option("--git", "Initialize git repository").option("--no-git", "Skip git initialization").option("--install", "Install dependencies after scaffolding").option("--no-install", "Skip dependency installation").option("-f, --force", "Remove existing files without prompting").option("-t, --template <type>", "Project template: rest | graphql | ddd | cqrs | minimal").action(async (name, opts) => {
|
|
555
|
-
console.log();
|
|
556
|
-
if (!name) {
|
|
557
|
-
name = await ask("Project name", "my-api");
|
|
558
|
-
}
|
|
559
|
-
let directory;
|
|
560
|
-
if (name === ".") {
|
|
561
|
-
directory = resolve(".");
|
|
562
|
-
name = basename(directory);
|
|
563
|
-
} else {
|
|
564
|
-
directory = resolve(opts.directory || name);
|
|
565
|
-
}
|
|
566
|
-
if (existsSync(directory)) {
|
|
567
|
-
const entries = readdirSync(directory);
|
|
568
|
-
if (entries.length > 0) {
|
|
569
|
-
if (opts.force) {
|
|
570
|
-
console.log(` Clearing existing files in ${directory}...
|
|
571
|
-
`);
|
|
572
|
-
} else {
|
|
573
|
-
console.log(` Directory "${name}" is not empty:`);
|
|
574
|
-
const shown = entries.slice(0, 5);
|
|
575
|
-
for (const entry of shown) {
|
|
576
|
-
console.log(` - ${entry}`);
|
|
577
|
-
}
|
|
578
|
-
if (entries.length > 5) {
|
|
579
|
-
console.log(` ... and ${entries.length - 5} more`);
|
|
580
|
-
}
|
|
581
|
-
console.log();
|
|
582
|
-
const shouldClear = await confirm("Remove all existing files and proceed?", false);
|
|
583
|
-
if (!shouldClear) {
|
|
584
|
-
console.log(" Aborted.\n");
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
for (const entry of entries) {
|
|
589
|
-
rmSync(resolve(directory, entry), {
|
|
590
|
-
recursive: true,
|
|
591
|
-
force: true
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
let template = opts.template;
|
|
597
|
-
if (!template) {
|
|
598
|
-
template = await choose("Project template:", [
|
|
599
|
-
"REST API (Express + Swagger)",
|
|
600
|
-
"GraphQL API (GraphQL + GraphiQL)",
|
|
601
|
-
"DDD (Domain-Driven Design modules)",
|
|
602
|
-
"CQRS (Commands, Queries, Events + WS/Queue)",
|
|
603
|
-
"Minimal (bare Express)"
|
|
604
|
-
], 0);
|
|
605
|
-
const templateMap = {
|
|
606
|
-
"REST API (Express + Swagger)": "rest",
|
|
607
|
-
"GraphQL API (GraphQL + GraphiQL)": "graphql",
|
|
608
|
-
"DDD (Domain-Driven Design modules)": "ddd",
|
|
609
|
-
"CQRS (Commands, Queries, Events + WS/Queue)": "cqrs",
|
|
610
|
-
"Minimal (bare Express)": "minimal"
|
|
611
|
-
};
|
|
612
|
-
template = templateMap[template] ?? "rest";
|
|
613
|
-
}
|
|
614
|
-
let packageManager = opts.pm;
|
|
615
|
-
if (!packageManager) {
|
|
616
|
-
packageManager = await choose("Package manager:", [
|
|
617
|
-
"pnpm",
|
|
618
|
-
"npm",
|
|
619
|
-
"yarn"
|
|
620
|
-
], 0);
|
|
621
|
-
}
|
|
622
|
-
let initGit;
|
|
623
|
-
if (opts.git === void 0) {
|
|
624
|
-
initGit = await confirm("Initialize git repository?", true);
|
|
625
|
-
} else {
|
|
626
|
-
initGit = opts.git;
|
|
627
|
-
}
|
|
628
|
-
let installDeps;
|
|
629
|
-
if (opts.install === void 0) {
|
|
630
|
-
installDeps = await confirm("Install dependencies?", true);
|
|
631
|
-
} else {
|
|
632
|
-
installDeps = opts.install;
|
|
633
|
-
}
|
|
634
|
-
await initProject({
|
|
635
|
-
name,
|
|
636
|
-
directory,
|
|
637
|
-
packageManager,
|
|
638
|
-
initGit,
|
|
639
|
-
installDeps,
|
|
640
|
-
template
|
|
641
|
-
});
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
__name(registerInitCommand, "registerInitCommand");
|
|
645
|
-
|
|
646
|
-
// src/commands/generate.ts
|
|
647
|
-
import { resolve as resolve4 } from "path";
|
|
648
|
-
|
|
649
|
-
// src/generators/module.ts
|
|
650
|
-
import { join as join2 } from "path";
|
|
651
|
-
import { createInterface as createInterface2 } from "readline";
|
|
652
|
-
|
|
653
|
-
// src/utils/naming.ts
|
|
654
|
-
function toPascalCase(name) {
|
|
655
|
-
return name.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
|
|
656
|
-
}
|
|
657
|
-
__name(toPascalCase, "toPascalCase");
|
|
658
|
-
function toCamelCase(name) {
|
|
659
|
-
const pascal = toPascalCase(name);
|
|
660
|
-
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
661
|
-
}
|
|
662
|
-
__name(toCamelCase, "toCamelCase");
|
|
663
|
-
function toKebabCase(name) {
|
|
664
|
-
return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
665
|
-
}
|
|
666
|
-
__name(toKebabCase, "toKebabCase");
|
|
667
|
-
function pluralize(name) {
|
|
668
|
-
if (name.endsWith("s")) return name;
|
|
669
|
-
if (name.endsWith("x") || name.endsWith("z")) return name + "es";
|
|
670
|
-
if (name.endsWith("sh") || name.endsWith("ch")) return name + "es";
|
|
671
|
-
if (name.endsWith("y") && !/[aeiou]y$/.test(name)) return name.slice(0, -1) + "ies";
|
|
672
|
-
return name + "s";
|
|
673
|
-
}
|
|
674
|
-
__name(pluralize, "pluralize");
|
|
675
|
-
function pluralizePascal(name) {
|
|
676
|
-
if (name.endsWith("s")) return name;
|
|
677
|
-
if (name.endsWith("x") || name.endsWith("z")) return name + "es";
|
|
678
|
-
if (name.endsWith("sh") || name.endsWith("ch")) return name + "es";
|
|
679
|
-
if (name.endsWith("y") && !/[aeiou]y$/i.test(name)) return name.slice(0, -1) + "ies";
|
|
680
|
-
return name + "s";
|
|
681
|
-
}
|
|
682
|
-
__name(pluralizePascal, "pluralizePascal");
|
|
683
|
-
|
|
684
|
-
// src/generators/module.ts
|
|
685
|
-
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
686
|
-
|
|
687
|
-
// src/generators/templates/module-index.ts
|
|
688
|
-
function repoMaps(pascal, kebab, repo) {
|
|
689
|
-
const repoClassMap = {
|
|
690
|
-
inmemory: `InMemory${pascal}Repository`,
|
|
691
|
-
drizzle: `Drizzle${pascal}Repository`,
|
|
692
|
-
prisma: `Prisma${pascal}Repository`
|
|
693
|
-
};
|
|
694
|
-
const repoFileMap = {
|
|
695
|
-
inmemory: `in-memory-${kebab}`,
|
|
696
|
-
drizzle: `drizzle-${kebab}`,
|
|
697
|
-
prisma: `prisma-${kebab}`
|
|
698
|
-
};
|
|
699
|
-
return {
|
|
700
|
-
repoClass: repoClassMap[repo] ?? repoClassMap.inmemory,
|
|
701
|
-
repoFile: repoFileMap[repo] ?? repoFileMap.inmemory
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
__name(repoMaps, "repoMaps");
|
|
705
|
-
function generateModuleIndex(pascal, kebab, plural, repo) {
|
|
706
|
-
const { repoClass, repoFile } = repoMaps(pascal, kebab, repo);
|
|
707
|
-
return `/**
|
|
708
|
-
* ${pascal} Module
|
|
249
|
+
`}s(Dt,"generateReadme");function re(e,t){let o=It({input:process.stdin,output:process.stdout}),r=t?` (${t})`:"";return new Promise(n=>{o.question(` ${e}${r}: `,i=>{o.close(),n(i.trim()||t||"")})})}s(re,"ask");async function je(e,t,o=0){console.log(` ${e}`);for(let i=0;i<t.length;i++)console.log(` ${i===o?">":" "} ${i+1}. ${t[i]}`);let r=await re("Choose",String(o+1)),n=parseInt(r,10)-1;return t[n]??t[o]}s(je,"choose");async function oe(e,t=!0){let r=await re(`${e} (${t?"Y/n":"y/N"})`);return r?r.toLowerCase().startsWith("y"):t}s(oe,"confirm");function Pe(e){e.command("new [name]").alias("init").description('Create a new KickJS project (use "." for current directory)').option("-d, --directory <dir>","Target directory (defaults to project name)").option("--pm <manager>","Package manager: pnpm | npm | yarn").option("--git","Initialize git repository").option("--no-git","Skip git initialization").option("--install","Install dependencies after scaffolding").option("--no-install","Skip dependency installation").option("-f, --force","Remove existing files without prompting").option("-t, --template <type>","Project template: rest | graphql | ddd | cqrs | minimal").action(async(t,o)=>{console.log(),t||(t=await re("Project name","my-api"));let r;if(t==="."?(r=te("."),t=Ot(r)):r=te(o.directory||t),St(r)){let a=Tt(r);if(a.length>0){if(o.force)console.log(` Clearing existing files in ${r}...
|
|
250
|
+
`);else{console.log(` Directory "${t}" is not empty:`);let p=a.slice(0,5);for(let u of p)console.log(` - ${u}`);if(a.length>5&&console.log(` ... and ${a.length-5} more`),console.log(),!await oe("Remove all existing files and proceed?",!1)){console.log(` Aborted.
|
|
251
|
+
`);return}}for(let p of a)jt(te(r,p),{recursive:!0,force:!0})}}let n=o.template;n||(n=await je("Project template:",["REST API (Express + Swagger)","GraphQL API (GraphQL + GraphiQL)","DDD (Domain-Driven Design modules)","CQRS (Commands, Queries, Events + WS/Queue)","Minimal (bare Express)"],0),n={"REST API (Express + Swagger)":"rest","GraphQL API (GraphQL + GraphiQL)":"graphql","DDD (Domain-Driven Design modules)":"ddd","CQRS (Commands, Queries, Events + WS/Queue)":"cqrs","Minimal (bare Express)":"minimal"}[n]??"rest");let i=o.pm;i||(i=await je("Package manager:",["pnpm","npm","yarn"],0));let d;o.git===void 0?d=await oe("Initialize git repository?",!0):d=o.git;let c;o.install===void 0?c=await oe("Install dependencies?",!0):c=o.install,await Te({name:t,directory:r,packageManager:i,initGit:d,installDeps:c,template:n})})}s(Pe,"registerInitCommand");import{resolve as z}from"path";import{join as Ce}from"path";import{createInterface as Pt}from"readline";function f(e){return e.replace(/[-_\s]+(.)?/g,(t,o)=>o?o.toUpperCase():"").replace(/^(.)/,t=>t.toUpperCase())}s(f,"toPascalCase");function x(e){let t=f(e);return t.charAt(0).toLowerCase()+t.slice(1)}s(x,"toCamelCase");function $(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}s($,"toKebabCase");function j(e){return e.endsWith("s")?e:e.endsWith("x")||e.endsWith("z")||e.endsWith("sh")||e.endsWith("ch")?e+"es":e.endsWith("y")&&!/[aeiou]y$/.test(e)?e.slice(0,-1)+"ies":e+"s"}s(j,"pluralize");function X(e){return e.endsWith("s")?e:e.endsWith("x")||e.endsWith("z")||e.endsWith("sh")||e.endsWith("ch")?e+"es":e.endsWith("y")&&!/[aeiou]y$/i.test(e)?e.slice(0,-1)+"ies":e+"s"}s(X,"pluralizePascal");import{readFile as Et,writeFile as At}from"fs/promises";function Ee(e,t,o){let r={inmemory:`InMemory${e}Repository`,drizzle:`Drizzle${e}Repository`,prisma:`Prisma${e}Repository`},n={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`};return{repoClass:r[o]??r.inmemory,repoFile:n[o]??n.inmemory}}s(Ee,"repoMaps");function ie(e,t,o,r){let{repoClass:n,repoFile:i}=Ee(e,t,r);return`/**
|
|
252
|
+
* ${e} Module
|
|
709
253
|
*
|
|
710
254
|
* Self-contained feature module following Domain-Driven Design (DDD).
|
|
711
255
|
* Registers dependencies in the DI container and declares HTTP routes.
|
|
@@ -718,9 +262,9 @@ function generateModuleIndex(pascal, kebab, plural, repo) {
|
|
|
718
262
|
*/
|
|
719
263
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
720
264
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
721
|
-
import { ${
|
|
722
|
-
import { ${
|
|
723
|
-
import { ${
|
|
265
|
+
import { ${e.toUpperCase()}_REPOSITORY } from './domain/repositories/${t}.repository'
|
|
266
|
+
import { ${n} } from './infrastructure/repositories/${i}.repository'
|
|
267
|
+
import { ${e}Controller } from './presentation/${t}.controller'
|
|
724
268
|
|
|
725
269
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
726
270
|
import.meta.glob(
|
|
@@ -728,402 +272,325 @@ import.meta.glob(
|
|
|
728
272
|
{ eager: true },
|
|
729
273
|
)
|
|
730
274
|
|
|
731
|
-
export class ${
|
|
275
|
+
export class ${e}Module implements AppModule {
|
|
732
276
|
/**
|
|
733
277
|
* Register module dependencies in the DI container.
|
|
734
278
|
* Bind repository interface tokens to their implementations here.
|
|
735
279
|
* To swap implementations (e.g. in-memory -> Drizzle), change the factory target.
|
|
736
280
|
*/
|
|
737
281
|
register(container: Container): void {
|
|
738
|
-
container.registerFactory(${
|
|
739
|
-
container.resolve(${
|
|
282
|
+
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
283
|
+
container.resolve(${n}),
|
|
740
284
|
)
|
|
741
285
|
}
|
|
742
286
|
|
|
743
287
|
/**
|
|
744
288
|
* Declare HTTP routes for this module.
|
|
745
|
-
* The path is prefixed with the global apiPrefix and version (e.g. /api/v1/${
|
|
289
|
+
* The path is prefixed with the global apiPrefix and version (e.g. /api/v1/${o}).
|
|
746
290
|
* Passing 'controller' enables automatic OpenAPI spec generation via SwaggerAdapter.
|
|
747
291
|
*/
|
|
748
292
|
routes(): ModuleRoutes {
|
|
749
293
|
return {
|
|
750
|
-
path: '/${
|
|
751
|
-
router: buildRoutes(${
|
|
752
|
-
controller: ${
|
|
294
|
+
path: '/${o}',
|
|
295
|
+
router: buildRoutes(${e}Controller),
|
|
296
|
+
controller: ${e}Controller,
|
|
753
297
|
}
|
|
754
298
|
}
|
|
755
299
|
}
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
__name(generateModuleIndex, "generateModuleIndex");
|
|
759
|
-
function generateRestModuleIndex(pascal, kebab, plural, repo) {
|
|
760
|
-
const { repoClass, repoFile } = repoMaps(pascal, kebab, repo);
|
|
761
|
-
return `/**
|
|
762
|
-
* ${pascal} Module
|
|
300
|
+
`}s(ie,"generateModuleIndex");function ne(e,t,o,r){let{repoClass:n,repoFile:i}=Ee(e,t,r);return`/**
|
|
301
|
+
* ${e} Module
|
|
763
302
|
*
|
|
764
303
|
* REST module with a flat folder structure.
|
|
765
304
|
* Controller delegates to service, service wraps the repository.
|
|
766
305
|
*
|
|
767
306
|
* Structure:
|
|
768
|
-
* ${
|
|
769
|
-
* ${
|
|
770
|
-
* ${
|
|
771
|
-
* ${
|
|
307
|
+
* ${t}.controller.ts \u2014 HTTP routes (CRUD)
|
|
308
|
+
* ${t}.service.ts \u2014 Business logic
|
|
309
|
+
* ${t}.repository.ts \u2014 Repository interface
|
|
310
|
+
* ${i}.repository.ts \u2014 Repository implementation
|
|
772
311
|
* dtos/ \u2014 Request/response schemas
|
|
773
312
|
*/
|
|
774
313
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
775
314
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
776
|
-
import { ${
|
|
777
|
-
import { ${
|
|
778
|
-
import { ${
|
|
315
|
+
import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
|
|
316
|
+
import { ${n} } from './${i}.repository'
|
|
317
|
+
import { ${e}Controller } from './${t}.controller'
|
|
779
318
|
|
|
780
319
|
// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container
|
|
781
320
|
import.meta.glob(['./**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'], { eager: true })
|
|
782
321
|
|
|
783
|
-
export class ${
|
|
322
|
+
export class ${e}Module implements AppModule {
|
|
784
323
|
register(container: Container): void {
|
|
785
|
-
container.registerFactory(${
|
|
786
|
-
container.resolve(${
|
|
324
|
+
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
325
|
+
container.resolve(${n}),
|
|
787
326
|
)
|
|
788
327
|
}
|
|
789
328
|
|
|
790
329
|
routes(): ModuleRoutes {
|
|
791
330
|
return {
|
|
792
|
-
path: '/${
|
|
793
|
-
router: buildRoutes(${
|
|
794
|
-
controller: ${
|
|
331
|
+
path: '/${o}',
|
|
332
|
+
router: buildRoutes(${e}Controller),
|
|
333
|
+
controller: ${e}Controller,
|
|
795
334
|
}
|
|
796
335
|
}
|
|
797
336
|
}
|
|
798
|
-
|
|
799
|
-
}
|
|
800
|
-
__name(generateRestModuleIndex, "generateRestModuleIndex");
|
|
801
|
-
function generateMinimalModuleIndex(pascal, kebab, plural) {
|
|
802
|
-
return `import { type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
337
|
+
`}s(ne,"generateRestModuleIndex");function se(e,t,o){return`import { type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
803
338
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
804
|
-
import { ${
|
|
339
|
+
import { ${e}Controller } from './${t}.controller'
|
|
805
340
|
|
|
806
|
-
export class ${
|
|
341
|
+
export class ${e}Module implements AppModule {
|
|
807
342
|
routes(): ModuleRoutes {
|
|
808
343
|
return {
|
|
809
|
-
path: '/${
|
|
810
|
-
router: buildRoutes(${
|
|
811
|
-
controller: ${
|
|
344
|
+
path: '/${o}',
|
|
345
|
+
router: buildRoutes(${e}Controller),
|
|
346
|
+
controller: ${e}Controller,
|
|
812
347
|
}
|
|
813
348
|
}
|
|
814
349
|
}
|
|
815
|
-
|
|
816
|
-
}
|
|
817
|
-
__name(generateMinimalModuleIndex, "generateMinimalModuleIndex");
|
|
818
|
-
|
|
819
|
-
// src/generators/templates/controller.ts
|
|
820
|
-
function generateController(pascal, kebab, plural, pluralPascal) {
|
|
821
|
-
return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
350
|
+
`}s(se,"generateMinimalModuleIndex");function ae(e,t,o,r){return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
822
351
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
823
352
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
824
|
-
import { Create${
|
|
825
|
-
import { Get${
|
|
826
|
-
import { List${
|
|
827
|
-
import { Update${
|
|
828
|
-
import { Delete${
|
|
829
|
-
import { create${
|
|
830
|
-
import { update${
|
|
831
|
-
import { ${
|
|
353
|
+
import { Create${e}UseCase } from '../application/use-cases/create-${t}.use-case'
|
|
354
|
+
import { Get${e}UseCase } from '../application/use-cases/get-${t}.use-case'
|
|
355
|
+
import { List${r}UseCase } from '../application/use-cases/list-${o}.use-case'
|
|
356
|
+
import { Update${e}UseCase } from '../application/use-cases/update-${t}.use-case'
|
|
357
|
+
import { Delete${e}UseCase } from '../application/use-cases/delete-${t}.use-case'
|
|
358
|
+
import { create${e}Schema } from '../application/dtos/create-${t}.dto'
|
|
359
|
+
import { update${e}Schema } from '../application/dtos/update-${t}.dto'
|
|
360
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from '../constants'
|
|
832
361
|
|
|
833
362
|
@Controller()
|
|
834
|
-
export class ${
|
|
835
|
-
@Autowired() private create${
|
|
836
|
-
@Autowired() private get${
|
|
837
|
-
@Autowired() private list${
|
|
838
|
-
@Autowired() private update${
|
|
839
|
-
@Autowired() private delete${
|
|
363
|
+
export class ${e}Controller {
|
|
364
|
+
@Autowired() private create${e}UseCase!: Create${e}UseCase
|
|
365
|
+
@Autowired() private get${e}UseCase!: Get${e}UseCase
|
|
366
|
+
@Autowired() private list${r}UseCase!: List${r}UseCase
|
|
367
|
+
@Autowired() private update${e}UseCase!: Update${e}UseCase
|
|
368
|
+
@Autowired() private delete${e}UseCase!: Delete${e}UseCase
|
|
840
369
|
|
|
841
370
|
@Get('/')
|
|
842
|
-
@ApiTags('${
|
|
843
|
-
@ApiQueryParams(${
|
|
371
|
+
@ApiTags('${e}')
|
|
372
|
+
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
844
373
|
async list(ctx: RequestContext) {
|
|
845
374
|
return ctx.paginate(
|
|
846
|
-
(parsed) => this.list${
|
|
847
|
-
${
|
|
375
|
+
(parsed) => this.list${r}UseCase.execute(parsed),
|
|
376
|
+
${e.toUpperCase()}_QUERY_CONFIG,
|
|
848
377
|
)
|
|
849
378
|
}
|
|
850
379
|
|
|
851
380
|
@Get('/:id')
|
|
852
|
-
@ApiTags('${
|
|
381
|
+
@ApiTags('${e}')
|
|
853
382
|
async getById(ctx: RequestContext) {
|
|
854
|
-
const result = await this.get${
|
|
855
|
-
if (!result) return ctx.notFound('${
|
|
383
|
+
const result = await this.get${e}UseCase.execute(ctx.params.id)
|
|
384
|
+
if (!result) return ctx.notFound('${e} not found')
|
|
856
385
|
ctx.json(result)
|
|
857
386
|
}
|
|
858
387
|
|
|
859
|
-
@Post('/', { body: create${
|
|
860
|
-
@ApiTags('${
|
|
388
|
+
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
389
|
+
@ApiTags('${e}')
|
|
861
390
|
async create(ctx: RequestContext) {
|
|
862
|
-
const result = await this.create${
|
|
391
|
+
const result = await this.create${e}UseCase.execute(ctx.body)
|
|
863
392
|
ctx.created(result)
|
|
864
393
|
}
|
|
865
394
|
|
|
866
|
-
@Put('/:id', { body: update${
|
|
867
|
-
@ApiTags('${
|
|
395
|
+
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
396
|
+
@ApiTags('${e}')
|
|
868
397
|
async update(ctx: RequestContext) {
|
|
869
|
-
const result = await this.update${
|
|
398
|
+
const result = await this.update${e}UseCase.execute(ctx.params.id, ctx.body)
|
|
870
399
|
ctx.json(result)
|
|
871
400
|
}
|
|
872
401
|
|
|
873
402
|
@Delete('/:id')
|
|
874
|
-
@ApiTags('${
|
|
403
|
+
@ApiTags('${e}')
|
|
875
404
|
async remove(ctx: RequestContext) {
|
|
876
|
-
await this.delete${
|
|
405
|
+
await this.delete${e}UseCase.execute(ctx.params.id)
|
|
877
406
|
ctx.noContent()
|
|
878
407
|
}
|
|
879
408
|
}
|
|
880
|
-
|
|
881
|
-
}
|
|
882
|
-
__name(generateController, "generateController");
|
|
883
|
-
function generateRestController(pascal, kebab, plural, pluralPascal) {
|
|
884
|
-
const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
885
|
-
return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
409
|
+
`}s(ae,"generateController");function de(e,t,o,r){let n=e.charAt(0).toLowerCase()+e.slice(1);return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
886
410
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
887
411
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
888
|
-
import { ${
|
|
889
|
-
import { create${
|
|
890
|
-
import { update${
|
|
891
|
-
import { ${
|
|
412
|
+
import { ${e}Service } from './${t}.service'
|
|
413
|
+
import { create${e}Schema } from './dtos/create-${t}.dto'
|
|
414
|
+
import { update${e}Schema } from './dtos/update-${t}.dto'
|
|
415
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
|
|
892
416
|
|
|
893
417
|
@Controller()
|
|
894
|
-
export class ${
|
|
895
|
-
@Autowired() private ${
|
|
418
|
+
export class ${e}Controller {
|
|
419
|
+
@Autowired() private ${n}Service!: ${e}Service
|
|
896
420
|
|
|
897
421
|
@Get('/')
|
|
898
|
-
@ApiTags('${
|
|
899
|
-
@ApiQueryParams(${
|
|
422
|
+
@ApiTags('${e}')
|
|
423
|
+
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
900
424
|
async list(ctx: RequestContext) {
|
|
901
425
|
return ctx.paginate(
|
|
902
|
-
(parsed) => this.${
|
|
903
|
-
${
|
|
426
|
+
(parsed) => this.${n}Service.findPaginated(parsed),
|
|
427
|
+
${e.toUpperCase()}_QUERY_CONFIG,
|
|
904
428
|
)
|
|
905
429
|
}
|
|
906
430
|
|
|
907
431
|
@Get('/:id')
|
|
908
|
-
@ApiTags('${
|
|
432
|
+
@ApiTags('${e}')
|
|
909
433
|
async getById(ctx: RequestContext) {
|
|
910
|
-
const result = await this.${
|
|
911
|
-
if (!result) return ctx.notFound('${
|
|
434
|
+
const result = await this.${n}Service.findById(ctx.params.id)
|
|
435
|
+
if (!result) return ctx.notFound('${e} not found')
|
|
912
436
|
ctx.json(result)
|
|
913
437
|
}
|
|
914
438
|
|
|
915
|
-
@Post('/', { body: create${
|
|
916
|
-
@ApiTags('${
|
|
439
|
+
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
440
|
+
@ApiTags('${e}')
|
|
917
441
|
async create(ctx: RequestContext) {
|
|
918
|
-
const result = await this.${
|
|
442
|
+
const result = await this.${n}Service.create(ctx.body)
|
|
919
443
|
ctx.created(result)
|
|
920
444
|
}
|
|
921
445
|
|
|
922
|
-
@Put('/:id', { body: update${
|
|
923
|
-
@ApiTags('${
|
|
446
|
+
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
447
|
+
@ApiTags('${e}')
|
|
924
448
|
async update(ctx: RequestContext) {
|
|
925
|
-
const result = await this.${
|
|
449
|
+
const result = await this.${n}Service.update(ctx.params.id, ctx.body)
|
|
926
450
|
ctx.json(result)
|
|
927
451
|
}
|
|
928
452
|
|
|
929
453
|
@Delete('/:id')
|
|
930
|
-
@ApiTags('${
|
|
454
|
+
@ApiTags('${e}')
|
|
931
455
|
async remove(ctx: RequestContext) {
|
|
932
|
-
await this.${
|
|
456
|
+
await this.${n}Service.delete(ctx.params.id)
|
|
933
457
|
ctx.noContent()
|
|
934
458
|
}
|
|
935
459
|
}
|
|
936
|
-
|
|
937
|
-
}
|
|
938
|
-
__name(generateRestController, "generateRestController");
|
|
939
|
-
|
|
940
|
-
// src/generators/templates/constants.ts
|
|
941
|
-
function generateConstants(pascal) {
|
|
942
|
-
return `import type { QueryParamsConfig } from '@forinda/kickjs-core'
|
|
460
|
+
`}s(de,"generateRestController");function ce(e){return`import type { QueryParamsConfig } from '@forinda/kickjs-core'
|
|
943
461
|
|
|
944
|
-
export const ${
|
|
462
|
+
export const ${e.toUpperCase()}_QUERY_CONFIG: QueryParamsConfig = {
|
|
945
463
|
filterable: ['name'],
|
|
946
464
|
sortable: ['name', 'createdAt'],
|
|
947
465
|
searchable: ['name'],
|
|
948
466
|
}
|
|
949
|
-
|
|
950
|
-
}
|
|
951
|
-
__name(generateConstants, "generateConstants");
|
|
952
|
-
function generateDrizzleConstants(pascal, kebab) {
|
|
953
|
-
return `import type { DrizzleQueryParamsConfig } from '@forinda/kickjs-drizzle'
|
|
467
|
+
`}s(ce,"generateConstants");function pe(e,t){return`import type { DrizzleQueryParamsConfig } from '@forinda/kickjs-drizzle'
|
|
954
468
|
// TODO: Import your schema table and reference actual columns for type safety
|
|
955
|
-
// import { ${
|
|
469
|
+
// import { ${t}s } from '@/db/schema'
|
|
956
470
|
|
|
957
|
-
export const ${
|
|
471
|
+
export const ${e.toUpperCase()}_QUERY_CONFIG: DrizzleQueryParamsConfig = {
|
|
958
472
|
columns: {
|
|
959
473
|
// Replace with actual Drizzle Column references for type-safe filtering:
|
|
960
|
-
// name: ${
|
|
961
|
-
// status: ${
|
|
474
|
+
// name: ${t}s.name,
|
|
475
|
+
// status: ${t}s.status,
|
|
962
476
|
},
|
|
963
477
|
sortable: {
|
|
964
|
-
// name: ${
|
|
965
|
-
// createdAt: ${
|
|
478
|
+
// name: ${t}s.name,
|
|
479
|
+
// createdAt: ${t}s.createdAt,
|
|
966
480
|
},
|
|
967
481
|
searchColumns: [
|
|
968
|
-
// ${
|
|
482
|
+
// ${t}s.name,
|
|
969
483
|
],
|
|
970
484
|
}
|
|
971
|
-
|
|
972
|
-
}
|
|
973
|
-
__name(generateDrizzleConstants, "generateDrizzleConstants");
|
|
974
|
-
|
|
975
|
-
// src/generators/templates/dtos.ts
|
|
976
|
-
function generateCreateDTO(pascal, kebab) {
|
|
977
|
-
return `import { z } from 'zod'
|
|
485
|
+
`}s(pe,"generateDrizzleConstants");function M(e,t){return`import { z } from 'zod'
|
|
978
486
|
|
|
979
487
|
/**
|
|
980
|
-
* Create ${
|
|
981
|
-
* This schema is passed to @Post('/', { body: create${
|
|
488
|
+
* Create ${e} DTO \u2014 Zod schema for validating POST request bodies.
|
|
489
|
+
* This schema is passed to @Post('/', { body: create${e}Schema }) for automatic validation.
|
|
982
490
|
* It also generates OpenAPI request body docs when SwaggerAdapter is used.
|
|
983
491
|
*
|
|
984
492
|
* Add more fields as needed. Supported Zod types:
|
|
985
493
|
* z.string(), z.number(), z.boolean(), z.enum([...]),
|
|
986
494
|
* z.array(), z.object(), .optional(), .default(), .transform()
|
|
987
495
|
*/
|
|
988
|
-
export const create${
|
|
496
|
+
export const create${e}Schema = z.object({
|
|
989
497
|
name: z.string().min(1, 'Name is required').max(200),
|
|
990
498
|
})
|
|
991
499
|
|
|
992
|
-
export type Create${
|
|
993
|
-
|
|
994
|
-
}
|
|
995
|
-
__name(generateCreateDTO, "generateCreateDTO");
|
|
996
|
-
function generateUpdateDTO(pascal, kebab) {
|
|
997
|
-
return `import { z } from 'zod'
|
|
500
|
+
export type Create${e}DTO = z.infer<typeof create${e}Schema>
|
|
501
|
+
`}s(M,"generateCreateDTO");function b(e,t){return`import { z } from 'zod'
|
|
998
502
|
|
|
999
|
-
export const update${
|
|
503
|
+
export const update${e}Schema = z.object({
|
|
1000
504
|
name: z.string().min(1).max(200).optional(),
|
|
1001
505
|
})
|
|
1002
506
|
|
|
1003
|
-
export type Update${
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
__name(generateUpdateDTO, "generateUpdateDTO");
|
|
1007
|
-
function generateResponseDTO(pascal, kebab) {
|
|
1008
|
-
return `export interface ${pascal}ResponseDTO {
|
|
507
|
+
export type Update${e}DTO = z.infer<typeof update${e}Schema>
|
|
508
|
+
`}s(b,"generateUpdateDTO");function Q(e,t){return`export interface ${e}ResponseDTO {
|
|
1009
509
|
id: string
|
|
1010
510
|
name: string
|
|
1011
511
|
createdAt: string
|
|
1012
512
|
updatedAt: string
|
|
1013
513
|
}
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1016
|
-
__name(generateResponseDTO, "generateResponseDTO");
|
|
1017
|
-
|
|
1018
|
-
// src/generators/templates/use-cases.ts
|
|
1019
|
-
function generateUseCases(pascal, kebab, plural, pluralPascal) {
|
|
1020
|
-
return [
|
|
1021
|
-
{
|
|
1022
|
-
file: `create-${kebab}.use-case.ts`,
|
|
1023
|
-
content: `/**
|
|
1024
|
-
* Create ${pascal} Use Case
|
|
514
|
+
`}s(Q,"generateResponseDTO");function me(e,t,o,r){return[{file:`create-${t}.use-case.ts`,content:`/**
|
|
515
|
+
* Create ${e} Use Case
|
|
1025
516
|
*
|
|
1026
517
|
* Application layer \u2014 orchestrates a single business operation.
|
|
1027
518
|
* Use cases are thin: validate input (via DTO), call domain/repo, return response.
|
|
1028
519
|
* Keep business rules in the domain service, not here.
|
|
1029
520
|
*/
|
|
1030
521
|
import { Service, Inject } from '@forinda/kickjs-core'
|
|
1031
|
-
import { ${
|
|
1032
|
-
import type { Create${
|
|
1033
|
-
import type { ${
|
|
522
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
523
|
+
import type { Create${e}DTO } from '../dtos/create-${t}.dto'
|
|
524
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1034
525
|
|
|
1035
526
|
@Service()
|
|
1036
|
-
export class Create${
|
|
527
|
+
export class Create${e}UseCase {
|
|
1037
528
|
constructor(
|
|
1038
|
-
@Inject(${
|
|
529
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1039
530
|
) {}
|
|
1040
531
|
|
|
1041
|
-
async execute(dto: Create${
|
|
532
|
+
async execute(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1042
533
|
return this.repo.create(dto)
|
|
1043
534
|
}
|
|
1044
535
|
}
|
|
1045
|
-
`
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
file: `get-${kebab}.use-case.ts`,
|
|
1049
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1050
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
1051
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
536
|
+
`},{file:`get-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
537
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
538
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1052
539
|
|
|
1053
540
|
@Service()
|
|
1054
|
-
export class Get${
|
|
541
|
+
export class Get${e}UseCase {
|
|
1055
542
|
constructor(
|
|
1056
|
-
@Inject(${
|
|
543
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1057
544
|
) {}
|
|
1058
545
|
|
|
1059
|
-
async execute(id: string): Promise<${
|
|
546
|
+
async execute(id: string): Promise<${e}ResponseDTO | null> {
|
|
1060
547
|
return this.repo.findById(id)
|
|
1061
548
|
}
|
|
1062
549
|
}
|
|
1063
|
-
`
|
|
1064
|
-
|
|
1065
|
-
{
|
|
1066
|
-
file: `list-${plural}.use-case.ts`,
|
|
1067
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1068
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
550
|
+
`},{file:`list-${o}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
551
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
1069
552
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1070
553
|
|
|
1071
554
|
@Service()
|
|
1072
|
-
export class List${
|
|
555
|
+
export class List${r}UseCase {
|
|
1073
556
|
constructor(
|
|
1074
|
-
@Inject(${
|
|
557
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1075
558
|
) {}
|
|
1076
559
|
|
|
1077
560
|
async execute(parsed: ParsedQuery) {
|
|
1078
561
|
return this.repo.findPaginated(parsed)
|
|
1079
562
|
}
|
|
1080
563
|
}
|
|
1081
|
-
`
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1086
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
1087
|
-
import type { Update${pascal}DTO } from '../dtos/update-${kebab}.dto'
|
|
1088
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
564
|
+
`},{file:`update-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
565
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
566
|
+
import type { Update${e}DTO } from '../dtos/update-${t}.dto'
|
|
567
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1089
568
|
|
|
1090
569
|
@Service()
|
|
1091
|
-
export class Update${
|
|
570
|
+
export class Update${e}UseCase {
|
|
1092
571
|
constructor(
|
|
1093
|
-
@Inject(${
|
|
572
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1094
573
|
) {}
|
|
1095
574
|
|
|
1096
|
-
async execute(id: string, dto: Update${
|
|
575
|
+
async execute(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1097
576
|
return this.repo.update(id, dto)
|
|
1098
577
|
}
|
|
1099
578
|
}
|
|
1100
|
-
`
|
|
1101
|
-
|
|
1102
|
-
{
|
|
1103
|
-
file: `delete-${kebab}.use-case.ts`,
|
|
1104
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1105
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
579
|
+
`},{file:`delete-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
580
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
1106
581
|
|
|
1107
582
|
@Service()
|
|
1108
|
-
export class Delete${
|
|
583
|
+
export class Delete${e}UseCase {
|
|
1109
584
|
constructor(
|
|
1110
|
-
@Inject(${
|
|
585
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1111
586
|
) {}
|
|
1112
587
|
|
|
1113
588
|
async execute(id: string): Promise<void> {
|
|
1114
589
|
await this.repo.delete(id)
|
|
1115
590
|
}
|
|
1116
591
|
}
|
|
1117
|
-
`
|
|
1118
|
-
|
|
1119
|
-
];
|
|
1120
|
-
}
|
|
1121
|
-
__name(generateUseCases, "generateUseCases");
|
|
1122
|
-
|
|
1123
|
-
// src/generators/templates/repository.ts
|
|
1124
|
-
function generateRepositoryInterface(pascal, kebab, dtoPrefix = "../../application/dtos") {
|
|
1125
|
-
return `/**
|
|
1126
|
-
* ${pascal} Repository Interface
|
|
592
|
+
`}]}s(me,"generateUseCases");function G(e,t,o="../../application/dtos"){return`/**
|
|
593
|
+
* ${e} Repository Interface
|
|
1127
594
|
*
|
|
1128
595
|
* Defines the contract for data access.
|
|
1129
596
|
* The interface declares what operations are available;
|
|
@@ -1131,27 +598,23 @@ function generateRepositoryInterface(pascal, kebab, dtoPrefix = "../../applicati
|
|
|
1131
598
|
*
|
|
1132
599
|
* To swap implementations, change the factory in the module's register() method.
|
|
1133
600
|
*/
|
|
1134
|
-
import type { ${
|
|
1135
|
-
import type { Create${
|
|
1136
|
-
import type { Update${
|
|
601
|
+
import type { ${e}ResponseDTO } from '${o}/${t}-response.dto'
|
|
602
|
+
import type { Create${e}DTO } from '${o}/create-${t}.dto'
|
|
603
|
+
import type { Update${e}DTO } from '${o}/update-${t}.dto'
|
|
1137
604
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1138
605
|
|
|
1139
|
-
export interface I${
|
|
1140
|
-
findById(id: string): Promise<${
|
|
1141
|
-
findAll(): Promise<${
|
|
1142
|
-
findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
1143
|
-
create(dto: Create${
|
|
1144
|
-
update(id: string, dto: Update${
|
|
606
|
+
export interface I${e}Repository {
|
|
607
|
+
findById(id: string): Promise<${e}ResponseDTO | null>
|
|
608
|
+
findAll(): Promise<${e}ResponseDTO[]>
|
|
609
|
+
findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }>
|
|
610
|
+
create(dto: Create${e}DTO): Promise<${e}ResponseDTO>
|
|
611
|
+
update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO>
|
|
1145
612
|
delete(id: string): Promise<void>
|
|
1146
613
|
}
|
|
1147
614
|
|
|
1148
|
-
export const ${
|
|
1149
|
-
|
|
1150
|
-
}
|
|
1151
|
-
__name(generateRepositoryInterface, "generateRepositoryInterface");
|
|
1152
|
-
function generateInMemoryRepository(pascal, kebab, repoPrefix = "../../domain/repositories", dtoPrefix = "../../application/dtos") {
|
|
1153
|
-
return `/**
|
|
1154
|
-
* In-Memory ${pascal} Repository
|
|
615
|
+
export const ${e.toUpperCase()}_REPOSITORY = Symbol('I${e}Repository')
|
|
616
|
+
`}s(G,"generateRepositoryInterface");function F(e,t,o="../../domain/repositories",r="../../application/dtos"){return`/**
|
|
617
|
+
* In-Memory ${e} Repository
|
|
1155
618
|
*
|
|
1156
619
|
* Implements the repository interface using a Map.
|
|
1157
620
|
* Useful for prototyping and testing. Replace with a database implementation
|
|
@@ -1162,32 +625,32 @@ function generateInMemoryRepository(pascal, kebab, repoPrefix = "../../domain/re
|
|
|
1162
625
|
import { randomUUID } from 'node:crypto'
|
|
1163
626
|
import { Repository, HttpException } from '@forinda/kickjs-core'
|
|
1164
627
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1165
|
-
import type { I${
|
|
1166
|
-
import type { ${
|
|
1167
|
-
import type { Create${
|
|
1168
|
-
import type { Update${
|
|
628
|
+
import type { I${e}Repository } from '${o}/${t}.repository'
|
|
629
|
+
import type { ${e}ResponseDTO } from '${r}/${t}-response.dto'
|
|
630
|
+
import type { Create${e}DTO } from '${r}/create-${t}.dto'
|
|
631
|
+
import type { Update${e}DTO } from '${r}/update-${t}.dto'
|
|
1169
632
|
|
|
1170
633
|
@Repository()
|
|
1171
|
-
export class InMemory${
|
|
1172
|
-
private store = new Map<string, ${
|
|
634
|
+
export class InMemory${e}Repository implements I${e}Repository {
|
|
635
|
+
private store = new Map<string, ${e}ResponseDTO>()
|
|
1173
636
|
|
|
1174
|
-
async findById(id: string): Promise<${
|
|
637
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
1175
638
|
return this.store.get(id) ?? null
|
|
1176
639
|
}
|
|
1177
640
|
|
|
1178
|
-
async findAll(): Promise<${
|
|
641
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
1179
642
|
return Array.from(this.store.values())
|
|
1180
643
|
}
|
|
1181
644
|
|
|
1182
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
645
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
1183
646
|
const all = Array.from(this.store.values())
|
|
1184
647
|
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
1185
648
|
return { data, total: all.length }
|
|
1186
649
|
}
|
|
1187
650
|
|
|
1188
|
-
async create(dto: Create${
|
|
651
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1189
652
|
const now = new Date().toISOString()
|
|
1190
|
-
const entity: ${
|
|
653
|
+
const entity: ${e}ResponseDTO = {
|
|
1191
654
|
id: randomUUID(),
|
|
1192
655
|
name: dto.name,
|
|
1193
656
|
createdAt: now,
|
|
@@ -1197,25 +660,21 @@ export class InMemory${pascal}Repository implements I${pascal}Repository {
|
|
|
1197
660
|
return entity
|
|
1198
661
|
}
|
|
1199
662
|
|
|
1200
|
-
async update(id: string, dto: Update${
|
|
663
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1201
664
|
const existing = this.store.get(id)
|
|
1202
|
-
if (!existing) throw HttpException.notFound('${
|
|
665
|
+
if (!existing) throw HttpException.notFound('${e} not found')
|
|
1203
666
|
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
1204
667
|
this.store.set(id, updated)
|
|
1205
668
|
return updated
|
|
1206
669
|
}
|
|
1207
670
|
|
|
1208
671
|
async delete(id: string): Promise<void> {
|
|
1209
|
-
if (!this.store.has(id)) throw HttpException.notFound('${
|
|
672
|
+
if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
|
|
1210
673
|
this.store.delete(id)
|
|
1211
674
|
}
|
|
1212
675
|
}
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
__name(generateInMemoryRepository, "generateInMemoryRepository");
|
|
1216
|
-
function generateDrizzleRepository(pascal, kebab, repoPrefix = "../../domain/repositories", dtoPrefix = "../../application/dtos") {
|
|
1217
|
-
return `/**
|
|
1218
|
-
* Drizzle ${pascal} Repository
|
|
676
|
+
`}s(F,"generateInMemoryRepository");function L(e,t,o="../../domain/repositories",r="../../application/dtos"){return`/**
|
|
677
|
+
* Drizzle ${e} Repository
|
|
1219
678
|
*
|
|
1220
679
|
* Implements the repository interface using Drizzle ORM.
|
|
1221
680
|
* Uses buildFromColumns() with Column objects for type-safe query building.
|
|
@@ -1229,185 +688,170 @@ import { eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc,
|
|
|
1229
688
|
import { Repository, HttpException, Inject } from '@forinda/kickjs-core'
|
|
1230
689
|
import { DRIZZLE_DB, DrizzleQueryAdapter } from '@forinda/kickjs-drizzle'
|
|
1231
690
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1232
|
-
import type { I${
|
|
1233
|
-
import type { ${
|
|
1234
|
-
import type { Create${
|
|
1235
|
-
import type { Update${
|
|
1236
|
-
import { ${
|
|
691
|
+
import type { I${e}Repository } from '${o}/${t}.repository'
|
|
692
|
+
import type { ${e}ResponseDTO } from '${r}/${t}-response.dto'
|
|
693
|
+
import type { Create${e}DTO } from '${r}/create-${t}.dto'
|
|
694
|
+
import type { Update${e}DTO } from '${r}/update-${t}.dto'
|
|
695
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from '../../constants'
|
|
1237
696
|
|
|
1238
697
|
// TODO: Import your Drizzle schema table \u2014 e.g.:
|
|
1239
|
-
// import { ${
|
|
698
|
+
// import { ${t}s } from '@/db/schema'
|
|
1240
699
|
|
|
1241
700
|
const queryAdapter = new DrizzleQueryAdapter({
|
|
1242
701
|
eq, ne, gt, gte, lt, lte, ilike, inArray, between, and, or, asc, desc,
|
|
1243
702
|
})
|
|
1244
703
|
|
|
1245
704
|
@Repository()
|
|
1246
|
-
export class Drizzle${
|
|
705
|
+
export class Drizzle${e}Repository implements I${e}Repository {
|
|
1247
706
|
constructor(@Inject(DRIZZLE_DB) private db: any) {}
|
|
1248
707
|
|
|
1249
|
-
async findById(id: string): Promise<${
|
|
708
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
1250
709
|
// TODO: Implement with Drizzle
|
|
1251
|
-
// const row = this.db.select().from(${
|
|
710
|
+
// const row = this.db.select().from(${t}s).where(eq(${t}s.id, id)).get()
|
|
1252
711
|
// return row ?? null
|
|
1253
|
-
throw new Error('Drizzle ${
|
|
712
|
+
throw new Error('Drizzle ${e} repository not yet implemented \u2014 update schema imports and queries')
|
|
1254
713
|
}
|
|
1255
714
|
|
|
1256
|
-
async findAll(): Promise<${
|
|
715
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
1257
716
|
// TODO: Implement with Drizzle
|
|
1258
|
-
// return this.db.select().from(${
|
|
1259
|
-
throw new Error('Drizzle ${
|
|
717
|
+
// return this.db.select().from(${t}s).all()
|
|
718
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
1260
719
|
}
|
|
1261
720
|
|
|
1262
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
721
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
1263
722
|
// TODO: Use buildFromColumns() with your query config for type-safe filtering
|
|
1264
|
-
// const query = queryAdapter.buildFromColumns(parsed, ${
|
|
723
|
+
// const query = queryAdapter.buildFromColumns(parsed, ${e.toUpperCase()}_QUERY_CONFIG)
|
|
1265
724
|
//
|
|
1266
725
|
// const data = this.db
|
|
1267
|
-
// .select().from(${
|
|
726
|
+
// .select().from(${t}s).$dynamic()
|
|
1268
727
|
// .where(query.where).orderBy(...query.orderBy)
|
|
1269
728
|
// .limit(query.limit).offset(query.offset).all()
|
|
1270
729
|
//
|
|
1271
730
|
// const totalResult = this.db
|
|
1272
|
-
// .select({ count: count() }).from(${
|
|
731
|
+
// .select({ count: count() }).from(${t}s)
|
|
1273
732
|
// .$dynamic().where(query.where).get()
|
|
1274
733
|
//
|
|
1275
734
|
// return { data, total: totalResult?.count ?? 0 }
|
|
1276
|
-
throw new Error('Drizzle ${
|
|
735
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
1277
736
|
}
|
|
1278
737
|
|
|
1279
|
-
async create(dto: Create${
|
|
738
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1280
739
|
// TODO: Implement with Drizzle
|
|
1281
|
-
// return this.db.insert(${
|
|
1282
|
-
throw new Error('Drizzle ${
|
|
740
|
+
// return this.db.insert(${t}s).values(dto).returning().get()
|
|
741
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
1283
742
|
}
|
|
1284
743
|
|
|
1285
|
-
async update(id: string, dto: Update${
|
|
744
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1286
745
|
// TODO: Implement with Drizzle
|
|
1287
|
-
// const row = this.db.update(${
|
|
1288
|
-
// if (!row) throw HttpException.notFound('${
|
|
746
|
+
// const row = this.db.update(${t}s).set(dto).where(eq(${t}s.id, id)).returning().get()
|
|
747
|
+
// if (!row) throw HttpException.notFound('${e} not found')
|
|
1289
748
|
// return row
|
|
1290
|
-
throw new Error('Drizzle ${
|
|
749
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
1291
750
|
}
|
|
1292
751
|
|
|
1293
752
|
async delete(id: string): Promise<void> {
|
|
1294
753
|
// TODO: Implement with Drizzle
|
|
1295
|
-
// this.db.delete(${
|
|
1296
|
-
throw new Error('Drizzle ${
|
|
754
|
+
// this.db.delete(${t}s).where(eq(${t}s.id, id)).run()
|
|
755
|
+
throw new Error('Drizzle ${e} repository not yet implemented')
|
|
1297
756
|
}
|
|
1298
757
|
}
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
__name(generateDrizzleRepository, "generateDrizzleRepository");
|
|
1302
|
-
function generatePrismaRepository(pascal, kebab, repoPrefix = "../../domain/repositories", dtoPrefix = "../../application/dtos") {
|
|
1303
|
-
const camel = kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
1304
|
-
return `/**
|
|
1305
|
-
* Prisma ${pascal} Repository
|
|
758
|
+
`}s(L,"generateDrizzleRepository");function N(e,t,o="../../domain/repositories",r="../../application/dtos"){let n=t.replace(/-([a-z])/g,(i,d)=>d.toUpperCase());return`/**
|
|
759
|
+
* Prisma ${e} Repository
|
|
1306
760
|
*
|
|
1307
761
|
* Implements the repository interface using Prisma Client.
|
|
1308
762
|
* Requires a PrismaClient instance injected via the DI container.
|
|
1309
763
|
*
|
|
1310
|
-
* TODO: Ensure your Prisma schema has a '${
|
|
764
|
+
* TODO: Ensure your Prisma schema has a '${e}' model defined.
|
|
1311
765
|
* TODO: Replace 'PRISMA_CLIENT' with your actual Prisma injection token.
|
|
1312
766
|
*
|
|
1313
767
|
* @Repository() registers this class in the DI container as a singleton.
|
|
1314
768
|
*/
|
|
1315
769
|
import { Repository, HttpException, Autowired } from '@forinda/kickjs-core'
|
|
1316
770
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1317
|
-
import type { I${
|
|
1318
|
-
import type { ${
|
|
1319
|
-
import type { Create${
|
|
1320
|
-
import type { Update${
|
|
771
|
+
import type { I${e}Repository } from '${o}/${t}.repository'
|
|
772
|
+
import type { ${e}ResponseDTO } from '${r}/${t}-response.dto'
|
|
773
|
+
import type { Create${e}DTO } from '${r}/create-${t}.dto'
|
|
774
|
+
import type { Update${e}DTO } from '${r}/update-${t}.dto'
|
|
1321
775
|
|
|
1322
776
|
// TODO: Import your Prisma injection token \u2014 e.g.:
|
|
1323
777
|
// import { PRISMA_CLIENT } from '@/db/prisma.provider'
|
|
1324
778
|
// import type { PrismaClient } from '@prisma/client'
|
|
1325
779
|
|
|
1326
780
|
@Repository()
|
|
1327
|
-
export class Prisma${
|
|
781
|
+
export class Prisma${e}Repository implements I${e}Repository {
|
|
1328
782
|
// TODO: Uncomment and configure your Prisma injection:
|
|
1329
783
|
// @Autowired(PRISMA_CLIENT) private prisma!: PrismaClient
|
|
1330
784
|
|
|
1331
|
-
async findById(id: string): Promise<${
|
|
785
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
1332
786
|
// TODO: Implement with Prisma
|
|
1333
|
-
// return this.prisma.${
|
|
1334
|
-
throw new Error('Prisma ${
|
|
787
|
+
// return this.prisma.${n}.findUnique({ where: { id } })
|
|
788
|
+
throw new Error('Prisma ${e} repository not yet implemented \u2014 update Prisma imports and queries')
|
|
1335
789
|
}
|
|
1336
790
|
|
|
1337
|
-
async findAll(): Promise<${
|
|
791
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
1338
792
|
// TODO: Implement with Prisma
|
|
1339
|
-
// return this.prisma.${
|
|
1340
|
-
throw new Error('Prisma ${
|
|
793
|
+
// return this.prisma.${n}.findMany()
|
|
794
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
1341
795
|
}
|
|
1342
796
|
|
|
1343
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
797
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
1344
798
|
// TODO: Implement with Prisma
|
|
1345
799
|
// const [data, total] = await Promise.all([
|
|
1346
|
-
// this.prisma.${
|
|
800
|
+
// this.prisma.${n}.findMany({
|
|
1347
801
|
// skip: parsed.pagination.offset,
|
|
1348
802
|
// take: parsed.pagination.limit,
|
|
1349
803
|
// }),
|
|
1350
|
-
// this.prisma.${
|
|
804
|
+
// this.prisma.${n}.count(),
|
|
1351
805
|
// ])
|
|
1352
806
|
// return { data, total }
|
|
1353
|
-
throw new Error('Prisma ${
|
|
807
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
1354
808
|
}
|
|
1355
809
|
|
|
1356
|
-
async create(dto: Create${
|
|
810
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1357
811
|
// TODO: Implement with Prisma
|
|
1358
|
-
// return this.prisma.${
|
|
1359
|
-
throw new Error('Prisma ${
|
|
812
|
+
// return this.prisma.${n}.create({ data: dto })
|
|
813
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
1360
814
|
}
|
|
1361
815
|
|
|
1362
|
-
async update(id: string, dto: Update${
|
|
816
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1363
817
|
// TODO: Implement with Prisma
|
|
1364
|
-
// const row = await this.prisma.${
|
|
1365
|
-
// if (!row) throw HttpException.notFound('${
|
|
818
|
+
// const row = await this.prisma.${n}.update({ where: { id }, data: dto })
|
|
819
|
+
// if (!row) throw HttpException.notFound('${e} not found')
|
|
1366
820
|
// return row
|
|
1367
|
-
throw new Error('Prisma ${
|
|
821
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
1368
822
|
}
|
|
1369
823
|
|
|
1370
824
|
async delete(id: string): Promise<void> {
|
|
1371
825
|
// TODO: Implement with Prisma
|
|
1372
|
-
// await this.prisma.${
|
|
1373
|
-
throw new Error('Prisma ${
|
|
826
|
+
// await this.prisma.${n}.delete({ where: { id } })
|
|
827
|
+
throw new Error('Prisma ${e} repository not yet implemented')
|
|
1374
828
|
}
|
|
1375
829
|
}
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
__name(generatePrismaRepository, "generatePrismaRepository");
|
|
1379
|
-
|
|
1380
|
-
// src/generators/templates/domain.ts
|
|
1381
|
-
function generateDomainService(pascal, kebab) {
|
|
1382
|
-
return `/**
|
|
1383
|
-
* ${pascal} Domain Service
|
|
830
|
+
`}s(N,"generatePrismaRepository");function le(e,t){return`/**
|
|
831
|
+
* ${e} Domain Service
|
|
1384
832
|
*
|
|
1385
833
|
* Domain layer \u2014 contains business rules that don't belong to a single entity.
|
|
1386
834
|
* Use this for cross-entity logic, validation rules, and domain invariants.
|
|
1387
835
|
* Keep it free of HTTP/framework concerns.
|
|
1388
836
|
*/
|
|
1389
837
|
import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
1390
|
-
import { ${
|
|
838
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../repositories/${t}.repository'
|
|
1391
839
|
|
|
1392
840
|
@Service()
|
|
1393
|
-
export class ${
|
|
841
|
+
export class ${e}DomainService {
|
|
1394
842
|
constructor(
|
|
1395
|
-
@Inject(${
|
|
843
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1396
844
|
) {}
|
|
1397
845
|
|
|
1398
846
|
async ensureExists(id: string): Promise<void> {
|
|
1399
847
|
const entity = await this.repo.findById(id)
|
|
1400
848
|
if (!entity) {
|
|
1401
|
-
throw HttpException.notFound('${
|
|
849
|
+
throw HttpException.notFound('${e} not found')
|
|
1402
850
|
}
|
|
1403
851
|
}
|
|
1404
852
|
}
|
|
1405
|
-
|
|
1406
|
-
}
|
|
1407
|
-
__name(generateDomainService, "generateDomainService");
|
|
1408
|
-
function generateEntity(pascal, kebab) {
|
|
1409
|
-
return `/**
|
|
1410
|
-
* ${pascal} Entity
|
|
853
|
+
`}s(le,"generateDomainService");function ue(e,t){return`/**
|
|
854
|
+
* ${e} Entity
|
|
1411
855
|
*
|
|
1412
856
|
* Domain layer \u2014 the core business object.
|
|
1413
857
|
* Uses a private constructor with static factory methods (create, reconstitute)
|
|
@@ -1419,33 +863,33 @@ function generateEntity(pascal, kebab) {
|
|
|
1419
863
|
* - reconstitute(): factory for rebuilding from persistence (no side effects)
|
|
1420
864
|
* - changeName(): mutation method that enforces business rules
|
|
1421
865
|
*/
|
|
1422
|
-
import { ${
|
|
866
|
+
import { ${e}Id } from '../value-objects/${t}-id.vo'
|
|
1423
867
|
|
|
1424
|
-
interface ${
|
|
1425
|
-
id: ${
|
|
868
|
+
interface ${e}Props {
|
|
869
|
+
id: ${e}Id
|
|
1426
870
|
name: string
|
|
1427
871
|
createdAt: Date
|
|
1428
872
|
updatedAt: Date
|
|
1429
873
|
}
|
|
1430
874
|
|
|
1431
|
-
export class ${
|
|
1432
|
-
private constructor(private props: ${
|
|
875
|
+
export class ${e} {
|
|
876
|
+
private constructor(private props: ${e}Props) {}
|
|
1433
877
|
|
|
1434
|
-
static create(params: { name: string }): ${
|
|
878
|
+
static create(params: { name: string }): ${e} {
|
|
1435
879
|
const now = new Date()
|
|
1436
|
-
return new ${
|
|
1437
|
-
id: ${
|
|
880
|
+
return new ${e}({
|
|
881
|
+
id: ${e}Id.create(),
|
|
1438
882
|
name: params.name,
|
|
1439
883
|
createdAt: now,
|
|
1440
884
|
updatedAt: now,
|
|
1441
885
|
})
|
|
1442
886
|
}
|
|
1443
887
|
|
|
1444
|
-
static reconstitute(props: ${
|
|
1445
|
-
return new ${
|
|
888
|
+
static reconstitute(props: ${e}Props): ${e} {
|
|
889
|
+
return new ${e}(props)
|
|
1446
890
|
}
|
|
1447
891
|
|
|
1448
|
-
get id(): ${
|
|
892
|
+
get id(): ${e}Id {
|
|
1449
893
|
return this.props.id
|
|
1450
894
|
}
|
|
1451
895
|
get name(): string {
|
|
@@ -1475,54 +919,44 @@ export class ${pascal} {
|
|
|
1475
919
|
}
|
|
1476
920
|
}
|
|
1477
921
|
}
|
|
1478
|
-
|
|
1479
|
-
}
|
|
1480
|
-
__name(generateEntity, "generateEntity");
|
|
1481
|
-
function generateValueObject(pascal, kebab) {
|
|
1482
|
-
return `/**
|
|
1483
|
-
* ${pascal} ID Value Object
|
|
922
|
+
`}s(ue,"generateEntity");function fe(e,t){return`/**
|
|
923
|
+
* ${e} ID Value Object
|
|
1484
924
|
*
|
|
1485
925
|
* Domain layer \u2014 wraps a primitive ID with type safety and validation.
|
|
1486
926
|
* Value objects are immutable and compared by value, not reference.
|
|
1487
927
|
*
|
|
1488
|
-
* ${
|
|
1489
|
-
* ${
|
|
928
|
+
* ${e}Id.create() \u2014 generate a new UUID
|
|
929
|
+
* ${e}Id.from(id) \u2014 wrap an existing ID string (validates non-empty)
|
|
1490
930
|
* id.equals(other) \u2014 compare two IDs by value
|
|
1491
931
|
*/
|
|
1492
932
|
import { randomUUID } from 'node:crypto'
|
|
1493
933
|
|
|
1494
|
-
export class ${
|
|
934
|
+
export class ${e}Id {
|
|
1495
935
|
private constructor(private readonly value: string) {}
|
|
1496
936
|
|
|
1497
|
-
static create(): ${
|
|
1498
|
-
return new ${
|
|
937
|
+
static create(): ${e}Id {
|
|
938
|
+
return new ${e}Id(randomUUID())
|
|
1499
939
|
}
|
|
1500
940
|
|
|
1501
|
-
static from(id: string): ${
|
|
941
|
+
static from(id: string): ${e}Id {
|
|
1502
942
|
if (!id || id.trim().length === 0) {
|
|
1503
|
-
throw new Error('${
|
|
943
|
+
throw new Error('${e}Id cannot be empty')
|
|
1504
944
|
}
|
|
1505
|
-
return new ${
|
|
945
|
+
return new ${e}Id(id)
|
|
1506
946
|
}
|
|
1507
947
|
|
|
1508
948
|
toString(): string {
|
|
1509
949
|
return this.value
|
|
1510
950
|
}
|
|
1511
951
|
|
|
1512
|
-
equals(other: ${
|
|
952
|
+
equals(other: ${e}Id): boolean {
|
|
1513
953
|
return this.value === other.value
|
|
1514
954
|
}
|
|
1515
955
|
}
|
|
1516
|
-
|
|
1517
|
-
}
|
|
1518
|
-
__name(generateValueObject, "generateValueObject");
|
|
1519
|
-
|
|
1520
|
-
// src/generators/templates/tests.ts
|
|
1521
|
-
function generateControllerTest(pascal, kebab, plural) {
|
|
1522
|
-
return `import { describe, it, expect, beforeEach } from 'vitest'
|
|
956
|
+
`}s(fe,"generateValueObject");function Y(e,t,o){return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
1523
957
|
import { Container } from '@forinda/kickjs-core'
|
|
1524
958
|
|
|
1525
|
-
describe('${
|
|
959
|
+
describe('${e}Controller', () => {
|
|
1526
960
|
beforeEach(() => {
|
|
1527
961
|
Container.reset()
|
|
1528
962
|
})
|
|
@@ -1531,64 +965,60 @@ describe('${pascal}Controller', () => {
|
|
|
1531
965
|
expect(true).toBe(true)
|
|
1532
966
|
})
|
|
1533
967
|
|
|
1534
|
-
describe('POST /${
|
|
1535
|
-
it('should create a new ${
|
|
968
|
+
describe('POST /${o}', () => {
|
|
969
|
+
it('should create a new ${t}', async () => {
|
|
1536
970
|
// TODO: Set up test module, call create endpoint, assert 201
|
|
1537
971
|
expect(true).toBe(true)
|
|
1538
972
|
})
|
|
1539
973
|
})
|
|
1540
974
|
|
|
1541
|
-
describe('GET /${
|
|
1542
|
-
it('should return paginated ${
|
|
975
|
+
describe('GET /${o}', () => {
|
|
976
|
+
it('should return paginated ${o}', async () => {
|
|
1543
977
|
// TODO: Set up test module, call list endpoint, assert { data, meta }
|
|
1544
978
|
expect(true).toBe(true)
|
|
1545
979
|
})
|
|
1546
980
|
})
|
|
1547
981
|
|
|
1548
|
-
describe('GET /${
|
|
1549
|
-
it('should return a ${
|
|
1550
|
-
// TODO: Create a ${
|
|
982
|
+
describe('GET /${o}/:id', () => {
|
|
983
|
+
it('should return a ${t} by id', async () => {
|
|
984
|
+
// TODO: Create a ${t}, then fetch by id, assert match
|
|
1551
985
|
expect(true).toBe(true)
|
|
1552
986
|
})
|
|
1553
987
|
|
|
1554
|
-
it('should return 404 for non-existent ${
|
|
988
|
+
it('should return 404 for non-existent ${t}', async () => {
|
|
1555
989
|
// TODO: Fetch non-existent id, assert 404
|
|
1556
990
|
expect(true).toBe(true)
|
|
1557
991
|
})
|
|
1558
992
|
})
|
|
1559
993
|
|
|
1560
|
-
describe('PUT /${
|
|
1561
|
-
it('should update an existing ${
|
|
994
|
+
describe('PUT /${o}/:id', () => {
|
|
995
|
+
it('should update an existing ${t}', async () => {
|
|
1562
996
|
// TODO: Create, update, assert changes
|
|
1563
997
|
expect(true).toBe(true)
|
|
1564
998
|
})
|
|
1565
999
|
})
|
|
1566
1000
|
|
|
1567
|
-
describe('DELETE /${
|
|
1568
|
-
it('should delete a ${
|
|
1001
|
+
describe('DELETE /${o}/:id', () => {
|
|
1002
|
+
it('should delete a ${t}', async () => {
|
|
1569
1003
|
// TODO: Create, delete, assert gone
|
|
1570
1004
|
expect(true).toBe(true)
|
|
1571
1005
|
})
|
|
1572
1006
|
})
|
|
1573
1007
|
})
|
|
1574
|
-
|
|
1575
|
-
}
|
|
1576
|
-
__name(generateControllerTest, "generateControllerTest");
|
|
1577
|
-
function generateRepositoryTest(pascal, kebab, plural, repoImport = `../infrastructure/repositories/in-memory-${kebab}.repository`) {
|
|
1578
|
-
return `import { describe, it, expect, beforeEach } from 'vitest'
|
|
1579
|
-
import { InMemory${pascal}Repository } from '${repoImport}'
|
|
1008
|
+
`}s(Y,"generateControllerTest");function B(e,t,o,r=`../infrastructure/repositories/in-memory-${t}.repository`){return`import { describe, it, expect, beforeEach } from 'vitest'
|
|
1009
|
+
import { InMemory${e}Repository } from '${r}'
|
|
1580
1010
|
|
|
1581
|
-
describe('InMemory${
|
|
1582
|
-
let repo: InMemory${
|
|
1011
|
+
describe('InMemory${e}Repository', () => {
|
|
1012
|
+
let repo: InMemory${e}Repository
|
|
1583
1013
|
|
|
1584
1014
|
beforeEach(() => {
|
|
1585
|
-
repo = new InMemory${
|
|
1015
|
+
repo = new InMemory${e}Repository()
|
|
1586
1016
|
})
|
|
1587
1017
|
|
|
1588
|
-
it('should create and retrieve a ${
|
|
1589
|
-
const created = await repo.create({ name: 'Test ${
|
|
1018
|
+
it('should create and retrieve a ${t}', async () => {
|
|
1019
|
+
const created = await repo.create({ name: 'Test ${e}' })
|
|
1590
1020
|
expect(created).toBeDefined()
|
|
1591
|
-
expect(created.name).toBe('Test ${
|
|
1021
|
+
expect(created.name).toBe('Test ${e}')
|
|
1592
1022
|
expect(created.id).toBeDefined()
|
|
1593
1023
|
|
|
1594
1024
|
const found = await repo.findById(created.id)
|
|
@@ -1600,18 +1030,18 @@ describe('InMemory${pascal}Repository', () => {
|
|
|
1600
1030
|
expect(found).toBeNull()
|
|
1601
1031
|
})
|
|
1602
1032
|
|
|
1603
|
-
it('should list all ${
|
|
1604
|
-
await repo.create({ name: '${
|
|
1605
|
-
await repo.create({ name: '${
|
|
1033
|
+
it('should list all ${o}', async () => {
|
|
1034
|
+
await repo.create({ name: '${e} 1' })
|
|
1035
|
+
await repo.create({ name: '${e} 2' })
|
|
1606
1036
|
|
|
1607
1037
|
const all = await repo.findAll()
|
|
1608
1038
|
expect(all).toHaveLength(2)
|
|
1609
1039
|
})
|
|
1610
1040
|
|
|
1611
1041
|
it('should return paginated results', async () => {
|
|
1612
|
-
await repo.create({ name: '${
|
|
1613
|
-
await repo.create({ name: '${
|
|
1614
|
-
await repo.create({ name: '${
|
|
1042
|
+
await repo.create({ name: '${e} 1' })
|
|
1043
|
+
await repo.create({ name: '${e} 2' })
|
|
1044
|
+
await repo.create({ name: '${e} 3' })
|
|
1615
1045
|
|
|
1616
1046
|
const result = await repo.findPaginated({
|
|
1617
1047
|
filters: [],
|
|
@@ -1624,43 +1054,37 @@ describe('InMemory${pascal}Repository', () => {
|
|
|
1624
1054
|
expect(result.total).toBe(3)
|
|
1625
1055
|
})
|
|
1626
1056
|
|
|
1627
|
-
it('should update a ${
|
|
1057
|
+
it('should update a ${t}', async () => {
|
|
1628
1058
|
const created = await repo.create({ name: 'Original' })
|
|
1629
1059
|
const updated = await repo.update(created.id, { name: 'Updated' })
|
|
1630
1060
|
expect(updated.name).toBe('Updated')
|
|
1631
1061
|
})
|
|
1632
1062
|
|
|
1633
|
-
it('should delete a ${
|
|
1063
|
+
it('should delete a ${t}', async () => {
|
|
1634
1064
|
const created = await repo.create({ name: 'To Delete' })
|
|
1635
1065
|
await repo.delete(created.id)
|
|
1636
1066
|
const found = await repo.findById(created.id)
|
|
1637
1067
|
expect(found).toBeNull()
|
|
1638
1068
|
})
|
|
1639
1069
|
})
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
__name(generateRepositoryTest, "generateRepositoryTest");
|
|
1643
|
-
|
|
1644
|
-
// src/generators/templates/rest-service.ts
|
|
1645
|
-
function generateRestService(pascal, kebab) {
|
|
1646
|
-
return `import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
1070
|
+
`}s(B,"generateRepositoryTest");function $e(e,t){return`import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
1647
1071
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1648
|
-
import { ${
|
|
1649
|
-
import type { ${
|
|
1650
|
-
import type { Create${
|
|
1651
|
-
import type { Update${
|
|
1072
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from './${t}.repository'
|
|
1073
|
+
import type { ${e}ResponseDTO } from './dtos/${t}-response.dto'
|
|
1074
|
+
import type { Create${e}DTO } from './dtos/create-${t}.dto'
|
|
1075
|
+
import type { Update${e}DTO } from './dtos/update-${t}.dto'
|
|
1652
1076
|
|
|
1653
1077
|
@Service()
|
|
1654
|
-
export class ${
|
|
1078
|
+
export class ${e}Service {
|
|
1655
1079
|
constructor(
|
|
1656
|
-
@Inject(${
|
|
1080
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1657
1081
|
) {}
|
|
1658
1082
|
|
|
1659
|
-
async findById(id: string): Promise<${
|
|
1083
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
1660
1084
|
return this.repo.findById(id)
|
|
1661
1085
|
}
|
|
1662
1086
|
|
|
1663
|
-
async findAll(): Promise<${
|
|
1087
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
1664
1088
|
return this.repo.findAll()
|
|
1665
1089
|
}
|
|
1666
1090
|
|
|
@@ -1668,11 +1092,11 @@ export class ${pascal}Service {
|
|
|
1668
1092
|
return this.repo.findPaginated(parsed)
|
|
1669
1093
|
}
|
|
1670
1094
|
|
|
1671
|
-
async create(dto: Create${
|
|
1095
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1672
1096
|
return this.repo.create(dto)
|
|
1673
1097
|
}
|
|
1674
1098
|
|
|
1675
|
-
async update(id: string, dto: Update${
|
|
1099
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1676
1100
|
return this.repo.update(id, dto)
|
|
1677
1101
|
}
|
|
1678
1102
|
|
|
@@ -1680,37 +1104,15 @@ export class ${pascal}Service {
|
|
|
1680
1104
|
await this.repo.delete(id)
|
|
1681
1105
|
}
|
|
1682
1106
|
}
|
|
1683
|
-
|
|
1684
|
-
}
|
|
1685
|
-
__name(generateRestService, "generateRestService");
|
|
1686
|
-
function generateRestConstants(pascal) {
|
|
1687
|
-
return `import type { QueryFieldConfig } from '@forinda/kickjs-http'
|
|
1107
|
+
`}s($e,"generateRestService");function ee(e){return`import type { QueryFieldConfig } from '@forinda/kickjs-http'
|
|
1688
1108
|
|
|
1689
|
-
export const ${
|
|
1109
|
+
export const ${e.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {
|
|
1690
1110
|
filterable: ['name'],
|
|
1691
1111
|
sortable: ['name', 'createdAt'],
|
|
1692
1112
|
searchable: ['name'],
|
|
1693
1113
|
}
|
|
1694
|
-
|
|
1695
|
-
}
|
|
1696
|
-
__name(generateRestConstants, "generateRestConstants");
|
|
1697
|
-
|
|
1698
|
-
// src/generators/templates/cqrs.ts
|
|
1699
|
-
function generateCqrsModuleIndex(pascal, kebab, plural, repo) {
|
|
1700
|
-
const repoClassMap = {
|
|
1701
|
-
inmemory: `InMemory${pascal}Repository`,
|
|
1702
|
-
drizzle: `Drizzle${pascal}Repository`,
|
|
1703
|
-
prisma: `Prisma${pascal}Repository`
|
|
1704
|
-
};
|
|
1705
|
-
const repoFileMap = {
|
|
1706
|
-
inmemory: `in-memory-${kebab}`,
|
|
1707
|
-
drizzle: `drizzle-${kebab}`,
|
|
1708
|
-
prisma: `prisma-${kebab}`
|
|
1709
|
-
};
|
|
1710
|
-
const repoClass = repoClassMap[repo] ?? repoClassMap.inmemory;
|
|
1711
|
-
const repoFile = repoFileMap[repo] ?? repoFileMap.inmemory;
|
|
1712
|
-
return `/**
|
|
1713
|
-
* ${pascal} Module \u2014 CQRS Pattern
|
|
1114
|
+
`}s(ee,"generateRestConstants");function ge(e,t,o,r){let n={inmemory:`InMemory${e}Repository`,drizzle:`Drizzle${e}Repository`,prisma:`Prisma${e}Repository`},i={inmemory:`in-memory-${t}`,drizzle:`drizzle-${t}`,prisma:`prisma-${t}`},d=n[r]??n.inmemory,c=i[r]??i.inmemory;return`/**
|
|
1115
|
+
* ${e} Module \u2014 CQRS Pattern
|
|
1714
1116
|
*
|
|
1715
1117
|
* Separates read (queries) and write (commands) operations.
|
|
1716
1118
|
* Events are emitted after state changes and can be handled via
|
|
@@ -1724,9 +1126,9 @@ function generateCqrsModuleIndex(pascal, kebab, plural, repo) {
|
|
|
1724
1126
|
*/
|
|
1725
1127
|
import { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs-core'
|
|
1726
1128
|
import { buildRoutes } from '@forinda/kickjs-http'
|
|
1727
|
-
import { ${
|
|
1728
|
-
import { ${
|
|
1729
|
-
import { ${
|
|
1129
|
+
import { ${e.toUpperCase()}_REPOSITORY } from './${t}.repository'
|
|
1130
|
+
import { ${d} } from './${c}.repository'
|
|
1131
|
+
import { ${e}Controller } from './${t}.controller'
|
|
1730
1132
|
|
|
1731
1133
|
// Eagerly load decorated classes
|
|
1732
1134
|
import.meta.glob(
|
|
@@ -1739,210 +1141,168 @@ import.meta.glob(
|
|
|
1739
1141
|
{ eager: true },
|
|
1740
1142
|
)
|
|
1741
1143
|
|
|
1742
|
-
export class ${
|
|
1144
|
+
export class ${e}Module implements AppModule {
|
|
1743
1145
|
register(container: Container): void {
|
|
1744
|
-
container.registerFactory(${
|
|
1745
|
-
container.resolve(${
|
|
1146
|
+
container.registerFactory(${e.toUpperCase()}_REPOSITORY, () =>
|
|
1147
|
+
container.resolve(${d}),
|
|
1746
1148
|
)
|
|
1747
1149
|
}
|
|
1748
1150
|
|
|
1749
1151
|
routes(): ModuleRoutes {
|
|
1750
1152
|
return {
|
|
1751
|
-
path: '/${
|
|
1752
|
-
router: buildRoutes(${
|
|
1753
|
-
controller: ${
|
|
1153
|
+
path: '/${o}',
|
|
1154
|
+
router: buildRoutes(${e}Controller),
|
|
1155
|
+
controller: ${e}Controller,
|
|
1754
1156
|
}
|
|
1755
1157
|
}
|
|
1756
1158
|
}
|
|
1757
|
-
|
|
1758
|
-
}
|
|
1759
|
-
__name(generateCqrsModuleIndex, "generateCqrsModuleIndex");
|
|
1760
|
-
function generateCqrsController(pascal, kebab, plural, pluralPascal) {
|
|
1761
|
-
const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
1762
|
-
return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
1159
|
+
`}s(ge,"generateCqrsModuleIndex");function ye(e,t,o,r){let n=e.charAt(0).toLowerCase()+e.slice(1);return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
1763
1160
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
1764
1161
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
1765
|
-
import { Create${
|
|
1766
|
-
import { Update${
|
|
1767
|
-
import { Delete${
|
|
1768
|
-
import { Get${
|
|
1769
|
-
import { List${
|
|
1770
|
-
import { create${
|
|
1771
|
-
import { update${
|
|
1772
|
-
import { ${
|
|
1162
|
+
import { Create${e}Command } from './commands/create-${t}.command'
|
|
1163
|
+
import { Update${e}Command } from './commands/update-${t}.command'
|
|
1164
|
+
import { Delete${e}Command } from './commands/delete-${t}.command'
|
|
1165
|
+
import { Get${e}Query } from './queries/get-${t}.query'
|
|
1166
|
+
import { List${r}Query } from './queries/list-${o}.query'
|
|
1167
|
+
import { create${e}Schema } from './dtos/create-${t}.dto'
|
|
1168
|
+
import { update${e}Schema } from './dtos/update-${t}.dto'
|
|
1169
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from './${t}.constants'
|
|
1773
1170
|
|
|
1774
1171
|
@Controller()
|
|
1775
|
-
export class ${
|
|
1776
|
-
@Autowired() private create${
|
|
1777
|
-
@Autowired() private update${
|
|
1778
|
-
@Autowired() private delete${
|
|
1779
|
-
@Autowired() private get${
|
|
1780
|
-
@Autowired() private list${
|
|
1172
|
+
export class ${e}Controller {
|
|
1173
|
+
@Autowired() private create${e}Command!: Create${e}Command
|
|
1174
|
+
@Autowired() private update${e}Command!: Update${e}Command
|
|
1175
|
+
@Autowired() private delete${e}Command!: Delete${e}Command
|
|
1176
|
+
@Autowired() private get${e}Query!: Get${e}Query
|
|
1177
|
+
@Autowired() private list${r}Query!: List${r}Query
|
|
1781
1178
|
|
|
1782
1179
|
@Get('/')
|
|
1783
|
-
@ApiTags('${
|
|
1784
|
-
@ApiQueryParams(${
|
|
1180
|
+
@ApiTags('${e}')
|
|
1181
|
+
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
1785
1182
|
async list(ctx: RequestContext) {
|
|
1786
1183
|
return ctx.paginate(
|
|
1787
|
-
(parsed) => this.list${
|
|
1788
|
-
${
|
|
1184
|
+
(parsed) => this.list${r}Query.execute(parsed),
|
|
1185
|
+
${e.toUpperCase()}_QUERY_CONFIG,
|
|
1789
1186
|
)
|
|
1790
1187
|
}
|
|
1791
1188
|
|
|
1792
1189
|
@Get('/:id')
|
|
1793
|
-
@ApiTags('${
|
|
1190
|
+
@ApiTags('${e}')
|
|
1794
1191
|
async getById(ctx: RequestContext) {
|
|
1795
|
-
const result = await this.get${
|
|
1796
|
-
if (!result) return ctx.notFound('${
|
|
1192
|
+
const result = await this.get${e}Query.execute(ctx.params.id)
|
|
1193
|
+
if (!result) return ctx.notFound('${e} not found')
|
|
1797
1194
|
ctx.json(result)
|
|
1798
1195
|
}
|
|
1799
1196
|
|
|
1800
|
-
@Post('/', { body: create${
|
|
1801
|
-
@ApiTags('${
|
|
1197
|
+
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
1198
|
+
@ApiTags('${e}')
|
|
1802
1199
|
async create(ctx: RequestContext) {
|
|
1803
|
-
const result = await this.create${
|
|
1200
|
+
const result = await this.create${e}Command.execute(ctx.body)
|
|
1804
1201
|
ctx.created(result)
|
|
1805
1202
|
}
|
|
1806
1203
|
|
|
1807
|
-
@Put('/:id', { body: update${
|
|
1808
|
-
@ApiTags('${
|
|
1204
|
+
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
1205
|
+
@ApiTags('${e}')
|
|
1809
1206
|
async update(ctx: RequestContext) {
|
|
1810
|
-
const result = await this.update${
|
|
1207
|
+
const result = await this.update${e}Command.execute(ctx.params.id, ctx.body)
|
|
1811
1208
|
ctx.json(result)
|
|
1812
1209
|
}
|
|
1813
1210
|
|
|
1814
1211
|
@Delete('/:id')
|
|
1815
|
-
@ApiTags('${
|
|
1212
|
+
@ApiTags('${e}')
|
|
1816
1213
|
async remove(ctx: RequestContext) {
|
|
1817
|
-
await this.delete${
|
|
1214
|
+
await this.delete${e}Command.execute(ctx.params.id)
|
|
1818
1215
|
ctx.noContent()
|
|
1819
1216
|
}
|
|
1820
1217
|
}
|
|
1821
|
-
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
{
|
|
1827
|
-
file: `create-${kebab}.command.ts`,
|
|
1828
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1829
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1830
|
-
import type { Create${pascal}DTO } from '../dtos/create-${kebab}.dto'
|
|
1831
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
1832
|
-
import { ${pascal}Events } from '../events/${kebab}.events'
|
|
1218
|
+
`}s(ye,"generateCqrsController");function he(e,t){return[{file:`create-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1219
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1220
|
+
import type { Create${e}DTO } from '../dtos/create-${t}.dto'
|
|
1221
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1222
|
+
import { ${e}Events } from '../events/${t}.events'
|
|
1833
1223
|
|
|
1834
1224
|
@Service()
|
|
1835
|
-
export class Create${
|
|
1225
|
+
export class Create${e}Command {
|
|
1836
1226
|
constructor(
|
|
1837
|
-
@Inject(${
|
|
1838
|
-
@Inject(${
|
|
1227
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1228
|
+
@Inject(${e}Events) private readonly events: ${e}Events,
|
|
1839
1229
|
) {}
|
|
1840
1230
|
|
|
1841
|
-
async execute(dto: Create${
|
|
1231
|
+
async execute(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
1842
1232
|
const result = await this.repo.create(dto)
|
|
1843
|
-
this.events.emit('${
|
|
1233
|
+
this.events.emit('${t}.created', result)
|
|
1844
1234
|
return result
|
|
1845
1235
|
}
|
|
1846
1236
|
}
|
|
1847
|
-
`
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1853
|
-
import type { Update${pascal}DTO } from '../dtos/update-${kebab}.dto'
|
|
1854
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
1855
|
-
import { ${pascal}Events } from '../events/${kebab}.events'
|
|
1237
|
+
`},{file:`update-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1238
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1239
|
+
import type { Update${e}DTO } from '../dtos/update-${t}.dto'
|
|
1240
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1241
|
+
import { ${e}Events } from '../events/${t}.events'
|
|
1856
1242
|
|
|
1857
1243
|
@Service()
|
|
1858
|
-
export class Update${
|
|
1244
|
+
export class Update${e}Command {
|
|
1859
1245
|
constructor(
|
|
1860
|
-
@Inject(${
|
|
1861
|
-
@Inject(${
|
|
1246
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1247
|
+
@Inject(${e}Events) private readonly events: ${e}Events,
|
|
1862
1248
|
) {}
|
|
1863
1249
|
|
|
1864
|
-
async execute(id: string, dto: Update${
|
|
1250
|
+
async execute(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
1865
1251
|
const result = await this.repo.update(id, dto)
|
|
1866
|
-
this.events.emit('${
|
|
1252
|
+
this.events.emit('${t}.updated', result)
|
|
1867
1253
|
return result
|
|
1868
1254
|
}
|
|
1869
1255
|
}
|
|
1870
|
-
`
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
file: `delete-${kebab}.command.ts`,
|
|
1874
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1875
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1876
|
-
import { ${pascal}Events } from '../events/${kebab}.events'
|
|
1256
|
+
`},{file:`delete-${t}.command.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1257
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1258
|
+
import { ${e}Events } from '../events/${t}.events'
|
|
1877
1259
|
|
|
1878
1260
|
@Service()
|
|
1879
|
-
export class Delete${
|
|
1261
|
+
export class Delete${e}Command {
|
|
1880
1262
|
constructor(
|
|
1881
|
-
@Inject(${
|
|
1882
|
-
@Inject(${
|
|
1263
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1264
|
+
@Inject(${e}Events) private readonly events: ${e}Events,
|
|
1883
1265
|
) {}
|
|
1884
1266
|
|
|
1885
1267
|
async execute(id: string): Promise<void> {
|
|
1886
1268
|
await this.repo.delete(id)
|
|
1887
|
-
this.events.emit('${
|
|
1269
|
+
this.events.emit('${t}.deleted', { id })
|
|
1888
1270
|
}
|
|
1889
1271
|
}
|
|
1890
|
-
`
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
}
|
|
1894
|
-
__name(generateCqrsCommands, "generateCqrsCommands");
|
|
1895
|
-
function generateCqrsQueries(pascal, kebab, plural, pluralPascal) {
|
|
1896
|
-
return [
|
|
1897
|
-
{
|
|
1898
|
-
file: `get-${kebab}.query.ts`,
|
|
1899
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1900
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1901
|
-
import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
1272
|
+
`}]}s(he,"generateCqrsCommands");function we(e,t,o,r){return[{file:`get-${t}.query.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1273
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1274
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1902
1275
|
|
|
1903
1276
|
@Service()
|
|
1904
|
-
export class Get${
|
|
1277
|
+
export class Get${e}Query {
|
|
1905
1278
|
constructor(
|
|
1906
|
-
@Inject(${
|
|
1279
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1907
1280
|
) {}
|
|
1908
1281
|
|
|
1909
|
-
async execute(id: string): Promise<${
|
|
1282
|
+
async execute(id: string): Promise<${e}ResponseDTO | null> {
|
|
1910
1283
|
return this.repo.findById(id)
|
|
1911
1284
|
}
|
|
1912
1285
|
}
|
|
1913
|
-
`
|
|
1914
|
-
|
|
1915
|
-
{
|
|
1916
|
-
file: `list-${plural}.query.ts`,
|
|
1917
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1918
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../${kebab}.repository'
|
|
1286
|
+
`},{file:`list-${o}.query.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1287
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../${t}.repository'
|
|
1919
1288
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
1920
1289
|
|
|
1921
1290
|
@Service()
|
|
1922
|
-
export class List${
|
|
1291
|
+
export class List${r}Query {
|
|
1923
1292
|
constructor(
|
|
1924
|
-
@Inject(${
|
|
1293
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
1925
1294
|
) {}
|
|
1926
1295
|
|
|
1927
1296
|
async execute(parsed: ParsedQuery) {
|
|
1928
1297
|
return this.repo.findPaginated(parsed)
|
|
1929
1298
|
}
|
|
1930
1299
|
}
|
|
1931
|
-
`
|
|
1932
|
-
}
|
|
1933
|
-
];
|
|
1934
|
-
}
|
|
1935
|
-
__name(generateCqrsQueries, "generateCqrsQueries");
|
|
1936
|
-
function generateCqrsEvents(pascal, kebab) {
|
|
1937
|
-
return [
|
|
1938
|
-
{
|
|
1939
|
-
file: `${kebab}.events.ts`,
|
|
1940
|
-
content: `import { Service } from '@forinda/kickjs-core'
|
|
1300
|
+
`}]}s(we,"generateCqrsQueries");function ve(e,t){return[{file:`${t}.events.ts`,content:`import { Service } from '@forinda/kickjs-core'
|
|
1941
1301
|
import { EventEmitter } from 'node:events'
|
|
1942
|
-
import type { ${
|
|
1302
|
+
import type { ${e}ResponseDTO } from '../dtos/${t}-response.dto'
|
|
1943
1303
|
|
|
1944
1304
|
/**
|
|
1945
|
-
* ${
|
|
1305
|
+
* ${e} domain event types.
|
|
1946
1306
|
*
|
|
1947
1307
|
* These events are emitted by commands after state changes.
|
|
1948
1308
|
* Subscribe to them in event handlers for side effects:
|
|
@@ -1951,346 +1311,118 @@ import type { ${pascal}ResponseDTO } from '../dtos/${kebab}-response.dto'
|
|
|
1951
1311
|
* - Audit logging
|
|
1952
1312
|
* - Cache invalidation
|
|
1953
1313
|
*/
|
|
1954
|
-
export interface ${
|
|
1955
|
-
'${
|
|
1956
|
-
'${
|
|
1957
|
-
'${
|
|
1314
|
+
export interface ${e}EventMap {
|
|
1315
|
+
'${t}.created': ${e}ResponseDTO
|
|
1316
|
+
'${t}.updated': ${e}ResponseDTO
|
|
1317
|
+
'${t}.deleted': { id: string }
|
|
1958
1318
|
}
|
|
1959
1319
|
|
|
1960
1320
|
@Service()
|
|
1961
|
-
export class ${
|
|
1321
|
+
export class ${e}Events {
|
|
1962
1322
|
private emitter = new EventEmitter()
|
|
1963
1323
|
|
|
1964
|
-
emit<K extends keyof ${
|
|
1324
|
+
emit<K extends keyof ${e}EventMap>(event: K, data: ${e}EventMap[K]): void {
|
|
1965
1325
|
this.emitter.emit(event, data)
|
|
1966
1326
|
}
|
|
1967
1327
|
|
|
1968
|
-
on<K extends keyof ${
|
|
1328
|
+
on<K extends keyof ${e}EventMap>(event: K, handler: (data: ${e}EventMap[K]) => void): void {
|
|
1969
1329
|
this.emitter.on(event, handler)
|
|
1970
1330
|
}
|
|
1971
1331
|
|
|
1972
|
-
off<K extends keyof ${
|
|
1332
|
+
off<K extends keyof ${e}EventMap>(event: K, handler: (data: ${e}EventMap[K]) => void): void {
|
|
1973
1333
|
this.emitter.off(event, handler)
|
|
1974
1334
|
}
|
|
1975
1335
|
}
|
|
1976
|
-
`
|
|
1977
|
-
|
|
1978
|
-
{
|
|
1979
|
-
file: `on-${kebab}-change.handler.ts`,
|
|
1980
|
-
content: `import { Service, Autowired } from '@forinda/kickjs-core'
|
|
1981
|
-
import { ${pascal}Events } from './${kebab}.events'
|
|
1336
|
+
`},{file:`on-${t}-change.handler.ts`,content:`import { Service, Autowired } from '@forinda/kickjs-core'
|
|
1337
|
+
import { ${e}Events } from './${t}.events'
|
|
1982
1338
|
|
|
1983
1339
|
/**
|
|
1984
|
-
* ${
|
|
1340
|
+
* ${e} Change Event Handler
|
|
1985
1341
|
*
|
|
1986
1342
|
* Reacts to domain events emitted by commands.
|
|
1987
1343
|
* Wire up side effects here:
|
|
1988
1344
|
*
|
|
1989
1345
|
* 1. WebSocket broadcast \u2014 notify connected clients in real-time
|
|
1990
1346
|
* import { WsGateway } from '@forinda/kickjs-ws'
|
|
1991
|
-
* this.ws.broadcast('${
|
|
1347
|
+
* this.ws.broadcast('${t}-channel', { event, data })
|
|
1992
1348
|
*
|
|
1993
1349
|
* 2. Queue dispatch \u2014 offload heavy processing to background workers
|
|
1994
1350
|
* import { QueueService } from '@forinda/kickjs-queue'
|
|
1995
|
-
* this.queue.add('${
|
|
1351
|
+
* this.queue.add('${t}-etl', { action: event, payload: data })
|
|
1996
1352
|
*
|
|
1997
1353
|
* 3. ETL pipeline \u2014 transform and load data to external systems
|
|
1998
1354
|
* await this.etlPipeline.process(data)
|
|
1999
1355
|
*/
|
|
2000
1356
|
@Service()
|
|
2001
|
-
export class On${
|
|
2002
|
-
@Autowired() private events!: ${
|
|
1357
|
+
export class On${e}ChangeHandler {
|
|
1358
|
+
@Autowired() private events!: ${e}Events
|
|
2003
1359
|
|
|
2004
1360
|
// Uncomment to inject WebSocket and Queue services:
|
|
2005
1361
|
// @Autowired() private ws!: WsGateway
|
|
2006
1362
|
// @Autowired() private queue!: QueueService
|
|
2007
1363
|
|
|
2008
1364
|
onInit(): void {
|
|
2009
|
-
this.events.on('${
|
|
2010
|
-
console.log('[${
|
|
1365
|
+
this.events.on('${t}.created', (data) => {
|
|
1366
|
+
console.log('[${e}] Created:', data.id)
|
|
2011
1367
|
// TODO: Broadcast via WebSocket
|
|
2012
|
-
// this.ws.broadcast('${
|
|
1368
|
+
// this.ws.broadcast('${t}-channel', { event: '${t}.created', data })
|
|
2013
1369
|
// TODO: Dispatch to queue for async processing / ETL
|
|
2014
|
-
// this.queue.add('${
|
|
1370
|
+
// this.queue.add('${t}-etl', { action: 'create', payload: data })
|
|
2015
1371
|
})
|
|
2016
1372
|
|
|
2017
|
-
this.events.on('${
|
|
2018
|
-
console.log('[${
|
|
1373
|
+
this.events.on('${t}.updated', (data) => {
|
|
1374
|
+
console.log('[${e}] Updated:', data.id)
|
|
2019
1375
|
// TODO: Broadcast via WebSocket
|
|
2020
|
-
// this.ws.broadcast('${
|
|
1376
|
+
// this.ws.broadcast('${t}-channel', { event: '${t}.updated', data })
|
|
2021
1377
|
})
|
|
2022
1378
|
|
|
2023
|
-
this.events.on('${
|
|
2024
|
-
console.log('[${
|
|
1379
|
+
this.events.on('${t}.deleted', (data) => {
|
|
1380
|
+
console.log('[${e}] Deleted:', data.id)
|
|
2025
1381
|
// TODO: Broadcast via WebSocket
|
|
2026
|
-
// this.ws.broadcast('${
|
|
1382
|
+
// this.ws.broadcast('${t}-channel', { event: '${t}.deleted', data })
|
|
2027
1383
|
})
|
|
2028
1384
|
}
|
|
2029
1385
|
}
|
|
2030
|
-
`
|
|
2031
|
-
|
|
2032
|
-
];
|
|
2033
|
-
}
|
|
2034
|
-
__name(generateCqrsEvents, "generateCqrsEvents");
|
|
2035
|
-
|
|
2036
|
-
// src/generators/module.ts
|
|
2037
|
-
function promptUser(question) {
|
|
2038
|
-
const rl = createInterface2({
|
|
2039
|
-
input: process.stdin,
|
|
2040
|
-
output: process.stdout
|
|
2041
|
-
});
|
|
2042
|
-
return new Promise((resolve8) => {
|
|
2043
|
-
rl.question(question, (answer) => {
|
|
2044
|
-
rl.close();
|
|
2045
|
-
resolve8(answer.trim().toLowerCase());
|
|
2046
|
-
});
|
|
2047
|
-
});
|
|
2048
|
-
}
|
|
2049
|
-
__name(promptUser, "promptUser");
|
|
2050
|
-
async function generateModule(options) {
|
|
2051
|
-
const { name, modulesDir, noEntity, noTests, repo = "inmemory", force, dryRun } = options;
|
|
2052
|
-
let pattern = options.pattern ?? "ddd";
|
|
2053
|
-
if (options.minimal) pattern = "minimal";
|
|
2054
|
-
const kebab = toKebabCase(name);
|
|
2055
|
-
const pascal = toPascalCase(name);
|
|
2056
|
-
const plural = pluralize(kebab);
|
|
2057
|
-
const pluralPascal = pluralizePascal(pascal);
|
|
2058
|
-
const moduleDir = join2(modulesDir, plural);
|
|
2059
|
-
const files = [];
|
|
2060
|
-
let overwriteAll = force ?? false;
|
|
2061
|
-
const write = /* @__PURE__ */ __name(async (relativePath, content) => {
|
|
2062
|
-
const fullPath = join2(moduleDir, relativePath);
|
|
2063
|
-
if (dryRun) {
|
|
2064
|
-
files.push(fullPath);
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
2067
|
-
if (!overwriteAll && await fileExists(fullPath)) {
|
|
2068
|
-
const answer = await promptUser(` File already exists: ${relativePath}
|
|
2069
|
-
Overwrite? (y/n/a = yes/no/all) `);
|
|
2070
|
-
if (answer === "a") {
|
|
2071
|
-
overwriteAll = true;
|
|
2072
|
-
} else if (answer !== "y") {
|
|
2073
|
-
console.log(` Skipped: ${relativePath}`);
|
|
2074
|
-
return;
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
await writeFileSafe(fullPath, content);
|
|
2078
|
-
files.push(fullPath);
|
|
2079
|
-
}, "write");
|
|
2080
|
-
const ctx = {
|
|
2081
|
-
kebab,
|
|
2082
|
-
pascal,
|
|
2083
|
-
plural,
|
|
2084
|
-
pluralPascal,
|
|
2085
|
-
moduleDir,
|
|
2086
|
-
repo,
|
|
2087
|
-
noEntity: noEntity ?? false,
|
|
2088
|
-
noTests: noTests ?? false,
|
|
2089
|
-
write,
|
|
2090
|
-
files
|
|
2091
|
-
};
|
|
2092
|
-
switch (pattern) {
|
|
2093
|
-
case "minimal":
|
|
2094
|
-
await generateMinimalFiles(ctx);
|
|
2095
|
-
break;
|
|
2096
|
-
case "rest":
|
|
2097
|
-
await generateRestFiles(ctx);
|
|
2098
|
-
break;
|
|
2099
|
-
case "cqrs":
|
|
2100
|
-
await generateCqrsFiles(ctx);
|
|
2101
|
-
break;
|
|
2102
|
-
case "graphql":
|
|
2103
|
-
case "ddd":
|
|
2104
|
-
default:
|
|
2105
|
-
await generateDddFiles(ctx);
|
|
2106
|
-
break;
|
|
2107
|
-
}
|
|
2108
|
-
if (!dryRun) {
|
|
2109
|
-
await autoRegisterModule(modulesDir, pascal, plural);
|
|
2110
|
-
}
|
|
2111
|
-
return files;
|
|
2112
|
-
}
|
|
2113
|
-
__name(generateModule, "generateModule");
|
|
2114
|
-
async function generateMinimalFiles(ctx) {
|
|
2115
|
-
const { pascal, kebab, plural, write } = ctx;
|
|
2116
|
-
await write("index.ts", generateMinimalModuleIndex(pascal, kebab, plural));
|
|
2117
|
-
await write(`${kebab}.controller.ts`, `import { Controller, Get } from '@forinda/kickjs-core'
|
|
1386
|
+
`}]}s(ve,"generateCqrsEvents");function Ut(e){let t=Pt({input:process.stdin,output:process.stdout});return new Promise(o=>{t.question(e,r=>{t.close(),o(r.trim().toLowerCase())})})}s(Ut,"promptUser");async function Ae(e){let{name:t,modulesDir:o,noEntity:r,noTests:n,repo:i="inmemory",force:d,dryRun:c}=e,a=e.pattern??"ddd";e.minimal&&(a="minimal");let p=$(t),m=f(t),u=j(p),w=X(m),g=Ce(o,u),y=[],W=d??!1,A={kebab:p,pascal:m,plural:u,pluralPascal:w,moduleDir:g,repo:i,noEntity:r??!1,noTests:n??!1,write:s(async(U,ft)=>{let J=Ce(g,U);if(c){y.push(J);return}if(!W&&await _(J)){let Ie=await Ut(` File already exists: ${U}
|
|
1387
|
+
Overwrite? (y/n/a = yes/no/all) `);if(Ie==="a")W=!0;else if(Ie!=="y"){console.log(` Skipped: ${U}`);return}}await l(J,ft),y.push(J)},"write"),files:y};switch(a){case"minimal":await zt(A);break;case"rest":await qt(A);break;case"cqrs":await _t(A);break;default:await Mt(A);break}return c||await bt(o,m,u),y}s(Ae,"generateModule");async function zt(e){let{pascal:t,kebab:o,plural:r,write:n}=e;await n("index.ts",se(t,o,r)),await n(`${o}.controller.ts`,`import { Controller, Get } from '@forinda/kickjs-core'
|
|
2118
1388
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
2119
1389
|
|
|
2120
1390
|
@Controller()
|
|
2121
|
-
export class ${
|
|
1391
|
+
export class ${t}Controller {
|
|
2122
1392
|
@Get('/')
|
|
2123
1393
|
async list(ctx: RequestContext) {
|
|
2124
|
-
ctx.json({ message: '${
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
`);
|
|
2128
|
-
}
|
|
2129
|
-
__name(generateMinimalFiles, "generateMinimalFiles");
|
|
2130
|
-
async function generateRestFiles(ctx) {
|
|
2131
|
-
const { pascal, kebab, plural, pluralPascal, repo, noTests, write } = ctx;
|
|
2132
|
-
await write("index.ts", generateRestModuleIndex(pascal, kebab, plural, repo));
|
|
2133
|
-
await write(`${kebab}.constants.ts`, generateRestConstants(pascal));
|
|
2134
|
-
await write(`${kebab}.controller.ts`, generateRestController(pascal, kebab, plural, pluralPascal));
|
|
2135
|
-
await write(`${kebab}.service.ts`, generateRestService(pascal, kebab));
|
|
2136
|
-
await write(`dtos/create-${kebab}.dto.ts`, generateCreateDTO(pascal, kebab));
|
|
2137
|
-
await write(`dtos/update-${kebab}.dto.ts`, generateUpdateDTO(pascal, kebab));
|
|
2138
|
-
await write(`dtos/${kebab}-response.dto.ts`, generateResponseDTO(pascal, kebab));
|
|
2139
|
-
await write(`${kebab}.repository.ts`, generateRepositoryInterface(pascal, kebab, "./dtos"));
|
|
2140
|
-
const repoFileMap = {
|
|
2141
|
-
inmemory: `in-memory-${kebab}`,
|
|
2142
|
-
drizzle: `drizzle-${kebab}`,
|
|
2143
|
-
prisma: `prisma-${kebab}`
|
|
2144
|
-
};
|
|
2145
|
-
const repoGeneratorMap = {
|
|
2146
|
-
inmemory: /* @__PURE__ */ __name(() => generateInMemoryRepository(pascal, kebab, ".", "./dtos"), "inmemory"),
|
|
2147
|
-
drizzle: /* @__PURE__ */ __name(() => generateDrizzleRepository(pascal, kebab, ".", "./dtos"), "drizzle"),
|
|
2148
|
-
prisma: /* @__PURE__ */ __name(() => generatePrismaRepository(pascal, kebab, ".", "./dtos"), "prisma")
|
|
2149
|
-
};
|
|
2150
|
-
await write(`${repoFileMap[repo]}.repository.ts`, repoGeneratorMap[repo]());
|
|
2151
|
-
if (!noTests) {
|
|
2152
|
-
await write(`__tests__/${kebab}.controller.test.ts`, generateControllerTest(pascal, kebab, plural));
|
|
2153
|
-
await write(`__tests__/${kebab}.repository.test.ts`, generateRepositoryTest(pascal, kebab, plural, `../${repoFileMap.inmemory}.repository`));
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
__name(generateRestFiles, "generateRestFiles");
|
|
2157
|
-
async function generateCqrsFiles(ctx) {
|
|
2158
|
-
const { pascal, kebab, plural, pluralPascal, repo, noTests, write } = ctx;
|
|
2159
|
-
await write("index.ts", generateCqrsModuleIndex(pascal, kebab, plural, repo));
|
|
2160
|
-
await write(`${kebab}.constants.ts`, generateRestConstants(pascal));
|
|
2161
|
-
await write(`${kebab}.controller.ts`, generateCqrsController(pascal, kebab, plural, pluralPascal));
|
|
2162
|
-
await write(`dtos/create-${kebab}.dto.ts`, generateCreateDTO(pascal, kebab));
|
|
2163
|
-
await write(`dtos/update-${kebab}.dto.ts`, generateUpdateDTO(pascal, kebab));
|
|
2164
|
-
await write(`dtos/${kebab}-response.dto.ts`, generateResponseDTO(pascal, kebab));
|
|
2165
|
-
const commands = generateCqrsCommands(pascal, kebab);
|
|
2166
|
-
for (const cmd of commands) {
|
|
2167
|
-
await write(`commands/${cmd.file}`, cmd.content);
|
|
2168
|
-
}
|
|
2169
|
-
const queries = generateCqrsQueries(pascal, kebab, plural, pluralPascal);
|
|
2170
|
-
for (const q of queries) {
|
|
2171
|
-
await write(`queries/${q.file}`, q.content);
|
|
2172
|
-
}
|
|
2173
|
-
const events = generateCqrsEvents(pascal, kebab);
|
|
2174
|
-
for (const e of events) {
|
|
2175
|
-
await write(`events/${e.file}`, e.content);
|
|
2176
|
-
}
|
|
2177
|
-
await write(`${kebab}.repository.ts`, generateRepositoryInterface(pascal, kebab, "./dtos"));
|
|
2178
|
-
const repoFileMap = {
|
|
2179
|
-
inmemory: `in-memory-${kebab}`,
|
|
2180
|
-
drizzle: `drizzle-${kebab}`,
|
|
2181
|
-
prisma: `prisma-${kebab}`
|
|
2182
|
-
};
|
|
2183
|
-
const repoGeneratorMap = {
|
|
2184
|
-
inmemory: /* @__PURE__ */ __name(() => generateInMemoryRepository(pascal, kebab, ".", "./dtos"), "inmemory"),
|
|
2185
|
-
drizzle: /* @__PURE__ */ __name(() => generateDrizzleRepository(pascal, kebab, ".", "./dtos"), "drizzle"),
|
|
2186
|
-
prisma: /* @__PURE__ */ __name(() => generatePrismaRepository(pascal, kebab, ".", "./dtos"), "prisma")
|
|
2187
|
-
};
|
|
2188
|
-
await write(`${repoFileMap[repo]}.repository.ts`, repoGeneratorMap[repo]());
|
|
2189
|
-
if (!noTests) {
|
|
2190
|
-
await write(`__tests__/${kebab}.controller.test.ts`, generateControllerTest(pascal, kebab, plural));
|
|
2191
|
-
await write(`__tests__/${kebab}.repository.test.ts`, generateRepositoryTest(pascal, kebab, plural, `../${repoFileMap.inmemory}.repository`));
|
|
1394
|
+
ctx.json({ message: '${t} list' })
|
|
2192
1395
|
}
|
|
2193
1396
|
}
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
await write(`application/dtos/${kebab}-response.dto.ts`, generateResponseDTO(pascal, kebab));
|
|
2203
|
-
const useCases = generateUseCases(pascal, kebab, plural, pluralPascal);
|
|
2204
|
-
for (const uc of useCases) {
|
|
2205
|
-
await write(`application/use-cases/${uc.file}`, uc.content);
|
|
2206
|
-
}
|
|
2207
|
-
await write(`domain/repositories/${kebab}.repository.ts`, generateRepositoryInterface(pascal, kebab));
|
|
2208
|
-
await write(`domain/services/${kebab}-domain.service.ts`, generateDomainService(pascal, kebab));
|
|
2209
|
-
const repoFileMap = {
|
|
2210
|
-
inmemory: `in-memory-${kebab}`,
|
|
2211
|
-
drizzle: `drizzle-${kebab}`,
|
|
2212
|
-
prisma: `prisma-${kebab}`
|
|
2213
|
-
};
|
|
2214
|
-
const repoGeneratorMap = {
|
|
2215
|
-
inmemory: /* @__PURE__ */ __name(() => generateInMemoryRepository(pascal, kebab), "inmemory"),
|
|
2216
|
-
drizzle: /* @__PURE__ */ __name(() => generateDrizzleRepository(pascal, kebab), "drizzle"),
|
|
2217
|
-
prisma: /* @__PURE__ */ __name(() => generatePrismaRepository(pascal, kebab), "prisma")
|
|
2218
|
-
};
|
|
2219
|
-
await write(`infrastructure/repositories/${repoFileMap[repo]}.repository.ts`, repoGeneratorMap[repo]());
|
|
2220
|
-
if (!noEntity) {
|
|
2221
|
-
await write(`domain/entities/${kebab}.entity.ts`, generateEntity(pascal, kebab));
|
|
2222
|
-
await write(`domain/value-objects/${kebab}-id.vo.ts`, generateValueObject(pascal, kebab));
|
|
2223
|
-
}
|
|
2224
|
-
if (!noTests) {
|
|
2225
|
-
await write(`__tests__/${kebab}.controller.test.ts`, generateControllerTest(pascal, kebab, plural));
|
|
2226
|
-
await write(`__tests__/${kebab}.repository.test.ts`, generateRepositoryTest(pascal, kebab, plural));
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
__name(generateDddFiles, "generateDddFiles");
|
|
2230
|
-
async function autoRegisterModule(modulesDir, pascal, plural) {
|
|
2231
|
-
const indexPath = join2(modulesDir, "index.ts");
|
|
2232
|
-
const exists = await fileExists(indexPath);
|
|
2233
|
-
if (!exists) {
|
|
2234
|
-
await writeFileSafe(indexPath, `import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
2235
|
-
import { ${pascal}Module } from './${plural}'
|
|
2236
|
-
|
|
2237
|
-
export const modules: AppModuleClass[] = [${pascal}Module]
|
|
2238
|
-
`);
|
|
2239
|
-
return;
|
|
2240
|
-
}
|
|
2241
|
-
let content = await readFile2(indexPath, "utf-8");
|
|
2242
|
-
const importLine = `import { ${pascal}Module } from './${plural}'`;
|
|
2243
|
-
if (!content.includes(`${pascal}Module`)) {
|
|
2244
|
-
const lastImportIdx = content.lastIndexOf("import ");
|
|
2245
|
-
if (lastImportIdx !== -1) {
|
|
2246
|
-
const lineEnd = content.indexOf("\n", lastImportIdx);
|
|
2247
|
-
content = content.slice(0, lineEnd + 1) + importLine + "\n" + content.slice(lineEnd + 1);
|
|
2248
|
-
} else {
|
|
2249
|
-
content = importLine + "\n" + content;
|
|
2250
|
-
}
|
|
2251
|
-
content = content.replace(/(=\s*\[)([\s\S]*?)(])/, (_match, open, existing, close) => {
|
|
2252
|
-
const trimmed = existing.trim();
|
|
2253
|
-
if (!trimmed) {
|
|
2254
|
-
return `${open}${pascal}Module${close}`;
|
|
2255
|
-
}
|
|
2256
|
-
const needsComma = trimmed.endsWith(",") ? "" : ",";
|
|
2257
|
-
return `${open}${existing.trimEnd()}${needsComma} ${pascal}Module${close}`;
|
|
2258
|
-
});
|
|
2259
|
-
}
|
|
2260
|
-
await writeFile2(indexPath, content, "utf-8");
|
|
2261
|
-
}
|
|
2262
|
-
__name(autoRegisterModule, "autoRegisterModule");
|
|
2263
|
-
|
|
2264
|
-
// src/generators/adapter.ts
|
|
2265
|
-
import { join as join3 } from "path";
|
|
2266
|
-
async function generateAdapter(options) {
|
|
2267
|
-
const { name, outDir } = options;
|
|
2268
|
-
const kebab = toKebabCase(name);
|
|
2269
|
-
const pascal = toPascalCase(name);
|
|
2270
|
-
const files = [];
|
|
2271
|
-
const filePath = join3(outDir, `${kebab}.adapter.ts`);
|
|
2272
|
-
await writeFileSafe(filePath, `import type { Express } from 'express'
|
|
1397
|
+
`)}s(zt,"generateMinimalFiles");async function qt(e){let{pascal:t,kebab:o,plural:r,pluralPascal:n,repo:i,noTests:d,write:c}=e;await c("index.ts",ne(t,o,r,i)),await c(`${o}.constants.ts`,ee(t)),await c(`${o}.controller.ts`,de(t,o,r,n)),await c(`${o}.service.ts`,$e(t,o)),await c(`dtos/create-${o}.dto.ts`,M(t,o)),await c(`dtos/update-${o}.dto.ts`,b(t,o)),await c(`dtos/${o}-response.dto.ts`,Q(t,o)),await c(`${o}.repository.ts`,G(t,o,"./dtos"));let a={inmemory:`in-memory-${o}`,drizzle:`drizzle-${o}`,prisma:`prisma-${o}`},p={inmemory:s(()=>F(t,o,".","./dtos"),"inmemory"),drizzle:s(()=>L(t,o,".","./dtos"),"drizzle"),prisma:s(()=>N(t,o,".","./dtos"),"prisma")};await c(`${a[i]}.repository.ts`,p[i]()),d||(await c(`__tests__/${o}.controller.test.ts`,Y(t,o,r)),await c(`__tests__/${o}.repository.test.ts`,B(t,o,r,`../${a.inmemory}.repository`)))}s(qt,"generateRestFiles");async function _t(e){let{pascal:t,kebab:o,plural:r,pluralPascal:n,repo:i,noTests:d,write:c}=e;await c("index.ts",ge(t,o,r,i)),await c(`${o}.constants.ts`,ee(t)),await c(`${o}.controller.ts`,ye(t,o,r,n)),await c(`dtos/create-${o}.dto.ts`,M(t,o)),await c(`dtos/update-${o}.dto.ts`,b(t,o)),await c(`dtos/${o}-response.dto.ts`,Q(t,o));let a=he(t,o);for(let g of a)await c(`commands/${g.file}`,g.content);let p=we(t,o,r,n);for(let g of p)await c(`queries/${g.file}`,g.content);let m=ve(t,o);for(let g of m)await c(`events/${g.file}`,g.content);await c(`${o}.repository.ts`,G(t,o,"./dtos"));let u={inmemory:`in-memory-${o}`,drizzle:`drizzle-${o}`,prisma:`prisma-${o}`},w={inmemory:s(()=>F(t,o,".","./dtos"),"inmemory"),drizzle:s(()=>L(t,o,".","./dtos"),"drizzle"),prisma:s(()=>N(t,o,".","./dtos"),"prisma")};await c(`${u[i]}.repository.ts`,w[i]()),d||(await c(`__tests__/${o}.controller.test.ts`,Y(t,o,r)),await c(`__tests__/${o}.repository.test.ts`,B(t,o,r,`../${u.inmemory}.repository`)))}s(_t,"generateCqrsFiles");async function Mt(e){let{pascal:t,kebab:o,plural:r,pluralPascal:n,repo:i,noEntity:d,noTests:c,write:a}=e;await a("index.ts",ie(t,o,r,i)),await a("constants.ts",i==="drizzle"?pe(t,o):ce(t)),await a(`presentation/${o}.controller.ts`,ae(t,o,r,n)),await a(`application/dtos/create-${o}.dto.ts`,M(t,o)),await a(`application/dtos/update-${o}.dto.ts`,b(t,o)),await a(`application/dtos/${o}-response.dto.ts`,Q(t,o));let p=me(t,o,r,n);for(let w of p)await a(`application/use-cases/${w.file}`,w.content);await a(`domain/repositories/${o}.repository.ts`,G(t,o)),await a(`domain/services/${o}-domain.service.ts`,le(t,o));let m={inmemory:`in-memory-${o}`,drizzle:`drizzle-${o}`,prisma:`prisma-${o}`},u={inmemory:s(()=>F(t,o),"inmemory"),drizzle:s(()=>L(t,o),"drizzle"),prisma:s(()=>N(t,o),"prisma")};await a(`infrastructure/repositories/${m[i]}.repository.ts`,u[i]()),d||(await a(`domain/entities/${o}.entity.ts`,ue(t,o)),await a(`domain/value-objects/${o}-id.vo.ts`,fe(t,o))),c||(await a(`__tests__/${o}.controller.test.ts`,Y(t,o,r)),await a(`__tests__/${o}.repository.test.ts`,B(t,o,r)))}s(Mt,"generateDddFiles");async function bt(e,t,o){let r=Ce(e,"index.ts");if(!await _(r)){await l(r,`import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
1398
|
+
import { ${t}Module } from './${o}'
|
|
1399
|
+
|
|
1400
|
+
export const modules: AppModuleClass[] = [${t}Module]
|
|
1401
|
+
`);return}let i=await Et(r,"utf-8"),d=`import { ${t}Module } from './${o}'`;if(!i.includes(`${t}Module`)){let c=i.lastIndexOf("import ");if(c!==-1){let a=i.indexOf(`
|
|
1402
|
+
`,c);i=i.slice(0,a+1)+d+`
|
|
1403
|
+
`+i.slice(a+1)}else i=d+`
|
|
1404
|
+
`+i;i=i.replace(/(=\s*\[)([\s\S]*?)(])/,(a,p,m,u)=>{let w=m.trim();if(!w)return`${p}${t}Module${u}`;let g=w.endsWith(",")?"":",";return`${p}${m.trimEnd()}${g} ${t}Module${u}`})}await At(r,i,"utf-8")}s(bt,"autoRegisterModule");import{join as Qt}from"path";async function Ue(e){let{name:t,outDir:o}=e,r=$(t),n=f(t),i=[],d=Qt(o,`${r}.adapter.ts`);return await l(d,`import type { Express } from 'express'
|
|
2273
1405
|
import type { AppAdapter, AdapterMiddleware, Container } from '@forinda/kickjs-core'
|
|
2274
1406
|
|
|
2275
|
-
export interface ${
|
|
1407
|
+
export interface ${n}AdapterOptions {
|
|
2276
1408
|
// Add your adapter configuration here
|
|
2277
1409
|
}
|
|
2278
1410
|
|
|
2279
1411
|
/**
|
|
2280
|
-
* ${
|
|
1412
|
+
* ${n} adapter.
|
|
2281
1413
|
*
|
|
2282
1414
|
* Hooks into the Application lifecycle to add middleware, routes,
|
|
2283
1415
|
* or external service connections.
|
|
2284
1416
|
*
|
|
2285
1417
|
* Usage:
|
|
2286
1418
|
* bootstrap({
|
|
2287
|
-
* adapters: [new ${
|
|
1419
|
+
* adapters: [new ${n}Adapter({ ... })],
|
|
2288
1420
|
* })
|
|
2289
1421
|
*/
|
|
2290
|
-
export class ${
|
|
2291
|
-
name = '${
|
|
1422
|
+
export class ${n}Adapter implements AppAdapter {
|
|
1423
|
+
name = '${n}Adapter'
|
|
2292
1424
|
|
|
2293
|
-
constructor(private options: ${
|
|
1425
|
+
constructor(private options: ${n}AdapterOptions = {}) {}
|
|
2294
1426
|
|
|
2295
1427
|
/**
|
|
2296
1428
|
* Return middleware entries that the Application will mount.
|
|
@@ -2303,7 +1435,7 @@ export class ${pascal}Adapter implements AppAdapter {
|
|
|
2303
1435
|
// {
|
|
2304
1436
|
// phase: 'beforeGlobal',
|
|
2305
1437
|
// handler: (_req: any, res: any, next: any) => {
|
|
2306
|
-
// res.setHeader('X-${
|
|
1438
|
+
// res.setHeader('X-${n}', 'true')
|
|
2307
1439
|
// next()
|
|
2308
1440
|
// },
|
|
2309
1441
|
// },
|
|
@@ -2323,7 +1455,7 @@ export class ${pascal}Adapter implements AppAdapter {
|
|
|
2323
1455
|
*/
|
|
2324
1456
|
beforeMount(app: Express, container: Container): void {
|
|
2325
1457
|
// Example: mount a status route
|
|
2326
|
-
// app.get('/${
|
|
1458
|
+
// app.get('/${r}/status', (_req, res) => {
|
|
2327
1459
|
// res.json({ status: 'ok' })
|
|
2328
1460
|
// })
|
|
2329
1461
|
}
|
|
@@ -2355,133 +1487,45 @@ export class ${pascal}Adapter implements AppAdapter {
|
|
|
2355
1487
|
// await this.pool.end()
|
|
2356
1488
|
}
|
|
2357
1489
|
}
|
|
2358
|
-
`);
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
}
|
|
2362
|
-
__name(generateAdapter, "generateAdapter");
|
|
2363
|
-
|
|
2364
|
-
// src/generators/middleware.ts
|
|
2365
|
-
import { join as join5 } from "path";
|
|
2366
|
-
|
|
2367
|
-
// src/utils/resolve-out-dir.ts
|
|
2368
|
-
import { resolve as resolve2, join as join4 } from "path";
|
|
2369
|
-
var DDD_FOLDER_MAP = {
|
|
2370
|
-
controller: "presentation",
|
|
2371
|
-
service: "domain/services",
|
|
2372
|
-
dto: "application/dtos",
|
|
2373
|
-
guard: "presentation/guards",
|
|
2374
|
-
middleware: "middleware"
|
|
2375
|
-
};
|
|
2376
|
-
var FLAT_FOLDER_MAP = {
|
|
2377
|
-
controller: "",
|
|
2378
|
-
service: "",
|
|
2379
|
-
dto: "dtos",
|
|
2380
|
-
guard: "guards",
|
|
2381
|
-
middleware: "middleware"
|
|
2382
|
-
};
|
|
2383
|
-
var CQRS_FOLDER_MAP = {
|
|
2384
|
-
controller: "",
|
|
2385
|
-
service: "",
|
|
2386
|
-
dto: "dtos",
|
|
2387
|
-
guard: "guards",
|
|
2388
|
-
middleware: "middleware",
|
|
2389
|
-
command: "commands",
|
|
2390
|
-
query: "queries",
|
|
2391
|
-
event: "events"
|
|
2392
|
-
};
|
|
2393
|
-
function resolveOutDir(options) {
|
|
2394
|
-
const { type, outDir, moduleName, modulesDir = "src/modules", defaultDir, pattern = "ddd" } = options;
|
|
2395
|
-
if (outDir) return resolve2(outDir);
|
|
2396
|
-
if (moduleName) {
|
|
2397
|
-
const folderMap = pattern === "ddd" ? DDD_FOLDER_MAP : pattern === "cqrs" ? CQRS_FOLDER_MAP : FLAT_FOLDER_MAP;
|
|
2398
|
-
const kebab = toKebabCase(moduleName);
|
|
2399
|
-
const plural = pluralize(kebab);
|
|
2400
|
-
const subfolder = folderMap[type] ?? "";
|
|
2401
|
-
const base = join4(modulesDir, plural);
|
|
2402
|
-
return resolve2(subfolder ? join4(base, subfolder) : base);
|
|
2403
|
-
}
|
|
2404
|
-
return resolve2(defaultDir);
|
|
2405
|
-
}
|
|
2406
|
-
__name(resolveOutDir, "resolveOutDir");
|
|
2407
|
-
|
|
2408
|
-
// src/generators/middleware.ts
|
|
2409
|
-
async function generateMiddleware(options) {
|
|
2410
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
2411
|
-
const outDir = resolveOutDir({
|
|
2412
|
-
type: "middleware",
|
|
2413
|
-
outDir: options.outDir,
|
|
2414
|
-
moduleName,
|
|
2415
|
-
modulesDir,
|
|
2416
|
-
defaultDir: "src/middleware",
|
|
2417
|
-
pattern
|
|
2418
|
-
});
|
|
2419
|
-
const kebab = toKebabCase(name);
|
|
2420
|
-
const camel = toCamelCase(name);
|
|
2421
|
-
const files = [];
|
|
2422
|
-
const filePath = join5(outDir, `${kebab}.middleware.ts`);
|
|
2423
|
-
await writeFileSafe(filePath, `import type { Request, Response, NextFunction } from 'express'
|
|
2424
|
-
|
|
2425
|
-
export interface ${toPascalCase(name)}Options {
|
|
1490
|
+
`),i.push(d),i}s(Ue,"generateAdapter");import{join as Nt}from"path";import{resolve as xe,join as ze}from"path";var Gt={controller:"presentation",service:"domain/services",dto:"application/dtos",guard:"presentation/guards",middleware:"middleware"},Ft={controller:"",service:"",dto:"dtos",guard:"guards",middleware:"middleware"},Lt={controller:"",service:"",dto:"dtos",guard:"guards",middleware:"middleware",command:"commands",query:"queries",event:"events"};function O(e){let{type:t,outDir:o,moduleName:r,modulesDir:n="src/modules",defaultDir:i,pattern:d="ddd"}=e;if(o)return xe(o);if(r){let c=d==="ddd"?Gt:d==="cqrs"?Lt:Ft,a=$(r),p=j(a),m=c[t]??"",u=ze(n,p);return xe(m?ze(u,m):u)}return xe(i)}s(O,"resolveOutDir");async function qe(e){let{name:t,moduleName:o,modulesDir:r,pattern:n}=e,i=O({type:"middleware",outDir:e.outDir,moduleName:o,modulesDir:r,defaultDir:"src/middleware",pattern:n}),d=$(t),c=x(t),a=[],p=Nt(i,`${d}.middleware.ts`);return await l(p,`import type { Request, Response, NextFunction } from 'express'
|
|
1491
|
+
|
|
1492
|
+
export interface ${f(t)}Options {
|
|
2426
1493
|
// Add configuration options here
|
|
2427
1494
|
}
|
|
2428
1495
|
|
|
2429
1496
|
/**
|
|
2430
|
-
* ${
|
|
1497
|
+
* ${f(t)} middleware.
|
|
2431
1498
|
*
|
|
2432
1499
|
* Usage in bootstrap:
|
|
2433
|
-
* middleware: [${
|
|
1500
|
+
* middleware: [${c}()]
|
|
2434
1501
|
*
|
|
2435
1502
|
* Usage with adapter:
|
|
2436
|
-
* middleware() { return [{ handler: ${
|
|
1503
|
+
* middleware() { return [{ handler: ${c}(), phase: 'afterGlobal' }] }
|
|
2437
1504
|
*
|
|
2438
1505
|
* Usage with @Middleware decorator:
|
|
2439
|
-
* @Middleware(${
|
|
1506
|
+
* @Middleware(${c}())
|
|
2440
1507
|
*/
|
|
2441
|
-
export function ${
|
|
1508
|
+
export function ${c}(options: ${f(t)}Options = {}) {
|
|
2442
1509
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
2443
1510
|
// Implement your middleware logic here
|
|
2444
1511
|
next()
|
|
2445
1512
|
}
|
|
2446
1513
|
}
|
|
2447
|
-
`);
|
|
2448
|
-
files.push(filePath);
|
|
2449
|
-
return files;
|
|
2450
|
-
}
|
|
2451
|
-
__name(generateMiddleware, "generateMiddleware");
|
|
2452
|
-
|
|
2453
|
-
// src/generators/guard.ts
|
|
2454
|
-
import { join as join6 } from "path";
|
|
2455
|
-
async function generateGuard(options) {
|
|
2456
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
2457
|
-
const outDir = resolveOutDir({
|
|
2458
|
-
type: "guard",
|
|
2459
|
-
outDir: options.outDir,
|
|
2460
|
-
moduleName,
|
|
2461
|
-
modulesDir,
|
|
2462
|
-
defaultDir: "src/guards",
|
|
2463
|
-
pattern
|
|
2464
|
-
});
|
|
2465
|
-
const kebab = toKebabCase(name);
|
|
2466
|
-
const camel = toCamelCase(name);
|
|
2467
|
-
const pascal = toPascalCase(name);
|
|
2468
|
-
const files = [];
|
|
2469
|
-
const filePath = join6(outDir, `${kebab}.guard.ts`);
|
|
2470
|
-
await writeFileSafe(filePath, `import { Container, HttpException } from '@forinda/kickjs-core'
|
|
1514
|
+
`),a.push(p),a}s(qe,"generateMiddleware");import{join as Yt}from"path";async function _e(e){let{name:t,moduleName:o,modulesDir:r,pattern:n}=e,i=O({type:"guard",outDir:e.outDir,moduleName:o,modulesDir:r,defaultDir:"src/guards",pattern:n}),d=$(t),c=x(t),a=f(t),p=[],m=Yt(i,`${d}.guard.ts`);return await l(m,`import { Container, HttpException } from '@forinda/kickjs-core'
|
|
2471
1515
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
2472
1516
|
|
|
2473
1517
|
/**
|
|
2474
|
-
* ${
|
|
1518
|
+
* ${a} guard.
|
|
2475
1519
|
*
|
|
2476
1520
|
* Guards protect routes by checking conditions before the handler runs.
|
|
2477
1521
|
* Return early with an error response to block access.
|
|
2478
1522
|
*
|
|
2479
1523
|
* Usage:
|
|
2480
|
-
* @Middleware(${
|
|
1524
|
+
* @Middleware(${c}Guard)
|
|
2481
1525
|
* @Get('/protected')
|
|
2482
1526
|
* async handler(ctx: RequestContext) { ... }
|
|
2483
1527
|
*/
|
|
2484
|
-
export async function ${
|
|
1528
|
+
export async function ${c}Guard(ctx: RequestContext, next: () => void): Promise<void> {
|
|
2485
1529
|
// Example: check for an authorization header
|
|
2486
1530
|
const header = ctx.headers.authorization
|
|
2487
1531
|
if (!header?.startsWith('Bearer ')) {
|
|
@@ -2503,146 +1547,46 @@ export async function ${camel}Guard(ctx: RequestContext, next: () => void): Prom
|
|
|
2503
1547
|
ctx.res.status(401).json({ message: 'Invalid or expired token' })
|
|
2504
1548
|
}
|
|
2505
1549
|
}
|
|
2506
|
-
`);
|
|
2507
|
-
files.push(filePath);
|
|
2508
|
-
return files;
|
|
2509
|
-
}
|
|
2510
|
-
__name(generateGuard, "generateGuard");
|
|
2511
|
-
|
|
2512
|
-
// src/generators/service.ts
|
|
2513
|
-
import { join as join7 } from "path";
|
|
2514
|
-
async function generateService(options) {
|
|
2515
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
2516
|
-
const outDir = resolveOutDir({
|
|
2517
|
-
type: "service",
|
|
2518
|
-
outDir: options.outDir,
|
|
2519
|
-
moduleName,
|
|
2520
|
-
modulesDir,
|
|
2521
|
-
defaultDir: "src/services",
|
|
2522
|
-
pattern
|
|
2523
|
-
});
|
|
2524
|
-
const kebab = toKebabCase(name);
|
|
2525
|
-
const pascal = toPascalCase(name);
|
|
2526
|
-
const files = [];
|
|
2527
|
-
const filePath = join7(outDir, `${kebab}.service.ts`);
|
|
2528
|
-
await writeFileSafe(filePath, `import { Service } from '@forinda/kickjs-core'
|
|
1550
|
+
`),p.push(m),p}s(_e,"generateGuard");import{join as Bt}from"path";async function Me(e){let{name:t,moduleName:o,modulesDir:r,pattern:n}=e,i=O({type:"service",outDir:e.outDir,moduleName:o,modulesDir:r,defaultDir:"src/services",pattern:n}),d=$(t),c=f(t),a=[],p=Bt(i,`${d}.service.ts`);return await l(p,`import { Service } from '@forinda/kickjs-core'
|
|
2529
1551
|
|
|
2530
1552
|
@Service()
|
|
2531
|
-
export class ${
|
|
1553
|
+
export class ${c}Service {
|
|
2532
1554
|
// Inject dependencies via constructor
|
|
2533
1555
|
// constructor(
|
|
2534
1556
|
// @Inject(MY_REPO) private readonly repo: IMyRepository,
|
|
2535
1557
|
// ) {}
|
|
2536
1558
|
}
|
|
2537
|
-
`);
|
|
2538
|
-
files.push(filePath);
|
|
2539
|
-
return files;
|
|
2540
|
-
}
|
|
2541
|
-
__name(generateService, "generateService");
|
|
2542
|
-
|
|
2543
|
-
// src/generators/controller.ts
|
|
2544
|
-
import { join as join8 } from "path";
|
|
2545
|
-
async function generateController2(options) {
|
|
2546
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
2547
|
-
const outDir = resolveOutDir({
|
|
2548
|
-
type: "controller",
|
|
2549
|
-
outDir: options.outDir,
|
|
2550
|
-
moduleName,
|
|
2551
|
-
modulesDir,
|
|
2552
|
-
defaultDir: "src/controllers",
|
|
2553
|
-
pattern
|
|
2554
|
-
});
|
|
2555
|
-
const kebab = toKebabCase(name);
|
|
2556
|
-
const pascal = toPascalCase(name);
|
|
2557
|
-
const files = [];
|
|
2558
|
-
const filePath = join8(outDir, `${kebab}.controller.ts`);
|
|
2559
|
-
await writeFileSafe(filePath, `import { Controller, Get, Post, Autowired } from '@forinda/kickjs-core'
|
|
1559
|
+
`),a.push(p),a}s(Me,"generateService");import{join as Ht}from"path";async function be(e){let{name:t,moduleName:o,modulesDir:r,pattern:n}=e,i=O({type:"controller",outDir:e.outDir,moduleName:o,modulesDir:r,defaultDir:"src/controllers",pattern:n}),d=$(t),c=f(t),a=[],p=Ht(i,`${d}.controller.ts`);return await l(p,`import { Controller, Get, Post, Autowired } from '@forinda/kickjs-core'
|
|
2560
1560
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
2561
1561
|
|
|
2562
1562
|
@Controller()
|
|
2563
|
-
export class ${
|
|
1563
|
+
export class ${c}Controller {
|
|
2564
1564
|
// @Autowired() private myService!: MyService
|
|
2565
1565
|
|
|
2566
1566
|
@Get('/')
|
|
2567
1567
|
async list(ctx: RequestContext) {
|
|
2568
|
-
ctx.json({ message: '${
|
|
1568
|
+
ctx.json({ message: '${c} list' })
|
|
2569
1569
|
}
|
|
2570
1570
|
|
|
2571
1571
|
@Post('/')
|
|
2572
1572
|
async create(ctx: RequestContext) {
|
|
2573
|
-
ctx.created({ message: '${
|
|
1573
|
+
ctx.created({ message: '${c} created', data: ctx.body })
|
|
2574
1574
|
}
|
|
2575
1575
|
}
|
|
2576
|
-
`);
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
}
|
|
2580
|
-
__name(generateController2, "generateController");
|
|
2581
|
-
|
|
2582
|
-
// src/generators/dto.ts
|
|
2583
|
-
import { join as join9 } from "path";
|
|
2584
|
-
async function generateDto(options) {
|
|
2585
|
-
const { name, moduleName, modulesDir, pattern } = options;
|
|
2586
|
-
const outDir = resolveOutDir({
|
|
2587
|
-
type: "dto",
|
|
2588
|
-
outDir: options.outDir,
|
|
2589
|
-
moduleName,
|
|
2590
|
-
modulesDir,
|
|
2591
|
-
defaultDir: "src/dtos",
|
|
2592
|
-
pattern
|
|
2593
|
-
});
|
|
2594
|
-
const kebab = toKebabCase(name);
|
|
2595
|
-
const pascal = toPascalCase(name);
|
|
2596
|
-
const camel = toCamelCase(name);
|
|
2597
|
-
const files = [];
|
|
2598
|
-
const filePath = join9(outDir, `${kebab}.dto.ts`);
|
|
2599
|
-
await writeFileSafe(filePath, `import { z } from 'zod'
|
|
2600
|
-
|
|
2601
|
-
export const ${camel}Schema = z.object({
|
|
1576
|
+
`),a.push(p),a}s(be,"generateController");import{join as Kt}from"path";async function Qe(e){let{name:t,moduleName:o,modulesDir:r,pattern:n}=e,i=O({type:"dto",outDir:e.outDir,moduleName:o,modulesDir:r,defaultDir:"src/dtos",pattern:n}),d=$(t),c=f(t),a=x(t),p=[],m=Kt(i,`${d}.dto.ts`);return await l(m,`import { z } from 'zod'
|
|
1577
|
+
|
|
1578
|
+
export const ${a}Schema = z.object({
|
|
2602
1579
|
// Define your schema fields here
|
|
2603
1580
|
name: z.string().min(1).max(200),
|
|
2604
1581
|
})
|
|
2605
1582
|
|
|
2606
|
-
export type ${
|
|
2607
|
-
`);
|
|
2608
|
-
|
|
2609
|
-
return files;
|
|
2610
|
-
}
|
|
2611
|
-
__name(generateDto, "generateDto");
|
|
2612
|
-
|
|
2613
|
-
// src/generators/config.ts
|
|
2614
|
-
import { join as join10 } from "path";
|
|
2615
|
-
import { existsSync as existsSync2 } from "fs";
|
|
2616
|
-
import { createInterface as createInterface3 } from "readline";
|
|
2617
|
-
async function confirm2(message) {
|
|
2618
|
-
const rl = createInterface3({
|
|
2619
|
-
input: process.stdin,
|
|
2620
|
-
output: process.stdout
|
|
2621
|
-
});
|
|
2622
|
-
return new Promise((resolve8) => {
|
|
2623
|
-
rl.question(` ${message} (y/N) `, (answer) => {
|
|
2624
|
-
rl.close();
|
|
2625
|
-
resolve8(answer.trim().toLowerCase() === "y");
|
|
2626
|
-
});
|
|
2627
|
-
});
|
|
2628
|
-
}
|
|
2629
|
-
__name(confirm2, "confirm");
|
|
2630
|
-
async function generateConfig(options) {
|
|
2631
|
-
const filePath = join10(options.outDir, "kick.config.ts");
|
|
2632
|
-
const modulesDir = options.modulesDir ?? "src/modules";
|
|
2633
|
-
const defaultRepo = options.defaultRepo ?? "inmemory";
|
|
2634
|
-
if (existsSync2(filePath) && !options.force) {
|
|
2635
|
-
const overwrite = await confirm2("kick.config.ts already exists. Overwrite?");
|
|
2636
|
-
if (!overwrite) {
|
|
2637
|
-
console.log("\n Skipped \u2014 existing kick.config.ts preserved.");
|
|
2638
|
-
return [];
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
await writeFileSafe(filePath, `import { defineConfig } from '@forinda/kickjs-cli'
|
|
1583
|
+
export type ${c}DTO = z.infer<typeof ${a}Schema>
|
|
1584
|
+
`),p.push(m),p}s(Qe,"generateDto");import{join as Wt}from"path";import{existsSync as Jt}from"fs";import{createInterface as Vt}from"readline";async function Zt(e){let t=Vt({input:process.stdin,output:process.stdout});return new Promise(o=>{t.question(` ${e} (y/N) `,r=>{t.close(),o(r.trim().toLowerCase()==="y")})})}s(Zt,"confirm");async function Ge(e){let t=Wt(e.outDir,"kick.config.ts"),o=e.modulesDir??"src/modules",r=e.defaultRepo??"inmemory";return Jt(t)&&!e.force&&!await Zt("kick.config.ts already exists. Overwrite?")?(console.log(`
|
|
1585
|
+
Skipped \u2014 existing kick.config.ts preserved.`),[]):(await l(t,`import { defineConfig } from '@forinda/kickjs-cli'
|
|
2642
1586
|
|
|
2643
1587
|
export default defineConfig({
|
|
2644
|
-
modulesDir: '${
|
|
2645
|
-
defaultRepo: '${
|
|
1588
|
+
modulesDir: '${o}',
|
|
1589
|
+
defaultRepo: '${r}',
|
|
2646
1590
|
|
|
2647
1591
|
commands: [
|
|
2648
1592
|
{
|
|
@@ -2668,31 +1612,11 @@ export default defineConfig({
|
|
|
2668
1612
|
},
|
|
2669
1613
|
],
|
|
2670
1614
|
})
|
|
2671
|
-
`);
|
|
2672
|
-
return [
|
|
2673
|
-
filePath
|
|
2674
|
-
];
|
|
2675
|
-
}
|
|
2676
|
-
__name(generateConfig, "generateConfig");
|
|
2677
|
-
|
|
2678
|
-
// src/generators/resolver.ts
|
|
2679
|
-
import { join as join11 } from "path";
|
|
2680
|
-
async function generateResolver(options) {
|
|
2681
|
-
const { name, outDir } = options;
|
|
2682
|
-
const pascal = toPascalCase(name);
|
|
2683
|
-
const kebab = toKebabCase(name);
|
|
2684
|
-
const camel = toCamelCase(name);
|
|
2685
|
-
const files = [];
|
|
2686
|
-
const write = /* @__PURE__ */ __name(async (relativePath, content) => {
|
|
2687
|
-
const fullPath = join11(outDir, relativePath);
|
|
2688
|
-
await writeFileSafe(fullPath, content);
|
|
2689
|
-
files.push(fullPath);
|
|
2690
|
-
}, "write");
|
|
2691
|
-
await write(`${kebab}.resolver.ts`, `import { Service } from '@forinda/kickjs-core'
|
|
1615
|
+
`),[t])}s(Ge,"generateConfig");import{join as Xt}from"path";async function Fe(e){let{name:t,outDir:o}=e,r=f(t),n=$(t),i=x(t),d=[],c=s(async(a,p)=>{let m=Xt(o,a);await l(m,p),d.push(m)},"write");return await c(`${n}.resolver.ts`,`import { Service } from '@forinda/kickjs-core'
|
|
2692
1616
|
import { Resolver, Query, Mutation, Arg } from '@forinda/kickjs-graphql'
|
|
2693
1617
|
|
|
2694
1618
|
/**
|
|
2695
|
-
* ${
|
|
1619
|
+
* ${r} GraphQL Resolver
|
|
2696
1620
|
*
|
|
2697
1621
|
* Decorators:
|
|
2698
1622
|
* @Resolver(typeName?) \u2014 marks this class as a GraphQL resolver
|
|
@@ -2701,35 +1625,35 @@ import { Resolver, Query, Mutation, Arg } from '@forinda/kickjs-graphql'
|
|
|
2701
1625
|
* @Arg(name, type?) \u2014 marks a method parameter as a GraphQL argument
|
|
2702
1626
|
*/
|
|
2703
1627
|
@Service()
|
|
2704
|
-
@Resolver('${
|
|
2705
|
-
export class ${
|
|
1628
|
+
@Resolver('${r}')
|
|
1629
|
+
export class ${r}Resolver {
|
|
2706
1630
|
private items: Array<{ id: string; name: string }> = []
|
|
2707
1631
|
|
|
2708
|
-
@Query('${
|
|
1632
|
+
@Query('${i}s', { returnType: '[${r}]', description: 'List all ${i}s' })
|
|
2709
1633
|
findAll() {
|
|
2710
1634
|
return this.items
|
|
2711
1635
|
}
|
|
2712
1636
|
|
|
2713
|
-
@Query('${
|
|
1637
|
+
@Query('${i}', { returnType: '${r}', description: 'Get a ${i} by ID' })
|
|
2714
1638
|
findById(@Arg('id', 'ID!') id: string) {
|
|
2715
1639
|
return this.items.find((item) => item.id === id) ?? null
|
|
2716
1640
|
}
|
|
2717
1641
|
|
|
2718
|
-
@Mutation('create${
|
|
1642
|
+
@Mutation('create${r}', { returnType: '${r}', description: 'Create a new ${i}' })
|
|
2719
1643
|
create(@Arg('name', 'String!') name: string) {
|
|
2720
1644
|
const item = { id: String(this.items.length + 1), name }
|
|
2721
1645
|
this.items.push(item)
|
|
2722
1646
|
return item
|
|
2723
1647
|
}
|
|
2724
1648
|
|
|
2725
|
-
@Mutation('update${
|
|
1649
|
+
@Mutation('update${r}', { returnType: '${r}', description: 'Update a ${i}' })
|
|
2726
1650
|
update(@Arg('id', 'ID!') id: string, @Arg('name', 'String!') name: string) {
|
|
2727
1651
|
const item = this.items.find((i) => i.id === id)
|
|
2728
1652
|
if (item) item.name = name
|
|
2729
1653
|
return item
|
|
2730
1654
|
}
|
|
2731
1655
|
|
|
2732
|
-
@Mutation('delete${
|
|
1656
|
+
@Mutation('delete${r}', { returnType: 'Boolean', description: 'Delete a ${i}' })
|
|
2733
1657
|
remove(@Arg('id', 'ID!') id: string) {
|
|
2734
1658
|
const idx = this.items.findIndex((i) => i.id === id)
|
|
2735
1659
|
if (idx === -1) return false
|
|
@@ -2737,41 +1661,21 @@ export class ${pascal}Resolver {
|
|
|
2737
1661
|
return true
|
|
2738
1662
|
}
|
|
2739
1663
|
}
|
|
2740
|
-
`)
|
|
2741
|
-
|
|
2742
|
-
* ${pascal} GraphQL type definitions.
|
|
1664
|
+
`),await c(`${n}.typedefs.ts`,`/**
|
|
1665
|
+
* ${r} GraphQL type definitions.
|
|
2743
1666
|
* Pass to GraphQLAdapter's typeDefs option to register custom types.
|
|
2744
1667
|
*/
|
|
2745
|
-
export const ${
|
|
2746
|
-
type ${
|
|
1668
|
+
export const ${i}TypeDefs = \`
|
|
1669
|
+
type ${r} {
|
|
2747
1670
|
id: ID!
|
|
2748
1671
|
name: String!
|
|
2749
1672
|
}
|
|
2750
1673
|
\`
|
|
2751
|
-
`);
|
|
2752
|
-
return files;
|
|
2753
|
-
}
|
|
2754
|
-
__name(generateResolver, "generateResolver");
|
|
2755
|
-
|
|
2756
|
-
// src/generators/job.ts
|
|
2757
|
-
import { join as join12 } from "path";
|
|
2758
|
-
async function generateJob(options) {
|
|
2759
|
-
const { name, outDir } = options;
|
|
2760
|
-
const pascal = toPascalCase(name);
|
|
2761
|
-
const kebab = toKebabCase(name);
|
|
2762
|
-
const camel = toCamelCase(name);
|
|
2763
|
-
const queueName = options.queue ?? `${kebab}-queue`;
|
|
2764
|
-
const files = [];
|
|
2765
|
-
const write = /* @__PURE__ */ __name(async (relativePath, content) => {
|
|
2766
|
-
const fullPath = join12(outDir, relativePath);
|
|
2767
|
-
await writeFileSafe(fullPath, content);
|
|
2768
|
-
files.push(fullPath);
|
|
2769
|
-
}, "write");
|
|
2770
|
-
await write(`${kebab}.job.ts`, `import { Inject } from '@forinda/kickjs-core'
|
|
1674
|
+
`),d}s(Fe,"generateResolver");import{join as eo}from"path";async function Le(e){let{name:t,outDir:o}=e,r=f(t),n=$(t),i=x(t),d=e.queue??`${n}-queue`,c=[];return await s(async(p,m)=>{let u=eo(o,p);await l(u,m),c.push(u)},"write")(`${n}.job.ts`,`import { Inject } from '@forinda/kickjs-core'
|
|
2771
1675
|
import { Job, Process, QUEUE_MANAGER, type QueueService } from '@forinda/kickjs-queue'
|
|
2772
1676
|
|
|
2773
1677
|
/**
|
|
2774
|
-
* ${
|
|
1678
|
+
* ${r} Job Processor
|
|
2775
1679
|
*
|
|
2776
1680
|
* Decorators:
|
|
2777
1681
|
* @Job(queueName) \u2014 marks this class as a job processor for a queue
|
|
@@ -2781,10 +1685,10 @@ import { Job, Process, QUEUE_MANAGER, type QueueService } from '@forinda/kickjs-
|
|
|
2781
1685
|
*
|
|
2782
1686
|
* To add jobs to this queue from a service or controller:
|
|
2783
1687
|
* @Inject(QUEUE_MANAGER) private queue: QueueService
|
|
2784
|
-
* await this.queue.add('${
|
|
1688
|
+
* await this.queue.add('${d}', '${i}', { ... })
|
|
2785
1689
|
*/
|
|
2786
|
-
@Job('${
|
|
2787
|
-
export class ${
|
|
1690
|
+
@Job('${d}')
|
|
1691
|
+
export class ${r}Job {
|
|
2788
1692
|
@Process()
|
|
2789
1693
|
async handle(job: { name: string; data: any; id?: string }) {
|
|
2790
1694
|
console.log(\`Processing \${job.name} (id: \${job.id})\`, job.data)
|
|
@@ -2794,239 +1698,74 @@ export class ${pascal}Job {
|
|
|
2794
1698
|
// await this.emailService.send(job.data.to, job.data.subject, job.data.body)
|
|
2795
1699
|
}
|
|
2796
1700
|
|
|
2797
|
-
@Process('${
|
|
1701
|
+
@Process('${i}.priority')
|
|
2798
1702
|
async handlePriority(job: { name: string; data: any; id?: string }) {
|
|
2799
1703
|
console.log(\`Priority job: \${job.name}\`, job.data)
|
|
2800
1704
|
// Handle high-priority variant of this job
|
|
2801
1705
|
}
|
|
2802
1706
|
}
|
|
2803
|
-
`);
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
// src/generators/scaffold.ts
|
|
2809
|
-
import { join as join13 } from "path";
|
|
2810
|
-
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
2811
|
-
var TYPE_MAP = {
|
|
2812
|
-
string: {
|
|
2813
|
-
ts: "string",
|
|
2814
|
-
zod: "z.string()"
|
|
2815
|
-
},
|
|
2816
|
-
text: {
|
|
2817
|
-
ts: "string",
|
|
2818
|
-
zod: "z.string()"
|
|
2819
|
-
},
|
|
2820
|
-
number: {
|
|
2821
|
-
ts: "number",
|
|
2822
|
-
zod: "z.number()"
|
|
2823
|
-
},
|
|
2824
|
-
int: {
|
|
2825
|
-
ts: "number",
|
|
2826
|
-
zod: "z.number().int()"
|
|
2827
|
-
},
|
|
2828
|
-
float: {
|
|
2829
|
-
ts: "number",
|
|
2830
|
-
zod: "z.number()"
|
|
2831
|
-
},
|
|
2832
|
-
boolean: {
|
|
2833
|
-
ts: "boolean",
|
|
2834
|
-
zod: "z.boolean()"
|
|
2835
|
-
},
|
|
2836
|
-
date: {
|
|
2837
|
-
ts: "string",
|
|
2838
|
-
zod: "z.string().datetime()"
|
|
2839
|
-
},
|
|
2840
|
-
email: {
|
|
2841
|
-
ts: "string",
|
|
2842
|
-
zod: "z.string().email()"
|
|
2843
|
-
},
|
|
2844
|
-
url: {
|
|
2845
|
-
ts: "string",
|
|
2846
|
-
zod: "z.string().url()"
|
|
2847
|
-
},
|
|
2848
|
-
uuid: {
|
|
2849
|
-
ts: "string",
|
|
2850
|
-
zod: "z.string().uuid()"
|
|
2851
|
-
},
|
|
2852
|
-
json: {
|
|
2853
|
-
ts: "any",
|
|
2854
|
-
zod: "z.any()"
|
|
2855
|
-
}
|
|
2856
|
-
};
|
|
2857
|
-
function parseFields(raw) {
|
|
2858
|
-
return raw.map((f) => {
|
|
2859
|
-
const colonIdx = f.indexOf(":");
|
|
2860
|
-
if (colonIdx === -1) {
|
|
2861
|
-
throw new Error(`Invalid field: "${f}". Use format: name:type (e.g. title:string)`);
|
|
2862
|
-
}
|
|
2863
|
-
const namePart = f.slice(0, colonIdx);
|
|
2864
|
-
const typePart = f.slice(colonIdx + 1);
|
|
2865
|
-
if (!namePart || !typePart) {
|
|
2866
|
-
throw new Error(`Invalid field: "${f}". Use format: name:type (e.g. title:string)`);
|
|
2867
|
-
}
|
|
2868
|
-
const optional = typePart.endsWith("?");
|
|
2869
|
-
const cleanType = optional ? typePart.slice(0, -1) : typePart;
|
|
2870
|
-
if (cleanType.startsWith("enum:")) {
|
|
2871
|
-
const values = cleanType.slice(5).split(",");
|
|
2872
|
-
return {
|
|
2873
|
-
name: namePart,
|
|
2874
|
-
type: "enum",
|
|
2875
|
-
tsType: values.map((v) => `'${v}'`).join(" | "),
|
|
2876
|
-
zodType: `z.enum([${values.map((v) => `'${v}'`).join(", ")}])`,
|
|
2877
|
-
optional
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
const mapped = TYPE_MAP[cleanType];
|
|
2881
|
-
if (!mapped) {
|
|
2882
|
-
const validTypes = [
|
|
2883
|
-
...Object.keys(TYPE_MAP),
|
|
2884
|
-
"enum:a,b,c"
|
|
2885
|
-
].join(", ");
|
|
2886
|
-
throw new Error(`Unknown field type: "${cleanType}". Valid types: ${validTypes}`);
|
|
2887
|
-
}
|
|
2888
|
-
return {
|
|
2889
|
-
name: namePart,
|
|
2890
|
-
type: cleanType,
|
|
2891
|
-
tsType: mapped.ts,
|
|
2892
|
-
zodType: mapped.zod,
|
|
2893
|
-
optional
|
|
2894
|
-
};
|
|
2895
|
-
});
|
|
2896
|
-
}
|
|
2897
|
-
__name(parseFields, "parseFields");
|
|
2898
|
-
async function generateScaffold(options) {
|
|
2899
|
-
const { name, fields, modulesDir, noEntity, noTests, repo = "inmemory" } = options;
|
|
2900
|
-
const kebab = toKebabCase(name);
|
|
2901
|
-
const pascal = toPascalCase(name);
|
|
2902
|
-
const camel = toCamelCase(name);
|
|
2903
|
-
const plural = pluralize(kebab);
|
|
2904
|
-
const pluralPascal = pluralizePascal(pascal);
|
|
2905
|
-
const moduleDir = join13(modulesDir, plural);
|
|
2906
|
-
const files = [];
|
|
2907
|
-
const write = /* @__PURE__ */ __name(async (relativePath, content) => {
|
|
2908
|
-
const fullPath = join13(moduleDir, relativePath);
|
|
2909
|
-
await writeFileSafe(fullPath, content);
|
|
2910
|
-
files.push(fullPath);
|
|
2911
|
-
}, "write");
|
|
2912
|
-
await write("index.ts", genModuleIndex(pascal, kebab, plural, repo));
|
|
2913
|
-
await write("constants.ts", genConstants(pascal, fields));
|
|
2914
|
-
await write(`presentation/${kebab}.controller.ts`, genController(pascal, kebab, plural, pluralPascal));
|
|
2915
|
-
await write(`application/dtos/create-${kebab}.dto.ts`, genCreateDTO(pascal, fields));
|
|
2916
|
-
await write(`application/dtos/update-${kebab}.dto.ts`, genUpdateDTO(pascal, fields));
|
|
2917
|
-
await write(`application/dtos/${kebab}-response.dto.ts`, genResponseDTO(pascal, fields));
|
|
2918
|
-
const useCases = genUseCases(pascal, kebab, plural, pluralPascal);
|
|
2919
|
-
for (const uc of useCases) {
|
|
2920
|
-
await write(`application/use-cases/${uc.file}`, uc.content);
|
|
2921
|
-
}
|
|
2922
|
-
await write(`domain/repositories/${kebab}.repository.ts`, genRepositoryInterface(pascal, kebab));
|
|
2923
|
-
await write(`domain/services/${kebab}-domain.service.ts`, genDomainService(pascal, kebab));
|
|
2924
|
-
if (repo === "inmemory") {
|
|
2925
|
-
await write(`infrastructure/repositories/in-memory-${kebab}.repository.ts`, genInMemoryRepository(pascal, kebab, fields));
|
|
2926
|
-
}
|
|
2927
|
-
if (!noEntity) {
|
|
2928
|
-
await write(`domain/entities/${kebab}.entity.ts`, genEntity(pascal, kebab, fields));
|
|
2929
|
-
await write(`domain/value-objects/${kebab}-id.vo.ts`, genValueObject(pascal));
|
|
2930
|
-
}
|
|
2931
|
-
await autoRegisterModule2(modulesDir, pascal, plural);
|
|
2932
|
-
return files;
|
|
2933
|
-
}
|
|
2934
|
-
__name(generateScaffold, "generateScaffold");
|
|
2935
|
-
function genCreateDTO(pascal, fields) {
|
|
2936
|
-
const zodFields = fields.map((f) => {
|
|
2937
|
-
const base = f.zodType;
|
|
2938
|
-
return ` ${f.name}: ${base}${f.optional ? ".optional()" : ""},`;
|
|
2939
|
-
}).join("\n");
|
|
2940
|
-
return `import { z } from 'zod'
|
|
2941
|
-
|
|
2942
|
-
export const create${pascal}Schema = z.object({
|
|
2943
|
-
${zodFields}
|
|
1707
|
+
`),c}s(Le,"generateJob");import{join as ke}from"path";import{readFile as to,writeFile as oo}from"fs/promises";var Ne={string:{ts:"string",zod:"z.string()"},text:{ts:"string",zod:"z.string()"},number:{ts:"number",zod:"z.number()"},int:{ts:"number",zod:"z.number().int()"},float:{ts:"number",zod:"z.number()"},boolean:{ts:"boolean",zod:"z.boolean()"},date:{ts:"string",zod:"z.string().datetime()"},email:{ts:"string",zod:"z.string().email()"},url:{ts:"string",zod:"z.string().url()"},uuid:{ts:"string",zod:"z.string().uuid()"},json:{ts:"any",zod:"z.any()"}};function Ye(e){return e.map(t=>{let o=t.indexOf(":");if(o===-1)throw new Error(`Invalid field: "${t}". Use format: name:type (e.g. title:string)`);let r=t.slice(0,o),n=t.slice(o+1);if(!r||!n)throw new Error(`Invalid field: "${t}". Use format: name:type (e.g. title:string)`);let i=n.endsWith("?"),d=i?n.slice(0,-1):n;if(d.startsWith("enum:")){let a=d.slice(5).split(",");return{name:r,type:"enum",tsType:a.map(p=>`'${p}'`).join(" | "),zodType:`z.enum([${a.map(p=>`'${p}'`).join(", ")}])`,optional:i}}let c=Ne[d];if(!c){let a=[...Object.keys(Ne),"enum:a,b,c"].join(", ");throw new Error(`Unknown field type: "${d}". Valid types: ${a}`)}return{name:r,type:d,tsType:c.ts,zodType:c.zod,optional:i}})}s(Ye,"parseFields");async function Be(e){let{name:t,fields:o,modulesDir:r,noEntity:n,noTests:i,repo:d="inmemory"}=e,c=$(t),a=f(t),p=x(t),m=j(c),u=X(a),w=ke(r,m),g=[],y=s(async(q,A)=>{let U=ke(w,q);await l(U,A),g.push(U)},"write");await y("index.ts",mo(a,c,m,d)),await y("constants.ts",so(a,o)),await y(`presentation/${c}.controller.ts`,lo(a,c,m,u)),await y(`application/dtos/create-${c}.dto.ts`,ro(a,o)),await y(`application/dtos/update-${c}.dto.ts`,io(a,o)),await y(`application/dtos/${c}-response.dto.ts`,no(a,o));let W=$o(a,c,m,u);for(let q of W)await y(`application/use-cases/${q.file}`,q.content);return await y(`domain/repositories/${c}.repository.ts`,uo(a,c)),await y(`domain/services/${c}-domain.service.ts`,fo(a,c)),d==="inmemory"&&await y(`infrastructure/repositories/in-memory-${c}.repository.ts`,ao(a,c,o)),n||(await y(`domain/entities/${c}.entity.ts`,co(a,c,o)),await y(`domain/value-objects/${c}-id.vo.ts`,po(a))),await go(r,a,m),g}s(Be,"generateScaffold");function ro(e,t){let o=t.map(r=>{let n=r.zodType;return` ${r.name}: ${n}${r.optional?".optional()":""},`}).join(`
|
|
1708
|
+
`);return`import { z } from 'zod'
|
|
1709
|
+
|
|
1710
|
+
export const create${e}Schema = z.object({
|
|
1711
|
+
${o}
|
|
2944
1712
|
})
|
|
2945
1713
|
|
|
2946
|
-
export type Create${
|
|
2947
|
-
|
|
2948
|
-
}
|
|
2949
|
-
__name(genCreateDTO, "genCreateDTO");
|
|
2950
|
-
function genUpdateDTO(pascal, fields) {
|
|
2951
|
-
const zodFields = fields.map((f) => ` ${f.name}: ${f.zodType}.optional(),`).join("\n");
|
|
2952
|
-
return `import { z } from 'zod'
|
|
1714
|
+
export type Create${e}DTO = z.infer<typeof create${e}Schema>
|
|
1715
|
+
`}s(ro,"genCreateDTO");function io(e,t){let o=t.map(r=>` ${r.name}: ${r.zodType}.optional(),`).join(`
|
|
1716
|
+
`);return`import { z } from 'zod'
|
|
2953
1717
|
|
|
2954
|
-
export const update${
|
|
2955
|
-
${
|
|
1718
|
+
export const update${e}Schema = z.object({
|
|
1719
|
+
${o}
|
|
2956
1720
|
})
|
|
2957
1721
|
|
|
2958
|
-
export type Update${
|
|
2959
|
-
|
|
2960
|
-
}
|
|
2961
|
-
__name(genUpdateDTO, "genUpdateDTO");
|
|
2962
|
-
function genResponseDTO(pascal, fields) {
|
|
2963
|
-
const tsFields = fields.map((f) => ` ${f.name}${f.optional ? "?" : ""}: ${f.tsType}`).join("\n");
|
|
2964
|
-
return `export interface ${pascal}ResponseDTO {
|
|
1722
|
+
export type Update${e}DTO = z.infer<typeof update${e}Schema>
|
|
1723
|
+
`}s(io,"genUpdateDTO");function no(e,t){let o=t.map(r=>` ${r.name}${r.optional?"?":""}: ${r.tsType}`).join(`
|
|
1724
|
+
`);return`export interface ${e}ResponseDTO {
|
|
2965
1725
|
id: string
|
|
2966
|
-
${
|
|
1726
|
+
${o}
|
|
2967
1727
|
createdAt: string
|
|
2968
1728
|
updatedAt: string
|
|
2969
1729
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
const allFieldNames = fields.map((f) => `'${f.name}'`);
|
|
2977
|
-
const filterable = [
|
|
2978
|
-
...allFieldNames
|
|
2979
|
-
].join(", ");
|
|
2980
|
-
const sortable = [
|
|
2981
|
-
...allFieldNames,
|
|
2982
|
-
"'createdAt'",
|
|
2983
|
-
"'updatedAt'"
|
|
2984
|
-
].join(", ");
|
|
2985
|
-
const searchable = stringFields.length > 0 ? stringFields.join(", ") : "'name'";
|
|
2986
|
-
return `import type { ApiQueryParamsConfig } from '@forinda/kickjs-core'
|
|
2987
|
-
|
|
2988
|
-
export const ${pascal.toUpperCase()}_QUERY_CONFIG: ApiQueryParamsConfig = {
|
|
2989
|
-
filterable: [${filterable}],
|
|
2990
|
-
sortable: [${sortable}],
|
|
2991
|
-
searchable: [${searchable}],
|
|
2992
|
-
}
|
|
2993
|
-
`;
|
|
1730
|
+
`}s(no,"genResponseDTO");function so(e,t){let o=t.filter(a=>a.tsType==="string").map(a=>`'${a.name}'`),r=t.filter(a=>a.tsType==="number").map(a=>`'${a.name}'`),n=t.map(a=>`'${a.name}'`),i=[...n].join(", "),d=[...n,"'createdAt'","'updatedAt'"].join(", "),c=o.length>0?o.join(", "):"'name'";return`import type { ApiQueryParamsConfig } from '@forinda/kickjs-core'
|
|
1731
|
+
|
|
1732
|
+
export const ${e.toUpperCase()}_QUERY_CONFIG: ApiQueryParamsConfig = {
|
|
1733
|
+
filterable: [${i}],
|
|
1734
|
+
sortable: [${d}],
|
|
1735
|
+
searchable: [${c}],
|
|
2994
1736
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
const fieldAssignments = fields.map((f) => ` ${f.name}: dto.${f.name},`).join("\n");
|
|
2998
|
-
const fieldSpread = "...dto";
|
|
2999
|
-
return `import { randomUUID } from 'node:crypto'
|
|
1737
|
+
`}s(so,"genConstants");function ao(e,t,o){let r=o.map(i=>` ${i.name}: dto.${i.name},`).join(`
|
|
1738
|
+
`);return`import { randomUUID } from 'node:crypto'
|
|
3000
1739
|
import { Repository, HttpException } from '@forinda/kickjs-core'
|
|
3001
1740
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
3002
|
-
import type { I${
|
|
3003
|
-
import type { ${
|
|
3004
|
-
import type { Create${
|
|
3005
|
-
import type { Update${
|
|
1741
|
+
import type { I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
1742
|
+
import type { ${e}ResponseDTO } from '../../application/dtos/${t}-response.dto'
|
|
1743
|
+
import type { Create${e}DTO } from '../../application/dtos/create-${t}.dto'
|
|
1744
|
+
import type { Update${e}DTO } from '../../application/dtos/update-${t}.dto'
|
|
3006
1745
|
|
|
3007
1746
|
@Repository()
|
|
3008
|
-
export class InMemory${
|
|
3009
|
-
private store = new Map<string, ${
|
|
1747
|
+
export class InMemory${e}Repository implements I${e}Repository {
|
|
1748
|
+
private store = new Map<string, ${e}ResponseDTO>()
|
|
3010
1749
|
|
|
3011
|
-
async findById(id: string): Promise<${
|
|
1750
|
+
async findById(id: string): Promise<${e}ResponseDTO | null> {
|
|
3012
1751
|
return this.store.get(id) ?? null
|
|
3013
1752
|
}
|
|
3014
1753
|
|
|
3015
|
-
async findAll(): Promise<${
|
|
1754
|
+
async findAll(): Promise<${e}ResponseDTO[]> {
|
|
3016
1755
|
return Array.from(this.store.values())
|
|
3017
1756
|
}
|
|
3018
1757
|
|
|
3019
|
-
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
1758
|
+
async findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }> {
|
|
3020
1759
|
const all = Array.from(this.store.values())
|
|
3021
1760
|
const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)
|
|
3022
1761
|
return { data, total: all.length }
|
|
3023
1762
|
}
|
|
3024
1763
|
|
|
3025
|
-
async create(dto: Create${
|
|
1764
|
+
async create(dto: Create${e}DTO): Promise<${e}ResponseDTO> {
|
|
3026
1765
|
const now = new Date().toISOString()
|
|
3027
|
-
const entity: ${
|
|
1766
|
+
const entity: ${e}ResponseDTO = {
|
|
3028
1767
|
id: randomUUID(),
|
|
3029
|
-
${
|
|
1768
|
+
${r}
|
|
3030
1769
|
createdAt: now,
|
|
3031
1770
|
updatedAt: now,
|
|
3032
1771
|
}
|
|
@@ -3034,337 +1773,240 @@ ${fieldAssignments}
|
|
|
3034
1773
|
return entity
|
|
3035
1774
|
}
|
|
3036
1775
|
|
|
3037
|
-
async update(id: string, dto: Update${
|
|
1776
|
+
async update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO> {
|
|
3038
1777
|
const existing = this.store.get(id)
|
|
3039
|
-
if (!existing) throw HttpException.notFound('${
|
|
3040
|
-
const updated = { ...existing,
|
|
1778
|
+
if (!existing) throw HttpException.notFound('${e} not found')
|
|
1779
|
+
const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }
|
|
3041
1780
|
this.store.set(id, updated)
|
|
3042
1781
|
return updated
|
|
3043
1782
|
}
|
|
3044
1783
|
|
|
3045
1784
|
async delete(id: string): Promise<void> {
|
|
3046
|
-
if (!this.store.has(id)) throw HttpException.notFound('${
|
|
1785
|
+
if (!this.store.has(id)) throw HttpException.notFound('${e} not found')
|
|
3047
1786
|
this.store.delete(id)
|
|
3048
1787
|
}
|
|
3049
1788
|
}
|
|
3050
|
-
|
|
3051
|
-
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
return `import { ${pascal}Id } from '../value-objects/${kebab}-id.vo'
|
|
3062
|
-
|
|
3063
|
-
interface ${pascal}Props {
|
|
3064
|
-
id: ${pascal}Id
|
|
3065
|
-
${propsInterface}
|
|
1789
|
+
`}s(ao,"genInMemoryRepository");function co(e,t,o){let r=o.map(a=>` ${a.name}${a.optional?"?":""}: ${a.tsType}`).join(`
|
|
1790
|
+
`),n=o.filter(a=>!a.optional).map(a=>`${a.name}: ${a.tsType}`).join("; "),i=o.filter(a=>!a.optional).map(a=>` ${a.name}: params.${a.name},`).join(`
|
|
1791
|
+
`),d=o.map(a=>` get ${a.name}(): ${a.tsType}${a.optional?" | undefined":""} {
|
|
1792
|
+
return this.props.${a.name}
|
|
1793
|
+
}`).join(`
|
|
1794
|
+
`),c=o.map(a=>` ${a.name}: this.props.${a.name},`).join(`
|
|
1795
|
+
`);return`import { ${e}Id } from '../value-objects/${t}-id.vo'
|
|
1796
|
+
|
|
1797
|
+
interface ${e}Props {
|
|
1798
|
+
id: ${e}Id
|
|
1799
|
+
${r}
|
|
3066
1800
|
createdAt: Date
|
|
3067
1801
|
updatedAt: Date
|
|
3068
1802
|
}
|
|
3069
1803
|
|
|
3070
|
-
export class ${
|
|
3071
|
-
private constructor(private props: ${
|
|
1804
|
+
export class ${e} {
|
|
1805
|
+
private constructor(private props: ${e}Props) {}
|
|
3072
1806
|
|
|
3073
|
-
static create(params: { ${
|
|
1807
|
+
static create(params: { ${n} }): ${e} {
|
|
3074
1808
|
const now = new Date()
|
|
3075
|
-
return new ${
|
|
3076
|
-
id: ${
|
|
3077
|
-
${
|
|
1809
|
+
return new ${e}({
|
|
1810
|
+
id: ${e}Id.create(),
|
|
1811
|
+
${i}
|
|
3078
1812
|
createdAt: now,
|
|
3079
1813
|
updatedAt: now,
|
|
3080
1814
|
})
|
|
3081
1815
|
}
|
|
3082
1816
|
|
|
3083
|
-
static reconstitute(props: ${
|
|
3084
|
-
return new ${
|
|
1817
|
+
static reconstitute(props: ${e}Props): ${e} {
|
|
1818
|
+
return new ${e}(props)
|
|
3085
1819
|
}
|
|
3086
1820
|
|
|
3087
|
-
get id(): ${
|
|
3088
|
-
${
|
|
1821
|
+
get id(): ${e}Id { return this.props.id }
|
|
1822
|
+
${d}
|
|
3089
1823
|
get createdAt(): Date { return this.props.createdAt }
|
|
3090
1824
|
get updatedAt(): Date { return this.props.updatedAt }
|
|
3091
1825
|
|
|
3092
1826
|
toJSON() {
|
|
3093
1827
|
return {
|
|
3094
1828
|
id: this.props.id.toString(),
|
|
3095
|
-
${
|
|
1829
|
+
${c}
|
|
3096
1830
|
createdAt: this.props.createdAt.toISOString(),
|
|
3097
1831
|
updatedAt: this.props.updatedAt.toISOString(),
|
|
3098
1832
|
}
|
|
3099
1833
|
}
|
|
3100
1834
|
}
|
|
3101
|
-
|
|
3102
|
-
}
|
|
3103
|
-
__name(genEntity, "genEntity");
|
|
3104
|
-
function genValueObject(pascal) {
|
|
3105
|
-
return `import { randomUUID } from 'node:crypto'
|
|
1835
|
+
`}s(co,"genEntity");function po(e){return`import { randomUUID } from 'node:crypto'
|
|
3106
1836
|
|
|
3107
|
-
export class ${
|
|
1837
|
+
export class ${e}Id {
|
|
3108
1838
|
private constructor(private readonly value: string) {}
|
|
3109
1839
|
|
|
3110
|
-
static create(): ${
|
|
1840
|
+
static create(): ${e}Id { return new ${e}Id(randomUUID()) }
|
|
3111
1841
|
|
|
3112
|
-
static from(id: string): ${
|
|
3113
|
-
if (!id || id.trim().length === 0) throw new Error('${
|
|
3114
|
-
return new ${
|
|
1842
|
+
static from(id: string): ${e}Id {
|
|
1843
|
+
if (!id || id.trim().length === 0) throw new Error('${e}Id cannot be empty')
|
|
1844
|
+
return new ${e}Id(id)
|
|
3115
1845
|
}
|
|
3116
1846
|
|
|
3117
1847
|
toString(): string { return this.value }
|
|
3118
|
-
equals(other: ${
|
|
1848
|
+
equals(other: ${e}Id): boolean { return this.value === other.value }
|
|
3119
1849
|
}
|
|
3120
|
-
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
import { ${pascal.toUpperCase()}_REPOSITORY } from './domain/repositories/${kebab}.repository'
|
|
3128
|
-
import { InMemory${pascal}Repository } from './infrastructure/repositories/in-memory-${kebab}.repository'
|
|
3129
|
-
|
|
3130
|
-
export class ${pascal}Module implements AppModule {
|
|
1850
|
+
`}s(po,"genValueObject");function mo(e,t,o,r){return`import type { AppModule, AppModuleClass } from '@forinda/kickjs-core'
|
|
1851
|
+
import { ${e}Controller } from './presentation/${t}.controller'
|
|
1852
|
+
import { ${e}DomainService } from './domain/services/${t}-domain.service'
|
|
1853
|
+
import { ${e.toUpperCase()}_REPOSITORY } from './domain/repositories/${t}.repository'
|
|
1854
|
+
import { InMemory${e}Repository } from './infrastructure/repositories/in-memory-${t}.repository'
|
|
1855
|
+
|
|
1856
|
+
export class ${e}Module implements AppModule {
|
|
3131
1857
|
register(container: any): void {
|
|
3132
1858
|
container.registerFactory(
|
|
3133
|
-
${
|
|
3134
|
-
() => container.resolve(InMemory${
|
|
1859
|
+
${e.toUpperCase()}_REPOSITORY,
|
|
1860
|
+
() => container.resolve(InMemory${e}Repository),
|
|
3135
1861
|
)
|
|
3136
1862
|
}
|
|
3137
1863
|
|
|
3138
1864
|
routes() {
|
|
3139
|
-
return { prefix: '/${
|
|
1865
|
+
return { prefix: '/${o}', controllers: [${e}Controller] }
|
|
3140
1866
|
}
|
|
3141
1867
|
}
|
|
3142
|
-
|
|
3143
|
-
}
|
|
3144
|
-
__name(genModuleIndex, "genModuleIndex");
|
|
3145
|
-
function genController(pascal, kebab, plural, pluralPascal) {
|
|
3146
|
-
return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
1868
|
+
`}s(mo,"genModuleIndex");function lo(e,t,o,r){return`import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams } from '@forinda/kickjs-core'
|
|
3147
1869
|
import type { RequestContext } from '@forinda/kickjs-http'
|
|
3148
1870
|
import { ApiTags } from '@forinda/kickjs-swagger'
|
|
3149
|
-
import { Create${
|
|
3150
|
-
import { Get${
|
|
3151
|
-
import { List${
|
|
3152
|
-
import { Update${
|
|
3153
|
-
import { Delete${
|
|
3154
|
-
import { create${
|
|
3155
|
-
import { update${
|
|
3156
|
-
import { ${
|
|
1871
|
+
import { Create${e}UseCase } from '../application/use-cases/create-${t}.use-case'
|
|
1872
|
+
import { Get${e}UseCase } from '../application/use-cases/get-${t}.use-case'
|
|
1873
|
+
import { List${r}UseCase } from '../application/use-cases/list-${o}.use-case'
|
|
1874
|
+
import { Update${e}UseCase } from '../application/use-cases/update-${t}.use-case'
|
|
1875
|
+
import { Delete${e}UseCase } from '../application/use-cases/delete-${t}.use-case'
|
|
1876
|
+
import { create${e}Schema } from '../application/dtos/create-${t}.dto'
|
|
1877
|
+
import { update${e}Schema } from '../application/dtos/update-${t}.dto'
|
|
1878
|
+
import { ${e.toUpperCase()}_QUERY_CONFIG } from '../constants'
|
|
3157
1879
|
|
|
3158
1880
|
@Controller()
|
|
3159
|
-
export class ${
|
|
3160
|
-
@Autowired() private create${
|
|
3161
|
-
@Autowired() private get${
|
|
3162
|
-
@Autowired() private list${
|
|
3163
|
-
@Autowired() private update${
|
|
3164
|
-
@Autowired() private delete${
|
|
1881
|
+
export class ${e}Controller {
|
|
1882
|
+
@Autowired() private create${e}UseCase!: Create${e}UseCase
|
|
1883
|
+
@Autowired() private get${e}UseCase!: Get${e}UseCase
|
|
1884
|
+
@Autowired() private list${r}UseCase!: List${r}UseCase
|
|
1885
|
+
@Autowired() private update${e}UseCase!: Update${e}UseCase
|
|
1886
|
+
@Autowired() private delete${e}UseCase!: Delete${e}UseCase
|
|
3165
1887
|
|
|
3166
1888
|
@Get('/')
|
|
3167
|
-
@ApiTags('${
|
|
3168
|
-
@ApiQueryParams(${
|
|
1889
|
+
@ApiTags('${e}')
|
|
1890
|
+
@ApiQueryParams(${e.toUpperCase()}_QUERY_CONFIG)
|
|
3169
1891
|
async list(ctx: RequestContext) {
|
|
3170
1892
|
return ctx.paginate(
|
|
3171
|
-
(parsed) => this.list${
|
|
3172
|
-
${
|
|
1893
|
+
(parsed) => this.list${r}UseCase.execute(parsed),
|
|
1894
|
+
${e.toUpperCase()}_QUERY_CONFIG,
|
|
3173
1895
|
)
|
|
3174
1896
|
}
|
|
3175
1897
|
|
|
3176
1898
|
@Get('/:id')
|
|
3177
|
-
@ApiTags('${
|
|
1899
|
+
@ApiTags('${e}')
|
|
3178
1900
|
async getById(ctx: RequestContext) {
|
|
3179
|
-
const result = await this.get${
|
|
3180
|
-
if (!result) return ctx.notFound('${
|
|
1901
|
+
const result = await this.get${e}UseCase.execute(ctx.params.id)
|
|
1902
|
+
if (!result) return ctx.notFound('${e} not found')
|
|
3181
1903
|
ctx.json(result)
|
|
3182
1904
|
}
|
|
3183
1905
|
|
|
3184
|
-
@Post('/', { body: create${
|
|
3185
|
-
@ApiTags('${
|
|
1906
|
+
@Post('/', { body: create${e}Schema, name: 'Create${e}' })
|
|
1907
|
+
@ApiTags('${e}')
|
|
3186
1908
|
async create(ctx: RequestContext) {
|
|
3187
|
-
const result = await this.create${
|
|
1909
|
+
const result = await this.create${e}UseCase.execute(ctx.body)
|
|
3188
1910
|
ctx.created(result)
|
|
3189
1911
|
}
|
|
3190
1912
|
|
|
3191
|
-
@Put('/:id', { body: update${
|
|
3192
|
-
@ApiTags('${
|
|
1913
|
+
@Put('/:id', { body: update${e}Schema, name: 'Update${e}' })
|
|
1914
|
+
@ApiTags('${e}')
|
|
3193
1915
|
async update(ctx: RequestContext) {
|
|
3194
|
-
const result = await this.update${
|
|
1916
|
+
const result = await this.update${e}UseCase.execute(ctx.params.id, ctx.body)
|
|
3195
1917
|
ctx.json(result)
|
|
3196
1918
|
}
|
|
3197
1919
|
|
|
3198
1920
|
@Delete('/:id')
|
|
3199
|
-
@ApiTags('${
|
|
1921
|
+
@ApiTags('${e}')
|
|
3200
1922
|
async remove(ctx: RequestContext) {
|
|
3201
|
-
await this.delete${
|
|
1923
|
+
await this.delete${e}UseCase.execute(ctx.params.id)
|
|
3202
1924
|
ctx.noContent()
|
|
3203
1925
|
}
|
|
3204
1926
|
}
|
|
3205
|
-
|
|
3206
|
-
}
|
|
3207
|
-
|
|
3208
|
-
function genRepositoryInterface(pascal, kebab) {
|
|
3209
|
-
return `import type { ${pascal}ResponseDTO } from '../../application/dtos/${kebab}-response.dto'
|
|
3210
|
-
import type { Create${pascal}DTO } from '../../application/dtos/create-${kebab}.dto'
|
|
3211
|
-
import type { Update${pascal}DTO } from '../../application/dtos/update-${kebab}.dto'
|
|
1927
|
+
`}s(lo,"genController");function uo(e,t){return`import type { ${e}ResponseDTO } from '../../application/dtos/${t}-response.dto'
|
|
1928
|
+
import type { Create${e}DTO } from '../../application/dtos/create-${t}.dto'
|
|
1929
|
+
import type { Update${e}DTO } from '../../application/dtos/update-${t}.dto'
|
|
3212
1930
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
3213
1931
|
|
|
3214
|
-
export interface I${
|
|
3215
|
-
findById(id: string): Promise<${
|
|
3216
|
-
findAll(): Promise<${
|
|
3217
|
-
findPaginated(parsed: ParsedQuery): Promise<{ data: ${
|
|
3218
|
-
create(dto: Create${
|
|
3219
|
-
update(id: string, dto: Update${
|
|
1932
|
+
export interface I${e}Repository {
|
|
1933
|
+
findById(id: string): Promise<${e}ResponseDTO | null>
|
|
1934
|
+
findAll(): Promise<${e}ResponseDTO[]>
|
|
1935
|
+
findPaginated(parsed: ParsedQuery): Promise<{ data: ${e}ResponseDTO[]; total: number }>
|
|
1936
|
+
create(dto: Create${e}DTO): Promise<${e}ResponseDTO>
|
|
1937
|
+
update(id: string, dto: Update${e}DTO): Promise<${e}ResponseDTO>
|
|
3220
1938
|
delete(id: string): Promise<void>
|
|
3221
1939
|
}
|
|
3222
1940
|
|
|
3223
|
-
export const ${
|
|
3224
|
-
|
|
3225
|
-
}
|
|
3226
|
-
__name(genRepositoryInterface, "genRepositoryInterface");
|
|
3227
|
-
function genDomainService(pascal, kebab) {
|
|
3228
|
-
return `import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
3229
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../repositories/${kebab}.repository'
|
|
1941
|
+
export const ${e.toUpperCase()}_REPOSITORY = Symbol('I${e}Repository')
|
|
1942
|
+
`}s(uo,"genRepositoryInterface");function fo(e,t){return`import { Service, Inject, HttpException } from '@forinda/kickjs-core'
|
|
1943
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../repositories/${t}.repository'
|
|
3230
1944
|
|
|
3231
1945
|
@Service()
|
|
3232
|
-
export class ${
|
|
1946
|
+
export class ${e}DomainService {
|
|
3233
1947
|
constructor(
|
|
3234
|
-
@Inject(${
|
|
1948
|
+
@Inject(${e.toUpperCase()}_REPOSITORY) private readonly repo: I${e}Repository,
|
|
3235
1949
|
) {}
|
|
3236
1950
|
|
|
3237
1951
|
async ensureExists(id: string): Promise<void> {
|
|
3238
1952
|
const entity = await this.repo.findById(id)
|
|
3239
|
-
if (!entity) throw HttpException.notFound('${
|
|
1953
|
+
if (!entity) throw HttpException.notFound('${e} not found')
|
|
3240
1954
|
}
|
|
3241
1955
|
}
|
|
3242
|
-
|
|
3243
|
-
}
|
|
3244
|
-
|
|
3245
|
-
function genUseCases(pascal, kebab, plural, pluralPascal) {
|
|
3246
|
-
return [
|
|
3247
|
-
{
|
|
3248
|
-
file: `create-${kebab}.use-case.ts`,
|
|
3249
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
3250
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
3251
|
-
import type { Create${pascal}DTO } from '../dtos/create-${kebab}.dto'
|
|
1956
|
+
`}s(fo,"genDomainService");function $o(e,t,o,r){return[{file:`create-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1957
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
1958
|
+
import type { Create${e}DTO } from '../dtos/create-${t}.dto'
|
|
3252
1959
|
|
|
3253
1960
|
@Service()
|
|
3254
|
-
export class Create${
|
|
3255
|
-
constructor(@Inject(${
|
|
3256
|
-
async execute(dto: Create${
|
|
1961
|
+
export class Create${e}UseCase {
|
|
1962
|
+
constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
|
|
1963
|
+
async execute(dto: Create${e}DTO) { return this.repo.create(dto) }
|
|
3257
1964
|
}
|
|
3258
|
-
`
|
|
3259
|
-
|
|
3260
|
-
{
|
|
3261
|
-
file: `get-${kebab}.use-case.ts`,
|
|
3262
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
3263
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
1965
|
+
`},{file:`get-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1966
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
3264
1967
|
|
|
3265
1968
|
@Service()
|
|
3266
|
-
export class Get${
|
|
3267
|
-
constructor(@Inject(${
|
|
1969
|
+
export class Get${e}UseCase {
|
|
1970
|
+
constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
|
|
3268
1971
|
async execute(id: string) { return this.repo.findById(id) }
|
|
3269
1972
|
}
|
|
3270
|
-
`
|
|
3271
|
-
},
|
|
3272
|
-
{
|
|
3273
|
-
file: `list-${plural}.use-case.ts`,
|
|
3274
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
1973
|
+
`},{file:`list-${o}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
3275
1974
|
import type { ParsedQuery } from '@forinda/kickjs-http'
|
|
3276
|
-
import { ${
|
|
1975
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
3277
1976
|
|
|
3278
1977
|
@Service()
|
|
3279
|
-
export class List${
|
|
3280
|
-
constructor(@Inject(${
|
|
1978
|
+
export class List${r}UseCase {
|
|
1979
|
+
constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
|
|
3281
1980
|
async execute(parsed: ParsedQuery) { return this.repo.findPaginated(parsed) }
|
|
3282
1981
|
}
|
|
3283
|
-
`
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
file: `update-${kebab}.use-case.ts`,
|
|
3287
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
3288
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
3289
|
-
import type { Update${pascal}DTO } from '../dtos/update-${kebab}.dto'
|
|
1982
|
+
`},{file:`update-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1983
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
1984
|
+
import type { Update${e}DTO } from '../dtos/update-${t}.dto'
|
|
3290
1985
|
|
|
3291
1986
|
@Service()
|
|
3292
|
-
export class Update${
|
|
3293
|
-
constructor(@Inject(${
|
|
3294
|
-
async execute(id: string, dto: Update${
|
|
1987
|
+
export class Update${e}UseCase {
|
|
1988
|
+
constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
|
|
1989
|
+
async execute(id: string, dto: Update${e}DTO) { return this.repo.update(id, dto) }
|
|
3295
1990
|
}
|
|
3296
|
-
`
|
|
3297
|
-
|
|
3298
|
-
{
|
|
3299
|
-
file: `delete-${kebab}.use-case.ts`,
|
|
3300
|
-
content: `import { Service, Inject } from '@forinda/kickjs-core'
|
|
3301
|
-
import { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from '../../domain/repositories/${kebab}.repository'
|
|
1991
|
+
`},{file:`delete-${t}.use-case.ts`,content:`import { Service, Inject } from '@forinda/kickjs-core'
|
|
1992
|
+
import { ${e.toUpperCase()}_REPOSITORY, type I${e}Repository } from '../../domain/repositories/${t}.repository'
|
|
3302
1993
|
|
|
3303
1994
|
@Service()
|
|
3304
|
-
export class Delete${
|
|
3305
|
-
constructor(@Inject(${
|
|
1995
|
+
export class Delete${e}UseCase {
|
|
1996
|
+
constructor(@Inject(${e.toUpperCase()}_REPOSITORY) private repo: I${e}Repository) {}
|
|
3306
1997
|
async execute(id: string) { return this.repo.delete(id) }
|
|
3307
1998
|
}
|
|
3308
|
-
`
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
}
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
if (!exists) {
|
|
3317
|
-
await writeFileSafe(indexPath, `import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
3318
|
-
import { ${pascal}Module } from './${plural}'
|
|
3319
|
-
|
|
3320
|
-
export const modules: AppModuleClass[] = [${pascal}Module]
|
|
3321
|
-
`);
|
|
3322
|
-
return;
|
|
3323
|
-
}
|
|
3324
|
-
let content = await readFile3(indexPath, "utf-8");
|
|
3325
|
-
const importLine = `import { ${pascal}Module } from './${plural}'`;
|
|
3326
|
-
if (!content.includes(`${pascal}Module`)) {
|
|
3327
|
-
const lastImportIdx = content.lastIndexOf("import ");
|
|
3328
|
-
if (lastImportIdx !== -1) {
|
|
3329
|
-
const lineEnd = content.indexOf("\n", lastImportIdx);
|
|
3330
|
-
content = content.slice(0, lineEnd + 1) + importLine + "\n" + content.slice(lineEnd + 1);
|
|
3331
|
-
} else {
|
|
3332
|
-
content = importLine + "\n" + content;
|
|
3333
|
-
}
|
|
3334
|
-
content = content.replace(/(=\s*\[)([\s\S]*?)(])/, (_match, open, existing, close) => {
|
|
3335
|
-
const trimmed = existing.trim();
|
|
3336
|
-
if (!trimmed) return `${open}${pascal}Module${close}`;
|
|
3337
|
-
const needsComma = trimmed.endsWith(",") ? "" : ",";
|
|
3338
|
-
return `${open}${existing.trimEnd()}${needsComma} ${pascal}Module${close}`;
|
|
3339
|
-
});
|
|
3340
|
-
}
|
|
3341
|
-
await writeFile3(indexPath, content, "utf-8");
|
|
3342
|
-
}
|
|
3343
|
-
__name(autoRegisterModule2, "autoRegisterModule");
|
|
3344
|
-
|
|
3345
|
-
// src/generators/test.ts
|
|
3346
|
-
import { join as join14, resolve as resolve3 } from "path";
|
|
3347
|
-
async function generateTest(options) {
|
|
3348
|
-
const { name, moduleName, modulesDir } = options;
|
|
3349
|
-
const kebab = toKebabCase(name);
|
|
3350
|
-
const pascal = toPascalCase(name);
|
|
3351
|
-
const files = [];
|
|
3352
|
-
let outDir;
|
|
3353
|
-
if (options.outDir) {
|
|
3354
|
-
outDir = resolve3(options.outDir);
|
|
3355
|
-
} else if (moduleName) {
|
|
3356
|
-
const modKebab = toKebabCase(moduleName);
|
|
3357
|
-
const modPlural = pluralize(modKebab);
|
|
3358
|
-
const modDir = modulesDir ?? "src/modules";
|
|
3359
|
-
outDir = resolve3(join14(modDir, modPlural, "__tests__"));
|
|
3360
|
-
} else {
|
|
3361
|
-
outDir = resolve3("src/__tests__");
|
|
3362
|
-
}
|
|
3363
|
-
const filePath = join14(outDir, `${kebab}.test.ts`);
|
|
3364
|
-
await writeFileSafe(filePath, `import { describe, it, expect, beforeEach } from 'vitest'
|
|
1999
|
+
`}]}s($o,"genUseCases");async function go(e,t,o){let r=ke(e,"index.ts");if(!await _(r)){await l(r,`import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
2000
|
+
import { ${t}Module } from './${o}'
|
|
2001
|
+
|
|
2002
|
+
export const modules: AppModuleClass[] = [${t}Module]
|
|
2003
|
+
`);return}let i=await to(r,"utf-8"),d=`import { ${t}Module } from './${o}'`;if(!i.includes(`${t}Module`)){let c=i.lastIndexOf("import ");if(c!==-1){let a=i.indexOf(`
|
|
2004
|
+
`,c);i=i.slice(0,a+1)+d+`
|
|
2005
|
+
`+i.slice(a+1)}else i=d+`
|
|
2006
|
+
`+i;i=i.replace(/(=\s*\[)([\s\S]*?)(])/,(a,p,m,u)=>{let w=m.trim();if(!w)return`${p}${t}Module${u}`;let g=w.endsWith(",")?"":",";return`${p}${m.trimEnd()}${g} ${t}Module${u}`})}await oo(r,i,"utf-8")}s(go,"autoRegisterModule");import{join as He,resolve as Re}from"path";async function Ke(e){let{name:t,moduleName:o,modulesDir:r}=e,n=$(t),i=f(t),d=[],c;if(e.outDir)c=Re(e.outDir);else if(o){let p=$(o),m=j(p);c=Re(He(r??"src/modules",m,"__tests__"))}else c=Re("src/__tests__");let a=He(c,`${n}.test.ts`);return await l(a,`import { describe, it, expect, beforeEach } from 'vitest'
|
|
3365
2007
|
import { Container } from '@forinda/kickjs-core'
|
|
3366
2008
|
|
|
3367
|
-
describe('${
|
|
2009
|
+
describe('${i}', () => {
|
|
3368
2010
|
beforeEach(() => {
|
|
3369
2011
|
Container.reset()
|
|
3370
2012
|
})
|
|
@@ -3384,385 +2026,36 @@ describe('${pascal}', () => {
|
|
|
3384
2026
|
expect(true).toBe(true)
|
|
3385
2027
|
})
|
|
3386
2028
|
})
|
|
3387
|
-
`);
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
"
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
}
|
|
3414
|
-
try {
|
|
3415
|
-
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
3416
|
-
const mod = await import(pathToFileURL2(filepath).href);
|
|
3417
|
-
return mod.default ?? mod;
|
|
3418
|
-
} catch (err) {
|
|
3419
|
-
if (filename.endsWith(".ts")) {
|
|
3420
|
-
console.warn(`Warning: Failed to load ${filename}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);
|
|
3421
|
-
}
|
|
3422
|
-
continue;
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
return null;
|
|
3426
|
-
}
|
|
3427
|
-
__name(loadKickConfig, "loadKickConfig");
|
|
3428
|
-
|
|
3429
|
-
// src/commands/generate.ts
|
|
3430
|
-
function isDryRun(cmd) {
|
|
3431
|
-
return cmd.parent?.opts()?.dryRun ?? false;
|
|
3432
|
-
}
|
|
3433
|
-
__name(isDryRun, "isDryRun");
|
|
3434
|
-
function printGenerated(files, dryRun = false) {
|
|
3435
|
-
const cwd = process.cwd();
|
|
3436
|
-
const label = dryRun ? "Would generate" : "Generated";
|
|
3437
|
-
console.log(`
|
|
3438
|
-
${label} ${files.length} file${files.length === 1 ? "" : "s"}:`);
|
|
3439
|
-
for (const f of files) {
|
|
3440
|
-
console.log(` ${f.replace(cwd + "/", "")}`);
|
|
3441
|
-
}
|
|
3442
|
-
if (dryRun) console.log("\n (dry run \u2014 no files were written)");
|
|
3443
|
-
console.log();
|
|
3444
|
-
}
|
|
3445
|
-
__name(printGenerated, "printGenerated");
|
|
3446
|
-
var GENERATORS = [
|
|
3447
|
-
{
|
|
3448
|
-
name: "module <name>",
|
|
3449
|
-
description: "Full DDD module (controller, DTOs, use-cases, repo)"
|
|
3450
|
-
},
|
|
3451
|
-
{
|
|
3452
|
-
name: "scaffold <name> <fields...>",
|
|
3453
|
-
description: "CRUD module from field definitions"
|
|
3454
|
-
},
|
|
3455
|
-
{
|
|
3456
|
-
name: "controller <name>",
|
|
3457
|
-
description: "@Controller() class [-m module]"
|
|
3458
|
-
},
|
|
3459
|
-
{
|
|
3460
|
-
name: "service <name>",
|
|
3461
|
-
description: "@Service() singleton [-m module]"
|
|
3462
|
-
},
|
|
3463
|
-
{
|
|
3464
|
-
name: "middleware <name>",
|
|
3465
|
-
description: "Express middleware function [-m module]"
|
|
3466
|
-
},
|
|
3467
|
-
{
|
|
3468
|
-
name: "guard <name>",
|
|
3469
|
-
description: "Route guard (auth, roles, etc.) [-m module]"
|
|
3470
|
-
},
|
|
3471
|
-
{
|
|
3472
|
-
name: "dto <name>",
|
|
3473
|
-
description: "Zod DTO schema [-m module]"
|
|
3474
|
-
},
|
|
3475
|
-
{
|
|
3476
|
-
name: "adapter <name>",
|
|
3477
|
-
description: "AppAdapter with lifecycle hooks (app-level only)"
|
|
3478
|
-
},
|
|
3479
|
-
{
|
|
3480
|
-
name: "test <name>",
|
|
3481
|
-
description: "Vitest test scaffold [-m module]"
|
|
3482
|
-
},
|
|
3483
|
-
{
|
|
3484
|
-
name: "resolver <name>",
|
|
3485
|
-
description: "GraphQL @Resolver class"
|
|
3486
|
-
},
|
|
3487
|
-
{
|
|
3488
|
-
name: "job <name>",
|
|
3489
|
-
description: "Queue @Job processor"
|
|
3490
|
-
},
|
|
3491
|
-
{
|
|
3492
|
-
name: "config",
|
|
3493
|
-
description: "Generate kick.config.ts"
|
|
3494
|
-
}
|
|
3495
|
-
];
|
|
3496
|
-
function printGeneratorList() {
|
|
3497
|
-
console.log("\n Available generators:\n");
|
|
3498
|
-
const maxName = Math.max(...GENERATORS.map((g) => g.name.length));
|
|
3499
|
-
for (const g of GENERATORS) {
|
|
3500
|
-
console.log(` kick g ${g.name.padEnd(maxName + 2)} ${g.description}`);
|
|
3501
|
-
}
|
|
3502
|
-
console.log();
|
|
3503
|
-
}
|
|
3504
|
-
__name(printGeneratorList, "printGeneratorList");
|
|
3505
|
-
function registerGenerateCommand(program) {
|
|
3506
|
-
const gen = program.command("generate").alias("g").description("Generate code scaffolds").option("--list", "List all available generators").option("--dry-run", "Preview files that would be generated without writing them").action((opts) => {
|
|
3507
|
-
if (opts.list) {
|
|
3508
|
-
printGeneratorList();
|
|
3509
|
-
} else {
|
|
3510
|
-
gen.help();
|
|
3511
|
-
}
|
|
3512
|
-
});
|
|
3513
|
-
gen.command("module <name>").description("Generate a module (structure depends on project pattern)").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--repo <type>", "Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>", "Override project pattern: rest | ddd | cqrs | minimal").option("--minimal", "Shorthand for --pattern minimal").option("--modules-dir <dir>", "Modules directory").option("-f, --force", "Overwrite existing files without prompting").action(async (name, opts, cmd) => {
|
|
3514
|
-
const dryRun = isDryRun(cmd);
|
|
3515
|
-
setDryRun(dryRun);
|
|
3516
|
-
const config = await loadKickConfig(process.cwd());
|
|
3517
|
-
const modulesDir = opts.modulesDir ?? config?.modulesDir ?? "src/modules";
|
|
3518
|
-
const repo = opts.repo ?? config?.defaultRepo ?? "inmemory";
|
|
3519
|
-
const pattern = opts.pattern ?? config?.pattern ?? "ddd";
|
|
3520
|
-
const files = await generateModule({
|
|
3521
|
-
name,
|
|
3522
|
-
modulesDir: resolve4(modulesDir),
|
|
3523
|
-
noEntity: opts.entity === false,
|
|
3524
|
-
noTests: opts.tests === false,
|
|
3525
|
-
repo,
|
|
3526
|
-
minimal: opts.minimal,
|
|
3527
|
-
force: opts.force,
|
|
3528
|
-
pattern,
|
|
3529
|
-
dryRun
|
|
3530
|
-
});
|
|
3531
|
-
printGenerated(files, dryRun);
|
|
3532
|
-
});
|
|
3533
|
-
gen.command("adapter <name>").description("Generate an AppAdapter with lifecycle hooks and middleware support").option("-o, --out <dir>", "Output directory", "src/adapters").action(async (name, opts, cmd) => {
|
|
3534
|
-
const dryRun = isDryRun(cmd);
|
|
3535
|
-
setDryRun(dryRun);
|
|
3536
|
-
const files = await generateAdapter({
|
|
3537
|
-
name,
|
|
3538
|
-
outDir: resolve4(opts.out)
|
|
3539
|
-
});
|
|
3540
|
-
printGenerated(files, dryRun);
|
|
3541
|
-
});
|
|
3542
|
-
gen.command("middleware <name>").description("Generate an Express middleware function\n Use -m to scope it to a module: kick g middleware auth -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
|
|
3543
|
-
const dryRun = isDryRun(cmd);
|
|
3544
|
-
setDryRun(dryRun);
|
|
3545
|
-
const config = await loadKickConfig(process.cwd());
|
|
3546
|
-
const modulesDir = config?.modulesDir ?? "src/modules";
|
|
3547
|
-
const files = await generateMiddleware({
|
|
3548
|
-
name,
|
|
3549
|
-
outDir: opts.out,
|
|
3550
|
-
moduleName: opts.module,
|
|
3551
|
-
modulesDir,
|
|
3552
|
-
pattern: config?.pattern
|
|
3553
|
-
});
|
|
3554
|
-
printGenerated(files, dryRun);
|
|
3555
|
-
});
|
|
3556
|
-
gen.command("guard <name>").description("Generate a route guard (auth, roles, etc.)\n Use -m to scope it to a module: kick g guard admin -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
|
|
3557
|
-
const dryRun = isDryRun(cmd);
|
|
3558
|
-
setDryRun(dryRun);
|
|
3559
|
-
const config = await loadKickConfig(process.cwd());
|
|
3560
|
-
const modulesDir = config?.modulesDir ?? "src/modules";
|
|
3561
|
-
const files = await generateGuard({
|
|
3562
|
-
name,
|
|
3563
|
-
outDir: opts.out,
|
|
3564
|
-
moduleName: opts.module,
|
|
3565
|
-
modulesDir,
|
|
3566
|
-
pattern: config?.pattern
|
|
3567
|
-
});
|
|
3568
|
-
printGenerated(files, dryRun);
|
|
3569
|
-
});
|
|
3570
|
-
gen.command("service <name>").description("Generate a @Service() class\n Use -m to scope it to a module: kick g service payment -m orders").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
|
|
3571
|
-
const dryRun = isDryRun(cmd);
|
|
3572
|
-
setDryRun(dryRun);
|
|
3573
|
-
const config = await loadKickConfig(process.cwd());
|
|
3574
|
-
const modulesDir = config?.modulesDir ?? "src/modules";
|
|
3575
|
-
const files = await generateService({
|
|
3576
|
-
name,
|
|
3577
|
-
outDir: opts.out,
|
|
3578
|
-
moduleName: opts.module,
|
|
3579
|
-
modulesDir,
|
|
3580
|
-
pattern: config?.pattern
|
|
3581
|
-
});
|
|
3582
|
-
printGenerated(files, dryRun);
|
|
3583
|
-
});
|
|
3584
|
-
gen.command("controller <name>").description("Generate a @Controller() class with basic routes\n Use -m to scope it to a module: kick g controller auth -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
|
|
3585
|
-
const dryRun = isDryRun(cmd);
|
|
3586
|
-
setDryRun(dryRun);
|
|
3587
|
-
const config = await loadKickConfig(process.cwd());
|
|
3588
|
-
const modulesDir = config?.modulesDir ?? "src/modules";
|
|
3589
|
-
const files = await generateController2({
|
|
3590
|
-
name,
|
|
3591
|
-
outDir: opts.out,
|
|
3592
|
-
moduleName: opts.module,
|
|
3593
|
-
modulesDir,
|
|
3594
|
-
pattern: config?.pattern
|
|
3595
|
-
});
|
|
3596
|
-
printGenerated(files, dryRun);
|
|
3597
|
-
});
|
|
3598
|
-
gen.command("dto <name>").description("Generate a Zod DTO schema\n Use -m to scope it to a module: kick g dto create-user -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
|
|
3599
|
-
const dryRun = isDryRun(cmd);
|
|
3600
|
-
setDryRun(dryRun);
|
|
3601
|
-
const config = await loadKickConfig(process.cwd());
|
|
3602
|
-
const modulesDir = config?.modulesDir ?? "src/modules";
|
|
3603
|
-
const files = await generateDto({
|
|
3604
|
-
name,
|
|
3605
|
-
outDir: opts.out,
|
|
3606
|
-
moduleName: opts.module,
|
|
3607
|
-
modulesDir,
|
|
3608
|
-
pattern: config?.pattern
|
|
3609
|
-
});
|
|
3610
|
-
printGenerated(files, dryRun);
|
|
3611
|
-
});
|
|
3612
|
-
gen.command("test <name>").description("Generate a Vitest test scaffold\n Use -m to scope it to a module: kick g test user-service -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module's __tests__/ folder").action(async (name, opts, cmd) => {
|
|
3613
|
-
const dryRun = isDryRun(cmd);
|
|
3614
|
-
setDryRun(dryRun);
|
|
3615
|
-
const config = await loadKickConfig(process.cwd());
|
|
3616
|
-
const modulesDir = config?.modulesDir ?? "src/modules";
|
|
3617
|
-
const files = await generateTest({
|
|
3618
|
-
name,
|
|
3619
|
-
outDir: opts.out,
|
|
3620
|
-
moduleName: opts.module,
|
|
3621
|
-
modulesDir
|
|
3622
|
-
});
|
|
3623
|
-
printGenerated(files, dryRun);
|
|
3624
|
-
});
|
|
3625
|
-
gen.command("resolver <name>").description("Generate a GraphQL @Resolver class with @Query and @Mutation methods").option("-o, --out <dir>", "Output directory", "src/resolvers").action(async (name, opts, cmd) => {
|
|
3626
|
-
const dryRun = isDryRun(cmd);
|
|
3627
|
-
setDryRun(dryRun);
|
|
3628
|
-
const files = await generateResolver({
|
|
3629
|
-
name,
|
|
3630
|
-
outDir: resolve4(opts.out)
|
|
3631
|
-
});
|
|
3632
|
-
printGenerated(files, dryRun);
|
|
3633
|
-
});
|
|
3634
|
-
gen.command("job <name>").description("Generate a @Job queue processor with @Process handlers").option("-o, --out <dir>", "Output directory", "src/jobs").option("-q, --queue <name>", "Queue name (default: <name>-queue)").action(async (name, opts, cmd) => {
|
|
3635
|
-
const dryRun = isDryRun(cmd);
|
|
3636
|
-
setDryRun(dryRun);
|
|
3637
|
-
const files = await generateJob({
|
|
3638
|
-
name,
|
|
3639
|
-
outDir: resolve4(opts.out),
|
|
3640
|
-
queue: opts.queue
|
|
3641
|
-
});
|
|
3642
|
-
printGenerated(files, dryRun);
|
|
3643
|
-
});
|
|
3644
|
-
gen.command("scaffold <name> [fields...]").description("Generate a full CRUD module from field definitions\n Example: kick g scaffold Post title:string body:text published:boolean?\n Types: string, text, number, int, float, boolean, date, email, url, uuid, json, enum:a,b,c\n Append ? for optional fields: description:text?").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--modules-dir <dir>", "Modules directory").action(async (name, rawFields, opts, cmd) => {
|
|
3645
|
-
const dryRun = isDryRun(cmd);
|
|
3646
|
-
setDryRun(dryRun);
|
|
3647
|
-
if (rawFields.length === 0) {
|
|
3648
|
-
console.error("\n Error: At least one field is required.\n Usage: kick g scaffold <name> <field:type> [field:type...]\n Example: kick g scaffold Post title:string body:text published:boolean\n");
|
|
3649
|
-
process.exit(1);
|
|
3650
|
-
}
|
|
3651
|
-
const config = await loadKickConfig(process.cwd());
|
|
3652
|
-
const modulesDir = opts.modulesDir ?? config?.modulesDir ?? "src/modules";
|
|
3653
|
-
const fields = parseFields(rawFields);
|
|
3654
|
-
const files = await generateScaffold({
|
|
3655
|
-
name,
|
|
3656
|
-
fields,
|
|
3657
|
-
modulesDir: resolve4(modulesDir),
|
|
3658
|
-
noEntity: opts.entity === false,
|
|
3659
|
-
noTests: opts.tests === false
|
|
3660
|
-
});
|
|
3661
|
-
console.log(`
|
|
3662
|
-
Scaffolded ${name} with ${fields.length} field(s):`);
|
|
3663
|
-
for (const f of fields) {
|
|
3664
|
-
console.log(` ${f.name}: ${f.type}${f.optional ? " (optional)" : ""}`);
|
|
3665
|
-
}
|
|
3666
|
-
printGenerated(files, dryRun);
|
|
3667
|
-
});
|
|
3668
|
-
gen.command("config").description("Generate a kick.config.ts at the project root").option("--modules-dir <dir>", "Modules directory path", "src/modules").option("--repo <type>", "Default repository type: inmemory | drizzle | prisma", "inmemory").option("-f, --force", "Overwrite existing kick.config.ts without prompting").action(async (opts, cmd) => {
|
|
3669
|
-
const dryRun = isDryRun(cmd);
|
|
3670
|
-
setDryRun(dryRun);
|
|
3671
|
-
const files = await generateConfig({
|
|
3672
|
-
outDir: resolve4("."),
|
|
3673
|
-
modulesDir: opts.modulesDir,
|
|
3674
|
-
defaultRepo: opts.repo,
|
|
3675
|
-
force: opts.force
|
|
3676
|
-
});
|
|
3677
|
-
printGenerated(files, dryRun);
|
|
3678
|
-
});
|
|
3679
|
-
}
|
|
3680
|
-
__name(registerGenerateCommand, "registerGenerateCommand");
|
|
3681
|
-
|
|
3682
|
-
// src/commands/run.ts
|
|
3683
|
-
import { cpSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
3684
|
-
import { resolve as resolve5, join as join16 } from "path";
|
|
3685
|
-
|
|
3686
|
-
// src/utils/shell.ts
|
|
3687
|
-
import { execSync as execSync2 } from "child_process";
|
|
3688
|
-
function runShellCommand(command, cwd) {
|
|
3689
|
-
execSync2(command, {
|
|
3690
|
-
cwd,
|
|
3691
|
-
stdio: "inherit"
|
|
3692
|
-
});
|
|
3693
|
-
}
|
|
3694
|
-
__name(runShellCommand, "runShellCommand");
|
|
3695
|
-
|
|
3696
|
-
// src/commands/run.ts
|
|
3697
|
-
function registerRunCommands(program) {
|
|
3698
|
-
program.command("dev").description("Start development server with Vite HMR (zero-downtime reload)").option("-e, --entry <file>", "Entry file", "src/index.ts").option("-p, --port <port>", "Port number").action((opts) => {
|
|
3699
|
-
const envVars = [];
|
|
3700
|
-
if (opts.port) envVars.push(`PORT=${opts.port}`);
|
|
3701
|
-
const cmd = `npx vite-node --watch ${opts.entry}`;
|
|
3702
|
-
const fullCmd = envVars.length ? `${envVars.join(" ")} ${cmd}` : cmd;
|
|
3703
|
-
console.log(`
|
|
3704
|
-
KickJS dev server starting...`);
|
|
3705
|
-
console.log(` Entry: ${opts.entry}`);
|
|
3706
|
-
console.log(` HMR: enabled (vite-node)
|
|
3707
|
-
`);
|
|
3708
|
-
try {
|
|
3709
|
-
runShellCommand(fullCmd);
|
|
3710
|
-
} catch {
|
|
3711
|
-
}
|
|
3712
|
-
});
|
|
3713
|
-
program.command("build").description("Build for production via Vite").action(async () => {
|
|
3714
|
-
console.log("\n Building for production...\n");
|
|
3715
|
-
runShellCommand("npx vite build");
|
|
3716
|
-
const config = await loadKickConfig(process.cwd());
|
|
3717
|
-
const copyDirs = config?.copyDirs ?? [];
|
|
3718
|
-
if (copyDirs.length > 0) {
|
|
3719
|
-
console.log("\n Copying directories to dist...");
|
|
3720
|
-
for (const entry of copyDirs) {
|
|
3721
|
-
const src = typeof entry === "string" ? entry : entry.src;
|
|
3722
|
-
const dest = typeof entry === "string" ? join16("dist", entry) : entry.dest ?? join16("dist", src);
|
|
3723
|
-
const srcPath = resolve5(src);
|
|
3724
|
-
const destPath = resolve5(dest);
|
|
3725
|
-
if (!existsSync3(srcPath)) {
|
|
3726
|
-
console.log(` \u26A0 Skipped ${src} (not found)`);
|
|
3727
|
-
continue;
|
|
3728
|
-
}
|
|
3729
|
-
mkdirSync(destPath, {
|
|
3730
|
-
recursive: true
|
|
3731
|
-
});
|
|
3732
|
-
cpSync(srcPath, destPath, {
|
|
3733
|
-
recursive: true
|
|
3734
|
-
});
|
|
3735
|
-
console.log(` \u2713 ${src} \u2192 ${dest}`);
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
console.log("\n Build complete.\n");
|
|
3739
|
-
});
|
|
3740
|
-
program.command("start").description("Start production server").option("-e, --entry <file>", "Entry file", "dist/index.js").option("-p, --port <port>", "Port number").action((opts) => {
|
|
3741
|
-
const envVars = [
|
|
3742
|
-
"NODE_ENV=production"
|
|
3743
|
-
];
|
|
3744
|
-
if (opts.port) envVars.push(`PORT=${opts.port}`);
|
|
3745
|
-
runShellCommand(`${envVars.join(" ")} node ${opts.entry}`);
|
|
3746
|
-
});
|
|
3747
|
-
program.command("dev:debug").description("Start dev server with Node.js inspector").option("-e, --entry <file>", "Entry file", "src/index.ts").option("-p, --port <port>", "Port number").action((opts) => {
|
|
3748
|
-
const envVars = opts.port ? `PORT=${opts.port} ` : "";
|
|
3749
|
-
try {
|
|
3750
|
-
runShellCommand(`${envVars}npx vite-node --inspect --watch ${opts.entry}`);
|
|
3751
|
-
} catch {
|
|
3752
|
-
}
|
|
3753
|
-
});
|
|
3754
|
-
}
|
|
3755
|
-
__name(registerRunCommands, "registerRunCommands");
|
|
3756
|
-
|
|
3757
|
-
// src/commands/info.ts
|
|
3758
|
-
import { platform, release, arch } from "os";
|
|
3759
|
-
function registerInfoCommand(program) {
|
|
3760
|
-
program.command("info").description("Print system and framework info").action(() => {
|
|
3761
|
-
console.log(`
|
|
2029
|
+
`),d.push(a),d}s(Ke,"generateTest");import{readFile as yo,access as ho}from"fs/promises";import{join as wo}from"path";var vo=["kick.config.ts","kick.config.js","kick.config.mjs","kick.config.json"];async function C(e){for(let t of vo){let o=wo(e,t);try{await ho(o)}catch{continue}if(t.endsWith(".json")){let r=await yo(o,"utf-8");return JSON.parse(r)}try{let{pathToFileURL:r}=await import("url"),n=await import(r(o).href);return n.default??n}catch{t.endsWith(".ts")&&console.warn(`Warning: Failed to load ${t}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);continue}}return null}s(C,"loadKickConfig");function k(e){return e.parent?.opts()?.dryRun??!1}s(k,"isDryRun");function R(e,t=!1){let o=process.cwd();console.log(`
|
|
2030
|
+
${t?"Would generate":"Generated"} ${e.length} file${e.length===1?"":"s"}:`);for(let n of e)console.log(` ${n.replace(o+"/","")}`);t&&console.log(`
|
|
2031
|
+
(dry run \u2014 no files were written)`),console.log()}s(R,"printGenerated");var We=[{name:"module <name>",description:"Full DDD module (controller, DTOs, use-cases, repo)"},{name:"scaffold <name> <fields...>",description:"CRUD module from field definitions"},{name:"controller <name>",description:"@Controller() class [-m module]"},{name:"service <name>",description:"@Service() singleton [-m module]"},{name:"middleware <name>",description:"Express middleware function [-m module]"},{name:"guard <name>",description:"Route guard (auth, roles, etc.) [-m module]"},{name:"dto <name>",description:"Zod DTO schema [-m module]"},{name:"adapter <name>",description:"AppAdapter with lifecycle hooks (app-level only)"},{name:"test <name>",description:"Vitest test scaffold [-m module]"},{name:"resolver <name>",description:"GraphQL @Resolver class"},{name:"job <name>",description:"Queue @Job processor"},{name:"config",description:"Generate kick.config.ts"}];function Co(){console.log(`
|
|
2032
|
+
Available generators:
|
|
2033
|
+
`);let e=Math.max(...We.map(t=>t.name.length));for(let t of We)console.log(` kick g ${t.name.padEnd(e+2)} ${t.description}`);console.log()}s(Co,"printGeneratorList");function Je(e){let t=e.command("generate").alias("g").description("Generate code scaffolds").option("--list","List all available generators").option("--dry-run","Preview files that would be generated without writing them").action(o=>{o.list?Co():t.help()});t.command("module <name>").description("Generate a module (structure depends on project pattern)").option("--no-entity","Skip entity and value object generation").option("--no-tests","Skip test file generation").option("--repo <type>","Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>","Override project pattern: rest | ddd | cqrs | minimal").option("--minimal","Shorthand for --pattern minimal").option("--modules-dir <dir>","Modules directory").option("-f, --force","Overwrite existing files without prompting").action(async(o,r,n)=>{let i=k(n);v(i);let d=await C(process.cwd()),c=r.modulesDir??d?.modulesDir??"src/modules",a=r.repo??d?.defaultRepo??"inmemory",p=r.pattern??d?.pattern??"ddd",m=await Ae({name:o,modulesDir:z(c),noEntity:r.entity===!1,noTests:r.tests===!1,repo:a,minimal:r.minimal,force:r.force,pattern:p,dryRun:i});R(m,i)}),t.command("adapter <name>").description("Generate an AppAdapter with lifecycle hooks and middleware support").option("-o, --out <dir>","Output directory","src/adapters").action(async(o,r,n)=>{let i=k(n);v(i);let d=await Ue({name:o,outDir:z(r.out)});R(d,i)}),t.command("middleware <name>").description(`Generate an Express middleware function
|
|
2034
|
+
Use -m to scope it to a module: kick g middleware auth -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(o,r,n)=>{let i=k(n);v(i);let d=await C(process.cwd()),c=d?.modulesDir??"src/modules",a=await qe({name:o,outDir:r.out,moduleName:r.module,modulesDir:c,pattern:d?.pattern});R(a,i)}),t.command("guard <name>").description(`Generate a route guard (auth, roles, etc.)
|
|
2035
|
+
Use -m to scope it to a module: kick g guard admin -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(o,r,n)=>{let i=k(n);v(i);let d=await C(process.cwd()),c=d?.modulesDir??"src/modules",a=await _e({name:o,outDir:r.out,moduleName:r.module,modulesDir:c,pattern:d?.pattern});R(a,i)}),t.command("service <name>").description(`Generate a @Service() class
|
|
2036
|
+
Use -m to scope it to a module: kick g service payment -m orders`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(o,r,n)=>{let i=k(n);v(i);let d=await C(process.cwd()),c=d?.modulesDir??"src/modules",a=await Me({name:o,outDir:r.out,moduleName:r.module,modulesDir:c,pattern:d?.pattern});R(a,i)}),t.command("controller <name>").description(`Generate a @Controller() class with basic routes
|
|
2037
|
+
Use -m to scope it to a module: kick g controller auth -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(o,r,n)=>{let i=k(n);v(i);let d=await C(process.cwd()),c=d?.modulesDir??"src/modules",a=await be({name:o,outDir:r.out,moduleName:r.module,modulesDir:c,pattern:d?.pattern});R(a,i)}),t.command("dto <name>").description(`Generate a Zod DTO schema
|
|
2038
|
+
Use -m to scope it to a module: kick g dto create-user -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module folder").action(async(o,r,n)=>{let i=k(n);v(i);let d=await C(process.cwd()),c=d?.modulesDir??"src/modules",a=await Qe({name:o,outDir:r.out,moduleName:r.module,modulesDir:c,pattern:d?.pattern});R(a,i)}),t.command("test <name>").description(`Generate a Vitest test scaffold
|
|
2039
|
+
Use -m to scope it to a module: kick g test user-service -m users`).option("-o, --out <dir>","Output directory (overrides --module)").option("-m, --module <module>","Place inside a module's __tests__/ folder").action(async(o,r,n)=>{let i=k(n);v(i);let c=(await C(process.cwd()))?.modulesDir??"src/modules",a=await Ke({name:o,outDir:r.out,moduleName:r.module,modulesDir:c});R(a,i)}),t.command("resolver <name>").description("Generate a GraphQL @Resolver class with @Query and @Mutation methods").option("-o, --out <dir>","Output directory","src/resolvers").action(async(o,r,n)=>{let i=k(n);v(i);let d=await Fe({name:o,outDir:z(r.out)});R(d,i)}),t.command("job <name>").description("Generate a @Job queue processor with @Process handlers").option("-o, --out <dir>","Output directory","src/jobs").option("-q, --queue <name>","Queue name (default: <name>-queue)").action(async(o,r,n)=>{let i=k(n);v(i);let d=await Le({name:o,outDir:z(r.out),queue:r.queue});R(d,i)}),t.command("scaffold <name> [fields...]").description(`Generate a full CRUD module from field definitions
|
|
2040
|
+
Example: kick g scaffold Post title:string body:text published:boolean?
|
|
2041
|
+
Types: string, text, number, int, float, boolean, date, email, url, uuid, json, enum:a,b,c
|
|
2042
|
+
Append ? for optional fields: description:text?`).option("--no-entity","Skip entity and value object generation").option("--no-tests","Skip test file generation").option("--modules-dir <dir>","Modules directory").action(async(o,r,n,i)=>{let d=k(i);v(d),r.length===0&&(console.error(`
|
|
2043
|
+
Error: At least one field is required.
|
|
2044
|
+
Usage: kick g scaffold <name> <field:type> [field:type...]
|
|
2045
|
+
Example: kick g scaffold Post title:string body:text published:boolean
|
|
2046
|
+
`),process.exit(1));let c=await C(process.cwd()),a=n.modulesDir??c?.modulesDir??"src/modules",p=Ye(r),m=await Be({name:o,fields:p,modulesDir:z(a),noEntity:n.entity===!1,noTests:n.tests===!1});console.log(`
|
|
2047
|
+
Scaffolded ${o} with ${p.length} field(s):`);for(let u of p)console.log(` ${u.name}: ${u.type}${u.optional?" (optional)":""}`);R(m,d)}),t.command("config").description("Generate a kick.config.ts at the project root").option("--modules-dir <dir>","Modules directory path","src/modules").option("--repo <type>","Default repository type: inmemory | drizzle | prisma","inmemory").option("-f, --force","Overwrite existing kick.config.ts without prompting").action(async(o,r)=>{let n=k(r);v(n);let i=await Ge({outDir:z("."),modulesDir:o.modulesDir,defaultRepo:o.repo,force:o.force});R(i,n)})}s(Je,"registerGenerateCommand");import{cpSync as ko,existsSync as Ro,mkdirSync as Do}from"fs";import{resolve as Ve,join as Ze}from"path";import{execSync as xo}from"child_process";function E(e,t){xo(e,{cwd:t,stdio:"inherit"})}s(E,"runShellCommand");function Xe(e){e.command("dev").description("Start development server with Vite HMR (zero-downtime reload)").option("-e, --entry <file>","Entry file","src/index.ts").option("-p, --port <port>","Port number").action(t=>{let o=[];t.port&&o.push(`PORT=${t.port}`);let r=`npx vite-node --watch ${t.entry}`,n=o.length?`${o.join(" ")} ${r}`:r;console.log(`
|
|
2048
|
+
KickJS dev server starting...`),console.log(` Entry: ${t.entry}`),console.log(` HMR: enabled (vite-node)
|
|
2049
|
+
`);try{E(n)}catch{}}),e.command("build").description("Build for production via Vite").action(async()=>{console.log(`
|
|
2050
|
+
Building for production...
|
|
2051
|
+
`),E("npx vite build");let o=(await C(process.cwd()))?.copyDirs??[];if(o.length>0){console.log(`
|
|
2052
|
+
Copying directories to dist...`);for(let r of o){let n=typeof r=="string"?r:r.src,i=typeof r=="string"?Ze("dist",r):r.dest??Ze("dist",n),d=Ve(n),c=Ve(i);if(!Ro(d)){console.log(` \u26A0 Skipped ${n} (not found)`);continue}Do(c,{recursive:!0}),ko(d,c,{recursive:!0}),console.log(` \u2713 ${n} \u2192 ${i}`)}}console.log(`
|
|
2053
|
+
Build complete.
|
|
2054
|
+
`)}),e.command("start").description("Start production server").option("-e, --entry <file>","Entry file","dist/index.js").option("-p, --port <port>","Port number").action(t=>{let o=["NODE_ENV=production"];t.port&&o.push(`PORT=${t.port}`),E(`${o.join(" ")} node ${t.entry}`)}),e.command("dev:debug").description("Start dev server with Node.js inspector").option("-e, --entry <file>","Entry file","src/index.ts").option("-p, --port <port>","Port number").action(t=>{let o=t.port?`PORT=${t.port} `:"";try{E(`${o}npx vite-node --inspect --watch ${t.entry}`)}catch{}})}s(Xe,"registerRunCommands");import{platform as Oo,release as Io,arch as So}from"os";function et(e){e.command("info").description("Print system and framework info").action(()=>{console.log(`
|
|
3762
2055
|
KickJS CLI
|
|
3763
2056
|
|
|
3764
2057
|
System:
|
|
3765
|
-
OS: ${
|
|
2058
|
+
OS: ${Oo()} ${Io()} (${So()})
|
|
3766
2059
|
Node: ${process.version}
|
|
3767
2060
|
|
|
3768
2061
|
Packages:
|
|
@@ -3770,502 +2063,32 @@ function registerInfoCommand(program) {
|
|
|
3770
2063
|
@forinda/kickjs-http workspace
|
|
3771
2064
|
@forinda/kickjs-config workspace
|
|
3772
2065
|
@forinda/kickjs-cli workspace
|
|
3773
|
-
`);
|
|
3774
|
-
|
|
3775
|
-
}
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
if (!config?.commands?.length) return;
|
|
3781
|
-
for (const cmd of config.commands) {
|
|
3782
|
-
registerSingleCommand(program, cmd);
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3785
|
-
__name(registerCustomCommands, "registerCustomCommands");
|
|
3786
|
-
function registerSingleCommand(program, def) {
|
|
3787
|
-
const command = program.command(def.name).description(def.description);
|
|
3788
|
-
if (def.aliases) {
|
|
3789
|
-
for (const alias of def.aliases) {
|
|
3790
|
-
command.alias(alias);
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
command.allowUnknownOption(true);
|
|
3794
|
-
command.argument("[args...]", "Additional arguments passed to the command");
|
|
3795
|
-
command.action((args) => {
|
|
3796
|
-
const extraArgs = args.join(" ");
|
|
3797
|
-
const steps = Array.isArray(def.steps) ? def.steps : [
|
|
3798
|
-
def.steps
|
|
3799
|
-
];
|
|
3800
|
-
for (const step of steps) {
|
|
3801
|
-
const finalCmd = extraArgs ? `${step} ${extraArgs}` : step;
|
|
3802
|
-
console.log(` $ ${finalCmd}`);
|
|
3803
|
-
try {
|
|
3804
|
-
runShellCommand(finalCmd);
|
|
3805
|
-
} catch (err) {
|
|
3806
|
-
console.error(` Command failed: ${def.name}`);
|
|
3807
|
-
process.exitCode = 1;
|
|
3808
|
-
return;
|
|
3809
|
-
}
|
|
3810
|
-
}
|
|
3811
|
-
});
|
|
3812
|
-
}
|
|
3813
|
-
__name(registerSingleCommand, "registerSingleCommand");
|
|
3814
|
-
|
|
3815
|
-
// src/commands/inspect.ts
|
|
3816
|
-
var esc = /* @__PURE__ */ __name((code) => `\x1B[${code}m`, "esc");
|
|
3817
|
-
var reset = esc("0");
|
|
3818
|
-
var bold = /* @__PURE__ */ __name((s) => `${esc("1")}${s}${reset}`, "bold");
|
|
3819
|
-
var dim = /* @__PURE__ */ __name((s) => `${esc("2")}${s}${reset}`, "dim");
|
|
3820
|
-
var green = /* @__PURE__ */ __name((s) => `${esc("32")}${s}${reset}`, "green");
|
|
3821
|
-
var red = /* @__PURE__ */ __name((s) => `${esc("31")}${s}${reset}`, "red");
|
|
3822
|
-
var yellow = /* @__PURE__ */ __name((s) => `${esc("33")}${s}${reset}`, "yellow");
|
|
3823
|
-
var cyan = /* @__PURE__ */ __name((s) => `${esc("36")}${s}${reset}`, "cyan");
|
|
3824
|
-
var magenta = /* @__PURE__ */ __name((s) => `${esc("35")}${s}${reset}`, "magenta");
|
|
3825
|
-
var blue = /* @__PURE__ */ __name((s) => `${esc("34")}${s}${reset}`, "blue");
|
|
3826
|
-
var METHOD_COLORS = {
|
|
3827
|
-
GET: green,
|
|
3828
|
-
POST: cyan,
|
|
3829
|
-
PUT: yellow,
|
|
3830
|
-
PATCH: magenta,
|
|
3831
|
-
DELETE: red
|
|
3832
|
-
};
|
|
3833
|
-
function colorMethod(method) {
|
|
3834
|
-
const fn = METHOD_COLORS[method] ?? dim;
|
|
3835
|
-
return fn(method.padEnd(7));
|
|
3836
|
-
}
|
|
3837
|
-
__name(colorMethod, "colorMethod");
|
|
3838
|
-
function formatUptime(seconds) {
|
|
3839
|
-
const d = Math.floor(seconds / 86400);
|
|
3840
|
-
const h = Math.floor(seconds % 86400 / 3600);
|
|
3841
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
3842
|
-
const s = seconds % 60;
|
|
3843
|
-
const parts = [];
|
|
3844
|
-
if (d) parts.push(`${d}d`);
|
|
3845
|
-
if (h) parts.push(`${h}h`);
|
|
3846
|
-
if (m) parts.push(`${m}m`);
|
|
3847
|
-
parts.push(`${s}s`);
|
|
3848
|
-
return parts.join(" ");
|
|
3849
|
-
}
|
|
3850
|
-
__name(formatUptime, "formatUptime");
|
|
3851
|
-
async function fetchJson(url) {
|
|
3852
|
-
const res = await fetch(url, {
|
|
3853
|
-
signal: AbortSignal.timeout(5e3)
|
|
3854
|
-
});
|
|
3855
|
-
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
3856
|
-
return res.json();
|
|
3857
|
-
}
|
|
3858
|
-
__name(fetchJson, "fetchJson");
|
|
3859
|
-
async function fetchEndpoint(base, path) {
|
|
3860
|
-
try {
|
|
3861
|
-
return await fetchJson(`${base}${path}`);
|
|
3862
|
-
} catch {
|
|
3863
|
-
return null;
|
|
3864
|
-
}
|
|
3865
|
-
}
|
|
3866
|
-
__name(fetchEndpoint, "fetchEndpoint");
|
|
3867
|
-
async function fetchAll(base) {
|
|
3868
|
-
const [health, metrics, routes, container, ws] = await Promise.all([
|
|
3869
|
-
fetchEndpoint(base, "/health"),
|
|
3870
|
-
fetchEndpoint(base, "/metrics"),
|
|
3871
|
-
fetchEndpoint(base, "/routes"),
|
|
3872
|
-
fetchEndpoint(base, "/container"),
|
|
3873
|
-
fetchEndpoint(base, "/ws")
|
|
3874
|
-
]);
|
|
3875
|
-
return {
|
|
3876
|
-
health,
|
|
3877
|
-
metrics,
|
|
3878
|
-
routes,
|
|
3879
|
-
container,
|
|
3880
|
-
ws
|
|
3881
|
-
};
|
|
3882
|
-
}
|
|
3883
|
-
__name(fetchAll, "fetchAll");
|
|
3884
|
-
function printSummary(base, data) {
|
|
3885
|
-
const { health, metrics, routes, container, ws } = data;
|
|
3886
|
-
const line = dim("\u2500".repeat(60));
|
|
3887
|
-
console.log();
|
|
3888
|
-
console.log(bold(` KickJS Inspector`) + dim(` \u2192 ${base}`));
|
|
3889
|
-
console.log(line);
|
|
3890
|
-
if (health) {
|
|
3891
|
-
const statusText = health.status === "healthy" ? green("\u25CF healthy") : red("\u25CF " + health.status);
|
|
3892
|
-
console.log(` ${bold("Health:")} ${statusText}`);
|
|
3893
|
-
} else {
|
|
3894
|
-
console.log(` ${bold("Health:")} ${red("\u25CF unreachable")}`);
|
|
3895
|
-
}
|
|
3896
|
-
if (metrics) {
|
|
3897
|
-
const rate = ((metrics.errorRate ?? 0) * 100).toFixed(1);
|
|
3898
|
-
const rateColor = metrics.errorRate > 0.1 ? red : metrics.errorRate > 0 ? yellow : green;
|
|
3899
|
-
console.log(` ${bold("Uptime:")} ${formatUptime(metrics.uptimeSeconds)}`);
|
|
3900
|
-
console.log(` ${bold("Requests:")} ${metrics.requests}`);
|
|
3901
|
-
console.log(` ${bold("Errors:")} ${metrics.serverErrors} server, ${metrics.clientErrors ?? 0} client ${dim("(")}${rateColor(rate + "%")}${dim(")")}`);
|
|
3902
|
-
}
|
|
3903
|
-
if (container) {
|
|
3904
|
-
console.log(` ${bold("DI:")} ${container.count} bindings`);
|
|
3905
|
-
}
|
|
3906
|
-
if (ws && ws.enabled) {
|
|
3907
|
-
console.log(` ${bold("WS:")} ${ws.connections ?? 0} connections, ${ws.namespaces ?? 0} namespaces`);
|
|
3908
|
-
}
|
|
3909
|
-
if (routes?.routes?.length) {
|
|
3910
|
-
console.log();
|
|
3911
|
-
console.log(bold(" Routes"));
|
|
3912
|
-
console.log(line);
|
|
3913
|
-
console.log(` ${dim("METHOD")} ${dim("PATH".padEnd(36))} ${dim("CONTROLLER")}`);
|
|
3914
|
-
for (const r of routes.routes) {
|
|
3915
|
-
const path = r.path.length > 36 ? r.path.slice(0, 33) + "..." : r.path.padEnd(36);
|
|
3916
|
-
console.log(` ${colorMethod(r.method)} ${path} ${blue(r.controller)}.${dim(r.handler)}`);
|
|
3917
|
-
}
|
|
3918
|
-
}
|
|
3919
|
-
console.log(line);
|
|
3920
|
-
console.log();
|
|
3921
|
-
}
|
|
3922
|
-
__name(printSummary, "printSummary");
|
|
3923
|
-
function registerInspectCommand(program) {
|
|
3924
|
-
program.command("inspect [url]").description("Connect to a running KickJS app and display debug info").option("-p, --port <port>", "Override port").option("-w, --watch", "Poll every 5 seconds").option("-j, --json", "Output raw JSON").action(async (url, opts) => {
|
|
3925
|
-
let base = url ?? "http://localhost:3000";
|
|
3926
|
-
if (opts.port) {
|
|
3927
|
-
try {
|
|
3928
|
-
const parsed = new URL(base);
|
|
3929
|
-
parsed.port = opts.port;
|
|
3930
|
-
base = parsed.origin;
|
|
3931
|
-
} catch {
|
|
3932
|
-
base = `http://localhost:${opts.port}`;
|
|
3933
|
-
}
|
|
3934
|
-
}
|
|
3935
|
-
const debugBase = `${base.replace(/\/$/, "")}/_debug`;
|
|
3936
|
-
const run = /* @__PURE__ */ __name(async () => {
|
|
3937
|
-
try {
|
|
3938
|
-
const data = await fetchAll(debugBase);
|
|
3939
|
-
if (opts.json) {
|
|
3940
|
-
console.log(JSON.stringify(data, null, 2));
|
|
3941
|
-
} else {
|
|
3942
|
-
printSummary(base, data);
|
|
3943
|
-
}
|
|
3944
|
-
} catch (err) {
|
|
3945
|
-
if (opts.json) {
|
|
3946
|
-
console.log(JSON.stringify({
|
|
3947
|
-
error: String(err)
|
|
3948
|
-
}));
|
|
3949
|
-
} else {
|
|
3950
|
-
console.error(red(` \u2716 Could not connect to ${base}`));
|
|
3951
|
-
console.error(dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
3952
|
-
}
|
|
3953
|
-
if (!opts.watch) process.exitCode = 1;
|
|
3954
|
-
}
|
|
3955
|
-
}, "run");
|
|
3956
|
-
if (opts.watch) {
|
|
3957
|
-
const poll = /* @__PURE__ */ __name(async () => {
|
|
3958
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
3959
|
-
await run();
|
|
3960
|
-
}, "poll");
|
|
3961
|
-
await poll();
|
|
3962
|
-
setInterval(poll, 5e3);
|
|
3963
|
-
} else {
|
|
3964
|
-
await run();
|
|
3965
|
-
}
|
|
3966
|
-
});
|
|
3967
|
-
}
|
|
3968
|
-
__name(registerInspectCommand, "registerInspectCommand");
|
|
3969
|
-
|
|
3970
|
-
// src/commands/add.ts
|
|
3971
|
-
import { execSync as execSync3 } from "child_process";
|
|
3972
|
-
import { existsSync as existsSync4 } from "fs";
|
|
3973
|
-
import { resolve as resolve6 } from "path";
|
|
3974
|
-
var PACKAGE_REGISTRY = {
|
|
3975
|
-
// Core (already installed by kick new)
|
|
3976
|
-
core: {
|
|
3977
|
-
pkg: "@forinda/kickjs-core",
|
|
3978
|
-
peers: [],
|
|
3979
|
-
description: "DI container, decorators, reactivity"
|
|
3980
|
-
},
|
|
3981
|
-
http: {
|
|
3982
|
-
pkg: "@forinda/kickjs-http",
|
|
3983
|
-
peers: [
|
|
3984
|
-
"express"
|
|
3985
|
-
],
|
|
3986
|
-
description: "Express 5, routing, middleware"
|
|
3987
|
-
},
|
|
3988
|
-
config: {
|
|
3989
|
-
pkg: "@forinda/kickjs-config",
|
|
3990
|
-
peers: [],
|
|
3991
|
-
description: "Zod-based env validation"
|
|
3992
|
-
},
|
|
3993
|
-
cli: {
|
|
3994
|
-
pkg: "@forinda/kickjs-cli",
|
|
3995
|
-
peers: [],
|
|
3996
|
-
description: "CLI tool and code generators",
|
|
3997
|
-
dev: true
|
|
3998
|
-
},
|
|
3999
|
-
// API
|
|
4000
|
-
swagger: {
|
|
4001
|
-
pkg: "@forinda/kickjs-swagger",
|
|
4002
|
-
peers: [],
|
|
4003
|
-
description: "OpenAPI spec + Swagger UI + ReDoc"
|
|
4004
|
-
},
|
|
4005
|
-
graphql: {
|
|
4006
|
-
pkg: "@forinda/kickjs-graphql",
|
|
4007
|
-
peers: [
|
|
4008
|
-
"graphql"
|
|
4009
|
-
],
|
|
4010
|
-
description: "GraphQL resolvers + GraphiQL"
|
|
4011
|
-
},
|
|
4012
|
-
// Database
|
|
4013
|
-
drizzle: {
|
|
4014
|
-
pkg: "@forinda/kickjs-drizzle",
|
|
4015
|
-
peers: [
|
|
4016
|
-
"drizzle-orm"
|
|
4017
|
-
],
|
|
4018
|
-
description: "Drizzle ORM adapter + query builder"
|
|
4019
|
-
},
|
|
4020
|
-
prisma: {
|
|
4021
|
-
pkg: "@forinda/kickjs-prisma",
|
|
4022
|
-
peers: [
|
|
4023
|
-
"@prisma/client"
|
|
4024
|
-
],
|
|
4025
|
-
description: "Prisma adapter + query builder"
|
|
4026
|
-
},
|
|
4027
|
-
// Real-time
|
|
4028
|
-
ws: {
|
|
4029
|
-
pkg: "@forinda/kickjs-ws",
|
|
4030
|
-
peers: [
|
|
4031
|
-
"socket.io"
|
|
4032
|
-
],
|
|
4033
|
-
description: "WebSocket with @WsController decorators"
|
|
4034
|
-
},
|
|
4035
|
-
// Observability
|
|
4036
|
-
otel: {
|
|
4037
|
-
pkg: "@forinda/kickjs-otel",
|
|
4038
|
-
peers: [
|
|
4039
|
-
"@opentelemetry/api"
|
|
4040
|
-
],
|
|
4041
|
-
description: "OpenTelemetry tracing + metrics"
|
|
4042
|
-
},
|
|
4043
|
-
// DevTools
|
|
4044
|
-
devtools: {
|
|
4045
|
-
pkg: "@forinda/kickjs-devtools",
|
|
4046
|
-
peers: [],
|
|
4047
|
-
description: "Development dashboard \u2014 routes, DI, metrics, health",
|
|
4048
|
-
dev: true
|
|
4049
|
-
},
|
|
4050
|
-
// Auth
|
|
4051
|
-
auth: {
|
|
4052
|
-
pkg: "@forinda/kickjs-auth",
|
|
4053
|
-
peers: [
|
|
4054
|
-
"jsonwebtoken"
|
|
4055
|
-
],
|
|
4056
|
-
description: "Authentication \u2014 JWT, API key, and custom strategies"
|
|
4057
|
-
},
|
|
4058
|
-
// Mailer
|
|
4059
|
-
mailer: {
|
|
4060
|
-
pkg: "@forinda/kickjs-mailer",
|
|
4061
|
-
peers: [
|
|
4062
|
-
"nodemailer"
|
|
4063
|
-
],
|
|
4064
|
-
description: "Email sending \u2014 SMTP, Resend, SES, or custom provider"
|
|
4065
|
-
},
|
|
4066
|
-
// Cron
|
|
4067
|
-
cron: {
|
|
4068
|
-
pkg: "@forinda/kickjs-cron",
|
|
4069
|
-
peers: [
|
|
4070
|
-
"croner"
|
|
4071
|
-
],
|
|
4072
|
-
description: "Cron job scheduling (production-grade with croner)"
|
|
4073
|
-
},
|
|
4074
|
-
// Queue
|
|
4075
|
-
queue: {
|
|
4076
|
-
pkg: "@forinda/kickjs-queue",
|
|
4077
|
-
peers: [],
|
|
4078
|
-
description: "Queue adapter (BullMQ/RabbitMQ/Kafka)"
|
|
4079
|
-
},
|
|
4080
|
-
"queue:bullmq": {
|
|
4081
|
-
pkg: "@forinda/kickjs-queue",
|
|
4082
|
-
peers: [
|
|
4083
|
-
"bullmq",
|
|
4084
|
-
"ioredis"
|
|
4085
|
-
],
|
|
4086
|
-
description: "Queue with BullMQ + Redis"
|
|
4087
|
-
},
|
|
4088
|
-
"queue:rabbitmq": {
|
|
4089
|
-
pkg: "@forinda/kickjs-queue",
|
|
4090
|
-
peers: [
|
|
4091
|
-
"amqplib"
|
|
4092
|
-
],
|
|
4093
|
-
description: "Queue with RabbitMQ"
|
|
4094
|
-
},
|
|
4095
|
-
"queue:kafka": {
|
|
4096
|
-
pkg: "@forinda/kickjs-queue",
|
|
4097
|
-
peers: [
|
|
4098
|
-
"kafkajs"
|
|
4099
|
-
],
|
|
4100
|
-
description: "Queue with Kafka"
|
|
4101
|
-
},
|
|
4102
|
-
// Multi-tenancy
|
|
4103
|
-
"multi-tenant": {
|
|
4104
|
-
pkg: "@forinda/kickjs-multi-tenant",
|
|
4105
|
-
peers: [],
|
|
4106
|
-
description: "Tenant resolution middleware"
|
|
4107
|
-
},
|
|
4108
|
-
// Notifications
|
|
4109
|
-
notifications: {
|
|
4110
|
-
pkg: "@forinda/kickjs-notifications",
|
|
4111
|
-
peers: [],
|
|
4112
|
-
description: "Multi-channel notifications \u2014 email, Slack, Discord, webhook"
|
|
4113
|
-
},
|
|
4114
|
-
// Testing
|
|
4115
|
-
testing: {
|
|
4116
|
-
pkg: "@forinda/kickjs-testing",
|
|
4117
|
-
peers: [],
|
|
4118
|
-
description: "Test utilities and TestModule builder",
|
|
4119
|
-
dev: true
|
|
4120
|
-
}
|
|
4121
|
-
};
|
|
4122
|
-
function detectPackageManager() {
|
|
4123
|
-
if (existsSync4(resolve6("pnpm-lock.yaml"))) return "pnpm";
|
|
4124
|
-
if (existsSync4(resolve6("yarn.lock"))) return "yarn";
|
|
4125
|
-
return "npm";
|
|
4126
|
-
}
|
|
4127
|
-
__name(detectPackageManager, "detectPackageManager");
|
|
4128
|
-
function printPackageList() {
|
|
4129
|
-
console.log("\n Available KickJS packages:\n");
|
|
4130
|
-
const maxName = Math.max(...Object.keys(PACKAGE_REGISTRY).map((k) => k.length));
|
|
4131
|
-
for (const [name, info] of Object.entries(PACKAGE_REGISTRY)) {
|
|
4132
|
-
const padded = name.padEnd(maxName + 2);
|
|
4133
|
-
const peers = info.peers.length ? ` (+ ${info.peers.join(", ")})` : "";
|
|
4134
|
-
console.log(` ${padded} ${info.description}${peers}`);
|
|
4135
|
-
}
|
|
4136
|
-
console.log("\n Usage: kick add graphql drizzle otel");
|
|
4137
|
-
console.log(" kick add queue:bullmq");
|
|
4138
|
-
console.log();
|
|
4139
|
-
}
|
|
4140
|
-
__name(printPackageList, "printPackageList");
|
|
4141
|
-
function registerListCommand(program) {
|
|
4142
|
-
program.command("list").alias("ls").description("List all available KickJS packages").action(() => {
|
|
4143
|
-
printPackageList();
|
|
4144
|
-
});
|
|
4145
|
-
}
|
|
4146
|
-
__name(registerListCommand, "registerListCommand");
|
|
4147
|
-
function registerAddCommand(program) {
|
|
4148
|
-
program.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 all available packages").action(async (packages, opts) => {
|
|
4149
|
-
if (opts.list || packages.length === 0) {
|
|
4150
|
-
printPackageList();
|
|
4151
|
-
return;
|
|
4152
|
-
}
|
|
4153
|
-
const pm = opts.pm ?? detectPackageManager();
|
|
4154
|
-
const forceDevFlag = opts.dev;
|
|
4155
|
-
const prodDeps = /* @__PURE__ */ new Set();
|
|
4156
|
-
const devDeps = /* @__PURE__ */ new Set();
|
|
4157
|
-
const unknown = [];
|
|
4158
|
-
for (const name of packages) {
|
|
4159
|
-
const entry = PACKAGE_REGISTRY[name];
|
|
4160
|
-
if (!entry) {
|
|
4161
|
-
unknown.push(name);
|
|
4162
|
-
continue;
|
|
4163
|
-
}
|
|
4164
|
-
const target = forceDevFlag || entry.dev ? devDeps : prodDeps;
|
|
4165
|
-
target.add(entry.pkg);
|
|
4166
|
-
for (const peer of entry.peers) {
|
|
4167
|
-
target.add(peer);
|
|
4168
|
-
}
|
|
4169
|
-
}
|
|
4170
|
-
if (unknown.length > 0) {
|
|
4171
|
-
console.log(`
|
|
4172
|
-
Unknown packages: ${unknown.join(", ")}`);
|
|
4173
|
-
console.log(' Run "kick add --list" to see available packages.\n');
|
|
4174
|
-
if (prodDeps.size === 0 && devDeps.size === 0) return;
|
|
4175
|
-
}
|
|
4176
|
-
if (prodDeps.size > 0) {
|
|
4177
|
-
const deps = Array.from(prodDeps);
|
|
4178
|
-
const cmd = `${pm} add ${deps.join(" ")}`;
|
|
4179
|
-
console.log(`
|
|
4180
|
-
Installing ${deps.length} dependency(ies):`);
|
|
4181
|
-
for (const dep of deps) console.log(` + ${dep}`);
|
|
4182
|
-
console.log();
|
|
4183
|
-
try {
|
|
4184
|
-
execSync3(cmd, {
|
|
4185
|
-
stdio: "inherit"
|
|
4186
|
-
});
|
|
4187
|
-
} catch {
|
|
4188
|
-
console.log(`
|
|
2066
|
+
`)})}s(et,"registerInfoCommand");function tt(e,t){if(t?.commands?.length)for(let o of t.commands)To(e,o)}s(tt,"registerCustomCommands");function To(e,t){let o=e.command(t.name).description(t.description);if(t.aliases)for(let r of t.aliases)o.alias(r);o.allowUnknownOption(!0),o.argument("[args...]","Additional arguments passed to the command"),o.action(r=>{let n=r.join(" "),i=Array.isArray(t.steps)?t.steps:[t.steps];for(let d of i){let c=n?`${d} ${n}`:d;console.log(` $ ${c}`);try{E(c)}catch{console.error(` Command failed: ${t.name}`),process.exitCode=1;return}}})}s(To,"registerSingleCommand");var T=s(e=>`\x1B[${e}m`,"esc"),P=T("0"),S=s(e=>`${T("1")}${e}${P}`,"bold"),I=s(e=>`${T("2")}${e}${P}`,"dim"),De=s(e=>`${T("32")}${e}${P}`,"green"),K=s(e=>`${T("31")}${e}${P}`,"red"),ot=s(e=>`${T("33")}${e}${P}`,"yellow"),jo=s(e=>`${T("36")}${e}${P}`,"cyan"),Po=s(e=>`${T("35")}${e}${P}`,"magenta"),Eo=s(e=>`${T("34")}${e}${P}`,"blue"),Ao={GET:De,POST:jo,PUT:ot,PATCH:Po,DELETE:K};function Uo(e){return(Ao[e]??I)(e.padEnd(7))}s(Uo,"colorMethod");function zo(e){let t=Math.floor(e/86400),o=Math.floor(e%86400/3600),r=Math.floor(e%3600/60),n=e%60,i=[];return t&&i.push(`${t}d`),o&&i.push(`${o}h`),r&&i.push(`${r}m`),i.push(`${n}s`),i.join(" ")}s(zo,"formatUptime");async function qo(e){let t=await fetch(e,{signal:AbortSignal.timeout(5e3)});if(!t.ok)throw new Error(`${t.status} ${t.statusText}`);return t.json()}s(qo,"fetchJson");async function H(e,t){try{return await qo(`${e}${t}`)}catch{return null}}s(H,"fetchEndpoint");async function _o(e){let[t,o,r,n,i]=await Promise.all([H(e,"/health"),H(e,"/metrics"),H(e,"/routes"),H(e,"/container"),H(e,"/ws")]);return{health:t,metrics:o,routes:r,container:n,ws:i}}s(_o,"fetchAll");function Mo(e,t){let{health:o,metrics:r,routes:n,container:i,ws:d}=t,c=I("\u2500".repeat(60));if(console.log(),console.log(S(" KickJS Inspector")+I(` \u2192 ${e}`)),console.log(c),o){let a=o.status==="healthy"?De("\u25CF healthy"):K("\u25CF "+o.status);console.log(` ${S("Health:")} ${a}`)}else console.log(` ${S("Health:")} ${K("\u25CF unreachable")}`);if(r){let a=((r.errorRate??0)*100).toFixed(1),p=r.errorRate>.1?K:r.errorRate>0?ot:De;console.log(` ${S("Uptime:")} ${zo(r.uptimeSeconds)}`),console.log(` ${S("Requests:")} ${r.requests}`),console.log(` ${S("Errors:")} ${r.serverErrors} server, ${r.clientErrors??0} client ${I("(")}${p(a+"%")}${I(")")}`)}if(i&&console.log(` ${S("DI:")} ${i.count} bindings`),d&&d.enabled&&console.log(` ${S("WS:")} ${d.connections??0} connections, ${d.namespaces??0} namespaces`),n?.routes?.length){console.log(),console.log(S(" Routes")),console.log(c),console.log(` ${I("METHOD")} ${I("PATH".padEnd(36))} ${I("CONTROLLER")}`);for(let a of n.routes){let p=a.path.length>36?a.path.slice(0,33)+"...":a.path.padEnd(36);console.log(` ${Uo(a.method)} ${p} ${Eo(a.controller)}.${I(a.handler)}`)}}console.log(c),console.log()}s(Mo,"printSummary");function rt(e){e.command("inspect [url]").description("Connect to a running KickJS app and display debug info").option("-p, --port <port>","Override port").option("-w, --watch","Poll every 5 seconds").option("-j, --json","Output raw JSON").action(async(t,o)=>{let r=t??"http://localhost:3000";if(o.port)try{let d=new URL(r);d.port=o.port,r=d.origin}catch{r=`http://localhost:${o.port}`}let n=`${r.replace(/\/$/,"")}/_debug`,i=s(async()=>{try{let d=await _o(n);o.json?console.log(JSON.stringify(d,null,2)):Mo(r,d)}catch(d){o.json?console.log(JSON.stringify({error:String(d)})):(console.error(K(` \u2716 Could not connect to ${r}`)),console.error(I(` ${d instanceof Error?d.message:String(d)}`))),o.watch||(process.exitCode=1)}},"run");if(o.watch){let d=s(async()=>{process.stdout.write("\x1B[2J\x1B[H"),await i()},"poll");await d(),setInterval(d,5e3)}else await i()})}s(rt,"registerInspectCommand");import{execSync as it}from"child_process";import{existsSync as nt}from"fs";import{resolve as st}from"path";var Oe={core:{pkg:"@forinda/kickjs-core",peers:[],description:"DI container, decorators, reactivity"},http:{pkg:"@forinda/kickjs-http",peers:["express"],description:"Express 5, routing, middleware"},config:{pkg:"@forinda/kickjs-config",peers:[],description:"Zod-based env validation"},cli:{pkg:"@forinda/kickjs-cli",peers:[],description:"CLI tool and code generators",dev:!0},swagger:{pkg:"@forinda/kickjs-swagger",peers:[],description:"OpenAPI spec + Swagger UI + ReDoc"},graphql:{pkg:"@forinda/kickjs-graphql",peers:["graphql"],description:"GraphQL resolvers + GraphiQL"},drizzle:{pkg:"@forinda/kickjs-drizzle",peers:["drizzle-orm"],description:"Drizzle ORM adapter + query builder"},prisma:{pkg:"@forinda/kickjs-prisma",peers:["@prisma/client"],description:"Prisma adapter + query builder"},ws:{pkg:"@forinda/kickjs-ws",peers:["socket.io"],description:"WebSocket with @WsController decorators"},otel:{pkg:"@forinda/kickjs-otel",peers:["@opentelemetry/api"],description:"OpenTelemetry tracing + metrics"},devtools:{pkg:"@forinda/kickjs-devtools",peers:[],description:"Development dashboard \u2014 routes, DI, metrics, health",dev:!0},auth:{pkg:"@forinda/kickjs-auth",peers:["jsonwebtoken"],description:"Authentication \u2014 JWT, API key, and custom strategies"},mailer:{pkg:"@forinda/kickjs-mailer",peers:["nodemailer"],description:"Email sending \u2014 SMTP, Resend, SES, or custom provider"},cron:{pkg:"@forinda/kickjs-cron",peers:["croner"],description:"Cron job scheduling (production-grade with croner)"},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"},"multi-tenant":{pkg:"@forinda/kickjs-multi-tenant",peers:[],description:"Tenant resolution middleware"},notifications:{pkg:"@forinda/kickjs-notifications",peers:[],description:"Multi-channel notifications \u2014 email, Slack, Discord, webhook"},testing:{pkg:"@forinda/kickjs-testing",peers:[],description:"Test utilities and TestModule builder",dev:!0}};function bo(){return nt(st("pnpm-lock.yaml"))?"pnpm":nt(st("yarn.lock"))?"yarn":"npm"}s(bo,"detectPackageManager");function at(){console.log(`
|
|
2067
|
+
Available KickJS packages:
|
|
2068
|
+
`);let e=Math.max(...Object.keys(Oe).map(t=>t.length));for(let[t,o]of Object.entries(Oe)){let r=t.padEnd(e+2),n=o.peers.length?` (+ ${o.peers.join(", ")})`:"";console.log(` ${r} ${o.description}${n}`)}console.log(`
|
|
2069
|
+
Usage: kick add graphql drizzle otel`),console.log(" kick add queue:bullmq"),console.log()}s(at,"printPackageList");function dt(e){e.command("list").alias("ls").description("List all available KickJS packages").action(()=>{at()})}s(dt,"registerListCommand");function ct(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 all available packages").action(async(t,o)=>{if(o.list||t.length===0){at();return}let r=o.pm??bo(),n=o.dev,i=new Set,d=new Set,c=[];for(let a of t){let p=Oe[a];if(!p){c.push(a);continue}let m=n||p.dev?d:i;m.add(p.pkg);for(let u of p.peers)m.add(u)}if(!(c.length>0&&(console.log(`
|
|
2070
|
+
Unknown packages: ${c.join(", ")}`),console.log(` Run "kick add --list" to see available packages.
|
|
2071
|
+
`),i.size===0&&d.size===0))){if(i.size>0){let a=Array.from(i),p=`${r} add ${a.join(" ")}`;console.log(`
|
|
2072
|
+
Installing ${a.length} dependency(ies):`);for(let m of a)console.log(` + ${m}`);console.log();try{it(p,{stdio:"inherit"})}catch{console.log(`
|
|
4189
2073
|
Installation failed. Run manually:
|
|
4190
|
-
${
|
|
4191
|
-
`)
|
|
4192
|
-
|
|
4193
|
-
}
|
|
4194
|
-
if (devDeps.size > 0) {
|
|
4195
|
-
const deps = Array.from(devDeps);
|
|
4196
|
-
const cmd = `${pm} add -D ${deps.join(" ")}`;
|
|
4197
|
-
console.log(`
|
|
4198
|
-
Installing ${deps.length} dev dependency(ies):`);
|
|
4199
|
-
for (const dep of deps) console.log(` + ${dep} (dev)`);
|
|
4200
|
-
console.log();
|
|
4201
|
-
try {
|
|
4202
|
-
execSync3(cmd, {
|
|
4203
|
-
stdio: "inherit"
|
|
4204
|
-
});
|
|
4205
|
-
} catch {
|
|
4206
|
-
console.log(`
|
|
2074
|
+
${p}
|
|
2075
|
+
`)}}if(d.size>0){let a=Array.from(d),p=`${r} add -D ${a.join(" ")}`;console.log(`
|
|
2076
|
+
Installing ${a.length} dev dependency(ies):`);for(let m of a)console.log(` + ${m} (dev)`);console.log();try{it(p,{stdio:"inherit"})}catch{console.log(`
|
|
4207
2077
|
Installation failed. Run manually:
|
|
4208
|
-
${
|
|
4209
|
-
`)
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
}
|
|
4215
|
-
__name(registerAddCommand, "registerAddCommand");
|
|
4216
|
-
|
|
4217
|
-
// src/commands/tinker.ts
|
|
4218
|
-
import { resolve as resolve7, join as join17 } from "path";
|
|
4219
|
-
import { existsSync as existsSync5 } from "fs";
|
|
4220
|
-
import { pathToFileURL } from "url";
|
|
4221
|
-
import { fork } from "child_process";
|
|
4222
|
-
function registerTinkerCommand(program) {
|
|
4223
|
-
program.command("tinker").description("Interactive REPL with DI container and services loaded").option("-e, --entry <file>", "Entry file to load", "src/index.ts").action(async (opts) => {
|
|
4224
|
-
const cwd = process.cwd();
|
|
4225
|
-
const entryPath = resolve7(cwd, opts.entry);
|
|
4226
|
-
if (!existsSync5(entryPath)) {
|
|
4227
|
-
console.error(`
|
|
4228
|
-
Error: ${opts.entry} not found.
|
|
4229
|
-
`);
|
|
4230
|
-
process.exit(1);
|
|
4231
|
-
}
|
|
4232
|
-
const tsxBin = findBin(cwd, "tsx");
|
|
4233
|
-
if (!tsxBin) {
|
|
4234
|
-
console.error("\n Error: tsx not found. Install it: pnpm add -D tsx\n");
|
|
4235
|
-
process.exit(1);
|
|
4236
|
-
}
|
|
4237
|
-
const tinkerScript = generateTinkerScript(entryPath, opts.entry);
|
|
4238
|
-
const tmpFile = join17(cwd, ".kick-tinker.mjs");
|
|
4239
|
-
const { writeFileSync, unlinkSync } = await import("fs");
|
|
4240
|
-
writeFileSync(tmpFile, tinkerScript, "utf-8");
|
|
4241
|
-
try {
|
|
4242
|
-
const child = fork(tmpFile, [], {
|
|
4243
|
-
cwd,
|
|
4244
|
-
execPath: tsxBin,
|
|
4245
|
-
stdio: "inherit"
|
|
4246
|
-
});
|
|
4247
|
-
await new Promise((resolve8) => {
|
|
4248
|
-
child.on("exit", () => resolve8());
|
|
4249
|
-
});
|
|
4250
|
-
} finally {
|
|
4251
|
-
try {
|
|
4252
|
-
unlinkSync(tmpFile);
|
|
4253
|
-
} catch {
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
});
|
|
4257
|
-
}
|
|
4258
|
-
__name(registerTinkerCommand, "registerTinkerCommand");
|
|
4259
|
-
function generateTinkerScript(entryPath, displayPath) {
|
|
4260
|
-
const entryUrl = pathToFileURL(entryPath).href;
|
|
4261
|
-
return `
|
|
2078
|
+
${p}
|
|
2079
|
+
`)}}console.log(` Done!
|
|
2080
|
+
`)}})}s(ct,"registerAddCommand");import{resolve as pt,join as mt}from"path";import{existsSync as lt}from"fs";import{pathToFileURL as Qo}from"url";import{fork as Go}from"child_process";function ut(e){e.command("tinker").description("Interactive REPL with DI container and services loaded").option("-e, --entry <file>","Entry file to load","src/index.ts").action(async t=>{let o=process.cwd(),r=pt(o,t.entry);lt(r)||(console.error(`
|
|
2081
|
+
Error: ${t.entry} not found.
|
|
2082
|
+
`),process.exit(1));let n=Lo(o,"tsx");n||(console.error(`
|
|
2083
|
+
Error: tsx not found. Install it: pnpm add -D tsx
|
|
2084
|
+
`),process.exit(1));let i=Fo(r,t.entry),d=mt(o,".kick-tinker.mjs"),{writeFileSync:c,unlinkSync:a}=await import("fs");c(d,i,"utf-8");try{let p=Go(d,[],{cwd:o,execPath:n,stdio:"inherit"});await new Promise(m=>{p.on("exit",()=>m())})}finally{try{a(d)}catch{}}})}s(ut,"registerTinkerCommand");function Fo(e,t){let o=Qo(e).href;return`
|
|
4262
2085
|
import 'reflect-metadata'
|
|
4263
2086
|
|
|
4264
2087
|
// Prevent bootstrap() from starting the HTTP server
|
|
4265
2088
|
process.env.KICK_TINKER = '1'
|
|
4266
2089
|
|
|
4267
2090
|
console.log('\\n \u{1F527} KickJS Tinker')
|
|
4268
|
-
console.log(' Loading: ${
|
|
2091
|
+
console.log(' Loading: ${t}\\n')
|
|
4269
2092
|
|
|
4270
2093
|
// Load core
|
|
4271
2094
|
let Container, Logger, HttpException, HttpStatus
|
|
@@ -4283,7 +2106,7 @@ try {
|
|
|
4283
2106
|
|
|
4284
2107
|
// Load entry to trigger decorator registration
|
|
4285
2108
|
try {
|
|
4286
|
-
await import('${
|
|
2109
|
+
await import('${o}')
|
|
4287
2110
|
} catch (err) {
|
|
4288
2111
|
console.warn(' Warning: ' + err.message)
|
|
4289
2112
|
console.warn(' Container may be partially initialized.\\n')
|
|
@@ -4312,44 +2135,4 @@ server.on('exit', () => {
|
|
|
4312
2135
|
console.log('\\n Goodbye!\\n')
|
|
4313
2136
|
process.exit(0)
|
|
4314
2137
|
})
|
|
4315
|
-
|
|
4316
|
-
}
|
|
4317
|
-
__name(generateTinkerScript, "generateTinkerScript");
|
|
4318
|
-
function findBin(startDir, name) {
|
|
4319
|
-
let dir = startDir;
|
|
4320
|
-
while (true) {
|
|
4321
|
-
const candidate = join17(dir, "node_modules", ".bin", name);
|
|
4322
|
-
if (existsSync5(candidate)) return candidate;
|
|
4323
|
-
const parent = resolve7(dir, "..");
|
|
4324
|
-
if (parent === dir) break;
|
|
4325
|
-
dir = parent;
|
|
4326
|
-
}
|
|
4327
|
-
return null;
|
|
4328
|
-
}
|
|
4329
|
-
__name(findBin, "findBin");
|
|
4330
|
-
|
|
4331
|
-
// src/cli.ts
|
|
4332
|
-
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
4333
|
-
var pkg = JSON.parse(readFileSync2(join18(__dirname2, "..", "package.json"), "utf-8"));
|
|
4334
|
-
async function main() {
|
|
4335
|
-
const program = new Command();
|
|
4336
|
-
program.name("kick").description("KickJS \u2014 A production-grade, decorator-driven Node.js framework").version(pkg.version);
|
|
4337
|
-
const config = await loadKickConfig(process.cwd());
|
|
4338
|
-
registerInitCommand(program);
|
|
4339
|
-
registerGenerateCommand(program);
|
|
4340
|
-
registerRunCommands(program);
|
|
4341
|
-
registerInfoCommand(program);
|
|
4342
|
-
registerInspectCommand(program);
|
|
4343
|
-
registerAddCommand(program);
|
|
4344
|
-
registerListCommand(program);
|
|
4345
|
-
registerTinkerCommand(program);
|
|
4346
|
-
registerCustomCommands(program, config);
|
|
4347
|
-
program.showHelpAfterError();
|
|
4348
|
-
await program.parseAsync(process.argv);
|
|
4349
|
-
}
|
|
4350
|
-
__name(main, "main");
|
|
4351
|
-
main().catch((err) => {
|
|
4352
|
-
console.error(err instanceof Error ? err.message : err);
|
|
4353
|
-
process.exitCode = 1;
|
|
4354
|
-
});
|
|
4355
|
-
//# sourceMappingURL=cli.js.map
|
|
2138
|
+
`}s(Fo,"generateTinkerScript");function Lo(e,t){let o=e;for(;;){let r=mt(o,"node_modules",".bin",t);if(lt(r))return r;let n=pt(o,"..");if(n===o)break;o=n}return null}s(Lo,"findBin");var Wo=Bo(Ko(import.meta.url)),Jo=JSON.parse(Yo(Ho(Wo,"..","package.json"),"utf-8"));async function Vo(){let e=new No;e.name("kick").description("KickJS \u2014 A production-grade, decorator-driven Node.js framework").version(Jo.version);let t=await C(process.cwd());Pe(e),Je(e),Xe(e),et(e),rt(e),ct(e),dt(e),ut(e),tt(e,t),e.showHelpAfterError(),await e.parseAsync(process.argv)}s(Vo,"main");Vo().catch(e=>{console.error(e instanceof Error?e.message:e),process.exitCode=1});
|