@forinda/kickjs-cli 3.1.3 → 4.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/README.md +12 -89
- package/dist/cli.mjs +974 -49
- package/dist/index.d.mts +252 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +182 -28
- package/dist/index.mjs.map +1 -1
- package/dist/{typegen-0X9tc3wa.mjs → typegen-vI1eqGLK.mjs} +446 -11
- package/dist/typegen-vI1eqGLK.mjs.map +1 -0
- package/package.json +9 -14
- package/dist/typegen-0X9tc3wa.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-cli
|
|
2
|
+
* @forinda/kickjs-cli v4.0.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
12
12
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
13
|
import * as clack from "@clack/prompts";
|
|
14
14
|
import pc from "picocolors";
|
|
15
15
|
import pkg from "pluralize";
|
|
16
16
|
import { execSync } from "node:child_process";
|
|
17
|
-
import { readFileSync } from "node:fs";
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
/** Write a file, creating parent directories if needed. Skips writing in dry run mode. */
|
|
20
20
|
async function writeFileSafe(filePath, content) {
|
|
@@ -1543,15 +1543,15 @@ function generateEntryFile(name, template, version, packages = []) {
|
|
|
1543
1543
|
const gqlAdapters = [];
|
|
1544
1544
|
if (packages.includes("devtools")) {
|
|
1545
1545
|
gqlImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
|
|
1546
|
-
gqlAdapters.push(`
|
|
1546
|
+
gqlAdapters.push(` DevToolsAdapter(),`);
|
|
1547
1547
|
}
|
|
1548
1548
|
if (packages.includes("otel")) {
|
|
1549
1549
|
gqlImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
1550
|
-
gqlAdapters.push(`
|
|
1550
|
+
gqlAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
1551
1551
|
}
|
|
1552
1552
|
if (packages.includes("swagger")) {
|
|
1553
1553
|
gqlImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
|
|
1554
|
-
gqlAdapters.push(`
|
|
1554
|
+
gqlAdapters.push(` SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`);
|
|
1555
1555
|
}
|
|
1556
1556
|
return `import 'reflect-metadata'
|
|
1557
1557
|
// Side-effect import — registers the extended env schema with kickjs
|
|
@@ -1584,15 +1584,15 @@ ${gqlAdapters.length ? gqlAdapters.join("\n") + "\n" : ""} new GraphQLAdapter
|
|
|
1584
1584
|
const cqrsAdapters = [];
|
|
1585
1585
|
if (packages.includes("otel")) {
|
|
1586
1586
|
cqrsImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
1587
|
-
cqrsAdapters.push(`
|
|
1587
|
+
cqrsAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
1588
1588
|
}
|
|
1589
1589
|
if (packages.includes("devtools")) {
|
|
1590
1590
|
cqrsImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
|
|
1591
|
-
cqrsAdapters.push(`
|
|
1591
|
+
cqrsAdapters.push(` DevToolsAdapter(),`);
|
|
1592
1592
|
}
|
|
1593
1593
|
if (packages.includes("swagger")) {
|
|
1594
1594
|
cqrsImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
|
|
1595
|
-
cqrsAdapters.push(`
|
|
1595
|
+
cqrsAdapters.push(` SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
|
|
1596
1596
|
}
|
|
1597
1597
|
if (packages.includes("graphql")) {
|
|
1598
1598
|
cqrsImports.push(`import { GraphQLAdapter } from '@forinda/kickjs-graphql'`);
|
|
@@ -1611,7 +1611,7 @@ ${cqrsImports.length ? cqrsImports.join("\n") + "\n" : ""}import { modules } fro
|
|
|
1611
1611
|
|
|
1612
1612
|
// Export the app for the Vite plugin (dev mode)
|
|
1613
1613
|
export const app = await bootstrap({
|
|
1614
|
-
modules,${cqrsImports.length ? `\n adapters: [\n${cqrsAdapters.join("\n")}\n // Uncomment for WebSocket support:\n //
|
|
1614
|
+
modules,${cqrsImports.length ? `\n adapters: [\n${cqrsAdapters.join("\n")}\n // Uncomment for WebSocket support:\n // WsAdapter(),\n // Uncomment when Redis is available:\n // QueueAdapter({\n // provider: new BullMQProvider({ host: 'localhost', port: 6379 }),\n // }),\n ],` : `\n adapters: [\n // Uncomment for WebSocket support:\n // WsAdapter(),\n // Uncomment when Redis is available:\n // QueueAdapter({\n // provider: new BullMQProvider({ host: 'localhost', port: 6379 }),\n // }),\n ],`}
|
|
1615
1615
|
})
|
|
1616
1616
|
`;
|
|
1617
1617
|
}
|
|
@@ -1620,15 +1620,15 @@ export const app = await bootstrap({
|
|
|
1620
1620
|
const adapters = [];
|
|
1621
1621
|
if (packages.includes("swagger")) {
|
|
1622
1622
|
imports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
|
|
1623
|
-
adapters.push(`
|
|
1623
|
+
adapters.push(` SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`);
|
|
1624
1624
|
}
|
|
1625
1625
|
if (packages.includes("devtools")) {
|
|
1626
1626
|
imports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
|
|
1627
|
-
adapters.push(`
|
|
1627
|
+
adapters.push(` DevToolsAdapter(),`);
|
|
1628
1628
|
}
|
|
1629
1629
|
if (packages.includes("otel")) {
|
|
1630
1630
|
imports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
1631
|
-
adapters.push(`
|
|
1631
|
+
adapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
1632
1632
|
}
|
|
1633
1633
|
if (packages.includes("graphql")) {
|
|
1634
1634
|
imports.push(`import { GraphQLAdapter } from '@forinda/kickjs-graphql'`);
|
|
@@ -1652,15 +1652,15 @@ export const app = await bootstrap({ modules${adapters.length ? `,\n adapters:
|
|
|
1652
1652
|
const restAdapters = [];
|
|
1653
1653
|
if (packages.includes("devtools")) {
|
|
1654
1654
|
restImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
|
|
1655
|
-
restAdapters.push(`
|
|
1655
|
+
restAdapters.push(` DevToolsAdapter(),`);
|
|
1656
1656
|
}
|
|
1657
1657
|
if (packages.includes("swagger")) {
|
|
1658
1658
|
restImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
|
|
1659
|
-
restAdapters.push(`
|
|
1659
|
+
restAdapters.push(` SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
|
|
1660
1660
|
}
|
|
1661
1661
|
if (packages.includes("otel")) {
|
|
1662
1662
|
restImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
|
|
1663
|
-
restAdapters.push(`
|
|
1663
|
+
restAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
|
|
1664
1664
|
}
|
|
1665
1665
|
return `import 'reflect-metadata'
|
|
1666
1666
|
// Side-effect import — registers the extended env schema with kickjs
|
|
@@ -2980,6 +2980,8 @@ Copy \`.env.example\` to \`.env\` and configure:
|
|
|
2980
2980
|
function generateClaude(name, template, pm) {
|
|
2981
2981
|
return `# CLAUDE.md — ${name} Development Guide
|
|
2982
2982
|
|
|
2983
|
+
> **Read \`AGENTS.md\` first.** It is the canonical, multi-agent reference for this project (Claude, Copilot, Codex, Gemini, etc.). This file contains the same project context distilled for Claude, plus Claude-specific notes. When the two disagree on anything substantive, treat \`AGENTS.md\` as authoritative and flag the discrepancy.
|
|
2984
|
+
|
|
2983
2985
|
## Project Overview
|
|
2984
2986
|
|
|
2985
2987
|
This is a **${{
|
|
@@ -3036,7 +3038,7 @@ generated \`KickRoutes\` namespace (refreshed on \`kick dev\` and \`kick typegen
|
|
|
3036
3038
|
\`\`\`ts
|
|
3037
3039
|
import { Controller, Get, Post, type Ctx } from '@forinda/kickjs'
|
|
3038
3040
|
|
|
3039
|
-
@Controller(
|
|
3041
|
+
@Controller()
|
|
3040
3042
|
export class UserController {
|
|
3041
3043
|
@Get('/')
|
|
3042
3044
|
async findAll(ctx: Ctx<KickRoutes.UserController['findAll']>) {
|
|
@@ -3071,9 +3073,12 @@ export class UserService {
|
|
|
3071
3073
|
|
|
3072
3074
|
### Modules
|
|
3073
3075
|
|
|
3074
|
-
Modules implement \`AppModule\` and wire controllers via \`buildRoutes()
|
|
3076
|
+
Modules implement \`AppModule\` and wire controllers via \`buildRoutes()\`.
|
|
3077
|
+
|
|
3078
|
+
> **Naming matters.** Module files **must** be named \`<name>.module.ts\` and live under \`src/modules/\`. The Vite plugin auto-discovers files matching \`*.module.[tj]sx?\` for HMR — a misnamed file (e.g., \`projects.ts\`) won't trigger a graceful module rebuild on save and will require a full server restart. The CLI generator (\`kick g module <name>\`) follows this convention automatically.
|
|
3075
3079
|
|
|
3076
3080
|
\`\`\`ts
|
|
3081
|
+
// src/modules/users/users.module.ts (named <feature>.module.ts)
|
|
3077
3082
|
import { type AppModule, type ModuleRoutes, buildRoutes } from '@forinda/kickjs'
|
|
3078
3083
|
import { UserController } from './user.controller'
|
|
3079
3084
|
|
|
@@ -3124,6 +3129,8 @@ ctx.notFound() // 404 Not Found
|
|
|
3124
3129
|
ctx.badRequest(msg) // 400 Bad Request
|
|
3125
3130
|
\`\`\`
|
|
3126
3131
|
|
|
3132
|
+
> **Context decorators** — when a middleware's only job is to populate \`ctx.set/get\` for the handler to read, prefer \`defineContextDecorator()\` over \`@Middleware()\`. Typed via \`ContextMeta\`, supports \`dependsOn\` ordering, validates the pipeline at boot. Full pattern reference in \`AGENTS.md\` and at <https://forinda.github.io/kick-js/guide/context-decorators>.
|
|
3133
|
+
|
|
3127
3134
|
## CLI Generators
|
|
3128
3135
|
|
|
3129
3136
|
Generate code with the \`kick\` CLI:
|
|
@@ -3312,7 +3319,7 @@ Run tests:
|
|
|
3312
3319
|
## Decorators Reference
|
|
3313
3320
|
|
|
3314
3321
|
### Route Decorators
|
|
3315
|
-
- \`@Controller(
|
|
3322
|
+
- \`@Controller()\` — mark a class as an HTTP controller (path comes from \`routes().path\`)
|
|
3316
3323
|
- \`@Get('/'), @Post('/'), @Put('/'), @Delete('/'), @Patch('/')\` — HTTP methods
|
|
3317
3324
|
- \`@Middleware(fn)\` — attach middleware
|
|
3318
3325
|
- \`@Public()\` — skip authentication (requires @forinda/kickjs-auth)
|
|
@@ -3387,6 +3394,8 @@ ${template === "graphql" ? "| GraphQL resolvers | `src/resolvers/` |\n" : ""}| E
|
|
|
3387
3394
|
|
|
3388
3395
|
### Module Pattern (${template.toUpperCase()})
|
|
3389
3396
|
|
|
3397
|
+
> **Vite HMR auto-discovery contract:** module files **must** be named \`<name>.module.ts\` (or \`.tsx\`/\`.js\`/\`.jsx\`) and live under \`src/modules/\`. The Vite plugin scans for \`*.module.[tj]sx?\` to drive graceful HMR rebuilds; renaming a file to \`projects.ts\` (no \`.module\`) silently breaks HMR — saves trigger a full restart instead of a swap. The CLI generator (\`kick g module <name>\`) follows the convention; manual files must too.
|
|
3398
|
+
|
|
3390
3399
|
Each module in \`src/modules/<name>/\` typically contains:
|
|
3391
3400
|
|
|
3392
3401
|
${template === "ddd" ? `\`\`\`
|
|
@@ -3457,7 +3466,7 @@ Then:
|
|
|
3457
3466
|
If not using generators:
|
|
3458
3467
|
|
|
3459
3468
|
- [ ] Create \`src/modules/<name>/<name>.controller.ts\`
|
|
3460
|
-
- [ ] Add \`@Controller(
|
|
3469
|
+
- [ ] Add \`@Controller()\` decorator
|
|
3461
3470
|
- [ ] Add route handlers with \`@Get()\`, \`@Post()\`, etc.
|
|
3462
3471
|
- [ ] Create module file implementing \`AppModule\` with \`routes()\` returning \`{ path, router: buildRoutes(Controller), controller }\`
|
|
3463
3472
|
- [ ] Register module in \`src/modules/index.ts\` (\`AppModuleClass[]\` array)
|
|
@@ -3519,8 +3528,8 @@ import { AuthAdapter, JwtStrategy } from '@forinda/kickjs-auth'
|
|
|
3519
3528
|
bootstrap({
|
|
3520
3529
|
modules,
|
|
3521
3530
|
adapters: [
|
|
3522
|
-
|
|
3523
|
-
strategies: [
|
|
3531
|
+
AuthAdapter({
|
|
3532
|
+
strategies: [JwtStrategy({ secret: process.env.JWT_SECRET! })],
|
|
3524
3533
|
}),
|
|
3525
3534
|
],
|
|
3526
3535
|
})
|
|
@@ -3550,7 +3559,7 @@ import { WsAdapter } from '@forinda/kickjs-ws'
|
|
|
3550
3559
|
|
|
3551
3560
|
bootstrap({
|
|
3552
3561
|
modules,
|
|
3553
|
-
adapters: [
|
|
3562
|
+
adapters: [WsAdapter()],
|
|
3554
3563
|
})
|
|
3555
3564
|
\`\`\`
|
|
3556
3565
|
|
|
@@ -3654,7 +3663,7 @@ These work anywhere — scripts, plain files, outside \`@Service\`/\`@Controller
|
|
|
3654
3663
|
### HTTP Routes
|
|
3655
3664
|
| Decorator | Purpose |
|
|
3656
3665
|
|-----------|---------|
|
|
3657
|
-
| \`@Controller(
|
|
3666
|
+
| \`@Controller()\` | Define route prefix |
|
|
3658
3667
|
| \`@Get('/'), @Post('/')\` | HTTP method handlers |
|
|
3659
3668
|
| \`@Middleware(fn)\` | Attach middleware |
|
|
3660
3669
|
| \`@Public()\` | Skip auth (requires auth adapter) |
|
|
@@ -3670,6 +3679,27 @@ These work anywhere — scripts, plain files, outside \`@Service\`/\`@Controller
|
|
|
3670
3679
|
| \`@Inject('token')\` | Token-based injection |
|
|
3671
3680
|
| \`@Value('VAR')\` | Inject env variable |
|
|
3672
3681
|
|
|
3682
|
+
### Context Decorators
|
|
3683
|
+
|
|
3684
|
+
Typed, ordered way to populate \`ctx.set/get\` keys before the handler runs.
|
|
3685
|
+
Use this **instead of \`@Middleware()\`** when the middleware's only output
|
|
3686
|
+
is a value other code reads off \`ctx\`.
|
|
3687
|
+
|
|
3688
|
+
| Concept | Where it lives |
|
|
3689
|
+
|---------|----------------|
|
|
3690
|
+
| \`defineContextDecorator({ key, deps, dependsOn, optional, onError, resolve })\` | \`@forinda/kickjs\` |
|
|
3691
|
+
| Method/class decorator | \`@LoadX\` on a controller method/class |
|
|
3692
|
+
| Module hook | \`AppModule.contributors?(): ContributorRegistration[]\` |
|
|
3693
|
+
| Adapter hook | \`AppAdapter.contributors?(): ContributorRegistration[]\` |
|
|
3694
|
+
| Global registration | \`bootstrap({ contributors: [LoadX.registration] })\` |
|
|
3695
|
+
| Type augmentation | \`declare module '@forinda/kickjs' { interface ContextMeta { ... } }\` |
|
|
3696
|
+
|
|
3697
|
+
Precedence high → low: **method > class > module > adapter > global**.
|
|
3698
|
+
Cycles and missing \`dependsOn\` keys throw at \`app.setup()\` (boot fails
|
|
3699
|
+
fast). The \`onError\` hook is async-permitted.
|
|
3700
|
+
|
|
3701
|
+
Full guide: <https://forinda.github.io/kick-js/guide/context-decorators>.
|
|
3702
|
+
|
|
3673
3703
|
${template === "graphql" ? `### GraphQL
|
|
3674
3704
|
| Decorator | Purpose |
|
|
3675
3705
|
|-----------|---------|
|
|
@@ -3692,9 +3722,11 @@ ${template === "graphql" ? `### GraphQL
|
|
|
3692
3722
|
2. **DI not working** — Ensure \`reflect-metadata\` is imported in \`src/index.ts\`
|
|
3693
3723
|
3. **Tests failing randomly** — Missing \`Container.reset()\` in \`beforeEach\`
|
|
3694
3724
|
4. **Routes not found** — Check controller path and module registration
|
|
3695
|
-
5. **HMR not working** —
|
|
3725
|
+
5. **HMR not working** — Two checks: (a) \`vite.config.ts\` has \`hmr: true\`; (b) module file is named \`<name>.module.ts\` (or \`.tsx\`/\`.js\`/\`.jsx\`) and lives under \`src/modules/\`. The Vite plugin auto-discovers \`*.module.[tj]sx?\` for graceful HMR — a misnamed module file (e.g., \`projects.ts\`) silently degrades to a full restart on every save.
|
|
3696
3726
|
6. **Decorators not working** — Check \`tsconfig.json\` has \`experimentalDecorators: true\`
|
|
3697
3727
|
7. **\`config.get('YOUR_KEY')\` returns \`undefined\`** — \`src/index.ts\` is missing \`import './config'\`. That side-effect import registers the env schema with kickjs (\`loadEnv(envSchema)\` runs at module load). Without it, \`ConfigService\` falls back to the base schema (\`PORT\`/\`NODE_ENV\`/\`LOG_LEVEL\` only) and every user-defined key reads as \`undefined\`. \`@Value()\` may *appear* to work because of a raw \`process.env\` fallback, but Zod coercion and schema defaults are silently skipped — investigate \`src/index.ts\` and \`src/config/index.ts\` first.
|
|
3728
|
+
8. **Used \`@Middleware()\` to compute a value for \`ctx\`** — prefer \`defineContextDecorator()\` (see Context Decorators above). It's typed via \`ContextMeta\`, supports \`dependsOn\` for ordering, and validates the pipeline at boot. \`@Middleware()\` is for response short-circuiting, stream mutation, and pre-route-matching work.
|
|
3729
|
+
9. **Context contributor's \`dependsOn\` key not produced anywhere** — boot throws \`MissingContributorError\` naming the dependent and the route. Either remove the dep or register a contributor that produces the key (at any precedence level: method/class/module/adapter/global).
|
|
3698
3730
|
|
|
3699
3731
|
## CLI Commands Reference
|
|
3700
3732
|
|
|
@@ -3772,7 +3804,7 @@ async function initProject(options) {
|
|
|
3772
3804
|
}
|
|
3773
3805
|
}
|
|
3774
3806
|
try {
|
|
3775
|
-
const { runTypegen } = await import("./typegen-
|
|
3807
|
+
const { runTypegen } = await import("./typegen-vI1eqGLK.mjs");
|
|
3776
3808
|
await runTypegen({
|
|
3777
3809
|
cwd: dir,
|
|
3778
3810
|
allowDuplicates: true,
|
|
@@ -3870,7 +3902,10 @@ async function loadKickConfig(cwd) {
|
|
|
3870
3902
|
try {
|
|
3871
3903
|
const { pathToFileURL } = await import("node:url");
|
|
3872
3904
|
const mod = await import(pathToFileURL(filepath).href);
|
|
3873
|
-
|
|
3905
|
+
const config = mod.default ?? mod;
|
|
3906
|
+
const warnings = validateAssetMap(config, cwd);
|
|
3907
|
+
for (const warning of warnings) console.warn(` Warning: ${warning}`);
|
|
3908
|
+
return config;
|
|
3874
3909
|
} catch (err) {
|
|
3875
3910
|
if (filename.endsWith(".ts")) 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.`);
|
|
3876
3911
|
continue;
|
|
@@ -3878,7 +3913,126 @@ async function loadKickConfig(cwd) {
|
|
|
3878
3913
|
}
|
|
3879
3914
|
return null;
|
|
3880
3915
|
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Validate `assetMap` entries on a loaded config. Returns a list of
|
|
3918
|
+
* human-readable warnings; the caller decides how to surface them
|
|
3919
|
+
* (typically `console.warn`). Never throws — `kick g` and other
|
|
3920
|
+
* unrelated commands should keep working even when the assetMap is
|
|
3921
|
+
* misconfigured.
|
|
3922
|
+
*
|
|
3923
|
+
* Checks:
|
|
3924
|
+
*
|
|
3925
|
+
* - Each entry's `src` is a non-empty string.
|
|
3926
|
+
* - The `src` directory exists on disk (otherwise the typegen + build
|
|
3927
|
+
* steps will fail later with cryptic errors).
|
|
3928
|
+
* - `dest` doesn't escape the project root (defensive — a `dest:
|
|
3929
|
+
* '../../etc'` typo could write files outside the workspace).
|
|
3930
|
+
* - The namespace key is a non-empty string and doesn't include a
|
|
3931
|
+
* `/` (would conflict with the `<namespace>/<key>` manifest format).
|
|
3932
|
+
*/
|
|
3933
|
+
function validateAssetMap(config, cwd) {
|
|
3934
|
+
const warnings = [];
|
|
3935
|
+
if (!config?.assetMap) return warnings;
|
|
3936
|
+
const root = resolve(cwd);
|
|
3937
|
+
for (const [namespace, entry] of Object.entries(config.assetMap)) {
|
|
3938
|
+
if (!namespace || namespace.includes("/")) {
|
|
3939
|
+
warnings.push(`assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`);
|
|
3940
|
+
continue;
|
|
3941
|
+
}
|
|
3942
|
+
if (typeof entry?.src !== "string" || entry.src.length === 0) {
|
|
3943
|
+
warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`);
|
|
3944
|
+
continue;
|
|
3945
|
+
}
|
|
3946
|
+
if (!existsSync(resolve(cwd, entry.src))) warnings.push(`assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`);
|
|
3947
|
+
if (entry.dest) {
|
|
3948
|
+
if (escapesRoot(resolve(cwd, entry.dest), root)) warnings.push(`assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
return warnings;
|
|
3952
|
+
}
|
|
3953
|
+
/**
|
|
3954
|
+
* Returns true when `path` (absolute) resolves outside of `root`
|
|
3955
|
+
* (also absolute). Uses `path.relative` for accuracy:
|
|
3956
|
+
*
|
|
3957
|
+
* - The result is empty when paths are identical (inside).
|
|
3958
|
+
* - It starts with `..` when the path traverses outside the root.
|
|
3959
|
+
* - It's absolute (Windows: cross-drive) when there's no relative
|
|
3960
|
+
* path between them.
|
|
3961
|
+
*
|
|
3962
|
+
* Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`
|
|
3963
|
+
* matching `/app2/...`, or case-mismatches on macOS / Windows).
|
|
3964
|
+
*/
|
|
3965
|
+
function escapesRoot(path, root) {
|
|
3966
|
+
const rel = relative(root, path);
|
|
3967
|
+
return rel === "" ? false : rel.startsWith("..") || isAbsolute(rel);
|
|
3968
|
+
}
|
|
3969
|
+
//#endregion
|
|
3970
|
+
//#region src/generator-extension/define.ts
|
|
3971
|
+
/**
|
|
3972
|
+
* Identity factory — returns the spec verbatim. Exists for type
|
|
3973
|
+
* inference and forward-compatibility (future fields can be added with
|
|
3974
|
+
* defaults).
|
|
3975
|
+
*
|
|
3976
|
+
* @example
|
|
3977
|
+
* ```ts
|
|
3978
|
+
* import { defineGenerator } from '@forinda/kickjs-cli'
|
|
3979
|
+
*
|
|
3980
|
+
* export default [
|
|
3981
|
+
* defineGenerator({
|
|
3982
|
+
* name: 'command',
|
|
3983
|
+
* description: 'Generate a CQRS command + handler',
|
|
3984
|
+
* files: (ctx) => [
|
|
3985
|
+
* {
|
|
3986
|
+
* path: `src/modules/${ctx.kebab}/commands/${ctx.kebab}.command.ts`,
|
|
3987
|
+
* content: `// command for ${ctx.pascal}`,
|
|
3988
|
+
* },
|
|
3989
|
+
* ],
|
|
3990
|
+
* }),
|
|
3991
|
+
* ]
|
|
3992
|
+
* ```
|
|
3993
|
+
*/
|
|
3994
|
+
function defineGenerator(spec) {
|
|
3995
|
+
return spec;
|
|
3996
|
+
}
|
|
3997
|
+
//#endregion
|
|
3998
|
+
//#region src/generator-extension/context.ts
|
|
3999
|
+
/** Convert any string to snake_case (`UserPost` / `user-post` → `user_post`). */
|
|
4000
|
+
function toSnakeCase(name) {
|
|
4001
|
+
return toKebabCase(name).replace(/-/g, "_");
|
|
4002
|
+
}
|
|
4003
|
+
/**
|
|
4004
|
+
* Build a {@link GeneratorContext} from the raw name + invocation
|
|
4005
|
+
* arguments. Centralises the case-transformation logic so every plugin
|
|
4006
|
+
* generator sees the same shape regardless of how the name was typed
|
|
4007
|
+
* on the command line (`Post` vs `post` vs `user_post`).
|
|
4008
|
+
*/
|
|
4009
|
+
function buildGeneratorContext(input) {
|
|
4010
|
+
const cwd = input.cwd ?? process.cwd();
|
|
4011
|
+
const usePlural = input.pluralize ?? true;
|
|
4012
|
+
const pascal = toPascalCase(input.name);
|
|
4013
|
+
const camel = toCamelCase(input.name);
|
|
4014
|
+
const kebab = toKebabCase(input.name);
|
|
4015
|
+
const snake = toSnakeCase(input.name);
|
|
4016
|
+
const ctx = {
|
|
4017
|
+
name: input.name,
|
|
4018
|
+
pascal,
|
|
4019
|
+
camel,
|
|
4020
|
+
kebab,
|
|
4021
|
+
snake,
|
|
4022
|
+
modulesDir: input.modulesDir ?? "src/modules",
|
|
4023
|
+
cwd,
|
|
4024
|
+
args: input.args ?? [],
|
|
4025
|
+
flags: input.flags ?? {}
|
|
4026
|
+
};
|
|
4027
|
+
if (usePlural) {
|
|
4028
|
+
const pluralKebab = pluralize(kebab);
|
|
4029
|
+
ctx.pluralKebab = pluralKebab;
|
|
4030
|
+
ctx.pluralPascal = toPascalCase(pluralKebab);
|
|
4031
|
+
ctx.pluralCamel = toCamelCase(pluralKebab);
|
|
4032
|
+
}
|
|
4033
|
+
return ctx;
|
|
4034
|
+
}
|
|
3881
4035
|
//#endregion
|
|
3882
|
-
export { defineConfig, generateAdapter, generateController, generateDto, generateGuard, generateMiddleware, generateModule, generateService, initProject, loadKickConfig, pluralize, toCamelCase, toKebabCase, toPascalCase };
|
|
4036
|
+
export { buildGeneratorContext, defineConfig, defineGenerator, generateAdapter, generateController, generateDto, generateGuard, generateMiddleware, generateModule, generateService, initProject, loadKickConfig, pluralize, toCamelCase, toKebabCase, toPascalCase };
|
|
3883
4037
|
|
|
3884
4038
|
//# sourceMappingURL=index.mjs.map
|