@forinda/kickjs-cli 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +526 -65
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.js +119 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
5
5
|
// src/cli.ts
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { readFileSync as readFileSync2 } from "fs";
|
|
8
|
-
import { dirname as dirname3, join as
|
|
8
|
+
import { dirname as dirname3, join as join14 } from "path";
|
|
9
9
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
@@ -44,11 +44,35 @@ var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
|
44
44
|
var cliPkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
45
45
|
var KICKJS_VERSION = `^${cliPkg.version}`;
|
|
46
46
|
async function initProject(options) {
|
|
47
|
-
const { name, directory, packageManager = "pnpm" } = options;
|
|
47
|
+
const { name, directory, packageManager = "pnpm", template = "rest" } = options;
|
|
48
48
|
const dir = directory;
|
|
49
49
|
console.log(`
|
|
50
50
|
Creating KickJS project: ${name}
|
|
51
51
|
`);
|
|
52
|
+
const baseDeps = {
|
|
53
|
+
"@forinda/kickjs-core": KICKJS_VERSION,
|
|
54
|
+
"@forinda/kickjs-http": KICKJS_VERSION,
|
|
55
|
+
"@forinda/kickjs-config": KICKJS_VERSION,
|
|
56
|
+
express: "^5.1.0",
|
|
57
|
+
"reflect-metadata": "^0.2.2",
|
|
58
|
+
zod: "^4.3.6",
|
|
59
|
+
pino: "^10.3.1",
|
|
60
|
+
"pino-pretty": "^13.1.3"
|
|
61
|
+
};
|
|
62
|
+
if (template !== "minimal") {
|
|
63
|
+
baseDeps["@forinda/kickjs-swagger"] = KICKJS_VERSION;
|
|
64
|
+
}
|
|
65
|
+
if (template === "graphql") {
|
|
66
|
+
baseDeps["@forinda/kickjs-graphql"] = KICKJS_VERSION;
|
|
67
|
+
baseDeps["graphql"] = "^16.11.0";
|
|
68
|
+
}
|
|
69
|
+
if (template === "microservice") {
|
|
70
|
+
baseDeps["@forinda/kickjs-queue"] = KICKJS_VERSION;
|
|
71
|
+
baseDeps["@forinda/kickjs-otel"] = KICKJS_VERSION;
|
|
72
|
+
}
|
|
73
|
+
if (template === "ddd") {
|
|
74
|
+
baseDeps["@forinda/kickjs-swagger"] = KICKJS_VERSION;
|
|
75
|
+
}
|
|
52
76
|
await writeFileSafe(join(dir, "package.json"), JSON.stringify({
|
|
53
77
|
name,
|
|
54
78
|
version: cliPkg.version,
|
|
@@ -64,17 +88,7 @@ async function initProject(options) {
|
|
|
64
88
|
lint: "eslint src/",
|
|
65
89
|
format: "prettier --write src/"
|
|
66
90
|
},
|
|
67
|
-
dependencies:
|
|
68
|
-
"@forinda/kickjs-core": KICKJS_VERSION,
|
|
69
|
-
"@forinda/kickjs-http": KICKJS_VERSION,
|
|
70
|
-
"@forinda/kickjs-config": KICKJS_VERSION,
|
|
71
|
-
"@forinda/kickjs-swagger": KICKJS_VERSION,
|
|
72
|
-
express: "^5.1.0",
|
|
73
|
-
"reflect-metadata": "^0.2.2",
|
|
74
|
-
zod: "^4.3.6",
|
|
75
|
-
pino: "^10.3.1",
|
|
76
|
-
"pino-pretty": "^13.1.3"
|
|
77
|
-
},
|
|
91
|
+
dependencies: baseDeps,
|
|
78
92
|
devDependencies: {
|
|
79
93
|
"@forinda/kickjs-cli": KICKJS_VERSION,
|
|
80
94
|
"@swc/core": "^1.7.28",
|
|
@@ -165,27 +179,18 @@ NODE_ENV=development
|
|
|
165
179
|
await writeFileSafe(join(dir, ".env.example"), `PORT=3000
|
|
166
180
|
NODE_ENV=development
|
|
167
181
|
`);
|
|
168
|
-
await writeFileSafe(join(dir, "src/index.ts"),
|
|
169
|
-
import { bootstrap } from '@forinda/kickjs-http'
|
|
170
|
-
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
171
|
-
import { modules } from './modules'
|
|
172
|
-
|
|
173
|
-
bootstrap({
|
|
174
|
-
modules,
|
|
175
|
-
adapters: [
|
|
176
|
-
new SwaggerAdapter({
|
|
177
|
-
info: { title: '${name}', version: '${cliPkg.version}' },
|
|
178
|
-
}),
|
|
179
|
-
],
|
|
180
|
-
})
|
|
181
|
-
`);
|
|
182
|
+
await writeFileSafe(join(dir, "src/index.ts"), getEntryFile(name, template));
|
|
182
183
|
await writeFileSafe(join(dir, "src/modules/index.ts"), `import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
183
184
|
|
|
184
185
|
export const modules: AppModuleClass[] = []
|
|
185
186
|
`);
|
|
187
|
+
if (template === "graphql") {
|
|
188
|
+
await writeFileSafe(join(dir, "src/resolvers/.gitkeep"), "");
|
|
189
|
+
}
|
|
186
190
|
await writeFileSafe(join(dir, "kick.config.ts"), `import { defineConfig } from '@forinda/kickjs-cli'
|
|
187
191
|
|
|
188
192
|
export default defineConfig({
|
|
193
|
+
pattern: '${template}',
|
|
189
194
|
modulesDir: 'src/modules',
|
|
190
195
|
defaultRepo: 'inmemory',
|
|
191
196
|
|
|
@@ -266,17 +271,103 @@ export default defineConfig({
|
|
|
266
271
|
console.log(" Next steps:");
|
|
267
272
|
if (needsCd) console.log(` cd ${name}`);
|
|
268
273
|
if (!options.installDeps) console.log(` ${packageManager} install`);
|
|
269
|
-
|
|
274
|
+
const genHint = {
|
|
275
|
+
rest: "kick g module user",
|
|
276
|
+
graphql: "kick g resolver user",
|
|
277
|
+
ddd: "kick g module user --repo drizzle",
|
|
278
|
+
microservice: "kick g module user && kick g job email",
|
|
279
|
+
minimal: "# add your routes to src/index.ts"
|
|
280
|
+
};
|
|
281
|
+
console.log(` ${genHint[template] ?? genHint.rest}`);
|
|
270
282
|
console.log(" kick dev");
|
|
271
283
|
console.log();
|
|
272
284
|
console.log(" Commands:");
|
|
273
285
|
console.log(" kick dev Start dev server with Vite HMR");
|
|
274
286
|
console.log(" kick build Production build via Vite");
|
|
275
287
|
console.log(" kick start Run production build");
|
|
276
|
-
console.log(
|
|
288
|
+
console.log(` kick g module X Generate a DDD module`);
|
|
289
|
+
if (template === "graphql") console.log(" kick g resolver X Generate a GraphQL resolver");
|
|
290
|
+
if (template === "microservice") console.log(" kick g job X Generate a queue job processor");
|
|
277
291
|
console.log();
|
|
278
292
|
}
|
|
279
293
|
__name(initProject, "initProject");
|
|
294
|
+
function getEntryFile(name, template) {
|
|
295
|
+
switch (template) {
|
|
296
|
+
case "graphql":
|
|
297
|
+
return `import 'reflect-metadata'
|
|
298
|
+
import { bootstrap } from '@forinda/kickjs-http'
|
|
299
|
+
import { DevToolsAdapter } from '@forinda/kickjs-http/devtools'
|
|
300
|
+
import { GraphQLAdapter } from '@forinda/kickjs-graphql'
|
|
301
|
+
import { modules } from './modules'
|
|
302
|
+
|
|
303
|
+
// Import your resolvers here
|
|
304
|
+
// import { UserResolver } from './resolvers/user.resolver'
|
|
305
|
+
|
|
306
|
+
bootstrap({
|
|
307
|
+
modules,
|
|
308
|
+
adapters: [
|
|
309
|
+
new DevToolsAdapter(),
|
|
310
|
+
new GraphQLAdapter({
|
|
311
|
+
resolvers: [/* UserResolver */],
|
|
312
|
+
// Add custom type definitions here:
|
|
313
|
+
// typeDefs: userTypeDefs,
|
|
314
|
+
}),
|
|
315
|
+
],
|
|
316
|
+
})
|
|
317
|
+
`;
|
|
318
|
+
case "microservice":
|
|
319
|
+
return `import 'reflect-metadata'
|
|
320
|
+
import { bootstrap } from '@forinda/kickjs-http'
|
|
321
|
+
import { DevToolsAdapter } from '@forinda/kickjs-http/devtools'
|
|
322
|
+
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
323
|
+
import { OtelAdapter } from '@forinda/kickjs-otel'
|
|
324
|
+
// import { QueueAdapter, BullMQProvider } from '@forinda/kickjs-queue'
|
|
325
|
+
import { modules } from './modules'
|
|
326
|
+
|
|
327
|
+
bootstrap({
|
|
328
|
+
modules,
|
|
329
|
+
adapters: [
|
|
330
|
+
new OtelAdapter({ serviceName: '${name}' }),
|
|
331
|
+
new DevToolsAdapter(),
|
|
332
|
+
new SwaggerAdapter({
|
|
333
|
+
info: { title: '${name}', version: '${cliPkg.version}' },
|
|
334
|
+
}),
|
|
335
|
+
// Uncomment when Redis is available:
|
|
336
|
+
// new QueueAdapter({
|
|
337
|
+
// provider: new BullMQProvider({ host: 'localhost', port: 6379 }),
|
|
338
|
+
// }),
|
|
339
|
+
],
|
|
340
|
+
})
|
|
341
|
+
`;
|
|
342
|
+
case "minimal":
|
|
343
|
+
return `import 'reflect-metadata'
|
|
344
|
+
import { bootstrap } from '@forinda/kickjs-http'
|
|
345
|
+
import { modules } from './modules'
|
|
346
|
+
|
|
347
|
+
bootstrap({ modules })
|
|
348
|
+
`;
|
|
349
|
+
case "ddd":
|
|
350
|
+
case "rest":
|
|
351
|
+
default:
|
|
352
|
+
return `import 'reflect-metadata'
|
|
353
|
+
import { bootstrap } from '@forinda/kickjs-http'
|
|
354
|
+
import { DevToolsAdapter } from '@forinda/kickjs-http/devtools'
|
|
355
|
+
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
356
|
+
import { modules } from './modules'
|
|
357
|
+
|
|
358
|
+
bootstrap({
|
|
359
|
+
modules,
|
|
360
|
+
adapters: [
|
|
361
|
+
new DevToolsAdapter(),
|
|
362
|
+
new SwaggerAdapter({
|
|
363
|
+
info: { title: '${name}', version: '${cliPkg.version}' },
|
|
364
|
+
}),
|
|
365
|
+
],
|
|
366
|
+
})
|
|
367
|
+
`;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
__name(getEntryFile, "getEntryFile");
|
|
280
371
|
|
|
281
372
|
// src/commands/init.ts
|
|
282
373
|
function ask(question, defaultValue) {
|
|
@@ -312,7 +403,7 @@ async function confirm(question, defaultYes = true) {
|
|
|
312
403
|
}
|
|
313
404
|
__name(confirm, "confirm");
|
|
314
405
|
function registerInitCommand(program) {
|
|
315
|
-
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").action(async (name, opts) => {
|
|
406
|
+
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 | microservice | minimal").action(async (name, opts) => {
|
|
316
407
|
console.log();
|
|
317
408
|
if (!name) {
|
|
318
409
|
name = await ask("Project name", "my-api");
|
|
@@ -354,6 +445,24 @@ function registerInitCommand(program) {
|
|
|
354
445
|
}
|
|
355
446
|
}
|
|
356
447
|
}
|
|
448
|
+
let template = opts.template;
|
|
449
|
+
if (!template) {
|
|
450
|
+
template = await choose("Project template:", [
|
|
451
|
+
"REST API (Express + Swagger)",
|
|
452
|
+
"GraphQL API (GraphQL + GraphiQL)",
|
|
453
|
+
"DDD (Domain-Driven Design modules)",
|
|
454
|
+
"Microservice (REST + Queue worker)",
|
|
455
|
+
"Minimal (bare Express)"
|
|
456
|
+
], 0);
|
|
457
|
+
const templateMap = {
|
|
458
|
+
"REST API (Express + Swagger)": "rest",
|
|
459
|
+
"GraphQL API (GraphQL + GraphiQL)": "graphql",
|
|
460
|
+
"DDD (Domain-Driven Design modules)": "ddd",
|
|
461
|
+
"Microservice (REST + Queue worker)": "microservice",
|
|
462
|
+
"Minimal (bare Express)": "minimal"
|
|
463
|
+
};
|
|
464
|
+
template = templateMap[template] ?? "rest";
|
|
465
|
+
}
|
|
357
466
|
let packageManager = opts.pm;
|
|
358
467
|
if (!packageManager) {
|
|
359
468
|
packageManager = await choose("Package manager:", [
|
|
@@ -379,7 +488,8 @@ function registerInitCommand(program) {
|
|
|
379
488
|
directory,
|
|
380
489
|
packageManager,
|
|
381
490
|
initGit,
|
|
382
|
-
installDeps
|
|
491
|
+
installDeps,
|
|
492
|
+
template
|
|
383
493
|
});
|
|
384
494
|
});
|
|
385
495
|
}
|
|
@@ -1418,10 +1528,10 @@ async function confirm2(message) {
|
|
|
1418
1528
|
input: process.stdin,
|
|
1419
1529
|
output: process.stdout
|
|
1420
1530
|
});
|
|
1421
|
-
return new Promise((
|
|
1531
|
+
return new Promise((resolve5) => {
|
|
1422
1532
|
rl.question(` ${message} (y/N) `, (answer) => {
|
|
1423
1533
|
rl.close();
|
|
1424
|
-
|
|
1534
|
+
resolve5(answer.trim().toLowerCase() === "y");
|
|
1425
1535
|
});
|
|
1426
1536
|
});
|
|
1427
1537
|
}
|
|
@@ -1474,6 +1584,136 @@ export default defineConfig({
|
|
|
1474
1584
|
}
|
|
1475
1585
|
__name(generateConfig, "generateConfig");
|
|
1476
1586
|
|
|
1587
|
+
// src/generators/resolver.ts
|
|
1588
|
+
import { join as join10 } from "path";
|
|
1589
|
+
async function generateResolver(options) {
|
|
1590
|
+
const { name, outDir } = options;
|
|
1591
|
+
const pascal = toPascalCase(name);
|
|
1592
|
+
const kebab = toKebabCase(name);
|
|
1593
|
+
const camel = toCamelCase(name);
|
|
1594
|
+
const files = [];
|
|
1595
|
+
const write = /* @__PURE__ */ __name(async (relativePath, content) => {
|
|
1596
|
+
const fullPath = join10(outDir, relativePath);
|
|
1597
|
+
await writeFileSafe(fullPath, content);
|
|
1598
|
+
files.push(fullPath);
|
|
1599
|
+
}, "write");
|
|
1600
|
+
await write(`${kebab}.resolver.ts`, `import { Service } from '@forinda/kickjs-core'
|
|
1601
|
+
import { Resolver, Query, Mutation, Arg } from '@forinda/kickjs-graphql'
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* ${pascal} GraphQL Resolver
|
|
1605
|
+
*
|
|
1606
|
+
* Decorators:
|
|
1607
|
+
* @Resolver(typeName?) \u2014 marks this class as a GraphQL resolver
|
|
1608
|
+
* @Query(name?, { returnType?, description? }) \u2014 defines a query field
|
|
1609
|
+
* @Mutation(name?, { returnType?, description? }) \u2014 defines a mutation field
|
|
1610
|
+
* @Arg(name, type?) \u2014 marks a method parameter as a GraphQL argument
|
|
1611
|
+
*/
|
|
1612
|
+
@Service()
|
|
1613
|
+
@Resolver('${pascal}')
|
|
1614
|
+
export class ${pascal}Resolver {
|
|
1615
|
+
private items: Array<{ id: string; name: string }> = []
|
|
1616
|
+
|
|
1617
|
+
@Query('${camel}s', { returnType: '[${pascal}]', description: 'List all ${camel}s' })
|
|
1618
|
+
findAll() {
|
|
1619
|
+
return this.items
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
@Query('${camel}', { returnType: '${pascal}', description: 'Get a ${camel} by ID' })
|
|
1623
|
+
findById(@Arg('id', 'ID!') id: string) {
|
|
1624
|
+
return this.items.find((item) => item.id === id) ?? null
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
@Mutation('create${pascal}', { returnType: '${pascal}', description: 'Create a new ${camel}' })
|
|
1628
|
+
create(@Arg('name', 'String!') name: string) {
|
|
1629
|
+
const item = { id: String(this.items.length + 1), name }
|
|
1630
|
+
this.items.push(item)
|
|
1631
|
+
return item
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
@Mutation('update${pascal}', { returnType: '${pascal}', description: 'Update a ${camel}' })
|
|
1635
|
+
update(@Arg('id', 'ID!') id: string, @Arg('name', 'String!') name: string) {
|
|
1636
|
+
const item = this.items.find((i) => i.id === id)
|
|
1637
|
+
if (item) item.name = name
|
|
1638
|
+
return item
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
@Mutation('delete${pascal}', { returnType: 'Boolean', description: 'Delete a ${camel}' })
|
|
1642
|
+
remove(@Arg('id', 'ID!') id: string) {
|
|
1643
|
+
const idx = this.items.findIndex((i) => i.id === id)
|
|
1644
|
+
if (idx === -1) return false
|
|
1645
|
+
this.items.splice(idx, 1)
|
|
1646
|
+
return true
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
`);
|
|
1650
|
+
await write(`${kebab}.typedefs.ts`, `/**
|
|
1651
|
+
* ${pascal} GraphQL type definitions.
|
|
1652
|
+
* Pass to GraphQLAdapter's typeDefs option to register custom types.
|
|
1653
|
+
*/
|
|
1654
|
+
export const ${camel}TypeDefs = \`
|
|
1655
|
+
type ${pascal} {
|
|
1656
|
+
id: ID!
|
|
1657
|
+
name: String!
|
|
1658
|
+
}
|
|
1659
|
+
\`
|
|
1660
|
+
`);
|
|
1661
|
+
return files;
|
|
1662
|
+
}
|
|
1663
|
+
__name(generateResolver, "generateResolver");
|
|
1664
|
+
|
|
1665
|
+
// src/generators/job.ts
|
|
1666
|
+
import { join as join11 } from "path";
|
|
1667
|
+
async function generateJob(options) {
|
|
1668
|
+
const { name, outDir } = options;
|
|
1669
|
+
const pascal = toPascalCase(name);
|
|
1670
|
+
const kebab = toKebabCase(name);
|
|
1671
|
+
const camel = toCamelCase(name);
|
|
1672
|
+
const queueName = options.queue ?? `${kebab}-queue`;
|
|
1673
|
+
const files = [];
|
|
1674
|
+
const write = /* @__PURE__ */ __name(async (relativePath, content) => {
|
|
1675
|
+
const fullPath = join11(outDir, relativePath);
|
|
1676
|
+
await writeFileSafe(fullPath, content);
|
|
1677
|
+
files.push(fullPath);
|
|
1678
|
+
}, "write");
|
|
1679
|
+
await write(`${kebab}.job.ts`, `import { Inject } from '@forinda/kickjs-core'
|
|
1680
|
+
import { Job, Process, QUEUE_MANAGER, type QueueService } from '@forinda/kickjs-queue'
|
|
1681
|
+
|
|
1682
|
+
/**
|
|
1683
|
+
* ${pascal} Job Processor
|
|
1684
|
+
*
|
|
1685
|
+
* Decorators:
|
|
1686
|
+
* @Job(queueName) \u2014 marks this class as a job processor for a queue
|
|
1687
|
+
* @Process(jobName?) \u2014 marks a method as the handler for a specific job type
|
|
1688
|
+
* - Without a name: handles all jobs in the queue
|
|
1689
|
+
* - With a name: handles only jobs matching that name
|
|
1690
|
+
*
|
|
1691
|
+
* To add jobs to this queue from a service or controller:
|
|
1692
|
+
* @Inject(QUEUE_MANAGER) private queue: QueueService
|
|
1693
|
+
* await this.queue.add('${queueName}', '${camel}', { ... })
|
|
1694
|
+
*/
|
|
1695
|
+
@Job('${queueName}')
|
|
1696
|
+
export class ${pascal}Job {
|
|
1697
|
+
@Process()
|
|
1698
|
+
async handle(job: { name: string; data: any; id?: string }) {
|
|
1699
|
+
console.log(\`Processing \${job.name} (id: \${job.id})\`, job.data)
|
|
1700
|
+
|
|
1701
|
+
// TODO: Implement job logic here
|
|
1702
|
+
// Example:
|
|
1703
|
+
// await this.emailService.send(job.data.to, job.data.subject, job.data.body)
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
@Process('${camel}.priority')
|
|
1707
|
+
async handlePriority(job: { name: string; data: any; id?: string }) {
|
|
1708
|
+
console.log(\`Priority job: \${job.name}\`, job.data)
|
|
1709
|
+
// Handle high-priority variant of this job
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
`);
|
|
1713
|
+
return files;
|
|
1714
|
+
}
|
|
1715
|
+
__name(generateJob, "generateJob");
|
|
1716
|
+
|
|
1477
1717
|
// src/commands/generate.ts
|
|
1478
1718
|
function printGenerated(files) {
|
|
1479
1719
|
const cwd = process.cwd();
|
|
@@ -1540,6 +1780,21 @@ function registerGenerateCommand(program) {
|
|
|
1540
1780
|
});
|
|
1541
1781
|
printGenerated(files);
|
|
1542
1782
|
});
|
|
1783
|
+
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) => {
|
|
1784
|
+
const files = await generateResolver({
|
|
1785
|
+
name,
|
|
1786
|
+
outDir: resolve2(opts.out)
|
|
1787
|
+
});
|
|
1788
|
+
printGenerated(files);
|
|
1789
|
+
});
|
|
1790
|
+
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) => {
|
|
1791
|
+
const files = await generateJob({
|
|
1792
|
+
name,
|
|
1793
|
+
outDir: resolve2(opts.out),
|
|
1794
|
+
queue: opts.queue
|
|
1795
|
+
});
|
|
1796
|
+
printGenerated(files);
|
|
1797
|
+
});
|
|
1543
1798
|
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", "inmemory").option("-f, --force", "Overwrite existing kick.config.ts without prompting").action(async (opts) => {
|
|
1544
1799
|
const files = await generateConfig({
|
|
1545
1800
|
outDir: resolve2("."),
|
|
@@ -1552,6 +1807,10 @@ function registerGenerateCommand(program) {
|
|
|
1552
1807
|
}
|
|
1553
1808
|
__name(registerGenerateCommand, "registerGenerateCommand");
|
|
1554
1809
|
|
|
1810
|
+
// src/commands/run.ts
|
|
1811
|
+
import { cpSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
1812
|
+
import { resolve as resolve3, join as join13 } from "path";
|
|
1813
|
+
|
|
1555
1814
|
// src/utils/shell.ts
|
|
1556
1815
|
import { execSync as execSync2 } from "child_process";
|
|
1557
1816
|
function runShellCommand(command, cwd) {
|
|
@@ -1562,6 +1821,42 @@ function runShellCommand(command, cwd) {
|
|
|
1562
1821
|
}
|
|
1563
1822
|
__name(runShellCommand, "runShellCommand");
|
|
1564
1823
|
|
|
1824
|
+
// src/config.ts
|
|
1825
|
+
import { readFile as readFile3, access as access2 } from "fs/promises";
|
|
1826
|
+
import { join as join12 } from "path";
|
|
1827
|
+
var CONFIG_FILES = [
|
|
1828
|
+
"kick.config.ts",
|
|
1829
|
+
"kick.config.js",
|
|
1830
|
+
"kick.config.mjs",
|
|
1831
|
+
"kick.config.json"
|
|
1832
|
+
];
|
|
1833
|
+
async function loadKickConfig(cwd) {
|
|
1834
|
+
for (const filename of CONFIG_FILES) {
|
|
1835
|
+
const filepath = join12(cwd, filename);
|
|
1836
|
+
try {
|
|
1837
|
+
await access2(filepath);
|
|
1838
|
+
} catch {
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (filename.endsWith(".json")) {
|
|
1842
|
+
const content = await readFile3(filepath, "utf-8");
|
|
1843
|
+
return JSON.parse(content);
|
|
1844
|
+
}
|
|
1845
|
+
try {
|
|
1846
|
+
const { pathToFileURL } = await import("url");
|
|
1847
|
+
const mod = await import(pathToFileURL(filepath).href);
|
|
1848
|
+
return mod.default ?? mod;
|
|
1849
|
+
} catch (err) {
|
|
1850
|
+
if (filename.endsWith(".ts")) {
|
|
1851
|
+
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.`);
|
|
1852
|
+
}
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return null;
|
|
1857
|
+
}
|
|
1858
|
+
__name(loadKickConfig, "loadKickConfig");
|
|
1859
|
+
|
|
1565
1860
|
// src/commands/run.ts
|
|
1566
1861
|
function registerRunCommands(program) {
|
|
1567
1862
|
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) => {
|
|
@@ -1579,9 +1874,32 @@ function registerRunCommands(program) {
|
|
|
1579
1874
|
} catch {
|
|
1580
1875
|
}
|
|
1581
1876
|
});
|
|
1582
|
-
program.command("build").description("Build for production via Vite").action(() => {
|
|
1877
|
+
program.command("build").description("Build for production via Vite").action(async () => {
|
|
1583
1878
|
console.log("\n Building for production...\n");
|
|
1584
1879
|
runShellCommand("npx vite build");
|
|
1880
|
+
const config = await loadKickConfig(process.cwd());
|
|
1881
|
+
const copyDirs = config?.copyDirs ?? [];
|
|
1882
|
+
if (copyDirs.length > 0) {
|
|
1883
|
+
console.log("\n Copying directories to dist...");
|
|
1884
|
+
for (const entry of copyDirs) {
|
|
1885
|
+
const src = typeof entry === "string" ? entry : entry.src;
|
|
1886
|
+
const dest = typeof entry === "string" ? join13("dist", entry) : entry.dest ?? join13("dist", src);
|
|
1887
|
+
const srcPath = resolve3(src);
|
|
1888
|
+
const destPath = resolve3(dest);
|
|
1889
|
+
if (!existsSync3(srcPath)) {
|
|
1890
|
+
console.log(` \u26A0 Skipped ${src} (not found)`);
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
mkdirSync(destPath, {
|
|
1894
|
+
recursive: true
|
|
1895
|
+
});
|
|
1896
|
+
cpSync(srcPath, destPath, {
|
|
1897
|
+
recursive: true
|
|
1898
|
+
});
|
|
1899
|
+
console.log(` \u2713 ${src} \u2192 ${dest}`);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
console.log("\n Build complete.\n");
|
|
1585
1903
|
});
|
|
1586
1904
|
program.command("start").description("Start production server").option("-e, --entry <file>", "Entry file", "dist/index.js").option("-p, --port <port>", "Port number").action((opts) => {
|
|
1587
1905
|
const envVars = [
|
|
@@ -1813,45 +2131,187 @@ function registerInspectCommand(program) {
|
|
|
1813
2131
|
}
|
|
1814
2132
|
__name(registerInspectCommand, "registerInspectCommand");
|
|
1815
2133
|
|
|
1816
|
-
// src/
|
|
1817
|
-
import {
|
|
1818
|
-
import {
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
]
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2134
|
+
// src/commands/add.ts
|
|
2135
|
+
import { execSync as execSync3 } from "child_process";
|
|
2136
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2137
|
+
import { resolve as resolve4 } from "path";
|
|
2138
|
+
var PACKAGE_REGISTRY = {
|
|
2139
|
+
// Core (already installed by kick new)
|
|
2140
|
+
core: {
|
|
2141
|
+
pkg: "@forinda/kickjs-core",
|
|
2142
|
+
peers: [],
|
|
2143
|
+
description: "DI container, decorators, reactivity"
|
|
2144
|
+
},
|
|
2145
|
+
http: {
|
|
2146
|
+
pkg: "@forinda/kickjs-http",
|
|
2147
|
+
peers: [
|
|
2148
|
+
"express"
|
|
2149
|
+
],
|
|
2150
|
+
description: "Express 5, routing, middleware"
|
|
2151
|
+
},
|
|
2152
|
+
config: {
|
|
2153
|
+
pkg: "@forinda/kickjs-config",
|
|
2154
|
+
peers: [],
|
|
2155
|
+
description: "Zod-based env validation"
|
|
2156
|
+
},
|
|
2157
|
+
cli: {
|
|
2158
|
+
pkg: "@forinda/kickjs-cli",
|
|
2159
|
+
peers: [],
|
|
2160
|
+
description: "CLI tool and code generators"
|
|
2161
|
+
},
|
|
2162
|
+
// API
|
|
2163
|
+
swagger: {
|
|
2164
|
+
pkg: "@forinda/kickjs-swagger",
|
|
2165
|
+
peers: [],
|
|
2166
|
+
description: "OpenAPI spec + Swagger UI + ReDoc"
|
|
2167
|
+
},
|
|
2168
|
+
graphql: {
|
|
2169
|
+
pkg: "@forinda/kickjs-graphql",
|
|
2170
|
+
peers: [
|
|
2171
|
+
"graphql"
|
|
2172
|
+
],
|
|
2173
|
+
description: "GraphQL resolvers + GraphiQL"
|
|
2174
|
+
},
|
|
2175
|
+
// Database
|
|
2176
|
+
drizzle: {
|
|
2177
|
+
pkg: "@forinda/kickjs-drizzle",
|
|
2178
|
+
peers: [
|
|
2179
|
+
"drizzle-orm"
|
|
2180
|
+
],
|
|
2181
|
+
description: "Drizzle ORM adapter + query builder"
|
|
2182
|
+
},
|
|
2183
|
+
prisma: {
|
|
2184
|
+
pkg: "@forinda/kickjs-prisma",
|
|
2185
|
+
peers: [
|
|
2186
|
+
"@prisma/client"
|
|
2187
|
+
],
|
|
2188
|
+
description: "Prisma adapter + query builder"
|
|
2189
|
+
},
|
|
2190
|
+
// Real-time
|
|
2191
|
+
ws: {
|
|
2192
|
+
pkg: "@forinda/kickjs-ws",
|
|
2193
|
+
peers: [
|
|
2194
|
+
"socket.io"
|
|
2195
|
+
],
|
|
2196
|
+
description: "WebSocket with @WsController decorators"
|
|
2197
|
+
},
|
|
2198
|
+
// Observability
|
|
2199
|
+
otel: {
|
|
2200
|
+
pkg: "@forinda/kickjs-otel",
|
|
2201
|
+
peers: [
|
|
2202
|
+
"@opentelemetry/api"
|
|
2203
|
+
],
|
|
2204
|
+
description: "OpenTelemetry tracing + metrics"
|
|
2205
|
+
},
|
|
2206
|
+
// Queue
|
|
2207
|
+
queue: {
|
|
2208
|
+
pkg: "@forinda/kickjs-queue",
|
|
2209
|
+
peers: [],
|
|
2210
|
+
description: "Queue adapter (BullMQ/RabbitMQ/Kafka)"
|
|
2211
|
+
},
|
|
2212
|
+
"queue:bullmq": {
|
|
2213
|
+
pkg: "@forinda/kickjs-queue",
|
|
2214
|
+
peers: [
|
|
2215
|
+
"bullmq",
|
|
2216
|
+
"ioredis"
|
|
2217
|
+
],
|
|
2218
|
+
description: "Queue with BullMQ + Redis"
|
|
2219
|
+
},
|
|
2220
|
+
"queue:rabbitmq": {
|
|
2221
|
+
pkg: "@forinda/kickjs-queue",
|
|
2222
|
+
peers: [
|
|
2223
|
+
"amqplib"
|
|
2224
|
+
],
|
|
2225
|
+
description: "Queue with RabbitMQ"
|
|
2226
|
+
},
|
|
2227
|
+
"queue:kafka": {
|
|
2228
|
+
pkg: "@forinda/kickjs-queue",
|
|
2229
|
+
peers: [
|
|
2230
|
+
"kafkajs"
|
|
2231
|
+
],
|
|
2232
|
+
description: "Queue with Kafka"
|
|
2233
|
+
},
|
|
2234
|
+
// Multi-tenancy
|
|
2235
|
+
"multi-tenant": {
|
|
2236
|
+
pkg: "@forinda/kickjs-multi-tenant",
|
|
2237
|
+
peers: [],
|
|
2238
|
+
description: "Tenant resolution middleware"
|
|
2239
|
+
},
|
|
2240
|
+
// Testing
|
|
2241
|
+
testing: {
|
|
2242
|
+
pkg: "@forinda/kickjs-testing",
|
|
2243
|
+
peers: [],
|
|
2244
|
+
description: "Test utilities and TestModule builder"
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
function detectPackageManager() {
|
|
2248
|
+
if (existsSync4(resolve4("pnpm-lock.yaml"))) return "pnpm";
|
|
2249
|
+
if (existsSync4(resolve4("yarn.lock"))) return "yarn";
|
|
2250
|
+
return "npm";
|
|
2251
|
+
}
|
|
2252
|
+
__name(detectPackageManager, "detectPackageManager");
|
|
2253
|
+
function registerAddCommand(program) {
|
|
2254
|
+
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) => {
|
|
2255
|
+
if (opts.list || packages.length === 0) {
|
|
2256
|
+
console.log("\n Available KickJS packages:\n");
|
|
2257
|
+
const maxName = Math.max(...Object.keys(PACKAGE_REGISTRY).map((k) => k.length));
|
|
2258
|
+
for (const [name, info] of Object.entries(PACKAGE_REGISTRY)) {
|
|
2259
|
+
const padded = name.padEnd(maxName + 2);
|
|
2260
|
+
const peers = info.peers.length ? ` (+ ${info.peers.join(", ")})` : "";
|
|
2261
|
+
console.log(` ${padded} ${info.description}${peers}`);
|
|
2262
|
+
}
|
|
2263
|
+
console.log("\n Usage: kick add graphql drizzle otel");
|
|
2264
|
+
console.log(" kick add queue:bullmq");
|
|
2265
|
+
console.log();
|
|
2266
|
+
return;
|
|
1832
2267
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
2268
|
+
const pm = opts.pm ?? detectPackageManager();
|
|
2269
|
+
const devFlag = opts.dev ? " -D" : "";
|
|
2270
|
+
const allDeps = /* @__PURE__ */ new Set();
|
|
2271
|
+
const unknown = [];
|
|
2272
|
+
for (const name of packages) {
|
|
2273
|
+
const entry = PACKAGE_REGISTRY[name];
|
|
2274
|
+
if (!entry) {
|
|
2275
|
+
unknown.push(name);
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
allDeps.add(entry.pkg);
|
|
2279
|
+
for (const peer of entry.peers) {
|
|
2280
|
+
allDeps.add(peer);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (unknown.length > 0) {
|
|
2284
|
+
console.log(`
|
|
2285
|
+
Unknown packages: ${unknown.join(", ")}`);
|
|
2286
|
+
console.log(' Run "kick add --list" to see available packages.\n');
|
|
2287
|
+
if (allDeps.size === 0) return;
|
|
2288
|
+
}
|
|
2289
|
+
const depsArray = Array.from(allDeps);
|
|
2290
|
+
const installCmd = `${pm} add${devFlag} ${depsArray.join(" ")}`;
|
|
2291
|
+
console.log(`
|
|
2292
|
+
Installing ${depsArray.length} package(s):`);
|
|
2293
|
+
for (const dep of depsArray) {
|
|
2294
|
+
console.log(` + ${dep}`);
|
|
1836
2295
|
}
|
|
2296
|
+
console.log();
|
|
1837
2297
|
try {
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2298
|
+
execSync3(installCmd, {
|
|
2299
|
+
stdio: "inherit"
|
|
2300
|
+
});
|
|
2301
|
+
console.log("\n Done!\n");
|
|
2302
|
+
} catch {
|
|
2303
|
+
console.log(`
|
|
2304
|
+
Installation failed. Run manually:
|
|
2305
|
+
${installCmd}
|
|
2306
|
+
`);
|
|
1846
2307
|
}
|
|
1847
|
-
}
|
|
1848
|
-
return null;
|
|
2308
|
+
});
|
|
1849
2309
|
}
|
|
1850
|
-
__name(
|
|
2310
|
+
__name(registerAddCommand, "registerAddCommand");
|
|
1851
2311
|
|
|
1852
2312
|
// src/cli.ts
|
|
1853
2313
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
1854
|
-
var pkg = JSON.parse(readFileSync2(
|
|
2314
|
+
var pkg = JSON.parse(readFileSync2(join14(__dirname2, "..", "package.json"), "utf-8"));
|
|
1855
2315
|
async function main() {
|
|
1856
2316
|
const program = new Command();
|
|
1857
2317
|
program.name("kick").description("KickJS \u2014 A production-grade, decorator-driven Node.js framework").version(pkg.version);
|
|
@@ -1861,6 +2321,7 @@ async function main() {
|
|
|
1861
2321
|
registerRunCommands(program);
|
|
1862
2322
|
registerInfoCommand(program);
|
|
1863
2323
|
registerInspectCommand(program);
|
|
2324
|
+
registerAddCommand(program);
|
|
1864
2325
|
registerCustomCommands(program, config);
|
|
1865
2326
|
program.showHelpAfterError();
|
|
1866
2327
|
await program.parseAsync(process.argv);
|