@prismakit/cli 3.0.2 → 3.0.3

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 CHANGED
@@ -82,12 +82,10 @@ function renderRepository(names, cacheEnabled, prismaImport, base) {
82
82
  `import { Prisma } from '{{prismaImport}}';
83
83
  import { createInjectableRepository } from '@prismakit/nestjs';
84
84
 
85
- export const {{pascal}}Repository = createInjectableRepository({
85
+ export class {{pascal}}Repository extends createInjectableRepository({
86
86
  model: '{{repoModel}}',
87
87
  scalarFields: Prisma.{{pascal}}ScalarFieldEnum,
88
- {{cacheBlock}}});
89
-
90
- export interface {{pascal}}Repository extends InstanceType<typeof {{pascal}}Repository> {}
88
+ {{cacheBlock}}}) {}
91
89
  `,
92
90
  names,
93
91
  {
package/dist/bin.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/generate.ts","../src/naming.ts","../src/templates.ts","../src/commands/validate.ts","../src/commands/skills.ts","../src/bin.ts"],"sourcesContent":["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 interface {{pascal}}Repository extends 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 {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n schemaPath?: string;\n autoRegisterModels?: 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 const validateOptions = {\n schemaPath: options.schemaPath,\n autoRegisterModels: options.autoRegisterModels,\n };\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd, validateOptions);\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, validateOptions);\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","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SKILL_NAMES = ['prismakit', 'prismakit-nestjs'] as const;\nconst RULE_NAME = 'data-access.mdc';\n\nexport type SkillsCommandOptions = {\n cwd?: string;\n /** Install into ~/.cursor/skills (all projects). Default: <cwd>/.cursor/skills */\n global?: boolean;\n /** Override project root when not using --global. */\n projectRoot?: string;\n /** Subset of skill folder names. Default: both. */\n skill?: string[];\n /** Also copy templates/cursor-rules/data-access.mdc into .cursor/rules. */\n withRules?: boolean;\n dryRun?: boolean;\n list?: boolean;\n /** Test override: directory that contains prismakit/ and prismakit-nestjs/. */\n skillsRoot?: string;\n /** Test override: path to data-access.mdc. */\n rulesPath?: string;\n};\n\nexport type SkillsInstallResult = {\n dest: string;\n installed: string[];\n ruleDest?: string;\n};\n\nfunction isSkillsRoot(dir: string): boolean {\n return fs.existsSync(path.join(dir, 'prismakit', 'SKILL.md'));\n}\n\n/**\n * Walk up from the CLI entrypoint looking for bundled `skills/` (published\n * package) or the monorepo `skills/` directory.\n */\nexport function findSkillsRoot(startDir: string): string | undefined {\n let dir = path.resolve(startDir);\n for (let i = 0; i < 10; i++) {\n for (const candidate of [\n path.join(dir, 'skills'),\n dir,\n path.join(dir, 'templates', 'cursor-skills'),\n ]) {\n if (isSkillsRoot(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\nexport function findRulesPath(skillsRoot: string): string | undefined {\n const candidates = [\n path.join(skillsRoot, '..', 'templates', 'cursor-rules', RULE_NAME),\n path.join(skillsRoot, '..', 'rules', RULE_NAME),\n path.join(path.dirname(skillsRoot), 'rules', RULE_NAME),\n ];\n return candidates.find((p) => fs.existsSync(p));\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n if (entry.name === '.git') continue;\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n copyDir(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n }\n }\n}\n\nfunction resolveHome(): string {\n return process.env.HOME || os.homedir();\n}\n\nfunction assertNotBuiltinSkills(dest: string): void {\n const forbidden = path.join(resolveHome(), '.cursor', 'skills-cursor');\n const resolved = path.resolve(dest);\n if (resolved === forbidden || resolved.startsWith(`${forbidden}${path.sep}`)) {\n throw new Error(\n `Refusing to install into ${resolved} (reserved for Cursor built-ins).`,\n );\n }\n}\n\nfunction cliStartDir(): string {\n try {\n const url = import.meta.url;\n if (typeof url === 'string' && url.length > 0) {\n return path.dirname(fileURLToPath(url));\n }\n } catch {\n // CJS bundle has an empty import.meta\n }\n return process.cwd();\n}\n\nexport function runSkills(options: SkillsCommandOptions = {}): SkillsInstallResult {\n const skillsRoot =\n options.skillsRoot ??\n findSkillsRoot(cliStartDir()) ??\n findSkillsRoot(process.cwd());\n if (!skillsRoot) {\n throw new Error(\n 'Could not find PrismaKit skills. Reinstall @prismakit/cli or clone fikiap23/prismakit.',\n );\n }\n\n const available = SKILL_NAMES.filter((name) =>\n fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')),\n );\n if (available.length === 0) {\n throw new Error(`No skills found in ${skillsRoot}`);\n }\n\n const requested = options.skill?.length\n ? options.skill.map((s) => s.trim()).filter(Boolean)\n : [...available];\n\n for (const name of requested) {\n if (!available.includes(name as (typeof SKILL_NAMES)[number])) {\n throw new Error(\n `Unknown skill \"${name}\". Available: ${available.join(', ')}`,\n );\n }\n }\n\n if (options.list) {\n console.log(`Skills in ${skillsRoot}:`);\n for (const name of available) {\n console.log(` - ${name}`);\n }\n return { dest: skillsRoot, installed: [] };\n }\n\n const cwd = options.cwd ?? process.cwd();\n const dest = options.global\n ? path.join(resolveHome(), '.cursor', 'skills')\n : path.join(\n path.resolve(options.projectRoot ?? cwd),\n '.cursor',\n 'skills',\n );\n\n assertNotBuiltinSkills(dest);\n\n if (!options.dryRun) {\n fs.mkdirSync(dest, { recursive: true });\n }\n\n const installed: string[] = [];\n for (const name of requested) {\n const src = path.join(skillsRoot, name);\n const target = path.join(dest, name);\n if (options.dryRun) {\n console.log(`[dry-run] would install ${name} -> ${target}`);\n } else {\n fs.rmSync(target, { recursive: true, force: true });\n copyDir(src, target);\n console.log(`Installed ${name} -> ${target}`);\n }\n installed.push(name);\n }\n\n let ruleDest: string | undefined;\n if (options.withRules) {\n const rulesSrc = options.rulesPath ?? findRulesPath(skillsRoot);\n if (!rulesSrc) {\n throw new Error(`Could not find ${RULE_NAME} next to skills.`);\n }\n const rulesDir = options.global\n ? path.join(resolveHome(), '.cursor', 'rules')\n : path.join(path.resolve(options.projectRoot ?? cwd), '.cursor', 'rules');\n ruleDest = path.join(rulesDir, RULE_NAME);\n if (options.dryRun) {\n console.log(`[dry-run] would install rule -> ${ruleDest}`);\n } else {\n fs.mkdirSync(rulesDir, { recursive: true });\n fs.copyFileSync(rulesSrc, ruleDest);\n console.log(`Installed rule -> ${ruleDest}`);\n }\n }\n\n const scope = options.global\n ? 'global (~/.cursor/skills)'\n : 'project (.cursor/skills)';\n if (options.dryRun) {\n console.log(`Dry-run complete (${scope}).`);\n } else if (options.global) {\n console.log(`Done. Skills are available in all Cursor projects on this machine (${scope}).`);\n } else {\n console.log(\n `Done. Commit .cursor/skills so the team shares the same agent contract (${scope}).`,\n );\n }\n\n return { dest, installed, ruleDest };\n}\n","import { runGenerate } from './commands/generate';\nimport { runValidate } from './commands/validate';\nimport { runSkills } from './commands/skills';\n\nfunction printHelp(): void {\n console.log(`prismakit — PrismaKit CLI\n\nUsage:\n prismakit generate <name> [--cache] [--full] [--helpers] [--dto] [--route <path>] [--prisma-import <path>] [--dry-run]\n prismakit validate [--schema <path>] [--auto-register] [--no-assert]\n prismakit skills [--global] [--with-rules] [--skill <name>] [--project <path>] [--list] [--dry-run]\n prismakit help\n\nBy default, generate writes only the repository file.\nPass --full for a Nest module (controller, service, types).\nPass --helpers / --dto with --full for helpers and Swagger DTOs.\n\nExamples:\n prismakit generate product --cache\n prismakit generate product --cache --full --helpers --dto --route products\n prismakit validate --auto-register\n prismakit skills\n prismakit skills --global\n prismakit skills --with-rules\n`);\n}\n\nfunction parseArgs(argv: string[]): {\n command: string;\n positional: string[];\n flags: Record<string, string | boolean>;\n} {\n const [command = 'help', ...rest] = argv;\n const positional: string[] = [];\n const flags: Record<string, string | boolean> = {};\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === '-g') {\n flags.global = true;\n } else if (arg.startsWith('--')) {\n const key = arg.slice(2);\n const next = rest[i + 1];\n if (next && !next.startsWith('-')) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n } else {\n positional.push(arg);\n }\n }\n\n return { command, positional, flags };\n}\n\nfunction main(): void {\n const { command, positional, flags } = parseArgs(process.argv.slice(2));\n\n try {\n switch (command) {\n case 'generate':\n case 'gen': {\n const name = positional[0];\n if (!name) {\n console.error('Missing module name. Usage: prismakit generate <name>');\n process.exitCode = 1;\n return;\n }\n runGenerate({\n name,\n cache: !!flags.cache,\n full: !!flags.full,\n helpers: !!flags.helpers,\n dto: !!flags.dto,\n route:\n typeof flags.route === 'string' ? flags.route : undefined,\n prismaImport:\n typeof flags['prisma-import'] === 'string'\n ? flags['prisma-import']\n : undefined,\n dryRun: !!flags['dry-run'],\n });\n break;\n }\n case 'validate': {\n runValidate({\n assert: !flags['no-assert'],\n schemaPath:\n typeof flags.schema === 'string' ? flags.schema : undefined,\n autoRegisterModels: !!flags['auto-register'],\n });\n break;\n }\n case 'skills':\n case 'skill': {\n const skillFlag = flags.skill;\n runSkills({\n global: !!flags.global,\n withRules: !!flags['with-rules'],\n dryRun: !!flags['dry-run'],\n list: !!flags.list,\n projectRoot:\n typeof flags.project === 'string' ? flags.project : undefined,\n skill:\n typeof skillFlag === 'string'\n ? skillFlag.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined,\n });\n break;\n }\n case 'help':\n case '--help':\n case '-h':\n printHelp();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exitCode = 1;\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n}\n\nmain();\n"],"mappings":"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","names":["fs","path"]}
1
+ {"version":3,"sources":["../src/commands/generate.ts","../src/naming.ts","../src/templates.ts","../src/commands/validate.ts","../src/commands/skills.ts","../src/bin.ts"],"sourcesContent":["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 class {{pascal}}Repository extends createInjectableRepository({\n model: '{{repoModel}}',\n scalarFields: Prisma.{{pascal}}ScalarFieldEnum,\n{{cacheBlock}}}) {}\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 {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n schemaPath?: string;\n autoRegisterModels?: 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 const validateOptions = {\n schemaPath: options.schemaPath,\n autoRegisterModels: options.autoRegisterModels,\n };\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd, validateOptions);\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, validateOptions);\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","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SKILL_NAMES = ['prismakit', 'prismakit-nestjs'] as const;\nconst RULE_NAME = 'data-access.mdc';\n\nexport type SkillsCommandOptions = {\n cwd?: string;\n /** Install into ~/.cursor/skills (all projects). Default: <cwd>/.cursor/skills */\n global?: boolean;\n /** Override project root when not using --global. */\n projectRoot?: string;\n /** Subset of skill folder names. Default: both. */\n skill?: string[];\n /** Also copy templates/cursor-rules/data-access.mdc into .cursor/rules. */\n withRules?: boolean;\n dryRun?: boolean;\n list?: boolean;\n /** Test override: directory that contains prismakit/ and prismakit-nestjs/. */\n skillsRoot?: string;\n /** Test override: path to data-access.mdc. */\n rulesPath?: string;\n};\n\nexport type SkillsInstallResult = {\n dest: string;\n installed: string[];\n ruleDest?: string;\n};\n\nfunction isSkillsRoot(dir: string): boolean {\n return fs.existsSync(path.join(dir, 'prismakit', 'SKILL.md'));\n}\n\n/**\n * Walk up from the CLI entrypoint looking for bundled `skills/` (published\n * package) or the monorepo `skills/` directory.\n */\nexport function findSkillsRoot(startDir: string): string | undefined {\n let dir = path.resolve(startDir);\n for (let i = 0; i < 10; i++) {\n for (const candidate of [\n path.join(dir, 'skills'),\n dir,\n path.join(dir, 'templates', 'cursor-skills'),\n ]) {\n if (isSkillsRoot(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\nexport function findRulesPath(skillsRoot: string): string | undefined {\n const candidates = [\n path.join(skillsRoot, '..', 'templates', 'cursor-rules', RULE_NAME),\n path.join(skillsRoot, '..', 'rules', RULE_NAME),\n path.join(path.dirname(skillsRoot), 'rules', RULE_NAME),\n ];\n return candidates.find((p) => fs.existsSync(p));\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n if (entry.name === '.git') continue;\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n copyDir(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n }\n }\n}\n\nfunction resolveHome(): string {\n return process.env.HOME || os.homedir();\n}\n\nfunction assertNotBuiltinSkills(dest: string): void {\n const forbidden = path.join(resolveHome(), '.cursor', 'skills-cursor');\n const resolved = path.resolve(dest);\n if (resolved === forbidden || resolved.startsWith(`${forbidden}${path.sep}`)) {\n throw new Error(\n `Refusing to install into ${resolved} (reserved for Cursor built-ins).`,\n );\n }\n}\n\nfunction cliStartDir(): string {\n try {\n const url = import.meta.url;\n if (typeof url === 'string' && url.length > 0) {\n return path.dirname(fileURLToPath(url));\n }\n } catch {\n // CJS bundle has an empty import.meta\n }\n return process.cwd();\n}\n\nexport function runSkills(options: SkillsCommandOptions = {}): SkillsInstallResult {\n const skillsRoot =\n options.skillsRoot ??\n findSkillsRoot(cliStartDir()) ??\n findSkillsRoot(process.cwd());\n if (!skillsRoot) {\n throw new Error(\n 'Could not find PrismaKit skills. Reinstall @prismakit/cli or clone fikiap23/prismakit.',\n );\n }\n\n const available = SKILL_NAMES.filter((name) =>\n fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')),\n );\n if (available.length === 0) {\n throw new Error(`No skills found in ${skillsRoot}`);\n }\n\n const requested = options.skill?.length\n ? options.skill.map((s) => s.trim()).filter(Boolean)\n : [...available];\n\n for (const name of requested) {\n if (!available.includes(name as (typeof SKILL_NAMES)[number])) {\n throw new Error(\n `Unknown skill \"${name}\". Available: ${available.join(', ')}`,\n );\n }\n }\n\n if (options.list) {\n console.log(`Skills in ${skillsRoot}:`);\n for (const name of available) {\n console.log(` - ${name}`);\n }\n return { dest: skillsRoot, installed: [] };\n }\n\n const cwd = options.cwd ?? process.cwd();\n const dest = options.global\n ? path.join(resolveHome(), '.cursor', 'skills')\n : path.join(\n path.resolve(options.projectRoot ?? cwd),\n '.cursor',\n 'skills',\n );\n\n assertNotBuiltinSkills(dest);\n\n if (!options.dryRun) {\n fs.mkdirSync(dest, { recursive: true });\n }\n\n const installed: string[] = [];\n for (const name of requested) {\n const src = path.join(skillsRoot, name);\n const target = path.join(dest, name);\n if (options.dryRun) {\n console.log(`[dry-run] would install ${name} -> ${target}`);\n } else {\n fs.rmSync(target, { recursive: true, force: true });\n copyDir(src, target);\n console.log(`Installed ${name} -> ${target}`);\n }\n installed.push(name);\n }\n\n let ruleDest: string | undefined;\n if (options.withRules) {\n const rulesSrc = options.rulesPath ?? findRulesPath(skillsRoot);\n if (!rulesSrc) {\n throw new Error(`Could not find ${RULE_NAME} next to skills.`);\n }\n const rulesDir = options.global\n ? path.join(resolveHome(), '.cursor', 'rules')\n : path.join(path.resolve(options.projectRoot ?? cwd), '.cursor', 'rules');\n ruleDest = path.join(rulesDir, RULE_NAME);\n if (options.dryRun) {\n console.log(`[dry-run] would install rule -> ${ruleDest}`);\n } else {\n fs.mkdirSync(rulesDir, { recursive: true });\n fs.copyFileSync(rulesSrc, ruleDest);\n console.log(`Installed rule -> ${ruleDest}`);\n }\n }\n\n const scope = options.global\n ? 'global (~/.cursor/skills)'\n : 'project (.cursor/skills)';\n if (options.dryRun) {\n console.log(`Dry-run complete (${scope}).`);\n } else if (options.global) {\n console.log(`Done. Skills are available in all Cursor projects on this machine (${scope}).`);\n } else {\n console.log(\n `Done. Commit .cursor/skills so the team shares the same agent contract (${scope}).`,\n );\n }\n\n return { dest, installed, ruleDest };\n}\n","import { runGenerate } from './commands/generate';\nimport { runValidate } from './commands/validate';\nimport { runSkills } from './commands/skills';\n\nfunction printHelp(): void {\n console.log(`prismakit — PrismaKit CLI\n\nUsage:\n prismakit generate <name> [--cache] [--full] [--helpers] [--dto] [--route <path>] [--prisma-import <path>] [--dry-run]\n prismakit validate [--schema <path>] [--auto-register] [--no-assert]\n prismakit skills [--global] [--with-rules] [--skill <name>] [--project <path>] [--list] [--dry-run]\n prismakit help\n\nBy default, generate writes only the repository file.\nPass --full for a Nest module (controller, service, types).\nPass --helpers / --dto with --full for helpers and Swagger DTOs.\n\nExamples:\n prismakit generate product --cache\n prismakit generate product --cache --full --helpers --dto --route products\n prismakit validate --auto-register\n prismakit skills\n prismakit skills --global\n prismakit skills --with-rules\n`);\n}\n\nfunction parseArgs(argv: string[]): {\n command: string;\n positional: string[];\n flags: Record<string, string | boolean>;\n} {\n const [command = 'help', ...rest] = argv;\n const positional: string[] = [];\n const flags: Record<string, string | boolean> = {};\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === '-g') {\n flags.global = true;\n } else if (arg.startsWith('--')) {\n const key = arg.slice(2);\n const next = rest[i + 1];\n if (next && !next.startsWith('-')) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n } else {\n positional.push(arg);\n }\n }\n\n return { command, positional, flags };\n}\n\nfunction main(): void {\n const { command, positional, flags } = parseArgs(process.argv.slice(2));\n\n try {\n switch (command) {\n case 'generate':\n case 'gen': {\n const name = positional[0];\n if (!name) {\n console.error('Missing module name. Usage: prismakit generate <name>');\n process.exitCode = 1;\n return;\n }\n runGenerate({\n name,\n cache: !!flags.cache,\n full: !!flags.full,\n helpers: !!flags.helpers,\n dto: !!flags.dto,\n route:\n typeof flags.route === 'string' ? flags.route : undefined,\n prismaImport:\n typeof flags['prisma-import'] === 'string'\n ? flags['prisma-import']\n : undefined,\n dryRun: !!flags['dry-run'],\n });\n break;\n }\n case 'validate': {\n runValidate({\n assert: !flags['no-assert'],\n schemaPath:\n typeof flags.schema === 'string' ? flags.schema : undefined,\n autoRegisterModels: !!flags['auto-register'],\n });\n break;\n }\n case 'skills':\n case 'skill': {\n const skillFlag = flags.skill;\n runSkills({\n global: !!flags.global,\n withRules: !!flags['with-rules'],\n dryRun: !!flags['dry-run'],\n list: !!flags.list,\n projectRoot:\n typeof flags.project === 'string' ? flags.project : undefined,\n skill:\n typeof skillFlag === 'string'\n ? skillFlag.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined,\n });\n break;\n }\n case 'help':\n case '--help':\n case '-h':\n printHelp();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exitCode = 1;\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exitCode = 1;\n }\n}\n\nmain();\n"],"mappings":"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","names":["fs","path"]}
package/dist/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  runGenerate,
4
4
  runSkills,
5
5
  runValidate
6
- } from "./chunk-E2CVH3MX.js";
6
+ } from "./chunk-7D4JHNVX.js";
7
7
 
8
8
  // src/bin.ts
9
9
  function printHelp() {
@@ -54,12 +54,10 @@ function renderRepository(names, cacheEnabled, prismaImport, base) {
54
54
  `import { Prisma } from '{{prismaImport}}';
55
55
  import { createInjectableRepository } from '@prismakit/nestjs';
56
56
 
57
- export const {{pascal}}Repository = createInjectableRepository({
57
+ export class {{pascal}}Repository extends createInjectableRepository({
58
58
  model: '{{repoModel}}',
59
59
  scalarFields: Prisma.{{pascal}}ScalarFieldEnum,
60
- {{cacheBlock}}});
61
-
62
- export interface {{pascal}}Repository extends InstanceType<typeof {{pascal}}Repository> {}
60
+ {{cacheBlock}}}) {}
63
61
  `,
64
62
  names,
65
63
  {
@@ -653,4 +651,4 @@ export {
653
651
  findSkillsRoot,
654
652
  runSkills
655
653
  };
656
- //# sourceMappingURL=chunk-E2CVH3MX.js.map
654
+ //# sourceMappingURL=chunk-7D4JHNVX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/naming.ts","../src/templates.ts","../src/commands/generate.ts","../src/commands/validate.ts","../src/commands/skills.ts"],"sourcesContent":["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 class {{pascal}}Repository extends createInjectableRepository({\n model: '{{repoModel}}',\n scalarFields: Prisma.{{pascal}}ScalarFieldEnum,\n{{cacheBlock}}}) {}\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 { 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","import {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n schemaPath?: string;\n autoRegisterModels?: 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 const validateOptions = {\n schemaPath: options.schemaPath,\n autoRegisterModels: options.autoRegisterModels,\n };\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd, validateOptions);\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, validateOptions);\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","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SKILL_NAMES = ['prismakit', 'prismakit-nestjs'] as const;\nconst RULE_NAME = 'data-access.mdc';\n\nexport type SkillsCommandOptions = {\n cwd?: string;\n /** Install into ~/.cursor/skills (all projects). Default: <cwd>/.cursor/skills */\n global?: boolean;\n /** Override project root when not using --global. */\n projectRoot?: string;\n /** Subset of skill folder names. Default: both. */\n skill?: string[];\n /** Also copy templates/cursor-rules/data-access.mdc into .cursor/rules. */\n withRules?: boolean;\n dryRun?: boolean;\n list?: boolean;\n /** Test override: directory that contains prismakit/ and prismakit-nestjs/. */\n skillsRoot?: string;\n /** Test override: path to data-access.mdc. */\n rulesPath?: string;\n};\n\nexport type SkillsInstallResult = {\n dest: string;\n installed: string[];\n ruleDest?: string;\n};\n\nfunction isSkillsRoot(dir: string): boolean {\n return fs.existsSync(path.join(dir, 'prismakit', 'SKILL.md'));\n}\n\n/**\n * Walk up from the CLI entrypoint looking for bundled `skills/` (published\n * package) or the monorepo `skills/` directory.\n */\nexport function findSkillsRoot(startDir: string): string | undefined {\n let dir = path.resolve(startDir);\n for (let i = 0; i < 10; i++) {\n for (const candidate of [\n path.join(dir, 'skills'),\n dir,\n path.join(dir, 'templates', 'cursor-skills'),\n ]) {\n if (isSkillsRoot(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\nexport function findRulesPath(skillsRoot: string): string | undefined {\n const candidates = [\n path.join(skillsRoot, '..', 'templates', 'cursor-rules', RULE_NAME),\n path.join(skillsRoot, '..', 'rules', RULE_NAME),\n path.join(path.dirname(skillsRoot), 'rules', RULE_NAME),\n ];\n return candidates.find((p) => fs.existsSync(p));\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n if (entry.name === '.git') continue;\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n copyDir(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n }\n }\n}\n\nfunction resolveHome(): string {\n return process.env.HOME || os.homedir();\n}\n\nfunction assertNotBuiltinSkills(dest: string): void {\n const forbidden = path.join(resolveHome(), '.cursor', 'skills-cursor');\n const resolved = path.resolve(dest);\n if (resolved === forbidden || resolved.startsWith(`${forbidden}${path.sep}`)) {\n throw new Error(\n `Refusing to install into ${resolved} (reserved for Cursor built-ins).`,\n );\n }\n}\n\nfunction cliStartDir(): string {\n try {\n const url = import.meta.url;\n if (typeof url === 'string' && url.length > 0) {\n return path.dirname(fileURLToPath(url));\n }\n } catch {\n // CJS bundle has an empty import.meta\n }\n return process.cwd();\n}\n\nexport function runSkills(options: SkillsCommandOptions = {}): SkillsInstallResult {\n const skillsRoot =\n options.skillsRoot ??\n findSkillsRoot(cliStartDir()) ??\n findSkillsRoot(process.cwd());\n if (!skillsRoot) {\n throw new Error(\n 'Could not find PrismaKit skills. Reinstall @prismakit/cli or clone fikiap23/prismakit.',\n );\n }\n\n const available = SKILL_NAMES.filter((name) =>\n fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')),\n );\n if (available.length === 0) {\n throw new Error(`No skills found in ${skillsRoot}`);\n }\n\n const requested = options.skill?.length\n ? options.skill.map((s) => s.trim()).filter(Boolean)\n : [...available];\n\n for (const name of requested) {\n if (!available.includes(name as (typeof SKILL_NAMES)[number])) {\n throw new Error(\n `Unknown skill \"${name}\". Available: ${available.join(', ')}`,\n );\n }\n }\n\n if (options.list) {\n console.log(`Skills in ${skillsRoot}:`);\n for (const name of available) {\n console.log(` - ${name}`);\n }\n return { dest: skillsRoot, installed: [] };\n }\n\n const cwd = options.cwd ?? process.cwd();\n const dest = options.global\n ? path.join(resolveHome(), '.cursor', 'skills')\n : path.join(\n path.resolve(options.projectRoot ?? cwd),\n '.cursor',\n 'skills',\n );\n\n assertNotBuiltinSkills(dest);\n\n if (!options.dryRun) {\n fs.mkdirSync(dest, { recursive: true });\n }\n\n const installed: string[] = [];\n for (const name of requested) {\n const src = path.join(skillsRoot, name);\n const target = path.join(dest, name);\n if (options.dryRun) {\n console.log(`[dry-run] would install ${name} -> ${target}`);\n } else {\n fs.rmSync(target, { recursive: true, force: true });\n copyDir(src, target);\n console.log(`Installed ${name} -> ${target}`);\n }\n installed.push(name);\n }\n\n let ruleDest: string | undefined;\n if (options.withRules) {\n const rulesSrc = options.rulesPath ?? findRulesPath(skillsRoot);\n if (!rulesSrc) {\n throw new Error(`Could not find ${RULE_NAME} next to skills.`);\n }\n const rulesDir = options.global\n ? path.join(resolveHome(), '.cursor', 'rules')\n : path.join(path.resolve(options.projectRoot ?? cwd), '.cursor', 'rules');\n ruleDest = path.join(rulesDir, RULE_NAME);\n if (options.dryRun) {\n console.log(`[dry-run] would install rule -> ${ruleDest}`);\n } else {\n fs.mkdirSync(rulesDir, { recursive: true });\n fs.copyFileSync(rulesSrc, ruleDest);\n console.log(`Installed rule -> ${ruleDest}`);\n }\n }\n\n const scope = options.global\n ? 'global (~/.cursor/skills)'\n : 'project (.cursor/skills)';\n if (options.dryRun) {\n console.log(`Dry-run complete (${scope}).`);\n } else if (options.global) {\n console.log(`Done. Skills are available in all Cursor projects on this machine (${scope}).`);\n } else {\n console.log(\n `Done. Commit .cursor/skills so the team shares the same agent contract (${scope}).`,\n );\n }\n\n return { dest, installed, ruleDest };\n}\n"],"mappings":"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","names":["fs","path"]}
package/dist/index.cjs CHANGED
@@ -100,12 +100,10 @@ function renderRepository(names, cacheEnabled, prismaImport, base) {
100
100
  `import { Prisma } from '{{prismaImport}}';
101
101
  import { createInjectableRepository } from '@prismakit/nestjs';
102
102
 
103
- export const {{pascal}}Repository = createInjectableRepository({
103
+ export class {{pascal}}Repository extends createInjectableRepository({
104
104
  model: '{{repoModel}}',
105
105
  scalarFields: Prisma.{{pascal}}ScalarFieldEnum,
106
- {{cacheBlock}}});
107
-
108
- export interface {{pascal}}Repository extends InstanceType<typeof {{pascal}}Repository> {}
106
+ {{cacheBlock}}}) {}
109
107
  `,
110
108
  names,
111
109
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/naming.ts","../src/templates.ts","../src/commands/validate.ts","../src/commands/skills.ts"],"sourcesContent":["export { runGenerate, type GenerateCommandOptions } from './commands/generate';\nexport { runValidate, type ValidateCommandOptions } from './commands/validate';\nexport {\n runSkills,\n findSkillsRoot,\n type SkillsCommandOptions,\n type SkillsInstallResult,\n} from './commands/skills';\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 interface {{pascal}}Repository extends 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 {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n schemaPath?: string;\n autoRegisterModels?: 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 const validateOptions = {\n schemaPath: options.schemaPath,\n autoRegisterModels: options.autoRegisterModels,\n };\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd, validateOptions);\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, validateOptions);\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","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SKILL_NAMES = ['prismakit', 'prismakit-nestjs'] as const;\nconst RULE_NAME = 'data-access.mdc';\n\nexport type SkillsCommandOptions = {\n cwd?: string;\n /** Install into ~/.cursor/skills (all projects). Default: <cwd>/.cursor/skills */\n global?: boolean;\n /** Override project root when not using --global. */\n projectRoot?: string;\n /** Subset of skill folder names. Default: both. */\n skill?: string[];\n /** Also copy templates/cursor-rules/data-access.mdc into .cursor/rules. */\n withRules?: boolean;\n dryRun?: boolean;\n list?: boolean;\n /** Test override: directory that contains prismakit/ and prismakit-nestjs/. */\n skillsRoot?: string;\n /** Test override: path to data-access.mdc. */\n rulesPath?: string;\n};\n\nexport type SkillsInstallResult = {\n dest: string;\n installed: string[];\n ruleDest?: string;\n};\n\nfunction isSkillsRoot(dir: string): boolean {\n return fs.existsSync(path.join(dir, 'prismakit', 'SKILL.md'));\n}\n\n/**\n * Walk up from the CLI entrypoint looking for bundled `skills/` (published\n * package) or the monorepo `skills/` directory.\n */\nexport function findSkillsRoot(startDir: string): string | undefined {\n let dir = path.resolve(startDir);\n for (let i = 0; i < 10; i++) {\n for (const candidate of [\n path.join(dir, 'skills'),\n dir,\n path.join(dir, 'templates', 'cursor-skills'),\n ]) {\n if (isSkillsRoot(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\nexport function findRulesPath(skillsRoot: string): string | undefined {\n const candidates = [\n path.join(skillsRoot, '..', 'templates', 'cursor-rules', RULE_NAME),\n path.join(skillsRoot, '..', 'rules', RULE_NAME),\n path.join(path.dirname(skillsRoot), 'rules', RULE_NAME),\n ];\n return candidates.find((p) => fs.existsSync(p));\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n if (entry.name === '.git') continue;\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n copyDir(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n }\n }\n}\n\nfunction resolveHome(): string {\n return process.env.HOME || os.homedir();\n}\n\nfunction assertNotBuiltinSkills(dest: string): void {\n const forbidden = path.join(resolveHome(), '.cursor', 'skills-cursor');\n const resolved = path.resolve(dest);\n if (resolved === forbidden || resolved.startsWith(`${forbidden}${path.sep}`)) {\n throw new Error(\n `Refusing to install into ${resolved} (reserved for Cursor built-ins).`,\n );\n }\n}\n\nfunction cliStartDir(): string {\n try {\n const url = import.meta.url;\n if (typeof url === 'string' && url.length > 0) {\n return path.dirname(fileURLToPath(url));\n }\n } catch {\n // CJS bundle has an empty import.meta\n }\n return process.cwd();\n}\n\nexport function runSkills(options: SkillsCommandOptions = {}): SkillsInstallResult {\n const skillsRoot =\n options.skillsRoot ??\n findSkillsRoot(cliStartDir()) ??\n findSkillsRoot(process.cwd());\n if (!skillsRoot) {\n throw new Error(\n 'Could not find PrismaKit skills. Reinstall @prismakit/cli or clone fikiap23/prismakit.',\n );\n }\n\n const available = SKILL_NAMES.filter((name) =>\n fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')),\n );\n if (available.length === 0) {\n throw new Error(`No skills found in ${skillsRoot}`);\n }\n\n const requested = options.skill?.length\n ? options.skill.map((s) => s.trim()).filter(Boolean)\n : [...available];\n\n for (const name of requested) {\n if (!available.includes(name as (typeof SKILL_NAMES)[number])) {\n throw new Error(\n `Unknown skill \"${name}\". Available: ${available.join(', ')}`,\n );\n }\n }\n\n if (options.list) {\n console.log(`Skills in ${skillsRoot}:`);\n for (const name of available) {\n console.log(` - ${name}`);\n }\n return { dest: skillsRoot, installed: [] };\n }\n\n const cwd = options.cwd ?? process.cwd();\n const dest = options.global\n ? path.join(resolveHome(), '.cursor', 'skills')\n : path.join(\n path.resolve(options.projectRoot ?? cwd),\n '.cursor',\n 'skills',\n );\n\n assertNotBuiltinSkills(dest);\n\n if (!options.dryRun) {\n fs.mkdirSync(dest, { recursive: true });\n }\n\n const installed: string[] = [];\n for (const name of requested) {\n const src = path.join(skillsRoot, name);\n const target = path.join(dest, name);\n if (options.dryRun) {\n console.log(`[dry-run] would install ${name} -> ${target}`);\n } else {\n fs.rmSync(target, { recursive: true, force: true });\n copyDir(src, target);\n console.log(`Installed ${name} -> ${target}`);\n }\n installed.push(name);\n }\n\n let ruleDest: string | undefined;\n if (options.withRules) {\n const rulesSrc = options.rulesPath ?? findRulesPath(skillsRoot);\n if (!rulesSrc) {\n throw new Error(`Could not find ${RULE_NAME} next to skills.`);\n }\n const rulesDir = options.global\n ? path.join(resolveHome(), '.cursor', 'rules')\n : path.join(path.resolve(options.projectRoot ?? cwd), '.cursor', 'rules');\n ruleDest = path.join(rulesDir, RULE_NAME);\n if (options.dryRun) {\n console.log(`[dry-run] would install rule -> ${ruleDest}`);\n } else {\n fs.mkdirSync(rulesDir, { recursive: true });\n fs.copyFileSync(rulesSrc, ruleDest);\n console.log(`Installed rule -> ${ruleDest}`);\n }\n }\n\n const scope = options.global\n ? 'global (~/.cursor/skills)'\n : 'project (.cursor/skills)';\n if (options.dryRun) {\n console.log(`Dry-run complete (${scope}).`);\n } else if (options.global) {\n console.log(`Done. Skills are available in all Cursor projects on this machine (${scope}).`);\n } else {\n console.log(\n `Done. Commit .cursor/skills so the team shares the same agent contract (${scope}).`,\n );\n }\n\n return { dest, installed, ruleDest };\n}\n"],"mappings":"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","names":["fs","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/naming.ts","../src/templates.ts","../src/commands/validate.ts","../src/commands/skills.ts"],"sourcesContent":["export { runGenerate, type GenerateCommandOptions } from './commands/generate';\nexport { runValidate, type ValidateCommandOptions } from './commands/validate';\nexport {\n runSkills,\n findSkillsRoot,\n type SkillsCommandOptions,\n type SkillsInstallResult,\n} from './commands/skills';\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 class {{pascal}}Repository extends createInjectableRepository({\n model: '{{repoModel}}',\n scalarFields: Prisma.{{pascal}}ScalarFieldEnum,\n{{cacheBlock}}}) {}\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 {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n schemaPath?: string;\n autoRegisterModels?: 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 const validateOptions = {\n schemaPath: options.schemaPath,\n autoRegisterModels: options.autoRegisterModels,\n };\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd, validateOptions);\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, validateOptions);\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","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SKILL_NAMES = ['prismakit', 'prismakit-nestjs'] as const;\nconst RULE_NAME = 'data-access.mdc';\n\nexport type SkillsCommandOptions = {\n cwd?: string;\n /** Install into ~/.cursor/skills (all projects). Default: <cwd>/.cursor/skills */\n global?: boolean;\n /** Override project root when not using --global. */\n projectRoot?: string;\n /** Subset of skill folder names. Default: both. */\n skill?: string[];\n /** Also copy templates/cursor-rules/data-access.mdc into .cursor/rules. */\n withRules?: boolean;\n dryRun?: boolean;\n list?: boolean;\n /** Test override: directory that contains prismakit/ and prismakit-nestjs/. */\n skillsRoot?: string;\n /** Test override: path to data-access.mdc. */\n rulesPath?: string;\n};\n\nexport type SkillsInstallResult = {\n dest: string;\n installed: string[];\n ruleDest?: string;\n};\n\nfunction isSkillsRoot(dir: string): boolean {\n return fs.existsSync(path.join(dir, 'prismakit', 'SKILL.md'));\n}\n\n/**\n * Walk up from the CLI entrypoint looking for bundled `skills/` (published\n * package) or the monorepo `skills/` directory.\n */\nexport function findSkillsRoot(startDir: string): string | undefined {\n let dir = path.resolve(startDir);\n for (let i = 0; i < 10; i++) {\n for (const candidate of [\n path.join(dir, 'skills'),\n dir,\n path.join(dir, 'templates', 'cursor-skills'),\n ]) {\n if (isSkillsRoot(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\nexport function findRulesPath(skillsRoot: string): string | undefined {\n const candidates = [\n path.join(skillsRoot, '..', 'templates', 'cursor-rules', RULE_NAME),\n path.join(skillsRoot, '..', 'rules', RULE_NAME),\n path.join(path.dirname(skillsRoot), 'rules', RULE_NAME),\n ];\n return candidates.find((p) => fs.existsSync(p));\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n if (entry.name === '.git') continue;\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n copyDir(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n }\n }\n}\n\nfunction resolveHome(): string {\n return process.env.HOME || os.homedir();\n}\n\nfunction assertNotBuiltinSkills(dest: string): void {\n const forbidden = path.join(resolveHome(), '.cursor', 'skills-cursor');\n const resolved = path.resolve(dest);\n if (resolved === forbidden || resolved.startsWith(`${forbidden}${path.sep}`)) {\n throw new Error(\n `Refusing to install into ${resolved} (reserved for Cursor built-ins).`,\n );\n }\n}\n\nfunction cliStartDir(): string {\n try {\n const url = import.meta.url;\n if (typeof url === 'string' && url.length > 0) {\n return path.dirname(fileURLToPath(url));\n }\n } catch {\n // CJS bundle has an empty import.meta\n }\n return process.cwd();\n}\n\nexport function runSkills(options: SkillsCommandOptions = {}): SkillsInstallResult {\n const skillsRoot =\n options.skillsRoot ??\n findSkillsRoot(cliStartDir()) ??\n findSkillsRoot(process.cwd());\n if (!skillsRoot) {\n throw new Error(\n 'Could not find PrismaKit skills. Reinstall @prismakit/cli or clone fikiap23/prismakit.',\n );\n }\n\n const available = SKILL_NAMES.filter((name) =>\n fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')),\n );\n if (available.length === 0) {\n throw new Error(`No skills found in ${skillsRoot}`);\n }\n\n const requested = options.skill?.length\n ? options.skill.map((s) => s.trim()).filter(Boolean)\n : [...available];\n\n for (const name of requested) {\n if (!available.includes(name as (typeof SKILL_NAMES)[number])) {\n throw new Error(\n `Unknown skill \"${name}\". Available: ${available.join(', ')}`,\n );\n }\n }\n\n if (options.list) {\n console.log(`Skills in ${skillsRoot}:`);\n for (const name of available) {\n console.log(` - ${name}`);\n }\n return { dest: skillsRoot, installed: [] };\n }\n\n const cwd = options.cwd ?? process.cwd();\n const dest = options.global\n ? path.join(resolveHome(), '.cursor', 'skills')\n : path.join(\n path.resolve(options.projectRoot ?? cwd),\n '.cursor',\n 'skills',\n );\n\n assertNotBuiltinSkills(dest);\n\n if (!options.dryRun) {\n fs.mkdirSync(dest, { recursive: true });\n }\n\n const installed: string[] = [];\n for (const name of requested) {\n const src = path.join(skillsRoot, name);\n const target = path.join(dest, name);\n if (options.dryRun) {\n console.log(`[dry-run] would install ${name} -> ${target}`);\n } else {\n fs.rmSync(target, { recursive: true, force: true });\n copyDir(src, target);\n console.log(`Installed ${name} -> ${target}`);\n }\n installed.push(name);\n }\n\n let ruleDest: string | undefined;\n if (options.withRules) {\n const rulesSrc = options.rulesPath ?? findRulesPath(skillsRoot);\n if (!rulesSrc) {\n throw new Error(`Could not find ${RULE_NAME} next to skills.`);\n }\n const rulesDir = options.global\n ? path.join(resolveHome(), '.cursor', 'rules')\n : path.join(path.resolve(options.projectRoot ?? cwd), '.cursor', 'rules');\n ruleDest = path.join(rulesDir, RULE_NAME);\n if (options.dryRun) {\n console.log(`[dry-run] would install rule -> ${ruleDest}`);\n } else {\n fs.mkdirSync(rulesDir, { recursive: true });\n fs.copyFileSync(rulesSrc, ruleDest);\n console.log(`Installed rule -> ${ruleDest}`);\n }\n }\n\n const scope = options.global\n ? 'global (~/.cursor/skills)'\n : 'project (.cursor/skills)';\n if (options.dryRun) {\n console.log(`Dry-run complete (${scope}).`);\n } else if (options.global) {\n console.log(`Done. Skills are available in all Cursor projects on this machine (${scope}).`);\n } else {\n console.log(\n `Done. Commit .cursor/skills so the team shares the same agent contract (${scope}).`,\n );\n }\n\n return { dest, installed, ruleDest };\n}\n"],"mappings":"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","names":["fs","path"]}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  runGenerate,
7
7
  runSkills,
8
8
  runValidate
9
- } from "./chunk-E2CVH3MX.js";
9
+ } from "./chunk-7D4JHNVX.js";
10
10
  export {
11
11
  assertKebabName,
12
12
  findSkillsRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismakit/cli",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "CLI for PrismaKit — generate modules, validate compose, install agent skills",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -62,13 +62,12 @@ import { defineRepo } from '../../../infrastructure/prisma/define-repo';
62
62
 
63
63
  const DAY = 86_400;
64
64
 
65
- export const UserRepository = defineRepo({
65
+ export class UserRepository extends defineRepo({
66
66
  model: 'user',
67
67
  scalarFields: Prisma.UserScalarFieldEnum,
68
68
  cache: { ttl: DAY, nullTtl: 60, sensitiveFields: ['password'] },
69
69
  lock: true,
70
- });
71
- export interface UserRepository extends InstanceType<typeof UserRepository> {}
70
+ }) {}
72
71
  ```
73
72
 
74
73
  Escape hatches (do not use as the app default):
package/src/templates.ts CHANGED
@@ -53,12 +53,10 @@ function renderRepository(
53
53
  `import { Prisma } from '{{prismaImport}}';
54
54
  import { createInjectableRepository } from '@prismakit/nestjs';
55
55
 
56
- export const {{pascal}}Repository = createInjectableRepository({
56
+ export class {{pascal}}Repository extends createInjectableRepository({
57
57
  model: '{{repoModel}}',
58
58
  scalarFields: Prisma.{{pascal}}ScalarFieldEnum,
59
- {{cacheBlock}}});
60
-
61
- export interface {{pascal}}Repository extends InstanceType<typeof {{pascal}}Repository> {}
59
+ {{cacheBlock}}}) {}
62
60
  `,
63
61
  names,
64
62
  {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/naming.ts","../src/templates.ts","../src/commands/generate.ts","../src/commands/validate.ts","../src/commands/skills.ts"],"sourcesContent":["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 interface {{pascal}}Repository extends 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 { 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","import {\n assertSelectComposeValid,\n validateSelectCompose,\n} from '@prismakit/core';\n\nexport type ValidateCommandOptions = {\n cwd?: string;\n assert?: boolean;\n schemaPath?: string;\n autoRegisterModels?: 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 const validateOptions = {\n schemaPath: options.schemaPath,\n autoRegisterModels: options.autoRegisterModels,\n };\n\n if (options.assert !== false) {\n try {\n assertSelectComposeValid(cwd, validateOptions);\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, validateOptions);\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","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SKILL_NAMES = ['prismakit', 'prismakit-nestjs'] as const;\nconst RULE_NAME = 'data-access.mdc';\n\nexport type SkillsCommandOptions = {\n cwd?: string;\n /** Install into ~/.cursor/skills (all projects). Default: <cwd>/.cursor/skills */\n global?: boolean;\n /** Override project root when not using --global. */\n projectRoot?: string;\n /** Subset of skill folder names. Default: both. */\n skill?: string[];\n /** Also copy templates/cursor-rules/data-access.mdc into .cursor/rules. */\n withRules?: boolean;\n dryRun?: boolean;\n list?: boolean;\n /** Test override: directory that contains prismakit/ and prismakit-nestjs/. */\n skillsRoot?: string;\n /** Test override: path to data-access.mdc. */\n rulesPath?: string;\n};\n\nexport type SkillsInstallResult = {\n dest: string;\n installed: string[];\n ruleDest?: string;\n};\n\nfunction isSkillsRoot(dir: string): boolean {\n return fs.existsSync(path.join(dir, 'prismakit', 'SKILL.md'));\n}\n\n/**\n * Walk up from the CLI entrypoint looking for bundled `skills/` (published\n * package) or the monorepo `skills/` directory.\n */\nexport function findSkillsRoot(startDir: string): string | undefined {\n let dir = path.resolve(startDir);\n for (let i = 0; i < 10; i++) {\n for (const candidate of [\n path.join(dir, 'skills'),\n dir,\n path.join(dir, 'templates', 'cursor-skills'),\n ]) {\n if (isSkillsRoot(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\nexport function findRulesPath(skillsRoot: string): string | undefined {\n const candidates = [\n path.join(skillsRoot, '..', 'templates', 'cursor-rules', RULE_NAME),\n path.join(skillsRoot, '..', 'rules', RULE_NAME),\n path.join(path.dirname(skillsRoot), 'rules', RULE_NAME),\n ];\n return candidates.find((p) => fs.existsSync(p));\n}\n\nfunction copyDir(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n if (entry.name === '.git') continue;\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n copyDir(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n }\n }\n}\n\nfunction resolveHome(): string {\n return process.env.HOME || os.homedir();\n}\n\nfunction assertNotBuiltinSkills(dest: string): void {\n const forbidden = path.join(resolveHome(), '.cursor', 'skills-cursor');\n const resolved = path.resolve(dest);\n if (resolved === forbidden || resolved.startsWith(`${forbidden}${path.sep}`)) {\n throw new Error(\n `Refusing to install into ${resolved} (reserved for Cursor built-ins).`,\n );\n }\n}\n\nfunction cliStartDir(): string {\n try {\n const url = import.meta.url;\n if (typeof url === 'string' && url.length > 0) {\n return path.dirname(fileURLToPath(url));\n }\n } catch {\n // CJS bundle has an empty import.meta\n }\n return process.cwd();\n}\n\nexport function runSkills(options: SkillsCommandOptions = {}): SkillsInstallResult {\n const skillsRoot =\n options.skillsRoot ??\n findSkillsRoot(cliStartDir()) ??\n findSkillsRoot(process.cwd());\n if (!skillsRoot) {\n throw new Error(\n 'Could not find PrismaKit skills. Reinstall @prismakit/cli or clone fikiap23/prismakit.',\n );\n }\n\n const available = SKILL_NAMES.filter((name) =>\n fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')),\n );\n if (available.length === 0) {\n throw new Error(`No skills found in ${skillsRoot}`);\n }\n\n const requested = options.skill?.length\n ? options.skill.map((s) => s.trim()).filter(Boolean)\n : [...available];\n\n for (const name of requested) {\n if (!available.includes(name as (typeof SKILL_NAMES)[number])) {\n throw new Error(\n `Unknown skill \"${name}\". Available: ${available.join(', ')}`,\n );\n }\n }\n\n if (options.list) {\n console.log(`Skills in ${skillsRoot}:`);\n for (const name of available) {\n console.log(` - ${name}`);\n }\n return { dest: skillsRoot, installed: [] };\n }\n\n const cwd = options.cwd ?? process.cwd();\n const dest = options.global\n ? path.join(resolveHome(), '.cursor', 'skills')\n : path.join(\n path.resolve(options.projectRoot ?? cwd),\n '.cursor',\n 'skills',\n );\n\n assertNotBuiltinSkills(dest);\n\n if (!options.dryRun) {\n fs.mkdirSync(dest, { recursive: true });\n }\n\n const installed: string[] = [];\n for (const name of requested) {\n const src = path.join(skillsRoot, name);\n const target = path.join(dest, name);\n if (options.dryRun) {\n console.log(`[dry-run] would install ${name} -> ${target}`);\n } else {\n fs.rmSync(target, { recursive: true, force: true });\n copyDir(src, target);\n console.log(`Installed ${name} -> ${target}`);\n }\n installed.push(name);\n }\n\n let ruleDest: string | undefined;\n if (options.withRules) {\n const rulesSrc = options.rulesPath ?? findRulesPath(skillsRoot);\n if (!rulesSrc) {\n throw new Error(`Could not find ${RULE_NAME} next to skills.`);\n }\n const rulesDir = options.global\n ? path.join(resolveHome(), '.cursor', 'rules')\n : path.join(path.resolve(options.projectRoot ?? cwd), '.cursor', 'rules');\n ruleDest = path.join(rulesDir, RULE_NAME);\n if (options.dryRun) {\n console.log(`[dry-run] would install rule -> ${ruleDest}`);\n } else {\n fs.mkdirSync(rulesDir, { recursive: true });\n fs.copyFileSync(rulesSrc, ruleDest);\n console.log(`Installed rule -> ${ruleDest}`);\n }\n }\n\n const scope = options.global\n ? 'global (~/.cursor/skills)'\n : 'project (.cursor/skills)';\n if (options.dryRun) {\n console.log(`Dry-run complete (${scope}).`);\n } else if (options.global) {\n console.log(`Done. Skills are available in all Cursor projects on this machine (${scope}).`);\n } else {\n console.log(\n `Done. Commit .cursor/skills so the team shares the same agent contract (${scope}).`,\n );\n }\n\n return { dest, installed, ruleDest };\n}\n"],"mappings":"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","names":["fs","path"]}