@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/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 { Controller, Get, Param } from '@nestjs/common';
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
- return this.{{camel}}Service.handleGetById(id);
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(_filter: {
345
+ export function where{{pascal}}GetManyPaginate(filter: {
210
346
  q?: string;
211
347
  }): {
212
348
  where: Prisma.{{pascal}}WhereInput;
213
349
  } {
214
- return { where: {} };
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
- return [
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) {
@@ -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
@@ -5,7 +5,7 @@ import {
5
5
  runCodegen,
6
6
  runGenerate,
7
7
  runValidate
8
- } from "./chunk-36ZM4UZV.js";
8
+ } from "./chunk-7E6MWEJP.js";
9
9
  export {
10
10
  assertKebabName,
11
11
  renderModuleFiles,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismakit/cli",
3
- "version": "2.1.0",
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.1.0"
30
+ "@prismakit/core": "2.2.0"
30
31
  },
31
32
  "peerDependencies": {
32
- "@prismakit/core": ">=2.1.0"
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();