@forinda/kickjs-cli 3.0.8 → 3.1.1
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 +222 -92
- package/dist/index.d.mts +19 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +18 -13
- package/dist/index.mjs.map +1 -1
- package/dist/{typegen-CbE_hoAJ.mjs → typegen-uoVqx5ib.mjs} +113 -44
- package/dist/typegen-uoVqx5ib.mjs.map +1 -0
- package/package.json +2 -2
- package/dist/typegen-CbE_hoAJ.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-cli v3.
|
|
2
|
+
* @forinda/kickjs-cli v3.1.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -577,11 +577,14 @@ export class HelloModule implements AppModule {
|
|
|
577
577
|
`;
|
|
578
578
|
}
|
|
579
579
|
/** Generate kick.config.ts CLI configuration */
|
|
580
|
-
function generateKickConfig(template, defaultRepo = "inmemory") {
|
|
580
|
+
function generateKickConfig(template, defaultRepo = "inmemory", packageManager = "pnpm") {
|
|
581
581
|
return `import { defineConfig } from '@forinda/kickjs-cli'
|
|
582
582
|
|
|
583
583
|
export default defineConfig({
|
|
584
584
|
pattern: '${template}',
|
|
585
|
+
// Pinned so \`kick add\` and other dep-installing commands always use the
|
|
586
|
+
// project's intended package manager, regardless of which lockfile exists.
|
|
587
|
+
packageManager: '${packageManager}',
|
|
585
588
|
modules: {
|
|
586
589
|
dir: 'src/modules',
|
|
587
590
|
repo: ${[
|
|
@@ -1482,7 +1485,7 @@ async function initProject(options) {
|
|
|
1482
1485
|
await writeFileSafe(join(dir, "src/modules/hello/hello.controller.ts"), generateHelloController());
|
|
1483
1486
|
await writeFileSafe(join(dir, "src/modules/hello/hello.module.ts"), generateHelloModule());
|
|
1484
1487
|
if (template === "graphql") await writeFileSafe(join(dir, "src/resolvers/.gitkeep"), "");
|
|
1485
|
-
await writeFileSafe(join(dir, "kick.config.ts"), generateKickConfig(template, defaultRepo));
|
|
1488
|
+
await writeFileSafe(join(dir, "kick.config.ts"), generateKickConfig(template, defaultRepo, packageManager));
|
|
1486
1489
|
await writeFileSafe(join(dir, "vitest.config.ts"), generateVitestConfig());
|
|
1487
1490
|
await writeFileSafe(join(dir, "README.md"), generateReadme(name, template, packageManager));
|
|
1488
1491
|
await writeFileSafe(join(dir, "CLAUDE.md"), generateClaude(name, template, packageManager));
|
|
@@ -1700,7 +1703,7 @@ const OPTIONAL_PACKAGES = [
|
|
|
1700
1703
|
}
|
|
1701
1704
|
];
|
|
1702
1705
|
function registerInitCommand(program) {
|
|
1703
|
-
program.command("new [name]").alias("init").description("Create a new KickJS project (use \".\" for current directory)").option("-d, --directory <dir>", "Target directory (defaults to project name)").option("--pm <manager>", "Package manager: pnpm | npm | yarn").option("--git", "Initialize git repository").option("--no-git", "Skip git initialization").option("--install", "Install dependencies after scaffolding").option("--no-install", "Skip dependency installation").option("-f, --force", "Remove existing files without prompting").option("-t, --template <type>", "Project template: rest | graphql | ddd | cqrs | minimal").option("-r, --repo <type>", "Default repository: prisma | drizzle | inmemory | custom").option("--packages <packages>", "Comma-separated packages to include (e.g. auth,swagger,otel)").action(async (name, opts) => {
|
|
1706
|
+
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 | graphql | 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,otel)").action(async (name, opts) => {
|
|
1704
1707
|
intro("KickJS — Create a new project");
|
|
1705
1708
|
if (!name) name = await text({
|
|
1706
1709
|
message: "Project name",
|
|
@@ -1781,6 +1784,10 @@ function registerInitCommand(program) {
|
|
|
1781
1784
|
{
|
|
1782
1785
|
value: "yarn",
|
|
1783
1786
|
label: "yarn"
|
|
1787
|
+
},
|
|
1788
|
+
{
|
|
1789
|
+
value: "bun",
|
|
1790
|
+
label: "bun"
|
|
1784
1791
|
}
|
|
1785
1792
|
]
|
|
1786
1793
|
});
|
|
@@ -3329,7 +3336,7 @@ export class Prisma${pascal}Repository implements I${pascal}Repository {
|
|
|
3329
3336
|
//#region src/generators/patterns/minimal.ts
|
|
3330
3337
|
async function generateMinimalFiles(ctx) {
|
|
3331
3338
|
const { pascal, kebab, plural, write } = ctx;
|
|
3332
|
-
await write(
|
|
3339
|
+
await write(`${kebab}.module.ts`, generateMinimalModuleIndex({
|
|
3333
3340
|
pascal,
|
|
3334
3341
|
kebab,
|
|
3335
3342
|
plural
|
|
@@ -3352,7 +3359,7 @@ export class ${pascal}Controller {
|
|
|
3352
3359
|
//#region src/generators/patterns/rest.ts
|
|
3353
3360
|
async function generateRestFiles(ctx) {
|
|
3354
3361
|
const { pascal, kebab, plural, pluralPascal, repo, noTests, prismaClientPath, write } = ctx;
|
|
3355
|
-
await write(
|
|
3362
|
+
await write(`${kebab}.module.ts`, generateRestModuleIndex({
|
|
3356
3363
|
pascal,
|
|
3357
3364
|
kebab,
|
|
3358
3365
|
plural,
|
|
@@ -3448,7 +3455,7 @@ async function generateRestFiles(ctx) {
|
|
|
3448
3455
|
//#region src/generators/patterns/cqrs.ts
|
|
3449
3456
|
async function generateCqrsFiles(ctx) {
|
|
3450
3457
|
const { pascal, kebab, plural, pluralPascal, repo, noTests, prismaClientPath, write } = ctx;
|
|
3451
|
-
await write(
|
|
3458
|
+
await write(`${kebab}.module.ts`, generateCqrsModuleIndex({
|
|
3452
3459
|
pascal,
|
|
3453
3460
|
kebab,
|
|
3454
3461
|
plural,
|
|
@@ -3557,7 +3564,7 @@ async function generateCqrsFiles(ctx) {
|
|
|
3557
3564
|
//#region src/generators/patterns/ddd.ts
|
|
3558
3565
|
async function generateDddFiles(ctx) {
|
|
3559
3566
|
const { pascal, kebab, plural, pluralPascal, repo, noEntity, noTests, prismaClientPath, write } = ctx;
|
|
3560
|
-
await write(
|
|
3567
|
+
await write(`${kebab}.module.ts`, generateModuleIndex({
|
|
3561
3568
|
pascal,
|
|
3562
3569
|
kebab,
|
|
3563
3570
|
plural,
|
|
@@ -3732,22 +3739,24 @@ async function generateModule(options) {
|
|
|
3732
3739
|
await generateDddFiles(ctx);
|
|
3733
3740
|
break;
|
|
3734
3741
|
}
|
|
3735
|
-
if (!dryRun) await autoRegisterModule$1(modulesDir, pascal, plural);
|
|
3742
|
+
if (!dryRun) await autoRegisterModule$1(modulesDir, pascal, plural, kebab);
|
|
3736
3743
|
return files;
|
|
3737
3744
|
}
|
|
3738
3745
|
/** Add the new module to src/modules/index.ts */
|
|
3739
|
-
async function autoRegisterModule$1(modulesDir, pascal, plural) {
|
|
3746
|
+
async function autoRegisterModule$1(modulesDir, pascal, plural, kebab) {
|
|
3740
3747
|
const indexPath = join(modulesDir, "index.ts");
|
|
3741
|
-
|
|
3748
|
+
const exists = await fileExists(indexPath);
|
|
3749
|
+
const importPath = `./${plural}/${kebab}.module`;
|
|
3750
|
+
if (!exists) {
|
|
3742
3751
|
await writeFileSafe(indexPath, `import type { AppModuleClass } from '@forinda/kickjs'
|
|
3743
|
-
import { ${pascal}Module } from '
|
|
3752
|
+
import { ${pascal}Module } from '${importPath}'
|
|
3744
3753
|
|
|
3745
3754
|
export const modules: AppModuleClass[] = [${pascal}Module]
|
|
3746
3755
|
`);
|
|
3747
3756
|
return;
|
|
3748
3757
|
}
|
|
3749
3758
|
let content = await readFile(indexPath, "utf-8");
|
|
3750
|
-
const importLine = `import { ${pascal}Module } from '
|
|
3759
|
+
const importLine = `import { ${pascal}Module } from '${importPath}'`;
|
|
3751
3760
|
if (!content.includes(`${pascal}Module`)) {
|
|
3752
3761
|
const lastImportIdx = content.lastIndexOf("import ");
|
|
3753
3762
|
if (lastImportIdx !== -1) {
|
|
@@ -4810,7 +4819,7 @@ async function generateScaffold(options) {
|
|
|
4810
4819
|
await writeFileSafe(fullPath, content);
|
|
4811
4820
|
files.push(fullPath);
|
|
4812
4821
|
};
|
|
4813
|
-
await write(
|
|
4822
|
+
await write(`${kebab}.module.ts`, genModuleIndex(pascal, kebab, plural));
|
|
4814
4823
|
await write("constants.ts", genConstants(pascal, fields));
|
|
4815
4824
|
await write(`presentation/${kebab}.controller.ts`, genController(pascal, kebab, plural, pluralPascal));
|
|
4816
4825
|
await write(`application/dtos/create-${kebab}.dto.ts`, genCreateDTO(pascal, fields));
|
|
@@ -4825,7 +4834,7 @@ async function generateScaffold(options) {
|
|
|
4825
4834
|
await write(`domain/entities/${kebab}.entity.ts`, genEntity(pascal, kebab, fields));
|
|
4826
4835
|
await write(`domain/value-objects/${kebab}-id.vo.ts`, genValueObject(pascal));
|
|
4827
4836
|
}
|
|
4828
|
-
await autoRegisterModule(modulesDir, pascal, plural);
|
|
4837
|
+
await autoRegisterModule(modulesDir, pascal, plural, kebab);
|
|
4829
4838
|
return files;
|
|
4830
4839
|
}
|
|
4831
4840
|
function genCreateDTO(pascal, fields) {
|
|
@@ -5208,14 +5217,16 @@ export class Delete${pascal}UseCase {
|
|
|
5208
5217
|
}
|
|
5209
5218
|
];
|
|
5210
5219
|
}
|
|
5211
|
-
async function autoRegisterModule(modulesDir, pascal, plural) {
|
|
5220
|
+
async function autoRegisterModule(modulesDir, pascal, plural, kebab) {
|
|
5212
5221
|
const indexPath = join(modulesDir, "index.ts");
|
|
5213
|
-
|
|
5214
|
-
|
|
5222
|
+
const exists = await fileExists(indexPath);
|
|
5223
|
+
const importPath = `./${plural}/${kebab}.module`;
|
|
5224
|
+
if (!exists) {
|
|
5225
|
+
await writeFileSafe(indexPath, `import type { AppModuleClass } from '@forinda/kickjs'\nimport { ${pascal}Module } from '${importPath}'\n\nexport const modules: AppModuleClass[] = [${pascal}Module]\n`);
|
|
5215
5226
|
return;
|
|
5216
5227
|
}
|
|
5217
5228
|
let content = await readFile(indexPath, "utf-8");
|
|
5218
|
-
const importLine = `import { ${pascal}Module } from '
|
|
5229
|
+
const importLine = `import { ${pascal}Module } from '${importPath}'`;
|
|
5219
5230
|
if (!content.includes(`${pascal}Module`)) {
|
|
5220
5231
|
const lastImportIdx = content.lastIndexOf("import ");
|
|
5221
5232
|
if (lastImportIdx !== -1) {
|
|
@@ -5276,6 +5287,12 @@ describe('${pascal}', () => {
|
|
|
5276
5287
|
}
|
|
5277
5288
|
//#endregion
|
|
5278
5289
|
//#region src/config.ts
|
|
5290
|
+
const PACKAGE_MANAGERS = [
|
|
5291
|
+
"pnpm",
|
|
5292
|
+
"npm",
|
|
5293
|
+
"yarn",
|
|
5294
|
+
"bun"
|
|
5295
|
+
];
|
|
5279
5296
|
const BUILTIN_REPO_TYPES = [
|
|
5280
5297
|
"drizzle",
|
|
5281
5298
|
"inmemory",
|
|
@@ -5356,6 +5373,16 @@ const DEFAULT_EXCLUDES = [
|
|
|
5356
5373
|
*/
|
|
5357
5374
|
const DECORATED_CLASS_REGEX = new RegExp(String.raw`@(${DECORATOR_NAMES.join("|")})\s*\([^)]*\)` + String.raw`(?:\s*@[A-Z]\w*(?:\s*\([^)]*\))?)*` + String.raw`\s*export\s+(default\s+)?(?:abstract\s+)?class\s+(\w+)`, "g");
|
|
5358
5375
|
/**
|
|
5376
|
+
* Match an exported class declaration that implements `AppModule`.
|
|
5377
|
+
* KickJS modules are not decorated — they implement the `AppModule`
|
|
5378
|
+
* interface — so the decorated-class scanner never picks them up. This
|
|
5379
|
+
* regex captures them by name so `ModuleToken` can be populated.
|
|
5380
|
+
*
|
|
5381
|
+
* Tolerates an `extends BaseClass` clause before `implements`, multiple
|
|
5382
|
+
* implements clauses (`implements Foo, AppModule`), and `default` exports.
|
|
5383
|
+
*/
|
|
5384
|
+
const APP_MODULE_CLASS_REGEX = new RegExp(String.raw`export\s+(default\s+)?(?:abstract\s+)?class\s+(\w+)` + String.raw`(?:\s+extends\s+\w+(?:<[^>]*>)?)?` + String.raw`\s+implements\s+[^{]*\bAppModule\b`, "g");
|
|
5385
|
+
/**
|
|
5359
5386
|
* Match a `createToken<T>('name')` call with optional `export const X =`
|
|
5360
5387
|
* or `const X =` prefix. Tolerates whitespace and the type parameter
|
|
5361
5388
|
* being absent (`createToken('name')`).
|
|
@@ -5369,52 +5396,89 @@ const BARE_CREATE_TOKEN_REGEX = /createToken\s*(?:<[^>]*>)?\s*\(\s*['"`]([^'"`]+
|
|
|
5369
5396
|
/** Match `@Inject('literal')` — only literals; computed args are skipped */
|
|
5370
5397
|
const INJECT_LITERAL_REGEX = /@Inject\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
5371
5398
|
/**
|
|
5372
|
-
*
|
|
5373
|
-
*
|
|
5374
|
-
*
|
|
5375
|
-
*
|
|
5376
|
-
*
|
|
5377
|
-
*
|
|
5378
|
-
* -
|
|
5379
|
-
* - `async` modifier on the method
|
|
5380
|
-
*
|
|
5381
|
-
* Run within a class body slice (see extractRoutesFromSource) so the
|
|
5382
|
-
* captured method name is unambiguously a method on that class.
|
|
5399
|
+
* Locate the start of a route decorator: `@Get(`, `@Post(`, etc.
|
|
5400
|
+
* Used by `extractRoutesFromSource`; the rest of the route declaration
|
|
5401
|
+
* (balanced parens, stacked decorators, method name) is parsed by walking
|
|
5402
|
+
* the source forward from this match. The previous all-in-one regex
|
|
5403
|
+
* couldn't handle nested parens in stacked decorator args (e.g.
|
|
5404
|
+
* `@ApiResponse(201, { schema: z.object({ id: z.string() }) })`) — see
|
|
5405
|
+
* forinda/kick-js#108.
|
|
5383
5406
|
*/
|
|
5384
|
-
const
|
|
5407
|
+
const ROUTE_DECORATOR_START = new RegExp(String.raw`@(${[
|
|
5385
5408
|
"Get",
|
|
5386
5409
|
"Post",
|
|
5387
5410
|
"Put",
|
|
5388
5411
|
"Delete",
|
|
5389
5412
|
"Patch"
|
|
5390
|
-
].join("|")})\s*\(
|
|
5391
|
-
/** Extract `:placeholder` segments from an Express route path */
|
|
5392
|
-
function extractPathParams(path) {
|
|
5393
|
-
return (path.match(/:([a-zA-Z_]\w*)/g) ?? []).map((m) => m.slice(1));
|
|
5394
|
-
}
|
|
5413
|
+
].join("|")})\s*\(`, "g");
|
|
5395
5414
|
/**
|
|
5396
|
-
*
|
|
5397
|
-
*
|
|
5398
|
-
*
|
|
5399
|
-
*
|
|
5400
|
-
*
|
|
5401
|
-
* `@Post('/', { body: createTaskSchema, name: 'CreateTask' }) async create(`
|
|
5402
|
-
* Returns:
|
|
5403
|
-
* `'/', { body: createTaskSchema, name: 'CreateTask' }`
|
|
5415
|
+
* Find the index of the `)` that balances the `(` at `openPos`.
|
|
5416
|
+
* Returns -1 if no matching `)` exists. Counts balanced parens only;
|
|
5417
|
+
* does not understand string literals, so a `(` or `)` inside a string
|
|
5418
|
+
* inside the args will skew the depth counter (matches the limitation
|
|
5419
|
+
* of `extractRouteOptionsArg`).
|
|
5404
5420
|
*/
|
|
5405
|
-
function
|
|
5406
|
-
const open = matchedText.indexOf("(");
|
|
5407
|
-
if (open < 0) return null;
|
|
5421
|
+
function findBalancedClose(text, openPos) {
|
|
5408
5422
|
let depth = 1;
|
|
5409
|
-
for (let i =
|
|
5410
|
-
const ch =
|
|
5423
|
+
for (let i = openPos + 1; i < text.length; i++) {
|
|
5424
|
+
const ch = text[i];
|
|
5411
5425
|
if (ch === "(") depth++;
|
|
5412
5426
|
else if (ch === ")") {
|
|
5413
5427
|
depth--;
|
|
5414
|
-
if (depth === 0) return
|
|
5428
|
+
if (depth === 0) return i;
|
|
5415
5429
|
}
|
|
5416
5430
|
}
|
|
5417
|
-
return
|
|
5431
|
+
return -1;
|
|
5432
|
+
}
|
|
5433
|
+
/**
|
|
5434
|
+
* Walk forward from the end of a route decorator past any stacked
|
|
5435
|
+
* decorators (`@ApiOperation(...)`, `@ApiResponse(...)`, `@Middleware(fn)`,
|
|
5436
|
+
* etc.), then past optional `public`/`private`/`protected` and `async`,
|
|
5437
|
+
* and capture the method name + opening `(`.
|
|
5438
|
+
*
|
|
5439
|
+
* Returns the method name and the position immediately after the method's
|
|
5440
|
+
* opening `(`, or `null` if the source between the route decorator and
|
|
5441
|
+
* the method body doesn't fit the expected shape.
|
|
5442
|
+
*/
|
|
5443
|
+
function readMethodAfterDecorators(block, startPos) {
|
|
5444
|
+
let pos = startPos;
|
|
5445
|
+
while (pos < block.length) {
|
|
5446
|
+
while (pos < block.length && /\s/.test(block[pos])) pos++;
|
|
5447
|
+
if (block[pos] !== "@") break;
|
|
5448
|
+
const decMatch = block.slice(pos).match(/^@([A-Z]\w*)/);
|
|
5449
|
+
if (!decMatch) break;
|
|
5450
|
+
pos += decMatch[0].length;
|
|
5451
|
+
while (pos < block.length && /\s/.test(block[pos])) pos++;
|
|
5452
|
+
if (block[pos] === "(") {
|
|
5453
|
+
const close = findBalancedClose(block, pos);
|
|
5454
|
+
if (close < 0) return null;
|
|
5455
|
+
pos = close + 1;
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
while (pos < block.length && /\s/.test(block[pos])) pos++;
|
|
5459
|
+
for (const mod of [
|
|
5460
|
+
"public",
|
|
5461
|
+
"private",
|
|
5462
|
+
"protected"
|
|
5463
|
+
]) if (block.slice(pos, pos + mod.length) === mod && /\s/.test(block.charAt(pos + mod.length))) {
|
|
5464
|
+
pos += mod.length;
|
|
5465
|
+
while (pos < block.length && /\s/.test(block[pos])) pos++;
|
|
5466
|
+
break;
|
|
5467
|
+
}
|
|
5468
|
+
if (block.slice(pos, pos + 5) === "async" && /\s/.test(block.charAt(pos + 5))) {
|
|
5469
|
+
pos += 5;
|
|
5470
|
+
while (pos < block.length && /\s/.test(block[pos])) pos++;
|
|
5471
|
+
}
|
|
5472
|
+
const methodMatch = block.slice(pos).match(/^([a-zA-Z_]\w*)\s*\(/);
|
|
5473
|
+
if (!methodMatch) return null;
|
|
5474
|
+
return {
|
|
5475
|
+
methodName: methodMatch[1],
|
|
5476
|
+
endPos: pos + methodMatch[0].length
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
/** Extract `:placeholder` segments from an Express route path */
|
|
5480
|
+
function extractPathParams(path) {
|
|
5481
|
+
return (path.match(/:([a-zA-Z_]\w*)/g) ?? []).map((m) => m.slice(1));
|
|
5418
5482
|
}
|
|
5419
5483
|
/**
|
|
5420
5484
|
* Extract a bare identifier value from a single field in an object literal
|
|
@@ -5545,6 +5609,19 @@ function extractClassesFromSource(source, filePath, cwd) {
|
|
|
5545
5609
|
isDefault: Boolean(defaultMarker)
|
|
5546
5610
|
});
|
|
5547
5611
|
}
|
|
5612
|
+
APP_MODULE_CLASS_REGEX.lastIndex = 0;
|
|
5613
|
+
let modMatch;
|
|
5614
|
+
while ((modMatch = APP_MODULE_CLASS_REGEX.exec(source)) !== null) {
|
|
5615
|
+
const [, defaultMarker, className] = modMatch;
|
|
5616
|
+
if (out.some((c) => c.className === className && c.filePath === filePath)) continue;
|
|
5617
|
+
out.push({
|
|
5618
|
+
className,
|
|
5619
|
+
decorator: "Module",
|
|
5620
|
+
filePath,
|
|
5621
|
+
relativePath: relPath,
|
|
5622
|
+
isDefault: Boolean(defaultMarker)
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5548
5625
|
return out;
|
|
5549
5626
|
}
|
|
5550
5627
|
/** Extract `createToken('name')` definitions from a single source file */
|
|
@@ -5604,16 +5681,25 @@ function extractRoutesFromSource(source, filePath, cwd, classesInFile) {
|
|
|
5604
5681
|
const { cls, start } = positions[i];
|
|
5605
5682
|
const end = i + 1 < positions.length ? positions[i + 1].start : source.length;
|
|
5606
5683
|
const block = source.slice(start, end);
|
|
5607
|
-
|
|
5608
|
-
let
|
|
5609
|
-
while ((
|
|
5610
|
-
const
|
|
5611
|
-
const
|
|
5612
|
-
const
|
|
5613
|
-
const
|
|
5614
|
-
|
|
5615
|
-
const
|
|
5616
|
-
const
|
|
5684
|
+
ROUTE_DECORATOR_START.lastIndex = 0;
|
|
5685
|
+
let startMatch;
|
|
5686
|
+
while ((startMatch = ROUTE_DECORATOR_START.exec(block)) !== null) {
|
|
5687
|
+
const verb = startMatch[1];
|
|
5688
|
+
const decoratorStart = startMatch.index;
|
|
5689
|
+
const openParen = ROUTE_DECORATOR_START.lastIndex - 1;
|
|
5690
|
+
const closeParen = findBalancedClose(block, openParen);
|
|
5691
|
+
if (closeParen < 0) continue;
|
|
5692
|
+
const routeArgs = block.slice(openParen + 1, closeParen);
|
|
5693
|
+
const pathLiteralMatch = routeArgs.match(/^\s*['"`]([^'"`]*)['"`]/);
|
|
5694
|
+
const path = pathLiteralMatch && pathLiteralMatch[1].length > 0 ? pathLiteralMatch[1] : "/";
|
|
5695
|
+
const methodInfo = readMethodAfterDecorators(block, closeParen + 1);
|
|
5696
|
+
if (!methodInfo) continue;
|
|
5697
|
+
const { methodName, endPos } = methodInfo;
|
|
5698
|
+
ROUTE_DECORATOR_START.lastIndex = endPos;
|
|
5699
|
+
const apiQp = extractApiQueryParams(block.slice(decoratorStart, endPos), source);
|
|
5700
|
+
const bodyId = extractObjectFieldIdentifier(routeArgs, "body");
|
|
5701
|
+
const queryId = extractObjectFieldIdentifier(routeArgs, "query");
|
|
5702
|
+
const paramsId = extractObjectFieldIdentifier(routeArgs, "params");
|
|
5617
5703
|
out.push({
|
|
5618
5704
|
controller: cls.className,
|
|
5619
5705
|
method: methodName,
|
|
@@ -6289,7 +6375,7 @@ async function safeRun(opts, silent) {
|
|
|
6289
6375
|
//#region src/commands/generate.ts
|
|
6290
6376
|
/** Check if --dry-run was passed on the parent generate command */
|
|
6291
6377
|
function isDryRun(cmd) {
|
|
6292
|
-
return cmd.parent?.opts()?.dryRun ?? false;
|
|
6378
|
+
return (cmd.parent?.opts())?.dryRun ?? false;
|
|
6293
6379
|
}
|
|
6294
6380
|
function printGenerated(files, dryRun = false) {
|
|
6295
6381
|
const cwd = process.cwd();
|
|
@@ -6377,39 +6463,55 @@ function printGeneratorList() {
|
|
|
6377
6463
|
for (const g of GENERATORS) console.log(` kick g ${g.name.padEnd(maxName + 2)} ${g.description}`);
|
|
6378
6464
|
console.log();
|
|
6379
6465
|
}
|
|
6466
|
+
/**
|
|
6467
|
+
* Generate one or more modules. Shared by `kick g module <names...>` and
|
|
6468
|
+
* the bare `kick g <names...>` shortcut.
|
|
6469
|
+
*/
|
|
6470
|
+
async function runModuleGeneration(names, opts, dryRun) {
|
|
6471
|
+
const config = await loadKickConfig(process.cwd());
|
|
6472
|
+
const mc = resolveModuleConfig(config);
|
|
6473
|
+
const modulesDir = opts.modulesDir ?? mc.dir ?? "src/modules";
|
|
6474
|
+
const repo = opts.repo ?? resolveRepoType(mc.repo);
|
|
6475
|
+
const pattern = opts.pattern ?? config?.pattern ?? "ddd";
|
|
6476
|
+
const shouldPluralize = opts.pluralize === false ? false : mc.pluralize ?? true;
|
|
6477
|
+
const allFiles = [];
|
|
6478
|
+
for (const name of names) {
|
|
6479
|
+
const files = await generateModule({
|
|
6480
|
+
name,
|
|
6481
|
+
modulesDir: resolve(modulesDir),
|
|
6482
|
+
noEntity: opts.entity === false,
|
|
6483
|
+
noTests: opts.tests === false,
|
|
6484
|
+
repo,
|
|
6485
|
+
minimal: opts.minimal,
|
|
6486
|
+
force: opts.force,
|
|
6487
|
+
pattern,
|
|
6488
|
+
dryRun,
|
|
6489
|
+
pluralize: shouldPluralize,
|
|
6490
|
+
prismaClientPath: mc.prismaClientPath
|
|
6491
|
+
});
|
|
6492
|
+
allFiles.push(...files);
|
|
6493
|
+
}
|
|
6494
|
+
printGenerated(allFiles, dryRun);
|
|
6495
|
+
await runPostTypegen(dryRun);
|
|
6496
|
+
}
|
|
6380
6497
|
function registerGenerateCommand(program) {
|
|
6381
|
-
const gen = program.command("generate").alias("g").description("Generate code scaffolds").option("--list", "List all available generators").option("--dry-run", "Preview files that would be generated without writing them").action((opts) => {
|
|
6382
|
-
if (opts.list)
|
|
6383
|
-
|
|
6498
|
+
const gen = program.command("generate [names...]").alias("g").description("Generate code scaffolds — bare form `kick g <name>` is shorthand for `kick g module <name>`").option("--list", "List all available generators").option("--dry-run", "Preview files that would be generated without writing them").option("--no-entity", "Skip entity and value object generation (module shortcut)").option("--no-tests", "Skip test file generation (module shortcut)").option("--repo <type>", "Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>", "Override project pattern: rest | ddd | cqrs | minimal").option("--minimal", "Shorthand for --pattern minimal").option("--modules-dir <dir>", "Modules directory").option("--no-pluralize", "Use singular names (skip auto-pluralization)").option("-f, --force", "Overwrite existing files without prompting").action(async (names, opts, cmd) => {
|
|
6499
|
+
if (opts.list) {
|
|
6500
|
+
printGeneratorList();
|
|
6501
|
+
return;
|
|
6502
|
+
}
|
|
6503
|
+
if (!names || names.length === 0) {
|
|
6504
|
+
gen.help();
|
|
6505
|
+
return;
|
|
6506
|
+
}
|
|
6507
|
+
const dryRun = isDryRun(cmd);
|
|
6508
|
+
setDryRun(dryRun);
|
|
6509
|
+
await runModuleGeneration(names, opts, dryRun);
|
|
6384
6510
|
});
|
|
6385
6511
|
gen.command("module <names...>").description("Generate one or more modules (e.g. kick g module user task project)").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--repo <type>", "Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>", "Override project pattern: rest | ddd | cqrs | minimal").option("--minimal", "Shorthand for --pattern minimal").option("--modules-dir <dir>", "Modules directory").option("--no-pluralize", "Use singular names (skip auto-pluralization)").option("-f, --force", "Overwrite existing files without prompting").action(async (names, opts, cmd) => {
|
|
6386
6512
|
const dryRun = isDryRun(cmd);
|
|
6387
6513
|
setDryRun(dryRun);
|
|
6388
|
-
|
|
6389
|
-
const mc = resolveModuleConfig(config);
|
|
6390
|
-
const modulesDir = opts.modulesDir ?? mc.dir ?? "src/modules";
|
|
6391
|
-
const repo = opts.repo ?? resolveRepoType(mc.repo);
|
|
6392
|
-
const pattern = opts.pattern ?? config?.pattern ?? "ddd";
|
|
6393
|
-
const shouldPluralize = opts.pluralize === false ? false : mc.pluralize ?? true;
|
|
6394
|
-
const allFiles = [];
|
|
6395
|
-
for (const name of names) {
|
|
6396
|
-
const files = await generateModule({
|
|
6397
|
-
name,
|
|
6398
|
-
modulesDir: resolve(modulesDir),
|
|
6399
|
-
noEntity: opts.entity === false,
|
|
6400
|
-
noTests: opts.tests === false,
|
|
6401
|
-
repo,
|
|
6402
|
-
minimal: opts.minimal,
|
|
6403
|
-
force: opts.force,
|
|
6404
|
-
pattern,
|
|
6405
|
-
dryRun,
|
|
6406
|
-
pluralize: shouldPluralize,
|
|
6407
|
-
prismaClientPath: mc.prismaClientPath
|
|
6408
|
-
});
|
|
6409
|
-
allFiles.push(...files);
|
|
6410
|
-
}
|
|
6411
|
-
printGenerated(allFiles, dryRun);
|
|
6412
|
-
await runPostTypegen(dryRun);
|
|
6514
|
+
await runModuleGeneration(names, opts, dryRun);
|
|
6413
6515
|
});
|
|
6414
6516
|
gen.command("adapter <name>").description("Generate an AppAdapter with lifecycle hooks and middleware support").option("-o, --out <dir>", "Output directory", "src/adapters").action(async (name, opts, cmd) => {
|
|
6415
6517
|
const dryRun = isDryRun(cmd);
|
|
@@ -7092,8 +7194,36 @@ const PACKAGE_REGISTRY = {
|
|
|
7092
7194
|
function detectPackageManager() {
|
|
7093
7195
|
if (existsSync(resolve("pnpm-lock.yaml"))) return "pnpm";
|
|
7094
7196
|
if (existsSync(resolve("yarn.lock"))) return "yarn";
|
|
7197
|
+
if (existsSync(resolve("bun.lockb")) || existsSync(resolve("bun.lock"))) return "bun";
|
|
7095
7198
|
return "npm";
|
|
7096
7199
|
}
|
|
7200
|
+
/** Read `packageManager` from package.json (corepack convention: "pnpm@10.0.0") */
|
|
7201
|
+
function packageManagerFromPackageJson() {
|
|
7202
|
+
try {
|
|
7203
|
+
const field = JSON.parse(readFileSync(resolve("package.json"), "utf-8")).packageManager;
|
|
7204
|
+
if (typeof field !== "string") return null;
|
|
7205
|
+
const name = field.split("@")[0];
|
|
7206
|
+
return PACKAGE_MANAGERS.includes(name) ? name : null;
|
|
7207
|
+
} catch {
|
|
7208
|
+
return null;
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
/**
|
|
7212
|
+
* Resolve which package manager to use, in priority order:
|
|
7213
|
+
* 1. `--pm` CLI flag
|
|
7214
|
+
* 2. `packageManager` in kick.config
|
|
7215
|
+
* 3. `packageManager` in package.json (corepack)
|
|
7216
|
+
* 4. Lockfile detection
|
|
7217
|
+
* 5. `'npm'`
|
|
7218
|
+
*/
|
|
7219
|
+
async function resolvePackageManager(flagPm) {
|
|
7220
|
+
if (flagPm && PACKAGE_MANAGERS.includes(flagPm)) return flagPm;
|
|
7221
|
+
const config = await loadKickConfig(process.cwd());
|
|
7222
|
+
if (config?.packageManager && PACKAGE_MANAGERS.includes(config.packageManager)) return config.packageManager;
|
|
7223
|
+
const fromPkg = packageManagerFromPackageJson();
|
|
7224
|
+
if (fromPkg) return fromPkg;
|
|
7225
|
+
return detectPackageManager();
|
|
7226
|
+
}
|
|
7097
7227
|
function printPackageList() {
|
|
7098
7228
|
console.log("\n Available KickJS packages:\n");
|
|
7099
7229
|
const maxName = Math.max(...Object.keys(PACKAGE_REGISTRY).map((k) => k.length));
|
|
@@ -7117,7 +7247,7 @@ function registerAddCommand(program) {
|
|
|
7117
7247
|
printPackageList();
|
|
7118
7248
|
return;
|
|
7119
7249
|
}
|
|
7120
|
-
const pm = opts.pm
|
|
7250
|
+
const pm = await resolvePackageManager(opts.pm);
|
|
7121
7251
|
const forceDevFlag = opts.dev;
|
|
7122
7252
|
const prodDeps = /* @__PURE__ */ new Set();
|
|
7123
7253
|
const devDeps = /* @__PURE__ */ new Set();
|
|
@@ -7911,7 +8041,7 @@ async function removeModule(options) {
|
|
|
7911
8041
|
if (await fileExists(indexPath)) {
|
|
7912
8042
|
let content = await readFile(indexPath, "utf-8");
|
|
7913
8043
|
const originalContent = content;
|
|
7914
|
-
const importPattern = new RegExp(`^import\\s*\\{\\s*${pascal}Module\\s*\\}\\s*from\\s*['"][^'"]*${plural}['"].*\\n?`, "gm");
|
|
8044
|
+
const importPattern = new RegExp(`^import\\s*\\{\\s*${pascal}Module\\s*\\}\\s*from\\s*['"][^'"]*${plural}(?:/[^'"]*)?['"].*\\n?`, "gm");
|
|
7915
8045
|
content = content.replace(importPattern, "");
|
|
7916
8046
|
content = content.replace(new RegExp(`\\s*,?\\s*${pascal}Module\\s*,?`, "g"), (match) => {
|
|
7917
8047
|
const startsWithComma = match.trimStart().startsWith(",");
|
package/dist/index.d.mts
CHANGED
|
@@ -20,6 +20,8 @@ interface KickCommandDefinition {
|
|
|
20
20
|
}
|
|
21
21
|
/** Project pattern — controls what generators produce and which deps are installed */
|
|
22
22
|
type ProjectPattern = 'rest' | 'graphql' | 'ddd' | 'cqrs' | 'minimal';
|
|
23
|
+
/** Package manager used for `kick add` and other dep-installing commands */
|
|
24
|
+
type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
|
|
23
25
|
/** Built-in repository types with first-class code generation support */
|
|
24
26
|
type BuiltinRepoType$1 = 'drizzle' | 'inmemory' | 'prisma';
|
|
25
27
|
/** Custom repository type — generates a stub with TODO markers */
|
|
@@ -132,6 +134,22 @@ interface KickConfig {
|
|
|
132
134
|
* }
|
|
133
135
|
*/
|
|
134
136
|
modules?: ModuleConfig;
|
|
137
|
+
/**
|
|
138
|
+
* Package manager used by `kick add` (and any future dep-installing command)
|
|
139
|
+
* to install dependencies. When set, overrides lockfile auto-detection so
|
|
140
|
+
* commands always use the project's intended package manager.
|
|
141
|
+
*
|
|
142
|
+
* Priority (highest first):
|
|
143
|
+
* 1. `--pm` flag on the CLI
|
|
144
|
+
* 2. `packageManager` in kick.config
|
|
145
|
+
* 3. `packageManager` field in package.json (corepack convention)
|
|
146
|
+
* 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)
|
|
147
|
+
* 5. `'npm'`
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* packageManager: 'pnpm'
|
|
151
|
+
*/
|
|
152
|
+
packageManager?: PackageManager;
|
|
135
153
|
/** @deprecated Use `modules.dir` instead */
|
|
136
154
|
modulesDir?: string;
|
|
137
155
|
/** @deprecated Use `modules.repo` instead */
|
|
@@ -281,7 +299,7 @@ type ProjectTemplate = 'rest' | 'graphql' | 'ddd' | 'cqrs' | 'minimal';
|
|
|
281
299
|
interface InitProjectOptions {
|
|
282
300
|
name: string;
|
|
283
301
|
directory: string;
|
|
284
|
-
packageManager?: 'pnpm' | 'npm' | 'yarn';
|
|
302
|
+
packageManager?: 'pnpm' | 'npm' | 'yarn' | 'bun';
|
|
285
303
|
initGit?: boolean;
|
|
286
304
|
installDeps?: boolean;
|
|
287
305
|
template?: ProjectTemplate;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config.ts","../src/generators/module.ts","../src/generators/adapter.ts","../src/generators/middleware.ts","../src/generators/guard.ts","../src/generators/service.ts","../src/generators/controller.ts","../src/generators/dto.ts","../src/generators/project.ts","../src/utils/naming.ts"],"mappings":";;;UAIiB,qBAAA;EAAqB;EAEpC,IAAA;EAFoC;EAIpC,WAAA;EAAA;;;;;AAeF;;;EANE,KAAA;EAMwB;EAJxB,OAAA;AAAA;;KAIU,cAAA;;KAGA,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/config.ts","../src/generators/module.ts","../src/generators/adapter.ts","../src/generators/middleware.ts","../src/generators/guard.ts","../src/generators/service.ts","../src/generators/controller.ts","../src/generators/dto.ts","../src/generators/project.ts","../src/utils/naming.ts"],"mappings":"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"}
|