feat(plop): 添加菜单/权限种子脚本生成功能
- 新增种子脚本模板 module-seed.hbs - 生成器新增「菜单/权限种子脚本」选项 - 添加菜单图标和排序的提示问题 - 生成的脚本包含 4 个 CRUD 权限和 1 个菜单项 - 支持独立运行或被主 seed.ts 导入调用 - 修复 ESM 模块导入路径(添加 .ts 扩展名) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
|
||||
import type { NodePlopAPI, ActionType } from 'plop';
|
||||
import pluralize from 'pluralize';
|
||||
import { parseFields, type FieldDefinition } from '../utils/field-parser';
|
||||
import { parseFields, type FieldDefinition } from '../utils/field-parser.ts';
|
||||
import {
|
||||
parseRelations,
|
||||
parseManyToMany,
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
type ManyToManyConfig,
|
||||
type OneToManyConfig,
|
||||
type ManyToOneConfig,
|
||||
} from '../utils/relation-parser';
|
||||
} from '../utils/relation-parser.ts';
|
||||
import {
|
||||
parseSchema,
|
||||
getAvailableModels,
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
getModelByName,
|
||||
inferManyToManyConfig,
|
||||
type SchemaModel,
|
||||
} from '../utils/schema-parser';
|
||||
} from '../utils/schema-parser.ts';
|
||||
|
||||
// 服务类型
|
||||
type ServiceType = 'CrudService' | 'RelationCrudService' | 'ManyToManyCrudService';
|
||||
@@ -73,6 +73,11 @@ interface TemplateData {
|
||||
hasCountRelations: boolean;
|
||||
needsResponseDto: boolean;
|
||||
needsDetailDto: boolean;
|
||||
|
||||
// 种子脚本相关
|
||||
menuIcon: string;
|
||||
menuSort: number;
|
||||
menuParentCode: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +113,7 @@ export function crudGenerator(plop: NodePlopAPI) {
|
||||
{ name: '前端 (Next.js)', value: 'web', checked: true },
|
||||
{ name: '共享类型', value: 'shared', checked: true },
|
||||
{ name: 'Prisma Model', value: 'prisma', checked: true },
|
||||
{ name: '菜单/权限种子脚本', value: 'seed', checked: true },
|
||||
],
|
||||
},
|
||||
// 服务类型选择
|
||||
@@ -412,6 +418,24 @@ name:string 名称 "示例名称" min:2 max:100
|
||||
],
|
||||
default: 'desc',
|
||||
},
|
||||
// 菜单图标(生成种子脚本时使用)
|
||||
{
|
||||
type: 'input',
|
||||
name: 'menuIcon',
|
||||
message: '菜单图标名称(Lucide 图标,如 Users、FileText、Settings):',
|
||||
when: (answers: { generateTargets: string[] }) =>
|
||||
answers.generateTargets.includes('seed'),
|
||||
default: 'FileText',
|
||||
},
|
||||
// 菜单排序
|
||||
{
|
||||
type: 'number',
|
||||
name: 'menuSort',
|
||||
message: '菜单排序值(数字越小越靠前):',
|
||||
when: (answers: { generateTargets: string[] }) =>
|
||||
answers.generateTargets.includes('seed'),
|
||||
default: 50,
|
||||
},
|
||||
],
|
||||
actions: (data) => {
|
||||
if (!data) return [];
|
||||
@@ -499,6 +523,11 @@ name:string 名称 "示例名称" min:2 max:100
|
||||
hasCountRelations,
|
||||
needsResponseDto,
|
||||
needsDetailDto,
|
||||
|
||||
// 种子脚本相关
|
||||
menuIcon: data.menuIcon || 'FileText',
|
||||
menuSort: data.menuSort || 50,
|
||||
menuParentCode: '', // 默认不设父目录
|
||||
};
|
||||
|
||||
const actions: ActionType[] = [];
|
||||
@@ -670,6 +699,17 @@ name:string 名称 "示例名称" min:2 max:100
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 种子脚本 =====
|
||||
if (data.generateTargets.includes('seed')) {
|
||||
actions.push({
|
||||
type: 'add',
|
||||
path: 'apps/api/prisma/seeds/{{kebabCase name}}.seed.ts',
|
||||
templateFile: 'templates/seed/module-seed.hbs',
|
||||
data: templateData,
|
||||
abortOnFail: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 打印生成信息
|
||||
actions.push(() => {
|
||||
console.log('\n✨ 生成完成!\n');
|
||||
@@ -711,13 +751,21 @@ name:string 名称 "示例名称" min:2 max:100
|
||||
}
|
||||
|
||||
console.log('\n后续步骤:');
|
||||
let stepNum = 1;
|
||||
if (hasOneToMany || hasManyToOne) {
|
||||
console.log('1. 按照上述提示修改目标模型');
|
||||
console.log('2. 运行 pnpm db:generate && pnpm db:push');
|
||||
} else if (data.generateTargets.includes('prisma')) {
|
||||
console.log('1. 运行 pnpm db:generate && pnpm db:push');
|
||||
console.log(`${stepNum}. 按照上述提示修改目标模型`);
|
||||
stepNum++;
|
||||
}
|
||||
console.log(`${hasOneToMany || hasManyToOne ? '3' : '2'}. 重启开发服务器 pnpm dev\n`);
|
||||
if (data.generateTargets.includes('prisma')) {
|
||||
console.log(`${stepNum}. 运行 pnpm db:generate && pnpm db:push`);
|
||||
stepNum++;
|
||||
}
|
||||
if (data.generateTargets.includes('seed')) {
|
||||
console.log(`${stepNum}. 查看并执行种子脚本:`);
|
||||
console.log(` cd apps/api && npx ts-node prisma/seeds/${data.name}.seed.ts`);
|
||||
stepNum++;
|
||||
}
|
||||
console.log(`${stepNum}. 重启开发服务器 pnpm dev\n`);
|
||||
return '完成';
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
getCellRenderer,
|
||||
getWhereCondition,
|
||||
getFormattedExample,
|
||||
} from '../utils/field-parser';
|
||||
} from '../utils/field-parser.ts';
|
||||
|
||||
/**
|
||||
* 命名转换工具函数
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NodePlopAPI } from 'plop';
|
||||
|
||||
import { registerHelpers } from './helpers';
|
||||
import { crudGenerator } from './generators/crud';
|
||||
import { registerHelpers } from './helpers/index.ts';
|
||||
import { crudGenerator } from './generators/crud.ts';
|
||||
|
||||
export default function (plop: NodePlopAPI) {
|
||||
// 注册自定义 Handlebars helpers
|
||||
|
||||
97
plop/templates/seed/module-seed.hbs
Normal file
97
plop/templates/seed/module-seed.hbs
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* {{chineseName}}模块种子数据
|
||||
*
|
||||
* 此脚本可独立运行:npx ts-node prisma/seeds/{{kebabCase name}}.seed.ts
|
||||
* 或作为模块被主 seed.ts 导入调用
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
// {{chineseName}}管理权限
|
||||
const {{camelCase name}}Permissions = [
|
||||
{ code: '{{kebabCase name}}:create', name: '创建{{chineseName}}', resource: '{{kebabCase name}}', action: 'create' },
|
||||
{ code: '{{kebabCase name}}:read', name: '查看{{chineseName}}', resource: '{{kebabCase name}}', action: 'read' },
|
||||
{ code: '{{kebabCase name}}:update', name: '更新{{chineseName}}', resource: '{{kebabCase name}}', action: 'update' },
|
||||
{ code: '{{kebabCase name}}:delete', name: '删除{{chineseName}}', resource: '{{kebabCase name}}', action: 'delete' },
|
||||
];
|
||||
|
||||
// {{chineseName}}管理菜单
|
||||
const {{camelCase name}}Menu = {
|
||||
code: '{{kebabCase name}}-management',
|
||||
name: '{{chineseName}}管理',
|
||||
type: 'menu',
|
||||
path: '/{{kebabCase pluralName}}',
|
||||
icon: '{{menuIcon}}',
|
||||
sort: {{menuSort}},
|
||||
isStatic: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建{{chineseName}}模块的权限和菜单
|
||||
* @param prisma PrismaClient 实例(可选,不传则创建新实例)
|
||||
*/
|
||||
export async function seed{{pascalCase name}}Module(prisma?: PrismaClient) {
|
||||
const db = prisma || new PrismaClient();
|
||||
const isOwnClient = !prisma;
|
||||
|
||||
try {
|
||||
console.log('\n开始创建{{chineseName}}模块种子数据...');
|
||||
|
||||
// 1. 创建权限
|
||||
console.log(' 创建权限...');
|
||||
for (const permission of {{camelCase name}}Permissions) {
|
||||
await db.permission.upsert({
|
||||
where: { code: permission.code },
|
||||
update: permission,
|
||||
create: permission,
|
||||
});
|
||||
}
|
||||
console.log(' ✓ 已创建 ' + {{camelCase name}}Permissions.length + ' 个权限');
|
||||
|
||||
// 2. 创建菜单
|
||||
console.log(' 创建菜单...');
|
||||
{{#if menuParentCode}}
|
||||
// 获取父菜单
|
||||
const parentMenu = await db.menu.findUnique({
|
||||
where: { code: '{{menuParentCode}}' },
|
||||
});
|
||||
|
||||
if (parentMenu) {
|
||||
await db.menu.upsert({
|
||||
where: { code: {{camelCase name}}Menu.code },
|
||||
update: { ...{{camelCase name}}Menu, parentId: parentMenu.id },
|
||||
create: { ...{{camelCase name}}Menu, parentId: parentMenu.id },
|
||||
});
|
||||
} else {
|
||||
console.warn(' ⚠ 父菜单 {{menuParentCode}} 不存在,将创建为顶级菜单');
|
||||
await db.menu.upsert({
|
||||
where: { code: {{camelCase name}}Menu.code },
|
||||
update: {{camelCase name}}Menu,
|
||||
create: {{camelCase name}}Menu,
|
||||
});
|
||||
}
|
||||
{{else}}
|
||||
await db.menu.upsert({
|
||||
where: { code: {{camelCase name}}Menu.code },
|
||||
update: {{camelCase name}}Menu,
|
||||
create: {{camelCase name}}Menu,
|
||||
});
|
||||
{{/if}}
|
||||
console.log(' ✓ 已创建菜单: {{chineseName}}管理');
|
||||
|
||||
console.log('{{chineseName}}模块种子数据创建完成!\n');
|
||||
} finally {
|
||||
if (isOwnClient) {
|
||||
await db.$disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 直接运行时执行
|
||||
if (require.main === module) {
|
||||
seed{{pascalCase name}}Module()
|
||||
.catch((e) => {
|
||||
console.error('{{chineseName}}模块种子数据创建失败:', e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user