@farris/cli 2.2.6 → 2.3.0

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 +6 -0
  2. package/lib/commands/add-app.js.map +1 -0
  3. package/lib/commands/add-lib.js +6 -0
  4. package/lib/commands/add-lib.js.map +1 -0
  5. package/lib/commands/add-project.js +194 -0
  6. package/lib/commands/add-project.js.map +1 -0
  7. package/lib/commands/build-components.js +8 -1
  8. package/lib/commands/build-components.js.map +1 -1
  9. package/lib/commands/build-lib.js +5 -0
  10. package/lib/commands/build-lib.js.map +1 -1
  11. package/lib/commands/build.js +7 -2
  12. package/lib/commands/build.js.map +1 -1
  13. package/lib/commands/create-app-options.js +0 -2
  14. package/lib/commands/create-app-options.js.map +1 -1
  15. package/lib/commands/create-app.js +6 -1
  16. package/lib/commands/create-app.js.map +1 -1
  17. package/lib/commands/create-options.js +3 -0
  18. package/lib/commands/create-options.js.map +1 -1
  19. package/lib/commands/create.js +95 -42
  20. package/lib/commands/create.js.map +1 -1
  21. package/lib/commands/generate/common.js +13 -12
  22. package/lib/commands/generate/common.js.map +1 -1
  23. package/lib/commands/workspace/add-app.js +10 -175
  24. package/lib/commands/workspace/add-app.js.map +1 -1
  25. package/lib/commands/workspace/create.js +29 -346
  26. package/lib/commands/workspace/create.js.map +1 -1
  27. package/lib/common/agents-doc.js +34 -0
  28. package/lib/common/agents-doc.js.map +1 -0
  29. package/lib/common/generate-app.js +48 -24
  30. package/lib/common/generate-app.js.map +1 -1
  31. package/lib/common/output.js +72 -2
  32. package/lib/common/output.js.map +1 -1
  33. package/lib/common/project-layout.js +143 -0
  34. package/lib/common/project-layout.js.map +1 -0
  35. package/lib/common/safety.js +4 -2
  36. package/lib/common/safety.js.map +1 -1
  37. package/lib/common/template-manifest.js +2 -4
  38. package/lib/common/template-manifest.js.map +1 -1
  39. package/lib/common/verifier.js +143 -99
  40. package/lib/common/verifier.js.map +1 -1
  41. package/lib/common/workspace-contract.js +86 -0
  42. package/lib/common/workspace-contract.js.map +1 -0
  43. package/lib/index.js +31 -2
  44. package/lib/index.js.map +1 -1
  45. package/lib/plugins/create-app-package-json.js +24 -0
  46. package/lib/plugins/create-app-package-json.js.map +1 -0
  47. package/package.json +53 -52
  48. package/templates/create/.gitignore.tpl +12 -0
  49. package/templates/create/AGENTS.md +62 -0
  50. package/templates/create/CLAUDE.md +1 -0
  51. package/templates/create/README.md +14 -1
  52. package/templates/create/package.json +11 -3
  53. package/templates/create/pnpm-workspace.yaml +2 -1
  54. package/templates/lib/.eslintrc.cjs +15 -0
  55. package/templates/lib/.gitignore.tpl +3 -0
  56. package/templates/lib/.prettierrc.json +8 -0
  57. package/templates/lib/index.html +1 -1
  58. package/templates/lib/package.json +38 -3
  59. package/templates/lib/{components → test/components}/button/button.spec.ts +3 -2
  60. package/templates/lib/test/components/button/button.spec.tsx +11 -0
  61. package/templates/lib/tsconfig.json +5 -1
  62. package/templates/lib/vitest.config.ts +1 -1
  63. package/templates/mobile/.gitignore.tpl +31 -0
  64. package/templates/mobile/.gitlab-ci.yml +3 -9
  65. package/templates/mobile/index.html +1 -1
  66. package/templates/mobile/package.json +14 -10
  67. package/templates/mobile/src/main.ts +5 -4
  68. package/templates/mobile/src/router/index.ts +17 -17
  69. package/templates/mobile/test/src/components/TheButton.spec.ts +15 -0
  70. package/templates/mobile/test/src/components/TheButton.spec.tsx +15 -0
  71. package/templates/mobile/tsconfig.json +3 -2
  72. package/templates/mobile/vitest.config.ts +2 -1
  73. package/templates/web/.gitignore.tpl +31 -0
  74. package/templates/web/.gitlab-ci.yml +3 -9
  75. package/templates/web/index.html +1 -1
  76. package/templates/web/package.json +14 -10
  77. package/templates/web/src/locales/index.ts +5 -0
  78. package/templates/web/src/locales/zh-CN.json +1 -0
  79. package/templates/web/src/main.ts +5 -2
  80. package/templates/web/src/router/index.ts +17 -17
  81. package/templates/web/src/styles/index.css +0 -0
  82. package/templates/web/src/styles/index.scss +0 -0
  83. package/templates/web/test/src/components/TheButton.spec.ts +15 -0
  84. package/templates/web/test/src/components/TheButton.spec.tsx +15 -0
  85. package/templates/web/tsconfig.json +3 -2
  86. package/templates/web/vitest.config.ts +2 -1
  87. package/lib/common/project-package-name.js +0 -5
  88. package/lib/common/project-package-name.js.map +0 -1
  89. package/templates/mobile/.changeset/README.md +0 -4
  90. package/templates/mobile/.changeset/config.json +0 -11
  91. package/templates/mobile/project.json +0 -4
  92. package/templates/mobile/src/components/TheButton.spec.ts +0 -8
  93. package/templates/web/.changeset/README.md +0 -4
  94. package/templates/web/.changeset/config.json +0 -11
  95. package/templates/web/project.json +0 -4
  96. package/templates/web/src/components/TheButton.spec.ts +0 -8
  97. package/templates/workspace/app/farris.config.mjs +0 -13
  98. package/templates/workspace/app/index.html +0 -12
  99. package/templates/workspace/app/package.json +0 -22
  100. package/templates/workspace/app/src/App.tsx +0 -11
  101. package/templates/workspace/app/src/main.ts +0 -12
  102. package/templates/workspace/app/src/router/index.ts +0 -16
  103. package/templates/workspace/app/src/views/home/composables/use-home.ts +0 -7
  104. package/templates/workspace/app/src/views/home/home.component.tsx +0 -15
  105. package/templates/workspace/app/src/views/home/index.ts +0 -4
  106. package/templates/workspace/app/tsconfig.json +0 -21
  107. package/templates/workspace/root/README.md +0 -28
  108. package/templates/workspace/root/package.json +0 -13
  109. package/templates/workspace/root/pnpm-workspace.yaml +0 -12
  110. /package/templates/{workspace/app → mobile}/src/locales/index.ts +0 -0
  111. /package/templates/{workspace/app → mobile}/src/locales/zh-CN.json +0 -0
  112. /package/templates/{workspace/app → mobile}/src/styles/index.css +0 -0
  113. /package/templates/{workspace/app → mobile}/src/styles/index.scss +0 -0
@@ -1,5 +1,8 @@
1
1
  import ora from 'ora';
2
+ import { resolve } from 'node:path';
2
3
  import { SCHEMA_VERSION, ExitCode } from './constant.js';
4
+ import { PROJECT_CONTAINERS } from './project-layout.js';
5
+ import { PROJECT_SCRIPT_CONTRACT, ROOT_SCRIPT_CONTRACT, cliScriptCommand, cliScriptTarget, rootScriptCommand } from './workspace-contract.js';
3
6
  let isJsonMode = false;
4
7
  export function setJsonMode(value) {
5
8
  isJsonMode = !!value;
@@ -38,7 +41,7 @@ export function logWarn(message) {
38
41
  process.stderr.write(`[WARN] ${message}\n`);
39
42
  }
40
43
  else {
41
- console.warn(`\x1b[33m${message}\x1b[0m`);
44
+ console.warn(message);
42
45
  }
43
46
  }
44
47
  export function logError(message) {
@@ -46,7 +49,7 @@ export function logError(message) {
46
49
  process.stderr.write(`[ERROR] ${message}\n`);
47
50
  }
48
51
  else {
49
- console.error(`\x1b[31m${message}\x1b[0m`);
52
+ console.error(message);
50
53
  }
51
54
  }
52
55
  /**
@@ -92,4 +95,71 @@ export function createSpinner(text) {
92
95
  const spinner = ora(text);
93
96
  return spinner;
94
97
  }
98
+ const INDENT = ' ';
99
+ const COMMAND_COLUMN_WIDTH = 30;
100
+ const DESCRIPTION_COLUMN_WIDTH = 18;
101
+ /** 粗略计算显示宽度:CJK 与全角按 2 列计,保证命令区块对齐。 */
102
+ function displayWidth(text) {
103
+ let width = 0;
104
+ for (const char of text) {
105
+ const code = char.codePointAt(0) ?? 0;
106
+ const isWide = code >= 0x1100 &&
107
+ (code <= 0x115f ||
108
+ (code >= 0x2e80 && code <= 0xa4cf) ||
109
+ (code >= 0xac00 && code <= 0xd7a3) ||
110
+ (code >= 0xf900 && code <= 0xfaff) ||
111
+ (code >= 0xfe30 && code <= 0xfe6f) ||
112
+ (code >= 0xff00 && code <= 0xff60) ||
113
+ (code >= 0xffe0 && code <= 0xffe6));
114
+ width += isWide ? 2 : 1;
115
+ }
116
+ return width;
117
+ }
118
+ function padColumns(command, description) {
119
+ const commandPad = Math.max(1, COMMAND_COLUMN_WIDTH - displayWidth(command));
120
+ const descriptionPad = Math.max(1, DESCRIPTION_COLUMN_WIDTH - displayWidth(description));
121
+ return `${command}${' '.repeat(commandPad)}# ${description}${' '.repeat(descriptionPad)}`;
122
+ }
123
+ function rootScriptLine(item) {
124
+ if (item.kind === 'cli') {
125
+ const command = cliScriptCommand(item.script, item.sampleArg);
126
+ const target = cliScriptTarget(item.containerKey, item.sampleArg);
127
+ return `${padColumns(command, item.description)}→ ${target}`;
128
+ }
129
+ return padColumns(rootScriptCommand(item.script), item.description);
130
+ }
131
+ function quoteIfNeeded(value) {
132
+ return /\s/.test(value) ? `"${value}"` : value;
133
+ }
134
+ export function printSuccess(summary) {
135
+ logInfo(`✔ ${summary}`);
136
+ }
137
+ /**
138
+ * 统一收尾提示:结果行 → 接下来 → 两个「常用命令」区块。
139
+ * 收录边界由 workspace-contract.ts 的脚本契约决定,脚本契约变化时提示随之变化。
140
+ */
141
+ export function printNextSteps(input) {
142
+ const lines = [''];
143
+ lines.push('接下来:');
144
+ lines.push('');
145
+ if (resolve(input.cwd, input.targetArg) !== resolve(input.cwd)) {
146
+ lines.push(`${INDENT}cd ${quoteIfNeeded(input.targetArg)}`);
147
+ }
148
+ if (!input.installed) {
149
+ lines.push(`${INDENT}pnpm install`);
150
+ }
151
+ if (input.appPackageName) {
152
+ lines.push(`${INDENT}pnpm --filter ${input.appPackageName} dev`);
153
+ }
154
+ lines.push('');
155
+ lines.push('| 常用命令 · 在 workspace 根目录执行');
156
+ lines.push('');
157
+ ROOT_SCRIPT_CONTRACT.forEach((item) => lines.push(`${INDENT}${rootScriptLine(item)}`));
158
+ lines.push('');
159
+ lines.push(`| 常用命令 · 在具体工程目录内执行(如 cd ${PROJECT_CONTAINERS.app}/app)`);
160
+ lines.push('');
161
+ PROJECT_SCRIPT_CONTRACT.forEach((item) => lines.push(`${INDENT}${padColumns(rootScriptCommand(item.script), item.description)}`));
162
+ lines.push('');
163
+ logInfo(lines.join('\n'));
164
+ }
95
165
  //# sourceMappingURL=output.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/common/output.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/common/output.ts"],"names":[],"mappings":"AAAA,OAAO,GAAY,MAAM,KAAK,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AASjC,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,QAA6B,EAAE,QAAmB;IACzE,MAAM,MAAM,GAAiB;QAC3B,aAAa,EAAE,cAAc;QAC7B,EAAE,EAAE,QAAQ,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;QAClD,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,SAAS;QACtC,GAAG,QAAQ;KACZ,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7D,IAAI,QAAQ,KAAK,SAAS,EAAE;QAC1B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;KACxB;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,IAAI,UAAU,EAAE;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;KAC7C;SAAM;QACL,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;KACtB;AACH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,IAAI,UAAU,EAAE;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;KAC7C;SAAM;QACL,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;KACvB;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,IAAI,UAAU,EAAE;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,OAAO,IAAI,CAAC,CAAC;KAC9C;SAAM;QACL,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;KACxB;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,IAAI,CAAC,OAKpB;IACC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC,aAAa,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IAClF,IAAI,UAAU,EAAE;QACd,QAAQ,CACN;YACE,OAAO;YACP,EAAE,EAAE,KAAK;YACT,KAAK;YACL,GAAG,KAAK;SACT,EACD,QAAQ,CACT,CAAC;KACH;SAAM;QACL,QAAQ,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;KACxB;IACD,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IAOxC,IAAI,UAAU,EAAE;QACd,OAAO;YACL,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC,CAAU,EAAE,EAAE;gBACtB,IAAI,CAAC,EAAE;oBAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;iBAAC;YACpD,CAAC;YACD,IAAI,EAAE,CAAC,CAAU,EAAE,EAAE;gBACnB,IAAI,CAAC,EAAE;oBAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;iBAAC;YACjD,CAAC;YACD,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;YACd,IAAI;SACL,CAAC;KACH;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,OAAO,OAAO,CAAC;AACjB,CAAC;AAeD,MAAM,MAAM,GAAG,KAAK,CAAC;AACrB,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAChC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAEpC,uCAAuC;AACvC,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,MAAM,GACV,IAAI,IAAI,MAAM;YACd,CAAC,IAAI,IAAI,MAAM;gBACb,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC;gBAClC,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC;gBAClC,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC;gBAClC,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC;gBAClC,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC;gBAClC,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC;QACxC,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACzB;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,WAAmB;IACtD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7E,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,wBAAwB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;IACzF,OAAO,GAAG,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;AAC5F,CAAC;AAED,SAAS,cAAc,CAAC,IAA2C;IACjE,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE;QACvB,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAClE,OAAO,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK,MAAM,EAAE,CAAC;KAC9D;IACD,OAAO,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAqB;IAClD,MAAM,KAAK,GAAa,CAAC,EAAE,CAAC,CAAC;IAC7B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;QAC9D,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,MAAM,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;KAC7D;IACD,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;QACpB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,cAAc,CAAC,CAAC;KACrC;IACD,IAAI,KAAK,CAAC,cAAc,EAAE;QACxB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,iBAAiB,KAAK,CAAC,cAAc,MAAM,CAAC,CAAC;KAClE;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IACzC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,oBAAoB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAEvF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,4BAA4B,kBAAkB,CAAC,GAAG,OAAO,CAAC,CAAC;IACtE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,uBAAuB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACvC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CACvF,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,143 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ /** 新结构容器:app → applications,lib → libraries。全仓库唯一来源。 */
4
+ export const PROJECT_CONTAINERS = { app: 'applications', lib: 'libraries' };
5
+ /** 旧结构容器:只读兼容识别,不再写入。 */
6
+ export const LEGACY_CONTAINERS = ['packages'];
7
+ export function containerFor(type) {
8
+ return PROJECT_CONTAINERS[type];
9
+ }
10
+ export function resolveProjectPath(wsRoot, type, dir) {
11
+ return join(wsRoot, containerFor(type), dir);
12
+ }
13
+ /**
14
+ * 解析 pnpm-workspace.yaml 的 packages 列表。
15
+ * 只识别 `- "dir/*"` 形式,不引入 yaml 依赖;结构超出该形式时返回空数组,由调用方回退。
16
+ */
17
+ export function parsePackagesBlock(content) {
18
+ const globs = [];
19
+ let inPackages = false;
20
+ for (const rawLine of content.split(/\r?\n/)) {
21
+ const line = rawLine.replace(/#.*$/, '');
22
+ if (/^packages\s*:\s*$/.test(line)) {
23
+ inPackages = true;
24
+ continue;
25
+ }
26
+ if (!inPackages || line.trim() === '') {
27
+ continue;
28
+ }
29
+ if (/^\S/.test(line)) {
30
+ // 回到顶层键,packages 块结束
31
+ inPackages = false;
32
+ continue;
33
+ }
34
+ const match = line.match(/^\s*-\s*["']?([^"'\s]+)["']?\s*$/);
35
+ if (match) {
36
+ globs.push(match[1]);
37
+ }
38
+ }
39
+ return globs;
40
+ }
41
+ /**
42
+ * 读出 workspace 的待扫描 glob。解析不到时回退到三个约定容器目录,并置 fallback=true。
43
+ */
44
+ export function readWorkspaceGlobs(wsRoot) {
45
+ const convention = [...LEGACY_CONTAINERS, ...Object.values(PROJECT_CONTAINERS)].map((container) => `${container}/*`);
46
+ const yamlPath = join(wsRoot, 'pnpm-workspace.yaml');
47
+ if (!existsSync(yamlPath)) {
48
+ return { globs: convention, fallback: true };
49
+ }
50
+ const parsed = parsePackagesBlock(readFileSync(yamlPath, 'utf-8'));
51
+ if (parsed.length === 0) {
52
+ return { globs: convention, fallback: true };
53
+ }
54
+ return { globs: parsed, fallback: false };
55
+ }
56
+ function detectLegacyType(projectDir) {
57
+ return existsSync(join(projectDir, 'components', 'index.ts')) ? 'lib' : 'app';
58
+ }
59
+ /**
60
+ * 扫描 workspace 内的工程:同时支持新结构(applications / libraries)与旧结构(packages)。
61
+ */
62
+ export function listProjects(wsRoot) {
63
+ const { globs } = readWorkspaceGlobs(wsRoot);
64
+ const entries = [];
65
+ const seen = new Set();
66
+ for (const glob of globs) {
67
+ const match = glob.match(/^([^/]+)\/\*$/);
68
+ if (!match) {
69
+ continue;
70
+ }
71
+ const container = match[1];
72
+ const containerDir = join(wsRoot, container);
73
+ if (!existsSync(containerDir) || !statSync(containerDir).isDirectory()) {
74
+ continue;
75
+ }
76
+ for (const name of readdirSync(containerDir).sort()) {
77
+ const projectDir = join(containerDir, name);
78
+ if (!statSync(projectDir).isDirectory()) {
79
+ continue;
80
+ }
81
+ const dir = `${container}/${name}`;
82
+ if (seen.has(dir)) {
83
+ continue;
84
+ }
85
+ seen.add(dir);
86
+ const type = container === PROJECT_CONTAINERS.app
87
+ ? 'app'
88
+ : container === PROJECT_CONTAINERS.lib
89
+ ? 'lib'
90
+ : detectLegacyType(projectDir);
91
+ entries.push({ type, dir, name });
92
+ }
93
+ }
94
+ return entries;
95
+ }
96
+ /**
97
+ * workspace 是否已采用新容器结构。verifier 用它区分「新工作空间强校验」与「旧工作空间只读兼容」。
98
+ */
99
+ export function usesNewContainers(wsRoot) {
100
+ const { globs, fallback } = readWorkspaceGlobs(wsRoot);
101
+ if (fallback) {
102
+ return false;
103
+ }
104
+ return globs.some((glob) => glob.startsWith(`${PROJECT_CONTAINERS.app}/`) || glob.startsWith(`${PROJECT_CONTAINERS.lib}/`));
105
+ }
106
+ /**
107
+ * 从包名推导目录名(去掉 scope 并转为合法 kebab-case)
108
+ */
109
+ export function deriveAppDir(name) {
110
+ const withoutScope = name.replace(/^@[^/]+\//, '');
111
+ return withoutScope
112
+ .toLowerCase()
113
+ .replace(/[^a-z0-9-_]/g, '-')
114
+ .replace(/^-+|-+$/g, '');
115
+ }
116
+ /**
117
+ * 校验 npm 包名有效性
118
+ */
119
+ export function validatePackageName(name) {
120
+ if (!name || typeof name !== 'string') {
121
+ return false;
122
+ }
123
+ return /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
124
+ }
125
+ /**
126
+ * 向上查找 Workspace 根目录(同时含 pnpm-workspace.yaml 与 package.json)。
127
+ */
128
+ export function findWorkspaceRoot(startDir = process.cwd()) {
129
+ let current = resolve(startDir);
130
+ while (true) {
131
+ if (existsSync(join(current, 'pnpm-workspace.yaml')) &&
132
+ existsSync(join(current, 'package.json'))) {
133
+ return current;
134
+ }
135
+ const parent = dirname(current);
136
+ if (parent === current) {
137
+ break;
138
+ }
139
+ current = parent;
140
+ }
141
+ return null;
142
+ }
143
+ //# sourceMappingURL=project-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-layout.js","sourceRoot":"","sources":["../../src/common/project-layout.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,8 @@ export const KNOWN_AI_AND_META_ENTRIES = new Set([
14
14
  '.github',
15
15
  '.codebuddy',
16
16
  '.trae',
17
+ '.gemini',
18
+ '.aider.conf.yml',
17
19
  'agents.md',
18
20
  'claude.md',
19
21
  'gemini.md',
@@ -32,6 +34,8 @@ export const PROJECT_EVIDENCE_FILES = [
32
34
  'farris.config.mjs',
33
35
  'tsconfig.json',
34
36
  'src',
37
+ 'applications',
38
+ 'libraries',
35
39
  'packages',
36
40
  'node_modules'
37
41
  ];
@@ -101,8 +105,6 @@ export function inspectTarget(targetPath) {
101
105
  }
102
106
  // 检查是否是现有 workspace
103
107
  const hasWorkspaceYaml = entries.includes('pnpm-workspace.yaml');
104
- const hasPackageJson = entries.includes('package.json');
105
- const hasPackagesDir = entries.includes('packages');
106
108
  if (hasWorkspaceYaml) {
107
109
  return {
108
110
  status: WorkspaceStatus.EXISTING_WORKSPACE,
@@ -1 +1 @@
1
- {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/common/safety.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAmC,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,4BAA4B;AAC5B,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IAC/C,MAAM;IACN,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,SAAS;IACT,SAAS;IACT,cAAc;IACd,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,OAAO;IACP,WAAW;IACX,WAAW;IACX,WAAW;IACX,gBAAgB;IAChB,cAAc;CACf,CAAC,CAAC;AAEH,WAAW;AACX,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,cAAc;IACd,qBAAqB;IACrB,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;IAClB,mBAAmB;IACnB,eAAe;IACf,KAAK;IACL,UAAU;IACV,cAAc;CACf,CAAC;AAYF;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACzC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;KAC1D;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;IAChD,sBAAsB;IACtB,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAC3B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;KAC5D;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,cAAc,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACtD,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE;QACzB,OAAO;YACL,MAAM,EAAE,eAAe,CAAC,cAAc;YACtC,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,KAAK;YACb,SAAS,EAAE,EAAE;YACb,qBAAqB,EAAE,EAAE;YACzB,OAAO,EAAE,cAAc,CAAC,KAAK;SAC9B,CAAC;KACH;IAED,MAAM,cAAc,GAAG,cAAc,CAAC,QAAQ,CAAC;IAE/C,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE;QAC/B,OAAO;YACL,MAAM,EAAE,eAAe,CAAC,OAAO;YAC/B,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,KAAK;YACb,SAAS,EAAE,EAAE;YACb,qBAAqB,EAAE,EAAE;SAC1B,CAAC;KACH;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE;QACvB,OAAO;YACL,MAAM,EAAE,eAAe,CAAC,cAAc;YACtC,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,CAAC,cAAc,CAAC;YAC3B,qBAAqB,EAAE,EAAE;YACzB,OAAO,EAAE,aAAa;SACvB,CAAC;KACH;IAED,MAAM,OAAO,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;IAE5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;QACxB,OAAO;YACL,MAAM,EAAE,eAAe,CAAC,KAAK;YAC7B,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,EAAE;YACb,qBAAqB,EAAE,EAAE;SAC1B,CAAC;KACH;IAED,oBAAoB;IACpB,MAAM,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IACxD,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAEpD,IAAI,gBAAgB,EAAE;QACpB,OAAO;YACL,MAAM,EAAE,eAAe,CAAC,kBAAkB;YAC1C,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,CAAC,qBAAqB,CAAC;YAClC,qBAAqB,EAAE,EAAE;SAC1B,CAAC;KACH;IAED,eAAe;IACf,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACnF,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE;QAC/B,OAAO;YACL,MAAM,EAAE,eAAe,CAAC,2BAA2B;YACnD,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,gBAAgB;YAC3B,qBAAqB,EAAE,EAAE;SAC1B,CAAC;KACH;IAED,qBAAqB;IACrB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrB,MAAM,eAAe,GAAa,EAAE,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,yBAAyB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YACxC,SAAS;SACV;QACD,SAAS,GAAG,KAAK,CAAC;QAClB,4CAA4C;QAC5C,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;KAC7B;IAED,IAAI,SAAS,EAAE;QACb,OAAO;YACL,MAAM,EAAE,eAAe,CAAC,cAAc;YACtC,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,EAAE;YACb,qBAAqB,EAAE,EAAE;SAC1B,CAAC;KACH;IAED,OAAO;QACL,MAAM,EAAE,eAAe,CAAC,oBAAoB;QAC5C,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,IAAI;QACZ,SAAS,EAAE,EAAE;QACb,qBAAqB,EAAE,eAAe;KACvC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB,EAAE,oBAA8B;IACrF,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC7C,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE;YACxB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SACzB;KACF;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/common/safety.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,13 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { getDirname } from './constant.js';
3
- const APP_REPLACE_FILES = ['package.json', 'farris.config.mjs', 'index.html', 'project.json'];
4
- const LIB_REPLACE_FILES = ['package.json', 'farris.config.mjs', 'README.md'];
5
3
  export function resolveTemplate(input) {
6
4
  if (input.type === 'lib') {
7
- return { id: 'lib', replaceFiles: [...LIB_REPLACE_FILES] };
5
+ return { id: 'lib' };
8
6
  }
9
7
  if (!input.platform) {
10
8
  throw new Error('platform is required when type is app');
11
9
  }
12
- return { id: input.platform, replaceFiles: [...APP_REPLACE_FILES] };
10
+ return { id: input.platform };
13
11
  }
14
12
  export function getTemplateDir(id) {
15
13
  return resolve(getDirname(import.meta.url), '../../templates', id);
@@ -1 +1 @@
1
- {"version":3,"file":"template-manifest.js","sourceRoot":"","sources":["../../src/common/template-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAS3C,MAAM,iBAAiB,GAAG,CAAC,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;AAC9F,MAAM,iBAAiB,GAAG,CAAC,cAAc,EAAE,mBAAmB,EAAE,WAAW,CAAC,CAAC;AAE7E,MAAM,UAAU,eAAe,CAAC,KAG/B;IACC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,GAAG,iBAAiB,CAAC,EAAE,CAAC;KAC5D;IACD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;QACnB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;KAC1D;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,GAAG,iBAAiB,CAAC,EAAE,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAc;IAC3C,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC"}
1
+ {"version":3,"file":"template-manifest.js","sourceRoot":"","sources":["../../src/common/template-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAQ3C,MAAM,UAAU,eAAe,CAAC,KAG/B;IACC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;KACtB;IACD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;QACnB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;KAC1D;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAc;IAC3C,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC"}
@@ -1,8 +1,84 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
- import { resolve, join } from 'node:path';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve, join, relative } from 'node:path';
3
3
  import { findUnreplacedPlaceholders } from './template-renderer.js';
4
+ import { LEGACY_CONTAINERS, PROJECT_CONTAINERS, listProjects, usesNewContainers } from './project-layout.js';
5
+ import { CLI_COMMAND_REGISTRY, FORBIDDEN_ROOT_SCRIPTS, ROOT_SCRIPT_CONTRACT, expectedRootScriptValue } from './workspace-contract.js';
6
+ /** 任何布局都必须存在的根脚本。 */
7
+ const REQUIRED_ROOT_SCRIPTS = ['build', 'typecheck'];
8
+ const CONTAINER_LIST = Object.values(PROJECT_CONTAINERS);
9
+ function readJson(filePath) {
10
+ try {
11
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ function verifyAppProject(project, projectPath, pkg, label, addCheck) {
18
+ addCheck(`${label}_catalog_deps`, pkg.dependencies?.['@farris/ui-vue'] === 'catalog:', `工程 ${project.dir} 的 @farris/ui-vue 依赖必须使用 catalog: 协议`);
19
+ const indexHtmlPath = join(projectPath, 'index.html');
20
+ if (!existsSync(indexHtmlPath)) {
21
+ addCheck(`${label}_index_html`, false, `工程 ${project.dir} 缺少 index.html`);
22
+ }
23
+ else {
24
+ const html = readFileSync(indexHtmlPath, 'utf-8');
25
+ const hasCorrectEntry = /<script\s+type="module"\s+src="\/src\/main\.ts">\s*<\/script>/.test(html);
26
+ const hasWrongEntry = /<script\s+type="module"\s+src="\.\/src\/main\.ts">\s*<\/script>/.test(html);
27
+ addCheck(`${label}_html_entry`, hasCorrectEntry && !hasWrongEntry, `工程 ${project.dir} 的 index.html 入口必须为 /src/main.ts,不得写为 ./src/main.ts`);
28
+ }
29
+ addCheck(`${label}_main_ts`, existsSync(join(projectPath, 'src', 'main.ts')), `工程 ${project.dir} 缺少 src/main.ts`);
30
+ const hasAppVue = existsSync(join(projectPath, 'src', 'App.vue'));
31
+ const hasAppTsx = existsSync(join(projectPath, 'src', 'App.tsx'));
32
+ addCheck(`${label}_app_entry`, hasAppVue || hasAppTsx, `工程 ${project.dir} 缺少 src/App.vue(或历史兼容的 src/App.tsx)`);
33
+ const farrisConfigPath = join(projectPath, 'farris.config.mjs');
34
+ const configContent = existsSync(farrisConfigPath) ? readFileSync(farrisConfigPath, 'utf-8') : '';
35
+ const hasRelativeBase = /base\s*:\s*['"]\.\/?['"]/.test(configContent);
36
+ const hasHistoryRouter = /routerMode\s*:\s*['"]history['"]/.test(configContent);
37
+ addCheck(`${label}_base_router_compatibility`, !(hasRelativeBase && hasHistoryRouter), `工程 ${project.dir} 配置冲突: 相对路径 base 与 history 路由模式不可同时使用`);
38
+ }
39
+ function verifyLibProject(project, projectPath, pkg, label, addCheck) {
40
+ addCheck(`${label}_components_entry`, existsSync(join(projectPath, 'components', 'index.ts')), `组件库 ${project.dir} 缺少 components/index.ts(发布物唯一入口)`);
41
+ addCheck(`${label}_files_whitelist`, Array.isArray(pkg.files) && pkg.files.length > 0, `组件库 ${project.dir} 的 package.json 缺少 files 白名单,误执行 npm publish 会把 src/ 推上去`);
42
+ addCheck(`${label}_publish_config`, !!pkg.publishConfig, `组件库 ${project.dir} 的 package.json 缺少 publishConfig`);
43
+ }
44
+ function verifyProject(wsRoot, project, addCheck) {
45
+ const label = `${project.type}_${project.dir.replace(/\//g, '_')}`;
46
+ const projectPath = join(wsRoot, project.dir);
47
+ const pkgPath = join(projectPath, 'package.json');
48
+ const farrisConfigPath = join(projectPath, 'farris.config.mjs');
49
+ const tsconfigPath = join(projectPath, 'tsconfig.json');
50
+ if (!existsSync(pkgPath)) {
51
+ addCheck(`${label}_package_json`, false, `工程 ${project.dir} 缺少 package.json`);
52
+ return;
53
+ }
54
+ const pkg = readJson(pkgPath);
55
+ if (!pkg) {
56
+ addCheck(`${label}_package_json_valid`, false, `工程 ${project.dir} 的 package.json 解析失败`);
57
+ return;
58
+ }
59
+ addCheck(`${label}_name`, !!pkg.name, `工程 ${project.dir} 的 package.json 缺少 name 字段`);
60
+ addCheck(`${label}_farris_config`, existsSync(farrisConfigPath), `工程 ${project.dir} 缺少 farris.config.mjs`);
61
+ addCheck(`${label}_tsconfig`, existsSync(tsconfigPath), `工程 ${project.dir} 缺少 tsconfig.json`);
62
+ addCheck(`${label}_typecheck_script`, !!pkg.scripts?.typecheck, `工程 ${project.dir} 缺少 typecheck 脚本,根 pnpm typecheck 会静默跳过它`);
63
+ if (project.type === 'app') {
64
+ verifyAppProject(project, projectPath, pkg, label, addCheck);
65
+ }
66
+ else {
67
+ verifyLibProject(project, projectPath, pkg, label, addCheck);
68
+ }
69
+ for (const file of [pkgPath, farrisConfigPath, tsconfigPath]) {
70
+ if (!existsSync(file)) {
71
+ continue;
72
+ }
73
+ const unreplaced = findUnreplacedPlaceholders(readFileSync(file, 'utf-8'));
74
+ addCheck(`${label}_placeholders_${relative(wsRoot, file).replace(/\\/g, '/')}`, unreplaced.length === 0, `文件 ${relative(wsRoot, file)} 存在未替换占位符: ${unreplaced.join(', ')}`);
75
+ }
76
+ }
4
77
  /**
5
- * 校验 Workspace 完整性与规范合规性
78
+ * 校验 Workspace 完整性与规范合规性。
79
+ *
80
+ * 新结构(pnpm-workspace.yaml 含 applications/* 或 libraries/*)走强校验;
81
+ * 旧结构(只含 packages/*)只做兼容校验,不因历史模板缺失的项失败。
6
82
  */
7
83
  export function verifyWorkspace(targetDir, preservedInputs = []) {
8
84
  const checks = [];
@@ -22,127 +98,95 @@ export function verifyWorkspace(targetDir, preservedInputs = []) {
22
98
  errors: [`目标路径不存在: ${resolvedTarget}`]
23
99
  };
24
100
  }
25
- // 1. 检查根 package.json
101
+ const isNewLayout = usesNewContainers(resolvedTarget);
102
+ // 1. 根 package.json
26
103
  const rootPkgPath = join(resolvedTarget, 'package.json');
27
104
  if (!existsSync(rootPkgPath)) {
28
105
  addCheck('root_package_json', false, '缺少根 package.json');
29
106
  }
30
107
  else {
31
- try {
32
- const pkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
108
+ const pkg = readJson(rootPkgPath);
109
+ if (!pkg) {
110
+ addCheck('root_package_json_valid', false, '根 package.json 解析失败');
111
+ }
112
+ else {
33
113
  addCheck('root_package_private', pkg.private === true, '根 package.json 必须声明 "private": true');
34
- addCheck('root_package_scripts', !!(pkg.scripts?.build && pkg.scripts?.dev && pkg.scripts?.typecheck), '根 package.json 必须声明 build, dev, typecheck 脚本');
35
114
  addCheck('root_package_no_name', !pkg.name, '根 package.json 不应设置 name');
36
- }
37
- catch (e) {
38
- addCheck('root_package_json_valid', false, `根 package.json 解析失败: ${e.message}`);
115
+ const scripts = (pkg.scripts ?? {});
116
+ const missingRootScripts = REQUIRED_ROOT_SCRIPTS.filter((script) => !scripts[script]);
117
+ addCheck('root_package_scripts', missingRootScripts.length === 0, `根 package.json 缺少必需脚本: ${missingRootScripts.join(', ')}`);
118
+ if (isNewLayout) {
119
+ const wrongScripts = ROOT_SCRIPT_CONTRACT.filter((item) => scripts[item.script] !== expectedRootScriptValue(item.script));
120
+ addCheck('root_package_scripts_contract', wrongScripts.length === 0, `根脚本必须按契约声明: ${wrongScripts
121
+ .map((item) => `${item.script} → ${expectedRootScriptValue(item.script)}`)
122
+ .join('; ')}`);
123
+ const forbidden = FORBIDDEN_ROOT_SCRIPTS.filter((script) => scripts[script] !== undefined);
124
+ addCheck('root_package_no_forbidden_scripts', forbidden.length === 0, `根 package.json 不得声明长驻服务脚本: ${forbidden.join(', ')}`);
125
+ addCheck('root_package_cli_dependency', pkg.devDependencies?.['@farris/cli'] === 'catalog:', '根 package.json 必须声明 "@farris/cli": "catalog:",否则根脚本无法解析 farris-cli bin');
126
+ }
39
127
  }
40
128
  }
41
- // 2. 检查 pnpm-workspace.yaml
129
+ // 2. pnpm-workspace.yaml
42
130
  const wsYamlPath = join(resolvedTarget, 'pnpm-workspace.yaml');
43
131
  if (!existsSync(wsYamlPath)) {
44
132
  addCheck('workspace_yaml', false, '缺少 pnpm-workspace.yaml');
45
133
  }
46
134
  else {
47
135
  const yamlContent = readFileSync(wsYamlPath, 'utf-8');
48
- addCheck('workspace_packages_glob', yamlContent.includes('packages/*'), 'pnpm-workspace.yaml 必须包含 "packages/*"');
136
+ if (isNewLayout) {
137
+ const missingContainers = CONTAINER_LIST.filter((container) => !yamlContent.includes(`${container}/*`));
138
+ addCheck('workspace_packages_glob', missingContainers.length === 0, `pnpm-workspace.yaml 必须包含新容器 glob: ${missingContainers
139
+ .map((container) => `${container}/*`)
140
+ .join(', ')}`);
141
+ }
142
+ else {
143
+ addCheck('workspace_packages_glob', yamlContent.includes('packages/*'), 'pnpm-workspace.yaml 必须包含 "packages/*"');
144
+ }
49
145
  addCheck('workspace_catalog', yamlContent.includes('catalog:'), 'pnpm-workspace.yaml 必须声明 catalog 依赖版本');
50
146
  }
51
- // 3. 检查 packages 目录与子应用
52
- const packagesDir = join(resolvedTarget, 'packages');
53
- if (!existsSync(packagesDir) || !statSync(packagesDir).isDirectory()) {
54
- addCheck('packages_dir', false, '缺少 packages/ 目录');
147
+ // 3. 容器目录
148
+ if (isNewLayout) {
149
+ for (const container of CONTAINER_LIST) {
150
+ addCheck(`container_${container}`, existsSync(join(resolvedTarget, container)), `缺少容器目录: ${container}/`);
151
+ }
55
152
  }
56
153
  else {
57
- const appDirs = readdirSync(packagesDir).filter((f) => {
58
- const p = join(packagesDir, f);
59
- return statSync(p).isDirectory();
60
- });
61
- if (appDirs.length === 0) {
62
- addCheck('has_sub_apps', false, 'packages/ 目录下未发现任何应用子包');
154
+ const legacyContainer = LEGACY_CONTAINERS[0];
155
+ addCheck(`container_${legacyContainer}`, existsSync(join(resolvedTarget, legacyContainer)), `缺少容器目录: ${legacyContainer}/`);
156
+ }
157
+ // 4. 逐个工程按类型校验
158
+ for (const project of listProjects(resolvedTarget)) {
159
+ verifyProject(resolvedTarget, project, addCheck);
160
+ }
161
+ // 5. AGENTS.md 体系(仅新结构强校验,见设计文档 §8.3)
162
+ if (isNewLayout) {
163
+ const agentsPath = join(resolvedTarget, 'AGENTS.md');
164
+ if (!existsSync(agentsPath)) {
165
+ addCheck('agents_doc_exists', false, '缺少根 AGENTS.md(AI 协作契约的唯一真源)');
63
166
  }
64
167
  else {
65
- addCheck('has_sub_apps', true);
66
- // 逐个检查子应用
67
- for (const appDir of appDirs) {
68
- const appPath = join(packagesDir, appDir);
69
- const appPkgPath = join(appPath, 'package.json');
70
- const farrisConfigPath = join(appPath, 'farris.config.mjs');
71
- const indexHtmlPath = join(appPath, 'index.html');
72
- const tsconfigPath = join(appPath, 'tsconfig.json');
73
- const mainTsPath = join(appPath, 'src', 'main.ts');
74
- const appTsxPath = join(appPath, 'src', 'App.tsx');
75
- if (!existsSync(appPkgPath)) {
76
- addCheck(`app_${appDir}_package_json`, false, `应用 ${appDir} 缺少 package.json`);
77
- }
78
- else {
79
- try {
80
- const appPkg = JSON.parse(readFileSync(appPkgPath, 'utf-8'));
81
- addCheck(`app_${appDir}_name`, !!appPkg.name, `应用 ${appDir} 的 package.json 缺少 name 字段`);
82
- addCheck(`app_${appDir}_catalog_deps`, appPkg.dependencies?.['@farris/ui-vue'] === 'catalog:', `应用 ${appDir} 的 @farris/ui-vue 依赖必须使用 catalog: 协议`);
83
- }
84
- catch (e) {
85
- addCheck(`app_${appDir}_package_json_valid`, false, `应用 ${appDir} package.json 解析失败: ${e.message}`);
86
- }
87
- }
88
- // 检查 index.html 入口
89
- if (!existsSync(indexHtmlPath)) {
90
- addCheck(`app_${appDir}_index_html`, false, `应用 ${appDir} 缺少 index.html`);
91
- }
92
- else {
93
- const html = readFileSync(indexHtmlPath, 'utf-8');
94
- const hasCorrectEntry = /<script\s+type="module"\s+src="\/src\/main\.ts">\s*<\/script>/.test(html);
95
- const hasWrongEntry = /<script\s+type="module"\s+src="\.\/src\/main\.ts">\s*<\/script>/.test(html);
96
- addCheck(`app_${appDir}_html_entry`, hasCorrectEntry && !hasWrongEntry, `应用 ${appDir} 的 index.html 入口必须为 /src/main.ts,不得写为 ./src/main.ts`);
97
- }
98
- // 检查 farris.config.mjs
99
- if (!existsSync(farrisConfigPath)) {
100
- addCheck(`app_${appDir}_farris_config`, false, `应用 ${appDir} 缺少 farris.config.mjs`);
101
- }
102
- else {
103
- const configContent = readFileSync(farrisConfigPath, 'utf-8');
104
- // 检查相对 base 与 history 组合
105
- const hasRelativeBase = /base\s*:\s*['"]\.\/?['"]/.test(configContent);
106
- const hasHistoryRouter = /routerMode\s*:\s*['"]history['"]/.test(configContent);
107
- if (hasRelativeBase && hasHistoryRouter) {
108
- addCheck(`app_${appDir}_base_router_compatibility`, false, `应用 ${appDir} 配置冲突: 相对路径 base 与 history 路由模式不可同时使用`);
109
- }
110
- else {
111
- addCheck(`app_${appDir}_base_router_compatibility`, true);
112
- }
113
- }
114
- // 检查 TSX 入口与多语言资源骨架
115
- const localesIndexPath = join(appPath, 'src', 'locales', 'index.ts');
116
- const localesZhJsonPath = join(appPath, 'src', 'locales', 'zh-CN.json');
117
- addCheck(`app_${appDir}_tsconfig`, existsSync(tsconfigPath), `应用 ${appDir} 缺少 tsconfig.json`);
118
- addCheck(`app_${appDir}_main_ts`, existsSync(mainTsPath), `应用 ${appDir} 缺少 src/main.ts`);
119
- addCheck(`app_${appDir}_app_tsx`, existsSync(appTsxPath), `应用 ${appDir} 缺少 src/App.tsx`);
120
- addCheck(`app_${appDir}_locales_index`, existsSync(localesIndexPath), `应用 ${appDir} 缺少 src/locales/index.ts`);
121
- addCheck(`app_${appDir}_locales_zh_json`, existsSync(localesZhJsonPath), `应用 ${appDir} 缺少 src/locales/zh-CN.json`);
122
- // 检查未替换占位符
123
- const checkFiles = [appPkgPath, farrisConfigPath, indexHtmlPath, mainTsPath, appTsxPath, localesIndexPath, localesZhJsonPath];
124
- for (const file of checkFiles) {
125
- if (existsSync(file)) {
126
- const content = readFileSync(file, 'utf-8');
127
- const unreplaced = findUnreplacedPlaceholders(content);
128
- if (unreplaced.length > 0) {
129
- addCheck(`app_${appDir}_placeholders_${file}`, false, `文件 ${file} 存在未替换占位符: ${unreplaced.join(', ')}`);
130
- }
131
- }
132
- }
133
- }
168
+ addCheck('agents_doc_exists', true);
169
+ const content = readFileSync(agentsPath, 'utf-8');
170
+ const missingDeclaration = CONTAINER_LIST.filter((container) => !content.includes(`${container}/`));
171
+ addCheck('agents_doc_containers', missingDeclaration.length === 0, `根 AGENTS.md 未声明容器目录: ${missingDeclaration.join(', ')}`);
172
+ const legacyContainer = LEGACY_CONTAINERS[0];
173
+ addCheck('agents_doc_no_legacy_container', !content.includes(`${legacyContainer}/`), `根 AGENTS.md 仍引用旧容器目录 ${legacyContainer}/,与 PROJECT_CONTAINERS 不一致`);
174
+ const missingCommands = CLI_COMMAND_REGISTRY.filter((command) => !content.includes(command));
175
+ addCheck('agents_doc_commands', missingCommands.length === 0, `根 AGENTS.md 未覆盖命令注册表中的命令: ${missingCommands.join(', ')}`);
134
176
  }
135
- }
136
- // 4. 检查受保护输入是否依然完好
137
- if (preservedInputs.length > 0) {
138
- for (const entry of preservedInputs) {
139
- const entryPath = join(resolvedTarget, entry);
140
- addCheck(`preserved_input_${entry}`, existsSync(entryPath), `受保护输入文件/目录丢失或被覆盖: ${entry}`);
177
+ const claudePath = join(resolvedTarget, 'CLAUDE.md');
178
+ if (existsSync(claudePath)) {
179
+ const raw = readFileSync(claudePath, 'utf-8');
180
+ const lines = raw.trimEnd().split(/\r?\n/);
181
+ addCheck('claude_pointer_thin', lines.length <= 3 && raw.includes('AGENTS.md'), 'CLAUDE.md 必须是不含规则正文的薄指针(≤ 3 行且包含 AGENTS.md 字样)');
141
182
  }
142
183
  }
143
- const ok = errors.length === 0;
184
+ // 6. 受保护输入是否依然完好
185
+ for (const entry of preservedInputs) {
186
+ addCheck(`preserved_input_${entry}`, existsSync(join(resolvedTarget, entry)), `受保护输入文件/目录丢失或被覆盖: ${entry}`);
187
+ }
144
188
  return {
145
- ok,
189
+ ok: errors.length === 0,
146
190
  target: resolvedTarget,
147
191
  checks,
148
192
  errors