@forinda/kickjs-cli 4.1.0 → 5.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.mjs +214 -321
- package/dist/index.d.mts +2 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +145 -159
- package/dist/index.mjs.map +1 -1
- package/dist/{typegen-C-H8pg-y.mjs → typegen-B13guHZF.mjs} +44 -6
- package/dist/typegen-B13guHZF.mjs.map +1 -0
- package/package.json +2 -8
- package/dist/typegen-C-H8pg-y.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-cli
|
|
2
|
+
* @forinda/kickjs-cli v5.0.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -108,15 +108,9 @@ async function fileExists(filePath) {
|
|
|
108
108
|
const PACKAGE_DEPS = {
|
|
109
109
|
auth: "@forinda/kickjs-auth",
|
|
110
110
|
swagger: "@forinda/kickjs-swagger",
|
|
111
|
-
otel: "@forinda/kickjs-otel",
|
|
112
111
|
ws: "@forinda/kickjs-ws",
|
|
113
112
|
queue: "@forinda/kickjs-queue",
|
|
114
|
-
|
|
115
|
-
mailer: "@forinda/kickjs-mailer",
|
|
116
|
-
graphql: "@forinda/kickjs-graphql",
|
|
117
|
-
devtools: "@forinda/kickjs-devtools",
|
|
118
|
-
notifications: "@forinda/kickjs-notifications",
|
|
119
|
-
"multi-tenant": "@forinda/kickjs-multi-tenant"
|
|
113
|
+
devtools: "@forinda/kickjs-devtools"
|
|
120
114
|
};
|
|
121
115
|
/** Generate package.json with template-aware dependencies */
|
|
122
116
|
function generatePackageJson(name, template, kickjsVersion, packages = []) {
|
|
@@ -129,15 +123,10 @@ function generatePackageJson(name, template, kickjsVersion, packages = []) {
|
|
|
129
123
|
pino: "^10.3.1",
|
|
130
124
|
"pino-pretty": "^13.1.3"
|
|
131
125
|
};
|
|
132
|
-
if (template === "graphql") {
|
|
133
|
-
baseDeps["@forinda/kickjs-graphql"] = kickjsVersion;
|
|
134
|
-
baseDeps["graphql"] = "^16.11.0";
|
|
135
|
-
}
|
|
136
126
|
for (const pkg of packages) {
|
|
137
127
|
const dep = PACKAGE_DEPS[pkg];
|
|
138
128
|
if (dep && !baseDeps[dep]) baseDeps[dep] = kickjsVersion;
|
|
139
129
|
}
|
|
140
|
-
if (packages.includes("graphql") && !baseDeps["graphql"]) baseDeps["graphql"] = "^16.11.0";
|
|
141
130
|
return JSON.stringify({
|
|
142
131
|
name,
|
|
143
132
|
version: kickjsVersion.replace("^", ""),
|
|
@@ -342,54 +331,9 @@ export default defineConfig({
|
|
|
342
331
|
*/
|
|
343
332
|
function generateEntryFile(name, template, version, packages = []) {
|
|
344
333
|
switch (template) {
|
|
345
|
-
case "graphql": {
|
|
346
|
-
const gqlImports = [];
|
|
347
|
-
const gqlAdapters = [];
|
|
348
|
-
if (packages.includes("devtools")) {
|
|
349
|
-
gqlImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
|
|
350
|
-
gqlAdapters.push(` DevToolsAdapter(),`);
|
|
351
|
-
}
|
|
352
|
-
if (packages.includes("otel")) {
|
|
353
|
-
gqlImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
354
|
-
gqlAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
355
|
-
}
|
|
356
|
-
if (packages.includes("swagger")) {
|
|
357
|
-
gqlImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
|
|
358
|
-
gqlAdapters.push(` SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`);
|
|
359
|
-
}
|
|
360
|
-
return `import 'reflect-metadata'
|
|
361
|
-
// Side-effect import — registers the extended env schema with kickjs
|
|
362
|
-
// **before** any controller / service / @Value gets resolved. Without
|
|
363
|
-
// this line ConfigService.get('YOUR_KEY') returns undefined because the
|
|
364
|
-
// cached schema would still be the base shape. See guide/configuration.
|
|
365
|
-
import './config'
|
|
366
|
-
import { bootstrap } from '@forinda/kickjs'
|
|
367
|
-
import { GraphQLAdapter } from '@forinda/kickjs-graphql'
|
|
368
|
-
${gqlImports.length ? gqlImports.join("\n") + "\n" : ""}import { modules } from './modules'
|
|
369
|
-
|
|
370
|
-
// Import your resolvers here
|
|
371
|
-
// import { UserResolver } from './resolvers/user.resolver'
|
|
372
|
-
|
|
373
|
-
// Export the app for the Vite plugin (dev mode)
|
|
374
|
-
export const app = await bootstrap({
|
|
375
|
-
modules,
|
|
376
|
-
adapters: [
|
|
377
|
-
${gqlAdapters.length ? gqlAdapters.join("\n") + "\n" : ""} new GraphQLAdapter({
|
|
378
|
-
resolvers: [/* UserResolver */],
|
|
379
|
-
// Add custom type definitions here:
|
|
380
|
-
// typeDefs: userTypeDefs,
|
|
381
|
-
}),
|
|
382
|
-
],
|
|
383
|
-
})
|
|
384
|
-
`;
|
|
385
|
-
}
|
|
386
334
|
case "cqrs": {
|
|
387
335
|
const cqrsImports = [];
|
|
388
336
|
const cqrsAdapters = [];
|
|
389
|
-
if (packages.includes("otel")) {
|
|
390
|
-
cqrsImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
391
|
-
cqrsAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
392
|
-
}
|
|
393
337
|
if (packages.includes("devtools")) {
|
|
394
338
|
cqrsImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
|
|
395
339
|
cqrsAdapters.push(` DevToolsAdapter(),`);
|
|
@@ -398,10 +342,6 @@ ${gqlAdapters.length ? gqlAdapters.join("\n") + "\n" : ""} new GraphQLAdapter
|
|
|
398
342
|
cqrsImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
|
|
399
343
|
cqrsAdapters.push(` SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
|
|
400
344
|
}
|
|
401
|
-
if (packages.includes("graphql")) {
|
|
402
|
-
cqrsImports.push(`import { GraphQLAdapter } from '@forinda/kickjs-graphql'`);
|
|
403
|
-
cqrsAdapters.push(` new GraphQLAdapter({ resolvers: [] }),`);
|
|
404
|
-
}
|
|
405
345
|
return `import 'reflect-metadata'
|
|
406
346
|
// Side-effect import — registers the extended env schema with kickjs
|
|
407
347
|
// **before** any controller / service / @Value gets resolved. Without
|
|
@@ -430,14 +370,6 @@ export const app = await bootstrap({
|
|
|
430
370
|
imports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
|
|
431
371
|
adapters.push(` DevToolsAdapter(),`);
|
|
432
372
|
}
|
|
433
|
-
if (packages.includes("otel")) {
|
|
434
|
-
imports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
435
|
-
adapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
436
|
-
}
|
|
437
|
-
if (packages.includes("graphql")) {
|
|
438
|
-
imports.push(`import { GraphQLAdapter } from '@forinda/kickjs-graphql'`);
|
|
439
|
-
adapters.push(` new GraphQLAdapter({ resolvers: [] }),`);
|
|
440
|
-
}
|
|
441
373
|
return `import 'reflect-metadata'
|
|
442
374
|
// Side-effect import — registers the extended env schema with kickjs
|
|
443
375
|
// **before** any controller / service / @Value gets resolved. Without
|
|
@@ -462,10 +394,6 @@ export const app = await bootstrap({ modules${adapters.length ? `,\n adapters:
|
|
|
462
394
|
restImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
|
|
463
395
|
restAdapters.push(` SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
|
|
464
396
|
}
|
|
465
|
-
if (packages.includes("otel")) {
|
|
466
|
-
restImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
467
|
-
restAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
468
|
-
}
|
|
469
397
|
return `import 'reflect-metadata'
|
|
470
398
|
// Side-effect import — registers the extended env schema with kickjs
|
|
471
399
|
// **before** any controller / service / @Value gets resolved. Without
|
|
@@ -685,15 +613,13 @@ export default defineConfig({
|
|
|
685
613
|
function generateReadme(name, template, pm) {
|
|
686
614
|
const templateLabels = {
|
|
687
615
|
rest: "REST API",
|
|
688
|
-
graphql: "GraphQL API",
|
|
689
616
|
ddd: "Domain-Driven Design",
|
|
690
617
|
cqrs: "CQRS + Event-Driven",
|
|
691
618
|
minimal: "Minimal"
|
|
692
619
|
};
|
|
693
620
|
const packages = ["@forinda/kickjs", "@forinda/kickjs-vite"];
|
|
694
621
|
if (template !== "minimal") packages.push("@forinda/kickjs-swagger", "@forinda/kickjs-devtools");
|
|
695
|
-
if (template === "
|
|
696
|
-
if (template === "cqrs") packages.push("@forinda/kickjs-queue", "@forinda/kickjs-ws", "@forinda/kickjs-otel");
|
|
622
|
+
if (template === "cqrs") packages.push("@forinda/kickjs-queue", "@forinda/kickjs-ws");
|
|
697
623
|
return `# ${name}
|
|
698
624
|
|
|
699
625
|
A **${templateLabels[template] ?? "REST API"}** built with [KickJS](https://forinda.github.io/kick-js/) — a decorator-driven Node.js framework on Express 5 and TypeScript.
|
|
@@ -738,11 +664,11 @@ kick add auth # Authentication (JWT, API key, OAuth)
|
|
|
738
664
|
kick add swagger # OpenAPI documentation
|
|
739
665
|
kick add ws # WebSocket support
|
|
740
666
|
kick add queue # Background job processing
|
|
741
|
-
kick add mailer # Email sending
|
|
742
|
-
kick add cron # Scheduled tasks
|
|
743
667
|
kick add --list # Show all available packages
|
|
744
668
|
\`\`\`
|
|
745
669
|
|
|
670
|
+
For email, scheduled tasks, multi-tenancy, OpenTelemetry, GraphQL, and notifications use the BYO recipes in the [KickJS guides](https://forinda.github.io/kick-js/guide/) — they wire the upstream library through \`defineAdapter()\` / \`definePlugin()\` directly, so you keep control of the integration.
|
|
671
|
+
|
|
746
672
|
## Environment Variables
|
|
747
673
|
|
|
748
674
|
Copy \`.env.example\` to \`.env\` and configure:
|
|
@@ -917,8 +843,8 @@ mistakes:
|
|
|
917
843
|
property assignment (\`ctx.tenant = …\`) sticks to the contributor
|
|
918
844
|
instance only — the handler instance never sees it.
|
|
919
845
|
- **Read across instances via \`ctx.set\` / \`ctx.get\`** (or
|
|
920
|
-
\`
|
|
921
|
-
|
|
846
|
+
\`getRequestValue(key)\` from a service that has no \`ctx\` reference
|
|
847
|
+
— typed via \`MetaValue<K>\`). \`ctx.req\` works because the underlying
|
|
922
848
|
Express request is shared; bespoke property assignments don't.
|
|
923
849
|
|
|
924
850
|
- **Test isolation** — default to \`Container.create()\` for fresh DI state.
|
|
@@ -986,7 +912,7 @@ package additions, env access patterns, troubleshooting) is detailed below.
|
|
|
986
912
|
| Module registry | \`src/modules/index.ts\` |
|
|
987
913
|
| Feature modules | \`src/modules/<module-name>/\` |
|
|
988
914
|
| **Module entry file** | \`src/modules/<name>/<name>.module.ts\` (filename suffix is required — see Vite HMR contract below) |
|
|
989
|
-
|
|
915
|
+
| Env values | \`.env\` |
|
|
990
916
|
| Env schema (Zod) | \`src/config/index.ts\` |
|
|
991
917
|
| TypeScript config | \`tsconfig.json\` |
|
|
992
918
|
| Vite config (HMR) | \`vite.config.ts\` |
|
|
@@ -1023,12 +949,6 @@ ${template === "ddd" ? `\`\`\`
|
|
|
1023
949
|
├── <name>.repository.ts # Data access
|
|
1024
950
|
└── <name>.module.ts # Module definition (implements AppModule)
|
|
1025
951
|
\`\`\`
|
|
1026
|
-
` : template === "graphql" ? `\`\`\`
|
|
1027
|
-
resolvers/
|
|
1028
|
-
├── <name>.resolver.ts # @Resolver, @Query, @Mutation
|
|
1029
|
-
├── <name>.types.ts # GraphQL type definitions
|
|
1030
|
-
└── <name>.service.ts # Business logic
|
|
1031
|
-
\`\`\`
|
|
1032
952
|
` : template === "rest" ? `\`\`\`
|
|
1033
953
|
<name>/
|
|
1034
954
|
├── <name>.controller.ts # HTTP routes (@Controller)
|
|
@@ -1301,15 +1221,7 @@ fast). The \`onError\` hook is async-permitted.
|
|
|
1301
1221
|
|
|
1302
1222
|
Full guide: <https://forinda.github.io/kick-js/guide/context-decorators>.
|
|
1303
1223
|
|
|
1304
|
-
${template === "
|
|
1305
|
-
| Decorator | Purpose |
|
|
1306
|
-
|-----------|---------|
|
|
1307
|
-
| \`@Resolver()\` | GraphQL resolver class |
|
|
1308
|
-
| \`@Query()\` | Query handler |
|
|
1309
|
-
| \`@Mutation()\` | Mutation handler |
|
|
1310
|
-
| \`@Arg('name')\` | Resolver argument |
|
|
1311
|
-
|
|
1312
|
-
` : ""}${template === "cqrs" ? `### Background Jobs
|
|
1224
|
+
${template === "cqrs" ? `### Background Jobs
|
|
1313
1225
|
| Decorator | Purpose |
|
|
1314
1226
|
|-----------|---------|
|
|
1315
1227
|
| \`@Job('name')\` | Queue job handler |
|
|
@@ -1567,10 +1479,11 @@ Precedence high → low: **method > class > module > adapter > global**.
|
|
|
1567
1479
|
Cycles or unmet \`dependsOn\` keys throw \`MissingContributorError\` at boot.
|
|
1568
1480
|
|
|
1569
1481
|
**Critical rules — all stem from the same shared-via-ALS instance model**:
|
|
1570
|
-
- Every per-request stage (middleware → contributors → handler) gets its OWN \`RequestContext\` instance, but they all read/write the SAME \`AsyncLocalStorage\`-backed
|
|
1482
|
+
- Every per-request stage (middleware → contributors → handler) gets its OWN \`RequestContext\` instance, but they all read/write the SAME \`AsyncLocalStorage\`-backed bag.
|
|
1571
1483
|
- **\`resolve\` and \`onError\` must RETURN the value** — the runner writes it via \`ctx.set(key, value)\`. Direct property assignment (\`ctx.tenant = …\`) sticks to one instance only and the handler instance never sees it.
|
|
1572
1484
|
- \`ctx.set('tenant', x)\` then \`ctx.get('tenant')\` works across instances. \`ctx.req.headers[...]\` works (the underlying Express request is shared).
|
|
1573
|
-
- Services
|
|
1485
|
+
- Services with no \`ctx\` reference: \`getRequestValue('tenant')\` returns \`MetaValue<'tenant'> | undefined\` (typed via the augmented \`ContextMeta\`). For \`requestId\` use \`getRequestStore()\`.
|
|
1486
|
+
- **No \`setRequestValue\` — writes flow through \`ctx.set\` or a contributor's return value.** Avoids "spooky action at a distance" where any service can pollute the per-request bag.
|
|
1574
1487
|
|
|
1575
1488
|
**Don't use this for**: response short-circuit, stream mutation, or
|
|
1576
1489
|
pre-route-matching work — keep \`@Middleware()\` for those.
|
|
@@ -1643,7 +1556,6 @@ async function initProject(options) {
|
|
|
1643
1556
|
await writeFileSafe(join(dir, "src/modules/hello/hello.service.ts"), generateHelloService());
|
|
1644
1557
|
await writeFileSafe(join(dir, "src/modules/hello/hello.controller.ts"), generateHelloController());
|
|
1645
1558
|
await writeFileSafe(join(dir, "src/modules/hello/hello.module.ts"), generateHelloModule());
|
|
1646
|
-
if (template === "graphql") await writeFileSafe(join(dir, "src/resolvers/.gitkeep"), "");
|
|
1647
1559
|
await writeFileSafe(join(dir, "kick.config.ts"), generateKickConfig(template, defaultRepo, packageManager));
|
|
1648
1560
|
await writeFileSafe(join(dir, "vitest.config.ts"), generateVitestConfig());
|
|
1649
1561
|
await writeFileSafe(join(dir, "README.md"), generateReadme(name, template, packageManager));
|
|
@@ -1699,7 +1611,6 @@ async function initProject(options) {
|
|
|
1699
1611
|
if (!options.installDeps) log(` ${packageManager} install`);
|
|
1700
1612
|
const genHint = {
|
|
1701
1613
|
rest: "kick g module user",
|
|
1702
|
-
graphql: "kick g resolver user",
|
|
1703
1614
|
ddd: "kick g module user --repo drizzle",
|
|
1704
1615
|
cqrs: "kick g module user --pattern cqrs",
|
|
1705
1616
|
minimal: "# add your routes to src/index.ts"
|
|
@@ -1721,7 +1632,6 @@ async function initProject(options) {
|
|
|
1721
1632
|
log(" kick g guard <name> Route guard (auth, roles, etc.)");
|
|
1722
1633
|
log(" kick g adapter <name> AppAdapter with lifecycle hooks");
|
|
1723
1634
|
log(" kick g dto <name> Zod DTO schema");
|
|
1724
|
-
if (template === "graphql") log(" kick g resolver <name> GraphQL resolver");
|
|
1725
1635
|
if (template === "cqrs") log(" kick g job <name> Queue job processor");
|
|
1726
1636
|
log(" kick g config Generate kick.config.ts");
|
|
1727
1637
|
log("");
|
|
@@ -1729,8 +1639,7 @@ async function initProject(options) {
|
|
|
1729
1639
|
log(" kick add <pkg> Install a KickJS package + peers");
|
|
1730
1640
|
log(" kick add --list Show all available packages");
|
|
1731
1641
|
log("");
|
|
1732
|
-
log("Available: auth, swagger,
|
|
1733
|
-
log(" queue, mailer, otel, devtools, multi-tenant, notifications, mcp, testing");
|
|
1642
|
+
log("Available: auth, swagger, drizzle, prisma, ws, queue, devtools, mcp, testing");
|
|
1734
1643
|
log("");
|
|
1735
1644
|
}
|
|
1736
1645
|
//#endregion
|
|
@@ -1816,11 +1725,6 @@ const OPTIONAL_PACKAGES = [
|
|
|
1816
1725
|
label: "Swagger",
|
|
1817
1726
|
hint: "OpenAPI docs"
|
|
1818
1727
|
},
|
|
1819
|
-
{
|
|
1820
|
-
value: "otel",
|
|
1821
|
-
label: "OpenTelemetry",
|
|
1822
|
-
hint: "tracing & metrics"
|
|
1823
|
-
},
|
|
1824
1728
|
{
|
|
1825
1729
|
value: "ws",
|
|
1826
1730
|
label: "WebSocket",
|
|
@@ -1831,39 +1735,14 @@ const OPTIONAL_PACKAGES = [
|
|
|
1831
1735
|
label: "Queue",
|
|
1832
1736
|
hint: "BullMQ/RabbitMQ/Kafka"
|
|
1833
1737
|
},
|
|
1834
|
-
{
|
|
1835
|
-
value: "cron",
|
|
1836
|
-
label: "Cron",
|
|
1837
|
-
hint: "scheduled jobs"
|
|
1838
|
-
},
|
|
1839
|
-
{
|
|
1840
|
-
value: "mailer",
|
|
1841
|
-
label: "Mailer",
|
|
1842
|
-
hint: "SMTP, Resend, SES"
|
|
1843
|
-
},
|
|
1844
|
-
{
|
|
1845
|
-
value: "graphql",
|
|
1846
|
-
label: "GraphQL",
|
|
1847
|
-
hint: "resolvers, GraphiQL"
|
|
1848
|
-
},
|
|
1849
1738
|
{
|
|
1850
1739
|
value: "devtools",
|
|
1851
1740
|
label: "DevTools",
|
|
1852
1741
|
hint: "debug dashboard"
|
|
1853
|
-
},
|
|
1854
|
-
{
|
|
1855
|
-
value: "notifications",
|
|
1856
|
-
label: "Notifications",
|
|
1857
|
-
hint: "email, Slack, Discord"
|
|
1858
|
-
},
|
|
1859
|
-
{
|
|
1860
|
-
value: "multi-tenant",
|
|
1861
|
-
label: "Multi-Tenant",
|
|
1862
|
-
hint: "tenant resolution"
|
|
1863
1742
|
}
|
|
1864
1743
|
];
|
|
1865
1744
|
function registerInitCommand(program) {
|
|
1866
|
-
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 | bun").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 |
|
|
1745
|
+
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 | bun").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 | ddd | cqrs | minimal").option("-r, --repo <type>", "Default repository: prisma | drizzle | inmemory | custom").option("--packages <packages>", "Comma-separated packages to include (e.g. auth,swagger,ws,queue)").action(async (name, opts) => {
|
|
1867
1746
|
intro("KickJS — Create a new project");
|
|
1868
1747
|
if (!name) name = await text({
|
|
1869
1748
|
message: "Project name",
|
|
@@ -1907,11 +1786,6 @@ function registerInitCommand(program) {
|
|
|
1907
1786
|
label: "REST API",
|
|
1908
1787
|
hint: "Express + Swagger"
|
|
1909
1788
|
},
|
|
1910
|
-
{
|
|
1911
|
-
value: "graphql",
|
|
1912
|
-
label: "GraphQL API",
|
|
1913
|
-
hint: "GraphQL + GraphiQL"
|
|
1914
|
-
},
|
|
1915
1789
|
{
|
|
1916
1790
|
value: "ddd",
|
|
1917
1791
|
label: "DDD",
|
|
@@ -4060,7 +3934,6 @@ function resolveRepoType(config) {
|
|
|
4060
3934
|
* Patterns:
|
|
4061
3935
|
* rest — flat folder: controller + service + DTOs + repo
|
|
4062
3936
|
* ddd — nested DDD: presentation/ application/ domain/ infrastructure/
|
|
4063
|
-
* graphql — flat folder: resolver + service + DTOs + repo (future)
|
|
4064
3937
|
* cqrs — commands, queries, events with WS/queue integration
|
|
4065
3938
|
* minimal — just controller + module index
|
|
4066
3939
|
*/
|
|
@@ -4173,7 +4046,13 @@ async function generateAdapter(options) {
|
|
|
4173
4046
|
const pascal = toPascalCase(name);
|
|
4174
4047
|
const files = [];
|
|
4175
4048
|
const filePath = join(outDir, `${kebab}.adapter.ts`);
|
|
4176
|
-
await writeFileSafe(filePath, `import {
|
|
4049
|
+
await writeFileSafe(filePath, `import {
|
|
4050
|
+
defineAdapter,
|
|
4051
|
+
type AdapterContext,
|
|
4052
|
+
type AdapterMiddleware,
|
|
4053
|
+
type ContributorRegistrations,
|
|
4054
|
+
type Constructor,
|
|
4055
|
+
} from '@forinda/kickjs'
|
|
4177
4056
|
|
|
4178
4057
|
/**
|
|
4179
4058
|
* Configuration for the ${pascal} adapter.
|
|
@@ -4194,7 +4073,12 @@ export interface ${pascal}AdapterConfig {
|
|
|
4194
4073
|
* factory's call / \`.scoped()\` / \`.async()\` surfaces for free.
|
|
4195
4074
|
*
|
|
4196
4075
|
* Hooks into the Application lifecycle to add middleware, routes,
|
|
4197
|
-
* or external service connections.
|
|
4076
|
+
* Context Contributors, or external service connections.
|
|
4077
|
+
*
|
|
4078
|
+
* Every lifecycle hook below is OPTIONAL. The scaffold emits all of
|
|
4079
|
+
* them so adopters can browse what's available and delete what they
|
|
4080
|
+
* don't need — \`build()\` returning \`{}\` is also valid for an adapter
|
|
4081
|
+
* that only contributes config defaults.
|
|
4198
4082
|
*
|
|
4199
4083
|
* @example
|
|
4200
4084
|
* \`\`\`ts
|
|
@@ -4210,59 +4094,126 @@ export interface ${pascal}AdapterConfig {
|
|
|
4210
4094
|
export const ${pascal}Adapter = defineAdapter<${pascal}AdapterConfig>({
|
|
4211
4095
|
name: '${pascal}Adapter',
|
|
4212
4096
|
defaults: {
|
|
4213
|
-
// Default config values go here
|
|
4097
|
+
// Default config values go here. The adopter's overrides shallow-merge
|
|
4098
|
+
// on top of these before \`build()\` runs.
|
|
4214
4099
|
},
|
|
4215
|
-
build: (_config, { name: _name }) =>
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
* 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.
|
|
4220
|
-
*/
|
|
4221
|
-
middleware(): AdapterMiddleware[] {
|
|
4222
|
-
return [
|
|
4223
|
-
// Example: add a custom header to all responses
|
|
4224
|
-
// {
|
|
4225
|
-
// phase: 'beforeGlobal',
|
|
4226
|
-
// handler: (_req, res, next) => {
|
|
4227
|
-
// res.setHeader('X-${pascal}', 'true')
|
|
4228
|
-
// next()
|
|
4229
|
-
// },
|
|
4230
|
-
// },
|
|
4231
|
-
]
|
|
4232
|
-
},
|
|
4233
|
-
|
|
4234
|
-
/**
|
|
4235
|
-
* Called before global middleware. Use this to mount routes that
|
|
4236
|
-
* bypass the middleware stack (health checks, docs UI, static
|
|
4237
|
-
* assets).
|
|
4238
|
-
*/
|
|
4239
|
-
beforeMount(_ctx: AdapterContext): void {
|
|
4240
|
-
// Example:
|
|
4241
|
-
// _ctx.app.get('/${kebab}/status', (_req, res) => res.json({ status: 'ok' }))
|
|
4242
|
-
},
|
|
4243
|
-
|
|
4244
|
-
/**
|
|
4245
|
-
* Called after modules and routes are registered, before the
|
|
4246
|
-
* server starts. Use this for late-stage DI registrations or
|
|
4247
|
-
* config validation.
|
|
4248
|
-
*/
|
|
4249
|
-
beforeStart(_ctx: AdapterContext): void {
|
|
4250
|
-
// Example: _ctx.container.bindToken(MY_TOKEN, new MyService(_config))
|
|
4251
|
-
},
|
|
4100
|
+
build: (_config, { name: _name }) => {
|
|
4101
|
+
// Closures inside \`build()\` are how each adapter instance owns its
|
|
4102
|
+
// own state (database client, Map, timer handle, …). The same
|
|
4103
|
+
// \`_config\` is visible to every hook below.
|
|
4252
4104
|
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4105
|
+
return {
|
|
4106
|
+
/**
|
|
4107
|
+
* Express middleware entries the Application mounts at named phases.
|
|
4108
|
+
*
|
|
4109
|
+
* \`phase\` controls where each handler sits in the pipeline:
|
|
4110
|
+
* 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.
|
|
4111
|
+
*
|
|
4112
|
+
* \`path\` (optional) scopes the entry to a path prefix.
|
|
4113
|
+
*
|
|
4114
|
+
* Delete this hook entirely if you don't add middleware.
|
|
4115
|
+
*/
|
|
4116
|
+
middleware(): AdapterMiddleware[] {
|
|
4117
|
+
return [
|
|
4118
|
+
// Example: add a custom header to all responses
|
|
4119
|
+
// {
|
|
4120
|
+
// phase: 'beforeGlobal',
|
|
4121
|
+
// handler: (_req, res, next) => {
|
|
4122
|
+
// res.setHeader('X-${pascal}', 'true')
|
|
4123
|
+
// next()
|
|
4124
|
+
// },
|
|
4125
|
+
// },
|
|
4126
|
+
// Example: scope a rate limiter to one path prefix
|
|
4127
|
+
// {
|
|
4128
|
+
// phase: 'beforeRoutes',
|
|
4129
|
+
// path: '/api/v1/auth',
|
|
4130
|
+
// handler: rateLimit({ max: 10 }),
|
|
4131
|
+
// },
|
|
4132
|
+
]
|
|
4133
|
+
},
|
|
4134
|
+
|
|
4135
|
+
/**
|
|
4136
|
+
* Runs BEFORE global middleware. Mount routes that should bypass the
|
|
4137
|
+
* middleware stack — health checks, docs UI, static assets, OAuth
|
|
4138
|
+
* callbacks. Anything you want reachable even if a global middleware
|
|
4139
|
+
* later in the chain rejects requests.
|
|
4140
|
+
*
|
|
4141
|
+
* Delete this hook if you have no early routes.
|
|
4142
|
+
*/
|
|
4143
|
+
beforeMount(_ctx: AdapterContext): void {
|
|
4144
|
+
// Example:
|
|
4145
|
+
// _ctx.app.get('/${kebab}/status', (_req, res) => res.json({ status: 'ok' }))
|
|
4146
|
+
},
|
|
4147
|
+
|
|
4148
|
+
/**
|
|
4149
|
+
* Fires once per controller class as the router mounts. Use this to
|
|
4150
|
+
* collect route metadata for OpenAPI specs, dependency graphs, route
|
|
4151
|
+
* inventories, devtools dashboards.
|
|
4152
|
+
*
|
|
4153
|
+
* Delete this hook unless your adapter introspects the route registry.
|
|
4154
|
+
*/
|
|
4155
|
+
onRouteMount(_controllerClass: Constructor, _mountPath: string): void {
|
|
4156
|
+
// Example (Swagger-style): collect routes for the spec.
|
|
4157
|
+
// openApiSpec.addController(_controllerClass, _mountPath)
|
|
4158
|
+
},
|
|
4159
|
+
|
|
4160
|
+
/**
|
|
4161
|
+
* Runs AFTER modules + routes are wired, BEFORE the server starts.
|
|
4162
|
+
* Right place for late-stage DI registrations or final config validation.
|
|
4163
|
+
*
|
|
4164
|
+
* Delete this hook if there's nothing to wire post-modules.
|
|
4165
|
+
*/
|
|
4166
|
+
beforeStart(_ctx: AdapterContext): void {
|
|
4167
|
+
// Example: _ctx.container.registerInstance(MY_TOKEN, new MyService(_config))
|
|
4168
|
+
},
|
|
4169
|
+
|
|
4170
|
+
/**
|
|
4171
|
+
* Runs AFTER the HTTP server is listening. The raw \`http.Server\` is
|
|
4172
|
+
* available on \`ctx.server\` — attach upgrade handlers (Socket.IO,
|
|
4173
|
+
* gRPC, GraphQL subscriptions), warm caches, log a banner.
|
|
4174
|
+
*
|
|
4175
|
+
* Delete this hook if you don't need the running server reference.
|
|
4176
|
+
*/
|
|
4177
|
+
afterStart(_ctx: AdapterContext): void {
|
|
4178
|
+
// Example: const io = new Server(_ctx.server)
|
|
4179
|
+
},
|
|
4180
|
+
|
|
4181
|
+
/**
|
|
4182
|
+
* Returns Context Contributors to merge into every route's pipeline
|
|
4183
|
+
* at the \`'adapter'\` precedence level. Per-route handlers can
|
|
4184
|
+
* override the value at the method / class / module level.
|
|
4185
|
+
*
|
|
4186
|
+
* Delete this hook unless your adapter ships typed per-request values
|
|
4187
|
+
* (auth user, tenant, locale, feature flags, geo, etc).
|
|
4188
|
+
*/
|
|
4189
|
+
contributors(): ContributorRegistrations {
|
|
4190
|
+
return [
|
|
4191
|
+
// Example:
|
|
4192
|
+
// import { defineHttpContextDecorator } from '@forinda/kickjs'
|
|
4193
|
+
// declare module '@forinda/kickjs' { interface ContextMeta { ${kebab}: { id: string } } }
|
|
4194
|
+
// const Load${pascal} = defineHttpContextDecorator({
|
|
4195
|
+
// key: '${kebab}',
|
|
4196
|
+
// resolve: (ctx) => ({ id: ctx.req.headers['x-${kebab}-id'] as string }),
|
|
4197
|
+
// })
|
|
4198
|
+
// return [Load${pascal}.registration]
|
|
4199
|
+
]
|
|
4200
|
+
},
|
|
4201
|
+
|
|
4202
|
+
/**
|
|
4203
|
+
* Runs on graceful shutdown (SIGINT/SIGTERM). Clean up long-lived
|
|
4204
|
+
* resources the adapter owns: close connections, flush buffers,
|
|
4205
|
+
* cancel timers. The framework runs every adapter's \`shutdown\`
|
|
4206
|
+
* concurrently via \`Promise.allSettled\` — one failure won't block
|
|
4207
|
+
* sibling adapters.
|
|
4208
|
+
*
|
|
4209
|
+
* Delete this hook if your adapter holds no resources.
|
|
4210
|
+
*/
|
|
4211
|
+
async shutdown(): Promise<void> {
|
|
4212
|
+
// Example: await this.pool.end()
|
|
4213
|
+
// Example: clearInterval(this.heartbeatTimer)
|
|
4214
|
+
},
|
|
4215
|
+
}
|
|
4216
|
+
},
|
|
4266
4217
|
})
|
|
4267
4218
|
`);
|
|
4268
4219
|
files.push(filePath);
|
|
@@ -4290,6 +4241,7 @@ async function generatePlugin(options) {
|
|
|
4290
4241
|
type AppAdapter,
|
|
4291
4242
|
type AppModuleClass,
|
|
4292
4243
|
type Container,
|
|
4244
|
+
type ContributorRegistrations,
|
|
4293
4245
|
} from '@forinda/kickjs'
|
|
4294
4246
|
|
|
4295
4247
|
/**
|
|
@@ -4321,8 +4273,9 @@ export interface ${pascal}PluginConfig {
|
|
|
4321
4273
|
* 2. \`modules()\` — plugin modules load before user modules.
|
|
4322
4274
|
* 3. \`adapters()\` — plugin adapters mount before user adapters.
|
|
4323
4275
|
* 4. \`middleware()\` — plugin middleware runs before user middleware.
|
|
4324
|
-
* 5. \`
|
|
4325
|
-
* 6. \`
|
|
4276
|
+
* 5. \`contributors()\` — Context Contributors merged into every route.
|
|
4277
|
+
* 6. \`onReady(container)\` — runs after the app has fully bootstrapped.
|
|
4278
|
+
* 7. \`shutdown()\` — runs on graceful shutdown.
|
|
4326
4279
|
*
|
|
4327
4280
|
* @example
|
|
4328
4281
|
* \`\`\`ts
|
|
@@ -4383,6 +4336,27 @@ export const ${pascal}Plugin = definePlugin<${pascal}PluginConfig>({
|
|
|
4383
4336
|
]
|
|
4384
4337
|
},
|
|
4385
4338
|
|
|
4339
|
+
/**
|
|
4340
|
+
* Return Context Contributors to merge into every route's pipeline.
|
|
4341
|
+
* Plugins contribute at the same \`'adapter'\` precedence level as
|
|
4342
|
+
* adapters — overrideable per-route at the method / class / module
|
|
4343
|
+
* level. See https://forinda.github.io/kick-js/guide/context-decorators
|
|
4344
|
+
*
|
|
4345
|
+
* Delete this hook if your plugin doesn't ship typed per-request values.
|
|
4346
|
+
*/
|
|
4347
|
+
contributors(): ContributorRegistrations {
|
|
4348
|
+
return [
|
|
4349
|
+
// Example:
|
|
4350
|
+
// import { defineHttpContextDecorator } from '@forinda/kickjs'
|
|
4351
|
+
// declare module '@forinda/kickjs' { interface ContextMeta { ${kebab}: { foo: string } } }
|
|
4352
|
+
// const Load${pascal} = defineHttpContextDecorator({
|
|
4353
|
+
// key: '${kebab}',
|
|
4354
|
+
// resolve: (ctx) => ({ foo: ctx.req.headers['x-${kebab}'] as string }),
|
|
4355
|
+
// })
|
|
4356
|
+
// return [Load${pascal}.registration]
|
|
4357
|
+
]
|
|
4358
|
+
},
|
|
4359
|
+
|
|
4386
4360
|
/**
|
|
4387
4361
|
* Called after the application has fully bootstrapped. Use this
|
|
4388
4362
|
* for post-startup work like logging, health checks, or warming
|
|
@@ -4839,7 +4813,6 @@ function escapesRoot$1(path, root) {
|
|
|
4839
4813
|
//#region src/generators/agent-docs.ts
|
|
4840
4814
|
const VALID_TEMPLATES = new Set([
|
|
4841
4815
|
"rest",
|
|
4842
|
-
"graphql",
|
|
4843
4816
|
"ddd",
|
|
4844
4817
|
"cqrs",
|
|
4845
4818
|
"minimal"
|
|
@@ -5151,82 +5124,6 @@ export class AuthService {
|
|
|
5151
5124
|
`;
|
|
5152
5125
|
}
|
|
5153
5126
|
//#endregion
|
|
5154
|
-
//#region src/generators/resolver.ts
|
|
5155
|
-
async function generateResolver(options) {
|
|
5156
|
-
const { name, outDir } = options;
|
|
5157
|
-
const pascal = toPascalCase(name);
|
|
5158
|
-
const kebab = toKebabCase(name);
|
|
5159
|
-
const camel = toCamelCase(name);
|
|
5160
|
-
const files = [];
|
|
5161
|
-
const write = async (relativePath, content) => {
|
|
5162
|
-
const fullPath = join(outDir, relativePath);
|
|
5163
|
-
await writeFileSafe(fullPath, content);
|
|
5164
|
-
files.push(fullPath);
|
|
5165
|
-
};
|
|
5166
|
-
await write(`${kebab}.resolver.ts`, `import { Service } from '@forinda/kickjs'
|
|
5167
|
-
import { Resolver, Query, Mutation, Arg } from '@forinda/kickjs-graphql'
|
|
5168
|
-
|
|
5169
|
-
/**
|
|
5170
|
-
* ${pascal} GraphQL Resolver
|
|
5171
|
-
*
|
|
5172
|
-
* Decorators:
|
|
5173
|
-
* @Resolver(typeName?) — marks this class as a GraphQL resolver
|
|
5174
|
-
* @Query(name?, { returnType?, description? }) — defines a query field
|
|
5175
|
-
* @Mutation(name?, { returnType?, description? }) — defines a mutation field
|
|
5176
|
-
* @Arg(name, type?) — marks a method parameter as a GraphQL argument
|
|
5177
|
-
*/
|
|
5178
|
-
@Service()
|
|
5179
|
-
@Resolver('${pascal}')
|
|
5180
|
-
export class ${pascal}Resolver {
|
|
5181
|
-
private items: Array<{ id: string; name: string }> = []
|
|
5182
|
-
|
|
5183
|
-
@Query('${camel}s', { returnType: '[${pascal}]', description: 'List all ${camel}s' })
|
|
5184
|
-
findAll() {
|
|
5185
|
-
return this.items
|
|
5186
|
-
}
|
|
5187
|
-
|
|
5188
|
-
@Query('${camel}', { returnType: '${pascal}', description: 'Get a ${camel} by ID' })
|
|
5189
|
-
findById(@Arg('id', 'ID!') id: string) {
|
|
5190
|
-
return this.items.find((item) => item.id === id) ?? null
|
|
5191
|
-
}
|
|
5192
|
-
|
|
5193
|
-
@Mutation('create${pascal}', { returnType: '${pascal}', description: 'Create a new ${camel}' })
|
|
5194
|
-
create(@Arg('name', 'String!') name: string) {
|
|
5195
|
-
const item = { id: String(this.items.length + 1), name }
|
|
5196
|
-
this.items.push(item)
|
|
5197
|
-
return item
|
|
5198
|
-
}
|
|
5199
|
-
|
|
5200
|
-
@Mutation('update${pascal}', { returnType: '${pascal}', description: 'Update a ${camel}' })
|
|
5201
|
-
update(@Arg('id', 'ID!') id: string, @Arg('name', 'String!') name: string) {
|
|
5202
|
-
const item = this.items.find((i) => i.id === id)
|
|
5203
|
-
if (item) item.name = name
|
|
5204
|
-
return item
|
|
5205
|
-
}
|
|
5206
|
-
|
|
5207
|
-
@Mutation('delete${pascal}', { returnType: 'Boolean', description: 'Delete a ${camel}' })
|
|
5208
|
-
remove(@Arg('id', 'ID!') id: string) {
|
|
5209
|
-
const idx = this.items.findIndex((i) => i.id === id)
|
|
5210
|
-
if (idx === -1) return false
|
|
5211
|
-
this.items.splice(idx, 1)
|
|
5212
|
-
return true
|
|
5213
|
-
}
|
|
5214
|
-
}
|
|
5215
|
-
`);
|
|
5216
|
-
await write(`${kebab}.typedefs.ts`, `/**
|
|
5217
|
-
* ${pascal} GraphQL type definitions.
|
|
5218
|
-
* Pass to GraphQLAdapter's typeDefs option to register custom types.
|
|
5219
|
-
*/
|
|
5220
|
-
export const ${camel}TypeDefs = \`
|
|
5221
|
-
type ${pascal} {
|
|
5222
|
-
id: ID!
|
|
5223
|
-
name: String!
|
|
5224
|
-
}
|
|
5225
|
-
\`
|
|
5226
|
-
`);
|
|
5227
|
-
return files;
|
|
5228
|
-
}
|
|
5229
|
-
//#endregion
|
|
5230
5127
|
//#region src/generators/job.ts
|
|
5231
5128
|
async function generateJob(options) {
|
|
5232
5129
|
const { name, outDir } = options;
|
|
@@ -6389,10 +6286,8 @@ function extractAugmentationsFromSource(source, filePath, cwd) {
|
|
|
6389
6286
|
const closeBrace = findBalancedBrace(source, bracePos);
|
|
6390
6287
|
if (closeBrace >= 0) {
|
|
6391
6288
|
const body = source.slice(bracePos + 1, closeBrace);
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
description = descMatch ? descMatch[1] : null;
|
|
6395
|
-
example = exampleMatch ? exampleMatch[1] : null;
|
|
6289
|
+
description = readStringField(body, "description");
|
|
6290
|
+
example = readStringField(body, "example");
|
|
6396
6291
|
}
|
|
6397
6292
|
}
|
|
6398
6293
|
}
|
|
@@ -6407,6 +6302,46 @@ function extractAugmentationsFromSource(source, filePath, cwd) {
|
|
|
6407
6302
|
return out;
|
|
6408
6303
|
}
|
|
6409
6304
|
/**
|
|
6305
|
+
* Pull a string-valued field out of a JS object-literal body, respecting
|
|
6306
|
+
* the opening quote so the value isn't truncated at the first foreign
|
|
6307
|
+
* quote character. Handles backslash escapes inside the literal.
|
|
6308
|
+
*
|
|
6309
|
+
* Why a custom parser instead of one regex per delimiter: real-world
|
|
6310
|
+
* `defineAugmentation` calls embed all three quote characters at once
|
|
6311
|
+
* — backtick template literals carrying TS shapes like
|
|
6312
|
+
* `'free' | 'pro'` (single quotes) AND `\`ctx.get(...)\`` (escaped
|
|
6313
|
+
* backticks). A character-class regex like `[^'"`]+` truncates on the
|
|
6314
|
+
* first foreign quote it sees. This walker scans char-by-char from
|
|
6315
|
+
* the matched delimiter and only stops on the matching one.
|
|
6316
|
+
*/
|
|
6317
|
+
function readStringField(body, field) {
|
|
6318
|
+
const m = new RegExp(`\\b${field}\\s*:\\s*(['"\`])`, "g").exec(body);
|
|
6319
|
+
if (!m) return null;
|
|
6320
|
+
const quote = m[1];
|
|
6321
|
+
const start = m.index + m[0].length;
|
|
6322
|
+
let i = start;
|
|
6323
|
+
let raw = null;
|
|
6324
|
+
while (i < body.length) {
|
|
6325
|
+
const ch = body[i];
|
|
6326
|
+
if (ch === "\\") {
|
|
6327
|
+
i += 2;
|
|
6328
|
+
continue;
|
|
6329
|
+
}
|
|
6330
|
+
if (ch === quote) {
|
|
6331
|
+
raw = body.slice(start, i);
|
|
6332
|
+
break;
|
|
6333
|
+
}
|
|
6334
|
+
i++;
|
|
6335
|
+
}
|
|
6336
|
+
if (raw === null) return null;
|
|
6337
|
+
return raw.replace(/\\(.)/g, (_m, c) => {
|
|
6338
|
+
if (c === "n") return "\n";
|
|
6339
|
+
if (c === "t") return " ";
|
|
6340
|
+
if (c === "r") return "\r";
|
|
6341
|
+
return c;
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
/**
|
|
6410
6345
|
* Default search order for the env schema file. Newer projects keep
|
|
6411
6346
|
* the schema under `src/config/` so the framework's "config" concept
|
|
6412
6347
|
* has a single home; older scaffolds dropped it at `src/env.ts` (kept
|
|
@@ -7417,10 +7352,6 @@ const GENERATORS = [
|
|
|
7417
7352
|
name: "test <name>",
|
|
7418
7353
|
description: "Vitest test scaffold [-m module]"
|
|
7419
7354
|
},
|
|
7420
|
-
{
|
|
7421
|
-
name: "resolver <name>",
|
|
7422
|
-
description: "GraphQL @Resolver class"
|
|
7423
|
-
},
|
|
7424
7355
|
{
|
|
7425
7356
|
name: "job <name>",
|
|
7426
7357
|
description: "Queue @Job processor"
|
|
@@ -7622,14 +7553,6 @@ function registerGenerateCommand(program) {
|
|
|
7622
7553
|
pluralize: mc.pluralize ?? true
|
|
7623
7554
|
}), dryRun);
|
|
7624
7555
|
});
|
|
7625
|
-
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) => {
|
|
7626
|
-
const dryRun = isDryRun(cmd);
|
|
7627
|
-
setDryRun(dryRun);
|
|
7628
|
-
printGenerated(await generateResolver({
|
|
7629
|
-
name,
|
|
7630
|
-
outDir: resolve(opts.out)
|
|
7631
|
-
}), dryRun);
|
|
7632
|
-
});
|
|
7633
7556
|
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) => {
|
|
7634
7557
|
const dryRun = isDryRun(cmd);
|
|
7635
7558
|
setDryRun(dryRun);
|
|
@@ -7699,7 +7622,7 @@ function registerGenerateCommand(program) {
|
|
|
7699
7622
|
force: opts.force
|
|
7700
7623
|
}), dryRun);
|
|
7701
7624
|
});
|
|
7702
|
-
gen.command("agents").alias("agent-docs").alias("ai-docs").description("Regenerate AGENTS.md + CLAUDE.md + kickjs-skills.md (sync after framework upgrades)").option("--only <which>", "Limit scope: agents | claude | skills | both (agents+claude) | all (default: all)", "all").option("--name <name>", "Project name (defaults to package.json name)").option("--pm <pm>", "Package manager (defaults to package.json packageManager)").option("--template <template>", "Template: rest |
|
|
7625
|
+
gen.command("agents").alias("agent-docs").alias("ai-docs").description("Regenerate AGENTS.md + CLAUDE.md + kickjs-skills.md (sync after framework upgrades)").option("--only <which>", "Limit scope: agents | claude | skills | both (agents+claude) | all (default: all)", "all").option("--name <name>", "Project name (defaults to package.json name)").option("--pm <pm>", "Package manager (defaults to package.json packageManager)").option("--template <template>", "Template: rest | ddd | cqrs | minimal").option("-f, --force", "Overwrite existing files without prompting").action(async (opts, cmd) => {
|
|
7703
7626
|
const dryRun = isDryRun(cmd);
|
|
7704
7627
|
setDryRun(dryRun);
|
|
7705
7628
|
const only = opts.only ?? "all";
|
|
@@ -8282,11 +8205,6 @@ const PACKAGE_REGISTRY = {
|
|
|
8282
8205
|
peers: [],
|
|
8283
8206
|
description: "OpenAPI spec + Swagger UI + ReDoc"
|
|
8284
8207
|
},
|
|
8285
|
-
graphql: {
|
|
8286
|
-
pkg: "@forinda/kickjs-graphql",
|
|
8287
|
-
peers: ["graphql"],
|
|
8288
|
-
description: "GraphQL resolvers + GraphiQL"
|
|
8289
|
-
},
|
|
8290
8208
|
drizzle: {
|
|
8291
8209
|
pkg: "@forinda/kickjs-drizzle",
|
|
8292
8210
|
peers: ["drizzle-orm"],
|
|
@@ -8302,11 +8220,6 @@ const PACKAGE_REGISTRY = {
|
|
|
8302
8220
|
peers: ["socket.io"],
|
|
8303
8221
|
description: "WebSocket with @WsController decorators"
|
|
8304
8222
|
},
|
|
8305
|
-
otel: {
|
|
8306
|
-
pkg: "@forinda/kickjs-otel",
|
|
8307
|
-
peers: ["@opentelemetry/api"],
|
|
8308
|
-
description: "OpenTelemetry tracing + metrics"
|
|
8309
|
-
},
|
|
8310
8223
|
devtools: {
|
|
8311
8224
|
pkg: "@forinda/kickjs-devtools",
|
|
8312
8225
|
peers: [],
|
|
@@ -8318,16 +8231,6 @@ const PACKAGE_REGISTRY = {
|
|
|
8318
8231
|
peers: ["jsonwebtoken"],
|
|
8319
8232
|
description: "Authentication — JWT, API key, and custom strategies"
|
|
8320
8233
|
},
|
|
8321
|
-
mailer: {
|
|
8322
|
-
pkg: "@forinda/kickjs-mailer",
|
|
8323
|
-
peers: ["nodemailer"],
|
|
8324
|
-
description: "Email sending — SMTP, Resend, SES, or custom provider"
|
|
8325
|
-
},
|
|
8326
|
-
cron: {
|
|
8327
|
-
pkg: "@forinda/kickjs-cron",
|
|
8328
|
-
peers: ["croner"],
|
|
8329
|
-
description: "Cron job scheduling (production-grade with croner)"
|
|
8330
|
-
},
|
|
8331
8234
|
queue: {
|
|
8332
8235
|
pkg: "@forinda/kickjs-queue",
|
|
8333
8236
|
peers: [],
|
|
@@ -8348,16 +8251,6 @@ const PACKAGE_REGISTRY = {
|
|
|
8348
8251
|
peers: ["kafkajs"],
|
|
8349
8252
|
description: "Queue with Kafka"
|
|
8350
8253
|
},
|
|
8351
|
-
"multi-tenant": {
|
|
8352
|
-
pkg: "@forinda/kickjs-multi-tenant",
|
|
8353
|
-
peers: [],
|
|
8354
|
-
description: "Tenant resolution middleware"
|
|
8355
|
-
},
|
|
8356
|
-
notifications: {
|
|
8357
|
-
pkg: "@forinda/kickjs-notifications",
|
|
8358
|
-
peers: [],
|
|
8359
|
-
description: "Multi-channel notifications — email, Slack, Discord, webhook"
|
|
8360
|
-
},
|
|
8361
8254
|
mcp: {
|
|
8362
8255
|
pkg: "@forinda/kickjs-mcp",
|
|
8363
8256
|
peers: ["@modelcontextprotocol/sdk"],
|
|
@@ -8411,7 +8304,7 @@ function printPackageList() {
|
|
|
8411
8304
|
const peers = info.peers.length ? ` (+ ${info.peers.join(", ")})` : "";
|
|
8412
8305
|
console.log(` ${padded} ${info.description}${peers}`);
|
|
8413
8306
|
}
|
|
8414
|
-
console.log("\n Usage: kick add
|
|
8307
|
+
console.log("\n Usage: kick add auth drizzle swagger");
|
|
8415
8308
|
console.log(" kick add queue:bullmq");
|
|
8416
8309
|
console.log();
|
|
8417
8310
|
}
|