@prismakit/cli 2.1.0 → 2.2.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/bin.cjs +246 -16
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +6 -3
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-36ZM4UZV.js → chunk-7E6MWEJP.js} +242 -15
- package/dist/chunk-7E6MWEJP.js.map +1 -0
- package/dist/index.cjs +241 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/src/__tests__/naming.spec.ts +42 -0
- package/src/bin.ts +114 -0
- package/src/commands/codegen.ts +69 -0
- package/src/commands/generate.ts +63 -0
- package/src/commands/validate.ts +38 -0
- package/src/index.ts +5 -0
- package/src/naming.ts +42 -0
- package/src/templates.ts +423 -0
- package/dist/chunk-36ZM4UZV.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -92,6 +92,7 @@ function renderRepository(names, cacheEnabled, prismaImport, base) {
|
|
|
92
92
|
const cacheBlock = cacheEnabled ? ` cache: {
|
|
93
93
|
ttl: 86400,
|
|
94
94
|
sensitiveFields: ['password'],
|
|
95
|
+
defaultSetCache: true,
|
|
95
96
|
},
|
|
96
97
|
` : "";
|
|
97
98
|
const content = apply(
|
|
@@ -117,7 +118,7 @@ export type {{pascal}}Repository = InstanceType<typeof {{pascal}}Repository>;
|
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
function renderModuleFiles(options) {
|
|
120
|
-
const { names, cacheEnabled, full = false } = options;
|
|
121
|
+
const { names, cacheEnabled, full = false, helpers = false, dto = false } = options;
|
|
121
122
|
const prismaImport = options.prismaImport ?? "@prisma/client";
|
|
122
123
|
const base = `src/modules/${names.kebab}`;
|
|
123
124
|
const repository = renderRepository(names, cacheEnabled, prismaImport, base);
|
|
@@ -129,11 +130,19 @@ function renderModuleFiles(options) {
|
|
|
129
130
|
|
|
130
131
|
import { {{pascal}}Repository } from '../repositories/{{kebab}}.repository';
|
|
131
132
|
import { get{{pascal}}Select } from '../types/select-{{kebab}}.type';
|
|
132
|
-
|
|
133
|
+
import { where{{pascal}}GetManyPaginate } from '../types/where-{{kebab}}.type';
|
|
134
|
+
{{dtoImport}}
|
|
133
135
|
@Injectable()
|
|
134
136
|
export class {{pascal}}Service {
|
|
135
137
|
constructor(private readonly {{camel}}Repository: {{pascal}}Repository) {}
|
|
136
138
|
|
|
139
|
+
async handleCreate(dto: Create{{pascal}}Dto) {
|
|
140
|
+
return await this.{{camel}}Repository.create({
|
|
141
|
+
data: { ...dto },
|
|
142
|
+
select: get{{pascal}}Select('general'),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
137
146
|
async handleGetById(id: string) {
|
|
138
147
|
return await this.{{camel}}Repository.getThrowById({
|
|
139
148
|
id,
|
|
@@ -141,28 +150,150 @@ export class {{pascal}}Service {
|
|
|
141
150
|
setCache: true,
|
|
142
151
|
});
|
|
143
152
|
}
|
|
153
|
+
|
|
154
|
+
async handleGetManyPaginate(filter: Filter{{pascal}}Dto) {
|
|
155
|
+
const { where } = where{{pascal}}GetManyPaginate(filter);
|
|
156
|
+
return await this.{{camel}}Repository.getManyPaginate({
|
|
157
|
+
where,
|
|
158
|
+
select: get{{pascal}}Select('general'),
|
|
159
|
+
page: filter.page,
|
|
160
|
+
pageSize: filter.pageSize,
|
|
161
|
+
setCache: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async handleUpdateById(id: string, dto: Update{{pascal}}Dto) {
|
|
166
|
+
return await this.{{camel}}Repository.updateById({
|
|
167
|
+
id,
|
|
168
|
+
data: { ...dto },
|
|
169
|
+
select: get{{pascal}}Select('general'),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async handleDeleteById(id: string) {
|
|
174
|
+
return await this.{{camel}}Repository.deleteById({
|
|
175
|
+
id,
|
|
176
|
+
select: get{{pascal}}Select('minimal'),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
144
179
|
}
|
|
145
180
|
`,
|
|
146
181
|
names,
|
|
147
|
-
{
|
|
182
|
+
{
|
|
183
|
+
"{{dtoImport}}": dto ? `import type {
|
|
184
|
+
Create{{pascal}}Dto,
|
|
185
|
+
Update{{pascal}}Dto,
|
|
186
|
+
Filter{{pascal}}Dto,
|
|
187
|
+
} from '../dto/{{kebab}}.dto';
|
|
188
|
+
` : `type Create{{pascal}}Dto = Record<string, unknown>;
|
|
189
|
+
type Update{{pascal}}Dto = Record<string, unknown>;
|
|
190
|
+
type Filter{{pascal}}Dto = { page?: number; pageSize?: number; q?: string };
|
|
191
|
+
`
|
|
192
|
+
}
|
|
148
193
|
);
|
|
149
194
|
const controller = apply(
|
|
150
|
-
`import {
|
|
195
|
+
`import {
|
|
196
|
+
Body,
|
|
197
|
+
Controller,
|
|
198
|
+
Delete,
|
|
199
|
+
Get,
|
|
200
|
+
HttpStatus,
|
|
201
|
+
Param,
|
|
202
|
+
Patch,
|
|
203
|
+
Post,
|
|
204
|
+
Query,
|
|
205
|
+
Res,
|
|
206
|
+
} from '@nestjs/common';
|
|
207
|
+
import type { Response } from 'express';
|
|
151
208
|
|
|
152
209
|
import { {{pascal}}Service } from '../services/{{kebab}}.service';
|
|
153
|
-
|
|
210
|
+
{{dtoImport}}
|
|
154
211
|
@Controller('{{route}}')
|
|
155
212
|
export class {{pascal}}Controller {
|
|
156
213
|
constructor(private readonly {{camel}}Service: {{pascal}}Service) {}
|
|
157
214
|
|
|
215
|
+
@Post()
|
|
216
|
+
async create(@Body() dto: Create{{pascal}}Dto, @Res() res: Response) {
|
|
217
|
+
try {
|
|
218
|
+
const result = await this.{{camel}}Service.handleCreate(dto);
|
|
219
|
+
return res.status(HttpStatus.CREATED).json({ data: result });
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const status = (error as { statusCode?: number })?.statusCode ?? 500;
|
|
222
|
+
return res.status(status).json({
|
|
223
|
+
error: { message: (error as Error).message, httpStatus: status },
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@Get()
|
|
229
|
+
async getMany(@Query() filter: Filter{{pascal}}Dto, @Res() res: Response) {
|
|
230
|
+
try {
|
|
231
|
+
const result = await this.{{camel}}Service.handleGetManyPaginate(filter);
|
|
232
|
+
return res.status(HttpStatus.OK).json({ data: result.data, meta: result.meta });
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const status = (error as { statusCode?: number })?.statusCode ?? 500;
|
|
235
|
+
return res.status(status).json({
|
|
236
|
+
error: { message: (error as Error).message, httpStatus: status },
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
158
241
|
@Get(':id')
|
|
159
|
-
async getById(@Param('id') id: string) {
|
|
160
|
-
|
|
242
|
+
async getById(@Param('id') id: string, @Res() res: Response) {
|
|
243
|
+
try {
|
|
244
|
+
const result = await this.{{camel}}Service.handleGetById(id);
|
|
245
|
+
return res.status(HttpStatus.OK).json({ data: result });
|
|
246
|
+
} catch (error) {
|
|
247
|
+
const status = (error as { statusCode?: number })?.statusCode ?? 500;
|
|
248
|
+
return res.status(status).json({
|
|
249
|
+
error: { message: (error as Error).message, httpStatus: status },
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@Patch(':id')
|
|
255
|
+
async update(
|
|
256
|
+
@Param('id') id: string,
|
|
257
|
+
@Body() dto: Update{{pascal}}Dto,
|
|
258
|
+
@Res() res: Response,
|
|
259
|
+
) {
|
|
260
|
+
try {
|
|
261
|
+
const result = await this.{{camel}}Service.handleUpdateById(id, dto);
|
|
262
|
+
return res.status(HttpStatus.OK).json({ data: result });
|
|
263
|
+
} catch (error) {
|
|
264
|
+
const status = (error as { statusCode?: number })?.statusCode ?? 500;
|
|
265
|
+
return res.status(status).json({
|
|
266
|
+
error: { message: (error as Error).message, httpStatus: status },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@Delete(':id')
|
|
272
|
+
async delete(@Param('id') id: string, @Res() res: Response) {
|
|
273
|
+
try {
|
|
274
|
+
const result = await this.{{camel}}Service.handleDeleteById(id);
|
|
275
|
+
return res.status(HttpStatus.OK).json({ data: result });
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const status = (error as { statusCode?: number })?.statusCode ?? 500;
|
|
278
|
+
return res.status(status).json({
|
|
279
|
+
error: { message: (error as Error).message, httpStatus: status },
|
|
280
|
+
});
|
|
281
|
+
}
|
|
161
282
|
}
|
|
162
283
|
}
|
|
163
284
|
`,
|
|
164
285
|
names,
|
|
165
|
-
{
|
|
286
|
+
{
|
|
287
|
+
"{{dtoImport}}": dto ? `import type {
|
|
288
|
+
Create{{pascal}}Dto,
|
|
289
|
+
Update{{pascal}}Dto,
|
|
290
|
+
Filter{{pascal}}Dto,
|
|
291
|
+
} from '../dto/{{kebab}}.dto';
|
|
292
|
+
` : `type Create{{pascal}}Dto = Record<string, unknown>;
|
|
293
|
+
type Update{{pascal}}Dto = Record<string, unknown>;
|
|
294
|
+
type Filter{{pascal}}Dto = { page?: number; pageSize?: number; q?: string };
|
|
295
|
+
`
|
|
296
|
+
}
|
|
166
297
|
);
|
|
167
298
|
const moduleFile = apply(
|
|
168
299
|
`import { Module } from '@nestjs/common';
|
|
@@ -170,16 +301,21 @@ export class {{pascal}}Controller {
|
|
|
170
301
|
import { {{pascal}}Controller } from './controllers/{{kebab}}.controller';
|
|
171
302
|
import { {{pascal}}Service } from './services/{{kebab}}.service';
|
|
172
303
|
import { {{pascal}}Repository } from './repositories/{{kebab}}.repository';
|
|
173
|
-
|
|
304
|
+
{{helpersImport}}
|
|
174
305
|
@Module({
|
|
175
306
|
controllers: [{{pascal}}Controller],
|
|
176
|
-
providers: [{{pascal}}Service, {{pascal}}Repository],
|
|
307
|
+
providers: [{{pascal}}Service, {{pascal}}Repository{{helpersProviders}}],
|
|
177
308
|
exports: [{{pascal}}Service, {{pascal}}Repository],
|
|
178
309
|
})
|
|
179
310
|
export class {{pascal}}Module {}
|
|
180
311
|
`,
|
|
181
312
|
names,
|
|
182
|
-
{
|
|
313
|
+
{
|
|
314
|
+
"{{helpersImport}}": helpers ? `import { {{pascal}}ValidateHelper } from './helpers/{{kebab}}-validate.helper';
|
|
315
|
+
import { {{pascal}}MapperHelper } from './helpers/{{kebab}}-mapper.helper';
|
|
316
|
+
` : "",
|
|
317
|
+
"{{helpersProviders}}": helpers ? `, {{pascal}}ValidateHelper, {{pascal}}MapperHelper` : ""
|
|
318
|
+
}
|
|
183
319
|
);
|
|
184
320
|
const select = apply(
|
|
185
321
|
`import { Prisma } from '{{prismaImport}}';
|
|
@@ -206,18 +342,22 @@ export const {{camel}}SelectPresets = {
|
|
|
206
342
|
const where = apply(
|
|
207
343
|
`import { Prisma } from '{{prismaImport}}';
|
|
208
344
|
|
|
209
|
-
export function where{{pascal}}GetManyPaginate(
|
|
345
|
+
export function where{{pascal}}GetManyPaginate(filter: {
|
|
210
346
|
q?: string;
|
|
211
347
|
}): {
|
|
212
348
|
where: Prisma.{{pascal}}WhereInput;
|
|
213
349
|
} {
|
|
214
|
-
|
|
350
|
+
const { q } = filter;
|
|
351
|
+
const where: Prisma.{{pascal}}WhereInput = {
|
|
352
|
+
...(q ? { /* add searchable fields */ } : {}),
|
|
353
|
+
};
|
|
354
|
+
return { where };
|
|
215
355
|
}
|
|
216
356
|
`,
|
|
217
357
|
names,
|
|
218
358
|
{ "{{prismaImport}}": prismaImport }
|
|
219
359
|
);
|
|
220
|
-
|
|
360
|
+
const files = [
|
|
221
361
|
{ relativePath: `${base}/${names.kebab}.module.ts`, content: moduleFile },
|
|
222
362
|
{
|
|
223
363
|
relativePath: `${base}/controllers/${names.kebab}.controller.ts`,
|
|
@@ -237,6 +377,91 @@ export function where{{pascal}}GetManyPaginate(_filter: {
|
|
|
237
377
|
content: where
|
|
238
378
|
}
|
|
239
379
|
];
|
|
380
|
+
if (dto) {
|
|
381
|
+
files.push({
|
|
382
|
+
relativePath: `${base}/dto/${names.kebab}.dto.ts`,
|
|
383
|
+
content: apply(
|
|
384
|
+
`import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
385
|
+
import { IsOptional, IsString } from 'class-validator';
|
|
386
|
+
|
|
387
|
+
export class Create{{pascal}}Dto {
|
|
388
|
+
@ApiProperty({ example: 'name' })
|
|
389
|
+
@IsString()
|
|
390
|
+
name!: string;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export class Update{{pascal}}Dto {
|
|
394
|
+
@ApiPropertyOptional({ example: 'name' })
|
|
395
|
+
@IsOptional()
|
|
396
|
+
@IsString()
|
|
397
|
+
name?: string;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export class Filter{{pascal}}Dto {
|
|
401
|
+
@ApiPropertyOptional()
|
|
402
|
+
@IsOptional()
|
|
403
|
+
page?: number;
|
|
404
|
+
|
|
405
|
+
@ApiPropertyOptional()
|
|
406
|
+
@IsOptional()
|
|
407
|
+
pageSize?: number;
|
|
408
|
+
|
|
409
|
+
@ApiPropertyOptional()
|
|
410
|
+
@IsOptional()
|
|
411
|
+
@IsString()
|
|
412
|
+
q?: string;
|
|
413
|
+
}
|
|
414
|
+
`,
|
|
415
|
+
names,
|
|
416
|
+
{}
|
|
417
|
+
)
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (helpers) {
|
|
421
|
+
files.push(
|
|
422
|
+
{
|
|
423
|
+
relativePath: `${base}/helpers/${names.kebab}-validate.helper.ts`,
|
|
424
|
+
content: apply(
|
|
425
|
+
`import { Injectable } from '@nestjs/common';
|
|
426
|
+
|
|
427
|
+
import { {{pascal}}Repository } from '../repositories/{{kebab}}.repository';
|
|
428
|
+
import { get{{pascal}}Select } from '../types/select-{{kebab}}.type';
|
|
429
|
+
|
|
430
|
+
@Injectable()
|
|
431
|
+
export class {{pascal}}ValidateHelper {
|
|
432
|
+
constructor(private readonly {{camel}}Repository: {{pascal}}Repository) {}
|
|
433
|
+
|
|
434
|
+
async assertExists(id: string) {
|
|
435
|
+
return this.{{camel}}Repository.getThrowById({
|
|
436
|
+
id,
|
|
437
|
+
select: get{{pascal}}Select('minimal'),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
`,
|
|
442
|
+
names,
|
|
443
|
+
{}
|
|
444
|
+
)
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
relativePath: `${base}/helpers/${names.kebab}-mapper.helper.ts`,
|
|
448
|
+
content: apply(
|
|
449
|
+
`import { Injectable } from '@nestjs/common';
|
|
450
|
+
|
|
451
|
+
@Injectable()
|
|
452
|
+
export class {{pascal}}MapperHelper {
|
|
453
|
+
toResponse(entity: Record<string, unknown>) {
|
|
454
|
+
return entity;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
`,
|
|
458
|
+
names,
|
|
459
|
+
{}
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return files;
|
|
240
465
|
}
|
|
241
466
|
|
|
242
467
|
// src/commands/generate.ts
|
|
@@ -248,6 +473,8 @@ function runGenerate(options) {
|
|
|
248
473
|
names,
|
|
249
474
|
cacheEnabled: !!options.cache,
|
|
250
475
|
full,
|
|
476
|
+
helpers: !!options.helpers,
|
|
477
|
+
dto: !!options.dto,
|
|
251
478
|
prismaImport: options.prismaImport
|
|
252
479
|
});
|
|
253
480
|
for (const file of files) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/naming.ts","../src/templates.ts","../src/commands/codegen.ts","../src/commands/validate.ts"],"sourcesContent":["export { runGenerate, type GenerateCommandOptions } from './commands/generate';\nexport { runCodegen, type CodegenCommandOptions } from './commands/codegen';\nexport { runValidate, type ValidateCommandOptions } from './commands/validate';\nexport { resolveNames, assertKebabName, type ModuleNames } from './naming';\nexport { renderModuleFiles, type GeneratedFile, type GenerateOptions } from './templates';\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { resolveNames } from '../naming';\nimport { renderModuleFiles } from '../templates';\n\nexport type GenerateCommandOptions = {\n name: string;\n cache?: boolean;\n route?: string;\n cwd?: string;\n dryRun?: boolean;\n /** Emit full Nest module (controller/service/types). Default: repo-only. */\n full?: boolean;\n /** Prisma client import path (default `@prisma/client`). */\n prismaImport?: string;\n};\n\nexport function runGenerate(options: GenerateCommandOptions): void {\n const cwd = options.cwd ?? process.cwd();\n const names = resolveNames(options.name, options.route);\n const full = !!options.full;\n const files = renderModuleFiles({\n names,\n cacheEnabled: !!options.cache,\n full,\n prismaImport: options.prismaImport,\n });\n\n for (const file of files) {\n const fullPath = path.join(cwd, file.relativePath);\n if (options.dryRun) {\n console.log(`[dry-run] would write ${file.relativePath}`);\n continue;\n }\n if (fs.existsSync(fullPath)) {\n console.warn(`skip (exists): ${file.relativePath}`);\n continue;\n }\n fs.mkdirSync(path.dirname(fullPath), { recursive: true });\n const content = file.content.endsWith('\\n')\n ? file.content\n : `${file.content}\\n`;\n fs.writeFileSync(fullPath, content, 'utf-8');\n console.log(`created ${file.relativePath}`);\n }\n\n if (full) {\n console.log(\n `\\nScaffolded module \"${names.kebab}\". Register ${names.pascal}Module in app.module.ts.`,\n );\n } else {\n console.log(\n `\\nScaffolded repository \"${names.pascal}Repository\". Register it in your feature module providers.`,\n );\n }\n}\n","export interface ModuleNames {\n kebab: string;\n camel: string;\n pascal: string;\n repoModel: string;\n route: string;\n}\n\nconst KEBAB_NAME_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;\n\nexport function assertKebabName(name: string): void {\n if (!KEBAB_NAME_RE.test(name)) {\n throw new Error(\n `Invalid module name \"${name}\". Use kebab-case (e.g. product, blog-post).`,\n );\n }\n}\n\nfunction kebabToPascal(kebab: string): string {\n return kebab\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\nfunction kebabToCamel(kebab: string): string {\n const pascal = kebabToPascal(kebab);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\nexport function resolveNames(kebab: string, route?: string): ModuleNames {\n assertKebabName(kebab);\n const pascal = kebabToPascal(kebab);\n const camel = kebabToCamel(kebab);\n return {\n kebab,\n camel,\n pascal,\n repoModel: camel,\n route: route ?? kebab,\n };\n}\n","import type { ModuleNames } from './naming';\n\nexport type GenerateOptions = {\n names: ModuleNames;\n cacheEnabled: boolean;\n /** When false (default), only emit the repository file. */\n full?: boolean;\n /** Prisma client import path (default `@prisma/client`). */\n prismaImport?: string;\n};\n\nfunction apply(template: string, names: ModuleNames, extras: Record<string, string>): string {\n const replacements: Record<string, string> = {\n '{{pascal}}': names.pascal,\n '{{camel}}': names.camel,\n '{{kebab}}': names.kebab,\n '{{route}}': names.route,\n '{{repoModel}}': names.repoModel,\n ...extras,\n };\n let result = template;\n for (const [key, value] of Object.entries(replacements)) {\n result = result.split(key).join(value);\n }\n return result;\n}\n\nexport type GeneratedFile = {\n relativePath: string;\n content: string;\n};\n\nfunction renderRepository(\n names: ModuleNames,\n cacheEnabled: boolean,\n prismaImport: string,\n base: string,\n): GeneratedFile {\n const cacheBlock = cacheEnabled\n ? ` cache: {\n ttl: 86400,\n sensitiveFields: ['password'],\n },\n`\n : '';\n\n const content = apply(\n `import { Prisma } from '{{prismaImport}}';\nimport { createInjectableRepository } from '@prismakit/nestjs';\n\nexport const {{pascal}}Repository = createInjectableRepository({\n model: '{{repoModel}}',\n scalarFields: Prisma.{{pascal}}ScalarFieldEnum,\n{{cacheBlock}}});\n\nexport type {{pascal}}Repository = InstanceType<typeof {{pascal}}Repository>;\n`,\n names,\n {\n '{{cacheBlock}}': cacheBlock,\n '{{prismaImport}}': prismaImport,\n },\n );\n\n return {\n relativePath: `${base}/repositories/${names.kebab}.repository.ts`,\n content,\n };\n}\n\nexport function renderModuleFiles(options: GenerateOptions): GeneratedFile[] {\n const { names, cacheEnabled, full = false } = options;\n const prismaImport = options.prismaImport ?? '@prisma/client';\n const base = `src/modules/${names.kebab}`;\n\n const repository = renderRepository(names, cacheEnabled, prismaImport, base);\n\n if (!full) {\n return [repository];\n }\n\n const service = apply(\n `import { Injectable } from '@nestjs/common';\n\nimport { {{pascal}}Repository } from '../repositories/{{kebab}}.repository';\nimport { get{{pascal}}Select } from '../types/select-{{kebab}}.type';\n\n@Injectable()\nexport class {{pascal}}Service {\n constructor(private readonly {{camel}}Repository: {{pascal}}Repository) {}\n\n async handleGetById(id: string) {\n return await this.{{camel}}Repository.getThrowById({\n id,\n select: get{{pascal}}Select('general'),\n setCache: true,\n });\n }\n}\n`,\n names,\n {},\n );\n\n const controller = apply(\n `import { Controller, Get, Param } from '@nestjs/common';\n\nimport { {{pascal}}Service } from '../services/{{kebab}}.service';\n\n@Controller('{{route}}')\nexport class {{pascal}}Controller {\n constructor(private readonly {{camel}}Service: {{pascal}}Service) {}\n\n @Get(':id')\n async getById(@Param('id') id: string) {\n return this.{{camel}}Service.handleGetById(id);\n }\n}\n`,\n names,\n {},\n );\n\n const moduleFile = apply(\n `import { Module } from '@nestjs/common';\n\nimport { {{pascal}}Controller } from './controllers/{{kebab}}.controller';\nimport { {{pascal}}Service } from './services/{{kebab}}.service';\nimport { {{pascal}}Repository } from './repositories/{{kebab}}.repository';\n\n@Module({\n controllers: [{{pascal}}Controller],\n providers: [{{pascal}}Service, {{pascal}}Repository],\n exports: [{{pascal}}Service, {{pascal}}Repository],\n})\nexport class {{pascal}}Module {}\n`,\n names,\n {},\n );\n\n const select = apply(\n `import { Prisma } from '{{prismaImport}}';\n\ntype {{pascal}}SelectPresetKey = keyof typeof {{camel}}SelectPresets;\n\nexport function get{{pascal}}Select<K extends {{pascal}}SelectPresetKey>(key: K) {\n return {{camel}}SelectPresets[key];\n}\n\nexport const {{camel}}SelectPresets = {\n minimal: {\n id: true,\n } satisfies Prisma.{{pascal}}Select,\n\n general: {\n id: true,\n } satisfies Prisma.{{pascal}}Select,\n};\n`,\n names,\n { '{{prismaImport}}': prismaImport },\n );\n\n const where = apply(\n `import { Prisma } from '{{prismaImport}}';\n\nexport function where{{pascal}}GetManyPaginate(_filter: {\n q?: string;\n}): {\n where: Prisma.{{pascal}}WhereInput;\n} {\n return { where: {} };\n}\n`,\n names,\n { '{{prismaImport}}': prismaImport },\n );\n\n return [\n { relativePath: `${base}/${names.kebab}.module.ts`, content: moduleFile },\n {\n relativePath: `${base}/controllers/${names.kebab}.controller.ts`,\n content: controller,\n },\n {\n relativePath: `${base}/services/${names.kebab}.service.ts`,\n content: service,\n },\n repository,\n {\n relativePath: `${base}/types/select-${names.kebab}.type.ts`,\n content: select,\n },\n {\n relativePath: `${base}/types/where-${names.kebab}.type.ts`,\n content: where,\n },\n ];\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n computeRelationAliasesFromSchema,\n getSchemaModels,\n} from '@prismakit/core';\n\nexport type CodegenCommandOptions = {\n cwd?: string;\n schemaPath?: string;\n write?: boolean;\n outFile?: string;\n};\n\n/**\n * Parse prisma/schema.prisma and print (or write) suggested relation aliases.\n */\nexport function runCodegen(options: CodegenCommandOptions = {}): void {\n const cwd = options.cwd ?? process.cwd();\n const schemaPath =\n options.schemaPath ?? path.join(cwd, 'prisma', 'schema.prisma');\n\n if (!fs.existsSync(schemaPath)) {\n throw new Error(`Prisma schema not found at ${schemaPath}`);\n }\n\n const models = getSchemaModels(schemaPath);\n const aliases = computeRelationAliasesFromSchema(models);\n\n const entries = Object.entries(aliases).sort(([a], [b]) =>\n a.localeCompare(b),\n );\n\n if (entries.length === 0) {\n console.log(\n 'No additional relation aliases suggested (suffix rules cover all).',\n );\n return;\n }\n\n const lines = [\n '// Suggested RELATION_MODEL_ALIASES entries (merge into your resolver config)',\n 'export const SUGGESTED_RELATION_MODEL_ALIASES = {',\n ...entries.map(([k, v]) => ` ${k}: '${v}',`),\n '} as const;',\n '',\n ];\n const output = lines.join('\\n');\n\n if (options.write) {\n const out =\n options.outFile ??\n path.join(\n cwd,\n 'src',\n 'infrastructure',\n 'prisma',\n 'suggested-relation-aliases.ts',\n );\n fs.mkdirSync(path.dirname(out), { recursive: true });\n fs.writeFileSync(out, output, 'utf-8');\n console.log(`wrote ${path.relative(cwd, out)}`);\n } else {\n console.log(output);\n }\n\n console.log(`\\n${entries.length} alias suggestion(s).`);\n}\n","import {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n};\n\n/**\n * Run select-compose validation from @prismakit/core.\n */\nexport function runValidate(options: ValidateCommandOptions = {}): void {\n const cwd = options.cwd ?? process.cwd();\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd);\n console.log('Select compose validation passed.');\n } catch (err) {\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n return;\n }\n\n const issues = validateSelectCompose(cwd);\n if (issues.length === 0) {\n console.log('Select compose validation passed.');\n return;\n }\n\n for (const issue of issues) {\n console.error(` - ${issue.file}: ${issue.message}`);\n }\n process.exitCode = 1;\n}\n"],"mappings":"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","names":["fs","path","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/naming.ts","../src/templates.ts","../src/commands/codegen.ts","../src/commands/validate.ts"],"sourcesContent":["export { runGenerate, type GenerateCommandOptions } from './commands/generate';\nexport { runCodegen, type CodegenCommandOptions } from './commands/codegen';\nexport { runValidate, type ValidateCommandOptions } from './commands/validate';\nexport { resolveNames, assertKebabName, type ModuleNames } from './naming';\nexport { renderModuleFiles, type GeneratedFile, type GenerateOptions } from './templates';\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { resolveNames } from '../naming';\nimport { renderModuleFiles } from '../templates';\n\nexport type GenerateCommandOptions = {\n name: string;\n cache?: boolean;\n route?: string;\n cwd?: string;\n dryRun?: boolean;\n /** Emit full Nest module (controller/service/types). Default: repo-only. */\n full?: boolean;\n /** Emit validate + mapper helpers. */\n helpers?: boolean;\n /** Emit class-validator DTOs. */\n dto?: boolean;\n /** Prisma client import path (default `@prisma/client`). */\n prismaImport?: string;\n};\n\nexport function runGenerate(options: GenerateCommandOptions): void {\n const cwd = options.cwd ?? process.cwd();\n const names = resolveNames(options.name, options.route);\n const full = !!options.full;\n const files = renderModuleFiles({\n names,\n cacheEnabled: !!options.cache,\n full,\n helpers: !!options.helpers,\n dto: !!options.dto,\n prismaImport: options.prismaImport,\n });\n\n for (const file of files) {\n const fullPath = path.join(cwd, file.relativePath);\n if (options.dryRun) {\n console.log(`[dry-run] would write ${file.relativePath}`);\n continue;\n }\n if (fs.existsSync(fullPath)) {\n console.warn(`skip (exists): ${file.relativePath}`);\n continue;\n }\n fs.mkdirSync(path.dirname(fullPath), { recursive: true });\n const content = file.content.endsWith('\\n')\n ? file.content\n : `${file.content}\\n`;\n fs.writeFileSync(fullPath, content, 'utf-8');\n console.log(`created ${file.relativePath}`);\n }\n\n if (full) {\n console.log(\n `\\nScaffolded module \"${names.kebab}\". Register ${names.pascal}Module in app.module.ts.`,\n );\n } else {\n console.log(\n `\\nScaffolded repository \"${names.pascal}Repository\". Register it in your feature module providers.`,\n );\n }\n}\n","export interface ModuleNames {\n kebab: string;\n camel: string;\n pascal: string;\n repoModel: string;\n route: string;\n}\n\nconst KEBAB_NAME_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;\n\nexport function assertKebabName(name: string): void {\n if (!KEBAB_NAME_RE.test(name)) {\n throw new Error(\n `Invalid module name \"${name}\". Use kebab-case (e.g. product, blog-post).`,\n );\n }\n}\n\nfunction kebabToPascal(kebab: string): string {\n return kebab\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\nfunction kebabToCamel(kebab: string): string {\n const pascal = kebabToPascal(kebab);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\nexport function resolveNames(kebab: string, route?: string): ModuleNames {\n assertKebabName(kebab);\n const pascal = kebabToPascal(kebab);\n const camel = kebabToCamel(kebab);\n return {\n kebab,\n camel,\n pascal,\n repoModel: camel,\n route: route ?? kebab,\n };\n}\n","import type { ModuleNames } from './naming';\n\nexport type GenerateOptions = {\n names: ModuleNames;\n cacheEnabled: boolean;\n /** When false (default), only emit the repository file. */\n full?: boolean;\n /** Emit validate + mapper helpers. */\n helpers?: boolean;\n /** Emit class-validator DTOs with @ApiProperty. */\n dto?: boolean;\n /** Prisma client import path (default `@prisma/client`). */\n prismaImport?: string;\n};\n\nfunction apply(template: string, names: ModuleNames, extras: Record<string, string>): string {\n const replacements: Record<string, string> = {\n '{{pascal}}': names.pascal,\n '{{camel}}': names.camel,\n '{{kebab}}': names.kebab,\n '{{route}}': names.route,\n '{{repoModel}}': names.repoModel,\n ...extras,\n };\n let result = template;\n for (const [key, value] of Object.entries(replacements)) {\n result = result.split(key).join(value);\n }\n return result;\n}\n\nexport type GeneratedFile = {\n relativePath: string;\n content: string;\n};\n\nfunction renderRepository(\n names: ModuleNames,\n cacheEnabled: boolean,\n prismaImport: string,\n base: string,\n): GeneratedFile {\n const cacheBlock = cacheEnabled\n ? ` cache: {\n ttl: 86400,\n sensitiveFields: ['password'],\n defaultSetCache: true,\n },\n`\n : '';\n\n const content = apply(\n `import { Prisma } from '{{prismaImport}}';\nimport { createInjectableRepository } from '@prismakit/nestjs';\n\nexport const {{pascal}}Repository = createInjectableRepository({\n model: '{{repoModel}}',\n scalarFields: Prisma.{{pascal}}ScalarFieldEnum,\n{{cacheBlock}}});\n\nexport type {{pascal}}Repository = InstanceType<typeof {{pascal}}Repository>;\n`,\n names,\n {\n '{{cacheBlock}}': cacheBlock,\n '{{prismaImport}}': prismaImport,\n },\n );\n\n return {\n relativePath: `${base}/repositories/${names.kebab}.repository.ts`,\n content,\n };\n}\n\nexport function renderModuleFiles(options: GenerateOptions): GeneratedFile[] {\n const { names, cacheEnabled, full = false, helpers = false, dto = false } =\n options;\n const prismaImport = options.prismaImport ?? '@prisma/client';\n const base = `src/modules/${names.kebab}`;\n\n const repository = renderRepository(names, cacheEnabled, prismaImport, base);\n\n if (!full) {\n return [repository];\n }\n\n const service = apply(\n `import { Injectable } from '@nestjs/common';\n\nimport { {{pascal}}Repository } from '../repositories/{{kebab}}.repository';\nimport { get{{pascal}}Select } from '../types/select-{{kebab}}.type';\nimport { where{{pascal}}GetManyPaginate } from '../types/where-{{kebab}}.type';\n{{dtoImport}}\n@Injectable()\nexport class {{pascal}}Service {\n constructor(private readonly {{camel}}Repository: {{pascal}}Repository) {}\n\n async handleCreate(dto: Create{{pascal}}Dto) {\n return await this.{{camel}}Repository.create({\n data: { ...dto },\n select: get{{pascal}}Select('general'),\n });\n }\n\n async handleGetById(id: string) {\n return await this.{{camel}}Repository.getThrowById({\n id,\n select: get{{pascal}}Select('general'),\n setCache: true,\n });\n }\n\n async handleGetManyPaginate(filter: Filter{{pascal}}Dto) {\n const { where } = where{{pascal}}GetManyPaginate(filter);\n return await this.{{camel}}Repository.getManyPaginate({\n where,\n select: get{{pascal}}Select('general'),\n page: filter.page,\n pageSize: filter.pageSize,\n setCache: true,\n });\n }\n\n async handleUpdateById(id: string, dto: Update{{pascal}}Dto) {\n return await this.{{camel}}Repository.updateById({\n id,\n data: { ...dto },\n select: get{{pascal}}Select('general'),\n });\n }\n\n async handleDeleteById(id: string) {\n return await this.{{camel}}Repository.deleteById({\n id,\n select: get{{pascal}}Select('minimal'),\n });\n }\n}\n`,\n names,\n {\n '{{dtoImport}}': dto\n ? `import type {\\n Create{{pascal}}Dto,\\n Update{{pascal}}Dto,\\n Filter{{pascal}}Dto,\\n} from '../dto/{{kebab}}.dto';\\n`\n : `type Create{{pascal}}Dto = Record<string, unknown>;\\ntype Update{{pascal}}Dto = Record<string, unknown>;\\ntype Filter{{pascal}}Dto = { page?: number; pageSize?: number; q?: string };\\n`,\n },\n );\n\n const controller = apply(\n `import {\n Body,\n Controller,\n Delete,\n Get,\n HttpStatus,\n Param,\n Patch,\n Post,\n Query,\n Res,\n} from '@nestjs/common';\nimport type { Response } from 'express';\n\nimport { {{pascal}}Service } from '../services/{{kebab}}.service';\n{{dtoImport}}\n@Controller('{{route}}')\nexport class {{pascal}}Controller {\n constructor(private readonly {{camel}}Service: {{pascal}}Service) {}\n\n @Post()\n async create(@Body() dto: Create{{pascal}}Dto, @Res() res: Response) {\n try {\n const result = await this.{{camel}}Service.handleCreate(dto);\n return res.status(HttpStatus.CREATED).json({ data: result });\n } catch (error) {\n const status = (error as { statusCode?: number })?.statusCode ?? 500;\n return res.status(status).json({\n error: { message: (error as Error).message, httpStatus: status },\n });\n }\n }\n\n @Get()\n async getMany(@Query() filter: Filter{{pascal}}Dto, @Res() res: Response) {\n try {\n const result = await this.{{camel}}Service.handleGetManyPaginate(filter);\n return res.status(HttpStatus.OK).json({ data: result.data, meta: result.meta });\n } catch (error) {\n const status = (error as { statusCode?: number })?.statusCode ?? 500;\n return res.status(status).json({\n error: { message: (error as Error).message, httpStatus: status },\n });\n }\n }\n\n @Get(':id')\n async getById(@Param('id') id: string, @Res() res: Response) {\n try {\n const result = await this.{{camel}}Service.handleGetById(id);\n return res.status(HttpStatus.OK).json({ data: result });\n } catch (error) {\n const status = (error as { statusCode?: number })?.statusCode ?? 500;\n return res.status(status).json({\n error: { message: (error as Error).message, httpStatus: status },\n });\n }\n }\n\n @Patch(':id')\n async update(\n @Param('id') id: string,\n @Body() dto: Update{{pascal}}Dto,\n @Res() res: Response,\n ) {\n try {\n const result = await this.{{camel}}Service.handleUpdateById(id, dto);\n return res.status(HttpStatus.OK).json({ data: result });\n } catch (error) {\n const status = (error as { statusCode?: number })?.statusCode ?? 500;\n return res.status(status).json({\n error: { message: (error as Error).message, httpStatus: status },\n });\n }\n }\n\n @Delete(':id')\n async delete(@Param('id') id: string, @Res() res: Response) {\n try {\n const result = await this.{{camel}}Service.handleDeleteById(id);\n return res.status(HttpStatus.OK).json({ data: result });\n } catch (error) {\n const status = (error as { statusCode?: number })?.statusCode ?? 500;\n return res.status(status).json({\n error: { message: (error as Error).message, httpStatus: status },\n });\n }\n }\n}\n`,\n names,\n {\n '{{dtoImport}}': dto\n ? `import type {\\n Create{{pascal}}Dto,\\n Update{{pascal}}Dto,\\n Filter{{pascal}}Dto,\\n} from '../dto/{{kebab}}.dto';\\n`\n : `type Create{{pascal}}Dto = Record<string, unknown>;\\ntype Update{{pascal}}Dto = Record<string, unknown>;\\ntype Filter{{pascal}}Dto = { page?: number; pageSize?: number; q?: string };\\n`,\n },\n );\n\n const moduleFile = apply(\n `import { Module } from '@nestjs/common';\n\nimport { {{pascal}}Controller } from './controllers/{{kebab}}.controller';\nimport { {{pascal}}Service } from './services/{{kebab}}.service';\nimport { {{pascal}}Repository } from './repositories/{{kebab}}.repository';\n{{helpersImport}}\n@Module({\n controllers: [{{pascal}}Controller],\n providers: [{{pascal}}Service, {{pascal}}Repository{{helpersProviders}}],\n exports: [{{pascal}}Service, {{pascal}}Repository],\n})\nexport class {{pascal}}Module {}\n`,\n names,\n {\n '{{helpersImport}}': helpers\n ? `import { {{pascal}}ValidateHelper } from './helpers/{{kebab}}-validate.helper';\\nimport { {{pascal}}MapperHelper } from './helpers/{{kebab}}-mapper.helper';\\n`\n : '',\n '{{helpersProviders}}': helpers\n ? `, {{pascal}}ValidateHelper, {{pascal}}MapperHelper`\n : '',\n },\n );\n\n const select = apply(\n `import { Prisma } from '{{prismaImport}}';\n\ntype {{pascal}}SelectPresetKey = keyof typeof {{camel}}SelectPresets;\n\nexport function get{{pascal}}Select<K extends {{pascal}}SelectPresetKey>(key: K) {\n return {{camel}}SelectPresets[key];\n}\n\nexport const {{camel}}SelectPresets = {\n minimal: {\n id: true,\n } satisfies Prisma.{{pascal}}Select,\n\n general: {\n id: true,\n } satisfies Prisma.{{pascal}}Select,\n};\n`,\n names,\n { '{{prismaImport}}': prismaImport },\n );\n\n const where = apply(\n `import { Prisma } from '{{prismaImport}}';\n\nexport function where{{pascal}}GetManyPaginate(filter: {\n q?: string;\n}): {\n where: Prisma.{{pascal}}WhereInput;\n} {\n const { q } = filter;\n const where: Prisma.{{pascal}}WhereInput = {\n ...(q ? { /* add searchable fields */ } : {}),\n };\n return { where };\n}\n`,\n names,\n { '{{prismaImport}}': prismaImport },\n );\n\n const files: GeneratedFile[] = [\n { relativePath: `${base}/${names.kebab}.module.ts`, content: moduleFile },\n {\n relativePath: `${base}/controllers/${names.kebab}.controller.ts`,\n content: controller,\n },\n {\n relativePath: `${base}/services/${names.kebab}.service.ts`,\n content: service,\n },\n repository,\n {\n relativePath: `${base}/types/select-${names.kebab}.type.ts`,\n content: select,\n },\n {\n relativePath: `${base}/types/where-${names.kebab}.type.ts`,\n content: where,\n },\n ];\n\n if (dto) {\n files.push({\n relativePath: `${base}/dto/${names.kebab}.dto.ts`,\n content: apply(\n `import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';\nimport { IsOptional, IsString } from 'class-validator';\n\nexport class Create{{pascal}}Dto {\n @ApiProperty({ example: 'name' })\n @IsString()\n name!: string;\n}\n\nexport class Update{{pascal}}Dto {\n @ApiPropertyOptional({ example: 'name' })\n @IsOptional()\n @IsString()\n name?: string;\n}\n\nexport class Filter{{pascal}}Dto {\n @ApiPropertyOptional()\n @IsOptional()\n page?: number;\n\n @ApiPropertyOptional()\n @IsOptional()\n pageSize?: number;\n\n @ApiPropertyOptional()\n @IsOptional()\n @IsString()\n q?: string;\n}\n`,\n names,\n {},\n ),\n });\n }\n\n if (helpers) {\n files.push(\n {\n relativePath: `${base}/helpers/${names.kebab}-validate.helper.ts`,\n content: apply(\n `import { Injectable } from '@nestjs/common';\n\nimport { {{pascal}}Repository } from '../repositories/{{kebab}}.repository';\nimport { get{{pascal}}Select } from '../types/select-{{kebab}}.type';\n\n@Injectable()\nexport class {{pascal}}ValidateHelper {\n constructor(private readonly {{camel}}Repository: {{pascal}}Repository) {}\n\n async assertExists(id: string) {\n return this.{{camel}}Repository.getThrowById({\n id,\n select: get{{pascal}}Select('minimal'),\n });\n }\n}\n`,\n names,\n {},\n ),\n },\n {\n relativePath: `${base}/helpers/${names.kebab}-mapper.helper.ts`,\n content: apply(\n `import { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class {{pascal}}MapperHelper {\n toResponse(entity: Record<string, unknown>) {\n return entity;\n }\n}\n`,\n names,\n {},\n ),\n },\n );\n }\n\n return files;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n computeRelationAliasesFromSchema,\n getSchemaModels,\n} from '@prismakit/core';\n\nexport type CodegenCommandOptions = {\n cwd?: string;\n schemaPath?: string;\n write?: boolean;\n outFile?: string;\n};\n\n/**\n * Parse prisma/schema.prisma and print (or write) suggested relation aliases.\n */\nexport function runCodegen(options: CodegenCommandOptions = {}): void {\n const cwd = options.cwd ?? process.cwd();\n const schemaPath =\n options.schemaPath ?? path.join(cwd, 'prisma', 'schema.prisma');\n\n if (!fs.existsSync(schemaPath)) {\n throw new Error(`Prisma schema not found at ${schemaPath}`);\n }\n\n const models = getSchemaModels(schemaPath);\n const aliases = computeRelationAliasesFromSchema(models);\n\n const entries = Object.entries(aliases).sort(([a], [b]) =>\n a.localeCompare(b),\n );\n\n if (entries.length === 0) {\n console.log(\n 'No additional relation aliases suggested (suffix rules cover all).',\n );\n return;\n }\n\n const lines = [\n '// Suggested RELATION_MODEL_ALIASES entries (merge into your resolver config)',\n 'export const SUGGESTED_RELATION_MODEL_ALIASES = {',\n ...entries.map(([k, v]) => ` ${k}: '${v}',`),\n '} as const;',\n '',\n ];\n const output = lines.join('\\n');\n\n if (options.write) {\n const out =\n options.outFile ??\n path.join(\n cwd,\n 'src',\n 'infrastructure',\n 'prisma',\n 'suggested-relation-aliases.ts',\n );\n fs.mkdirSync(path.dirname(out), { recursive: true });\n fs.writeFileSync(out, output, 'utf-8');\n console.log(`wrote ${path.relative(cwd, out)}`);\n } else {\n console.log(output);\n }\n\n console.log(`\\n${entries.length} alias suggestion(s).`);\n}\n","import {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n};\n\n/**\n * Run select-compose validation from @prismakit/core.\n */\nexport function runValidate(options: ValidateCommandOptions = {}): void {\n const cwd = options.cwd ?? process.cwd();\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd);\n console.log('Select compose validation passed.');\n } catch (err) {\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n return;\n }\n\n const issues = validateSelectCompose(cwd);\n if (issues.length === 0) {\n console.log('Select compose validation passed.');\n return;\n }\n\n for (const issue of issues) {\n console.error(` - ${issue.file}: ${issue.message}`);\n }\n process.exitCode = 1;\n}\n"],"mappings":"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","names":["fs","path","import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -6,6 +6,10 @@ type GenerateCommandOptions = {
|
|
|
6
6
|
dryRun?: boolean;
|
|
7
7
|
/** Emit full Nest module (controller/service/types). Default: repo-only. */
|
|
8
8
|
full?: boolean;
|
|
9
|
+
/** Emit validate + mapper helpers. */
|
|
10
|
+
helpers?: boolean;
|
|
11
|
+
/** Emit class-validator DTOs. */
|
|
12
|
+
dto?: boolean;
|
|
9
13
|
/** Prisma client import path (default `@prisma/client`). */
|
|
10
14
|
prismaImport?: string;
|
|
11
15
|
};
|
|
@@ -46,6 +50,10 @@ type GenerateOptions = {
|
|
|
46
50
|
cacheEnabled: boolean;
|
|
47
51
|
/** When false (default), only emit the repository file. */
|
|
48
52
|
full?: boolean;
|
|
53
|
+
/** Emit validate + mapper helpers. */
|
|
54
|
+
helpers?: boolean;
|
|
55
|
+
/** Emit class-validator DTOs with @ApiProperty. */
|
|
56
|
+
dto?: boolean;
|
|
49
57
|
/** Prisma client import path (default `@prisma/client`). */
|
|
50
58
|
prismaImport?: string;
|
|
51
59
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ type GenerateCommandOptions = {
|
|
|
6
6
|
dryRun?: boolean;
|
|
7
7
|
/** Emit full Nest module (controller/service/types). Default: repo-only. */
|
|
8
8
|
full?: boolean;
|
|
9
|
+
/** Emit validate + mapper helpers. */
|
|
10
|
+
helpers?: boolean;
|
|
11
|
+
/** Emit class-validator DTOs. */
|
|
12
|
+
dto?: boolean;
|
|
9
13
|
/** Prisma client import path (default `@prisma/client`). */
|
|
10
14
|
prismaImport?: string;
|
|
11
15
|
};
|
|
@@ -46,6 +50,10 @@ type GenerateOptions = {
|
|
|
46
50
|
cacheEnabled: boolean;
|
|
47
51
|
/** When false (default), only emit the repository file. */
|
|
48
52
|
full?: boolean;
|
|
53
|
+
/** Emit validate + mapper helpers. */
|
|
54
|
+
helpers?: boolean;
|
|
55
|
+
/** Emit class-validator DTOs with @ApiProperty. */
|
|
56
|
+
dto?: boolean;
|
|
49
57
|
/** Prisma client import path (default `@prisma/client`). */
|
|
50
58
|
prismaImport?: string;
|
|
51
59
|
};
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prismakit/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "CLI for PrismaKit — generate modules, codegen relation aliases, validate compose",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -22,14 +22,15 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
+
"src",
|
|
25
26
|
"LICENSE",
|
|
26
27
|
"README.md"
|
|
27
28
|
],
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@prismakit/core": "2.
|
|
30
|
+
"@prismakit/core": "2.2.0"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"@prismakit/core": ">=2.1.
|
|
33
|
+
"@prismakit/core": ">=2.1.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"typescript": "^5.9.2",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveNames } from '../naming';
|
|
3
|
+
import { renderModuleFiles } from '../templates';
|
|
4
|
+
|
|
5
|
+
describe('cli naming', () => {
|
|
6
|
+
it('resolves kebab to pascal/camel', () => {
|
|
7
|
+
const n = resolveNames('product-category');
|
|
8
|
+
expect(n.kebab).toBe('product-category');
|
|
9
|
+
expect(n.pascal).toBe('ProductCategory');
|
|
10
|
+
expect(n.camel).toBe('productCategory');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('cli generate templates', () => {
|
|
15
|
+
it('defaults to repo-only', () => {
|
|
16
|
+
const files = renderModuleFiles({
|
|
17
|
+
names: resolveNames('product'),
|
|
18
|
+
cacheEnabled: true,
|
|
19
|
+
});
|
|
20
|
+
expect(files).toHaveLength(1);
|
|
21
|
+
expect(files[0].relativePath).toContain('repositories/product.repository.ts');
|
|
22
|
+
expect(files[0].content).toContain("model: 'product'");
|
|
23
|
+
expect(files[0].content).toContain('cache: {');
|
|
24
|
+
expect(files[0].content).not.toContain('getDelegate');
|
|
25
|
+
expect(files[0].content).toContain("from '@prisma/client'");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('emits full Nest module when full: true', () => {
|
|
29
|
+
const files = renderModuleFiles({
|
|
30
|
+
names: resolveNames('product'),
|
|
31
|
+
cacheEnabled: false,
|
|
32
|
+
full: true,
|
|
33
|
+
});
|
|
34
|
+
const paths = files.map((f) => f.relativePath);
|
|
35
|
+
expect(paths.some((p) => p.endsWith('product.module.ts'))).toBe(true);
|
|
36
|
+
expect(paths.some((p) => p.includes('controllers/'))).toBe(true);
|
|
37
|
+
expect(paths.some((p) => p.includes('services/'))).toBe(true);
|
|
38
|
+
const controller = files.find((f) => f.relativePath.includes('controller'));
|
|
39
|
+
expect(controller?.content).not.toContain('JwtGuard');
|
|
40
|
+
expect(controller?.content).not.toContain('@nestjs/swagger');
|
|
41
|
+
});
|
|
42
|
+
});
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { runGenerate } from './commands/generate';
|
|
2
|
+
import { runCodegen } from './commands/codegen';
|
|
3
|
+
import { runValidate } from './commands/validate';
|
|
4
|
+
|
|
5
|
+
function printHelp(): void {
|
|
6
|
+
console.log(`prismakit — PrismaKit CLI
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
prismakit generate <name> [--cache] [--full] [--helpers] [--dto] [--route <path>] [--prisma-import <path>] [--dry-run]
|
|
10
|
+
prismakit codegen [--schema <path>] [--write] [--out <file>]
|
|
11
|
+
prismakit validate [--no-assert]
|
|
12
|
+
prismakit help
|
|
13
|
+
|
|
14
|
+
By default, generate writes only the repository file.
|
|
15
|
+
Pass --full for a Nest module (controller, service, types).
|
|
16
|
+
Pass --helpers / --dto with --full for helpers and Swagger DTOs.
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
prismakit generate product --cache
|
|
20
|
+
prismakit generate product --cache --full --helpers --dto --route products
|
|
21
|
+
prismakit codegen --write
|
|
22
|
+
prismakit validate
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv: string[]): {
|
|
27
|
+
command: string;
|
|
28
|
+
positional: string[];
|
|
29
|
+
flags: Record<string, string | boolean>;
|
|
30
|
+
} {
|
|
31
|
+
const [command = 'help', ...rest] = argv;
|
|
32
|
+
const positional: string[] = [];
|
|
33
|
+
const flags: Record<string, string | boolean> = {};
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < rest.length; i++) {
|
|
36
|
+
const arg = rest[i];
|
|
37
|
+
if (arg.startsWith('--')) {
|
|
38
|
+
const key = arg.slice(2);
|
|
39
|
+
const next = rest[i + 1];
|
|
40
|
+
if (next && !next.startsWith('--')) {
|
|
41
|
+
flags[key] = next;
|
|
42
|
+
i++;
|
|
43
|
+
} else {
|
|
44
|
+
flags[key] = true;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
positional.push(arg);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { command, positional, flags };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function main(): void {
|
|
55
|
+
const { command, positional, flags } = parseArgs(process.argv.slice(2));
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
switch (command) {
|
|
59
|
+
case 'generate':
|
|
60
|
+
case 'gen': {
|
|
61
|
+
const name = positional[0];
|
|
62
|
+
if (!name) {
|
|
63
|
+
console.error('Missing module name. Usage: prismakit generate <name>');
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
runGenerate({
|
|
68
|
+
name,
|
|
69
|
+
cache: !!flags.cache,
|
|
70
|
+
full: !!flags.full,
|
|
71
|
+
helpers: !!flags.helpers,
|
|
72
|
+
dto: !!flags.dto,
|
|
73
|
+
route:
|
|
74
|
+
typeof flags.route === 'string' ? flags.route : undefined,
|
|
75
|
+
prismaImport:
|
|
76
|
+
typeof flags['prisma-import'] === 'string'
|
|
77
|
+
? flags['prisma-import']
|
|
78
|
+
: undefined,
|
|
79
|
+
dryRun: !!flags['dry-run'],
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'codegen': {
|
|
84
|
+
runCodegen({
|
|
85
|
+
schemaPath:
|
|
86
|
+
typeof flags.schema === 'string' ? flags.schema : undefined,
|
|
87
|
+
write: !!flags.write,
|
|
88
|
+
outFile: typeof flags.out === 'string' ? flags.out : undefined,
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'validate': {
|
|
93
|
+
runValidate({
|
|
94
|
+
assert: !flags['no-assert'],
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'help':
|
|
99
|
+
case '--help':
|
|
100
|
+
case '-h':
|
|
101
|
+
printHelp();
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
console.error(`Unknown command: ${command}`);
|
|
105
|
+
printHelp();
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error((err as Error).message);
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main();
|