moyan-mfw-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +12 -61
  2. package/dist/index.cjs +35 -16
  3. package/dist/index.js +34 -14
  4. package/dist/templates/business/backend/.env.hbs +17 -0
  5. package/dist/templates/business/backend/nest-cli.json.hbs +10 -0
  6. package/dist/templates/business/backend/package.json.hbs +36 -0
  7. package/dist/templates/business/backend/src/app-types.config.ts.hbs +3 -0
  8. package/dist/templates/business/backend/src/app.modules.ts.hbs +6 -0
  9. package/dist/templates/business/backend/src/main.ts.hbs +38 -0
  10. package/dist/templates/business/backend/src/permissions.ts.hbs +4 -0
  11. package/dist/templates/business/backend/tsconfig.json.hbs +18 -0
  12. package/dist/templates/business/frontend/index.html.hbs +35 -0
  13. package/dist/templates/business/frontend/package.json.hbs +31 -0
  14. package/dist/templates/business/frontend/src/main.ts.hbs +24 -0
  15. package/dist/templates/business/frontend/src/permissions.ts.hbs +4 -0
  16. package/dist/templates/business/frontend/src/router.ts.hbs +9 -0
  17. package/dist/templates/business/frontend/src/views/dashboard/Index.vue.hbs +93 -0
  18. package/dist/templates/business/frontend/src/views/dashboard/index.ts.hbs +11 -0
  19. package/dist/templates/business/frontend/tsconfig.json.hbs +19 -0
  20. package/dist/templates/business/frontend/vite.config.ts.hbs +27 -0
  21. package/dist/templates/business/package.json.hbs +17 -0
  22. package/dist/templates/business/shared/package.json.hbs +34 -0
  23. package/dist/templates/business/shared/src/index.ts.hbs +1 -0
  24. package/dist/templates/business/shared/src/permissions.ts.hbs +3 -0
  25. package/dist/templates/business/shared/tsconfig.cjs.json.hbs +13 -0
  26. package/dist/templates/business/shared/tsconfig.json.hbs +13 -0
  27. package/dist/templates/business/tsconfig.base.json.hbs +12 -0
  28. package/dist/templates/extension/README.md.hbs +27 -0
  29. package/dist/templates/extension/backend/.env.hbs +10 -0
  30. package/dist/templates/extension/backend/nest-cli.json.hbs +1 -0
  31. package/dist/templates/extension/backend/package.json.hbs +1 -0
  32. package/dist/templates/extension/backend/src/controller/.gitkeep +0 -0
  33. package/dist/templates/extension/backend/src/dto/.gitkeep +0 -0
  34. package/dist/templates/extension/backend/src/entities/.gitkeep +0 -0
  35. package/dist/templates/extension/backend/src/index.ts.hbs +1 -0
  36. package/dist/templates/extension/backend/src/main.ts.hbs +34 -0
  37. package/dist/templates/extension/backend/src/service/.gitkeep +0 -0
  38. package/dist/templates/extension/backend/src/{{name}}.module.ts.hbs +4 -0
  39. package/dist/templates/extension/backend/tsconfig.json.hbs +1 -0
  40. package/dist/templates/extension/frontend/api.build.cjs.hbs +8 -0
  41. package/dist/templates/extension/frontend/index.html.hbs +12 -0
  42. package/dist/templates/extension/frontend/package.json.hbs +1 -0
  43. package/dist/templates/extension/frontend/src/apis/.gitkeep +0 -0
  44. package/dist/templates/extension/frontend/src/components/.gitkeep +0 -0
  45. package/dist/templates/extension/frontend/src/env.d.ts.hbs +1 -0
  46. package/dist/templates/extension/frontend/src/index.ts.hbs +13 -0
  47. package/dist/templates/extension/frontend/src/main.ts.hbs +13 -0
  48. package/dist/templates/extension/frontend/src/views/.gitkeep +0 -0
  49. package/dist/templates/extension/frontend/tsconfig.json.hbs +1 -0
  50. package/dist/templates/extension/frontend/vite.config.mts.hbs +30 -0
  51. package/dist/templates/extension/package.json.hbs +1 -0
  52. package/dist/templates/extension/shared/package.json.hbs +1 -0
  53. package/dist/templates/extension/shared/src/constants.ts.hbs +12 -0
  54. package/dist/templates/extension/shared/src/dict.ts.hbs +8 -0
  55. package/dist/templates/extension/shared/src/index.ts.hbs +10 -0
  56. package/dist/templates/extension/shared/src/paths.ts.hbs +5 -0
  57. package/dist/templates/extension/shared/src/permission-values.ts.hbs +3 -0
  58. package/dist/templates/extension/shared/src/types.ts.hbs +5 -0
  59. package/dist/templates/extension/shared/tsconfig.json.hbs +1 -0
  60. package/dist/templates/extension/tsconfig.json.hbs +1 -0
  61. package/package.json +52 -1
package/README.md CHANGED
@@ -57,6 +57,12 @@ extension-my-ext/
57
57
  └── package.json
58
58
  ```
59
59
 
60
+ **非交互模式**(跳过所有提示,使用默认值):
61
+
62
+ ```bash
63
+ mfw create extension my-ext -y
64
+ ```
65
+
60
66
  **后续步骤**:
61
67
 
62
68
  ```bash
@@ -82,6 +88,12 @@ mfw create business my-shop
82
88
  | 后端端口 | `3000` | NestJS 服务端口 |
83
89
  | 前端端口 | `5173` | Vite 开发服务器端口 |
84
90
 
91
+ **非交互模式**(跳过所有提示,使用默认值):
92
+
93
+ ```bash
94
+ mfw create business my-shop -y
95
+ ```
96
+
85
97
  **生成目录结构**:
86
98
 
87
99
  ```
@@ -120,67 +132,6 @@ pnpm --filter my-shop-backend dev # 启动后端
120
132
  pnpm --filter my-shop-frontend dev # 启动前端
121
133
  ```
122
134
 
123
- ## 模板说明
124
-
125
- ### Handlebars 模板变量
126
-
127
- | 变量 | 示例值 | 说明 |
128
- |------|--------|------|
129
- | `{{name}}` | `my-ext` | kebab-case 项目名 |
130
- | `{{displayName}}` | `MyExt` | 显示名称 |
131
- | `{{description}}` | 用户输入 | 项目描述 |
132
- | `{{className}}` | `MyExt` | PascalCase 类名 |
133
- | `{{version}}` | `0.1.0` | 初始版本 |
134
- | `{{year}}` | `2026` | 当前年份 |
135
-
136
- ### 自定义 Helpers
137
-
138
- | Helper | 输入 | 输出 | 说明 |
139
- |--------|------|------|------|
140
- | `pascalCase` | `my-ext` | `MyExt` | 转 PascalCase |
141
- | `pascalCaseUpper` | `my-ext` | `MYEXT` | 转大写 PascalCase |
142
- | `camelCase` | `my-ext` | `myExt` | 转 camelCase |
143
- | `snakeCase` | `my-ext` | `my_ext` | 转 snake_case |
144
-
145
- ### 模板变量示例
146
-
147
- ```hbs
148
- <!-- 权限常量声明 -->
149
- export const {{pascalCaseUpper name}}_PERMISSION_VALUES = [...]
150
-
151
- <!-- 数据库名 -->
152
- DB_NAME={{snakeCase name}}
153
-
154
- <!-- 包名引用 -->
155
- "moyan-mfw-extension-{{name}}": "workspace:*"
156
- ```
157
-
158
- ## 开发
159
-
160
- ```bash
161
- # 克隆仓库后进入 CLI 目录
162
- cd packages/cli
163
-
164
- # 安装依赖
165
- pnpm install
166
-
167
- # 开发模式(watch)
168
- pnpm dev
169
-
170
- # 构建
171
- pnpm build
172
-
173
- # 类型检查
174
- pnpm typecheck
175
- ```
176
-
177
- ### 添加新模板
178
-
179
- 1. 在 `src/templates/` 下创建新目录
180
- 2. 编写 `.hbs` 模板文件
181
- 3. 在 `src/commands/` 添加对应命令
182
- 4. 在 `src/utils/template.ts` 注册 helpers(如需)
183
-
184
135
  ## 已知修复
185
136
 
186
137
  | 版本 | 修复内容 |
package/dist/index.cjs CHANGED
@@ -32,8 +32,6 @@ var import_commander = require("commander");
32
32
  var import_inquirer = __toESM(require("inquirer"), 1);
33
33
  var import_chalk = __toESM(require("chalk"), 1);
34
34
  var path3 = __toESM(require("path"), 1);
35
- var import_node_path = require("path");
36
- var import_node_url = require("url");
37
35
  var fs3 = __toESM(require("fs/promises"), 1);
38
36
 
39
37
  // src/utils/fs.ts
@@ -58,7 +56,10 @@ async function exists(filePath) {
58
56
  // src/utils/template.ts
59
57
  var import_handlebars = __toESM(require("handlebars"), 1);
60
58
  var fs2 = __toESM(require("fs/promises"), 1);
59
+ var fsSync = __toESM(require("fs"), 1);
61
60
  var path2 = __toESM(require("path"), 1);
61
+ var import_node_url = require("url");
62
+ var import_meta = {};
62
63
  import_handlebars.default.registerHelper(
63
64
  "pascalCase",
64
65
  (str) => str.replace(/(^\w|-\w)/g, (c) => c.slice(-1).toUpperCase())
@@ -72,6 +73,14 @@ import_handlebars.default.registerHelper("pascalCaseUpper", (str) => {
72
73
  return pascal.toUpperCase();
73
74
  });
74
75
  import_handlebars.default.registerHelper("snakeCase", (str) => str.replace(/-/g, "_"));
76
+ function getTemplateDir(type) {
77
+ const currentDir = path2.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
78
+ const distTemplate = path2.resolve(currentDir, `templates/${type}`);
79
+ const srcTemplate = path2.resolve(currentDir, `../templates/${type}`);
80
+ if (fsSync.existsSync(distTemplate)) return distTemplate;
81
+ if (fsSync.existsSync(srcTemplate)) return srcTemplate;
82
+ return distTemplate;
83
+ }
75
84
  function getParser(filePath) {
76
85
  const ext = path2.extname(filePath);
77
86
  const map = {
@@ -148,10 +157,17 @@ async function getAllFiles(dir) {
148
157
  }
149
158
 
150
159
  // src/commands/create.ts
151
- var import_meta = {};
152
- var __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
153
- var __dirname = (0, import_node_path.dirname)(__filename);
154
- var createCommand = new import_commander.Command("extension").description("Create a new MFW extension package").argument("<name>", 'Extension name in kebab-case (e.g., "ad", "blog")').option("-t, --template <name>", "Template to use", "default").option("-d, --dir <path>", "Output directory", "packages/extensions").option("-f, --force", "Force overwrite existing directory", false).action(async (name, opts) => {
160
+ function defaultExtensionAnswers(name, className) {
161
+ return {
162
+ displayName: className,
163
+ description: "",
164
+ routePrefix: `/ext/${name}`,
165
+ hasBackend: true,
166
+ hasFrontend: true,
167
+ hasShared: true
168
+ };
169
+ }
170
+ var createCommand = new import_commander.Command("extension").description("Create a new MFW extension package").argument("<name>", 'Extension name in kebab-case (e.g., "ad", "blog")').option("-t, --template <name>", "Template to use", "default").option("-d, --dir <path>", "Output directory", "packages/extensions").option("-f, --force", "Force overwrite existing directory", false).option("-y, --yes", "Skip prompts and use default values", false).action(async (name, opts) => {
155
171
  if (!/^[a-z][a-z0-9-]*$/.test(name)) {
156
172
  console.error(import_chalk.default.red(`Invalid name "${name}". Must be kebab-case.`));
157
173
  process.exit(1);
@@ -165,7 +181,7 @@ Use --force to overwrite.`));
165
181
  process.exit(1);
166
182
  }
167
183
  }
168
- const answers = await import_inquirer.default.prompt([
184
+ const answers = opts.yes ? defaultExtensionAnswers(name, className) : await import_inquirer.default.prompt([
169
185
  { name: "displayName", message: "\u663E\u793A\u540D\u79F0:", default: className },
170
186
  { name: "description", message: "\u63CF\u8FF0:", default: "" },
171
187
  { name: "routePrefix", message: "\u8DEF\u7531\u524D\u7F00:", default: `/ext/${name}` },
@@ -191,7 +207,7 @@ Use --force to overwrite.`));
191
207
  version: "0.1.0",
192
208
  year: (/* @__PURE__ */ new Date()).getFullYear()
193
209
  };
194
- const templateDir = path3.resolve(__dirname, "../templates/extension");
210
+ const templateDir = getTemplateDir("extension");
195
211
  console.log(import_chalk.default.blue(`
196
212
  \u{1F528} Generating extension "${name}" at ${targetDir}...`));
197
213
  await renderTemplateToDir(templateDir, targetDir, vars);
@@ -221,12 +237,15 @@ var import_commander2 = require("commander");
221
237
  var import_inquirer2 = __toESM(require("inquirer"), 1);
222
238
  var import_chalk2 = __toESM(require("chalk"), 1);
223
239
  var path4 = __toESM(require("path"), 1);
224
- var import_node_path2 = require("path");
225
- var import_node_url2 = require("url");
226
- var import_meta2 = {};
227
- var __filename2 = (0, import_node_url2.fileURLToPath)(import_meta2.url);
228
- var __dirname2 = (0, import_node_path2.dirname)(__filename2);
229
- var createBusinessCommand = new import_commander2.Command("business").description("Create a new business project (backend + frontend + shared)").argument("<name>", 'Project name in kebab-case (e.g., "my-shop")').option("-d, --dir <path>", "Output directory", ".").option("-f, --force", "Force overwrite existing directory", false).action(async (name, opts) => {
240
+ function defaultBusinessAnswers(className) {
241
+ return {
242
+ displayName: className,
243
+ description: `${className} \u4E1A\u52A1\u9879\u76EE`,
244
+ port: "3000",
245
+ frontendPort: "5173"
246
+ };
247
+ }
248
+ var createBusinessCommand = new import_commander2.Command("business").description("Create a new business project (backend + frontend + shared)").argument("<name>", 'Project name in kebab-case (e.g., "my-shop")').option("-d, --dir <path>", "Output directory", ".").option("-f, --force", "Force overwrite existing directory", false).option("-y, --yes", "Skip prompts and use default values", false).action(async (name, opts) => {
230
249
  if (!/^[a-z][a-z0-9-]*$/.test(name)) {
231
250
  console.error(import_chalk2.default.red(`Invalid name "${name}". Must be kebab-case (e.g., "my-shop").`));
232
251
  process.exit(1);
@@ -240,7 +259,7 @@ Use --force to overwrite.`));
240
259
  process.exit(1);
241
260
  }
242
261
  }
243
- const answers = await import_inquirer2.default.prompt([
262
+ const answers = opts.yes ? defaultBusinessAnswers(className) : await import_inquirer2.default.prompt([
244
263
  {
245
264
  name: "displayName",
246
265
  message: "\u663E\u793A\u540D\u79F0:",
@@ -280,7 +299,7 @@ Use --force to overwrite.`));
280
299
  version: "0.1.0",
281
300
  year: (/* @__PURE__ */ new Date()).getFullYear()
282
301
  };
283
- const templateDir = path4.resolve(__dirname2, "../templates/business");
302
+ const templateDir = getTemplateDir("business");
284
303
  console.log(import_chalk2.default.blue(`
285
304
  \u{1F528} Generating business project "${name}" at ${targetDir}...`));
286
305
  await renderTemplateToDir(templateDir, targetDir, vars);
package/dist/index.js CHANGED
@@ -9,8 +9,6 @@ import { Command } from "commander";
9
9
  import inquirer from "inquirer";
10
10
  import chalk from "chalk";
11
11
  import * as path3 from "path";
12
- import { dirname as dirname2 } from "path";
13
- import { fileURLToPath } from "url";
14
12
  import * as fs3 from "fs/promises";
15
13
 
16
14
  // src/utils/fs.ts
@@ -35,7 +33,9 @@ async function exists(filePath) {
35
33
  // src/utils/template.ts
36
34
  import Handlebars from "handlebars";
37
35
  import * as fs2 from "fs/promises";
36
+ import * as fsSync from "fs";
38
37
  import * as path2 from "path";
38
+ import { fileURLToPath } from "url";
39
39
  Handlebars.registerHelper(
40
40
  "pascalCase",
41
41
  (str) => str.replace(/(^\w|-\w)/g, (c) => c.slice(-1).toUpperCase())
@@ -49,6 +49,14 @@ Handlebars.registerHelper("pascalCaseUpper", (str) => {
49
49
  return pascal.toUpperCase();
50
50
  });
51
51
  Handlebars.registerHelper("snakeCase", (str) => str.replace(/-/g, "_"));
52
+ function getTemplateDir(type) {
53
+ const currentDir = path2.dirname(fileURLToPath(import.meta.url));
54
+ const distTemplate = path2.resolve(currentDir, `templates/${type}`);
55
+ const srcTemplate = path2.resolve(currentDir, `../templates/${type}`);
56
+ if (fsSync.existsSync(distTemplate)) return distTemplate;
57
+ if (fsSync.existsSync(srcTemplate)) return srcTemplate;
58
+ return distTemplate;
59
+ }
52
60
  function getParser(filePath) {
53
61
  const ext = path2.extname(filePath);
54
62
  const map = {
@@ -125,9 +133,17 @@ async function getAllFiles(dir) {
125
133
  }
126
134
 
127
135
  // src/commands/create.ts
128
- var __filename = fileURLToPath(import.meta.url);
129
- var __dirname = dirname2(__filename);
130
- var createCommand = new Command("extension").description("Create a new MFW extension package").argument("<name>", 'Extension name in kebab-case (e.g., "ad", "blog")').option("-t, --template <name>", "Template to use", "default").option("-d, --dir <path>", "Output directory", "packages/extensions").option("-f, --force", "Force overwrite existing directory", false).action(async (name, opts) => {
136
+ function defaultExtensionAnswers(name, className) {
137
+ return {
138
+ displayName: className,
139
+ description: "",
140
+ routePrefix: `/ext/${name}`,
141
+ hasBackend: true,
142
+ hasFrontend: true,
143
+ hasShared: true
144
+ };
145
+ }
146
+ var createCommand = new Command("extension").description("Create a new MFW extension package").argument("<name>", 'Extension name in kebab-case (e.g., "ad", "blog")').option("-t, --template <name>", "Template to use", "default").option("-d, --dir <path>", "Output directory", "packages/extensions").option("-f, --force", "Force overwrite existing directory", false).option("-y, --yes", "Skip prompts and use default values", false).action(async (name, opts) => {
131
147
  if (!/^[a-z][a-z0-9-]*$/.test(name)) {
132
148
  console.error(chalk.red(`Invalid name "${name}". Must be kebab-case.`));
133
149
  process.exit(1);
@@ -141,7 +157,7 @@ Use --force to overwrite.`));
141
157
  process.exit(1);
142
158
  }
143
159
  }
144
- const answers = await inquirer.prompt([
160
+ const answers = opts.yes ? defaultExtensionAnswers(name, className) : await inquirer.prompt([
145
161
  { name: "displayName", message: "\u663E\u793A\u540D\u79F0:", default: className },
146
162
  { name: "description", message: "\u63CF\u8FF0:", default: "" },
147
163
  { name: "routePrefix", message: "\u8DEF\u7531\u524D\u7F00:", default: `/ext/${name}` },
@@ -167,7 +183,7 @@ Use --force to overwrite.`));
167
183
  version: "0.1.0",
168
184
  year: (/* @__PURE__ */ new Date()).getFullYear()
169
185
  };
170
- const templateDir = path3.resolve(__dirname, "../templates/extension");
186
+ const templateDir = getTemplateDir("extension");
171
187
  console.log(chalk.blue(`
172
188
  \u{1F528} Generating extension "${name}" at ${targetDir}...`));
173
189
  await renderTemplateToDir(templateDir, targetDir, vars);
@@ -197,11 +213,15 @@ import { Command as Command2 } from "commander";
197
213
  import inquirer2 from "inquirer";
198
214
  import chalk2 from "chalk";
199
215
  import * as path4 from "path";
200
- import { dirname as dirname3 } from "path";
201
- import { fileURLToPath as fileURLToPath2 } from "url";
202
- var __filename2 = fileURLToPath2(import.meta.url);
203
- var __dirname2 = dirname3(__filename2);
204
- var createBusinessCommand = new Command2("business").description("Create a new business project (backend + frontend + shared)").argument("<name>", 'Project name in kebab-case (e.g., "my-shop")').option("-d, --dir <path>", "Output directory", ".").option("-f, --force", "Force overwrite existing directory", false).action(async (name, opts) => {
216
+ function defaultBusinessAnswers(className) {
217
+ return {
218
+ displayName: className,
219
+ description: `${className} \u4E1A\u52A1\u9879\u76EE`,
220
+ port: "3000",
221
+ frontendPort: "5173"
222
+ };
223
+ }
224
+ var createBusinessCommand = new Command2("business").description("Create a new business project (backend + frontend + shared)").argument("<name>", 'Project name in kebab-case (e.g., "my-shop")').option("-d, --dir <path>", "Output directory", ".").option("-f, --force", "Force overwrite existing directory", false).option("-y, --yes", "Skip prompts and use default values", false).action(async (name, opts) => {
205
225
  if (!/^[a-z][a-z0-9-]*$/.test(name)) {
206
226
  console.error(chalk2.red(`Invalid name "${name}". Must be kebab-case (e.g., "my-shop").`));
207
227
  process.exit(1);
@@ -215,7 +235,7 @@ Use --force to overwrite.`));
215
235
  process.exit(1);
216
236
  }
217
237
  }
218
- const answers = await inquirer2.prompt([
238
+ const answers = opts.yes ? defaultBusinessAnswers(className) : await inquirer2.prompt([
219
239
  {
220
240
  name: "displayName",
221
241
  message: "\u663E\u793A\u540D\u79F0:",
@@ -255,7 +275,7 @@ Use --force to overwrite.`));
255
275
  version: "0.1.0",
256
276
  year: (/* @__PURE__ */ new Date()).getFullYear()
257
277
  };
258
- const templateDir = path4.resolve(__dirname2, "../templates/business");
278
+ const templateDir = getTemplateDir("business");
259
279
  console.log(chalk2.blue(`
260
280
  \u{1F528} Generating business project "${name}" at ${targetDir}...`));
261
281
  await renderTemplateToDir(templateDir, targetDir, vars);
@@ -0,0 +1,17 @@
1
+ # 数据库配置
2
+ DB_TYPE=mysql
3
+ DB_HOST=localhost
4
+ DB_PORT=3306
5
+ DB_USERNAME=root
6
+ DB_PASSWORD=root
7
+ DB_NAME={{snakeCase name}}
8
+
9
+ # JWT 配置
10
+ JWT_SECRET=your-jwt-secret-change-in-production
11
+ JWT_EXPIRES_IN=7d
12
+
13
+ # 服务端口
14
+ PORT={{port}}
15
+
16
+ # 运行环境
17
+ NODE_ENV=development
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true,
7
+ "webpack": false,
8
+ "tsConfigPath": "tsconfig.json"
9
+ }
10
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "{{name}}-backend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "prebuild": "pnpm --filter {{name}}-shared build && pnpm --filter moyan-mfw-base build",
7
+ "dev": "pnpm run prebuild && nest start --watch",
8
+ "build": "nest build",
9
+ "typecheck": "tsc --noEmit -p tsconfig.json",
10
+ "start": "node dist/main",
11
+ "start:prod": "node dist/main"
12
+ },
13
+ "dependencies": {
14
+ "@nestjs/common": "catalog:",
15
+ "@nestjs/core": "catalog:",
16
+ "@nestjs/platform-express": "catalog:",
17
+ "@nestjs/typeorm": "catalog:",
18
+ "@nestjs/swagger": "catalog:",
19
+ "typeorm": "catalog:",
20
+ "class-transformer": "catalog:",
21
+ "class-validator": "catalog:",
22
+ "reflect-metadata": "catalog:",
23
+ "rxjs": "catalog:",
24
+ "mysql2": "catalog:",
25
+ "dotenv": "catalog:",
26
+ "moyan-mfw-base": "workspace:*",
27
+ "{{name}}-shared": "workspace:*"
28
+ },
29
+ "devDependencies": {
30
+ "@nestjs/cli": "catalog:",
31
+ "@nestjs/schematics": "catalog:",
32
+ "typescript": "catalog:",
33
+ "ts-node": "catalog:",
34
+ "tsconfig-paths": "catalog:"
35
+ }
36
+ }
@@ -0,0 +1,3 @@
1
+ import { AppTypeConfig } from 'moyan-mfw-base/backend'
2
+
3
+ export const appTypesConfig: AppTypeConfig[] = []
@@ -0,0 +1,6 @@
1
+ import { Module } from '@nestjs/common'
2
+
3
+ @Module({
4
+ imports: [],
5
+ })
6
+ export class AppModule {}
@@ -0,0 +1,38 @@
1
+ import 'dotenv/config'
2
+ import { createBaseBackendApp, SwaggerGroupConfig } from 'moyan-mfw-base/backend'
3
+ import { appTypesConfig } from './app-types.config'
4
+ import { AppModule } from './app.modules'
5
+
6
+ const swaggerGroups: SwaggerGroupConfig[] = []
7
+
8
+ async function bootstrap() {
9
+ const startTime = Date.now()
10
+ console.log('[{{displayName}}] ====== Bootstrap Start ======')
11
+
12
+ try {
13
+ const app = await createBaseBackendApp({
14
+ name: '{{displayName}}',
15
+ appTypes: appTypesConfig,
16
+ syncAppTypes: true,
17
+ modules: [AppModule],
18
+ swagger: swaggerGroups,
19
+ permissionValues: [],
20
+ hooks: {
21
+ onAppInit: async (ctx) => {
22
+ console.log('[{{displayName}}] 应用初始化完成')
23
+ },
24
+ },
25
+ })
26
+
27
+ const port = Number(process.env.PORT) || {{port}}
28
+ await app.listen(port)
29
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2)
30
+ console.log(`\n[{{displayName}}] ✅ Server listening on http://localhost:${port} (${elapsed}s)`)
31
+ console.log(`[{{displayName}}] 📖 Swagger: http://localhost:${port}/api-docs/sys`)
32
+ } catch (error) {
33
+ console.error('\n[{{displayName}}] ❌ Bootstrap failed:', error)
34
+ process.exit(1)
35
+ }
36
+ }
37
+
38
+ bootstrap()
@@ -0,0 +1,4 @@
1
+ import { createBusinessPermissionDecorator } from 'moyan-mfw-base/backend'
2
+ import { {{pascalCaseUpper name}}_PERMISSION_VALUES } from '{{name}}-shared'
3
+
4
+ export const Permission = createBusinessPermissionDecorator({{pascalCaseUpper name}}_PERMISSION_VALUES)
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "strictPropertyInitialization": false,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "declaration": true,
10
+ "sourceMap": true,
11
+ "baseUrl": ".",
12
+ "paths": {
13
+ "@/*": ["./src/*"]
14
+ }
15
+ },
16
+ "include": ["src/**/*.ts"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -0,0 +1,35 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{displayName}}</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ #loading-placeholder {
10
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
11
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
12
+ background: linear-gradient(135deg, #ffffff 0%, #f8fafc 50%, #ffffff 100%);
13
+ z-index: 9999; transition: opacity 0.4s ease;
14
+ }
15
+ #loading-placeholder.hidden { opacity: 0; pointer-events: none; }
16
+ .loader {
17
+ width: 48px; height: 48px;
18
+ border: 4px solid #e5e7eb;
19
+ border-top-color: #3b82f6;
20
+ border-radius: 50%;
21
+ animation: spin 0.8s linear infinite;
22
+ }
23
+ @keyframes spin { to { transform: rotate(360deg); } }
24
+ .loading-text { margin-top: 16px; font-family: system-ui, sans-serif; font-size: 14px; color: #6b7280; }
25
+ </style>
26
+ </head>
27
+ <body>
28
+ <div id="app"></div>
29
+ <div id="loading-placeholder">
30
+ <div class="loader"></div>
31
+ <div class="loading-text">正在加载...</div>
32
+ </div>
33
+ <script type="module" src="/src/main.ts"></script>
34
+ </body>
35
+ </html>
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "{{name}}-frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --config vite.config.ts",
8
+ "build": "vue-tsc --noEmit -p tsconfig.json && vite build --config vite.config.ts",
9
+ "typecheck:vue": "vue-tsc --noEmit -p tsconfig.json",
10
+ "typecheck": "vue-tsc --noEmit -p tsconfig.json"
11
+ },
12
+ "dependencies": {
13
+ "vue": "catalog:",
14
+ "vue-router": "catalog:",
15
+ "element-plus": "catalog:",
16
+ "@element-plus/icons-vue": "catalog:",
17
+ "pinia": "catalog:",
18
+ "@vueuse/core": "catalog:",
19
+ "axios": "catalog:",
20
+ "moyan-mfw-base": "workspace:*",
21
+ "{{name}}-shared": "workspace:*"
22
+ },
23
+ "devDependencies": {
24
+ "vite": "catalog:",
25
+ "@vitejs/plugin-vue": "catalog:",
26
+ "@vitejs/plugin-vue-jsx": "catalog:",
27
+ "vue-tsc": "catalog:",
28
+ "sass": "catalog:",
29
+ "typescript": "catalog:"
30
+ }
31
+ }
@@ -0,0 +1,24 @@
1
+ import { createBaseAdminApp, registerPermissionValues } from 'moyan-mfw-base/frontend'
2
+ import { businessRoutes } from './router'
3
+ import './permissions'
4
+
5
+ const admin = createBaseAdminApp({
6
+ title: '{{displayName}}',
7
+ routes: [...businessRoutes],
8
+ layout: {
9
+ layoutMode: 'dual',
10
+ showTabs: true,
11
+ colorMode: 'system',
12
+ themePackage: 'default',
13
+ },
14
+ navigation: {
15
+ brandName: '{{displayName}}',
16
+ brandTagline: '{{description}}',
17
+ homePath: '/dashboard',
18
+ },
19
+ })
20
+
21
+ const values = await admin.fetchPermissionValues()
22
+ admin.initPermissionCache(values)
23
+
24
+ await admin.mount('#app')
@@ -0,0 +1,4 @@
1
+ import { createBusinessPageConfigFn } from 'moyan-mfw-base/frontend'
2
+ import { {{pascalCaseUpper name}}_PERMISSION_VALUES } from '{{name}}-shared'
3
+
4
+ export const defineBusinessPageConfig = createBusinessPageConfigFn({{pascalCaseUpper name}}_PERMISSION_VALUES)
@@ -0,0 +1,9 @@
1
+ import type { RouteRecordRaw } from 'vue-router'
2
+ import { buildRoutesFromConfigs } from 'moyan-mfw-base/frontend'
3
+
4
+ const allConfigs = import.meta.glob('./views/**/index.{ts,tsx}', {
5
+ eager: true,
6
+ import: 'default',
7
+ }) as Record<string, unknown>
8
+
9
+ export const businessRoutes: RouteRecordRaw[] = buildRoutesFromConfigs(allConfigs, { minSegments: 1 })
@@ -0,0 +1,93 @@
1
+ <template>
2
+ <div class="dashboard">
3
+ <el-card class="welcome-card">
4
+ <h1>欢迎使用 {{displayName}}</h1>
5
+ <p>基于 Moyan MFW 框架构建的管理后台</p>
6
+ </el-card>
7
+
8
+ <el-row :gutter="16" class="stats-row">
9
+ <el-col :span="8">
10
+ <el-card shadow="hover">
11
+ <div class="stat-item">
12
+ <div class="stat-label">后端服务</div>
13
+ <div class="stat-value" style="color: #22c55e">运行中</div>
14
+ </div>
15
+ </el-card>
16
+ </el-col>
17
+ <el-col :span="8">
18
+ <el-card shadow="hover">
19
+ <div class="stat-item">
20
+ <div class="stat-label">API 端点</div>
21
+ <div class="stat-value" v-text="apiCount"></div>
22
+ </div>
23
+ </el-card>
24
+ </el-col>
25
+ <el-col :span="8">
26
+ <el-card shadow="hover">
27
+ <div class="stat-item">
28
+ <div class="stat-label">框架版本</div>
29
+ <div class="stat-value">1.0.0</div>
30
+ </div>
31
+ </el-card>
32
+ </el-col>
33
+ </el-row>
34
+
35
+ <el-card class="quick-start-card">
36
+ <template #header>
37
+ <span>快速开始</span>
38
+ </template>
39
+ <el-steps :active="2" finish-status="success">
40
+ <el-step title="创建项目" description="通过 mfw CLI 初始化" />
41
+ <el-step title="添加模块" description="使用 plop 生成业务模块" />
42
+ <el-step title="集成扩展" description="在 AppModule 中引入扩展模块" />
43
+ <el-step title="部署上线" description="构建并发布到生产环境" />
44
+ </el-steps>
45
+ </el-card>
46
+ </div>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ const apiCount = 0
51
+ </script>
52
+
53
+ <style scoped>
54
+ .dashboard {
55
+ padding: 16px;
56
+ }
57
+
58
+ .welcome-card {
59
+ margin-bottom: 16px;
60
+ text-align: center;
61
+ }
62
+
63
+ .welcome-card h1 {
64
+ font-size: 24px;
65
+ color: #1e293b;
66
+ margin-bottom: 8px;
67
+ }
68
+
69
+ .welcome-card p {
70
+ color: #64748b;
71
+ }
72
+
73
+ .stats-row {
74
+ margin-bottom: 16px;
75
+ }
76
+
77
+ .stat-item {
78
+ text-align: center;
79
+ padding: 12px 0;
80
+ }
81
+
82
+ .stat-label {
83
+ font-size: 13px;
84
+ color: #94a3b8;
85
+ margin-bottom: 4px;
86
+ }
87
+
88
+ .stat-value {
89
+ font-size: 28px;
90
+ font-weight: 700;
91
+ color: #3b82f6;
92
+ }
93
+ </style>
@@ -0,0 +1,11 @@
1
+ import { definePageConfig } from 'moyan-mfw-base/frontend'
2
+ import Dashboard from './Index.vue'
3
+
4
+ export default definePageConfig({
5
+ page: Dashboard,
6
+ path: 'dashboard',
7
+ name: '仪表盘',
8
+ icon: 'Odometer',
9
+ auth: true,
10
+ order: 1,
11
+ })
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "preserve",
8
+ "jsxImportSource": "vue",
9
+ "noEmit": true,
10
+ "declaration": false,
11
+ "types": ["vite/client", "node"],
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["./src/*"]
15
+ }
16
+ },
17
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.d.ts"],
18
+ "exclude": ["node_modules"]
19
+ }
@@ -0,0 +1,27 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ import vueJsx from '@vitejs/plugin-vue-jsx'
4
+ import { resolve } from 'path'
5
+
6
+ export default defineConfig({
7
+ root: '.',
8
+ plugins: [vue(), vueJsx()],
9
+ resolve: {
10
+ alias: {
11
+ '@': resolve(__dirname, 'src'),
12
+ },
13
+ },
14
+ server: {
15
+ port: {{frontendPort}},
16
+ proxy: {
17
+ '/api': {
18
+ target: 'http://localhost:{{port}}',
19
+ changeOrigin: true,
20
+ },
21
+ },
22
+ },
23
+ build: {
24
+ outDir: 'dist',
25
+ target: 'es2022',
26
+ },
27
+ })
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "{{description}}",
6
+ "scripts": {
7
+ "preinstall": "npx only-allow pnpm",
8
+ "build": "pnpm run -r build",
9
+ "typecheck": "tsc --noEmit --project tsconfig.json",
10
+ "format": "prettier --write .",
11
+ "format:check": "prettier --check ."
12
+ },
13
+ "engines": {
14
+ "node": ">=20.0.0",
15
+ "pnpm": ">=8.0.0"
16
+ }
17
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "{{name}}-shared",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Shared types, constants, and dictionary definitions for {{displayName}}",
6
+ "main": "./dist/cjs/index.js",
7
+ "module": "./dist/esm/index.js",
8
+ "types": "./dist/esm/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/esm/index.d.ts",
13
+ "default": "./dist/esm/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/cjs/index.d.ts",
17
+ "default": "./dist/cjs/index.js"
18
+ }
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build:cjs": "tsc -p tsconfig.cjs.json",
23
+ "build:esm": "tsc -p tsconfig.json",
24
+ "build": "pnpm run build:cjs && pnpm run build:esm",
25
+ "typecheck": "tsc --noEmit -p tsconfig.json"
26
+ },
27
+ "dependencies": {
28
+ "reflect-metadata": "catalog:",
29
+ "moyan-mfw-base": "workspace:*"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "catalog:"
33
+ }
34
+ }
@@ -0,0 +1 @@
1
+ export * from './permissions'
@@ -0,0 +1,3 @@
1
+ export const {{pascalCaseUpper name}}_PERMISSION_VALUES = [] as const
2
+
3
+ export type {{pascalCase name}}PermissionName = (typeof {{pascalCaseUpper name}}_PERMISSION_VALUES)[number]
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "outDir": "./dist/cjs",
7
+ "rootDir": "./src",
8
+ "declaration": true,
9
+ "declarationMap": true
10
+ },
11
+ "include": ["src/**/*"],
12
+ "exclude": ["node_modules", "dist"]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "outDir": "./dist/esm",
9
+ "rootDir": "./src"
10
+ },
11
+ "include": ["src/**/*"],
12
+ "exclude": ["node_modules", "dist"]
13
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "experimentalDecorators": true,
5
+ "emitDecoratorMetadata": true,
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true
11
+ }
12
+ }
@@ -0,0 +1,27 @@
1
+ # {{displayName}}
2
+
3
+ {{description}}
4
+
5
+ ## 快速开始
6
+
7
+ ```bash
8
+ # 安装依赖
9
+ pnpm install
10
+
11
+ # 启动后端开发服务器
12
+ pnpm dev:backend
13
+
14
+ # 启动前端开发服务器
15
+ pnpm dev:frontend
16
+ ```
17
+
18
+ ## 目录结构
19
+
20
+ ```
21
+ extension-{{name}}/
22
+ ├── src/
23
+ │ ├── backend/ # NestJS 后端
24
+ │ ├── frontend/ # Vue3 前端
25
+ │ └── shared/ # 共享类型与常量
26
+ ├── database/migrations/
27
+ ```
@@ -0,0 +1,10 @@
1
+ DB_HOST=localhost
2
+ DB_PORT=3306
3
+ DB_USERNAME=root
4
+ DB_PASSWORD=root
5
+ DB_NAME=moyan_{{name}}
6
+ REDIS_HOST=localhost
7
+ REDIS_PORT=6379
8
+ JWT_SECRET=change-me-{{name}}-jwt-secret
9
+ JWT_EXPIRES_IN=7d
10
+ PORT=3001
@@ -0,0 +1 @@
1
+ {"$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": {"deleteOutDir": true}}
@@ -0,0 +1 @@
1
+ {"name": "@internal/{{name}}-backend", "version": "{{version}}", "type": "module", "main": "./src/index.ts", "exports": {".": {"types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs"}}, "scripts": {"build": "tsup src/index.ts --format esm,cjs --dts --clean", "typecheck": "tsc --noEmit", "dev": "tsx watch src/index.ts"}, "dependencies": {"moyan-mfw-base/backend": "workspace:*", "@internal/{{name}}-shared": "workspace:*"}, "devDependencies": {"typescript": "catalog:", "tsup": "catalog:", "tsx": "catalog:"}}
@@ -0,0 +1 @@
1
+ export * from './{{name}}.module'
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @fileoverview {{displayName}}扩展包后端独立启动入口
3
+ */
4
+ import { NestFactory } from '@nestjs/core'
5
+ import { {{pascalCase name}}Module } from './{{name}}.module'
6
+ import { createExtensionBackendApp } from 'moyan-mfw-base/backend'
7
+
8
+ async function bootstrap() {
9
+ const startTime = Date.now()
10
+
11
+ console.log('[{{pascalCase name}}] ====== Bootstrap Start ======')
12
+ console.log(`[{{pascalCase name}}] Node.js: ${process.version}`)
13
+ console.log(`[{{pascalCase name}}] ENV: ${process.env.NODE_ENV ?? 'development'}`)
14
+ console.log(`[{{pascalCase name}}] PORT: ${process.env.PORT ?? '3001 (default)'}`)
15
+
16
+ try {
17
+ const app = await createExtensionBackendApp({
18
+ name: '{{name}}',
19
+ module: {{pascalCase name}}Module,
20
+ })
21
+
22
+ const port = Number(process.env.PORT) || 3001
23
+ await app.listen(port)
24
+
25
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2)
26
+ console.log(`\n[{{pascalCase name}}] ✅ Server listening on http://localhost:${port} (${elapsed}s)`)
27
+ } catch (error) {
28
+ console.error('\n[{{pascalCase name}}] ❌ Bootstrap failed:')
29
+ console.error(error)
30
+ process.exit(1)
31
+ }
32
+ }
33
+
34
+ bootstrap()
@@ -0,0 +1,4 @@
1
+ import { Module } from '@nestjs/common'
2
+
3
+ @Module({})
4
+ export class {{pascalCase name}}Module {}
@@ -0,0 +1 @@
1
+ {"compilerOptions": {"target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "lib": ["ES2022"], "outDir": "dist", "rootDir": "src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "composite": true}, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]}
@@ -0,0 +1,8 @@
1
+ // @ts-check
2
+ const { defineConfig } = require('moyan-api/config')
3
+
4
+ module.exports = defineConfig({
5
+ backendUrl: process.env.API_BASE_URL || 'http://localhost:3001',
6
+ outputDir: './src/apis',
7
+ namespace: '{{name}}',
8
+ })
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{displayName}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1 @@
1
+ {"name": "@internal/{{name}}-frontend", "version": "{{version}}", "type": "module", "main": "./src/index.ts", "exports": {".": {"types": "./src/index.d.ts", "import": "./src/index.mjs", "require": "./src/index.js"}}, "scripts": {"build": "vite build && vue-tsc --emitOnly", "typecheck": "vue-tsc --noEmit", "dev": "vite", "preview": "vite preview"}, "dependencies": {"moyan-mfw-base/frontend": "workspace:*", "@internal/{{name}}-shared": "workspace:*", "vue": "catalog:", "vue-router": "catalog:", "element-plus": "catalog:", "@element-plus/icons-vue": "catalog:"}, "devDependencies": {"@vitejs/plugin-vue": "catalog:", "@vitejs/plugin-vue-jsx": "catalog:", "vite": "catalog:", "vue-tsc": "catalog:", "typescript": "catalog:"}}
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview {{displayName}}扩展包前端入口
3
+ */
4
+ import { buildExtensionRoutes } from 'moyan-mfw-base/frontend'
5
+
6
+ const allConfigs = import.meta.glob('./views/**/index.{ts,tsx}', {
7
+ eager: true,
8
+ import: 'default',
9
+ })
10
+
11
+ export const {{pascalCase name}}Routes = buildExtensionRoutes(allConfigs, '{{name}}', {
12
+ namespaceName: '{{displayName}}',
13
+ })
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview {{displayName}}扩展包前端自启动入口
3
+ */
4
+ import 'moyan-mfw-base/frontend/styles/base-admin.scss'
5
+ import { createExtensionFrontendApp } from 'moyan-mfw-base/frontend'
6
+ import { {{pascalCase name}}Routes } from './index'
7
+
8
+ const app = createExtensionFrontendApp({
9
+ name: '{{displayName}}',
10
+ routes: {{pascalCase name}}Routes,
11
+ })
12
+
13
+ app.mount('#app')
@@ -0,0 +1 @@
1
+ {"compilerOptions": {"target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "lib": ["ES2022", "DOM", "DOM.Iterable"], "outDir": "dist", "rootDir": "src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "composite": true, "jsx": "preserve", "jsxImportSource": "vue", "types": ["vite/client"]}, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]}
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+ import vueJsx from '@vitejs/plugin-vue-jsx';
4
+ import { resolve } from 'path';
5
+
6
+ export default defineConfig({
7
+ root: '.',
8
+ plugins: [vue(), vueJsx()],
9
+ resolve: {
10
+ alias: {
11
+ '@': resolve(__dirname, 'src'),
12
+ 'moyan-mfw-base/frontend/styles': resolve(__dirname, '../../../../base/src/frontend/src/styles'),
13
+ 'moyan-mfw-base/frontend': resolve(__dirname, '../../../../base/src/frontend/src/index.ts'),
14
+ 'moyan-mfw-base/shared': resolve(__dirname, '../../../../base/src/shared/src/index.ts'),
15
+ 'moyan-mfw-extension-{{name}}/shared': resolve(__dirname, '../shared/src/index.ts'),
16
+ },
17
+ },
18
+ build: {
19
+ outDir: '../../dist/frontend',
20
+ lib: {
21
+ entry: resolve(__dirname, 'src/index.ts'),
22
+ formats: ['es'],
23
+ fileName: () => 'index.js',
24
+ },
25
+ rollupOptions: {
26
+ external: ['vue', 'vue-router', 'element-plus', '@element-plus/icons-vue', 'moyan-mfw-base/frontend', 'moyan-mfw-base/shared'],
27
+ output: { exports: 'named' },
28
+ },
29
+ },
30
+ });
@@ -0,0 +1 @@
1
+ {"name": "moyan-mfw-extension-{{name}}", "version": "{{version}}", "description": "{{description}}", "private": true, "type": "module", "exports": {"./backend": {"import": "./src/backend/dist/index.js", "require": "./src/backend/dist/index.js", "types": "./src/backend/dist/index.d.ts"}, "./backend/*": {"import": "./src/backend/dist/*.js", "require": "./src/backend/dist/*", "types": "./src/backend/dist/*.d.ts"}, "./frontend": {"import": "./src/frontend/dist/index.mjs", "require": "./src/frontend/dist/index.js", "types": "./src/frontend/dist/index.d.ts"}, "./frontend/*": {"import": "./src/frontend/dist/*", "require": "./src/frontend/dist/*", "types": "./src/frontend/dist/*.d.ts"}, "./shared": {"import": "./src/shared/dist/index.js", "require": "./src/shared/dist/index.js", "types": "./src/shared/dist/index.d.ts"}, "./shared/*": {"import": "./src/shared/dist/*", "require": "./src/shared/dist/*", "types": "./src/shared/dist/*.d.ts"}}, "typesVersions": {"*": {"*": ["./src/*/dist/*.d.ts", "./src/*/dist/index.d.ts"]}}, "scripts": {"build:shared": "pnpm --filter @internal/{{name}}-shared build", "build:backend": "pnpm --filter moyan-mfw-base run build:shared && pnpm --filter moyan-mfw-base run build:backend && pnpm --filter @internal/{{name}}-backend build", "build:frontend": "pnpm --filter @internal/{{name}}-frontend build", "build": "pnpm run build:shared && pnpm run build:backend && pnpm run build:frontend", "dev:backend": "pnpm --filter @internal/{{name}}-backend dev", "dev:frontend": "pnpm --filter @internal/{{name}}-frontend dev", "typecheck:shared": "pnpm --filter @internal/{{name}}-shared typecheck", "typecheck:backend": "pnpm --filter @internal/{{name}}-backend typecheck", "typecheck:frontend": "pnpm --filter @internal/{{name}}-frontend typecheck", "typecheck": "pnpm run typecheck:shared && pnpm run typecheck:backend && pnpm run typecheck:frontend"}, "devDependencies": {"@internal/{{name}}-backend": "workspace:*", "@internal/{{name}}-frontend": "workspace:*", "@internal/{{name}}-shared": "workspace:*", "moyan-mfw-base": "workspace:*", "typescript": "catalog:"}}
@@ -0,0 +1 @@
1
+ {"name": "@internal/{{name}}-shared", "version": "{{version}}", "type": "module", "main": "./src/index.ts", "exports": {".": {"types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs"}}, "scripts": {"build": "tsup src/index.ts --format esm,cjs --dts --clean", "typecheck": "tsc --noEmit", "dev": "tsx watch src/index.ts"}, "dependencies": {"moyan-mfw-base/shared": "workspace:*"}, "devDependencies": {"typescript": "catalog:", "tsup": "catalog:", "tsx": "catalog:"}}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @fileoverview {{displayName}}常量定义
3
+ */
4
+ export const LINK_TYPE = {
5
+ IMAGE: 'image',
6
+ URL: 'url',
7
+ } as const
8
+ export type LinkType = (typeof LINK_TYPE)[keyof typeof LINK_TYPE]
9
+ export const LINK_TYPE_LABELS: Record<LinkType, string> = {
10
+ image: '图片',
11
+ url: '链接',
12
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview {{displayName}}字典定义
3
+ */
4
+ import { DictMeta } from 'moyan-mfw-base/shared'
5
+
6
+ export const {{pascalCase name}}Dict: DictMeta[] = [
7
+ // TODO: 添加字典定义
8
+ ]
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview {{displayName}}共享类型定义
3
+ */
4
+ export type { LinkType } from './constants'
5
+ export { LINK_TYPE, LINK_TYPE_LABELS } from './constants'
6
+ export type { {{pascalCase name}}ItemType } from './types'
7
+ export { {{pascalCase name}}Dict } from './dict'
8
+ export { {{pascalCaseUpper name}}_PATHS } from './paths'
9
+ export { {{pascalCaseUpper name}}_EXTENSION_PERMISSION_VALUES } from './permission-values'
10
+ export type { {{pascalCase name}}PermissionName } from './permission-values'
@@ -0,0 +1,5 @@
1
+ /** {{displayName}} 路径常量 */
2
+ export const {{pascalCaseUpper name}}_PATHS = {
3
+ LIST: '/{{name}}',
4
+ DETAIL: '/{{name}}/:id',
5
+ } as const
@@ -0,0 +1,3 @@
1
+ /** {{displayName}} 权限标签 */
2
+ export const {{pascalCaseUpper name}}_EXTENSION_PERMISSION_VALUES = [] as const
3
+ export type {{pascalCase name}}PermissionName = (typeof {{pascalCaseUpper name}}_EXTENSION_PERMISSION_VALUES)[number]
@@ -0,0 +1,5 @@
1
+ /** {{displayName}} 业务类型定义 */
2
+ export interface {{pascalCase name}}Item {
3
+ id: number
4
+ // TODO: 添加业务字段
5
+ }
@@ -0,0 +1 @@
1
+ {"compilerOptions": {"target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "lib": ["ES2022"], "outDir": "dist", "rootDir": "src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "composite": true}, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]}
@@ -0,0 +1 @@
1
+ {"references": [{"path": "./shared/tsconfig.json"}, {"path": "./backend/tsconfig.json"}, {"path": "./frontend/tsconfig.json"}], "compilerOptions": {"composite": true}}
package/package.json CHANGED
@@ -1 +1,52 @@
1
- {"name": "moyan-mfw-cli", "version": "0.1.0", "description": "MFW framework CLI — extension scaffolding, validation, and publishing tools", "type": "module", "bin": {"mfw": "./bin/mfw.js"}, "exports": {".": {"types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs"}}, "files": ["bin/", "dist/", "README.md", "LICENSE"], "private": false, "scripts": {"dev": "tsx watch src/index.ts", "build": "tsup src/index.ts --format esm,cjs --dts --clean", "typecheck": "tsc --noEmit"}, "dependencies": {"commander": "^12.0.0", "inquirer": "^9.2.0", "chalk": "^5.3.0", "handlebars": "^4.7.8", "semver": "^7.6.0", "glob": "^10.3.0", "execa": "^9.3.0", "prettier": "^3.4.0"}, "devDependencies": {"tsup": "^8.0.0", "tsx": "^4.7.0", "typescript": "^5.7.3", "@types/inquirer": "^9.0.0", "@types/prettier": "^3.0.0"}, "engines": {"node": ">=20.0.0"}}
1
+ {
2
+ "name": "moyan-mfw-cli",
3
+ "version": "0.1.1",
4
+ "description": "MFW framework CLI — extension scaffolding, validation, and publishing tools",
5
+ "type": "module",
6
+ "bin": {
7
+ "mfw": "./bin/mfw.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "bin/",
18
+ "dist/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "private": false,
23
+ "scripts": {
24
+ "dev": "tsx watch src/index.ts",
25
+ "copy-templates": "node --input-type=module -e \"import fs from 'node:fs'; fs.cpSync('src/templates','dist/templates',{recursive:true})\"",
26
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean && pnpm copy-templates",
27
+ "typecheck": "tsc --noEmit",
28
+ "release:patch": "npm version patch && pnpm build && npm publish",
29
+ "release:minor": "npm version minor && pnpm build && npm publish",
30
+ "release:major": "npm version major && pnpm build && npm publish"
31
+ },
32
+ "dependencies": {
33
+ "commander": "^12.0.0",
34
+ "inquirer": "^9.2.0",
35
+ "chalk": "^5.3.0",
36
+ "handlebars": "^4.7.8",
37
+ "semver": "^7.6.0",
38
+ "glob": "^10.3.0",
39
+ "execa": "^9.3.0",
40
+ "prettier": "^3.4.0"
41
+ },
42
+ "devDependencies": {
43
+ "tsup": "^8.0.0",
44
+ "tsx": "^4.7.0",
45
+ "typescript": "^5.7.3",
46
+ "@types/inquirer": "^9.0.0",
47
+ "@types/prettier": "^3.0.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=20.0.0"
51
+ }
52
+ }