@forinda/kickjs-cli 0.6.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 +693 -66
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.js +127 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,9 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
4
4
|
|
|
5
5
|
// src/cli.ts
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
8
|
+
import { dirname as dirname3, join as join14 } from "path";
|
|
9
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
10
|
|
|
8
11
|
// src/commands/init.ts
|
|
9
12
|
import { resolve, basename } from "path";
|
|
@@ -11,8 +14,10 @@ import { createInterface } from "readline";
|
|
|
11
14
|
import { existsSync, readdirSync, rmSync } from "fs";
|
|
12
15
|
|
|
13
16
|
// src/generators/project.ts
|
|
14
|
-
import { join } from "path";
|
|
17
|
+
import { join, dirname as dirname2 } from "path";
|
|
15
18
|
import { execSync } from "child_process";
|
|
19
|
+
import { readFileSync } from "fs";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
16
21
|
|
|
17
22
|
// src/utils/fs.ts
|
|
18
23
|
import { writeFile, mkdir, access, readFile } from "fs/promises";
|
|
@@ -35,15 +40,42 @@ async function fileExists(filePath) {
|
|
|
35
40
|
__name(fileExists, "fileExists");
|
|
36
41
|
|
|
37
42
|
// src/generators/project.ts
|
|
43
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
44
|
+
var cliPkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
45
|
+
var KICKJS_VERSION = `^${cliPkg.version}`;
|
|
38
46
|
async function initProject(options) {
|
|
39
|
-
const { name, directory, packageManager = "pnpm" } = options;
|
|
47
|
+
const { name, directory, packageManager = "pnpm", template = "rest" } = options;
|
|
40
48
|
const dir = directory;
|
|
41
49
|
console.log(`
|
|
42
50
|
Creating KickJS project: ${name}
|
|
43
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
|
+
}
|
|
44
76
|
await writeFileSafe(join(dir, "package.json"), JSON.stringify({
|
|
45
77
|
name,
|
|
46
|
-
version:
|
|
78
|
+
version: cliPkg.version,
|
|
47
79
|
type: "module",
|
|
48
80
|
scripts: {
|
|
49
81
|
dev: "kick dev",
|
|
@@ -56,19 +88,9 @@ async function initProject(options) {
|
|
|
56
88
|
lint: "eslint src/",
|
|
57
89
|
format: "prettier --write src/"
|
|
58
90
|
},
|
|
59
|
-
dependencies:
|
|
60
|
-
"@forinda/kickjs-core": "^0.1.0",
|
|
61
|
-
"@forinda/kickjs-http": "^0.1.0",
|
|
62
|
-
"@forinda/kickjs-config": "^0.1.0",
|
|
63
|
-
"@forinda/kickjs-swagger": "^0.1.0",
|
|
64
|
-
express: "^5.1.0",
|
|
65
|
-
"reflect-metadata": "^0.2.2",
|
|
66
|
-
zod: "^4.3.6",
|
|
67
|
-
pino: "^10.3.1",
|
|
68
|
-
"pino-pretty": "^13.1.3"
|
|
69
|
-
},
|
|
91
|
+
dependencies: baseDeps,
|
|
70
92
|
devDependencies: {
|
|
71
|
-
"@forinda/kickjs-cli":
|
|
93
|
+
"@forinda/kickjs-cli": KICKJS_VERSION,
|
|
72
94
|
"@swc/core": "^1.7.28",
|
|
73
95
|
"@types/express": "^5.0.6",
|
|
74
96
|
"@types/node": "^24.5.2",
|
|
@@ -157,27 +179,18 @@ NODE_ENV=development
|
|
|
157
179
|
await writeFileSafe(join(dir, ".env.example"), `PORT=3000
|
|
158
180
|
NODE_ENV=development
|
|
159
181
|
`);
|
|
160
|
-
await writeFileSafe(join(dir, "src/index.ts"),
|
|
161
|
-
import { bootstrap } from '@forinda/kickjs-http'
|
|
162
|
-
import { SwaggerAdapter } from '@forinda/kickjs-swagger'
|
|
163
|
-
import { modules } from './modules'
|
|
164
|
-
|
|
165
|
-
bootstrap({
|
|
166
|
-
modules,
|
|
167
|
-
adapters: [
|
|
168
|
-
new SwaggerAdapter({
|
|
169
|
-
info: { title: '${name}', version: '0.1.0' },
|
|
170
|
-
}),
|
|
171
|
-
],
|
|
172
|
-
})
|
|
173
|
-
`);
|
|
182
|
+
await writeFileSafe(join(dir, "src/index.ts"), getEntryFile(name, template));
|
|
174
183
|
await writeFileSafe(join(dir, "src/modules/index.ts"), `import type { AppModuleClass } from '@forinda/kickjs-core'
|
|
175
184
|
|
|
176
185
|
export const modules: AppModuleClass[] = []
|
|
177
186
|
`);
|
|
187
|
+
if (template === "graphql") {
|
|
188
|
+
await writeFileSafe(join(dir, "src/resolvers/.gitkeep"), "");
|
|
189
|
+
}
|
|
178
190
|
await writeFileSafe(join(dir, "kick.config.ts"), `import { defineConfig } from '@forinda/kickjs-cli'
|
|
179
191
|
|
|
180
192
|
export default defineConfig({
|
|
193
|
+
pattern: '${template}',
|
|
181
194
|
modulesDir: 'src/modules',
|
|
182
195
|
defaultRepo: 'inmemory',
|
|
183
196
|
|
|
@@ -258,17 +271,103 @@ export default defineConfig({
|
|
|
258
271
|
console.log(" Next steps:");
|
|
259
272
|
if (needsCd) console.log(` cd ${name}`);
|
|
260
273
|
if (!options.installDeps) console.log(` ${packageManager} install`);
|
|
261
|
-
|
|
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}`);
|
|
262
282
|
console.log(" kick dev");
|
|
263
283
|
console.log();
|
|
264
284
|
console.log(" Commands:");
|
|
265
285
|
console.log(" kick dev Start dev server with Vite HMR");
|
|
266
286
|
console.log(" kick build Production build via Vite");
|
|
267
287
|
console.log(" kick start Run production build");
|
|
268
|
-
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");
|
|
269
291
|
console.log();
|
|
270
292
|
}
|
|
271
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");
|
|
272
371
|
|
|
273
372
|
// src/commands/init.ts
|
|
274
373
|
function ask(question, defaultValue) {
|
|
@@ -304,7 +403,7 @@ async function confirm(question, defaultYes = true) {
|
|
|
304
403
|
}
|
|
305
404
|
__name(confirm, "confirm");
|
|
306
405
|
function registerInitCommand(program) {
|
|
307
|
-
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) => {
|
|
308
407
|
console.log();
|
|
309
408
|
if (!name) {
|
|
310
409
|
name = await ask("Project name", "my-api");
|
|
@@ -346,6 +445,24 @@ function registerInitCommand(program) {
|
|
|
346
445
|
}
|
|
347
446
|
}
|
|
348
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
|
+
}
|
|
349
466
|
let packageManager = opts.pm;
|
|
350
467
|
if (!packageManager) {
|
|
351
468
|
packageManager = await choose("Package manager:", [
|
|
@@ -371,7 +488,8 @@ function registerInitCommand(program) {
|
|
|
371
488
|
directory,
|
|
372
489
|
packageManager,
|
|
373
490
|
initGit,
|
|
374
|
-
installDeps
|
|
491
|
+
installDeps,
|
|
492
|
+
template
|
|
375
493
|
});
|
|
376
494
|
});
|
|
377
495
|
}
|
|
@@ -1410,10 +1528,10 @@ async function confirm2(message) {
|
|
|
1410
1528
|
input: process.stdin,
|
|
1411
1529
|
output: process.stdout
|
|
1412
1530
|
});
|
|
1413
|
-
return new Promise((
|
|
1531
|
+
return new Promise((resolve5) => {
|
|
1414
1532
|
rl.question(` ${message} (y/N) `, (answer) => {
|
|
1415
1533
|
rl.close();
|
|
1416
|
-
|
|
1534
|
+
resolve5(answer.trim().toLowerCase() === "y");
|
|
1417
1535
|
});
|
|
1418
1536
|
});
|
|
1419
1537
|
}
|
|
@@ -1466,6 +1584,136 @@ export default defineConfig({
|
|
|
1466
1584
|
}
|
|
1467
1585
|
__name(generateConfig, "generateConfig");
|
|
1468
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
|
+
|
|
1469
1717
|
// src/commands/generate.ts
|
|
1470
1718
|
function printGenerated(files) {
|
|
1471
1719
|
const cwd = process.cwd();
|
|
@@ -1532,6 +1780,21 @@ function registerGenerateCommand(program) {
|
|
|
1532
1780
|
});
|
|
1533
1781
|
printGenerated(files);
|
|
1534
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
|
+
});
|
|
1535
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) => {
|
|
1536
1799
|
const files = await generateConfig({
|
|
1537
1800
|
outDir: resolve2("."),
|
|
@@ -1544,6 +1807,10 @@ function registerGenerateCommand(program) {
|
|
|
1544
1807
|
}
|
|
1545
1808
|
__name(registerGenerateCommand, "registerGenerateCommand");
|
|
1546
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
|
+
|
|
1547
1814
|
// src/utils/shell.ts
|
|
1548
1815
|
import { execSync as execSync2 } from "child_process";
|
|
1549
1816
|
function runShellCommand(command, cwd) {
|
|
@@ -1554,6 +1821,42 @@ function runShellCommand(command, cwd) {
|
|
|
1554
1821
|
}
|
|
1555
1822
|
__name(runShellCommand, "runShellCommand");
|
|
1556
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
|
+
|
|
1557
1860
|
// src/commands/run.ts
|
|
1558
1861
|
function registerRunCommands(program) {
|
|
1559
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) => {
|
|
@@ -1571,9 +1874,32 @@ function registerRunCommands(program) {
|
|
|
1571
1874
|
} catch {
|
|
1572
1875
|
}
|
|
1573
1876
|
});
|
|
1574
|
-
program.command("build").description("Build for production via Vite").action(() => {
|
|
1877
|
+
program.command("build").description("Build for production via Vite").action(async () => {
|
|
1575
1878
|
console.log("\n Building for production...\n");
|
|
1576
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");
|
|
1577
1903
|
});
|
|
1578
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) => {
|
|
1579
1905
|
const envVars = [
|
|
@@ -1650,51 +1976,352 @@ function registerSingleCommand(program, def) {
|
|
|
1650
1976
|
}
|
|
1651
1977
|
__name(registerSingleCommand, "registerSingleCommand");
|
|
1652
1978
|
|
|
1653
|
-
// src/
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
var
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1979
|
+
// src/commands/inspect.ts
|
|
1980
|
+
var esc = /* @__PURE__ */ __name((code) => `\x1B[${code}m`, "esc");
|
|
1981
|
+
var reset = esc("0");
|
|
1982
|
+
var bold = /* @__PURE__ */ __name((s) => `${esc("1")}${s}${reset}`, "bold");
|
|
1983
|
+
var dim = /* @__PURE__ */ __name((s) => `${esc("2")}${s}${reset}`, "dim");
|
|
1984
|
+
var green = /* @__PURE__ */ __name((s) => `${esc("32")}${s}${reset}`, "green");
|
|
1985
|
+
var red = /* @__PURE__ */ __name((s) => `${esc("31")}${s}${reset}`, "red");
|
|
1986
|
+
var yellow = /* @__PURE__ */ __name((s) => `${esc("33")}${s}${reset}`, "yellow");
|
|
1987
|
+
var cyan = /* @__PURE__ */ __name((s) => `${esc("36")}${s}${reset}`, "cyan");
|
|
1988
|
+
var magenta = /* @__PURE__ */ __name((s) => `${esc("35")}${s}${reset}`, "magenta");
|
|
1989
|
+
var blue = /* @__PURE__ */ __name((s) => `${esc("34")}${s}${reset}`, "blue");
|
|
1990
|
+
var METHOD_COLORS = {
|
|
1991
|
+
GET: green,
|
|
1992
|
+
POST: cyan,
|
|
1993
|
+
PUT: yellow,
|
|
1994
|
+
PATCH: magenta,
|
|
1995
|
+
DELETE: red
|
|
1996
|
+
};
|
|
1997
|
+
function colorMethod(method) {
|
|
1998
|
+
const fn = METHOD_COLORS[method] ?? dim;
|
|
1999
|
+
return fn(method.padEnd(7));
|
|
2000
|
+
}
|
|
2001
|
+
__name(colorMethod, "colorMethod");
|
|
2002
|
+
function formatUptime(seconds) {
|
|
2003
|
+
const d = Math.floor(seconds / 86400);
|
|
2004
|
+
const h = Math.floor(seconds % 86400 / 3600);
|
|
2005
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
2006
|
+
const s = seconds % 60;
|
|
2007
|
+
const parts = [];
|
|
2008
|
+
if (d) parts.push(`${d}d`);
|
|
2009
|
+
if (h) parts.push(`${h}h`);
|
|
2010
|
+
if (m) parts.push(`${m}m`);
|
|
2011
|
+
parts.push(`${s}s`);
|
|
2012
|
+
return parts.join(" ");
|
|
2013
|
+
}
|
|
2014
|
+
__name(formatUptime, "formatUptime");
|
|
2015
|
+
async function fetchJson(url) {
|
|
2016
|
+
const res = await fetch(url, {
|
|
2017
|
+
signal: AbortSignal.timeout(5e3)
|
|
2018
|
+
});
|
|
2019
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
2020
|
+
return res.json();
|
|
2021
|
+
}
|
|
2022
|
+
__name(fetchJson, "fetchJson");
|
|
2023
|
+
async function fetchEndpoint(base, path) {
|
|
2024
|
+
try {
|
|
2025
|
+
return await fetchJson(`${base}${path}`);
|
|
2026
|
+
} catch {
|
|
2027
|
+
return null;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
__name(fetchEndpoint, "fetchEndpoint");
|
|
2031
|
+
async function fetchAll(base) {
|
|
2032
|
+
const [health, metrics, routes, container, ws] = await Promise.all([
|
|
2033
|
+
fetchEndpoint(base, "/health"),
|
|
2034
|
+
fetchEndpoint(base, "/metrics"),
|
|
2035
|
+
fetchEndpoint(base, "/routes"),
|
|
2036
|
+
fetchEndpoint(base, "/container"),
|
|
2037
|
+
fetchEndpoint(base, "/ws")
|
|
2038
|
+
]);
|
|
2039
|
+
return {
|
|
2040
|
+
health,
|
|
2041
|
+
metrics,
|
|
2042
|
+
routes,
|
|
2043
|
+
container,
|
|
2044
|
+
ws
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
__name(fetchAll, "fetchAll");
|
|
2048
|
+
function printSummary(base, data) {
|
|
2049
|
+
const { health, metrics, routes, container, ws } = data;
|
|
2050
|
+
const line = dim("\u2500".repeat(60));
|
|
2051
|
+
console.log();
|
|
2052
|
+
console.log(bold(` KickJS Inspector`) + dim(` \u2192 ${base}`));
|
|
2053
|
+
console.log(line);
|
|
2054
|
+
if (health) {
|
|
2055
|
+
const statusText = health.status === "healthy" ? green("\u25CF healthy") : red("\u25CF " + health.status);
|
|
2056
|
+
console.log(` ${bold("Health:")} ${statusText}`);
|
|
2057
|
+
} else {
|
|
2058
|
+
console.log(` ${bold("Health:")} ${red("\u25CF unreachable")}`);
|
|
2059
|
+
}
|
|
2060
|
+
if (metrics) {
|
|
2061
|
+
const rate = ((metrics.errorRate ?? 0) * 100).toFixed(1);
|
|
2062
|
+
const rateColor = metrics.errorRate > 0.1 ? red : metrics.errorRate > 0 ? yellow : green;
|
|
2063
|
+
console.log(` ${bold("Uptime:")} ${formatUptime(metrics.uptimeSeconds)}`);
|
|
2064
|
+
console.log(` ${bold("Requests:")} ${metrics.requests}`);
|
|
2065
|
+
console.log(` ${bold("Errors:")} ${metrics.serverErrors} server, ${metrics.clientErrors ?? 0} client ${dim("(")}${rateColor(rate + "%")}${dim(")")}`);
|
|
2066
|
+
}
|
|
2067
|
+
if (container) {
|
|
2068
|
+
console.log(` ${bold("DI:")} ${container.count} bindings`);
|
|
2069
|
+
}
|
|
2070
|
+
if (ws && ws.enabled) {
|
|
2071
|
+
console.log(` ${bold("WS:")} ${ws.connections ?? 0} connections, ${ws.namespaces ?? 0} namespaces`);
|
|
2072
|
+
}
|
|
2073
|
+
if (routes?.routes?.length) {
|
|
2074
|
+
console.log();
|
|
2075
|
+
console.log(bold(" Routes"));
|
|
2076
|
+
console.log(line);
|
|
2077
|
+
console.log(` ${dim("METHOD")} ${dim("PATH".padEnd(36))} ${dim("CONTROLLER")}`);
|
|
2078
|
+
for (const r of routes.routes) {
|
|
2079
|
+
const path = r.path.length > 36 ? r.path.slice(0, 33) + "..." : r.path.padEnd(36);
|
|
2080
|
+
console.log(` ${colorMethod(r.method)} ${path} ${blue(r.controller)}.${dim(r.handler)}`);
|
|
1669
2081
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
2082
|
+
}
|
|
2083
|
+
console.log(line);
|
|
2084
|
+
console.log();
|
|
2085
|
+
}
|
|
2086
|
+
__name(printSummary, "printSummary");
|
|
2087
|
+
function registerInspectCommand(program) {
|
|
2088
|
+
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) => {
|
|
2089
|
+
let base = url ?? "http://localhost:3000";
|
|
2090
|
+
if (opts.port) {
|
|
2091
|
+
try {
|
|
2092
|
+
const parsed = new URL(base);
|
|
2093
|
+
parsed.port = opts.port;
|
|
2094
|
+
base = parsed.origin;
|
|
2095
|
+
} catch {
|
|
2096
|
+
base = `http://localhost:${opts.port}`;
|
|
2097
|
+
}
|
|
1673
2098
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2099
|
+
const debugBase = `${base.replace(/\/$/, "")}/_debug`;
|
|
2100
|
+
const run = /* @__PURE__ */ __name(async () => {
|
|
2101
|
+
try {
|
|
2102
|
+
const data = await fetchAll(debugBase);
|
|
2103
|
+
if (opts.json) {
|
|
2104
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2105
|
+
} else {
|
|
2106
|
+
printSummary(base, data);
|
|
2107
|
+
}
|
|
2108
|
+
} catch (err) {
|
|
2109
|
+
if (opts.json) {
|
|
2110
|
+
console.log(JSON.stringify({
|
|
2111
|
+
error: String(err)
|
|
2112
|
+
}));
|
|
2113
|
+
} else {
|
|
2114
|
+
console.error(red(` \u2716 Could not connect to ${base}`));
|
|
2115
|
+
console.error(dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
2116
|
+
}
|
|
2117
|
+
if (!opts.watch) process.exitCode = 1;
|
|
1681
2118
|
}
|
|
1682
|
-
|
|
2119
|
+
}, "run");
|
|
2120
|
+
if (opts.watch) {
|
|
2121
|
+
const poll = /* @__PURE__ */ __name(async () => {
|
|
2122
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
2123
|
+
await run();
|
|
2124
|
+
}, "poll");
|
|
2125
|
+
await poll();
|
|
2126
|
+
setInterval(poll, 5e3);
|
|
2127
|
+
} else {
|
|
2128
|
+
await run();
|
|
1683
2129
|
}
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
__name(registerInspectCommand, "registerInspectCommand");
|
|
2133
|
+
|
|
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"
|
|
1684
2245
|
}
|
|
1685
|
-
|
|
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;
|
|
2267
|
+
}
|
|
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}`);
|
|
2295
|
+
}
|
|
2296
|
+
console.log();
|
|
2297
|
+
try {
|
|
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
|
+
`);
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
1686
2309
|
}
|
|
1687
|
-
__name(
|
|
2310
|
+
__name(registerAddCommand, "registerAddCommand");
|
|
1688
2311
|
|
|
1689
2312
|
// src/cli.ts
|
|
2313
|
+
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
2314
|
+
var pkg = JSON.parse(readFileSync2(join14(__dirname2, "..", "package.json"), "utf-8"));
|
|
1690
2315
|
async function main() {
|
|
1691
2316
|
const program = new Command();
|
|
1692
|
-
program.name("kick").description("KickJS \u2014 A production-grade, decorator-driven Node.js framework").version(
|
|
2317
|
+
program.name("kick").description("KickJS \u2014 A production-grade, decorator-driven Node.js framework").version(pkg.version);
|
|
1693
2318
|
const config = await loadKickConfig(process.cwd());
|
|
1694
2319
|
registerInitCommand(program);
|
|
1695
2320
|
registerGenerateCommand(program);
|
|
1696
2321
|
registerRunCommands(program);
|
|
1697
2322
|
registerInfoCommand(program);
|
|
2323
|
+
registerInspectCommand(program);
|
|
2324
|
+
registerAddCommand(program);
|
|
1698
2325
|
registerCustomCommands(program, config);
|
|
1699
2326
|
program.showHelpAfterError();
|
|
1700
2327
|
await program.parseAsync(process.argv);
|