create-lumfall 1.0.1 → 1.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 (55) hide show
  1. package/README.md +24 -17
  2. package/cli.js +177 -22
  3. package/package.json +5 -7
  4. package/templates/basic/README.md +0 -113
  5. package/templates/basic/app/controller/demo.js +0 -28
  6. package/templates/basic/app/extend/README.md +0 -3
  7. package/templates/basic/app/middleware/README.md +0 -2
  8. package/templates/basic/app/middleware.js +0 -13
  9. package/templates/basic/app/pages/home/entry.home.js +0 -4
  10. package/templates/basic/app/pages/home/home.vue +0 -164
  11. package/templates/basic/app/router/demo.js +0 -17
  12. package/templates/basic/app/router-schema/demo.js +0 -29
  13. package/templates/basic/app/service/demo.js +0 -51
  14. package/templates/basic/app/webpack.config.js +0 -4
  15. package/templates/basic/build.js +0 -7
  16. package/templates/basic/config/config.beta.js +0 -4
  17. package/templates/basic/config/config.default.js +0 -32
  18. package/templates/basic/config/config.local.js +0 -4
  19. package/templates/basic/config/config.prod.js +0 -6
  20. package/templates/basic/package.json +0 -31
  21. package/templates/basic/server.js +0 -26
  22. package/templates/document/README.md +0 -58
  23. package/templates/document/app/extend/README.md +0 -6
  24. package/templates/document/app/middleware/README.md +0 -2
  25. package/templates/document/app/middleware.js +0 -2
  26. package/templates/document/app/pages/docs/assets/docs-logo.svg +0 -5
  27. package/templates/document/app/pages/docs/components/doc-layout.vue +0 -144
  28. package/templates/document/app/pages/docs/components/doc-navbar.vue +0 -103
  29. package/templates/document/app/pages/docs/components/doc-search.vue +0 -131
  30. package/templates/document/app/pages/docs/components/doc-sidebar.vue +0 -29
  31. package/templates/document/app/pages/docs/components/doc-toc.vue +0 -21
  32. package/templates/document/app/pages/docs/content.js +0 -28
  33. package/templates/document/app/pages/docs/docs-config.js +0 -68
  34. package/templates/document/app/pages/docs/docs.vue +0 -24
  35. package/templates/document/app/pages/docs/entry.docs.js +0 -26
  36. package/templates/document/app/pages/docs/markdown/highlight.js +0 -30
  37. package/templates/document/app/pages/docs/markdown/index.js +0 -129
  38. package/templates/document/app/pages/docs/search.js +0 -142
  39. package/templates/document/app/pages/docs/styles/docs.less +0 -1091
  40. package/templates/document/app/pages/docs/styles/vars.less +0 -87
  41. package/templates/document/app/pages/docs/theme.js +0 -47
  42. package/templates/document/app/pages/docs/utils.js +0 -58
  43. package/templates/document/app/pages/docs/views/doc-home.vue +0 -56
  44. package/templates/document/app/pages/docs/views/doc-page.vue +0 -147
  45. package/templates/document/app/webpack.config.js +0 -15
  46. package/templates/document/build.js +0 -6
  47. package/templates/document/config/config.beta.js +0 -2
  48. package/templates/document/config/config.default.js +0 -9
  49. package/templates/document/config/config.local.js +0 -2
  50. package/templates/document/config/config.prod.js +0 -2
  51. package/templates/document/docs/guide/example.md +0 -41
  52. package/templates/document/docs/guide/introduction.md +0 -31
  53. package/templates/document/package.json +0 -36
  54. package/templates/document/scripts/build-static.js +0 -129
  55. package/templates/document/server.js +0 -13
package/README.md CHANGED
@@ -1,24 +1,33 @@
1
1
  # create-lumfall
2
2
 
3
- [lumfall](https://www.npmjs.com/package/lumfall) 应用脚手架:一条命令生成可直接运行的
4
- 完整工程,不用安装依赖后再手动搭目录。
3
+ [lumfall](https://www.npmjs.com/package/lumfall) 应用脚手架:一条命令从 GitHub 拉取模板并生成
4
+ 可直接运行的完整工程,不用安装依赖后再手动搭目录。
5
5
 
6
6
  ```sh
7
- pnpm create lumfall my-app # 交互式选择模板
8
- pnpm create lumfall my-app -t basic # 基础业务项目
9
- pnpm create lumfall my-doc -t document # 技术文档站
7
+ pnpm create lumfall my-app # 交互式选择模板
8
+ pnpm create lumfall my-app -t basic # 基础业务项目
9
+ pnpm create lumfall my-admin -t business # B 端全栈管理台
10
+ pnpm create lumfall my-doc -t document # 技术文档站
10
11
 
11
12
  # npm / npx 同样可用
12
13
  npm create lumfall my-app
13
- npx create-lumfall my-app -t basic
14
+ npx create-lumfall my-app -t business
14
15
  ```
15
16
 
16
17
  ## 模板
17
18
 
18
- | 模板 | 说明 |
19
- | --- | --- |
20
- | `basic` | 基础业务项目:`server.js` / `build.js` / `config/` / `app/` 全套目录约定 + 示例 API 与示例页面(来自 `lumfall-basic-project`) |
21
- | `document` | 技术文档站:对标 VitePress(导航/侧栏/TOC/搜索/暗色模式),内置 lumfall 技术文档内容(来自 `lumfall-document`) |
19
+ 模板内容不在本包内维护,而是在生成时按注册表从 GitHub 拉取(模板仓库更新后
20
+ 无需重发本包):
21
+
22
+ | 模板 | 说明 | 模板仓库 |
23
+ | --- | --- | --- |
24
+ | `basic` | 基础业务项目:`server.js` / `build.js` / `config/` / `app/` 全套目录约定 + 示例 API 与示例页面 | `ikun-Lg/lumfall-basic-project@main` |
25
+ | `business` | B 端全栈管理台:电商 + 课程双系统、JWT 登录、DSL 驱动菜单(schema/group/sider/iframe 全形态)、通用 CRUD 接口,内置演示数据(admin / 123456) | `ikun-Lg/lumfall-business@master` |
26
+ | `document` | 技术文档站:对标 VitePress(导航/侧栏/TOC/搜索/暗色模式),生成空站后放入自己的内容 | `ikun-Lg/lumfall-document@template/empty` |
27
+
28
+ 拉取策略:优先 codeload `tar.gz`(无需安装 git),失败自动回退 `git clone --depth 1`
29
+ (复用 git 的代理 / 凭据配置)。`--repo <owner/name>` 可覆盖模板仓库来源,用于
30
+ fork、私有镜像或离线自建。
22
31
 
23
32
  生成后:
24
33
 
@@ -31,14 +40,12 @@ pnpm start:prod # 生产构建 + 启动
31
40
 
32
41
  脚手架会把模板里的自指名称(package.json / server.js / config 中的应用名)替换为你
33
42
  传入的项目名。模板内容与框架版本解耦:模板 `package.json` 里的 `lumfall` 用的是
34
- semver 范围,安装时取最新发布版。
43
+ semver 范围,安装时取最新发布版。lockfile 不随模板分发(安装时解析最新依赖)。
35
44
 
36
45
  ## 维护
37
46
 
38
- 模板源是本工作区同级的 `lumfall-basic-project/` 与 `lumfall-document/` 两个项目。
39
- 模板内容变更后,在本目录执行:
47
+ 模板内容在各模板仓库里维护,本包只保留注册表(`cli.js` 顶部的 `TEMPLATES`):
40
48
 
41
- ```sh
42
- pnpm sync # 重新拷贝(自动排除 node_modules / 构建产物 / logs / .git / pnpm-lock.yaml)
43
- npm publish # 发布新版
44
- ```
49
+ - 更新模板 → 直接提交推送对应仓库(分支由注册表 `ref` 指定),无需发布本包版本;
50
+ - 新增模板 → 在 `TEMPLATES` 增加一项(key / alias / name / repo / ref / selfName / hint);
51
+ - 发布本包 → `npm publish`(改动注册表或 CLI 逻辑时)。
package/cli.js CHANGED
@@ -3,36 +3,63 @@
3
3
  * create-lumfall —— lumfall 应用脚手架。
4
4
  *
5
5
  * 用法:
6
- * pnpm create lumfall [项目名] [-t basic|document]
7
- * npm create lumfall [项目名] [-t basic|document]
8
- * npx create-lumfall [项目名] [-t basic|document]
6
+ * pnpm create lumfall [项目名] [-t basic|business|document]
7
+ * npm create lumfall [项目名] [-t basic|business|document]
8
+ * npx create-lumfall [项目名] [-t basic|business|document]
9
9
  *
10
- * 零依赖:参数缺省时用 readline 交互补齐;模板内嵌在本包 templates/ 下,
11
- * 生成即可 pnpm install && pnpm start:dev,不需要手动建目录。
10
+ * 零依赖:参数缺省时用 readline 交互补齐。
11
+ *
12
+ * 模板不内置在 npm 包里,而是按注册表(TEMPLATES,见下)在生成时从 GitHub 拉取:
13
+ * 优先 codeload tar.gz(无需 git),失败自动回退 `git clone --depth 1`。
14
+ * 模板仓库更新后无需重发本包;`--repo` 可覆盖为 fork / 私有镜像。
12
15
  */
13
16
  const fs = require("fs");
14
17
  const os = require("os");
15
18
  const path = require("path");
19
+ const https = require("https");
16
20
  const readline = require("readline");
21
+ const { execFileSync } = require("child_process");
17
22
 
23
+ // ── 模板注册表:repo = GitHub owner/name,ref = 分支(可含 /,如 template/empty)──
18
24
  const TEMPLATES = [
19
25
  {
20
26
  key: "basic",
21
27
  alias: "b",
22
28
  name: "基础业务项目",
23
29
  desc: "目录约定 + 示例 API + 示例页面,一般业务从这里起步",
30
+ repo: "ikun-Lg/lumfall-basic-project",
31
+ ref: "main",
24
32
  selfName: "lumfall-basic-project",
33
+ hint: "示例页面: http://localhost:3000/view/home(示例 API 在 app/ 下按目录约定组织)",
34
+ },
35
+ {
36
+ key: "business",
37
+ alias: "biz",
38
+ name: "B 端全栈管理台(电商 + 课程双系统)",
39
+ desc: "管理台 SPA + JWT 登录 + DSL 驱动菜单 + schema CRUD,内置双业务系统演示数据",
40
+ repo: "ikun-Lg/lumfall-business",
41
+ ref: "master",
42
+ selfName: "lumfall-business",
43
+ hint: `管理台: http://localhost:3000/view/admin(默认账号 admin / 123456,普通用户 user / 123456)
44
+ 项目列表: http://localhost:3000/view/project-list(顶栏可切换电商 / 课程系统)`,
25
45
  },
26
46
  {
27
47
  key: "document",
28
48
  alias: "d",
29
49
  name: "技术文档站(空模板)",
30
50
  desc: "导航/侧栏/搜索/主题就绪,生成后放入自己的内容即可",
51
+ repo: "ikun-Lg/lumfall-document",
52
+ ref: "template/empty",
31
53
  selfName: "lumfall-document",
54
+ hint: "文档站入口: http://localhost:3000/view/docs(站点配置在 app/pages/docs/docs-config.js)",
32
55
  },
33
56
  ];
34
57
 
58
+ // 同步进生成目录时需要剔除的文件(lockfile 不随模板分发,安装时取最新依赖)
59
+ const EXCLUDED_NAMES = new Set(["pnpm-lock.yaml", ".DS_Store", "node_modules", ".git"]);
60
+
35
61
  const NAME_PATTERN = /^[a-z][a-z0-9-_]*$/i;
62
+ const REPO_PATTERN = /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9._-]+$/;
36
63
 
37
64
  function ask(rl, question) {
38
65
  return new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));
@@ -83,14 +110,24 @@ function printHelp() {
83
110
  npx create-lumfall [项目名] [选项]
84
111
 
85
112
  选项:
86
- -t, --template <basic|document> 模板类型(basic=基础业务项目, document=技术文档站)
113
+ -t, --template <${TEMPLATES.map((t) => t.key).join("|")}>
114
+ 模板类型
115
+ -r, --repo <owner/name> 覆盖模板仓库(fork / 私有镜像 / 离线自建)
87
116
  -h, --help 显示帮助
88
117
  -v, --version 显示版本
89
118
 
119
+ 模板:
120
+ ${TEMPLATES.map((t) => ` ${t.key.padEnd(10)} ${t.name} —— ${t.desc}`).join("\n")}
121
+
122
+ 说明:
123
+ 模板在生成时从 GitHub 拉取(${TEMPLATES.map((t) => t.repo).join(", ")}),
124
+ 需要网络;无 git 亦可用(优先走 tar.gz),失败自动回退 git clone。
125
+
90
126
  示例:
91
127
  pnpm create lumfall my-app # 交互式选择模板
92
- pnpm create lumfall my-app -t document # 直接生成文档站
93
- npx create-lumfall admin -t basic`);
128
+ pnpm create lumfall my-app -t business # 生成 B 端管理台
129
+ pnpm create lumfall my-doc -t document # 生成文档站
130
+ pnpm create lumfall my-app -t basic -r your-name/lumfall-basic-project`);
94
131
  }
95
132
 
96
133
  function resolveTemplate(flagValue) {
@@ -124,9 +161,108 @@ function rewriteSelfName(targetDir, template, projectName) {
124
161
  }
125
162
  }
126
163
 
164
+ // ── 模板拉取 ────────────────────────────────────────────────
165
+
166
+ /** 跟随重定向下载 URL 到本地文件(最多 5 跳),HTTP 状态非 200 时报错并附响应片段 */
167
+ function downloadFile(url, destFile, redirectsLeft = 5) {
168
+ return new Promise((resolve, reject) => {
169
+ const request = https.get(
170
+ url,
171
+ { headers: { "user-agent": "create-lumfall", accept: "*/*" }, timeout: 30000 },
172
+ (response) => {
173
+ const { statusCode, headers } = response;
174
+
175
+ if (statusCode >= 300 && statusCode < 400 && headers.location) {
176
+ response.resume();
177
+ if (redirectsLeft <= 0) {
178
+ reject(new Error(`${url} 重定向次数过多`));
179
+ return;
180
+ }
181
+ const next = new URL(headers.location, url).toString();
182
+ resolve(downloadFile(next, destFile, redirectsLeft - 1));
183
+ return;
184
+ }
185
+
186
+ if (statusCode !== 200) {
187
+ let body = "";
188
+ response.on("data", (chunk) => {
189
+ if (body.length < 200) body += chunk.toString();
190
+ });
191
+ response.on("end", () => {
192
+ reject(new Error(`HTTP ${statusCode}${body ? `:${body.trim().slice(0, 160)}` : ""}`));
193
+ });
194
+ return;
195
+ }
196
+
197
+ const file = fs.createWriteStream(destFile);
198
+ response.pipe(file);
199
+ file.on("finish", () => file.close(() => resolve(destFile)));
200
+ file.on("error", reject);
201
+ }
202
+ );
203
+ request.on("timeout", () => request.destroy(new Error("请求超时(30s)")));
204
+ request.on("error", reject);
205
+ });
206
+ }
207
+
208
+ /** 解包 tar.gz 到指定目录(依赖系统 tar:macOS/Linux/Win10+ 均自带) */
209
+ function extractTarball(tarballPath, destDir) {
210
+ fs.mkdirSync(destDir, { recursive: true });
211
+ execFileSync("tar", ["-xzf", tarballPath, "-C", destDir], { stdio: "pipe" });
212
+ }
213
+
214
+ /** 从 GitHub 拉取模板到临时目录,返回仓库内容根目录 */
215
+ async function fetchTemplate({ repo, ref, workDir }) {
216
+ // 方案一:codeload tar.gz(无需 git,包体最小)
217
+ try {
218
+ const tarball = path.join(workDir, "template.tar.gz");
219
+ const url = `https://codeload.github.com/${repo}/tar.gz/refs/heads/${ref}`;
220
+ log(` 拉取 https://github.com/${repo} (${ref}) ...`);
221
+ await downloadFile(url, tarball);
222
+ const extractDir = path.join(workDir, "extract");
223
+ extractTarball(tarball, extractDir);
224
+ // tar 包顶层是唯一目录(<repo>-<ref>),其内容才是模板根
225
+ const topLevel = fs.readdirSync(extractDir).filter((name) => name !== ".DS_Store");
226
+ if (topLevel.length !== 1) {
227
+ throw new Error(`tar 包结构异常(顶层 ${topLevel.length} 个目录)`);
228
+ }
229
+ return path.join(extractDir, topLevel[0]);
230
+ } catch (tarballError) {
231
+ log(` tar.gz 拉取失败(${tarballError.message}),回退 git clone ...`);
232
+ }
233
+
234
+ // 方案二:git clone(复用 git 的代理 / 凭据配置)
235
+ const cloneDir = path.join(workDir, "clone");
236
+ try {
237
+ execFileSync(
238
+ "git",
239
+ ["clone", "--depth", "1", "--branch", ref, `https://github.com/${repo}.git`, cloneDir],
240
+ { stdio: "pipe" }
241
+ );
242
+ return cloneDir;
243
+ } catch (gitError) {
244
+ const detail = String(gitError.stderr || gitError.message).trim().split("\n").slice(-2).join(" ");
245
+ throw new Error(
246
+ `无法获取模板仓库 ${repo}@${ref}:\n` +
247
+ ` - 请确认网络可访问 GitHub(或配置 git 代理)\n` +
248
+ ` - 请确认仓库存在且分支 "${ref}" 已推送\n` +
249
+ ` - git: ${detail}`
250
+ );
251
+ }
252
+ }
253
+
254
+ /** 把模板内容拷贝到目标目录(剔除 lockfile / 系统文件 / 版本控制目录) */
255
+ function copyTemplate(sourceRoot, targetDir) {
256
+ fs.mkdirSync(targetDir, { recursive: true });
257
+ fs.cpSync(sourceRoot, targetDir, {
258
+ recursive: true,
259
+ filter: (src) => !EXCLUDED_NAMES.has(path.basename(src)),
260
+ });
261
+ }
262
+
127
263
  async function main() {
128
264
  const argv = process.argv.slice(2);
129
- const flags = { template: null };
265
+ const flags = { template: null, repo: null };
130
266
 
131
267
  const positional = [];
132
268
  for (let i = 0; i < argv.length; i++) {
@@ -143,6 +279,10 @@ async function main() {
143
279
  flags.template = argv[++i];
144
280
  continue;
145
281
  }
282
+ if (arg === "-r" || arg === "--repo") {
283
+ flags.repo = argv[++i];
284
+ continue;
285
+ }
146
286
  if (arg.startsWith("-")) {
147
287
  throw new Error(`未知选项 "${arg}"(--help 查看用法)`);
148
288
  }
@@ -150,7 +290,9 @@ async function main() {
150
290
  }
151
291
 
152
292
  const prompt = createPrompt();
153
- try { // 1. 项目名
293
+ let workDir = null;
294
+ try {
295
+ // 1. 项目名
154
296
  let projectName = positional[0];
155
297
  if (!projectName) {
156
298
  projectName = await prompt.question("项目名称(用作目录名,如 my-app): ");
@@ -175,6 +317,15 @@ async function main() {
175
317
  }
176
318
  }
177
319
 
320
+ // 2.1 --repo 覆盖(fork / 私有镜像 / 离线自建仓库)
321
+ let repo = template.repo;
322
+ if (flags.repo) {
323
+ if (!REPO_PATTERN.test(flags.repo)) {
324
+ throw new Error(`--repo "${flags.repo}" 不合法:格式为 owner/name`);
325
+ }
326
+ repo = flags.repo;
327
+ }
328
+
178
329
  // 3. 目标目录
179
330
  const targetDir = path.resolve(process.cwd(), projectName);
180
331
  if (!(await confirmOverwrite(prompt, targetDir))) {
@@ -182,33 +333,37 @@ async function main() {
182
333
  return;
183
334
  }
184
335
 
185
- // 4. 拷贝模板 + 重写项目名
186
- const templateDir = path.join(__dirname, "templates", template.key);
187
- if (!fs.existsSync(templateDir)) {
188
- throw new Error(`模板缺失: ${template.key}(包可能未完整发布)`);
336
+ // 4. 从 GitHub 拉取模板 → 拷贝 → 重写项目名
337
+ workDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-lumfall-"));
338
+ const sourceRoot = await fetchTemplate({ repo, ref: template.ref, workDir });
339
+ copyTemplate(sourceRoot, targetDir);
340
+
341
+ if (!fs.existsSync(path.join(targetDir, "server.js"))) {
342
+ log("⚠ 仓库内容未包含 server.js,可能不是 lumfall 模板仓库,请留意产物。");
189
343
  }
190
- fs.mkdirSync(path.dirname(targetDir), { recursive: true });
191
- fs.cpSync(templateDir, targetDir, { recursive: true });
192
344
  rewriteSelfName(targetDir, template, projectName);
193
345
 
194
346
  log(`
195
347
  ✔ 已创建 ${template.name} 项目: ${projectName}
196
- 模板: ${template.key} 位置: ${path.relative(os.homedir(), targetDir) || targetDir}
348
+ 模板: ${template.key} 仓库: ${repo}@${template.ref} 位置: ${path.relative(os.homedir(), targetDir) || targetDir}
197
349
 
198
350
  下一步:
199
351
  cd ${projectName}
200
352
  pnpm install
201
353
  pnpm start:dev # 本地开发(前端 HMR + 服务)
202
354
  pnpm start:prod # 生产构建 + 启动
203
- ${template.key === "document"
204
- ? `
205
- 文档站入口: http://localhost:3000/view/docs(站点配置在 app/pages/docs/docs-config.js)`
206
- : `
207
- 示例页面: http://localhost:3000/view/home(示例 API 在 app/ 下按目录约定组织)`}
355
+ ${template.hint}
208
356
 
209
357
  详细用法见项目内 README.md。`);
210
358
  } finally {
211
359
  prompt.close();
360
+ if (workDir) {
361
+ try {
362
+ fs.rmSync(workDir, { recursive: true, force: true });
363
+ } catch (e) {
364
+ // 临时目录清理失败不影响结果
365
+ }
366
+ }
212
367
  }
213
368
  }
214
369
 
package/package.json CHANGED
@@ -1,25 +1,23 @@
1
1
  {
2
2
  "name": "create-lumfall",
3
- "version": "1.0.1",
4
- "description": "lumfall 应用脚手架:一条命令生成可运行的基础业务项目或技术文档站(pnpm create lumfall my-app)",
3
+ "version": "1.1.1",
4
+ "description": "lumfall 应用脚手架:一条命令从 GitHub 拉取并生成基础业务项目 / B 端管理台 / 技术文档站(pnpm create lumfall my-app)",
5
5
  "bin": {
6
6
  "create-lumfall": "cli.js"
7
7
  },
8
8
  "files": [
9
9
  "cli.js",
10
- "templates/",
11
10
  "README.md"
12
11
  ],
13
- "scripts": {
14
- "sync": "node scripts/sync-templates.js"
15
- },
16
12
  "keywords": [
17
13
  "lumfall",
18
14
  "create",
19
15
  "scaffold",
16
+ "template",
20
17
  "koa",
21
18
  "vue",
22
- "fullstack"
19
+ "fullstack",
20
+ "admin"
23
21
  ],
24
22
  "author": "lggbond",
25
23
  "license": "ISC",
@@ -1,113 +0,0 @@
1
- # lumfall-basic-project
2
-
3
- 基于 [lumfall](https://www.npmjs.com/package/lumfall) 的基础业务项目骨架。
4
- 克隆或复制本目录,重命名后即可开始业务开发,不必从零搭建工程。
5
-
6
- ## 目录结构
7
-
8
- ```text
9
- lumfall-basic-project/
10
- ├── server.js # 服务端入口:serviceStart()
11
- ├── build.js # 前端构建入口:frontendBuild(_ENV)
12
- ├── package.json
13
- ├── config/
14
- │ ├── config.default.js # 全环境基础配置
15
- │ ├── config.local.js # _ENV=local 覆盖(可选)
16
- │ ├── config.beta.js # _ENV=beta 覆盖(可选)
17
- │ └── config.prod.js # _ENV=prod 覆盖(可选)
18
- └── app/
19
- ├── middleware.js # 业务全局中间件注册入口
20
- ├── middleware/ # 可复用中间件 → app.middlewares.<dir>.<name>
21
- ├── controller/ # 示例:demo.js → app.controllers.demo
22
- ├── service/ # 示例:demo.js → app.services.demo
23
- ├── router/ # 路由注册(URL → controller 方法)
24
- ├── router-schema/ # API 参数 JSON Schema(Ajv 校验)
25
- ├── extend/ # 扩展点:返回值直接挂到 app 上
26
- ├── pages/
27
- │ └── home/ # 示例页面,访问 /view/home
28
- │ ├── entry.home.js # 页面入口(命名必须是 entry.<page-name>.js)
29
- │ └── home.vue
30
- └── webpack.config.js # Webpack 扩展配置(与框架配置 merge.smart 合并)
31
- ```
32
-
33
- 框架通过 npm 依赖 `lumfall` 引入,业务代码只写在本目录,不要修改
34
- `node_modules/lumfall` 里的框架本体。
35
-
36
- ## 常用命令
37
-
38
- ```sh
39
- pnpm install # 安装依赖
40
-
41
- pnpm start:dev # 本地开发:webpack dev server(HMR)+ 服务
42
- pnpm build:prod && pnpm prod # 生产构建 + 启动
43
- pnpm new-page user-list # 新建页面 app/pages/user-list/,访问 /view/user-list
44
- pnpm new-page user-list --header # 新建页面并套用框架的 HeaderContainer 布局
45
- ```
46
-
47
- 环境由 `_ENV` 区分(`local` / `beta` / `prod`,缺省 `local`),不是 `NODE_ENV`。
48
-
49
- ## 示例包含什么
50
-
51
- 骨架自带一套最小但完整的示例,覆盖最常见的开发路径:
52
-
53
- | 内容 | 位置 | 说明 |
54
- | --- | --- | --- |
55
- | 页面 | `app/pages/home/` | 访问 `/view/home`,演示 `$lumfallCurl` 调接口、Arco 组件 |
56
- | 读配置的接口 | `GET /api/demo/info` | 返回 `config` 合并结果,改 `config.<env>.js` 即可看到变化 |
57
- | 分页列表接口 | `GET /api/demo/note/list` | 演示 query 参数 + router-schema 校验 |
58
- | 创建接口 | `POST /api/demo/note` | 演示 body 参数 + 校验失败返回 code 442 |
59
-
60
- 新增一个 API 只需四步:`app/service/xxx.js` → `app/controller/xxx.js` →
61
- `app/router/xxx.js` → `app/router-schema/xxx.js`,目录与挂载约定见下表。
62
-
63
- ## 目录约定与挂载点
64
-
65
- 文件 / 目录名用 `kebab-case` 或 `snake_case`,加载后自动转 `camelCase`。
66
-
67
- | 业务目录 | 导出约定 | 挂载结果 |
68
- | --- | --- | --- |
69
- | `app/middleware/**/*.js` | `(app) => (ctx, next) => {}` | `app.middlewares.<dir>.<name>` |
70
- | `app/controller/**/*.js` | `(app) => class` | `app.controllers.<dir>.<name>`,启动时实例化 |
71
- | `app/service/**/*.js` | `(app) => class` | `app.services.<dir>.<name>`,启动时实例化 |
72
- | `app/extend/**/*.js` | `(app) => object` | 直接挂到 `app`,例如 `app.logger` |
73
- | `app/router/**/*.js` | `(app, router) => {}` | 注册路由到 `app.router` |
74
- | `app/router-schema/**/*.js` | schema 对象或 `(app) => map` | 合并进 `app.routerSchema` |
75
-
76
- controller / service 继承基类后可用:`this.services`(= `app.services`)、
77
- `this.config`(= `app.config`,仅请求阶段读取),以及统一响应
78
- `this.success(ctx, data, metadata)` / `this.fail(ctx, message, code)`。
79
-
80
- ## 配置
81
-
82
- 四层浅合并,后者覆盖前者同名键:
83
-
84
- ```text
85
- 框架 config.default -> 业务 config.default -> 框架 config.<env> -> 业务 config.<env>
86
- ```
87
-
88
- - 需要强约束时在 `server.js` 传 `configSchema`(JSON Schema),不匹配直接启动失败
89
- - 需要强校验时同样可传 `lifecycle`(启动/停止 hook)、`plugins`(数据库等前置能力)、
90
- `monitoring`(请求级观测),用法见框架文档
91
-
92
- ## 常见注意点
93
-
94
- 1. 服务与构建都必须在本目录(业务根目录 = `process.cwd()`)下执行
95
- 2. 业务代码里取业务路径用 `app.businessPath`,不要用 `__dirname`
96
- 3. `frontendBuild` 只认 `_ENV=local`(webpack dev server)和 `_ENV=prod`(产物构建)
97
- 4. 完全未命中路由会 302 到 `server.js` 里配置的 `homePath`
98
- 5. `/health/live`、`/health/ready` 是框架内置健康检查;业务依赖探针通过
99
- `app/extend/` 注册到 `app.health`
100
- 6. 生产环境建议开启 `config.security.apiSignature` 并把 `secret` 放到环境变量
101
- 7. 框架共享依赖(`vue`、`@arco-design/web-vue`、`vue-router`、`pinia`、
102
- `@babel/runtime`、`lodash`、`axios` 等)可直接 import,由框架
103
- `resolve.alias` 白名单解析(需要 lumfall ≥ 1.1.1),无需重复安装,
104
- 运行时也只有一份实例。白名单之外的库先 `pnpm add xxx` 再使用;
105
- `_` 与 `axios` 另有 webpack 全局注入,页面代码不 import 也能用
106
-
107
- ## 下一步
108
-
109
- - 需要 B 端管理台(登录、Dashboard、Schema 组件、菜单管理)时,参考同工作区的
110
- `lumfall-business/`,它演示了 `model/`(Dashboard 的 Model + Project 配置)、
111
- schema 表格/表单/搜索栏等开箱组件的用法
112
- - 需要技术文档站模板时,参考同工作区的 `lumfall-document/`
113
- - 框架完整能力(安全策略、生命周期、插件、诊断清单等)见 lumfall 技术文档
@@ -1,28 +0,0 @@
1
- // 示例 controller:演示「工厂返回 class、继承 Controller.Base」的约定。
2
- // 文件名 demo.js + 无子目录 → 挂载到 app.controllers.demo
3
- module.exports = (app) => {
4
- const BaseController = require("lumfall").Controller.Base(app);
5
-
6
- return class DemoController extends BaseController {
7
- // 通过 this.services(= app.services)在请求阶段取 service,安全
8
- async getInfo(ctx) {
9
- const { demo: demoService } = this.services;
10
- await this.success(ctx, demoService.getInfo());
11
- }
12
-
13
- async getNoteList(ctx) {
14
- const { demo: demoService } = this.services;
15
- const { data, total, page, size } = demoService.getNoteList({
16
- page: Number(ctx.request.query.page) || 1,
17
- size: Number(ctx.request.query.pageSize) || 10,
18
- });
19
- await this.success(ctx, data, { total, page, size });
20
- }
21
-
22
- async createNote(ctx) {
23
- const { demo: demoService } = this.services;
24
- const note = demoService.createNote(ctx.request.body);
25
- await this.success(ctx, note);
26
- }
27
- };
28
- };
@@ -1,3 +0,0 @@
1
- // 扩展点目录:每个文件导出 (app) => object,返回值直接挂到 app 上。
2
- // 例如文件 cache.js 导出 (app) => ({ get, set }),即可通过 app.cache 使用。
3
- // 框架已占用:app.logger、app.health、app.env 等,重名会被跳过并告警。
@@ -1,2 +0,0 @@
1
- // 可复用中间件目录:每个文件导出 (app) => (ctx, next) => {},
2
- // 自动挂载到 app.middlewares.<目录名>.<文件名>(kebab/snake-case 自动转 camelCase)。
@@ -1,13 +0,0 @@
1
- // 业务全局中间件注册入口。
2
- // 执行时机在框架全局中间件之后(static → nunjucks → bodyParser → errorHandler
3
- // → monitoring → apiParamsVerify → securityPolicy),因此这里注册的中间件
4
- // 位于框架中间件链的内层。
5
- // 可复用中间件放在 app/middleware/ 目录,会自动挂到 app.middlewares.<dir>.<name>。
6
- module.exports = (app) => {
7
- // 示例:简单请求日志(生产观测更推荐 serviceStart 的 monitoring 配置)
8
- // app.use(async (ctx, next) => {
9
- // const start = Date.now();
10
- // await next();
11
- // console.log(`${ctx.method} ${ctx.path} ${ctx.status} ${Date.now() - start}ms`);
12
- // });
13
- };
@@ -1,4 +0,0 @@
1
- import boot from "$lumfallBoot";
2
- import Home from "./home.vue";
3
-
4
- boot(Home);