moyan-mfw-cli 0.1.0 → 0.1.2
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/README.md +12 -61
- package/dist/index.cjs +35 -16
- package/dist/index.js +34 -14
- package/dist/templates/business/backend/.env.hbs +17 -0
- package/dist/templates/business/backend/nest-cli.json.hbs +10 -0
- package/dist/templates/business/backend/package.json.hbs +37 -0
- package/dist/templates/business/backend/src/app-types.config.ts.hbs +3 -0
- package/dist/templates/business/backend/src/app.modules.ts.hbs +7 -0
- package/dist/templates/business/backend/src/main.ts.hbs +46 -0
- package/dist/templates/business/backend/src/permissions.ts.hbs +4 -0
- package/dist/templates/business/backend/tsconfig.json.hbs +18 -0
- package/dist/templates/business/frontend/index.html.hbs +35 -0
- package/dist/templates/business/frontend/package.json.hbs +32 -0
- package/dist/templates/business/frontend/src/main.ts.hbs +28 -0
- package/dist/templates/business/frontend/src/permissions.ts.hbs +4 -0
- package/dist/templates/business/frontend/src/router.ts.hbs +9 -0
- package/dist/templates/business/frontend/src/views/dashboard/Index.vue.hbs +93 -0
- package/dist/templates/business/frontend/src/views/dashboard/index.ts.hbs +11 -0
- package/dist/templates/business/frontend/tsconfig.json.hbs +19 -0
- package/dist/templates/business/frontend/vite.config.ts.hbs +27 -0
- package/dist/templates/business/package.json.hbs +17 -0
- package/dist/templates/business/pnpm-workspace.yaml.hbs +35 -0
- package/dist/templates/business/shared/package.json.hbs +34 -0
- package/dist/templates/business/shared/src/index.ts.hbs +1 -0
- package/dist/templates/business/shared/src/permissions.ts.hbs +3 -0
- package/dist/templates/business/shared/tsconfig.cjs.json.hbs +13 -0
- package/dist/templates/business/shared/tsconfig.json.hbs +13 -0
- package/dist/templates/business/tsconfig.base.json.hbs +12 -0
- package/dist/templates/extension/README.md.hbs +27 -0
- package/dist/templates/extension/backend/.env.hbs +10 -0
- package/dist/templates/extension/backend/nest-cli.json.hbs +1 -0
- package/dist/templates/extension/backend/package.json.hbs +1 -0
- package/dist/templates/extension/backend/src/controller/.gitkeep +0 -0
- package/dist/templates/extension/backend/src/dto/.gitkeep +0 -0
- package/dist/templates/extension/backend/src/entities/.gitkeep +0 -0
- package/dist/templates/extension/backend/src/index.ts.hbs +1 -0
- package/dist/templates/extension/backend/src/main.ts.hbs +34 -0
- package/dist/templates/extension/backend/src/service/.gitkeep +0 -0
- package/dist/templates/extension/backend/src/{{name}}.module.ts.hbs +4 -0
- package/dist/templates/extension/backend/tsconfig.json.hbs +1 -0
- package/dist/templates/extension/frontend/api.build.cjs.hbs +8 -0
- package/dist/templates/extension/frontend/index.html.hbs +12 -0
- package/dist/templates/extension/frontend/package.json.hbs +1 -0
- package/dist/templates/extension/frontend/src/apis/.gitkeep +0 -0
- package/dist/templates/extension/frontend/src/components/.gitkeep +0 -0
- package/dist/templates/extension/frontend/src/env.d.ts.hbs +1 -0
- package/dist/templates/extension/frontend/src/index.ts.hbs +13 -0
- package/dist/templates/extension/frontend/src/main.ts.hbs +13 -0
- package/dist/templates/extension/frontend/src/views/.gitkeep +0 -0
- package/dist/templates/extension/frontend/tsconfig.json.hbs +1 -0
- package/dist/templates/extension/frontend/vite.config.mts.hbs +30 -0
- package/dist/templates/extension/package.json.hbs +1 -0
- package/dist/templates/extension/shared/package.json.hbs +1 -0
- package/dist/templates/extension/shared/src/constants.ts.hbs +12 -0
- package/dist/templates/extension/shared/src/dict.ts.hbs +8 -0
- package/dist/templates/extension/shared/src/index.ts.hbs +10 -0
- package/dist/templates/extension/shared/src/paths.ts.hbs +5 -0
- package/dist/templates/extension/shared/src/permission-values.ts.hbs +3 -0
- package/dist/templates/extension/shared/src/types.ts.hbs +5 -0
- package/dist/templates/extension/shared/tsconfig.json.hbs +1 -0
- package/dist/templates/extension/tsconfig.json.hbs +1 -0
- 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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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 =
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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 =
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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 =
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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 =
|
|
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,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}-backend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"prebuild": "pnpm --filter {{name}}-shared 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": "^1.0.0",
|
|
27
|
+
"moyan-mfw-extension-ad": "^0.1.0",
|
|
28
|
+
"{{name}}-shared": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@nestjs/cli": "catalog:",
|
|
32
|
+
"@nestjs/schematics": "catalog:",
|
|
33
|
+
"typescript": "catalog:",
|
|
34
|
+
"ts-node": "catalog:",
|
|
35
|
+
"tsconfig-paths": "catalog:"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
import { AdModule, AD_EXTENSION_PERMISSION_VALUES } from 'moyan-mfw-extension-ad/backend'
|
|
6
|
+
|
|
7
|
+
const swaggerGroups: SwaggerGroupConfig[] = [
|
|
8
|
+
{
|
|
9
|
+
name: 'ad-extension',
|
|
10
|
+
title: '广告管理API文档',
|
|
11
|
+
description: '广告位类型、广告位、广告内容管理 API',
|
|
12
|
+
include: [AdModule],
|
|
13
|
+
},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
async function bootstrap() {
|
|
17
|
+
const startTime = Date.now()
|
|
18
|
+
console.log('[{{displayName}}] ====== Bootstrap Start ======')
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const app = await createBaseBackendApp({
|
|
22
|
+
name: '{{displayName}}',
|
|
23
|
+
appTypes: appTypesConfig,
|
|
24
|
+
syncAppTypes: true,
|
|
25
|
+
modules: [AppModule],
|
|
26
|
+
swagger: swaggerGroups,
|
|
27
|
+
permissionValues: [...AD_EXTENSION_PERMISSION_VALUES],
|
|
28
|
+
hooks: {
|
|
29
|
+
onAppInit: async (ctx) => {
|
|
30
|
+
console.log('[{{displayName}}] 应用初始化完成')
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const port = Number(process.env.PORT) || {{port}}
|
|
36
|
+
await app.listen(port)
|
|
37
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2)
|
|
38
|
+
console.log(`\n[{{displayName}}] ✅ Server listening on http://localhost:${port} (${elapsed}s)`)
|
|
39
|
+
console.log(`[{{displayName}}] 📖 Swagger: http://localhost:${port}/api-docs/sys`)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('\n[{{displayName}}] ❌ Bootstrap failed:', error)
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
bootstrap()
|
|
@@ -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,32 @@
|
|
|
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": "^1.0.0",
|
|
21
|
+
"moyan-mfw-extension-ad": "^0.1.0",
|
|
22
|
+
"{{name}}-shared": "workspace:*"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"vite": "catalog:",
|
|
26
|
+
"@vitejs/plugin-vue": "catalog:",
|
|
27
|
+
"@vitejs/plugin-vue-jsx": "catalog:",
|
|
28
|
+
"vue-tsc": "catalog:",
|
|
29
|
+
"sass": "catalog:",
|
|
30
|
+
"typescript": "catalog:"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createBaseAdminApp, registerPermissionValues } from 'moyan-mfw-base/frontend'
|
|
2
|
+
import { businessRoutes } from './router'
|
|
3
|
+
import { adRoutes } from 'moyan-mfw-extension-ad/frontend'
|
|
4
|
+
import { AD_EXTENSION_PERMISSION_VALUES } from 'moyan-mfw-extension-ad/shared'
|
|
5
|
+
import './permissions'
|
|
6
|
+
|
|
7
|
+
registerPermissionValues([...AD_EXTENSION_PERMISSION_VALUES])
|
|
8
|
+
|
|
9
|
+
const admin = createBaseAdminApp({
|
|
10
|
+
title: '{{displayName}}',
|
|
11
|
+
routes: [...businessRoutes, ...adRoutes],
|
|
12
|
+
layout: {
|
|
13
|
+
layoutMode: 'dual',
|
|
14
|
+
showTabs: true,
|
|
15
|
+
colorMode: 'system',
|
|
16
|
+
themePackage: 'default',
|
|
17
|
+
},
|
|
18
|
+
navigation: {
|
|
19
|
+
brandName: '{{displayName}}',
|
|
20
|
+
brandTagline: '{{description}}',
|
|
21
|
+
homePath: '/dashboard',
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const values = await admin.fetchPermissionValues()
|
|
26
|
+
admin.initPermissionCache(values)
|
|
27
|
+
|
|
28
|
+
await admin.mount('#app')
|
|
@@ -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 })
|