@farris/cli 2.3.8 → 2.3.11

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 (113) hide show
  1. package/lib/commands/add-app.js +5 -5
  2. package/lib/commands/add-lib.js +5 -5
  3. package/lib/commands/add-micro.js +11 -11
  4. package/lib/commands/add-project.js +196 -196
  5. package/lib/commands/add-project.js.map +1 -1
  6. package/lib/commands/build-components.js +60 -60
  7. package/lib/commands/build-components.js.map +1 -1
  8. package/lib/commands/build-css.js +38 -38
  9. package/lib/commands/build-css.js.map +1 -1
  10. package/lib/commands/build-lib.js +30 -30
  11. package/lib/commands/build.js +19 -19
  12. package/lib/commands/build.js.map +1 -1
  13. package/lib/commands/create-app-options.js +85 -85
  14. package/lib/commands/create-app-options.js.map +1 -1
  15. package/lib/commands/create-app.js +131 -131
  16. package/lib/commands/create-app.js.map +1 -1
  17. package/lib/commands/create-options.js +69 -58
  18. package/lib/commands/create-options.js.map +1 -1
  19. package/lib/commands/create.js +345 -341
  20. package/lib/commands/create.js.map +1 -1
  21. package/lib/commands/dev-serve.js +18 -18
  22. package/lib/commands/extract-i18n.js +74 -74
  23. package/lib/commands/extract-i18n.js.map +1 -1
  24. package/lib/commands/generate/api.js +124 -124
  25. package/lib/commands/generate/api.js.map +1 -1
  26. package/lib/commands/generate/common.js +101 -101
  27. package/lib/commands/generate/common.js.map +1 -1
  28. package/lib/commands/generate/component.js +156 -156
  29. package/lib/commands/generate/component.js.map +1 -1
  30. package/lib/commands/generate/view.js +168 -168
  31. package/lib/commands/generate/view.js.map +1 -1
  32. package/lib/commands/preview-serve.js +16 -16
  33. package/lib/commands/workspace/add-app.js +15 -15
  34. package/lib/commands/workspace/add-app.js.map +1 -1
  35. package/lib/commands/workspace/create.js +62 -62
  36. package/lib/commands/workspace/create.js.map +1 -1
  37. package/lib/commands/workspace/inspect.js +30 -30
  38. package/lib/commands/workspace/inspect.js.map +1 -1
  39. package/lib/commands/workspace/verify.js +38 -38
  40. package/lib/commands/workspace/verify.js.map +1 -1
  41. package/lib/common/agents-doc.js +33 -33
  42. package/lib/common/agents-doc.js.map +1 -1
  43. package/lib/common/apply-catalog-deps.js +15 -15
  44. package/lib/common/apply-catalog-deps.js.map +1 -1
  45. package/lib/common/component-file-name.js +3 -3
  46. package/lib/common/constant.js +85 -84
  47. package/lib/common/constant.js.map +1 -1
  48. package/lib/common/create-app-target.js +46 -46
  49. package/lib/common/create-app-target.js.map +1 -1
  50. package/lib/common/generate-app.js +106 -106
  51. package/lib/common/generate-app.js.map +1 -1
  52. package/lib/common/get-dependencies.js +9 -9
  53. package/lib/common/get-farris-config.js +25 -25
  54. package/lib/common/get-farris-config.js.map +1 -1
  55. package/lib/common/get-version.js +6 -6
  56. package/lib/common/get-vite-config.js +192 -192
  57. package/lib/common/get-vite-config.js.map +1 -1
  58. package/lib/common/lib-package-exports.js +24 -24
  59. package/lib/common/lib-package-exports.js.map +1 -1
  60. package/lib/common/list-lib-components.js +23 -23
  61. package/lib/common/list-lib-components.js.map +1 -1
  62. package/lib/common/output.js +164 -164
  63. package/lib/common/output.js.map +1 -1
  64. package/lib/common/project-layout.js +155 -155
  65. package/lib/common/project-layout.js.map +1 -1
  66. package/lib/common/safety.js +172 -172
  67. package/lib/common/safety.js.map +1 -1
  68. package/lib/common/template-manifest.js +18 -18
  69. package/lib/common/template-manifest.js.map +1 -1
  70. package/lib/common/template-renderer.js +114 -114
  71. package/lib/common/template-renderer.js.map +1 -1
  72. package/lib/common/transaction.js +83 -83
  73. package/lib/common/transaction.js.map +1 -1
  74. package/lib/common/verifier.js +187 -187
  75. package/lib/common/verifier.js.map +1 -1
  76. package/lib/common/workspace-contract.js +195 -191
  77. package/lib/common/workspace-contract.js.map +1 -1
  78. package/lib/config/vite-app.js +13 -13
  79. package/lib/config/vite-common.js +20 -20
  80. package/lib/config/vite-common.js.map +1 -1
  81. package/lib/config/vite-component.js +32 -32
  82. package/lib/config/vite-component.js.map +1 -1
  83. package/lib/config/vite-lib.js +26 -26
  84. package/lib/index.js +280 -261
  85. package/lib/index.js.map +1 -1
  86. package/lib/plugins/create-app-package-json.js +23 -23
  87. package/lib/plugins/create-component-style.js +44 -44
  88. package/lib/plugins/create-package-json.js +48 -48
  89. package/lib/plugins/create-package-json.js.map +1 -1
  90. package/lib/plugins/dts.js +8 -8
  91. package/lib/plugins/html-system.js +9 -9
  92. package/lib/plugins/mock.js +240 -292
  93. package/lib/plugins/mock.js.map +1 -1
  94. package/lib/plugins/msw-adapter.js +141 -0
  95. package/lib/plugins/msw-adapter.js.map +1 -0
  96. package/lib/plugins/replace.js +17 -17
  97. package/lib/plugins/systemjs-bundle.js +15 -15
  98. package/lib/plugins/systemjs-bundle.js.map +1 -1
  99. package/package.json +93 -87
  100. package/templates/create/AGENTS.md +4 -3
  101. package/templates/lib/.gitignore.tpl +1 -0
  102. package/templates/micro/.gitignore.tpl +1 -0
  103. package/templates/micro/mock/README.md +39 -26
  104. package/templates/micro/mock/tsconfig.json +10 -0
  105. package/templates/micro/package.json +1 -0
  106. package/templates/mobile/.gitignore.tpl +1 -0
  107. package/templates/mobile/mock/README.md +39 -26
  108. package/templates/mobile/mock/tsconfig.json +10 -0
  109. package/templates/mobile/package.json +1 -0
  110. package/templates/web/.gitignore.tpl +1 -0
  111. package/templates/web/mock/README.md +39 -26
  112. package/templates/web/mock/tsconfig.json +10 -0
  113. package/templates/web/package.json +1 -0
@@ -0,0 +1,141 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { join } from 'node:path';
4
+ import { Readable } from 'node:stream';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { randomUUID } from 'node:crypto';
8
+ /** @farris/cli 目前适配的 msw 主版本(应用的 msw 由模板声明、随工作区 catalog 统一) */
9
+ const SUPPORTED_MAJOR = 2;
10
+ const toPosix = (p) => p.split('\\').join('/');
11
+ /** 路径比较统一按 posix + 小写(Windows 下 server 侧给的是 C:/...,本地 join 给的是 C:\...) */
12
+ const normalize = (p) => toPosix(p).toLowerCase();
13
+ // handleRequest 的 emitter 参数只用于生命周期事件通知,dev 期无需监听;
14
+ // msw 未从包根导出该类型,按参数位置断言,避免额外依赖 strict-event-emitter
15
+ const noopEmitter = { emit() { } };
16
+ const sharedOptions = { onUnhandledRequest: () => null };
17
+ const loadingRuntimes = new Map();
18
+ async function resolveAppMsw(appRoot) {
19
+ const require = createRequire(join(appRoot, 'package.json'));
20
+ let entry;
21
+ let pkgPath;
22
+ try {
23
+ // 先按包名解析:pnpm 下命中应用自己的 node_modules,npm 下命中提升后的 node_modules
24
+ pkgPath = require.resolve('msw/package.json');
25
+ entry = require.resolve('msw');
26
+ }
27
+ catch (err) {
28
+ throw new Error(`未能在应用目录解析到 msw(${appRoot}):请先在应用工程内安装依赖(msw 已由应用模板放入 devDependencies)。` +
29
+ `若工程由旧版脚手架创建,请升级 @farris/cli 或手动执行 pnpm add -D msw@^2。原因:` +
30
+ `${err instanceof Error ? err.message : String(err)}`);
31
+ }
32
+ const version = String(JSON.parse(readFileSync(pkgPath, 'utf8')).version ?? '');
33
+ const major = Number.parseInt(version.split('.')[0] ?? '', 10);
34
+ if (major !== SUPPORTED_MAJOR) {
35
+ throw new Error(`应用安装的 msw 版本为 ${version},@farris/cli 目前只适配 ${SUPPORTED_MAJOR}.x:` +
36
+ `请把应用里的 msw 对齐到 ${SUPPORTED_MAJOR}.x(工作区 catalog 已统一版本)`);
37
+ }
38
+ const mod = (await import(pathToFileURL(entry).href));
39
+ const handleRequest = mod.handleRequest ?? mod.default?.handleRequest;
40
+ if (typeof handleRequest !== 'function') {
41
+ throw new Error(`msw ${version} 的入口未导出 handleRequest(${entry}),无法执行 mock handler`);
42
+ }
43
+ return { handleRequest, version };
44
+ }
45
+ /**
46
+ * 从应用目录解析并加载 msw(应用 devDependencies 里的那一份)。
47
+ * 同一应用目录只加载一次;加载失败不缓存,装好依赖后重试即可。
48
+ */
49
+ export function loadAppMsw(appRoot) {
50
+ const cached = loadingRuntimes.get(appRoot);
51
+ if (cached) {
52
+ return cached;
53
+ }
54
+ const loading = resolveAppMsw(appRoot);
55
+ loadingRuntimes.set(appRoot, loading);
56
+ loading.catch(() => loadingRuntimes.delete(appRoot));
57
+ return loading;
58
+ }
59
+ /** 读取 handler 的 info(方法/路径),用于重复路径校验 */
60
+ export function handlerInfo(handler) {
61
+ return handler.info ?? {};
62
+ }
63
+ /** 默认导出的项是否为 MSW 的 HTTP handler */
64
+ export function isRequestHandler(value) {
65
+ return !!value && typeof handlerInfo(value).method === 'string';
66
+ }
67
+ /** 是否位于指定目录内(按 posix、大小写不敏感比较,并要求完整目录边界,避免同名前缀目录误判) */
68
+ export function isInsideDir(file, dir) {
69
+ const target = normalize(file);
70
+ const base = normalize(dir).replace(/\/+$/, '');
71
+ return target === base || target.startsWith(`${base}/`);
72
+ }
73
+ /** 非流式场景下的请求体读取(按 chunk 收集) */
74
+ function readBufferedBody(req) {
75
+ return new Promise((resolve) => {
76
+ const chunks = [];
77
+ req.on('data', (chunk) => chunks.push(String(chunk)));
78
+ req.on('end', () => resolve(chunks.length > 0 ? chunks.join('') : undefined));
79
+ req.on('error', () => resolve(undefined));
80
+ });
81
+ }
82
+ /** 把 Node 请求转成 fetch Request(handleRequest 的输入) */
83
+ export async function toWebRequest(req, origin) {
84
+ const method = (req.method ?? 'GET').toUpperCase();
85
+ const hasBody = method !== 'GET' && method !== 'HEAD';
86
+ const init = {
87
+ method,
88
+ headers: new Headers(req.headers)
89
+ };
90
+ if (hasBody) {
91
+ // 真实 dev server 下 req 是可读流,直接流式交给 fetch;测试替身等场景退化为缓冲读取
92
+ init.duplex = 'half';
93
+ init.body =
94
+ req.readable && typeof Readable.toWeb === 'function'
95
+ ? Readable.toWeb(req)
96
+ : await readBufferedBody(req);
97
+ }
98
+ return new Request(new URL(req.url ?? '/', origin), init);
99
+ }
100
+ /** 把 fetch Response 流式写回 Node 响应 */
101
+ async function writeResponse(res, response) {
102
+ res.statusCode = response.status;
103
+ if (response.statusText) {
104
+ res.statusMessage = response.statusText;
105
+ }
106
+ response.headers.forEach((value, name) => {
107
+ // appendHeader 才符合多值头(如 set-cookie)的语义,Node 18.3 起可用
108
+ if (typeof res.appendHeader === 'function') {
109
+ res.appendHeader(name, value);
110
+ }
111
+ else {
112
+ res.setHeader(name, value);
113
+ }
114
+ });
115
+ if (response.body) {
116
+ try {
117
+ await pipeline(Readable.fromWeb(response.body), res);
118
+ }
119
+ catch {
120
+ // 客户端提前断开或响应流异常:连接已不可用,回收即可,不向上抛
121
+ res.destroy();
122
+ }
123
+ return;
124
+ }
125
+ res.end();
126
+ }
127
+ /**
128
+ * 用应用侧的 msw 处理一次请求。
129
+ * 命中 mock 时写回响应并返回状态码;未命中返回 null,由调用方放行给代理或 404 守卫。
130
+ */
131
+ export async function runMockHandlers(req, res, handlers, runtime) {
132
+ const origin = `http://${req.headers.host ?? 'localhost'}`;
133
+ const request = await toWebRequest(req, origin);
134
+ const response = await runtime.handleRequest(request, randomUUID(), handlers, sharedOptions, noopEmitter, { resolutionContext: { baseUrl: origin } });
135
+ if (!response) {
136
+ return null;
137
+ }
138
+ await writeResponse(res, response);
139
+ return response.status;
140
+ }
141
+ //# sourceMappingURL=msw-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msw-adapter.js","sourceRoot":"","sources":["../../src/plugins/msw-adapter.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,18 @@
1
- import { getPackageName } from '../common/constant.js';
2
- export function replace(path) {
3
- return {
4
- name: 'farris-replace-path',
5
- renderChunk(code, chunk) {
6
- const fileNames = chunk.fileName.split('.');
7
- const format = fileNames[fileNames.length - 2];
8
- // const regExpress = new RegExp(/@farris\/mobile-ui-vue\/([\w-]+)/g);
9
- const dynamicRegExp = new RegExp(`${getPackageName().replace(/\//g, '\\/')}\\/([\\w-]+)`, // 自动转义斜杠
10
- 'g');
11
- return code.replace(dynamicRegExp, (match, captureGroup) => {
12
- return path(format, [captureGroup]);
13
- });
14
- }
15
- };
16
- }
17
- ;
1
+ import { getPackageName } from '../common/constant.js';
2
+ export function replace(path) {
3
+ return {
4
+ name: 'farris-replace-path',
5
+ renderChunk(code, chunk) {
6
+ const fileNames = chunk.fileName.split('.');
7
+ const format = fileNames[fileNames.length - 2];
8
+ // const regExpress = new RegExp(/@farris\/mobile-ui-vue\/([\w-]+)/g);
9
+ const dynamicRegExp = new RegExp(`${getPackageName().replace(/\//g, '\\/')}\\/([\\w-]+)`, // 自动转义斜杠
10
+ 'g');
11
+ return code.replace(dynamicRegExp, (match, captureGroup) => {
12
+ return path(format, [captureGroup]);
13
+ });
14
+ }
15
+ };
16
+ }
17
+ ;
18
18
  //# sourceMappingURL=replace.js.map
@@ -1,16 +1,16 @@
1
- export function systemjsBundle() {
2
- return {
3
- name: 'systemjs-bundle',
4
- outputOptions(options) {
5
- if (options.format === 'systemjs') {
6
- return {
7
- ...options,
8
- name: undefined
9
- };
10
- }
11
- return options;
12
- }
13
- };
14
- }
15
- ;
1
+ export function systemjsBundle() {
2
+ return {
3
+ name: 'systemjs-bundle',
4
+ outputOptions(options) {
5
+ if (options.format === 'systemjs') {
6
+ return {
7
+ ...options,
8
+ name: undefined
9
+ };
10
+ }
11
+ return options;
12
+ }
13
+ };
14
+ }
15
+ ;
16
16
  //# sourceMappingURL=systemjs-bundle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"systemjs-bundle.js","sourceRoot":"","sources":["../../src/plugins/systemjs-bundle.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,aAAa,CAAC,OAAY;YACxB,IAAG,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE;gBAChC,OAAO;oBACL,GAAG,OAAO;oBACV,IAAI,EAAE,SAAS;iBAChB,CAAC;aACH;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAAA,CAAC"}
1
+ {"version":3,"file":"systemjs-bundle.js","sourceRoot":"","sources":["../../src/plugins/systemjs-bundle.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,aAAa,CAAC,OAAY;YACxB,IAAG,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACjC,OAAO;oBACL,GAAG,OAAO;oBACV,IAAI,EAAE,SAAS;iBAChB,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAAA,CAAC"}
package/package.json CHANGED
@@ -1,87 +1,93 @@
1
- {
2
- "name": "@farris/cli",
3
- "private": false,
4
- "version": "2.3.8",
5
- "type": "module",
6
- "typings": "lib/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "types": "./lib/commands/create.js",
10
- "import": "./lib/commands/create.js"
11
- }
12
- },
13
- "bin": {
14
- "farris-cli": "./bin/index.js"
15
- },
16
- "engines": {
17
- "node": "^18.0.0 || >=20.0.0",
18
- "pnpm": ">=9.0.0"
19
- },
20
- "scripts": {
21
- "build": "rimraf ./lib && tsc",
22
- "test": "vitest run --config vitest.config.ts"
23
- },
24
- "files": [
25
- "lib",
26
- "templates",
27
- "bin"
28
- ],
29
- "license": "MIT",
30
- "dependencies": {
31
- "commander": "^9.4.0",
32
- "glob": "^10.4.5",
33
- "inquirer": "^9.3.0",
34
- "ora": "^8.0.1"
35
- },
36
- "peerDependencies": {
37
- "@vitejs/plugin-vue": "^5.2.4",
38
- "@vitejs/plugin-vue-jsx": "^3.0.0",
39
- "esbuild": "^0.23.0",
40
- "esbuild-sass-plugin": "^3.3.1",
41
- "typescript": "^5.7.3",
42
- "vite": "^5.4.19",
43
- "vite-plugin-css-injected-by-js": "^3.5.2",
44
- "vite-plugin-dts": "^3.9.1"
45
- },
46
- "peerDependenciesMeta": {
47
- "@vitejs/plugin-vue": {
48
- "optional": true
49
- },
50
- "@vitejs/plugin-vue-jsx": {
51
- "optional": true
52
- },
53
- "esbuild": {
54
- "optional": true
55
- },
56
- "esbuild-sass-plugin": {
57
- "optional": true
58
- },
59
- "typescript": {
60
- "optional": true
61
- },
62
- "vite": {
63
- "optional": true
64
- },
65
- "vite-plugin-css-injected-by-js": {
66
- "optional": true
67
- },
68
- "vite-plugin-dts": {
69
- "optional": true
70
- }
71
- },
72
- "devDependencies": {
73
- "@types/inquirer": "^9.0.7",
74
- "@vitejs/plugin-vue": "^5.2.4",
75
- "@vitejs/plugin-vue-jsx": "^3.0.0",
76
- "esbuild": "^0.23.0",
77
- "esbuild-sass-plugin": "^3.3.1",
78
- "rimraf": "^5.0.7",
79
- "sass": "^1.90.0",
80
- "typescript": "^5.7.3",
81
- "vite": "^5.4.19",
82
- "vite-plugin-css-injected-by-js": "^3.5.2",
83
- "vite-plugin-dts": "^3.9.1",
84
- "vitest": "^1.6.1",
85
- "vue": "^3.5.13"
86
- }
87
- }
1
+ {
2
+ "name": "@farris/cli",
3
+ "private": false,
4
+ "version": "2.3.11",
5
+ "type": "module",
6
+ "typings": "lib/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./lib/commands/create.js",
10
+ "import": "./lib/commands/create.js"
11
+ }
12
+ },
13
+ "bin": {
14
+ "farris-cli": "./bin/index.js"
15
+ },
16
+ "engines": {
17
+ "node": "^18.0.0 || >=20.0.0",
18
+ "pnpm": ">=9.0.0"
19
+ },
20
+ "scripts": {
21
+ "build": "rimraf ./lib && tsc",
22
+ "prepublishOnly": "npm run build",
23
+ "test": "vitest run --config vitest.config.ts"
24
+ },
25
+ "files": [
26
+ "lib",
27
+ "templates",
28
+ "bin"
29
+ ],
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "commander": "^9.4.0",
33
+ "glob": "^10.4.5",
34
+ "inquirer": "^9.3.0",
35
+ "ora": "^8.0.1"
36
+ },
37
+ "peerDependencies": {
38
+ "@vitejs/plugin-vue": "^5.2.4",
39
+ "@vitejs/plugin-vue-jsx": "^3.0.0",
40
+ "esbuild": "^0.23.0",
41
+ "esbuild-sass-plugin": "^3.3.1",
42
+ "msw": "^2.15.0",
43
+ "typescript": "^5.7.3",
44
+ "vite": "^5.4.19",
45
+ "vite-plugin-css-injected-by-js": "^3.5.2",
46
+ "vite-plugin-dts": "^3.9.1"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "@vitejs/plugin-vue": {
50
+ "optional": true
51
+ },
52
+ "@vitejs/plugin-vue-jsx": {
53
+ "optional": true
54
+ },
55
+ "esbuild": {
56
+ "optional": true
57
+ },
58
+ "esbuild-sass-plugin": {
59
+ "optional": true
60
+ },
61
+ "msw": {
62
+ "optional": true
63
+ },
64
+ "typescript": {
65
+ "optional": true
66
+ },
67
+ "vite": {
68
+ "optional": true
69
+ },
70
+ "vite-plugin-css-injected-by-js": {
71
+ "optional": true
72
+ },
73
+ "vite-plugin-dts": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "devDependencies": {
78
+ "@types/inquirer": "^9.0.7",
79
+ "@vitejs/plugin-vue": "^5.2.4",
80
+ "@vitejs/plugin-vue-jsx": "^3.0.0",
81
+ "esbuild": "^0.23.0",
82
+ "esbuild-sass-plugin": "^3.3.1",
83
+ "msw": "^2.15.0",
84
+ "rimraf": "^5.0.7",
85
+ "sass": "^1.90.0",
86
+ "typescript": "^5.7.3",
87
+ "vite": "^5.4.19",
88
+ "vite-plugin-css-injected-by-js": "^3.5.2",
89
+ "vite-plugin-dts": "^3.9.1",
90
+ "vitest": "^1.6.1",
91
+ "vue": "^3.5.13"
92
+ }
93
+ }
@@ -52,9 +52,10 @@
52
52
 
53
53
  ### 2.5 数据来源与 Mock
54
54
 
55
- - 开发期接口数据来自工程内 `mock/` 目录(写法与行为见该目录 README);未命中的请求按 `farris.config.mjs` 的 `proxy` 转发。
56
- - 联调真实后端:在**工程目录内**执行 `pnpm dev --no-mock`,业务代码不改。
57
- - 请求路径必须与 mock 的 `url` 逐字一致;禁止在组件里散落假数据;`mock/` 不得进入构建产物。
55
+ - 开发期接口数据默认来自工程内 `mock/` 目录,用 **MSW handler** 写法(见该目录 README);未命中请求按 `farris.config.mjs` 的 `proxy` 转发。
56
+ - 联调真实后端:在**工程目录内**执行 `pnpm dev --no-mock`(或 `MOCK=none pnpm dev`),业务代码不改。
57
+ - 请求路径必须与 mock 的 `url` 逐字一致;禁止在组件里散落假数据。
58
+ - mock 不得进入构建产物:`msw` 已在应用 `devDependencies`(开发期依赖),不要在 `mock/` 之外引用它,也不要把它加到 `dependencies`。
58
59
 
59
60
  ### 2.6 工程内生成器
60
61
 
@@ -31,3 +31,4 @@ package-lock.json
31
31
  .windsurf/
32
32
  .trae/
33
33
  .opencode/
34
+ .prototypes/
@@ -31,3 +31,4 @@ package-lock.json
31
31
  .windsurf/
32
32
  .trae/
33
33
  .opencode/
34
+ .prototypes/
@@ -1,40 +1,53 @@
1
1
  # mock 目录
2
2
 
3
- 开发期接口数据。一个领域一个文件,命名 `*.mock.ts`,默认导出路由数组:
3
+ 开发期接口数据。用 MSW 写法,库本身的用法见 MSW 官方文档。一个领域一个文件,命名 `*.mock.ts`,只导出 handler 数组:
4
4
 
5
5
  ```ts
6
- export default [
7
- {
8
- url: '/api/v1/users', // 必须与代码中的请求路径逐字一致
9
- method: 'GET', // 默认 GET
10
- timeout: 300, // 可选 模拟延迟毫秒
11
- status: 200, // 可选 响应状态码
12
- response: ({ query, params, body }) => ({
13
- data: [],
14
- total: 0
15
- })
16
- }
6
+ import { http, HttpResponse, delay } from 'msw';
7
+
8
+ export const handlers = [
9
+ http.get('/api/v1/users', async ({ request }) => {
10
+ const keyword = new URL(request.url).searchParams.get('keyword') ?? '';
11
+ await delay(300); // 可选:模拟延迟,单位毫秒
12
+ return HttpResponse.json({ data: [], total: 0 }); // 形状跟客户端出口走
13
+ }),
14
+ http.get('/api/v1/users/:id', ({ params }) => HttpResponse.json({ data: { id: params.id } })),
15
+ http.post('/api/v1/users', async ({ request }) => {
16
+ const body = await request.json();
17
+ return HttpResponse.json({ id: 'u-new', ...body }, { status: 201 });
18
+ })
17
19
  ];
18
20
  ```
19
21
 
20
22
  ## 加一条 mock 并验证
21
23
 
22
- 1. 先在 `src/api/` 里确认请求路径(例如 `/api/v1/users`),逐字抄进 `url`。
23
- 2. 新建或追加 `mock/<领域>.mock.ts`,按上面的形状写 `url` / `method` / `response`。
24
+ 1. 先在 `src/api/` 里确认实际请求路径(例如 `/api/v1/users`),逐字抄进 handler 路径,前缀也要带上。
25
+ 2. 新建或追加 `mock/<领域>.mock.ts`,按上面的形状写 handler;同一前缀下字面量路径写在 `:参数名` 之前。
24
26
  3. 在工程目录执行 `pnpm dev`,打开用到该接口的页面,**验证**返回的是 mock 数据。
25
- 4. 需要联调真实后端时:`pnpm dev --no-mock`(或 `MOCK=none pnpm dev`),业务代码不改。
27
+ 4. 需要联调真实后端时执行 `pnpm dev --no-mock`(或 `MOCK=none pnpm dev`),业务代码不改。
26
28
 
27
- ## 规则
29
+ 规则:
28
30
 
29
- - url 必须与代码中的请求路径逐字一致,见 `src/api/` 下的接口模块。
30
- - 路径参数在 url 里用 `:参数名` 表示,例如 `/api/v1/users/:id`;查询参数不写进 url,在 response 的 `query` 里读取。
31
- - mock 响应体必须能被工程的 HTTP 客户端正确消费,形状跟着客户端出口走。
32
- - 同一 url 不允许在多个 mock 文件中重复定义,重复时开发服务启动即报错。
33
- - mock 只用于开发期:不进构建产物,也不要在组件里散落假数据。
31
+ - url 必须与代码实际发出的请求路径逐字一致,前缀也要带上;请求路径见 `src/api/` 下的接口模块。
32
+ - url 只写路径,以 `/` 开头,不要写协议与主机。写 `/api/v1/users`,不要写 `http://localhost:3000/api/v1/users`;主机或端口不同时不会命中。
33
+ - 路径参数在 url 里用 `:参数名` 表示,例如 `/api/v1/users/:id`。查询参数不写进 url,用 `new URL(request.url).searchParams` 读取。
34
+ - 同一前缀下同时有字面量路径与 `:参数名` 路径时,字面量写在前面。MSW 取第一个匹配的 handler,`/api/v1/users/current` 排在 `/api/v1/users/:id` 后面会被 `:id` 接走。
35
+ - 响应体必须能被工程的 HTTP 客户端正确消费,形状跟着客户端出口走。数据要有业务语义,用中文姓名、机构、单据号这类内容,不要用占位文本。
36
+ - 关键场景要模拟:空态、错误、慢响应。错误用 `HttpResponse.json(..., { status: 500 })`;慢响应用 `delay`。
37
+ - 目录里只放 handler 文件。共享数据或工具函数要放进 `_` 开头的文件,例如 `_data.ts`,加载时会跳过;其他文件会被当作 handler 文件加载,导出内容不是 handler 就会报错。
38
+ - 同一路径不允许在多个 mock 文件中重复定义,重复时开发服务启动或请求即报错。
39
+ - 文件只导出 handler 数组,写 `export const handlers = [...]` 或 `export default [...]`。不需要 `setupServer` / `setupWorker` 接线;接线由脚手架完成。
40
+ - `msw` 已由模板放进应用 `devDependencies`,版本随工作区 catalog 统一。不要把它加到 `dependencies`,也不要在 `mock/` 之外引用它,否则构建期会报错。
41
+ - mock 只用于开发期,不参与构建,也不要在组件里散落假数据。
34
42
 
35
- ## 行为与开关
43
+ 行为:
36
44
 
37
- - 命中 mock → 返回 mock 数据。
38
- - 未命中 → 按 `farris.config.mjs` 的 `proxy` 配置转发真实后端。
39
- - 未命中且未配代理的 `/api` 路径 → 返回 404 并提示,便于排查漏配。
40
- - 开关:工程目录内 `pnpm dev` 默认生效;`pnpm dev --no-mock` 或 `MOCK=none pnpm dev` 进真实后端联调。
45
+ - 命中 mock 时返回 mock 数据。
46
+ - 未命中时按 `farris.config.mjs` 的 `proxy` 配置转发真实后端。
47
+ - 未命中且未配代理的 `/api` 路径返回 404 并提示,便于排查漏配。
48
+
49
+ 开关:在工程目录执行 `pnpm dev` 默认生效;`pnpm dev --no-mock` 或 `MOCK=none pnpm dev` 进入真实后端联调。
50
+
51
+ 类型:本目录附带独立的 `tsconfig.json`,只服务于编辑器与按需类型检查。应用自身的 `typecheck` 不包含本目录,mock 也不参与构建。
52
+
53
+ 约定版本 2026-09。本文件随 `@farris/cli` 模板发布,AI 协作以本文件为准。
@@ -0,0 +1,10 @@
1
+ {
2
+ // mock 目录的独立 TS 工程:只服务于编辑器与按需类型检查(应用自身的 typecheck 不含本目录)。
3
+ // msw 是应用的开发期依赖(见 package.json 的 devDependencies),按常规模块解析即可。
4
+ "extends": "../tsconfig.json",
5
+ "compilerOptions": {
6
+ "composite": false,
7
+ "noEmit": true
8
+ },
9
+ "include": ["**/*.ts", "**/*.d.ts"]
10
+ }
@@ -35,6 +35,7 @@
35
35
  "eslint": "^8.57.0",
36
36
  "eslint-plugin-vue": "^9.23.0",
37
37
  "jsdom": "^25.0.1",
38
+ "msw": "^2.15.0",
38
39
  "prettier": "^3.2.5",
39
40
  "typescript": "~5.4.0",
40
41
  "vite": "^5.4.19",
@@ -31,3 +31,4 @@ package-lock.json
31
31
  .windsurf/
32
32
  .trae/
33
33
  .opencode/
34
+ .prototypes/
@@ -1,40 +1,53 @@
1
1
  # mock 目录
2
2
 
3
- 开发期接口数据。一个领域一个文件,命名 `*.mock.ts`,默认导出路由数组:
3
+ 开发期接口数据。用 MSW 写法,库本身的用法见 MSW 官方文档。一个领域一个文件,命名 `*.mock.ts`,只导出 handler 数组:
4
4
 
5
5
  ```ts
6
- export default [
7
- {
8
- url: '/api/v1/users', // 必须与代码中的请求路径逐字一致
9
- method: 'GET', // 默认 GET
10
- timeout: 300, // 可选 模拟延迟毫秒
11
- status: 200, // 可选 响应状态码
12
- response: ({ query, params, body }) => ({
13
- data: [],
14
- total: 0
15
- })
16
- }
6
+ import { http, HttpResponse, delay } from 'msw';
7
+
8
+ export const handlers = [
9
+ http.get('/api/v1/users', async ({ request }) => {
10
+ const keyword = new URL(request.url).searchParams.get('keyword') ?? '';
11
+ await delay(300); // 可选:模拟延迟,单位毫秒
12
+ return HttpResponse.json({ data: [], total: 0 }); // 形状跟客户端出口走
13
+ }),
14
+ http.get('/api/v1/users/:id', ({ params }) => HttpResponse.json({ data: { id: params.id } })),
15
+ http.post('/api/v1/users', async ({ request }) => {
16
+ const body = await request.json();
17
+ return HttpResponse.json({ id: 'u-new', ...body }, { status: 201 });
18
+ })
17
19
  ];
18
20
  ```
19
21
 
20
22
  ## 加一条 mock 并验证
21
23
 
22
- 1. 先在 `src/api/` 里确认请求路径(例如 `/api/v1/users`),逐字抄进 `url`。
23
- 2. 新建或追加 `mock/<领域>.mock.ts`,按上面的形状写 `url` / `method` / `response`。
24
+ 1. 先在 `src/api/` 里确认实际请求路径(例如 `/api/v1/users`),逐字抄进 handler 路径,前缀也要带上。
25
+ 2. 新建或追加 `mock/<领域>.mock.ts`,按上面的形状写 handler;同一前缀下字面量路径写在 `:参数名` 之前。
24
26
  3. 在工程目录执行 `pnpm dev`,打开用到该接口的页面,**验证**返回的是 mock 数据。
25
- 4. 需要联调真实后端时:`pnpm dev --no-mock`(或 `MOCK=none pnpm dev`),业务代码不改。
27
+ 4. 需要联调真实后端时执行 `pnpm dev --no-mock`(或 `MOCK=none pnpm dev`),业务代码不改。
26
28
 
27
- ## 规则
29
+ 规则:
28
30
 
29
- - url 必须与代码中的请求路径逐字一致,见 `src/api/` 下的接口模块。
30
- - 路径参数在 url 里用 `:参数名` 表示,例如 `/api/v1/users/:id`;查询参数不写进 url,在 response 的 `query` 里读取。
31
- - mock 响应体必须能被工程的 HTTP 客户端正确消费,形状跟着客户端出口走。
32
- - 同一 url 不允许在多个 mock 文件中重复定义,重复时开发服务启动即报错。
33
- - mock 只用于开发期:不进构建产物,也不要在组件里散落假数据。
31
+ - url 必须与代码实际发出的请求路径逐字一致,前缀也要带上;请求路径见 `src/api/` 下的接口模块。
32
+ - url 只写路径,以 `/` 开头,不要写协议与主机。写 `/api/v1/users`,不要写 `http://localhost:3000/api/v1/users`;主机或端口不同时不会命中。
33
+ - 路径参数在 url 里用 `:参数名` 表示,例如 `/api/v1/users/:id`。查询参数不写进 url,用 `new URL(request.url).searchParams` 读取。
34
+ - 同一前缀下同时有字面量路径与 `:参数名` 路径时,字面量写在前面。MSW 取第一个匹配的 handler,`/api/v1/users/current` 排在 `/api/v1/users/:id` 后面会被 `:id` 接走。
35
+ - 响应体必须能被工程的 HTTP 客户端正确消费,形状跟着客户端出口走。数据要有业务语义,用中文姓名、机构、单据号这类内容,不要用占位文本。
36
+ - 关键场景要模拟:空态、错误、慢响应。错误用 `HttpResponse.json(..., { status: 500 })`;慢响应用 `delay`。
37
+ - 目录里只放 handler 文件。共享数据或工具函数要放进 `_` 开头的文件,例如 `_data.ts`,加载时会跳过;其他文件会被当作 handler 文件加载,导出内容不是 handler 就会报错。
38
+ - 同一路径不允许在多个 mock 文件中重复定义,重复时开发服务启动或请求即报错。
39
+ - 文件只导出 handler 数组,写 `export const handlers = [...]` 或 `export default [...]`。不需要 `setupServer` / `setupWorker` 接线;接线由脚手架完成。
40
+ - `msw` 已由模板放进应用 `devDependencies`,版本随工作区 catalog 统一。不要把它加到 `dependencies`,也不要在 `mock/` 之外引用它,否则构建期会报错。
41
+ - mock 只用于开发期,不参与构建,也不要在组件里散落假数据。
34
42
 
35
- ## 行为与开关
43
+ 行为:
36
44
 
37
- - 命中 mock → 返回 mock 数据。
38
- - 未命中 → 按 `farris.config.mjs` 的 `proxy` 配置转发真实后端。
39
- - 未命中且未配代理的 `/api` 路径 → 返回 404 并提示,便于排查漏配。
40
- - 开关:工程目录内 `pnpm dev` 默认生效;`pnpm dev --no-mock` 或 `MOCK=none pnpm dev` 进真实后端联调。
45
+ - 命中 mock 时返回 mock 数据。
46
+ - 未命中时按 `farris.config.mjs` 的 `proxy` 配置转发真实后端。
47
+ - 未命中且未配代理的 `/api` 路径返回 404 并提示,便于排查漏配。
48
+
49
+ 开关:在工程目录执行 `pnpm dev` 默认生效;`pnpm dev --no-mock` 或 `MOCK=none pnpm dev` 进入真实后端联调。
50
+
51
+ 类型:本目录附带独立的 `tsconfig.json`,只服务于编辑器与按需类型检查。应用自身的 `typecheck` 不包含本目录,mock 也不参与构建。
52
+
53
+ 约定版本 2026-09。本文件随 `@farris/cli` 模板发布,AI 协作以本文件为准。