@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68

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 (198) hide show
  1. package/README.en.md +86 -241
  2. package/README.ja.md +86 -241
  3. package/README.md +86 -241
  4. package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +1304 -532
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/README.md +48 -145
  21. package/templates/apps/admin/messages/ja-JP.json +38 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  23. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  24. package/templates/apps/api/.dockerignore.hbs +14 -7
  25. package/templates/apps/api/.gitignore.hbs +7 -0
  26. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  27. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  28. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  29. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
  30. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  31. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  32. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  33. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  34. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  35. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  36. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  38. package/templates/apps/api/hai.build.json.hbs +1 -1
  39. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  40. package/templates/apps/api/package.json.hbs +6 -1
  41. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  42. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  43. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
  51. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  54. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  58. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
  59. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  60. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  61. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  62. package/templates/apps/fullstack/package.json.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  64. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  66. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
  88. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  89. package/templates/apps/h5/messages/ja-JP.json +40 -0
  90. package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
  91. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  92. package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
  93. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  94. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  95. package/templates/apps/website/messages/ja-JP.json +32 -0
  96. package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
  97. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  98. package/templates/base/.dockerignore.hbs +14 -8
  99. package/templates/base/.gitignore.hbs +7 -0
  100. package/templates/base/e2e/app.spec.ts.hbs +23 -8
  101. package/templates/base/hai.build.json.hbs +1 -1
  102. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  103. package/templates/base/package.json.hbs +11 -3
  104. package/templates/base/playwright.config.ts +15 -4
  105. package/templates/base/src/hooks.server.ts.hbs +15 -6
  106. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  107. package/templates/base/vite.config.ts.hbs +5 -3
  108. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  109. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  110. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  111. package/templates/i18n/project.inlang/settings.json +1 -1
  112. package/templates/shared/.gitattributes +6 -0
  113. package/templates/shared/e2e-env.ts +98 -0
  114. package/templates/shared/run-e2e.mjs +51 -12
  115. package/templates/skills/AGENTS.md +7 -3
  116. package/templates/skills/bridges/admin/AGENTS.md +6 -2
  117. package/templates/skills/bridges/api/AGENTS.md +3 -1
  118. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
  119. package/templates/skills/bridges/h5/AGENTS.md +6 -2
  120. package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
  121. package/templates/skills/bridges/website/AGENTS.md +6 -2
  122. package/templates/skills/hai-ai/SKILL.md +8 -2
  123. package/templates/skills/hai-ai/guide.md +134 -60
  124. package/templates/skills/hai-ai/reference.md +51 -11
  125. package/templates/skills/hai-api-client/SKILL.md +15 -160
  126. package/templates/skills/hai-api-client/reference.md +178 -0
  127. package/templates/skills/hai-api-contract/SKILL.md +16 -273
  128. package/templates/skills/hai-api-contract/reference.md +280 -0
  129. package/templates/skills/hai-app-create/SKILL.md +6 -2
  130. package/templates/skills/hai-app-create/development.md +24 -8
  131. package/templates/skills/hai-app-create/reference.md +7 -5
  132. package/templates/skills/hai-app-review/SKILL.md +7 -3
  133. package/templates/skills/hai-app-review/differential.md +34 -0
  134. package/templates/skills/hai-app-tests/SKILL.md +6 -3
  135. package/templates/skills/hai-app-tests/reference.md +5 -5
  136. package/templates/skills/hai-app-tests/validation.md +38 -0
  137. package/templates/skills/hai-audit/SKILL.md +24 -147
  138. package/templates/skills/hai-audit/reference.md +148 -0
  139. package/templates/skills/hai-build/SKILL.md +14 -8
  140. package/templates/skills/hai-build/reference.md +16 -13
  141. package/templates/skills/hai-cache/SKILL.md +20 -181
  142. package/templates/skills/hai-cache/reference.md +229 -0
  143. package/templates/skills/hai-capacitor/SKILL.md +7 -4
  144. package/templates/skills/hai-capacitor/reference.md +15 -9
  145. package/templates/skills/hai-ci/SKILL.md +2 -0
  146. package/templates/skills/hai-core/SKILL.md +7 -3
  147. package/templates/skills/hai-core/reference.md +25 -5
  148. package/templates/skills/hai-crypto/SKILL.md +22 -250
  149. package/templates/skills/hai-crypto/reference.md +310 -0
  150. package/templates/skills/hai-datapipe/SKILL.md +16 -195
  151. package/templates/skills/hai-datapipe/reference.md +204 -0
  152. package/templates/skills/hai-deploy/SKILL.md +24 -54
  153. package/templates/skills/hai-deploy/middleware.md +51 -43
  154. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  155. package/templates/skills/hai-framework-sync/sync.md +1 -1
  156. package/templates/skills/hai-iam/SKILL.md +25 -11
  157. package/templates/skills/hai-iam/reference.md +181 -154
  158. package/templates/skills/hai-kit/SKILL.md +35 -174
  159. package/templates/skills/hai-kit/reference.md +190 -0
  160. package/templates/skills/hai-payment/SKILL.md +33 -263
  161. package/templates/skills/hai-payment/reference.md +295 -0
  162. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  163. package/templates/skills/hai-reach/SKILL.md +6 -3
  164. package/templates/skills/hai-reach/reference.md +83 -22
  165. package/templates/skills/hai-reldb/SKILL.md +6 -3
  166. package/templates/skills/hai-reldb/reference.md +68 -25
  167. package/templates/skills/hai-scheduler/SKILL.md +18 -195
  168. package/templates/skills/hai-scheduler/reference.md +216 -0
  169. package/templates/skills/hai-serv/SKILL.md +25 -10
  170. package/templates/skills/hai-serv/reference.md +123 -87
  171. package/templates/skills/hai-spec-implement/SKILL.md +5 -1
  172. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  173. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  174. package/templates/skills/hai-spec-implement/templates.md +5 -0
  175. package/templates/skills/hai-spec-implement/workflow.md +5 -3
  176. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  177. package/templates/skills/hai-storage/SKILL.md +23 -238
  178. package/templates/skills/hai-storage/reference.md +282 -0
  179. package/templates/skills/hai-ui/SKILL.md +17 -3
  180. package/templates/skills/hai-ui/guide.md +3 -3
  181. package/templates/skills/hai-ui/reference.md +26 -11
  182. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  183. package/templates/skills/hai-ui-design/patterns.md +26 -0
  184. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  185. package/templates/skills/hai-ui-review/checklist.md +22 -0
  186. package/templates/skills/hai-vecdb/SKILL.md +14 -10
  187. package/templates/skills/hai-vecdb/reference.md +33 -3
  188. package/dist/chunk-FWN5B54L.js +0 -468
  189. package/dist/chunk-FWN5B54L.js.map +0 -1
  190. package/dist/chunk-QYX6V7H2.js.map +0 -1
  191. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  192. package/dist/src-RDQ2EIBT.js.map +0 -1
  193. package/templates/apps/api/Dockerfile.hbs +0 -35
  194. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  195. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  196. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  197. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  198. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
package/dist/index.js CHANGED
@@ -1,24 +1,27 @@
1
1
  #!/usr/bin/env node
2
- import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-QYX6V7H2.js';
3
- import { cliM } from './chunk-FWN5B54L.js';
2
+ import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, generateProductionEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-ZFNBUF77.js';
3
+ import { cliM } from './chunk-MBPB37FR.js';
4
+ import { external_exports } from './chunk-FI56GONY.js';
4
5
  import './chunk-2H7UOFLK.js';
5
6
  import process4 from 'process';
6
7
  import { cac } from 'cac';
7
8
  import chalk from 'chalk';
8
- import path2, { resolve, join, dirname } from 'path';
9
+ import path2, { resolve, join, dirname, relative } from 'path';
9
10
  import { fileURLToPath, pathToFileURL } from 'url';
10
11
  import { wrapAnsi } from 'fast-wrap-ansi';
11
12
  import fse from 'fs-extra';
12
13
  import Handlebars2 from 'handlebars';
13
- import { core } from '@h-ai/core';
14
+ import { parseEnv } from 'util';
14
15
  import ora from 'ora';
15
16
  import prompts from 'prompts';
16
17
  import { Buffer } from 'buffer';
17
18
  import { execSync, execFileSync, spawn } from 'child_process';
18
- import { randomBytes, createHash } from 'crypto';
19
- import { stringify, parseDocument } from 'yaml';
20
- import { readFileSync, mkdirSync, mkdtempSync, existsSync, createReadStream, writeFileSync, renameSync, rmSync } from 'fs';
19
+ import { randomBytes, createECDH, createHash, randomUUID } from 'crypto';
20
+ import { stringify, parse, parseDocument, isScalar, isMap } from 'yaml';
21
+ import { readFileSync, existsSync, mkdirSync, mkdtempSync, createReadStream, writeFileSync, renameSync, rmSync, readdirSync, copyFileSync } from 'fs';
21
22
  import { createRequire } from 'module';
23
+ import { core } from '@h-ai/core';
24
+ import { glob } from 'fs/promises';
22
25
 
23
26
  var CAMEL_CASE_SEPARATOR_REGEX = /[-_\s]+(.)?/g;
24
27
  var LEADING_CHAR_REGEX = /^./;
@@ -29,6 +32,8 @@ function writeCliMessage(message) {
29
32
  function writeCliError(message, error) {
30
33
  process4.stderr.write(`${message}
31
34
  `);
35
+ if (error && typeof error === "object" && "error" in error)
36
+ error = error.error;
32
37
  if (error !== void 0)
33
38
  process4.stderr.write(`${error instanceof Error ? error.message : String(error)}
34
39
  `);
@@ -133,56 +138,56 @@ var CAPACITOR_SKIPPED_SERVER_FILES = /* @__PURE__ */ new Set([
133
138
  "src/lib/server/init.ts"
134
139
  ]);
135
140
  var VERSIONS = {
136
- antfuEslintConfig: "^9.5.1",
137
- capacitorSecureStorage: "^8.0.1",
141
+ antfuEslintConfig: "9.5.1",
142
+ capacitorSecureStorage: "8.0.1",
138
143
  // UI / Icon
139
- bitsUi: "^2.19.5",
140
- iconifyJsonTabler: "^1.2.42",
141
- iconifyTailwind: "^1.2.3",
144
+ bitsUi: "2.19.5",
145
+ iconifyJsonTabler: "1.2.42",
146
+ iconifyTailwind: "1.2.3",
142
147
  // Capacitor
143
- capacitorAndroid: "^8.5.3",
144
- capacitorApp: "^8.1.2",
145
- capacitorCamera: "^8.2.5",
146
- capacitorCli: "^8.5.3",
147
- capacitorCore: "^8.5.3",
148
- capacitorDevice: "^8.0.3",
149
- capacitorIos: "^8.5.3",
150
- capacitorPreferences: "^8.0.1",
151
- capacitorPushNotifications: "^8.1.3",
152
- capacitorStatusBar: "^8.0.4",
148
+ capacitorAndroid: "8.5.3",
149
+ capacitorApp: "8.1.2",
150
+ capacitorCamera: "8.2.5",
151
+ capacitorCli: "8.5.3",
152
+ capacitorCore: "8.5.3",
153
+ capacitorDevice: "8.0.3",
154
+ capacitorIos: "8.5.3",
155
+ capacitorPreferences: "8.0.1",
156
+ capacitorPushNotifications: "8.1.3",
157
+ capacitorStatusBar: "8.0.4",
153
158
  // Tailwind / DaisyUI
154
- daisyui: "^5.7.47",
155
- tailwindcss: "^4.3.3",
156
- tailwindcssVite: "^4.3.3",
159
+ daisyui: "5.7.47",
160
+ tailwindcss: "4.3.3",
161
+ tailwindcssVite: "4.3.3",
157
162
  // i18n
158
- inlangParaglide: "^2.27.0",
159
- inlangMessageFormat: "^4.5.0",
163
+ inlangParaglide: "2.27.0",
164
+ inlangMessageFormat: "4.5.0",
160
165
  // Svelte / Vite
161
- svelte: "^5.57.2",
162
- svelteKit: "^2.70.3",
163
- svelteAdapterNode: "^5.5.7",
164
- svelteCheck: "^4.7.6",
165
- sveltePlugin: "^7.3.1",
166
+ svelte: "5.57.2",
167
+ svelteKit: "2.70.3",
168
+ svelteAdapterNode: "5.5.7",
169
+ svelteCheck: "4.7.6",
170
+ sveltePlugin: "7.3.1",
166
171
  // Build / Test
167
- vite: "^8.3.4",
168
- vitest: "^4.1.11",
169
- tsup: "^8.5.1",
170
- rimraf: "^6.1.3",
172
+ vite: "8.3.4",
173
+ vitest: "4.1.11",
174
+ tsup: "8.5.1",
175
+ rimraf: "6.1.3",
171
176
  // ESLint
172
- eslint: "^10.12.0",
173
- eslintPluginFormat: "^2.0.1",
174
- eslintPluginSvelte: "^3.23.1",
175
- svelteEslintParser: "^1.9.0",
177
+ eslint: "10.12.0",
178
+ eslintPluginFormat: "2.0.1",
179
+ eslintPluginSvelte: "3.23.1",
180
+ svelteEslintParser: "1.9.0",
176
181
  // Node / TS
177
- nodeTypes: "^26.6.5",
182
+ nodeTypes: "26.6.5",
178
183
  // Svelte / ESLint / tsup 继续使用稳定的 JavaScript 编译 API。
179
- typescript: "^6.0.3",
184
+ typescript: "6.0.3",
180
185
  // E2E
181
- playwright: "^1.64.0",
186
+ playwright: "1.64.0",
182
187
  // Validation
183
- zod: "^4.6.5",
188
+ zod: "4.6.5",
184
189
  // Tauri
185
- tauriCli: "^2.12.1"
190
+ tauriCli: "2.12.1"
186
191
  };
187
192
  var FRONTEND_PORTS_DEFAULT = {
188
193
  web: 4173,
@@ -427,6 +432,8 @@ function buildAppHaiCatalogPackages(options) {
427
432
  const packageNames = ["@h-ai/cli"];
428
433
  if (options.isSvelteOnlyApp) {
429
434
  packageNames.push("@h-ai/api-client", "@h-ai/api-contract", "@h-ai/capacitor", "@h-ai/ui");
435
+ if (options.featureMap.crypto)
436
+ packageNames.push("@h-ai/crypto");
430
437
  return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
431
438
  }
432
439
  packageNames.push("@h-ai/core", "@h-ai/kit");
@@ -468,7 +475,7 @@ function buildAppHaiCatalogPackages(options) {
468
475
  packageNames.push("@h-ai/capacitor");
469
476
  return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
470
477
  }
471
- function buildFullstackCatalogPackages(frontends, deploy = false) {
478
+ function buildFullstackCatalogPackages(frontends, deploy = false, features = {}) {
472
479
  const packageNames = [
473
480
  ...FULLSTACK_ROOT_CATALOG_PACKAGE_NAMES,
474
481
  "@h-ai/cli",
@@ -484,6 +491,10 @@ function buildFullstackCatalogPackages(frontends, deploy = false) {
484
491
  packageNames.push(...FULLSTACK_DESKTOP_CATALOG_PACKAGE_NAMES);
485
492
  if (deploy)
486
493
  packageNames.push("@h-ai/deploy");
494
+ if (features.ai)
495
+ packageNames.push("@h-ai/ai");
496
+ if (features.vecdb)
497
+ packageNames.push("@h-ai/vecdb");
487
498
  return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
488
499
  }
489
500
  var FEATURE_ROUTE_DIRS = {
@@ -580,6 +591,7 @@ function buildTemplateContext(options) {
580
591
  for (const f of options.features) {
581
592
  featureMap[f] = true;
582
593
  }
594
+ featureMap.deploy = options.deploy !== false;
583
595
  if (options.appType === "fullstack") {
584
596
  for (const feature of ["iam", "db", "cache", "crypto", "storage", "audit"]) featureMap[feature] = true;
585
597
  }
@@ -588,8 +600,12 @@ function buildTemplateContext(options) {
588
600
  featureMap.cache = true;
589
601
  featureMap.crypto = true;
590
602
  }
591
- if (featureMap.audit || featureMap.scheduler || featureMap.payment) {
603
+ if (featureMap.audit || featureMap.scheduler || featureMap.payment || featureMap.crypto) {
604
+ featureMap.db = true;
605
+ }
606
+ if (featureMap.ai) {
592
607
  featureMap.db = true;
608
+ featureMap.vecdb = true;
593
609
  }
594
610
  const isApi = options.appType === "api";
595
611
  const isFullstack = options.appType === "fullstack";
@@ -600,7 +616,7 @@ function buildTemplateContext(options) {
600
616
  const selectedFrontends = options.frontends ?? DEFAULT_FULLSTACK_FRONTENDS;
601
617
  let haiCatalogPackages;
602
618
  if (isFullstack) {
603
- haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy);
619
+ haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy, featureMap);
604
620
  } else if (isApi) {
605
621
  haiCatalogPackages = buildApiWorkspaceCatalogPackages(featureMap);
606
622
  } else {
@@ -692,9 +708,8 @@ function buildFullstackTemplateContext(projectName, frontendsInput, moduleConfig
692
708
  function resolveI18nContext(moduleConfigs) {
693
709
  const coreLocale = moduleConfigs?.core?.defaultLocale ?? "zh-CN";
694
710
  const baseLocale = moduleConfigs?.fullstack?.i18n?.baseLocale ?? coreLocale;
695
- const fallbackLocale = baseLocale === "en-US" ? "zh-CN" : "en-US";
696
711
  const requested = moduleConfigs?.fullstack?.i18n?.locales;
697
- const locales = requested && requested.length > 0 ? [...new Set(requested)] : [baseLocale, fallbackLocale];
712
+ const locales = requested && requested.length > 0 ? [...new Set(requested)] : [.../* @__PURE__ */ new Set([baseLocale, "zh-CN", "en-US", "ja-JP"])];
698
713
  if (!locales.includes(baseLocale)) {
699
714
  locales.unshift(baseLocale);
700
715
  }
@@ -776,10 +791,13 @@ async function generateFromTemplates(projectPath, context) {
776
791
  }
777
792
  await renderDynamicFiles(root, projectPath, context);
778
793
  await fse.copy(path2.join(root, "shared", "run-e2e.mjs"), path2.join(projectPath, "scripts", "run-e2e.mjs"));
794
+ await fse.copy(path2.join(root, "shared", "e2e-env.ts"), path2.join(projectPath, "scripts", "e2e-env.ts"));
795
+ await fse.ensureDir(path2.join(projectPath, "images", "dependencies"));
796
+ await fse.writeFile(path2.join(projectPath, "images", "dependencies", ".gitkeep"), "");
779
797
  await fse.copy(path2.join(root, "shared", ".gitattributes"), path2.join(projectPath, ".gitattributes"));
780
798
  if (context.isSvelteOnlyApp) {
781
799
  await fse.copy(path2.join(root, "shared", "vite-runtime-config.ts"), path2.join(projectPath, "scripts", "vite-runtime-config.ts"));
782
- await fse.copy(path2.join(root, "shared", "container-runtime-config.sh"), path2.join(projectPath, "docker", "19-runtime-config.sh"));
800
+ await fse.copy(path2.join(root, "shared", "container-runtime-config.sh"), path2.join(projectPath, "images", "web", "19-runtime-config.sh"));
783
801
  }
784
802
  if (context.isSvelteOnlyApp || context.appType === "fullstack") {
785
803
  const navigationDir = context.appType === "fullstack" ? path2.join(projectPath, "packages", `${context.projectName}-shared`, "src", "lib", "navigation") : path2.join(projectPath, "src", "lib", "navigation");
@@ -789,6 +807,9 @@ async function generateFromTemplates(projectPath, context) {
789
807
  const i18nDir = path2.join(root, "i18n");
790
808
  if (await fse.pathExists(i18nDir)) {
791
809
  await fse.copy(i18nDir, projectPath, { overwrite: true });
810
+ const settingsPath = path2.join(projectPath, "project.inlang", "settings.json");
811
+ const settings = await fse.readJson(settingsPath);
812
+ await fse.writeJson(settingsPath, { ...settings, baseLocale: context.defaultLocale }, { spaces: 2 });
792
813
  }
793
814
  }
794
815
  if (includeBaseTemplate) {
@@ -916,136 +937,242 @@ var RE_HTTPS_URL = /^https:\/\/.+/;
916
937
  var RE_GIT_SSH_URL = /^git@.+:.+/;
917
938
  var APP_TYPES = {
918
939
  "admin": {
919
- name: "\u7BA1\u7406\u540E\u53F0",
920
- description: "\u4F01\u4E1A\u7EA7\u7BA1\u7406\u7CFB\u7EDF\uFF0C\u542B IAM\u3001\u6570\u636E\u5E93\u3001\u5B8C\u6574 UI",
940
+ get name() {
941
+ return cliM("cli_appAdminName");
942
+ },
943
+ get description() {
944
+ return cliM("cli_appAdminDescription");
945
+ },
921
946
  defaultFeatures: ["iam", "db", "cache", "crypto"]
922
947
  },
923
948
  "website": {
924
- name: "\u4F01\u4E1A\u5B98\u7F51",
925
- description: "SSR/SSG \u4F01\u4E1A\u5B98\u7F51\uFF0CSEO \u53CB\u597D\u3001\u54CD\u5E94\u5F0F\u5E03\u5C40",
949
+ get name() {
950
+ return cliM("cli_appWebsiteName");
951
+ },
952
+ get description() {
953
+ return cliM("cli_appWebsiteDescription");
954
+ },
926
955
  defaultFeatures: ["db", "cache"]
927
956
  },
928
957
  "h5": {
929
- name: "H5 \u5E94\u7528",
930
- description: "\u79FB\u52A8\u7AEF H5 \u5E94\u7528\uFF0C\u89E6\u6478\u4F18\u5316\u3001PWA \u652F\u6301",
958
+ get name() {
959
+ return cliM("cli_appH5Name");
960
+ },
961
+ get description() {
962
+ return cliM("cli_appH5Description");
963
+ },
931
964
  defaultFeatures: ["db", "cache"]
932
965
  },
933
966
  "api": {
934
- name: "API \u670D\u52A1",
935
- description: "typed API workspace\uFF0C\u542B contract + service \u53CC\u5DE5\u7A0B\u3001\u65E0 UI",
967
+ get name() {
968
+ return cliM("cli_appApiName");
969
+ },
970
+ get description() {
971
+ return cliM("cli_appApiDescription");
972
+ },
936
973
  defaultFeatures: ["db", "cache"]
937
974
  },
938
975
  "mobile-app": {
939
- name: "\u79FB\u52A8\u5E94\u7528",
940
- description: "\u79FB\u52A8\u7AEF\u5E94\u7528\uFF08Capacitor + Svelte 5 + Vite\uFF09\uFF0C\u53EF\u6253\u5305\u4E3A Android/iOS",
976
+ get name() {
977
+ return cliM("cli_appMobileName");
978
+ },
979
+ get description() {
980
+ return cliM("cli_appMobileDescription");
981
+ },
941
982
  defaultFeatures: ["api-client", "capacitor"]
942
983
  },
943
984
  "fullstack": {
944
- name: "\u524D\u540E\u7AEF\u5206\u79BB\u5DE5\u7A0B",
945
- description: "\u591A\u7AEF mini \u7BA1\u7406\u7CFB\u7EDF\uFF0C\u5305\u542B\u7528\u6237\u3001\u89D2\u8272\u3001\u5BA1\u8BA1\u3001\u4E2A\u4EBA\u8BBE\u7F6E\u4E0E\u53CC\u5411\u4F20\u8F93\u52A0\u5BC6",
985
+ get name() {
986
+ return cliM("cli_appFullstackName");
987
+ },
988
+ get description() {
989
+ return cliM("cli_appFullstackDescription");
990
+ },
946
991
  defaultFeatures: ["iam", "db", "cache", "crypto", "storage", "audit"]
947
992
  }
948
993
  };
949
994
  var FRONTEND_TARGETS = {
950
- web: { name: "Web", description: "\u6D4F\u89C8\u5668 Web \u5E94\u7528", implemented: true },
951
- app: { name: "App", description: "\u79FB\u52A8\u7AEF Capacitor App / H5 \u524D\u7AEF", implemented: true },
952
- miniapp: { name: "\u5C0F\u7A0B\u5E8F", description: "\u9884\u7559\u76EE\u6807\uFF0C\u5F53\u524D\u53EA\u751F\u6210\u8BF4\u660E\u5360\u4F4D", implemented: false },
953
- desktop: { name: "\u684C\u9762\u7AEF", description: "Tauri \u684C\u9762\u7AEF\u524D\u7AEF\u5DE5\u7A0B", implemented: true }
995
+ web: { name: "Web", get description() {
996
+ return cliM("cli_frontendWebDescription");
997
+ }, implemented: true },
998
+ app: { name: "App", get description() {
999
+ return cliM("cli_frontendAppDescription");
1000
+ }, implemented: true },
1001
+ miniapp: {
1002
+ get name() {
1003
+ return cliM("cli_frontendMiniappName");
1004
+ },
1005
+ get description() {
1006
+ return cliM("cli_frontendMiniappDescription");
1007
+ },
1008
+ implemented: false
1009
+ },
1010
+ desktop: {
1011
+ get name() {
1012
+ return cliM("cli_frontendDesktopName");
1013
+ },
1014
+ get description() {
1015
+ return cliM("cli_frontendDesktopDescription");
1016
+ },
1017
+ implemented: true
1018
+ }
954
1019
  };
955
1020
  var DEFAULT_FULLSTACK_FRONTENDS2 = ["web", "app", "desktop"];
956
1021
  var FEATURES = {
957
1022
  "iam": {
958
1023
  id: "iam",
959
- name: "\u8EAB\u4EFD\u4E0E\u8BBF\u95EE\u7BA1\u7406",
960
- description: "Session/JWT \u4F1A\u8BDD\u7BA1\u7406\u3001RBAC \u6743\u9650\u63A7\u5236",
1024
+ get name() {
1025
+ return cliM("cli_featureIamName");
1026
+ },
1027
+ get description() {
1028
+ return cliM("cli_featureIamDescription");
1029
+ },
961
1030
  packages: ["@h-ai/iam"],
962
1031
  dependencies: ["crypto", "db", "cache"]
963
1032
  },
964
1033
  "db": {
965
1034
  id: "db",
966
- name: "\u6570\u636E\u5E93",
967
- description: "\u591A\u6570\u636E\u5E93\u652F\u6301 (SQLite/PostgreSQL/MySQL)",
1035
+ get name() {
1036
+ return cliM("cli_featureDbName");
1037
+ },
1038
+ get description() {
1039
+ return cliM("cli_featureDbDescription");
1040
+ },
968
1041
  packages: ["@h-ai/reldb"]
969
1042
  },
970
1043
  "cache": {
971
1044
  id: "cache",
972
- name: "\u7F13\u5B58",
973
- description: "Redis / \u5185\u5B58\u7F13\u5B58",
1045
+ get name() {
1046
+ return cliM("cli_featureCacheName");
1047
+ },
1048
+ get description() {
1049
+ return cliM("cli_featureCacheDescription");
1050
+ },
974
1051
  packages: ["@h-ai/cache"]
975
1052
  },
976
1053
  "ai": {
977
1054
  id: "ai",
978
- name: "AI \u96C6\u6210",
979
- description: "LLM \u9002\u914D\u5668\u3001MCP \u534F\u8BAE\u3001\u6280\u80FD\u7CFB\u7EDF\u3001\u6D41\u5F0F\u54CD\u5E94",
980
- packages: ["@h-ai/ai"]
1055
+ get name() {
1056
+ return cliM("cli_featureAiName");
1057
+ },
1058
+ get description() {
1059
+ return cliM("cli_featureAiDescription");
1060
+ },
1061
+ packages: ["@h-ai/ai"],
1062
+ dependencies: ["db", "vecdb"]
981
1063
  },
982
1064
  "storage": {
983
1065
  id: "storage",
984
- name: "\u6587\u4EF6\u5B58\u50A8",
985
- description: "\u672C\u5730\u5B58\u50A8\u3001S3/OSS/COS \u4E91\u5B58\u50A8",
1066
+ get name() {
1067
+ return cliM("cli_featureStorageName");
1068
+ },
1069
+ get description() {
1070
+ return cliM("cli_featureStorageDescription");
1071
+ },
986
1072
  packages: ["@h-ai/storage"]
987
1073
  },
988
1074
  "crypto": {
989
1075
  id: "crypto",
990
- name: "\u52A0\u5BC6\u6A21\u5757",
991
- description: "\u56FD\u5BC6 SM2/SM3/SM4\u3001Argon2 \u5BC6\u7801\u54C8\u5E0C",
992
- packages: ["@h-ai/crypto"]
1076
+ get name() {
1077
+ return cliM("cli_featureCryptoName");
1078
+ },
1079
+ get description() {
1080
+ return cliM("cli_featureCryptoDescription");
1081
+ },
1082
+ packages: ["@h-ai/crypto"],
1083
+ dependencies: ["db"]
993
1084
  },
994
1085
  "audit": {
995
1086
  id: "audit",
996
- name: "\u5BA1\u8BA1\u65E5\u5FD7",
997
- description: "\u64CD\u4F5C\u5BA1\u8BA1\u3001\u5B89\u5168\u8FFD\u8E2A",
1087
+ get name() {
1088
+ return cliM("cli_featureAuditName");
1089
+ },
1090
+ get description() {
1091
+ return cliM("cli_featureAuditDescription");
1092
+ },
998
1093
  packages: ["@h-ai/audit"],
999
1094
  dependencies: ["db"]
1000
1095
  },
1001
1096
  "reach": {
1002
1097
  id: "reach",
1003
- name: "\u89E6\u8FBE\u670D\u52A1",
1004
- description: "\u90AE\u4EF6\u3001\u77ED\u4FE1\u3001\u5FAE\u4FE1\u7B49\u6D88\u606F\u89E6\u8FBE",
1098
+ get name() {
1099
+ return cliM("cli_featureReachName");
1100
+ },
1101
+ get description() {
1102
+ return cliM("cli_featureReachDescription");
1103
+ },
1005
1104
  packages: ["@h-ai/reach"]
1006
1105
  },
1007
1106
  "payment": {
1008
1107
  id: "payment",
1009
- name: "\u7EDF\u4E00\u652F\u4ED8",
1010
- description: "\u5FAE\u4FE1\u652F\u4ED8\u3001\u652F\u4ED8\u5B9D\u3001Stripe \u7EDF\u4E00\u63A5\u5165",
1108
+ get name() {
1109
+ return cliM("cli_featurePaymentName");
1110
+ },
1111
+ get description() {
1112
+ return cliM("cli_featurePaymentDescription");
1113
+ },
1011
1114
  packages: ["@h-ai/payment"],
1012
1115
  dependencies: ["db"]
1013
1116
  },
1014
1117
  "vecdb": {
1015
1118
  id: "vecdb",
1016
- name: "\u5411\u91CF\u6570\u636E\u5E93",
1017
- description: "\u5411\u91CF\u68C0\u7D22\u4E0E\u76F8\u4F3C\u5EA6\u641C\u7D22\uFF08LanceDB / pgvector / Qdrant\uFF09",
1119
+ get name() {
1120
+ return cliM("cli_featureVecdbName");
1121
+ },
1122
+ get description() {
1123
+ return cliM("cli_featureVecdbDescription");
1124
+ },
1018
1125
  packages: ["@h-ai/vecdb"]
1019
1126
  },
1020
1127
  "datapipe": {
1021
1128
  id: "datapipe",
1022
- name: "\u6570\u636E\u5904\u7406\u7BA1\u9053",
1023
- description: "\u6587\u672C\u6E05\u6D17\u3001\u5206\u5757\u4E0E\u53EF\u7EC4\u5408\u5904\u7406\u6D41\u6C34\u7EBF",
1129
+ get name() {
1130
+ return cliM("cli_featureDatapipeName");
1131
+ },
1132
+ get description() {
1133
+ return cliM("cli_featureDatapipeDescription");
1134
+ },
1024
1135
  packages: ["@h-ai/datapipe"]
1025
1136
  },
1026
1137
  "scheduler": {
1027
1138
  id: "scheduler",
1028
- name: "\u4EFB\u52A1\u8C03\u5EA6",
1029
- description: "Cron \u4EFB\u52A1\u6CE8\u518C\u3001\u6267\u884C\u4E0E\u65E5\u5FD7\u8FFD\u8E2A",
1139
+ get name() {
1140
+ return cliM("cli_featureSchedulerName");
1141
+ },
1142
+ get description() {
1143
+ return cliM("cli_featureSchedulerDescription");
1144
+ },
1030
1145
  packages: ["@h-ai/scheduler"],
1031
1146
  dependencies: ["db"]
1032
1147
  },
1033
1148
  "deploy": {
1034
1149
  id: "deploy",
1035
- name: "\u81EA\u52A8\u5316\u90E8\u7F72",
1036
- description: "\u7EDF\u4E00\u90E8\u7F72\u7F16\u6392\u4E0E\u591A\u5E73\u53F0\u53D1\u5E03\u652F\u6301",
1150
+ get name() {
1151
+ return cliM("cli_featureDeployName");
1152
+ },
1153
+ get description() {
1154
+ return cliM("cli_featureDeployDescription");
1155
+ },
1037
1156
  packages: ["@h-ai/deploy"]
1038
1157
  },
1039
1158
  "api-client": {
1040
1159
  id: "api-client",
1041
- name: "HTTP \u5BA2\u6237\u7AEF",
1042
- description: "\u7EDF\u4E00 API \u8C03\u7528\u3001\u9519\u8BEF\u5904\u7406\u4E0E\u6D41\u5F0F\u8BF7\u6C42\u652F\u6301",
1160
+ get name() {
1161
+ return cliM("cli_featureApiClientName");
1162
+ },
1163
+ get description() {
1164
+ return cliM("cli_featureApiClientDescription");
1165
+ },
1043
1166
  packages: ["@h-ai/api-client"]
1044
1167
  },
1045
1168
  "capacitor": {
1046
1169
  id: "capacitor",
1047
- name: "\u79FB\u52A8\u7AEF\u80FD\u529B",
1048
- description: "Capacitor \u539F\u751F\u6865\u63A5\u80FD\u529B\uFF08\u504F\u79FB\u52A8\u5E94\u7528\uFF09",
1170
+ get name() {
1171
+ return cliM("cli_featureCapacitorName");
1172
+ },
1173
+ get description() {
1174
+ return cliM("cli_featureCapacitorDescription");
1175
+ },
1049
1176
  packages: ["@h-ai/capacitor"]
1050
1177
  }
1051
1178
  };
@@ -1062,7 +1189,6 @@ var SELECTABLE_FEATURES = [
1062
1189
  "vecdb",
1063
1190
  "datapipe",
1064
1191
  "scheduler",
1065
- "deploy",
1066
1192
  "api-client",
1067
1193
  "capacitor"
1068
1194
  ];
@@ -1070,17 +1196,23 @@ var PROJECT_NAME_REGEX = /^[a-z0-9-]+$/;
1070
1196
  var PROJECT_TEMPLATES = {
1071
1197
  minimal: {
1072
1198
  name: "minimal",
1073
- description: "\u6700\u5C0F\u6A21\u677F \u2014 \u4EC5\u6838\u5FC3\u529F\u80FD",
1199
+ get description() {
1200
+ return cliM("cli_templateMinimalDescription");
1201
+ },
1074
1202
  features: []
1075
1203
  },
1076
1204
  default: {
1077
1205
  name: "default",
1078
- description: "\u6807\u51C6\u6A21\u677F \u2014 IAM + \u6570\u636E\u5E93 + \u7F13\u5B58",
1206
+ get description() {
1207
+ return cliM("cli_templateDefaultDescription");
1208
+ },
1079
1209
  features: ["iam", "db", "cache", "crypto"]
1080
1210
  },
1081
1211
  full: {
1082
1212
  name: "full",
1083
- description: "\u5B8C\u6574\u6A21\u677F \u2014 \u6240\u6709\u529F\u80FD",
1213
+ get description() {
1214
+ return cliM("cli_templateFullDescription");
1215
+ },
1084
1216
  features: [
1085
1217
  "iam",
1086
1218
  "db",
@@ -1101,7 +1233,9 @@ var PROJECT_TEMPLATES = {
1101
1233
  },
1102
1234
  custom: {
1103
1235
  name: "custom",
1104
- description: "\u81EA\u5B9A\u4E49 \u2014 \u9009\u62E9\u9700\u8981\u7684\u529F\u80FD",
1236
+ get description() {
1237
+ return cliM("cli_templateCustomDescription");
1238
+ },
1105
1239
  features: []
1106
1240
  }
1107
1241
  };
@@ -1109,39 +1243,40 @@ async function createProject(options) {
1109
1243
  const spinner = ora();
1110
1244
  try {
1111
1245
  const resolvedOptions = await resolveOptions(options);
1112
- if (!PROJECT_NAME_REGEX.test(resolvedOptions.name)) {
1113
- writeCliError(chalk.red(`\u9879\u76EE\u540D\u79F0\u4E0D\u5408\u6CD5: "${resolvedOptions.name}"\uFF0C\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26`));
1114
- return;
1115
- }
1246
+ if (!PROJECT_NAME_REGEX.test(resolvedOptions.name))
1247
+ throw new Error(cliM("cli_createInvalidName", { params: { name: resolvedOptions.name } }));
1116
1248
  const projectPath = path2.resolve(resolvedOptions.cwd ?? ".", resolvedOptions.name);
1117
1249
  if (await fileExists(projectPath)) {
1118
1250
  const { overwrite } = await prompts({
1119
1251
  type: "confirm",
1252
+ active: cliM("cli_yes"),
1253
+ inactive: cliM("cli_no"),
1120
1254
  name: "overwrite",
1121
- message: `\u76EE\u5F55 ${resolvedOptions.name} \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
1255
+ message: cliM("cli_createOverwrite", { params: { name: resolvedOptions.name } }),
1122
1256
  initial: false
1123
1257
  });
1124
1258
  if (!overwrite) {
1125
- writeCliMessage(chalk.yellow("\u5DF2\u53D6\u6D88"));
1259
+ writeCliMessage(chalk.yellow(cliM("cli_cancelled")));
1126
1260
  return;
1127
1261
  }
1128
1262
  await fse.remove(projectPath);
1129
1263
  }
1130
- spinner.start("\u521B\u5EFA\u9879\u76EE\u76EE\u5F55...");
1264
+ spinner.start(cliM("cli_createDirectory"));
1131
1265
  await fse.ensureDir(projectPath);
1132
1266
  spinner.succeed();
1133
- spinner.start("\u751F\u6210\u9879\u76EE\u6587\u4EF6...");
1267
+ spinner.start(cliM("cli_createFiles"));
1134
1268
  const context = buildTemplateContext({
1135
1269
  name: resolvedOptions.name,
1136
1270
  appType: resolvedOptions.appType,
1137
1271
  features: resolvedOptions.features,
1272
+ deploy: resolvedOptions.deploy,
1138
1273
  frontends: resolvedOptions.frontends,
1139
1274
  moduleConfigs: resolvedOptions.moduleConfigs,
1140
1275
  packageManager: resolvedOptions.packageManager
1141
1276
  });
1142
1277
  await generateFromTemplates(projectPath, context);
1143
1278
  {
1144
- const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-3XSFIXBP.js');
1279
+ const { generateConfigFile: generateConfigFile2, generateEnvExample, generateProductionEnvironment: generateProductionEnvironment2 } = await import('./cli-config-templates-W2LLPMB6.js');
1145
1280
  const configs = resolvedOptions.moduleConfigs;
1146
1281
  const isApiWorkspace = resolvedOptions.appType === "api";
1147
1282
  const isFullstack = resolvedOptions.appType === "fullstack";
@@ -1167,7 +1302,7 @@ async function createProject(options) {
1167
1302
  if (/^_.*\.yml$/.test(file))
1168
1303
  configContents[file.slice(1, -4)] = await fse.readFile(path2.join(configDir, file), "utf8");
1169
1304
  }
1170
- const envFeatures = resolvedOptions.features;
1305
+ const envFeatures = isFullstack ? [.../* @__PURE__ */ new Set([...resolvedOptions.features, "reach"])] : resolvedOptions.features;
1171
1306
  const envContents = generateEnvExample(envFeatures, configs, isFullstack, configContents);
1172
1307
  let envExample = isApiWorkspace ? envContents : isFullstack ? `${envContents}
1173
1308
  # First-start administrator password; must satisfy the IAM password policy.
@@ -1200,50 +1335,57 @@ ${runtimeExample.split("\n").map((line) => line && !line.startsWith("#") ? `# ${
1200
1335
  ${sharedVariables.join("\n")}
1201
1336
  `);
1202
1337
  for (const [mode, environment] of [["dev", "development"], ["test", "test"], ["prod", "production"]]) {
1203
- await writeFile(path2.join(envDirectory, `.env.${mode}`), `# \u516C\u5171 ${mode} \u73AF\u5883\u914D\u7F6E\uFF1B\u672C\u673A\u79C1\u6709\u503C\u653E .env.local\uFF0C\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF\u4F18\u5148\u3002
1338
+ const values = mode === "prod" ? generateProductionEnvironment2(envFeatures, configs) : `# \u516C\u5171 ${mode} \u73AF\u5883\u914D\u7F6E\uFF1B\u672C\u673A\u79C1\u6709\u503C\u653E .env.local\uFF0C\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF\u4F18\u5148\u3002
1204
1339
  HAI_CORE_ENV=${environment}
1205
- HAI_CORE_DEBUG=false
1206
- HAI_CORE_LOGGING_LEVEL=${mode === "test" ? "warn" : mode === "dev" ? "debug" : "info"}
1207
- `);
1340
+ HAI_CORE_LOGGING_LEVEL=${mode === "test" ? "warn" : "debug"}
1341
+ `;
1342
+ await writeFile(path2.join(envDirectory, `.env.${mode}`), values);
1208
1343
  }
1344
+ const privateEntries = [];
1209
1345
  if (isFullstack) {
1210
1346
  const password = `Aa1!${randomBytes(24).toString("base64url")}`;
1211
- await fse.writeFile(path2.join(projectPath, ".env.local"), `# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002
1212
- HAI_ADMIN_DEFAULT_PASSWORD=${password}
1213
- `, { mode: 384 });
1214
- } else {
1215
- await writeFile(path2.join(envDirectory, ".env.local"), "# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\uFF1B\u8986\u76D6\u516C\u5171\u57FA\u7840\u548C\u73AF\u5883\u914D\u7F6E\u3002\n");
1347
+ privateEntries.push(`HAI_ADMIN_DEFAULT_PASSWORD=${password}`);
1348
+ }
1349
+ if (isFullstack || isApiWorkspace && resolvedOptions.features.includes("crypto")) {
1350
+ const key = createECDH("SM2");
1351
+ key.generateKeys();
1352
+ const id = `bootstrap-${randomBytes(6).toString("hex")}`;
1353
+ const ring = { activeKeyId: id, keys: { [id]: { publicKey: key.getPublicKey("hex"), privateKey: key.getPrivateKey("hex").padStart(64, "0") } } };
1354
+ privateEntries.push(`HAI_CRYPTO_TRANSPORT_SERVERKEYRING='${JSON.stringify(ring)}'`);
1216
1355
  }
1356
+ await fse.writeFile(path2.join(envDirectory, ".env.local"), `# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\uFF1B\u90E8\u7F72\u51ED\u636E\u7531\u76EE\u6807\u73AF\u5883\u5355\u72EC\u63D0\u4F9B\u3002
1357
+ ${privateEntries.join("\n")}
1358
+ `, { mode: 384 });
1217
1359
  }
1218
- const { generateSkillFiles } = await import('./cli-skill-templates-TSYEQTCY.js');
1360
+ const { generateSkillFiles } = await import('./cli-skill-templates-US5CCAJ4.js');
1219
1361
  await generateSkillFiles(
1220
1362
  projectPath,
1221
1363
  resolvedOptions.features,
1222
1364
  resolvedOptions.appType,
1223
1365
  resolvedOptions.name
1224
1366
  );
1225
- const appTypeLabel = APP_TYPES[resolvedOptions.appType].name;
1367
+ const appTypeLabel = cliM(`cli_app${{ "admin": "Admin", "website": "Website", "h5": "H5", "api": "Api", "mobile-app": "Mobile", "fullstack": "Fullstack" }[resolvedOptions.appType]}Name`, { locale: "zh-CN" });
1226
1368
  const readmeContent = resolvedOptions.appType === "fullstack" ? generateFullstackReadme(resolvedOptions.name, resolvedOptions.packageManager, resolvedOptions.frontends) : resolvedOptions.appType === "api" ? generateApiWorkspaceReadme(resolvedOptions.name, resolvedOptions.packageManager) : generateReadme(resolvedOptions.name, appTypeLabel, resolvedOptions.packageManager, resolvedOptions.appType);
1227
1369
  await writeFile(
1228
1370
  path2.join(projectPath, "README.md"),
1229
- readmeContent
1371
+ resolvedOptions.deploy === false ? readmeContent.replace(/### 构建与部署[\s\S]*?(?=\n#{2,3} |$)/, "### \u6784\u5EFA\n\npnpm build\npnpm build --target docker\n\n\u672C\u9879\u76EE\u521B\u5EFA\u65F6\u5173\u95ED\u4E86\u90E8\u7F72\u5DE5\u5177\u3002\n") : readmeContent
1230
1372
  );
1231
1373
  spinner.succeed();
1232
1374
  if (resolvedOptions.git) {
1233
- spinner.start("\u521D\u59CB\u5316 Git \u4ED3\u5E93...");
1375
+ spinner.start(cliM("cli_createGit"));
1234
1376
  execSync("git init", { cwd: projectPath, stdio: "ignore" });
1235
1377
  if (resolvedOptions.gitRemote) {
1236
1378
  const gitRemote = resolvedOptions.gitRemote;
1237
1379
  if (!RE_HTTPS_URL.test(gitRemote) && !RE_GIT_SSH_URL.test(gitRemote)) {
1238
1380
  spinner.fail();
1239
- throw new Error(`Invalid gitRemote URL: ${gitRemote}`);
1381
+ throw new Error(cliM("cli_createRemoteFailure", { params: { remote: gitRemote } }));
1240
1382
  }
1241
1383
  execFileSync("git", ["remote", "add", "origin", gitRemote], { cwd: projectPath, stdio: "ignore" });
1242
1384
  }
1243
1385
  spinner.succeed();
1244
1386
  }
1245
1387
  if (resolvedOptions.install) {
1246
- spinner.start(`\u5B89\u88C5\u4F9D\u8D56 (${resolvedOptions.packageManager})...`);
1388
+ spinner.start(cliM("cli_createInstall", { params: { manager: resolvedOptions.packageManager } }));
1247
1389
  installDependencies(projectPath, resolvedOptions.packageManager);
1248
1390
  spinner.succeed();
1249
1391
  }
@@ -1258,10 +1400,10 @@ async function resolveOptions(options) {
1258
1400
  const useDefaults = options.yes ?? false;
1259
1401
  writeCliMessage("");
1260
1402
  writeCliMessage(chalk.bold.cyan(" \u{1F680} hai Agent Framework"));
1261
- writeCliMessage(chalk.gray(" AI-Native \xB7 Configuration-Driven \xB7 Security-First"));
1403
+ writeCliMessage(chalk.gray(cliM("cli_createTagline")));
1262
1404
  writeCliMessage("");
1263
1405
  const onCancel = () => {
1264
- writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1406
+ writeCliMessage(chalk.red(cliM("cli_cancelledLine")));
1265
1407
  process4.exit(1);
1266
1408
  };
1267
1409
  let projectName = options.name;
@@ -1271,13 +1413,13 @@ async function resolveOptions(options) {
1271
1413
  const { name } = await prompts({
1272
1414
  type: "text",
1273
1415
  name: "name",
1274
- message: "\u9879\u76EE\u540D\u79F0:",
1416
+ message: cliM("cli_createNamePrompt"),
1275
1417
  initial: "my-app",
1276
1418
  validate: (value) => {
1277
1419
  if (!value.trim())
1278
- return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
1420
+ return cliM("cli_createNameRequired");
1279
1421
  if (!PROJECT_NAME_REGEX.test(value))
1280
- return "\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
1422
+ return cliM("cli_createNamePattern");
1281
1423
  return true;
1282
1424
  }
1283
1425
  }, { onCancel });
@@ -1290,8 +1432,9 @@ async function resolveOptions(options) {
1290
1432
  const { appType } = await prompts({
1291
1433
  type: "select",
1292
1434
  name: "appType",
1293
- message: "\u5E94\u7528\u7C7B\u578B:",
1294
- hint: cliM("cli_selectHint"),
1435
+ message: cliM("cli_createTypePrompt"),
1436
+ hint: `
1437
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1295
1438
  choices: Object.entries(APP_TYPES).map(([key, t]) => ({
1296
1439
  title: t.name,
1297
1440
  description: formatPromptDescription(t.description),
@@ -1313,8 +1456,9 @@ async function resolveOptions(options) {
1313
1456
  const { template } = await prompts({
1314
1457
  type: "select",
1315
1458
  name: "template",
1316
- message: "\u9009\u62E9\u6A21\u677F:",
1317
- hint: cliM("cli_selectHint"),
1459
+ message: cliM("cli_createTemplatePrompt"),
1460
+ hint: `
1461
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1318
1462
  choices: Object.values(PROJECT_TEMPLATES).map((t) => ({
1319
1463
  title: t.name,
1320
1464
  description: formatPromptDescription(t.description),
@@ -1328,7 +1472,9 @@ async function resolveOptions(options) {
1328
1472
  let selectedFeatures = [];
1329
1473
  const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
1330
1474
  if (selectedAppType === "fullstack") {
1331
- selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features?.includes("deploy") ? ["deploy"] : []]);
1475
+ selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features ?? []]);
1476
+ } else if (options.features) {
1477
+ selectedFeatures = resolveFeatureDependencies(options.features);
1332
1478
  } else if (selectedTemplate === "custom" && !options.features && useDefaults) {
1333
1479
  selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
1334
1480
  } else if (selectedTemplate === "custom" && !options.features) {
@@ -1343,18 +1489,20 @@ async function resolveOptions(options) {
1343
1489
  name: "features",
1344
1490
  message: cliM("cli_featuresPrompt"),
1345
1491
  choices: featureChoices,
1346
- hint: cliM("cli_multiselectHint"),
1492
+ hint: `
1493
+ ${formatPromptDescription(cliM("cli_multiselectHint"))}`,
1347
1494
  instructions: false
1348
1495
  }, { onCancel });
1349
1496
  selectedFeatures = features || [];
1350
1497
  selectedFeatures = resolveFeatureDependencies(selectedFeatures);
1351
- } else if (selectedTemplate === "custom" && options.features) {
1352
- selectedFeatures = resolveFeatureDependencies(options.features);
1353
1498
  } else {
1354
1499
  const templateFeatures = PROJECT_TEMPLATES[selectedTemplate]?.features || [];
1355
1500
  const merged = /* @__PURE__ */ new Set([...templateFeatures, ...appTypeDefaults]);
1356
1501
  selectedFeatures = resolveFeatureDependencies(Array.from(merged));
1357
1502
  }
1503
+ selectedFeatures = selectedFeatures.filter((feature) => feature !== "deploy");
1504
+ if (options.deploy !== false)
1505
+ selectedFeatures.push("deploy");
1358
1506
  const moduleConfigs = selectedAppType === "fullstack" ? { ...buildDefaultModuleConfigs(selectedFeatures, projectName), ...options.moduleConfigs } : options.moduleConfigs ?? (useDefaults ? buildDefaultModuleConfigs(selectedFeatures, projectName) : await promptModuleConfigs(selectedFeatures, projectName));
1359
1507
  let addExamples = options.examples;
1360
1508
  if (addExamples === void 0 && useDefaults) {
@@ -1362,8 +1510,10 @@ async function resolveOptions(options) {
1362
1510
  } else if (addExamples === void 0) {
1363
1511
  const { examples } = await prompts({
1364
1512
  type: "confirm",
1513
+ active: cliM("cli_yes"),
1514
+ inactive: cliM("cli_no"),
1365
1515
  name: "examples",
1366
- message: "\u662F\u5426\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801?",
1516
+ message: cliM("cli_createExamplesPrompt"),
1367
1517
  initial: true
1368
1518
  }, { onCancel });
1369
1519
  addExamples = examples;
@@ -1376,10 +1526,12 @@ async function resolveOptions(options) {
1376
1526
  if (!packageManager) {
1377
1527
  const { pm } = await prompts({
1378
1528
  type: "select",
1529
+ hint: `
1530
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1379
1531
  name: "pm",
1380
- message: "\u5305\u7BA1\u7406\u5668:",
1532
+ message: cliM("cli_createManagerPrompt"),
1381
1533
  choices: [
1382
- { title: "pnpm (\u63A8\u8350)", value: "pnpm" },
1534
+ { title: cliM("cli_createPnpmRecommended"), value: "pnpm" },
1383
1535
  { title: "npm", value: "npm" },
1384
1536
  { title: "yarn", value: "yarn" }
1385
1537
  ],
@@ -1393,8 +1545,10 @@ async function resolveOptions(options) {
1393
1545
  } else if (install === void 0) {
1394
1546
  const { doInstall } = await prompts({
1395
1547
  type: "confirm",
1548
+ active: cliM("cli_yes"),
1549
+ inactive: cliM("cli_no"),
1396
1550
  name: "doInstall",
1397
- message: "\u662F\u5426\u5B89\u88C5\u4F9D\u8D56?",
1551
+ message: cliM("cli_createInstallPrompt"),
1398
1552
  initial: true
1399
1553
  }, { onCancel });
1400
1554
  install = doInstall;
@@ -1405,8 +1559,10 @@ async function resolveOptions(options) {
1405
1559
  } else if (git === void 0) {
1406
1560
  const { initGit } = await prompts({
1407
1561
  type: "confirm",
1562
+ active: cliM("cli_yes"),
1563
+ inactive: cliM("cli_no"),
1408
1564
  name: "initGit",
1409
- message: "\u662F\u5426\u521D\u59CB\u5316 Git?",
1565
+ message: cliM("cli_createGitPrompt"),
1410
1566
  initial: true
1411
1567
  }, { onCancel });
1412
1568
  git = initGit;
@@ -1418,14 +1574,14 @@ async function resolveOptions(options) {
1418
1574
  const { remote } = await prompts({
1419
1575
  type: "text",
1420
1576
  name: "remote",
1421
- message: "Git \u4ED3\u5E93\u5730\u5740 (\u53EF\u9009\uFF0C\u56DE\u8F66\u8DF3\u8FC7):",
1577
+ message: cliM("cli_createRemotePrompt"),
1422
1578
  initial: "",
1423
1579
  validate: (value) => {
1424
1580
  if (!value.trim())
1425
1581
  return true;
1426
1582
  if (RE_HTTPS_URL.test(value) || RE_GIT_SSH_URL.test(value))
1427
1583
  return true;
1428
- return "\u8BF7\u8F93\u5165\u5408\u6CD5\u7684 Git \u4ED3\u5E93\u5730\u5740 (https://... \u6216 git@...:...)";
1584
+ return cliM("cli_createRemoteInvalid");
1429
1585
  }
1430
1586
  }, { onCancel });
1431
1587
  gitRemote = remote?.trim() || "";
@@ -1436,6 +1592,7 @@ async function resolveOptions(options) {
1436
1592
  frontends: selectedFrontends,
1437
1593
  template: selectedTemplate ?? "default",
1438
1594
  features: selectedFeatures,
1595
+ deploy: options.deploy !== false,
1439
1596
  moduleConfigs,
1440
1597
  examples: addExamples ?? true,
1441
1598
  install: install ?? true,
@@ -1485,7 +1642,9 @@ async function resolveFrontendTargets(frontends, useDefaults, onCancel) {
1485
1642
  value: id,
1486
1643
  selected: DEFAULT_FULLSTACK_FRONTENDS2.includes(id)
1487
1644
  })),
1488
- hint: cliM("cli_multiselectHint"),
1645
+ // 独立行显示帮助,避免宽字符让 prompts 按字符数清除时遗留折行。
1646
+ hint: `
1647
+ ${formatPromptDescription(cliM("cli_multiselectHint"))}`,
1489
1648
  instructions: false
1490
1649
  }, { onCancel });
1491
1650
  return normalizeFrontendTargets(selectedFrontends ?? DEFAULT_FULLSTACK_FRONTENDS2);
@@ -1517,12 +1676,12 @@ function resolveFeatureDependencies(features) {
1517
1676
  async function promptModuleConfigs(features, projectName) {
1518
1677
  const configs = {};
1519
1678
  const onCancel = () => {
1520
- writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1679
+ writeCliMessage(chalk.red(cliM("cli_cancelledLine")));
1521
1680
  process4.exit(1);
1522
1681
  };
1523
1682
  writeCliMessage("");
1524
- writeCliMessage(chalk.bold(" \u2699 \u6A21\u5757\u914D\u7F6E"));
1525
- writeCliMessage(chalk.gray(" \u8BBE\u7F6E\u5173\u952E\u914D\u7F6E\u9879\uFF08\u56DE\u8F66\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF09"));
1683
+ writeCliMessage(chalk.bold(cliM("cli_createModuleConfig")));
1684
+ writeCliMessage(chalk.gray(cliM("cli_createModuleConfigHint")));
1526
1685
  writeCliMessage("");
1527
1686
  configs.core = await promptCoreConfig(projectName, onCancel);
1528
1687
  if (features.includes("db")) {
@@ -1543,27 +1702,32 @@ async function promptModuleConfigs(features, projectName) {
1543
1702
  return configs;
1544
1703
  }
1545
1704
  async function promptCoreConfig(projectName, onCancel) {
1546
- writeCliMessage(chalk.cyan(" [core] \u5E94\u7528\u57FA\u7840\u914D\u7F6E"));
1705
+ writeCliMessage(chalk.cyan(cliM("cli_createCoreConfig")));
1547
1706
  const { defaultLocale } = await prompts({
1548
1707
  type: "select",
1708
+ hint: `
1709
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1549
1710
  name: "defaultLocale",
1550
- message: "\u9ED8\u8BA4\u8BED\u8A00:",
1711
+ message: cliM("cli_createLocalePrompt"),
1551
1712
  choices: [
1552
- { title: "\u4E2D\u6587 (zh-CN)", value: "zh-CN" },
1553
- { title: "English (en-US)", value: "en-US" }
1713
+ { title: cliM("cli_createChineseLocale"), value: "zh-CN" },
1714
+ { title: cliM("cli_createEnglishLocale"), value: "en-US" },
1715
+ { title: cliM("cli_createJapaneseLocale"), value: "ja-JP" }
1554
1716
  ],
1555
1717
  initial: 0
1556
1718
  }, { onCancel });
1557
1719
  return { name: projectName, defaultLocale };
1558
1720
  }
1559
1721
  async function promptDbConfig(projectName, onCancel) {
1560
- writeCliMessage(chalk.cyan(" [db] \u6570\u636E\u5E93\u914D\u7F6E"));
1722
+ writeCliMessage(chalk.cyan(cliM("cli_createDbConfig")));
1561
1723
  const { type } = await prompts({
1562
1724
  type: "select",
1725
+ hint: `
1726
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1563
1727
  name: "type",
1564
- message: "\u6570\u636E\u5E93\u7C7B\u578B:",
1728
+ message: cliM("cli_createDbTypePrompt"),
1565
1729
  choices: [
1566
- { title: "SQLite (\u96F6\u914D\u7F6E\uFF0C\u9002\u5408\u5F00\u53D1)", value: "sqlite" },
1730
+ { title: cliM("cli_createSqliteOption"), value: "sqlite" },
1567
1731
  { title: "PostgreSQL", value: "postgresql" },
1568
1732
  { title: "MySQL", value: "mysql" }
1569
1733
  ],
@@ -1573,26 +1737,28 @@ async function promptDbConfig(projectName, onCancel) {
1573
1737
  const { database } = await prompts({
1574
1738
  type: "text",
1575
1739
  name: "database",
1576
- message: "SQLite \u6570\u636E\u5E93\u8DEF\u5F84:",
1740
+ message: cliM("cli_createSqlitePathPrompt"),
1577
1741
  initial: "./data/app.db"
1578
1742
  }, { onCancel });
1579
1743
  return { type, database };
1580
1744
  }
1581
1745
  const answers = await prompts([
1582
- { type: "text", name: "host", message: `${type === "postgresql" ? "PostgreSQL" : "MySQL"} \u4E3B\u673A:`, initial: "localhost" },
1583
- { type: "number", name: "port", message: "\u7AEF\u53E3:", initial: type === "postgresql" ? 5432 : 3306 },
1584
- { type: "text", name: "database", message: "\u6570\u636E\u5E93\u540D\u79F0:", initial: projectName }
1746
+ { type: "text", name: "host", message: cliM("cli_createDbHostPrompt", { params: { backend: type === "postgresql" ? "PostgreSQL" : "MySQL" } }), initial: "localhost" },
1747
+ { type: "number", name: "port", message: cliM("cli_createPortPrompt"), initial: type === "postgresql" ? 5432 : 3306 },
1748
+ { type: "text", name: "database", message: cliM("cli_createDatabasePrompt"), initial: projectName }
1585
1749
  ], { onCancel });
1586
1750
  return { type, host: answers.host, port: answers.port, database: answers.database };
1587
1751
  }
1588
1752
  async function promptCacheConfig(onCancel) {
1589
- writeCliMessage(chalk.cyan(" [cache] \u7F13\u5B58\u914D\u7F6E"));
1753
+ writeCliMessage(chalk.cyan(cliM("cli_createCacheConfig")));
1590
1754
  const { type } = await prompts({
1591
1755
  type: "select",
1756
+ hint: `
1757
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1592
1758
  name: "type",
1593
- message: "\u7F13\u5B58\u7C7B\u578B:",
1759
+ message: cliM("cli_createCacheTypePrompt"),
1594
1760
  choices: [
1595
- { title: "\u5185\u5B58\u7F13\u5B58 (\u96F6\u914D\u7F6E\uFF0C\u9002\u5408\u5F00\u53D1)", value: "memory" },
1761
+ { title: cliM("cli_createMemoryOption"), value: "memory" },
1596
1762
  { title: "Redis", value: "redis" }
1597
1763
  ],
1598
1764
  initial: 0
@@ -1600,27 +1766,29 @@ async function promptCacheConfig(onCancel) {
1600
1766
  if (type === "memory")
1601
1767
  return { type };
1602
1768
  const answers = await prompts([
1603
- { type: "text", name: "host", message: "Redis \u4E3B\u673A:", initial: "localhost" },
1604
- { type: "number", name: "port", message: "Redis \u7AEF\u53E3:", initial: 6379 }
1769
+ { type: "text", name: "host", message: cliM("cli_createRedisHostPrompt"), initial: "localhost" },
1770
+ { type: "number", name: "port", message: cliM("cli_createRedisPortPrompt"), initial: 6379 }
1605
1771
  ], { onCancel });
1606
1772
  return { type, host: answers.host, port: answers.port };
1607
1773
  }
1608
1774
  async function promptIamConfig(onCancel) {
1609
- writeCliMessage(chalk.cyan(" [iam] \u8BA4\u8BC1\u6388\u6743\u914D\u7F6E"));
1775
+ writeCliMessage(chalk.cyan(cliM("cli_createIamConfig")));
1610
1776
  const answers = await prompts([
1611
- { type: "confirm", name: "loginPassword", message: "\u542F\u7528\u5BC6\u7801\u767B\u5F55?", initial: true },
1612
- { type: "confirm", name: "loginOtp", message: "\u542F\u7528 OTP \u9A8C\u8BC1\u7801\u767B\u5F55?", initial: false }
1777
+ { type: "confirm", active: cliM("cli_yes"), inactive: cliM("cli_no"), name: "loginPassword", message: cliM("cli_createPasswordPrompt"), initial: true },
1778
+ { type: "confirm", active: cliM("cli_yes"), inactive: cliM("cli_no"), name: "loginOtp", message: cliM("cli_createOtpPrompt"), initial: false }
1613
1779
  ], { onCancel });
1614
1780
  return { loginPassword: answers.loginPassword, loginOtp: answers.loginOtp };
1615
1781
  }
1616
1782
  async function promptStorageConfig(onCancel) {
1617
- writeCliMessage(chalk.cyan(" [storage] \u6587\u4EF6\u5B58\u50A8\u914D\u7F6E"));
1783
+ writeCliMessage(chalk.cyan(cliM("cli_createStorageConfig")));
1618
1784
  const { type } = await prompts({
1619
1785
  type: "select",
1786
+ hint: `
1787
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1620
1788
  name: "type",
1621
- message: "\u5B58\u50A8\u7C7B\u578B:",
1789
+ message: cliM("cli_createStorageTypePrompt"),
1622
1790
  choices: [
1623
- { title: "\u672C\u5730\u5B58\u50A8 (\u96F6\u914D\u7F6E\uFF0C\u9002\u5408\u5F00\u53D1)", value: "local" },
1791
+ { title: cliM("cli_createLocalStorageOption"), value: "local" },
1624
1792
  { title: "S3 / MinIO / OSS", value: "s3" }
1625
1793
  ],
1626
1794
  initial: 0
@@ -1629,7 +1797,7 @@ async function promptStorageConfig(onCancel) {
1629
1797
  const { localPath } = await prompts({
1630
1798
  type: "text",
1631
1799
  name: "localPath",
1632
- message: "\u5B58\u50A8\u8DEF\u5F84:",
1800
+ message: cliM("cli_createStoragePathPrompt"),
1633
1801
  initial: "./data/uploads"
1634
1802
  }, { onCancel });
1635
1803
  return { type, localPath };
@@ -1637,15 +1805,17 @@ async function promptStorageConfig(onCancel) {
1637
1805
  return { type };
1638
1806
  }
1639
1807
  async function promptAiConfig(onCancel) {
1640
- writeCliMessage(chalk.cyan(" [ai] AI \u96C6\u6210\u914D\u7F6E"));
1808
+ writeCliMessage(chalk.cyan(cliM("cli_createAiConfig")));
1641
1809
  const { defaultProvider } = await prompts({
1642
1810
  type: "select",
1811
+ hint: `
1812
+ ${formatPromptDescription(cliM("cli_selectHint"))}`,
1643
1813
  name: "defaultProvider",
1644
- message: "AI Provider:",
1814
+ message: cliM("cli_createProviderPrompt"),
1645
1815
  choices: [
1646
1816
  { title: "OpenAI", value: "openai" },
1647
1817
  { title: "Anthropic (Claude)", value: "anthropic" },
1648
- { title: "\u5176\u4ED6 (OpenAI \u517C\u5BB9)", value: "custom" }
1818
+ { title: cliM("cli_createCompatibleAiOption"), value: "custom" }
1649
1819
  ],
1650
1820
  initial: 0
1651
1821
  }, { onCancel });
@@ -1653,7 +1823,7 @@ async function promptAiConfig(onCancel) {
1653
1823
  const { model } = await prompts({
1654
1824
  type: "text",
1655
1825
  name: "model",
1656
- message: "\u9ED8\u8BA4\u6A21\u578B:",
1826
+ message: cliM("cli_createModelPrompt"),
1657
1827
  initial: defaultModel
1658
1828
  }, { onCancel });
1659
1829
  return { defaultProvider, model };
@@ -1732,27 +1902,25 @@ function normalizeChildOutput(output) {
1732
1902
  return "";
1733
1903
  }
1734
1904
  function generateAiDevelopmentReadme() {
1735
- return `## AI \u8F85\u52A9\u5F00\u53D1
1905
+ return `### AI \u8F85\u52A9\u5F00\u53D1
1736
1906
 
1737
1907
  \u4ECE\u672C\u9879\u76EE\u6839\u76EE\u5F55\u6253\u5F00\u6240\u7528\u7F16\u7801\u5DE5\u5177\uFF0C\u5148\u8BFB\u53D6 [AGENTS.md](./AGENTS.md)\u3002Claude Code \u901A\u8FC7 [CLAUDE.md](./CLAUDE.md) \u52A0\u8F7D\u9879\u76EE\u89C4\u5219\uFF1BGitHub Copilot \u4F7F\u7528 [.github/copilot-instructions.md](./.github/copilot-instructions.md)\uFF0COpenCode \u4F7F\u7528 AGENTS.md \u548C [opencode.json](./opencode.json)\u3002
1738
1908
 
1739
- \u4E1A\u52A1\u4EE5\u7528\u6237\u5F53\u524D\u660E\u786E\u8981\u6C42\u53CA\u5176\u6307\u5B9A\u7684\u9700\u6C42/\u8BBE\u8BA1\u6587\u6863\u4E3A\u51C6\u3002\u6A21\u677F AGENTS\u3001README\u3001skills\u3001\u751F\u6210\u4EE3\u7801\u4E0E\u6D4B\u8BD5\u4E2D\u7684\u89D2\u8272/\u6743\u9650\u3001\u6D41\u7A0B\u3001\u9650\u989D\u548C\u67B6\u6784\u662F\u53EF\u8C03\u6574\u7684\u521D\u59CB\u65B9\u6848\uFF1B\u5BA1\u67E5\u5C06\u5DEE\u5F02\u5217\u4E3A\u6A21\u677F\u9002\u914D\u9879\uFF0C\u5B9E\u65BD\u6309\u6388\u6743\u540C\u6B65\u51B2\u7A81\u4EE3\u7801\u3001\u6D4B\u8BD5\u4E0E\u8FC7\u671F\u89C4\u5219\uFF0C\u4E0D\u8981\u6C42\u628A\u9700\u6C42\u6539\u56DE\u6A21\u677F\u3002\u6846\u67B6 API \u4EE5\u5F53\u524D\u5B89\u88C5\u7248\u672C\u516C\u5F00\u5951\u7EA6\u4E3A\u51C6\uFF0C\u670D\u52A1\u7AEF\u6388\u6743\u3001\u8D44\u6E90\u9694\u79BB\u3001\u8F93\u5165\u6821\u9A8C\u548C\u51ED\u636E\u4FDD\u62A4\u4ECD\u9700\u843D\u5B9E\u3002
1740
-
1741
- Codex\u3001Cursor\u3001Copilot\u3001OpenCode \u4ECE .agents/skills/ \u53D1\u73B0\u5171\u4EAB\u6280\u80FD\uFF1BClaude Code \u901A\u8FC7 CLAUDE.md \u5F15\u7528 AGENTS.md\uFF0C\u5E76\u6309\u9879\u76EE\u6307\u5F15\u8BFB\u53D6 .agents/skills/ \u7684\u540C\u4E00\u4EFD\u6B63\u6587\u3002\u9700\u6C42\u6216\u8BBE\u8BA1\u6587\u6863\u5728\u751F\u6210\u524D\u4F7F\u7528 [\u6587\u6863\u5BA1\u67E5 Skill](./.agents/skills/hai-spec-review/SKILL.md) \u4E25\u683C\u68C0\u67E5\u5B8C\u5907\u6027\u3001\u6B67\u4E49\u3001\u6280\u672F\u6B63\u786E\u6027\u548C\u547D\u540D\u4E00\u81F4\u6027\uFF0C\u95EE\u9898\u89E3\u51B3\u540E\u518D\u751F\u6210\u3002\u666E\u901A\u65B0\u589E\u3001\u4FEE\u6539\u3001\u4FEE\u590D\u4EFB\u52A1\u4F7F\u7528 [\u5E94\u7528\u5F00\u53D1 Skill](./.agents/skills/hai-app-create/SKILL.md) \u548C [\u5F00\u53D1\u89C4\u8303\u4E0E\u6846\u67B6\u80FD\u529B\u8DEF\u7531](./.agents/skills/hai-app-create/development.md)\uFF1B\u5177\u4F53\u6A21\u5757\u518D\u6309\u9700\u8BFB\u53D6\uFF0C\u4E0D\u80FD\u628A Skill \u5DF2\u4EA4\u4ED8\u5F53\u6210\u4F9D\u8D56\u5DF2\u5B89\u88C5\u6216\u914D\u7F6E\u5DF2\u542F\u7528\u3002
1909
+ \u751F\u6210\u7684\u9875\u9762\u548C\u4E1A\u52A1\u89C4\u5219\u662F\u8D77\u70B9\uFF0C\u53EF\u4EE5\u6309\u9700\u6C42\u8C03\u6574\u3002\u628A\u529F\u80FD\u3001\u6743\u9650\u548C\u9A8C\u6536\u6761\u4EF6\u544A\u8BC9 AI\uFF0C\u518D\u8BA9\u5B83\u8BFB\u53D6\u5BF9\u5E94\u6280\u80FD\u4E0E\u5F53\u524D\u5B89\u88C5\u7248\u672C\u7684 API\u3002
1742
1910
 
1743
- \u6839\u636E\u6587\u6863\u89C4\u5212\u3001\u5B9E\u65BD\u3001\u8FD4\u4FEE\u6216\u6309\u5DF2\u63D0\u4EA4\u6587\u6863\u5DEE\u5F02\u540C\u6B65\u5B9E\u73B0\u65F6\uFF0C\u4F7F\u7528 [\u6587\u6863\u5B9E\u65BD Skill](./.agents/skills/hai-spec-implement/SKILL.md)\uFF1A\u7EF4\u62A4 docs/task-list.md \u4E0E docs/tasks/\uFF0C\u8BB0\u5F55\u5DF2\u9A8C\u6536\u6587\u6863\u548C\u5B9E\u73B0\u7684 Git SHA\uFF0C\u6BD4\u8F83\u56FA\u5B9A\u76EE\u6807\u7248\u672C\u540E\u66F4\u65B0\u539F\u4EFB\u52A1\u6216\u65B0\u5EFA\u4EFB\u52A1\uFF0C\u6BCF\u9879\u72EC\u7ACB\u5206\u652F TDD\u3001\u9A8C\u8BC1\u5E76\u672C\u5730\u5408\u5E76\uFF1B\u5168\u90E8\u5DEE\u5F02\u4E0E\u6574\u4F53\u9A8C\u6536\u901A\u8FC7\u540E\u624D\u63A8\u8FDB\u57FA\u7EBF\u3002\u8FD4\u4FEE\u6CBF\u7528\u539F\u4EFB\u52A1\u4E0E\u5206\u652F\u3002\u4EC5\u89C4\u5212\u4E0D\u5F00\u59CB\u5B9E\u73B0\uFF0C\u6307\u5B9A\u4EFB\u52A1\u5148\u6838\u9A8C\u4F9D\u8D56\u3002
1911
+ \u666E\u901A\u529F\u80FD\u5F00\u53D1\u4F7F\u7528 [\u5E94\u7528\u5F00\u53D1 Skill](./.agents/skills/hai-app-create/SKILL.md)\u3002\u9700\u6C42\u6587\u6863\u8F83\u591A\u65F6\uFF0C\u5148\u7528 [\u6587\u6863\u5BA1\u67E5 Skill](./.agents/skills/hai-spec-review/SKILL.md) \u627E\u51FA\u7F3A\u5931\u548C\u77DB\u76FE\uFF0C\u518D\u7528 [\u6587\u6863\u5B9E\u65BD Skill](./.agents/skills/hai-spec-implement/SKILL.md) \u7EF4\u62A4 docs/task-list.md \u4E0E docs/tasks/\uFF0C\u9010\u9879\u5B9E\u73B0\u3001\u6D4B\u8BD5\u548C\u4EA4\u4ED8\u3002
1744
1912
 
1745
- \u4F18\u5148\u590D\u7528\u9002\u7528\u7684 hai-framework \u516C\u5F00 API \u4E0E\u5DF2\u6709\u4E1A\u52A1\u5B9E\u73B0\u3002\u5B8C\u6210\u65F6\u8BF4\u660E\u590D\u7528\u7684\u80FD\u529B\u3001\u76F8\u5173\u80FD\u529B\u672A\u91C7\u7528\u7684\u539F\u56E0\uFF0C\u4EE5\u53CA\u5B89\u5168\u8FB9\u754C\u548C\u7C7B\u578B\u3001\u914D\u7F6E\u3001\u6587\u6848\u3001\u8C03\u7528\u65B9\u7684\u540C\u6B65\u60C5\u51B5\u3002\u8D28\u91CF\u9A8C\u8BC1\u6309\u5F53\u524D package.json \u6267\u884C typecheck \u2192 lint \u2192 build \u2192 test\uFF1BUI\u3001\u8DEF\u7531\u4E0E\u7AEF\u5230\u7AEF\u6D41\u7A0B\u53E6\u8FD0\u884C test:e2e\uFF0C\u672A\u8FD0\u884C\u9879\u8BF4\u660E\u539F\u56E0\u3002\u5B89\u5168\u4E0E\u8D28\u91CF\u89C4\u5219\u65E2\u8981\u6309\u89C4\u8303\u5BA1\u67E5\uFF0C\u4E5F\u8981\u8FD0\u884C\u95E8\u7981\u9A8C\u8BC1\u3002
1746
-
1747
- \u5DF2\u6709 hai \u9879\u76EE\u53EF\u8FD0\u884C hai init \u8865\u9F50\u7F3A\u5931\u7684 AI \u652F\u6301\u6587\u4EF6\u4E0E\u53C2\u8003\u8D44\u6E90\uFF1B\u5DF2\u6709 AGENTS.md\u3001Skill \u548C\u81EA\u5B9A\u4E49\u89C4\u5219\u4F1A\u4FDD\u7559\uFF0C\u5347\u7EA7\u65F6\u9700\u5C06\u4E0A\u8FF0\u9700\u6C42\u4F18\u5148\u89C4\u5219\u3001\u666E\u901A\u5F00\u53D1\u5165\u53E3\u4E0E\u89C4\u8303\u663E\u5F0F\u5408\u5E76\u5230\u65E7\u6307\u5F15\uFF0C\u5E76\u4FEE\u6B63\u5DF2\u88AB\u9700\u6C42\u66FF\u4EE3\u7684\u6A21\u677F\u7EA6\u675F\u3002
1913
+ \u5DF2\u6709\u9879\u76EE\u53EF\u8FD0\u884C hai init \u8865\u9F50\u7F3A\u5931\u7684 AI \u5165\u53E3\uFF1B\u5B83\u4FDD\u7559\u5DF2\u6709\u81EA\u5B9A\u4E49\u5185\u5BB9\u3002\u5347\u7EA7\u6846\u67B6\u4F7F\u7528 [\u6846\u67B6\u5347\u7EA7 Skill](./.agents/skills/hai-framework-sync/SKILL.md)\uFF0C\u540C\u65F6\u6838\u5BF9\u4F9D\u8D56\u3001\u914D\u7F6E\u3001\u4EE3\u7801\u548C\u6280\u80FD\u3002
1748
1914
 
1749
1915
  `;
1750
1916
  }
1751
1917
  function generateEnvironmentReadme() {
1752
- return `## \u5206\u73AF\u5883\u914D\u7F6E
1918
+ return `### \u5206\u73AF\u5883\u914D\u7F6E
1753
1919
 
1754
1920
  \u8FD0\u884C\u53C2\u6570\u5728 \`.env.example\` \u4E2D\u9010\u9879\u8BF4\u660E\u7528\u9014\u3001\u679A\u4E3E\u548C\u8303\u56F4\u3002\`.env\` \u4E3A\u516C\u5171\u57FA\u7840\uFF0C\`.env.dev\` / \`.env.test\` / \`.env.prod\` \u4E3A\u73AF\u5883\u8986\u76D6\uFF0C\u5747\u63D0\u4EA4 Git\uFF1B\`.env.local\` \u4E3A\u672C\u673A\u79C1\u6709\u8986\u76D6\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002\u4F18\u5148\u7EA7\u4E3A\u8FDB\u7A0B\u73AF\u5883 > \`.env.local\` > \u73AF\u5883\u6587\u4EF6 > \`.env\`\u3002YAML \u663E\u5F0F\u5F15\u7528 \`HAI_<\u914D\u7F6E\u540D>_<\u8DEF\u5F84>\` \u5E76\u4FDD\u7559\u672C\u5730\u56DE\u9000\uFF0CcamelCase \u4E0D\u62C6\u8BCD\u3002\u5B8C\u6574\u547D\u4EE4\u3001\u52A0\u8F7D\u4F18\u5148\u7EA7\u548C Provider \u5207\u6362\u89C1 [\u73AF\u5883\u914D\u7F6E](.agents/skills/hai-app-create/development.md#\u73AF\u5883\u914D\u7F6E)\u3002
1755
1921
 
1922
+ \u751F\u4EA7 .env.prod \u6309\u5DF2\u542F\u7528\u6A21\u5757\u9009\u62E9\u5171\u4EAB PostgreSQL\uFF08\u660E\u786E\u9009\u62E9 MySQL \u65F6\u4FDD\u7559\uFF09\u3001Redis\u3001S3\u3001Qdrant\uFF0C\u9ED8\u8BA4\u4F7F\u7528 Compose \u5185\u90E8\u670D\u52A1\u540D db/cache/storage/vecdb\u3002\u5BC6\u7801\u3001S3 \u51ED\u636E\u3001\u6A21\u578B Key \u4E0E\u53D7\u4FDD\u62A4 Qdrant \u7684 Key \u53EA\u7531\u79C1\u6709\u73AF\u5883\u6CE8\u5165\uFF1B\u5BB9\u5668\u5916\u8FD0\u884C\u8986\u76D6\u5185\u90E8\u8FDE\u63A5\u5730\u5740\u3002\u751F\u4EA7 Reach \u5FC5\u987B\u6CE8\u5165\u771F\u5B9E HAI_REACH_PROVIDERS JSON\uFF0C\u516C\u5171\u7A7A\u6570\u7EC4\u4F1A\u62D2\u7EDD\u542F\u52A8\u3002AI \u81EA\u52A8\u8865\u5168 db+vecdb \u4F9D\u8D56\uFF0C\u5E76\u5728\u4E8C\u8005\u5C31\u7EEA\u540E\u521D\u59CB\u5316\uFF0C\u5931\u8D25\u9006\u5E8F\u6E05\u7406\u3002
1923
+
1756
1924
  | \u7528\u9014 | \u547D\u4EE4 |
1757
1925
  | --- | --- |
1758
1926
  | \u5F00\u53D1 / \u6D4B\u8BD5\u6A21\u5F0F / \u751F\u4EA7\u6A21\u5F0F\u9884\u89C8 | \`pnpm dev\` / \`dev:test\` / \`dev:prod\` |
@@ -1764,17 +1932,58 @@ function generateEnvironmentReadme() {
1764
1932
 
1765
1933
  Vite \u5BA2\u6237\u7AEF\u4F7F\u7528 \`preview\` \u9884\u89C8\u9759\u6001\u4EA7\u7269\uFF0CNode \u670D\u52A1\u4F7F\u7528 \`start\`\uFF1B\u7EAF contract/shared \u5E93\u53EA\u63D0\u4F9B\u9002\u7528\u7684\u6784\u5EFA\u548C\u8D28\u91CF\u547D\u4EE4\u3002API workspace \u7684\u73AF\u5883\u6587\u4EF6\u4F4D\u4E8E \`apps/<name>-service\`\uFF0Cfullstack \u4F4D\u4E8E\u5DE5\u7A0B\u6839\u3002`;
1766
1934
  }
1935
+ function generateBuildDeploymentReadme(pm, appDir) {
1936
+ return `### \u6784\u5EFA\u4E0E\u90E8\u7F72
1937
+
1938
+ \u521B\u5EFA\u65F6\u9ED8\u8BA4\u5305\u542B\u90E8\u7F72\u5DE5\u5177\uFF1B\u4E0D\u9700\u8981\u65F6\u4F7F\u7528 hai create --no-deploy\u3002
1939
+ \u6BCF\u4E2A\u955C\u50CF\u5BF9\u5E94 images/ \u4E2D\u4E00\u4E2A Dockerfile\uFF0Chai.build.json \u7528 dockerfile \u6307\u5B9A\u6587\u4EF6\u3002
1940
+ \u4E0B\u8F7D\u3001\u5B89\u88C5\u548C\u6574\u7406\u751F\u4EA7\u4F9D\u8D56\u590D\u7528\u540C\u4E00\u4EFD\u5305\u7F13\u5B58\u4E0E SQLite \u9A71\u52A8\u7F13\u5B58\uFF0C\u907F\u514D\u91CD\u590D\u4E0B\u8F7D\u3002
1941
+ \u9ED8\u8BA4\u5BB9\u5668\u7528\u4E8E CPU \u8FD0\u884C\u6216\u8FDE\u63A5\u8FDC\u7A0B AI \u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4E0B\u8F7D GPU \u8FD0\u884C\u5E93\uFF1B\u9700\u8981\u4F7F\u7528\u90E8\u7F72\u673A\u5668\u4E0A\u7684 GPU \u65F6\uFF0C\u81EA\u884C\u5B9A\u5236\u955C\u50CF\u548C\u5BB9\u5668\u8FD0\u884C\u53C2\u6570\u3002
1942
+
1943
+ \`\`\`sh
1944
+ ${pm} build
1945
+ ${pm} build --target docker
1946
+ # \u6784\u5EFA\u548C\u90E8\u7F72\u4E00\u6B21\u5B8C\u6210
1947
+ ${pm} exec hai deploy ${appDir} --env test --build
1948
+ # \u590D\u7528\u5DF2\u6709\u955C\u50CF\uFF1B\u53EA\u751F\u6210\u5B8C\u6574\u79BB\u7EBF\u5305\u53CA\u8DE8\u5E73\u53F0\u90E8\u7F72\u811A\u672C
1949
+ ${pm} exec hai deploy ${appDir} --env prod --bundle-only
1950
+ # \u53EA\u66F4\u65B0\u524D\u7AEF\uFF08\u5DE5\u7A0B\u5B58\u5728 web \u955C\u50CF\u65F6\uFF09
1951
+ ${pm} build --target docker --images web
1952
+ ${pm} exec hai deploy ${appDir} --env prod --services web
1953
+ \`\`\`
1954
+
1955
+ \u6784\u5EFA\u4E0D\u8BFB\u53D6\u90E8\u7F72\u73AF\u5883\uFF1B\u4EA7\u7269\u4F4D\u4E8E .hai/build/docker/<buildId>\uFF0C\u7528 --artifact-dir \u7CBE\u786E\u9009\u62E9\u3002
1956
+ \u5E94\u7528\u6839 package.json.version \u9A71\u52A8\u7248\u672C\uFF0C\u540C\u7248\u672C\u5141\u8BB8\u91CD\u65B0\u6784\u5EFA\u548C\u53D1\u5E03\uFF1B\u6E05\u5355\u8BB0\u5F55\u72EC\u7ACB\u6784\u5EFA\u7F16\u53F7\u4E0E\u6821\u9A8C\u548C\u3002
1957
+ \u5B8C\u6574\u79BB\u7EBF\u5305\u81EA\u52A8\u643A\u5E26\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u955C\u50CF\uFF1B\u51C6\u5907\u4E2D\u95F4\u4EF6\u955C\u50CF\u9700\u8981\u6784\u5EFA\u673A\u6709\u53EF\u7528 Podman/Docker\uFF0C\u7F3A\u955C\u50CF\u65F6\u5728\u6253\u5305\u9636\u6BB5\u62C9\u53D6\u3002
1958
+ \u670D\u52A1\u5668\u9700\u8981 Linux\u3001Docker/Podman\u3001Docker Compose v2 \u6216 podman-compose 1.6.0 \u53CA\u4EE5\u4E0A\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\u3001util-linux flock \u548C\u652F\u6301 flock \u7684\u6587\u4EF6\u7CFB\u7EDF\uFF0C\u542F\u52A8\u9636\u6BB5\u4E0D\u8054\u7F51\u62C9\u53D6\u3002
1959
+ \u56FA\u5B9A .deploy.lock \u5185\u6838\u9501\u8986\u76D6\u90E8\u7F72\u53CA\u6062\u590D\uFF1B\u7981\u6B62\u5220\u9664\u6216\u66FF\u6362\uFF0C\u6240\u6709\u6301\u6709\u8FDB\u7A0B\u9000\u51FA\u6216\u91CD\u542F\u540E\u81EA\u52A8\u91CA\u653E\u3002\u5F3A\u6740\u540E\u987B\u6838\u5BF9\u7ED3\u679C\u6587\u4EF6\u3001current \u548C\u5BB9\u5668\u72B6\u6001\uFF0C\u518D\u7528\u65B0\u5305\u6062\u590D\u3002
1960
+ tar \u65C1\u7684\u90E8\u7F72\u76EE\u5F55\u63D0\u4F9B deploy.ps1\uFF08Windows\uFF09\u548C deploy.sh\uFF08Linux/macOS\uFF09\uFF0C\u6309\u5DF2\u4FDD\u5B58\u7684 SSH \u53C2\u6570\u4E0A\u4F20\u3001\u5BFC\u5165\u3001\u542F\u52A8\u5E76\u9A8C\u8BC1 HTTP\u3002
1961
+ \u5B8C\u5168\u9694\u79BB\u65F6\u624B\u5DE5\u642C\u8FD0 tar\uFF0C\u72EC\u7ACB\u76EE\u5F55\u89E3\u5305\u540E\u6267\u884C sh apply.sh\uFF0C\u6216\u4F20\u5165\u90E8\u7F72\u76EE\u5F55\u8986\u76D6\u9ED8\u8BA4\u4F4D\u7F6E\u3002
1962
+ \u90E8\u5206\u66F4\u65B0\u8981\u6C42\u5DF2\u6709\u6210\u529F\u7684\u5B8C\u6574\u90E8\u7F72\uFF0C\u4FDD\u7559\u672A\u9009\u670D\u52A1\u53CA\u6570\u636E\u5377\uFF1B\u5931\u8D25\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u7801\uFF0C\u5DF2\u6709\u90E8\u7F72\u5C1D\u8BD5\u6062\u590D\u9009\u4E2D\u670D\u52A1\u3002
1963
+ \u4E2D\u95F4\u4EF6\u8FDE\u63A5\u9F50\u5168\u65F6\u4F7F\u7528 --skip-middleware \u8DF3\u8FC7\u914D\u7F6E\u5411\u5BFC\u3002\u5305\u4E2D\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002
1964
+ `;
1965
+ }
1767
1966
  function generateReadme(name, appTypeLabel, pm, appType) {
1768
1967
  const install = getInstallCommand(pm);
1769
1968
  return `# ${name}
1770
1969
 
1970
+ ## \u7B80\u4ECB
1971
+
1771
1972
  \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684${appTypeLabel}\u5E94\u7528\u3002
1772
1973
 
1773
- ## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1974
+ ## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
1774
1975
 
1775
- \u9875\u9762\u7EDF\u4E00\u7EE7\u627F\u6839\u5E03\u5C40\u7684 header/footer\u3002${appType === "mobile-app" ? "\u9875\u9762\u653E src/lib/pages/**/*.svelte\uFF0Chash \u8DEF\u7531\u81EA\u52A8\u53D1\u73B0\uFF0C\u5237\u65B0\u4E0E\u524D\u8FDB\u540E\u9000\u6062\u590D\u5F53\u524D\u9875\u3002" : "\u4F7F\u7528 SvelteKit \u81EA\u52A8\u6587\u4EF6\u8DEF\u7531\uFF0C\u65B0\u589E +page.svelte \u65E0\u9700\u7EF4\u62A4\u8DEF\u7531\u8868\uFF1B\u5237\u65B0\u5B9A\u4F4D\u4E0E\u670D\u52A1\u7AEF\u9274\u6743\u4FDD\u6301\u4E00\u81F4\u3002"}
1976
+ - \u5F00\u53D1\u670D\u52A1\u5668\u3001\u6E90\u7801\u7F16\u8BD1\u4E0E\u6D4F\u89C8\u5668\u6D4B\u8BD5\u3002
1977
+ - \u6309\u6240\u9009\u6A21\u5757\u63D0\u4F9B\u9875\u9762\u548C\u670D\u52A1\u914D\u7F6E\u3002
1978
+ - \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\uFF0C\u4FBF\u4E8E\u4EA4\u4ED8\u3002
1776
1979
 
1777
- ## \u5F00\u53D1
1980
+ ## \u4F7F\u7528\u5165\u95E8
1981
+
1982
+ ### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1983
+
1984
+ \u9875\u9762\u7EDF\u4E00\u7EE7\u627F\u6839\u5E03\u5C40\u7684 header/footer\u3002${appType === "mobile-app" ? "\u9875\u9762\u653E src/lib/pages/**/*.svelte\uFF0Chash \u8DEF\u7531\u81EA\u52A8\u53D1\u73B0\uFF0C\u5237\u65B0\u4E0E\u524D\u8FDB\u540E\u9000\u6062\u590D\u5F53\u524D\u9875\u3002" : "\u4F7F\u7528 SvelteKit \u81EA\u52A8\u6587\u4EF6\u8DEF\u7531\uFF0C\u65B0\u589E +page.svelte \u65E0\u9700\u7EF4\u62A4\u8DEF\u7531\u8868\uFF1B\u5237\u65B0\u5B9A\u4F4D\u4E0E\u670D\u52A1\u7AEF\u9274\u6743\u4FDD\u6301\u4E00\u81F4\u3002"}${appType === "mobile-app" ? "\n\n\u9009\u62E9 IAM \u6216 crypto \u7684\u79FB\u52A8\u5BA2\u6237\u7AEF\u9ED8\u8BA4\u542F\u7528\u52A0\u5BC6 transport\uFF0C\u5148\u521D\u59CB\u5316 crypto\uFF0C\u518D\u521D\u59CB\u5316 typed API client\uFF1B\u7ED3\u675F\u65F6\u6309\u76F8\u53CD\u987A\u5E8F\u5173\u95ED\u3002\u4EC5\u660E\u786E\u8FDE\u63A5\u5339\u914D\u7684\u660E\u6587 API \u65F6\u8BBE\u7F6E PUBLIC_API_TRANSPORT=off\uFF1BPUBLIC_API_KEY_EXCHANGE_PATH \u4EC5\u8986\u76D6\u516C\u5F00\u534F\u5546\u5B50\u8DEF\u5F84\u3002\u63E1\u624B\u5931\u8D25\u4E0D\u4F1A\u9000\u56DE\u660E\u6587\uFF0C\u670D\u52A1\u7AEF\u79C1\u94A5\u4E0D\u8FDB\u5165\u5BA2\u6237\u7AEF\u3002\u672A\u9009\u62E9 crypto \u7684\u5BA2\u6237\u7AEF\u4FDD\u7559\u660E\u6587 API \u6A21\u5F0F\u3002" : ""}
1985
+
1986
+ ### \u5F00\u53D1
1778
1987
 
1779
1988
  \`\`\`bash
1780
1989
  ${install}
@@ -1783,46 +1992,57 @@ ${getRunCommand(pm, "dev")}
1783
1992
 
1784
1993
  ${generateEnvironmentReadme()}
1785
1994
 
1786
- ## \u6784\u5EFA
1995
+ ### \u6784\u5EFA
1787
1996
 
1788
1997
  \`\`\`bash
1789
1998
  ${getRunCommand(pm, "build")}
1790
- ${getRunCommand(pm, "i18n:compile")}
1999
+ ${getRunCommand(pm, "paraglide:compile")}
1791
2000
  ${getRunCommand(pm, "preview")}
1792
2001
  \`\`\`
1793
2002
 
1794
- ## \u6784\u5EFA\u4E0E\u90E8\u7F72
2003
+ ${generateBuildDeploymentReadme(pm, `.`)}
1795
2004
 
1796
- \`\`\`bash
1797
- # \u9ED8\u8BA4\u53EA\u7F16\u8BD1\u6E90\u7801\uFF1B\u4E0D\u9009\u62E9\u90E8\u7F72\u73AF\u5883
1798
- ${pm} build
1799
- # \u6784\u5EFA\u5E76\u5BFC\u51FA API/Web \u955C\u50CF\uFF0Cauto \u4F18\u5148 Podman\u3001\u5176\u6B21 Docker
1800
- ${pm} build --target docker
1801
- # \u9009\u62E9\u73AF\u5883\u3001\u4E2D\u95F4\u4EF6\u5E76\u4E0A\u4F20\u4E00\u4E2A Compose tar\uFF1B\u4E0D\u4F1A\u91CD\u65B0\u6784\u5EFA
1802
- ${pm} exec hai deploy . --env test
1803
- # \u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u5668\u65F6\u53EA\u751F\u6210\u5305\uFF0C\u6309\u8F93\u51FA\u8BF4\u660E\u624B\u5DE5\u642C\u8FD0\u548C\u542F\u52A8
1804
- ${pm} exec hai deploy . --env prod --bundle-only
1805
- \`\`\`
1806
-
1807
- \u955C\u50CF\u6784\u5EFA\u914D\u7F6E\u89C1 \`hai.build.json\`\u3002\u6784\u5EFA\u4EA7\u7269\u4FDD\u5B58\u5728 \`.hai/build/docker/<buildId>\`\uFF0C\u53EF\u7528 \`--artifact-dir\` \u9009\u62E9\u540C\u4E00\u4EFD\u4EA7\u7269\u90E8\u7F72\u5230 test/prod\u3002\u90E8\u7F72\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u80FD\u63D0\u4EA4 Git\u3002\u73AF\u5883\u5DF2\u914D\u7F6E\u5B8C\u5907\u53EF\u52A0 \`--skip-middleware\`\u3002\u670D\u52A1\u8FD0\u884C\u6570\u636E\u7531 volume \u6301\u4E45\u5316\uFF0C\u955C\u50CF\u4E0D\u6253\u5305\u672C\u673A data\u3002API/Web \u8054\u5408\u90E8\u7F72\u516C\u5F00 Web \u5165\u53E3\u5E76\u4EE3\u7406\u540C\u6E90 API\uFF1B\u6CA1\u6709 Web \u4EA7\u7269\u65F6\u53EA\u90E8\u7F72\u670D\u52A1\u3002
1808
-
1809
- ${generateAiDevelopmentReadme()}## \u6587\u6863
2005
+ ${generateAiDevelopmentReadme()}### \u6587\u6863
1810
2006
 
1811
2007
  - [hai Agent Framework](https://github.com/hai-series/hai-framework)
1812
2008
  - [Svelte 5](https://svelte.dev/)
2009
+
2010
+ ## \u5F00\u53D1\u6D4B\u8BD5
2011
+
2012
+ \`\`\`sh
2013
+ ${getRunCommand(pm, "typecheck")}
2014
+ ${getRunCommand(pm, "lint")}
2015
+ ${getRunCommand(pm, "build")}
2016
+ ${getRunCommand(pm, "test")}
2017
+ ${getRunCommand(pm, "test:e2e")}
2018
+ \`\`\`
2019
+
2020
+ ## \u8BB8\u53EF\u8BC1
2021
+
2022
+ Apache-2.0
1813
2023
  `;
1814
2024
  }
1815
2025
  function generateApiWorkspaceReadme(name, pm) {
1816
2026
  return `# ${name}
1817
2027
 
1818
- \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684 typed API workspace\u3002
2028
+ ## \u7B80\u4ECB
2029
+
2030
+ \u540E\u7AEF API \u5DE5\u7A0B\uFF0C\u63A5\u53E3\u5B9A\u4E49\u4E0E\u670D\u52A1\u5B9E\u73B0\u5206\u5305\u7EF4\u62A4\uFF0C\u5BA2\u6237\u7AEF\u53EF\u590D\u7528\u540C\u4E00\u4EFD\u63A5\u53E3\u7C7B\u578B\u3002
2031
+
2032
+ ## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
2033
+
2034
+ - \u5171\u4EAB contract\u3001\u8F93\u5165\u8F93\u51FA\u6821\u9A8C\u4E0E\u670D\u52A1 procedures\u3002
2035
+ - \u6A21\u5757\u914D\u7F6E\u3001\u5065\u5EB7\u68C0\u67E5\u548C HTTP \u670D\u52A1\u3002
2036
+ - \u72EC\u7ACB\u955C\u50CF\u6784\u5EFA\u4E0E\u79BB\u7EBF\u4EA4\u4ED8\u3002
2037
+
2038
+ ## \u4F7F\u7528\u5165\u95E8
1819
2039
 
1820
- ## \u5DE5\u7A0B\u7ED3\u6784
2040
+ ### \u5DE5\u7A0B\u7ED3\u6784
1821
2041
 
1822
2042
  - \`apps/${name}-contract\`\uFF1A\u5171\u4EAB API contract\u3001Zod schema \u4E0E\u7C7B\u578B
1823
2043
  - \`apps/${name}-service\`\uFF1A\u540E\u7AEF API service\uFF0C\u57FA\u4E8E \`@h-ai/serv\` \u66B4\u9732 HTTP \u5E94\u7528
1824
2044
 
1825
- ## \u5F00\u53D1
2045
+ ### \u5F00\u53D1
1826
2046
 
1827
2047
  \`\`\`bash
1828
2048
  ${pm} install
@@ -1831,52 +2051,55 @@ ${pm} dev
1831
2051
 
1832
2052
  ${generateEnvironmentReadme()}
1833
2053
 
1834
- ## \u8D28\u91CF\u95E8\u7981
1835
-
1836
- \`\`\`bash
1837
- ${pm} typecheck
1838
- ${pm} lint
1839
- ${pm} build
1840
- ${pm} test
1841
- ${pm} test:e2e
1842
- \`\`\`
1843
-
1844
- ## \u6784\u5EFA\u4E0E\u90E8\u7F72
1845
-
1846
- \`\`\`bash
1847
- # \u9ED8\u8BA4\u53EA\u7F16\u8BD1\u6E90\u7801\uFF1B\u4E0D\u9009\u62E9\u90E8\u7F72\u73AF\u5883
1848
- ${pm} build
1849
- # \u6784\u5EFA\u5E76\u5BFC\u51FA API/Web \u955C\u50CF\uFF0Cauto \u4F18\u5148 Podman\u3001\u5176\u6B21 Docker
1850
- ${pm} build --target docker
1851
- # \u9009\u62E9\u73AF\u5883\u3001\u4E2D\u95F4\u4EF6\u5E76\u4E0A\u4F20\u4E00\u4E2A Compose tar\uFF1B\u4E0D\u4F1A\u91CD\u65B0\u6784\u5EFA
1852
- ${pm} exec hai deploy apps/${name}-service --env test
1853
- # \u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u5668\u65F6\u53EA\u751F\u6210\u5305\uFF0C\u6309\u8F93\u51FA\u8BF4\u660E\u624B\u5DE5\u642C\u8FD0\u548C\u542F\u52A8
1854
- ${pm} exec hai deploy apps/${name}-service --env prod --bundle-only
1855
- \`\`\`
1856
2054
 
1857
- \u955C\u50CF\u6784\u5EFA\u914D\u7F6E\u89C1 \`hai.build.json\`\u3002\u6784\u5EFA\u4EA7\u7269\u4FDD\u5B58\u5728 \`.hai/build/docker/<buildId>\`\uFF0C\u53EF\u7528 \`--artifact-dir\` \u9009\u62E9\u540C\u4E00\u4EFD\u4EA7\u7269\u90E8\u7F72\u5230 test/prod\u3002\u90E8\u7F72\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u80FD\u63D0\u4EA4 Git\u3002\u73AF\u5883\u5DF2\u914D\u7F6E\u5B8C\u5907\u53EF\u52A0 \`--skip-middleware\`\u3002\u670D\u52A1\u8FD0\u884C\u6570\u636E\u7531 volume \u6301\u4E45\u5316\uFF0C\u955C\u50CF\u4E0D\u6253\u5305\u672C\u673A data\u3002API/Web \u8054\u5408\u90E8\u7F72\u516C\u5F00 Web \u5165\u53E3\u5E76\u4EE3\u7406\u540C\u6E90 API\uFF1B\u6CA1\u6709 Web \u4EA7\u7269\u65F6\u53EA\u90E8\u7F72\u670D\u52A1\u3002
2055
+ ${generateBuildDeploymentReadme(pm, `apps/${name}-service`)}
1858
2056
 
1859
- ## \u6269\u5C55\u5EFA\u8BAE
2057
+ ### \u6269\u5C55\u5EFA\u8BAE
1860
2058
 
1861
2059
  - \u5728 \`${name}-contract\` \u4E2D\u7EF4\u62A4\u5E94\u7528\u7EA7 contract\uFF0C\u524D\u540E\u7AEF\u5171\u7528\u540C\u4E00\u4EFD\u7C7B\u578B\u4E0E\u8DEF\u5F84\u5B9A\u4E49\u3002
1862
2060
  - \u5728 \`${name}-service\` \u4E2D\u6269\u5C55 procedures\u3001\u6A21\u5757\u521D\u59CB\u5316\u4E0E\u914D\u7F6E\u3002
1863
2061
  - \u9700\u8981 IAM / storage / ai \u65F6\uFF0C\u4F18\u5148\u590D\u7528 \`@h-ai/api-contract\` \u4E0E \`@h-ai/serv/features/*\`\u3002
1864
2062
 
1865
- ${generateAiDevelopmentReadme()}## \u6587\u6863
2063
+ ${generateAiDevelopmentReadme()}### \u6587\u6863
1866
2064
 
1867
2065
  - [hai Agent Framework](https://github.com/hai-series/hai-framework)
1868
2066
  - [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
1869
2067
  - [@h-ai/api-contract](./.agents/skills/hai-api-contract/SKILL.md)
1870
2068
  - [@h-ai/api-client](./.agents/skills/hai-api-client/SKILL.md)
2069
+
2070
+ ## \u5F00\u53D1\u6D4B\u8BD5
2071
+
2072
+ \`\`\`bash
2073
+ ${pm} typecheck
2074
+ ${pm} lint
2075
+ ${pm} build
2076
+ ${pm} test
2077
+ ${pm} test:e2e
2078
+ \`\`\`
2079
+
2080
+ ## \u8BB8\u53EF\u8BC1
2081
+
2082
+ Apache-2.0
1871
2083
  `;
1872
2084
  }
1873
2085
  function generateFullstackReadme(name, pm, frontends) {
1874
2086
  const enabledFrontends = frontends.map((target) => FRONTEND_TARGETS[target].name).join(" / ");
1875
2087
  return `# ${name}
1876
2088
 
1877
- \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684\u524D\u540E\u7AEF\u5206\u79BB\u591A\u5305\u5DE5\u7A0B\u3002
2089
+ ## \u7B80\u4ECB
2090
+
2091
+ \u524D\u540E\u7AEF\u5206\u79BB\u5DE5\u7A0B\uFF0CAPI \u4E0E Web\u3001\u79FB\u52A8\u7AEF\u3001\u684C\u9762\u7AEF\u5171\u4EAB\u63A5\u53E3\u7C7B\u578B\u548C\u7BA1\u7406\u754C\u9762\uFF0C\u53EF\u6309\u5B9E\u9645\u4E1A\u52A1\u8C03\u6574\u3002
2092
+
2093
+ ## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
2094
+
2095
+ - API \u670D\u52A1\u3001\u5171\u4EAB\u754C\u9762\u4E0E\u6309\u9700\u751F\u6210\u7684\u5BA2\u6237\u7AEF\u3002
2096
+ - \u7528\u6237\u7BA1\u7406\u3001\u89D2\u8272\u3001\u5BA1\u8BA1\u548C\u4E2A\u4EBA\u8BBE\u7F6E\u793A\u4F8B\u3002
2097
+ - \u767B\u5F55\u3001\u4E3B\u9898\u3001\u8BED\u8A00\u3001\u65B0\u624B\u5F15\u5BFC\u548C\u5E2E\u52A9\u4E2D\u5FC3\u3002
2098
+ - API/Web \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\u3002
2099
+
2100
+ ## \u4F7F\u7528\u5165\u95E8
1878
2101
 
1879
- ## \u5DE5\u7A0B\u7ED3\u6784
2102
+ ### \u5DE5\u7A0B\u7ED3\u6784
1880
2103
 
1881
2104
  - \`packages/${name}-contract\`\uFF1A\u524D\u540E\u7AEF\u5171\u4EAB API \u5951\u7EA6\u3001Zod Schema \u4E0E\u7C7B\u578B
1882
2105
  - \`packages/${name}-serv\`\uFF1A\u540E\u7AEF API Service\uFF08\u901A\u8FC7 \`@h-ai/serv\` \u66B4\u9732\u6700\u5C0F HTTP App \u62BD\u8C61\uFF0C\u4E0D\u76F4\u63A5\u66B4\u9732 Hono\uFF09
@@ -1888,11 +2111,11 @@ function generateFullstackReadme(name, pm, frontends) {
1888
2111
 
1889
2112
  \u5DF2\u542F\u7528\u524D\u7AEF\uFF1A${enabledFrontends || "Web / App / Desktop"}
1890
2113
 
1891
- ## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
2114
+ ### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1892
2115
 
1893
2116
  Web/App/Desktop \u5171\u4EAB AppShell \u7684 header/footer\u3002\u65B0\u589E\u9875\u9762\u653E shared/src/lib/pages/**/*.svelte\uFF0C\u7531 HashRouter \u81EA\u52A8\u53D1\u73B0\u5E76\u6309\u9700\u52A0\u8F7D\uFF1B\u83DC\u5355\u53EA\u7EF4\u62A4\u5165\u53E3\u3002\u5730\u5740\u4F7F\u7528 #/\u9875\u9762\u8DEF\u5F84\uFF0C\u652F\u6301\u5237\u65B0\u3001\u67E5\u8BE2\u4E32\u4E0E\u6D4F\u89C8\u5668\u524D\u8FDB\u540E\u9000\uFF1BAPI \u6743\u9650\u4ECD\u7531\u670D\u52A1\u7AEF\u6821\u9A8C\u3002
1894
2117
 
1895
- ## \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
2118
+ ### \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
1896
2119
 
1897
2120
  \u4EE5\u4E0B\u63CF\u8FF0\u53EF\u8FD0\u884C\u793A\u4F8B\u7684\u521D\u59CB\u4E1A\u52A1\uFF0C\u53EF\u6309\u5B9E\u9645\u9700\u6C42\u8C03\u6574\u6216\u79FB\u9664\uFF0C\u5E76\u540C\u6B65\u4EE3\u7801\u3001\u6570\u636E\u3001\u9274\u6743\u3001\u754C\u9762\u3001\u6D4B\u8BD5\u548C\u6587\u6863\u3002
1898
2121
 
@@ -1902,11 +2125,13 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
1902
2125
 
1903
2126
  \u672C\u5730\u5F00\u53D1\u9ED8\u8BA4 \`PUBLIC_API_BASE=http://127.0.0.1:3000\`\uFF0CVite \u4F7F\u7528\u540C\u6E90\u4EE3\u7406\uFF1B\u90E8\u7F72\u65F6\u6539\u4E3A\u5B9E\u9645\u516C\u7F51 API Origin\uFF0CAndroid \u6A21\u62DF\u5668\u8BBF\u95EE\u5BBF\u4E3B\u673A\u65F6\u4F7F\u7528 \`http://10.0.2.2:3000\`\u3002\u4E0D\u8981\u5C06\u5BC6\u94A5\u653E\u5165\u4EFB\u4F55 \`PUBLIC_\` \u6216 \`VITE_\` \u53D8\u91CF\u3002
1904
2127
 
2128
+ \u670D\u52A1\u7AEF\u7248\u672C\u5316\u4F20\u8F93\u5BC6\u94A5\u73AF\u5728\u521B\u5EFA\u65F6\u4E00\u6B21\u6027\u5199\u5165\u88AB\u5FFD\u7565\u7684 \`.env.local\`\uFF0C\u53D8\u91CF\u4E3A \`HAI_CRYPTO_TRANSPORT_SERVERKEYRING\`\uFF1B\u8FD0\u884C\u65F6\u4E0D\u751F\u6210\u5BC6\u94A5\u3002\u90E8\u7F72\u7531 Secret Manager \u5411\u6240\u6709\u8282\u70B9\u5206\u53D1\u540C\u4E00\u53D7\u7BA1 ring\uFF0C\u914D\u5408\u5171\u4EAB registry\u3002\u79C1\u94A5\u4E0D\u8FDB\u5165\u516C\u5F00\u914D\u7F6E\u3001PUBLIC_/VITE_ \u6216\u65E5\u5FD7\u3002
2129
+
1905
2130
  \u6240\u6709\u4E1A\u52A1 JSON \u8BF7\u6C42\u548C\u54CD\u5E94\u9ED8\u8BA4\u901A\u8FC7 \`@h-ai/crypto\` transport \u53CC\u5411\u52A0\u5BC6\uFF08\u542B\u767B\u5F55\u3001\u5237\u65B0\u3001\u5934\u50CF\u4E0A\u4F20\uFF09\uFF1B\u524D\u7AEF\u7EDF\u4E00\u4F7F\u7528\u5171\u4EAB typed API client\u3002Web \u8BBF\u95EE\u4EE4\u724C\u4EC5\u5B58\u5185\u5B58\u3001\u5237\u65B0\u4EE4\u724C\u7531 HttpOnly Cookie \u4FDD\u5B58\uFF1B\u53D7\u4FE1\u539F\u751F\u7AEF\u4F7F\u7528\u5E73\u53F0\u5B89\u5168\u5B58\u50A8\u6216\u5185\u5B58\u3002\u5934\u50CF\u4E3A\u968F\u673A\u8DEF\u5F84\u53EA\u8BFB\u56FE\u7247\u8D44\u6E90\uFF0C\u652F\u6301 PNG/JPEG/WebP\uFF0C\u6700\u5927 512 KiB\u3002\u5065\u5EB7\u68C0\u67E5\u548C\u53EA\u8BFB\u5934\u50CF\u56FE\u7247\u53EF\u76F4\u63A5\u8BBF\u95EE\u3002
1906
2131
 
1907
- \u5E94\u7528\u663E\u793A\u540D\u79F0\u3001\u7248\u672C\u3001\u73AF\u5883\u3001\u9ED8\u8BA4\u8BED\u8A00\u548C\u65E5\u5FD7\u8BBE\u7F6E\u6765\u81EA\u540E\u7AEF \`config/_core.yml\`\uFF0C\u524D\u7AEF\u901A\u8FC7 \`app.info\` \u83B7\u53D6\u5B89\u5168\u5143\u4FE1\u606F\u3002\u9ED8\u8BA4 SQLite\u3001\u672C\u5730\u5B58\u50A8\u4E0E\u5185\u5B58\u7F13\u5B58\u9002\u5408\u5355\u5B9E\u4F8B\uFF0C\u7BA1\u7406\u5199\u5165\u4EC5\u652F\u6301\u5355\u5199\u5B9E\u4F8B\uFF1B\u591A\u5B9E\u4F8B\u8BFB\u53D6\u53EF\u4F7F\u7528\u5171\u4EAB\u6570\u636E\u5E93\u3001Redis cache \u548C S3 \u5B58\u50A8\uFF0C\u4F46\u7BA1\u7406\u5199\u8BF7\u6C42\u987B\u7EDF\u4E00\u8DEF\u7531\u5230\u540C\u4E00\u5199\u5B9E\u4F8B\u3002\u53E6\u9700\u4F1A\u8BDD\u7C98\u6027\u8DEF\u7531\u4F7F\u8BF7\u6C42\u547D\u4E2D\u540C\u4E00\u8FDB\u7A0B\u5185\u4F20\u8F93\u79C1\u94A5\uFF0C\u670D\u52A1\u91CD\u542F\u540E\u5BA2\u6237\u7AEF\u91CD\u65B0\u534F\u5546\u3002\u751F\u4EA7\u9700\u663E\u5F0F\u914D\u7F6E\u5141\u8BB8\u7684 CORS Origin\u3002\u4E1A\u52A1\u63D0\u4EA4\u540E\u7684\u5BA1\u8BA1\u5199\u5165\u91C7\u7528\u6700\u5927\u52AA\u529B\uFF0C\u5931\u8D25\u8BB0\u5F55\u7ED3\u6784\u5316\u9519\u8BEF\u65E5\u5FD7\uFF0C\u4FDD\u7559\u5DF2\u5B8C\u6210\u4E1A\u52A1\u7684\u539F\u59CB\u7ED3\u679C\u3002
2132
+ \u5E94\u7528\u663E\u793A\u540D\u79F0\u3001\u7248\u672C\u3001\u73AF\u5883\u3001\u9ED8\u8BA4\u8BED\u8A00\u548C\u65E5\u5FD7\u8BBE\u7F6E\u6765\u81EA\u540E\u7AEF \`config/_core.yml\`\uFF0C\u524D\u7AEF\u901A\u8FC7 \`app.info\` \u83B7\u53D6\u5B89\u5168\u5143\u4FE1\u606F\u3002\u5F00\u53D1\u9ED8\u8BA4 SQLite\u3001\u672C\u5730\u5B58\u50A8\u4E0E\u5185\u5B58\u7F13\u5B58\uFF1B\u751F\u4EA7\u542F\u52A8\u62D2\u7EDD\u8FD9\u4E9B\u672C\u5730 Provider\uFF0C\u5FC5\u987B\u5C06\u6240\u6709\u8282\u70B9\u8FDE\u63A5\u5230\u540C\u4E00\u5171\u4EAB\u6570\u636E\u5E93\u3001Redis \u4E0E S3\u3002\u4F20\u8F93\u5FC5\u987B\u5728\u6240\u6709\u8282\u70B9\u6CE8\u5165\u76F8\u540C\u53D7\u7BA1\u7248\u672C\u5316\u5BC6\u94A5\u73AF\u4E0E\u5171\u4EAB registry\uFF0C\u65E0\u9700\u4F1A\u8BDD\u7C98\u6027\uFF1B\u91CD\u542F\u8BFB\u53D6\u539F ring\uFF0C\u8F6E\u6362\u5148\u5206\u53D1\u65B0\u65E7\u7248\u672C\u518D\u5207 activeKeyId\u3002\u751F\u4EA7\u9700\u663E\u5F0F\u914D\u7F6E\u5141\u8BB8\u7684 CORS Origin\u3002\u4E1A\u52A1\u63D0\u4EA4\u540E\u7684\u5BA1\u8BA1\u5199\u5165\u91C7\u7528\u6700\u5927\u52AA\u529B\uFF0C\u5931\u8D25\u8BB0\u5F55\u7ED3\u6784\u5316\u9519\u8BEF\u65E5\u5FD7\uFF0C\u4FDD\u7559\u5DF2\u5B8C\u6210\u4E1A\u52A1\u7684\u539F\u59CB\u7ED3\u679C\u3002
1908
2133
 
1909
- ## \u914D\u7F6E\u6269\u5C55
2134
+ ### \u914D\u7F6E\u6269\u5C55
1910
2135
 
1911
2136
  \u670D\u52A1\u7AEF \`packages/${name}-serv/config/\` \u59CB\u7EC8\u4FDD\u7559 core\u3001serv\u3001db\u3001cache\u3001iam\u3001storage\u3001audit\u3001reach\u3001ai\u3001vecdb\u3001payment\u3001scheduler\u3001deploy \u5171 13 \u4EFD YAML\u3002\u5DF2\u7528\u6A21\u5757\u4FDD\u7559\u5DE5\u4F5C\u9ED8\u8BA4\u503C\uFF0C\u672A\u542F\u7528\u6A21\u5757\u6574\u4EFD\u6CE8\u91CA\uFF0C\u53EF\u9009\u5B57\u6BB5\u548C\u66FF\u4EE3 Provider \u6309\u5757\u6CE8\u91CA\uFF1B\u6839 \`.env.example\` \u4E5F\u4FDD\u7559\u672A\u542F\u7528\u6A21\u5757\u7684\u51ED\u636E\u793A\u4F8B\u3002Reach \u9ED8\u8BA4\u4E3A\u5F00\u53D1\u63A7\u5236\u53F0\u901A\u77E5\uFF0C\u751F\u4EA7\u6309 \`_reach.yml\` \u6CE8\u91CA\u66FF\u6362\u4E3A SMTP\u3001\u77ED\u4FE1\u6216 API Provider\uFF0C\u5E76\u4FDD\u6301\u540D\u79F0\u552F\u4E00\u3002
1912
2137
 
@@ -1918,7 +2143,7 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
1918
2143
 
1919
2144
  ${generateEnvironmentReadme()}
1920
2145
 
1921
- ## \u5F00\u53D1\u4E0E\u6784\u5EFA
2146
+ ### \u5F00\u53D1\u4E0E\u6784\u5EFA
1922
2147
 
1923
2148
  \`\`\`bash
1924
2149
  ${pm} install
@@ -1932,22 +2157,9 @@ ${frontends.includes("desktop") ? `${pm} build --target desktop --platform windo
1932
2157
 
1933
2158
  Tauri \u9700\u8981\u76EE\u6807\u7CFB\u7EDF\u7684 Rust target\u3001SDK \u548C\u6253\u5305\u5DE5\u5177\u3002\u6D4B\u8BD5\u4EA7\u751F\u7684\u4E34\u65F6\u5DE5\u7A0B\u3001\u622A\u56FE\u3001\u62A5\u544A\u3001\u5BB9\u5668\u3001\u955C\u50CF\u4E0E\u8FDB\u7A0B\u5E94\u5728 finally/teardown \u4E2D\u81EA\u52A8\u6E05\u7406\uFF0C\u5931\u8D25\u4E5F\u6267\u884C\u3002
1934
2159
 
1935
- ## \u6784\u5EFA\u4E0E\u90E8\u7F72
1936
-
1937
- \`\`\`bash
1938
- # \u9ED8\u8BA4\u53EA\u7F16\u8BD1\u6E90\u7801\uFF1B\u4E0D\u9009\u62E9\u90E8\u7F72\u73AF\u5883
1939
- ${pm} build
1940
- # \u6784\u5EFA\u5E76\u5BFC\u51FA API/Web \u955C\u50CF\uFF0Cauto \u4F18\u5148 Podman\u3001\u5176\u6B21 Docker
1941
- ${pm} build --target docker
1942
- # \u9009\u62E9\u73AF\u5883\u3001\u4E2D\u95F4\u4EF6\u5E76\u4E0A\u4F20\u4E00\u4E2A Compose tar\uFF1B\u4E0D\u4F1A\u91CD\u65B0\u6784\u5EFA
1943
- ${pm} exec hai deploy packages/${name}-serv --env test
1944
- # \u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u5668\u65F6\u53EA\u751F\u6210\u5305\uFF0C\u6309\u8F93\u51FA\u8BF4\u660E\u624B\u5DE5\u642C\u8FD0\u548C\u542F\u52A8
1945
- ${pm} exec hai deploy packages/${name}-serv --env prod --bundle-only
1946
- \`\`\`
1947
-
1948
- \u955C\u50CF\u6784\u5EFA\u914D\u7F6E\u89C1 \`hai.build.json\`\u3002\u6784\u5EFA\u4EA7\u7269\u4FDD\u5B58\u5728 \`.hai/build/docker/<buildId>\`\uFF0C\u53EF\u7528 \`--artifact-dir\` \u9009\u62E9\u540C\u4E00\u4EFD\u4EA7\u7269\u90E8\u7F72\u5230 test/prod\u3002\u90E8\u7F72\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u80FD\u63D0\u4EA4 Git\u3002\u73AF\u5883\u5DF2\u914D\u7F6E\u5B8C\u5907\u53EF\u52A0 \`--skip-middleware\`\u3002\u670D\u52A1\u8FD0\u884C\u6570\u636E\u7531 volume \u6301\u4E45\u5316\uFF0C\u955C\u50CF\u4E0D\u6253\u5305\u672C\u673A data\u3002API/Web \u8054\u5408\u90E8\u7F72\u516C\u5F00 Web \u5165\u53E3\u5E76\u4EE3\u7406\u540C\u6E90 API\uFF1B\u6CA1\u6709 Web \u4EA7\u7269\u65F6\u53EA\u90E8\u7F72\u670D\u52A1\u3002
2160
+ ${generateBuildDeploymentReadme(pm, `packages/${name}-serv`)}
1949
2161
 
1950
- ## i18n \u4E0E shared \u534F\u540C
2162
+ ### i18n \u4E0E shared \u534F\u540C
1951
2163
 
1952
2164
  - shared\uFF1A\`packages/${name}-shared/project.inlang\` + \`messages/\`\uFF0C\u53EA\u653E\u8DE8\u7AEF\u5171\u4EAB\u7EC4\u4EF6\u6587\u6848\u3002
1953
2165
  - web/app/desktop\uFF1A\u6BCF\u4E2A\u5E94\u7528\u90FD\u6709\u81EA\u5DF1\u7684 \`project.inlang\` + \`messages/\`\uFF0C\u7528\u4E8E\u672C\u7AEF\u9875\u9762\u4E0E\u4EA7\u54C1\u6587\u6848\u3002
@@ -1955,13 +2167,20 @@ ${pm} exec hai deploy packages/${name}-serv --env prod --bundle-only
1955
2167
  - \u65B0\u589E locale \u65F6\uFF0Cshared \u4E0E\u6240\u6709\u542F\u7528\u524D\u7AEF\u7684 \`locales\` \u5FC5\u987B\u540C\u6B65\uFF1B\u8BED\u8A00\u5207\u6362\u5668\u4F1A\u540C\u65F6\u66F4\u65B0 shared runtime \u4E0E\u5BBF\u4E3B\u5E94\u7528 runtime\u3002
1956
2168
  - \`${pm} i18n:compile\` \u4F1A\u7EDF\u4E00\u7F16\u8BD1 shared \u4E0E\u6BCF\u4E2A\u542F\u7528\u524D\u7AEF\u7684 Paraglide \u8F93\u51FA\uFF1B\u5404\u524D\u7AEF\u4E5F\u53EF\u5355\u72EC\u8FD0\u884C \`${pm} --filter ${name}-web paraglide:compile\` \u7B49\u547D\u4EE4\u3002
1957
2169
 
1958
- ## \u81EA\u5B9A\u4E49\u5EFA\u8BAE
2170
+ ### \u81EA\u5B9A\u4E49\u5EFA\u8BAE
1959
2171
 
1960
2172
  - \u6539\u5168\u5C40 Shell/\u4E3B\u9898/\u8BED\u8A00\u5207\u6362\uFF1A\u7F16\u8F91 \`packages/${name}-shared\`\u3002
1961
- - \u6539\u67D0\u4E2A\u524D\u7AEF\u9875\u9762\u548C\u6587\u6848\uFF1A\u7F16\u8F91\u5BF9\u5E94 \`apps/${name}-*/src/routes\` \u4E0E \`apps/${name}-*/messages\`\u3002
2173
+ - \u6539\u67D0\u4E2A\u524D\u7AEF\u9875\u9762\u548C\u6587\u6848\uFF1A\u7F16\u8F91\u5171\u4EAB \`packages/${name}-shared/src/lib/pages\`\u3001\u672C\u7AEF\u5165\u53E3\u4E0E \`apps/${name}-*/messages\`\u3002
1962
2174
  - \u6539\u4E3B\u9898\u5217\u8868\uFF1A\u8C03\u6574\u811A\u624B\u67B6\u914D\u7F6E\u4E2D\u7684 \`fullstack.theme.themes\`\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\u5404\u7AEF \`src/app.css\` \u7684 daisyUI \`themes\`\u3002
1963
2175
 
1964
- ## \u8D28\u91CF\u95E8\u7981
2176
+
2177
+ ${generateAiDevelopmentReadme()}### \u6587\u6863
2178
+
2179
+ - [hai Agent Framework](https://github.com/hai-series/hai-framework)
2180
+ - [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
2181
+ - [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
2182
+
2183
+ ## \u5F00\u53D1\u6D4B\u8BD5
1965
2184
 
1966
2185
  \`\`\`bash
1967
2186
  ${pm} typecheck
@@ -1971,19 +2190,17 @@ ${pm} test
1971
2190
  ${pm} test:e2e
1972
2191
  \`\`\`
1973
2192
 
1974
- ${generateAiDevelopmentReadme()}## \u6587\u6863
2193
+ ## \u8BB8\u53EF\u8BC1
1975
2194
 
1976
- - [hai Agent Framework](https://github.com/hai-series/hai-framework)
1977
- - [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
1978
- - [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
2195
+ Apache-2.0
1979
2196
  `;
1980
2197
  }
1981
2198
  function printCompletionMessage(options) {
1982
2199
  writeCliMessage("");
1983
- writeCliMessage(chalk.green("\u2714 \u9879\u76EE\u521B\u5EFA\u6210\u529F\uFF01"));
2200
+ writeCliMessage(chalk.green(cliM("cli_createSuccess")));
1984
2201
  writeCliMessage("");
1985
2202
  if (options.features.length > 0) {
1986
- writeCliMessage(chalk.bold("\u5DF2\u542F\u7528\u529F\u80FD:"));
2203
+ writeCliMessage(chalk.bold(cliM("cli_createFeatures")));
1987
2204
  for (const featureId of options.features) {
1988
2205
  const feature = FEATURES[featureId];
1989
2206
  if (feature) {
@@ -1992,7 +2209,7 @@ function printCompletionMessage(options) {
1992
2209
  }
1993
2210
  writeCliMessage("");
1994
2211
  }
1995
- writeCliMessage("\u4E0B\u4E00\u6B65\uFF1A");
2212
+ writeCliMessage(cliM("cli_nextSteps"));
1996
2213
  writeCliMessage(chalk.cyan(` cd ${options.name}`));
1997
2214
  if (options.appType === "fullstack")
1998
2215
  writeCliMessage(chalk.cyan(cliM("cli_adminPasswordSaved")));
@@ -2033,25 +2250,61 @@ async function detectProject(cwd) {
2033
2250
  // src/commands/cli-add.ts
2034
2251
  var MODULE_MAP = {
2035
2252
  // iam 依赖 crypto(password hash)/ db(user 存储)/ cache(session 缓存)
2036
- "iam": { packages: ["@h-ai/iam"], deps: ["crypto", "db", "cache"], configKey: "iam", description: "\u8EAB\u4EFD\u4E0E\u8BBF\u95EE\u7BA1\u7406" },
2253
+ "iam": { packages: ["@h-ai/iam"], deps: ["crypto", "db", "cache"], configKey: "iam", get description() {
2254
+ return cliM("cli_featureIamName");
2255
+ } },
2037
2256
  // 'db' 和 'reldb' 都映射到 @h-ai/reldb,兼容用户习惯(hai add db 和 hai add reldb 均可)
2038
- "db": { packages: ["@h-ai/reldb"], configKey: "db", description: "\u6570\u636E\u5E93" },
2039
- "reldb": { packages: ["@h-ai/reldb"], configKey: "db", description: "\u6570\u636E\u5E93" },
2040
- "cache": { packages: ["@h-ai/cache"], configKey: "cache", description: "\u7F13\u5B58" },
2041
- "ai": { packages: ["@h-ai/ai"], configKey: "ai", description: "AI \u96C6\u6210" },
2042
- "storage": { packages: ["@h-ai/storage"], configKey: "storage", description: "\u6587\u4EF6\u5B58\u50A8" },
2043
- "crypto": { packages: ["@h-ai/crypto"], description: "\u52A0\u5BC6\u6A21\u5757" },
2044
- "kit": { packages: ["@h-ai/kit"], description: "SvelteKit \u96C6\u6210\u5DE5\u5177" },
2045
- "ui": { packages: ["@h-ai/ui"], description: "UI \u7EC4\u4EF6\u5E93" },
2046
- "deploy": { packages: ["@h-ai/deploy"], configKey: "deploy", description: "\u81EA\u52A8\u5316\u90E8\u7F72" },
2047
- "vecdb": { packages: ["@h-ai/vecdb"], configKey: "vecdb", description: "\u5411\u91CF\u6570\u636E\u5E93" },
2048
- "datapipe": { packages: ["@h-ai/datapipe"], description: "\u6570\u636E\u5904\u7406\u7BA1\u9053" },
2049
- "reach": { packages: ["@h-ai/reach"], configKey: "reach", description: "\u6D88\u606F\u63A8\u9001" },
2050
- "scheduler": { packages: ["@h-ai/scheduler"], deps: ["db"], configKey: "scheduler", description: "\u4EFB\u52A1\u8C03\u5EA6" },
2051
- "audit": { packages: ["@h-ai/audit"], deps: ["db"], configKey: "audit", description: "\u5BA1\u8BA1\u65E5\u5FD7" },
2052
- "payment": { packages: ["@h-ai/payment"], configKey: "payment", description: "\u652F\u4ED8" },
2053
- "capacitor": { packages: ["@h-ai/capacitor"], description: "\u79FB\u52A8\u7AEF\u80FD\u529B" },
2054
- "api-client": { packages: ["@h-ai/api-client"], description: "HTTP \u5BA2\u6237\u7AEF" }
2257
+ "db": { packages: ["@h-ai/reldb"], configKey: "db", get description() {
2258
+ return cliM("cli_featureDbName");
2259
+ } },
2260
+ "reldb": { packages: ["@h-ai/reldb"], configKey: "db", get description() {
2261
+ return cliM("cli_featureDbName");
2262
+ } },
2263
+ "cache": { packages: ["@h-ai/cache"], configKey: "cache", get description() {
2264
+ return cliM("cli_featureCacheName");
2265
+ } },
2266
+ "ai": { packages: ["@h-ai/ai"], deps: ["db", "vecdb"], configKey: "ai", get description() {
2267
+ return cliM("cli_featureAiName");
2268
+ } },
2269
+ "storage": { packages: ["@h-ai/storage"], configKey: "storage", get description() {
2270
+ return cliM("cli_featureStorageName");
2271
+ } },
2272
+ "crypto": { packages: ["@h-ai/crypto"], deps: ["db"], get description() {
2273
+ return cliM("cli_featureCryptoName");
2274
+ } },
2275
+ "kit": { packages: ["@h-ai/kit"], get description() {
2276
+ return cliM("cli_featureKitName");
2277
+ } },
2278
+ "ui": { packages: ["@h-ai/ui"], get description() {
2279
+ return cliM("cli_featureUiName");
2280
+ } },
2281
+ "deploy": { packages: ["@h-ai/deploy"], configKey: "deploy", get description() {
2282
+ return cliM("cli_featureDeployName");
2283
+ } },
2284
+ "vecdb": { packages: ["@h-ai/vecdb"], configKey: "vecdb", get description() {
2285
+ return cliM("cli_featureVecdbName");
2286
+ } },
2287
+ "datapipe": { packages: ["@h-ai/datapipe"], get description() {
2288
+ return cliM("cli_featureDatapipeName");
2289
+ } },
2290
+ "reach": { packages: ["@h-ai/reach"], configKey: "reach", get description() {
2291
+ return cliM("cli_featureReachName");
2292
+ } },
2293
+ "scheduler": { packages: ["@h-ai/scheduler"], deps: ["db"], configKey: "scheduler", get description() {
2294
+ return cliM("cli_featureSchedulerName");
2295
+ } },
2296
+ "audit": { packages: ["@h-ai/audit"], deps: ["db"], configKey: "audit", get description() {
2297
+ return cliM("cli_featureAuditName");
2298
+ } },
2299
+ "payment": { packages: ["@h-ai/payment"], configKey: "payment", get description() {
2300
+ return cliM("cli_featurePaymentName");
2301
+ } },
2302
+ "capacitor": { packages: ["@h-ai/capacitor"], get description() {
2303
+ return cliM("cli_featureCapacitorName");
2304
+ } },
2305
+ "api-client": { packages: ["@h-ai/api-client"], get description() {
2306
+ return cliM("cli_featureApiClientName");
2307
+ } }
2055
2308
  };
2056
2309
  async function addModule(options) {
2057
2310
  const spinner = ora();
@@ -2059,7 +2312,7 @@ async function addModule(options) {
2059
2312
  try {
2060
2313
  const project = await detectProject(cwd);
2061
2314
  if (!project) {
2062
- core.logger.error(chalk.red("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\uFF08\u627E\u4E0D\u5230 package.json\uFF09"));
2315
+ writeCliError(chalk.red(cliM("cli_projectNotFound")));
2063
2316
  return;
2064
2317
  }
2065
2318
  let moduleName = options.module;
@@ -2070,13 +2323,14 @@ async function addModule(options) {
2070
2323
  value: key
2071
2324
  }));
2072
2325
  if (choices.length === 0) {
2073
- core.logger.info(chalk.green("\u6240\u6709\u6A21\u5757\u5DF2\u5B89\u88C5\uFF01"));
2326
+ writeCliMessage(chalk.green(cliM("cli_addAllInstalled")));
2074
2327
  return;
2075
2328
  }
2076
2329
  const { selected } = await prompts({
2077
2330
  type: "select",
2331
+ hint: cliM("cli_selectHint"),
2078
2332
  name: "selected",
2079
- message: "\u9009\u62E9\u8981\u6DFB\u52A0\u7684\u6A21\u5757:",
2333
+ message: cliM("cli_addModulePrompt"),
2080
2334
  choices
2081
2335
  });
2082
2336
  if (!selected)
@@ -2085,11 +2339,11 @@ async function addModule(options) {
2085
2339
  }
2086
2340
  const moduleDef = MODULE_MAP[moduleName];
2087
2341
  if (!moduleDef) {
2088
- core.logger.error(chalk.red(`\u672A\u77E5\u6A21\u5757: ${moduleName}`));
2089
- core.logger.info(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
2342
+ writeCliError(chalk.red(cliM("cli_addUnknownModule", { params: { module: moduleName } })));
2343
+ writeCliMessage(cliM("cli_addAvailableModules", { params: { modules: Object.keys(MODULE_MAP).join(", ") } }));
2090
2344
  return;
2091
2345
  }
2092
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-TSYEQTCY.js');
2346
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-US5CCAJ4.js');
2093
2347
  if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
2094
2348
  const generatedFiles2 = await generateProjectAiSupport(
2095
2349
  cwd,
@@ -2100,12 +2354,12 @@ async function addModule(options) {
2100
2354
  }
2101
2355
  );
2102
2356
  if (generatedFiles2.length > 0) {
2103
- core.logger.info(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5\uFF0C\u5DF2\u8865\u5168 AI \u652F\u6301\u6587\u4EF6`));
2357
+ writeCliMessage(chalk.yellow(cliM("cli_addSupportCompleted", { params: { module: moduleName } })));
2104
2358
  for (const generatedFile of generatedFiles2) {
2105
- core.logger.info(chalk.gray(` + ${generatedFile} (AI support)`));
2359
+ writeCliMessage(chalk.gray(cliM("cli_addSupportFile", { params: { file: generatedFile } })));
2106
2360
  }
2107
2361
  } else {
2108
- core.logger.info(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5`));
2362
+ writeCliMessage(chalk.yellow(cliM("cli_addAlreadyInstalled", { params: { module: moduleName } })));
2109
2363
  }
2110
2364
  return;
2111
2365
  }
@@ -2120,7 +2374,7 @@ async function addModule(options) {
2120
2374
  }
2121
2375
  }
2122
2376
  }
2123
- spinner.start("\u66F4\u65B0 package.json...");
2377
+ spinner.start(cliM("cli_addUpdateManifest"));
2124
2378
  const pkgPath = path2.join(cwd, "package.json");
2125
2379
  const pkg = await fse.readJson(pkgPath);
2126
2380
  if (!pkg.dependencies)
@@ -2128,7 +2382,10 @@ async function addModule(options) {
2128
2382
  const existingHaiSpecifier = Object.entries(pkg.dependencies).find(([pkgName]) => pkgName.startsWith("@h-ai/"))?.[1];
2129
2383
  const depVersion = existingHaiSpecifier === "catalog:" ? "catalog:" : getCliVersion();
2130
2384
  for (const pkgName of allPackages) {
2131
- if (!pkg.dependencies[pkgName]) {
2385
+ if (pkgName === "@h-ai/deploy") {
2386
+ pkg.devDependencies ??= {};
2387
+ pkg.devDependencies[pkgName] ??= depVersion;
2388
+ } else if (!pkg.dependencies[pkgName]) {
2132
2389
  pkg.dependencies[pkgName] = depVersion;
2133
2390
  }
2134
2391
  }
@@ -2138,12 +2395,12 @@ async function addModule(options) {
2138
2395
  const configDir = path2.join(cwd, "config");
2139
2396
  const configPath = path2.join(configDir, `_${moduleDef.configKey}.yml`);
2140
2397
  if (!await fileExists(configPath)) {
2141
- spinner.start(`\u751F\u6210\u914D\u7F6E\u6587\u4EF6 config/_${moduleDef.configKey}.yml...`);
2398
+ spinner.start(cliM("cli_addConfig", { params: { config: moduleDef.configKey } }));
2142
2399
  const content = generateConfigFile(moduleDef.configKey);
2143
2400
  await writeFile(configPath, content);
2144
2401
  spinner.succeed();
2145
2402
  } else {
2146
- core.logger.info(chalk.gray(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: config/_${moduleDef.configKey}.yml`));
2403
+ writeCliMessage(chalk.gray(cliM("cli_addConfigExists", { params: { config: moduleDef.configKey } })));
2147
2404
  }
2148
2405
  }
2149
2406
  if (moduleDef.deps) {
@@ -2154,7 +2411,7 @@ async function addModule(options) {
2154
2411
  if (!await fileExists(depConfigPath)) {
2155
2412
  const content = generateConfigFile(depDef.configKey);
2156
2413
  await writeFile(depConfigPath, content);
2157
- core.logger.info(chalk.gray(` + config/_${depDef.configKey}.yml (\u4F9D\u8D56)`));
2414
+ writeCliMessage(chalk.gray(cliM("cli_addDependencyConfig", { params: { config: depDef.configKey } })));
2158
2415
  }
2159
2416
  }
2160
2417
  }
@@ -2170,28 +2427,36 @@ async function addModule(options) {
2170
2427
  );
2171
2428
  if (generatedFiles.length > 0) {
2172
2429
  for (const generatedFile of generatedFiles) {
2173
- core.logger.info(chalk.gray(` + ${generatedFile} (AI support)`));
2430
+ writeCliMessage(chalk.gray(cliM("cli_addSupportFile", { params: { file: generatedFile } })));
2174
2431
  }
2175
2432
  }
2433
+ const productionPath = path2.join(cwd, ".env.prod");
2434
+ const previousProduction = await fileExists(productionPath) ? await fse.readFile(productionPath, "utf8") : "";
2435
+ const configured = new Set([...previousProduction.matchAll(/^([A-Z][A-Z0-9_]*)=/gm)].map((match) => match[1]));
2436
+ const previousValues = parseEnv(previousProduction);
2437
+ const production = generateProductionEnvironment([moduleName, ...moduleDef.deps ?? []], previousValues.HAI_DB_TYPE === "mysql" ? { db: { type: "mysql", database: previousValues.HAI_DB_DATABASE } } : void 0).split("\n").filter((line) => !/^[A-Z][A-Z0-9_]*=/.test(line) || !configured.has(line.split("=")[0]));
2438
+ await writeFile(productionPath, `${previousProduction.trimEnd()}
2439
+
2440
+ ${production.join("\n")}`);
2176
2441
  const doInstall = options.install !== false;
2177
2442
  if (doInstall) {
2178
- spinner.start("\u5B89\u88C5\u4F9D\u8D56...");
2443
+ spinner.start(cliM("cli_addInstall"));
2179
2444
  const { execSync: execSync2 } = await import('child_process');
2180
2445
  const pm = await detectPackageManager(cwd);
2181
2446
  execSync2(`${pm} install`, { cwd, stdio: "ignore" });
2182
2447
  spinner.succeed();
2183
2448
  }
2184
- core.logger.info("");
2185
- core.logger.info(chalk.green(`\u2714 \u6A21\u5757 ${moduleName} \u6DFB\u52A0\u6210\u529F\uFF01`));
2186
- core.logger.info("");
2187
- core.logger.info("\u5DF2\u6DFB\u52A0\u7684\u5305:");
2449
+ writeCliMessage("");
2450
+ writeCliMessage(chalk.green(cliM("cli_addSuccess", { params: { module: moduleName } })));
2451
+ writeCliMessage("");
2452
+ writeCliMessage(cliM("cli_addPackages"));
2188
2453
  for (const pkg2 of allPackages) {
2189
- core.logger.info(chalk.cyan(` ${pkg2}`));
2454
+ writeCliMessage(chalk.cyan(` ${pkg2}`));
2190
2455
  }
2191
- core.logger.info("");
2456
+ writeCliMessage("");
2192
2457
  } catch (error) {
2193
2458
  spinner.fail();
2194
- core.logger.error(chalk.red("\u6DFB\u52A0\u6A21\u5757\u5931\u8D25:"), { error });
2459
+ writeCliError(chalk.red(cliM("cli_addFailed")), { error });
2195
2460
  throw error;
2196
2461
  }
2197
2462
  }
@@ -2216,8 +2481,8 @@ async function pnpm(args, cwd) {
2216
2481
  const executable = join(directory, "pnpm.exe");
2217
2482
  if (existsSync(executable))
2218
2483
  return run(executable, args, cwd);
2219
- for (const relative of ["node_modules/pnpm/bin/pnpm.cjs", "pnpm.cjs"]) {
2220
- const candidate = join(directory, relative);
2484
+ for (const relative2 of ["node_modules/pnpm/bin/pnpm.cjs", "pnpm.cjs"]) {
2485
+ const candidate = join(directory, relative2);
2221
2486
  if (existsSync(candidate))
2222
2487
  return run(process4.execPath, [candidate, ...args], cwd);
2223
2488
  }
@@ -2231,7 +2496,7 @@ async function buildCommand(options) {
2231
2496
  const target = options.target ?? "compile";
2232
2497
  if (!["compile", "docker", "desktop"].includes(target))
2233
2498
  throw new Error(cliM("cli_build_target_invalid"));
2234
- if (target !== "docker" && (options.runtime !== void 0 || options.network !== void 0 || options.sqlitePrebuildMirror !== void 0 || options.httpProxy === false) || target !== "desktop" && (options.platform !== void 0 || options.arch !== void 0)) {
2499
+ if (target !== "docker" && (options.images !== void 0 || options.runtime !== void 0 || options.network !== void 0 || options.sqlitePrebuildMirror !== void 0 || options.httpProxy === false) || target !== "desktop" && (options.platform !== void 0 || options.arch !== void 0)) {
2235
2500
  throw new Error(cliM("cli_build_options_invalid"));
2236
2501
  }
2237
2502
  if (target === "compile") {
@@ -2256,12 +2521,20 @@ async function buildCommand(options) {
2256
2521
  const bundles = platform === "windows" ? "nsis" : platform === "macos" ? "app,dmg" : "deb,appimage";
2257
2522
  for (const directory of config.desktop)
2258
2523
  await pnpm(["exec", "tauri", "build", "--target", rustTarget, "--bundles", bundles, "--config", JSON.stringify({ version: appVersion })], resolve(cwd, directory));
2259
- core.logger.info(cliM("cli_build_native_ready", { params: { target: rustTarget } }));
2524
+ writeCliMessage(cliM("cli_build_native_ready", { params: { target: rustTarget } }));
2260
2525
  return;
2261
2526
  }
2262
- if (!Array.isArray(config.images) || !config.images.length || config.images.filter((image) => image.name === "app").length !== 1 || config.images.length > 2 || new Set(config.images.map((image) => image.name)).size !== config.images.length || config.images.some((image) => !["app", "web"].includes(image.name) || image.containerPort !== void 0 && (!Number.isInteger(image.containerPort) || image.containerPort < 1 || image.containerPort > 65535))) {
2527
+ if (!Array.isArray(config.images) || !config.images.length || config.images.some((image) => !image || typeof image !== "object") || config.images.filter((image) => image.name === "app").length !== 1 || config.images.length > 2 || new Set(config.images.map((image) => image.name)).size !== config.images.length || config.images.some((image) => !["app", "web"].includes(image.name) || typeof image.dockerfile !== "string" || !image.dockerfile || "target" in image || image.containerPort !== void 0 && (!Number.isInteger(image.containerPort) || image.containerPort < 1 || image.containerPort > 65535))) {
2263
2528
  throw new Error(cliM("cli_build_images_missing"));
2264
2529
  }
2530
+ const selected = options.images?.split(",").map((name) => name.trim());
2531
+ if (selected && (selected.some((name) => !config.images.some((image) => image.name === name)) || new Set(selected).size !== selected.length))
2532
+ throw new Error(cliM("cli_build_selection_invalid"));
2533
+ const images = config.images.filter((image) => !selected || selected.includes(image.name));
2534
+ for (const image of images) {
2535
+ if (!existsSync(resolve(cwd, image.dockerfile)))
2536
+ throw new Error(cliM("cli_build_dockerfile_missing", { params: { path: image.dockerfile } }));
2537
+ }
2265
2538
  const candidates = options.runtime && options.runtime !== "auto" ? [options.runtime] : ["podman", "docker"];
2266
2539
  if (candidates.some((runtime2) => !["podman", "docker"].includes(runtime2)))
2267
2540
  throw new Error(cliM("cli_build_runtime_missing"));
@@ -2282,21 +2555,27 @@ async function buildCommand(options) {
2282
2555
  throw new Error(cliM("cli_build_mirror_invalid"));
2283
2556
  }
2284
2557
  const buildId = `${appVersion}-${Date.now()}-${randomBytes(3).toString("hex")}`;
2285
- const artifact = { version: 2, appVersion, buildId, images: [] };
2558
+ const artifact = { version: 3, appVersion, buildId, partial: images.length !== config.images.length, hasWeb: config.images.some((image) => image.name === "web"), images: [] };
2559
+ for (const image of config.images) {
2560
+ if (image.containerPort !== void 0)
2561
+ artifact[image.name === "app" ? "appContainerPort" : "webContainerPort"] = image.containerPort;
2562
+ }
2286
2563
  const parent = join(cwd, ".hai/build/docker");
2287
2564
  mkdirSync(parent, { recursive: true });
2288
2565
  const staging = mkdtempSync(join(parent, ".building-"));
2289
2566
  const built = [];
2290
2567
  try {
2291
- for (const image of config.images) {
2568
+ for (const context of new Set(images.map((image) => resolve(cwd, image.context ?? "."))))
2569
+ prepareDependencyInputs(context);
2570
+ for (const image of images) {
2292
2571
  if (!["app", "web"].includes(image.name))
2293
2572
  throw new Error(cliM("cli_build_images_missing"));
2294
2573
  const context = resolve(cwd, image.context ?? ".");
2295
2574
  const imageRef = `localhost/hai-${basenameSafe(cwd)}-${image.name}:${buildId.toLowerCase().replaceAll("+", "_")}`;
2296
- const args = ["build", "-f", resolve(context, image.dockerfile ?? "Dockerfile"), "-t", imageRef];
2575
+ const args = ["build", "-f", resolve(cwd, image.dockerfile), "-t", imageRef];
2297
2576
  if (options.network !== void 0)
2298
2577
  args.push("--network", options.network);
2299
- const ignore = `${resolve(context, image.dockerfile ?? "Dockerfile")}.dockerignore`;
2578
+ const ignore = `${resolve(cwd, image.dockerfile)}.dockerignore`;
2300
2579
  if (runtime === "podman" && existsSync(ignore))
2301
2580
  args.push("--ignorefile", ignore);
2302
2581
  if (runtime === "podman") {
@@ -2308,8 +2587,6 @@ async function buildCommand(options) {
2308
2587
  }
2309
2588
  }
2310
2589
  }
2311
- if (image.target)
2312
- args.push("--target", image.target);
2313
2590
  if (options.sqlitePrebuildMirror)
2314
2591
  args.push("--build-arg", `SQLITE_PREBUILD_MIRROR=${options.sqlitePrebuildMirror}`);
2315
2592
  built.push(imageRef);
@@ -2328,7 +2605,7 @@ async function buildCommand(options) {
2328
2605
  writeFileSync(join(parent, `latest-${buildId}`), `${buildId}
2329
2606
  `);
2330
2607
  renameSync(join(parent, `latest-${buildId}`), join(parent, "latest"));
2331
- core.logger.info(cliM("cli_build_ready", { params: { path: output, runtime } }));
2608
+ writeCliMessage(cliM("cli_build_ready", { params: { path: output, runtime } }));
2332
2609
  } catch (error) {
2333
2610
  for (const imageRef of built) {
2334
2611
  try {
@@ -2341,41 +2618,172 @@ async function buildCommand(options) {
2341
2618
  rmSync(staging, { recursive: true, force: true });
2342
2619
  }
2343
2620
  }
2344
- function basenameSafe(directory) {
2345
- return directory.slice(dirname(directory).length + 1).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "") || "app";
2346
- }
2347
- function displayDeployValue(name, value) {
2348
- if (/PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(name.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(name) || value.includes("PRIVATE KEY"))
2349
- return cliM("cli_deploy_secret");
2350
- try {
2351
- const parsed2 = JSON.parse(value);
2352
- if (parsed2 && typeof parsed2 === "object") {
2353
- const sanitize = (object) => {
2354
- if (Array.isArray(object))
2355
- return object.map(sanitize);
2356
- if (object && typeof object === "object")
2357
- return Object.fromEntries(Object.entries(object).map(([key, item]) => [key, /PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(key.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(key) ? cliM("cli_deploy_secret") : typeof item === "string" ? displayDeployValue(key, item) : sanitize(item)]));
2358
- return object;
2359
- };
2360
- return JSON.stringify(sanitize(parsed2));
2361
- }
2362
- } catch {
2363
- }
2364
- try {
2365
- const url = new URL(value);
2366
- if (url.username || url.password) {
2367
- url.username = "***";
2368
- url.password = "***";
2369
- }
2370
- for (const key of url.searchParams.keys()) {
2371
- if (/password|secret|token|key|authorization/i.test(key))
2372
- url.searchParams.set(key, "***");
2621
+ function prepareDependencyInputs(cwd) {
2622
+ const destination = join(cwd, "images", "dependencies");
2623
+ mkdirSync(destination, { recursive: true });
2624
+ const tracking = join(destination, ".managed.json");
2625
+ const previous = existsSync(tracking) ? JSON.parse(readFileSync(tracking, "utf8")) : [];
2626
+ const files = [];
2627
+ const walk = (directory) => {
2628
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
2629
+ const source = join(directory, entry.name);
2630
+ if (source === destination || ["node_modules", ".git", ".hai", ".turbo", ".svelte-kit", ".pnpm-store", ".cache", "dist", "build", "data", "tmp", "test-results", "playwright-report"].includes(entry.name))
2631
+ continue;
2632
+ if (entry.isDirectory()) {
2633
+ walk(source);
2634
+ continue;
2635
+ }
2636
+ if (!entry.isFile() || !(entry.name === "package.json" || entry.name === ".pnpmfile.cjs" || /\.(?:tgz|patch)$/.test(entry.name)))
2637
+ continue;
2638
+ const file = relative(cwd, source);
2639
+ mkdirSync(dirname(join(destination, file)), { recursive: true });
2640
+ const target = join(destination, file);
2641
+ if (!existsSync(target) || !readFileSync(source).equals(readFileSync(target)))
2642
+ copyFileSync(source, target);
2643
+ files.push(file);
2373
2644
  }
2374
- return url.toString();
2375
- } catch {
2376
- return value;
2645
+ };
2646
+ walk(cwd);
2647
+ for (const file of previous) {
2648
+ const target = resolve(destination, file);
2649
+ if (!files.includes(file) && !relative(destination, target).startsWith(".."))
2650
+ rmSync(target, { force: true });
2377
2651
  }
2652
+ const content = `${JSON.stringify(files)}
2653
+ `;
2654
+ if (!existsSync(tracking) || readFileSync(tracking, "utf8") !== content)
2655
+ writeFileSync(tracking, content);
2656
+ }
2657
+ function basenameSafe(directory) {
2658
+ return directory.slice(dirname(directory).length + 1).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "") || "app";
2378
2659
  }
2660
+
2661
+ // ../reldb/dist/chunk-SDS2ZT3V.js
2662
+ external_exports.enum(["sqlite", "postgresql", "mysql"]);
2663
+ var ReldbOperationLogConfigSchema = external_exports.object({
2664
+ read: external_exports.boolean().default(false),
2665
+ write: external_exports.boolean().default(false),
2666
+ /** 安全数量摘要的最大字符数;0 表示省略摘要,永不记录 SQL 原文或参数值。 */
2667
+ maxLength: external_exports.number().int().min(0).default(1e3),
2668
+ level: external_exports.enum(["info", "debug", "trace"]).default("debug")
2669
+ });
2670
+ var OperationLogConfigFieldSchema = {
2671
+ operationLog: ReldbOperationLogConfigSchema.optional()
2672
+ };
2673
+ var PoolConfigSchema = external_exports.object({
2674
+ /** PostgreSQL 已建立连接的最小保留数(默认 1,不主动预建) */
2675
+ min: external_exports.number().int().min(0).default(1),
2676
+ /** 最大连接数(默认 10) */
2677
+ max: external_exports.number().int().min(1).default(10),
2678
+ /** PostgreSQL 空闲连接超时,毫秒(默认 30000;0 禁用) */
2679
+ idleTimeout: external_exports.number().int().min(0).default(3e4),
2680
+ /** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */
2681
+ acquireTimeout: external_exports.number().int().min(0).default(1e4)
2682
+ });
2683
+ var MysqlPoolConfigSchema = external_exports.object({
2684
+ /** 最大连接数(默认 10) */
2685
+ max: external_exports.number().int().min(1).default(10),
2686
+ /** 获取连接超时,毫秒(默认10000);PostgreSQL >=0,0禁用;MySQL排队+建连总期限为1–2147483647 */
2687
+ acquireTimeout: external_exports.number().int().min(1).max(2147483647).default(1e4),
2688
+ /** MySQL 等待队列最大请求数(默认 1000;正整数,不支持无限队列) */
2689
+ queueLimit: external_exports.number().int().min(1).default(1e3)
2690
+ }).strict();
2691
+ var SslConfigSchema = external_exports.union([
2692
+ external_exports.boolean(),
2693
+ external_exports.enum(["require", "prefer", "allow", "disable"]),
2694
+ external_exports.record(external_exports.string(), external_exports.unknown())
2695
+ ]);
2696
+ var MysqlSslConfigSchema = external_exports.union([
2697
+ external_exports.literal(false),
2698
+ external_exports.object({
2699
+ /** MySQL 是否校验证书链(默认 true);false 明确放弃服务端认证 */
2700
+ rejectUnauthorized: external_exports.boolean().default(true),
2701
+ /** MySQL 是否验证 DNS 主机身份(默认 true);仅证书链验证开启时有效 */
2702
+ verifyIdentity: external_exports.boolean().default(true),
2703
+ /** MySQL 信任的 CA,PEM 内容;省略时使用 Node 信任库 */
2704
+ ca: external_exports.string().min(1).optional(),
2705
+ /** MySQL 客户端证书,PEM 内容;必须同时提供 key */
2706
+ cert: external_exports.string().min(1).optional(),
2707
+ /** MySQL 客户端私钥,PEM 内容;必须同时提供 cert */
2708
+ key: external_exports.string().min(1).optional()
2709
+ }).strict().refine((ssl) => Boolean(ssl.cert) === Boolean(ssl.key), { path: ["key"] })
2710
+ ]);
2711
+ var SqliteOptionsSchema = external_exports.object({
2712
+ /** 是否启用 WAL 模式(默认 true),可提高并发读写性能 */
2713
+ walMode: external_exports.boolean().default(true),
2714
+ /** 是否只读模式(默认 false) */
2715
+ readonly: external_exports.boolean().default(false),
2716
+ /**
2717
+ * 执行方式(默认 sync)。
2718
+ *
2719
+ * - `sync`:主线程直接执行 better-sqlite3(同步阻塞主线程事件循环)。
2720
+ * - `worker`:在专用 worker 线程持有唯一连接执行,把阻塞移出主线程。
2721
+ * 注意 WAL 只改善数据库锁并发,无法避免 better-sqlite3 同步调用占用调用线程。
2722
+ */
2723
+ executor: external_exports.enum(["sync", "worker"]).default("sync"),
2724
+ /** worker 执行方式下等待连接打开的超时(毫秒,默认 10000) */
2725
+ workerConnectTimeoutMs: external_exports.number().int().positive().default(1e4)
2726
+ });
2727
+ var MysqlOptionsSchema = external_exports.object({
2728
+ /** 字符集(默认 utf8mb4),推荐使用 utf8mb4 支持完整 Unicode */
2729
+ charset: external_exports.string().default("utf8mb4")
2730
+ }).strict();
2731
+ var NetworkConfigSchema = external_exports.object({
2732
+ /**
2733
+ * 连接字符串(可选)
2734
+ * 如果提供 url,将优先使用,忽略 host/port 等字段
2735
+ */
2736
+ url: external_exports.string().optional(),
2737
+ /** 数据库主机地址(默认 localhost) */
2738
+ host: external_exports.string().default("localhost"),
2739
+ /**
2740
+ * 数据库端口
2741
+ * - PostgreSQL 默认 5432
2742
+ * - MySQL 默认 3306
2743
+ */
2744
+ port: external_exports.number().int().min(1).max(65535).optional(),
2745
+ /** 数据库名称 */
2746
+ database: external_exports.string(),
2747
+ /** 数据库用户名 */
2748
+ user: external_exports.string().optional(),
2749
+ /** 数据库密码 */
2750
+ password: external_exports.string().optional(),
2751
+ /** SSL/TLS 连接配置 */
2752
+ ssl: SslConfigSchema.optional(),
2753
+ /** 连接池配置 */
2754
+ pool: PoolConfigSchema.optional()
2755
+ });
2756
+ var SqliteConfigSchema = external_exports.object({
2757
+ type: external_exports.literal("sqlite"),
2758
+ /** 数据库文件路径(如 './data.db')或 ':memory:'(内存数据库) */
2759
+ database: external_exports.string(),
2760
+ /** SQLite 特定选项 */
2761
+ sqlite: SqliteOptionsSchema.optional(),
2762
+ ...OperationLogConfigFieldSchema
2763
+ });
2764
+ var PostgresqlConfigSchema = NetworkConfigSchema.extend({
2765
+ type: external_exports.literal("postgresql"),
2766
+ ...OperationLogConfigFieldSchema
2767
+ });
2768
+ var MysqlConfigSchema = NetworkConfigSchema.extend({
2769
+ type: external_exports.literal("mysql"),
2770
+ /** 网络数据库URL;MySQL空串表示使用分字段,非空仅mysql://且拒绝query/fragment,优先于host/port/database/user/password */
2771
+ url: external_exports.union([external_exports.literal("").transform(() => void 0), external_exports.url({ protocol: /^mysql$/ }).refine((value) => !value.includes("?") && !value.includes("#"))]).optional(),
2772
+ /** 数据库:SQLite文件路径或:memory:;PostgreSQL必填名称;MySQL无URL时必填,URL模式从URL读取 */
2773
+ database: external_exports.string().min(1).optional(),
2774
+ /** TLS:PostgreSQL使用其模式;MySQL仅false或严格对象,空对象默认校验证书链和DNS身份 */
2775
+ ssl: MysqlSslConfigSchema.optional(),
2776
+ /** 连接池配置:PostgreSQL支持min/idleTimeout;MySQL仅max/acquireTimeout/queueLimit,拒绝其他字段 */
2777
+ pool: MysqlPoolConfigSchema.optional(),
2778
+ /** MySQL 特定选项 */
2779
+ mysql: MysqlOptionsSchema.optional(),
2780
+ ...OperationLogConfigFieldSchema
2781
+ }).refine((config) => Boolean(config.url) || Boolean(config.database), { path: ["database"] });
2782
+ var ReldbConfigSchema = external_exports.discriminatedUnion("type", [
2783
+ SqliteConfigSchema,
2784
+ PostgresqlConfigSchema,
2785
+ MysqlConfigSchema
2786
+ ]);
2379
2787
  var CLOUD_FIELDS = {
2380
2788
  "neon": ["apiKey"],
2381
2789
  "upstash": ["email", "apiKey"],
@@ -2421,7 +2829,7 @@ async function ask(question) {
2421
2829
  throw new Error(cliM("cli_deploy_cancelled"));
2422
2830
  return answer.value;
2423
2831
  }
2424
- function validateField(value, field) {
2832
+ function validateField(value, field, backend) {
2425
2833
  if (value === "")
2426
2834
  return field.required ? cliM("cli_deploy_required") : true;
2427
2835
  if (field.kind === "port")
@@ -2439,8 +2847,8 @@ function validateField(value, field) {
2439
2847
  }
2440
2848
  if (field.name === "ssl") {
2441
2849
  const parsed2 = parseDocument(value);
2442
- const ssl = parsed2.errors.length ? void 0 : parsed2.toJS();
2443
- return typeof ssl === "boolean" || ["require", "prefer", "allow", "disable"].includes(String(ssl)) || ssl !== null && typeof ssl === "object" && !Array.isArray(ssl) || cliM("cli_deploy_invalid_value");
2850
+ const schema = backend === "mysql" ? MysqlSslConfigSchema : SslConfigSchema;
2851
+ return parsed2.errors.length === 0 && schema.safeParse(parsed2.toJS()).success || cliM("cli_deploy_invalid_value");
2444
2852
  }
2445
2853
  if (field.name === "headers") {
2446
2854
  try {
@@ -2459,21 +2867,21 @@ function ensureConfigNode(appDir, requirement, backend) {
2459
2867
  return;
2460
2868
  const directory = join(appDir, "config");
2461
2869
  mkdirSync(directory, { recursive: true });
2462
- const path7 = join(directory, `_${requirement.configName}.yml`);
2463
- const document = parseDocument(existsSync(path7) ? readFileSync(path7, "utf8") : "{}\n");
2870
+ const path9 = join(directory, `_${requirement.configName}.yml`);
2871
+ const document = parseDocument(existsSync(path9) ? readFileSync(path9, "utf8") : "{}\n");
2464
2872
  if (document.errors.length)
2465
2873
  throw new Error(cliM("cli_deploy_invalid_value"));
2466
2874
  if (requirement.configPath.length && !document.hasIn(requirement.configPath))
2467
- document.setIn(requirement.configPath, requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {});
2875
+ document.setIn(requirement.configPath, document.createNode(requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {}));
2468
2876
  if (requirement.service === "ldap") {
2469
2877
  if (!document.hasIn(["login"]))
2470
- document.setIn(["login"], {});
2878
+ document.set("login", document.createNode({}));
2471
2879
  if (!document.hasIn(["login", "ldap"]))
2472
2880
  document.setIn(["login", "ldap"], `\${HAI_IAM_LOGIN_LDAP:false}`);
2473
2881
  }
2474
2882
  const content = document.toString();
2475
- if (!existsSync(path7) || readFileSync(path7, "utf8") !== content)
2476
- writeFileSync(path7, content);
2883
+ if (!existsSync(path9) || readFileSync(path9, "utf8") !== content)
2884
+ writeFileSync(path9, content);
2477
2885
  }
2478
2886
  function connectionEnv(requirement, backend, config) {
2479
2887
  if (requirement.service === "ldap")
@@ -2490,7 +2898,7 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2490
2898
  const current = choices.find((item) => item.backend === requirement.backend);
2491
2899
  if (!current)
2492
2900
  throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: requirement.backend } }));
2493
- core.logger.info(cliM("cli_deploy_middleware", { params: { service: requirement.service, backend: current.label, purpose: module.describeMiddlewareBackend(current), source: requirement.source } }));
2901
+ writeCliMessage(cliM("cli_deploy_middleware", { params: { service: requirement.service, backend: current.label, purpose: module.describeMiddlewareBackend(current), source: requirement.source } }));
2494
2902
  const canKeep = canKeepMiddleware(requirement, current, environment);
2495
2903
  const mode = yes ? canKeep ? "keep" : "default" : await ask({
2496
2904
  type: "select",
@@ -2529,8 +2937,11 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2529
2937
  if (deployment.errors.length)
2530
2938
  throw new Error(cliM("cli_deploy_invalid_value"));
2531
2939
  if (!deployment.hasIn(["container", "s3Engine"])) {
2532
- if (!deployment.hasIn(["container"]))
2533
- deployment.setIn(["container"], {});
2940
+ const node = deployment.get("container", true);
2941
+ if (node === void 0 || isScalar(node) && node.value === null)
2942
+ deployment.set("container", deployment.createNode({}));
2943
+ else if (!isMap(node))
2944
+ throw new Error(cliM("cli_deploy_yaml_node", { params: { path: deploymentFile, field: "container" } }));
2534
2945
  deployment.setIn(["container", "s3Engine"], `\${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}`);
2535
2946
  writeFileSync(deploymentFile, deployment.toString());
2536
2947
  }
@@ -2544,15 +2955,16 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2544
2955
  for (const name of CLOUD_FIELDS[backend.provisioner]) {
2545
2956
  const key = `HAI_DEPLOY_SERVICES_${requirement.service.toUpperCase()}_${name.toUpperCase()}`;
2546
2957
  const initial = String(priorConfig[name] ?? environment.values[key] ?? "");
2547
- const value = yes ? initial : String(await ask({ type: /key|token|secret/i.test(name) ? "password" : "text", name: "value", message: key, initial, validate: (input) => Boolean(input) || cliM("cli_deploy_required") }));
2958
+ const label = `${backend.label} \u2014 ${cliM(`cli_deploy_cloud_input_${name}`)}`;
2959
+ const value = yes ? initial : String(await ask({ type: /key|token|secret/i.test(name) ? "password" : "text", name: "value", message: label, initial, validate: (input) => Boolean(input) || cliM("cli_deploy_required") }));
2548
2960
  if (!value)
2549
- throw new Error(`${key}: ${cliM("cli_deploy_required")}`);
2961
+ throw new Error(`${label}: ${cliM("cli_deploy_required")}`);
2550
2962
  cloud[name] = value;
2551
2963
  updates[key] = value;
2552
2964
  }
2553
2965
  services[requirement.service] = cloud;
2554
2966
  } else {
2555
- if (mode === "default" && (requirement.service === "sms" || backendName === "api"))
2967
+ if (mode === "default" && ["aliyun-sms", "api"].includes(backendName))
2556
2968
  throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: backend.label } }));
2557
2969
  const connection = mode === "custom" && backendName === current.backend ? { ...requirement.config } : requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service } : {};
2558
2970
  for (const field of backend.fields) {
@@ -2560,22 +2972,27 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2560
2972
  const initial = previous2 === void 0 ? field.defaultValue ?? "" : typeof previous2 === "object" ? JSON.stringify(previous2) : String(previous2);
2561
2973
  const variable = `${requirement.envPrefix}_${field.name.toUpperCase()}`;
2562
2974
  const info = moduleEnvironmentVariables(requirement.configName).find((item) => item.name === variable);
2563
- const value = mode === "custom" && !yes ? String(await ask({ type: field.secret ? "password" : "text", name: "value", message: cliM("cli_deploy_field", { params: { name: variable, description: info?.description ?? field.name, constraint: info?.constraint ?? field.kind ?? "string" } }), initial, validate: (input) => validateField(String(input), field) })) : initial;
2975
+ const value = mode === "custom" && !yes ? String(await ask({ type: field.secret ? "password" : "text", name: "value", message: cliM("cli_deploy_field", { params: { description: info?.description ?? field.name, constraint: info?.constraint ?? field.kind ?? "string" } }), initial, validate: (input) => validateField(String(input), field, backendName) })) : initial;
2976
+ if (field.name === "ssl" && validateField(value, field, backendName) !== true)
2977
+ throw new Error(cliM("cli_deploy_invalid_value"));
2564
2978
  if (value !== "") {
2565
2979
  connection[field.name] = field.kind === "number" || field.kind === "port" ? Number(value) : field.kind === "boolean" ? value === "true" : field.name === "headers" || field.name === "ssl" ? parseDocument(value).toJS() : value;
2566
2980
  } else {
2567
2981
  delete connection[field.name];
2568
2982
  }
2569
2983
  }
2984
+ if (requirement.service === "db") {
2985
+ const validated = ReldbConfigSchema.safeParse({ ...connection, type: backendName });
2986
+ if (!validated.success)
2987
+ throw new Error(cliM("cli_deploy_config_invalid", { params: { path: join(scan.appDir ?? ".", "config", "_db.yml"), fields: validated.error.issues.map((issue) => issue.path.join(".")).join(", ") } }));
2988
+ }
2570
2989
  Object.assign(updates, connectionEnv(requirement, backendName, connection));
2571
2990
  }
2572
2991
  ensureConfigNode(scan.appDir ?? ".", requirement, backendName);
2573
2992
  const saved = module.saveDeploymentEnvironment(environment, updates);
2574
2993
  if (!saved.success)
2575
2994
  throw new Error(saved.error.message);
2576
- for (const [name, value] of Object.entries(updates)) {
2577
- core.logger.info(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
2578
- }
2995
+ writeCliMessage(cliM("cli_deploy_saved", { params: { service: requirement.service } }));
2579
2996
  }
2580
2997
  config.services = Object.keys(services).length ? services : void 0;
2581
2998
  return modes;
@@ -2591,14 +3008,19 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2591
3008
  if (existing.type && existing.type !== "docker-ssh")
2592
3009
  throw new Error(cliM("cli_deploy_bundle_provider"));
2593
3010
  config.provider = { type: "docker-ssh", remote: { runtime: "auto" }, expose: { hostPort: 3e3 }, ...existing };
3011
+ const candidate = module.ProviderConfigSchema.safeParse(config.provider);
3012
+ if (!candidate.success && candidate.error.issues.some((issue) => issue.path[0] === "ssh")) {
3013
+ const provider2 = configObject(config.provider);
3014
+ delete provider2.ssh;
3015
+ }
2594
3016
  }
2595
3017
  const current = module.ProviderConfigSchema.safeParse(config.provider);
2596
3018
  if (current.success && (bundleOnly || current.data.type !== "docker-ssh" || current.data.ssh)) {
2597
3019
  config.provider = current.data;
2598
- core.logger.info(cliM("cli_deploy_target_reused", { params: { target: current.data.type === "docker-ssh" ? current.data.ssh ? `${current.data.ssh.username}@${current.data.ssh.host}:${current.data.ssh.port}` : cliM("cli_deploy_bundle_local") : "Vercel" } }));
3020
+ writeCliMessage(bundleOnly ? cliM("cli_deploy_bundle_target") : cliM("cli_deploy_target_reused", { params: { target: current.data.type === "docker-ssh" && current.data.ssh ? `${current.data.ssh.username}@${current.data.ssh.host}:${current.data.ssh.port}` : "Vercel" } }));
2599
3021
  return;
2600
3022
  }
2601
- if (nonInteractive)
3023
+ if (nonInteractive || bundleOnly)
2602
3024
  throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.success ? "provider.ssh" : current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
2603
3025
  const original = configObject(config.provider);
2604
3026
  const platform = await prompts({
@@ -2614,9 +3036,9 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2614
3036
  const answer = await prompts({
2615
3037
  type: options.secret ? "password" : "text",
2616
3038
  name: "value",
2617
- message: cliM("cli_deploy_target_field", { params: { name } }),
3039
+ message: cliM(`cli_deploy_input${name.split("_").at(-1)}`),
2618
3040
  initial: options.secret ? void 0 : initial === void 0 ? "" : String(initial),
2619
- validate: (value) => options.port ? /^\d+$/.test(value) && Number(value) >= 1 && Number(value) <= 65535 || cliM("cli_deploy_invalid_value") : options.optional || !!value.trim() || cliM("cli_deploy_invalid_value")
3041
+ validate: (value) => options.port ? /^\d+$/.test(value) && Number(value) >= 1 && Number(value) <= 65535 || cliM("cli_deploy_port_invalid") : options.optional || !!value.trim() || cliM("cli_deploy_invalid_value")
2620
3042
  });
2621
3043
  if (answer.value === void 0)
2622
3044
  throw new Error(cliM("cli_deploy_cancelled"));
@@ -2624,7 +3046,7 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2624
3046
  };
2625
3047
  let provider;
2626
3048
  if (platform.value === "docker-ssh") {
2627
- core.logger.info(cliM("cli_deploy_target_ssh_hint"));
3049
+ writeCliMessage(cliM("cli_deploy_target_ssh_hint"));
2628
3050
  const ssh = configObject(original.ssh);
2629
3051
  const remote = configObject(original.remote);
2630
3052
  const expose = configObject(original.expose);
@@ -2655,15 +3077,27 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2655
3077
  if (!saved.success)
2656
3078
  throw new Error(saved.error.message);
2657
3079
  config.provider = validated.data;
2658
- core.logger.info(cliM("cli_deploy_target_saved"));
3080
+ writeCliMessage(cliM("cli_deploy_target_saved"));
2659
3081
  }
2660
3082
  async function deployCommand(options) {
2661
3083
  const spinner = ora();
2662
3084
  const cwd = options.cwd ?? process4.cwd();
2663
3085
  let appDir = resolve(cwd, options.appDir ?? ".");
2664
3086
  let closeDeploy = null;
3087
+ let restoreProjectLogger;
2665
3088
  const originalEnv = { ...process4.env };
3089
+ const previousLogLevel = core.logger.getLevel();
3090
+ core.logger.setLevel("fatal");
2666
3091
  try {
3092
+ if (options.runtime && !["auto", "podman", "docker"].includes(options.runtime))
3093
+ throw new Error(cliM("cli_build_runtime_missing"));
3094
+ if (options.artifactDir && options.build)
3095
+ throw new Error(cliM("cli_deploy_build_artifact_conflict"));
3096
+ if (!options.build && (options.network !== void 0 || options.sqlitePrebuildMirror !== void 0 || options.httpProxy === false))
3097
+ throw new Error(cliM("cli_deploy_build_options_required"));
3098
+ let selectedServices = options.services?.split(",").map((name) => name.trim());
3099
+ if (selectedServices && (!selectedServices.length || selectedServices.some((name) => !/^[a-z][a-z0-9-]*$/.test(name)) || new Set(selectedServices).size !== selectedServices.length))
3100
+ throw new Error(cliM("cli_build_selection_invalid"));
2667
3101
  let environmentName = options.env;
2668
3102
  if (!environmentName) {
2669
3103
  if (!process4.stdin.isTTY)
@@ -2678,15 +3112,24 @@ async function deployCommand(options) {
2678
3112
  let deployModule;
2679
3113
  try {
2680
3114
  const localRequire = createRequire(resolve(appDir, "package.json"));
3115
+ try {
3116
+ const { core: projectCore } = await import(pathToFileURL(localRequire.resolve("@h-ai/core")).href);
3117
+ if (projectCore !== core) {
3118
+ const level = projectCore.logger.getLevel();
3119
+ projectCore.logger.setLevel("fatal");
3120
+ restoreProjectLogger = () => projectCore.logger.setLevel(level);
3121
+ }
3122
+ } catch {
3123
+ }
2681
3124
  let localModule;
2682
3125
  try {
2683
3126
  localModule = localRequire.resolve("@h-ai/deploy");
2684
3127
  } catch {
2685
3128
  }
2686
- deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-RDQ2EIBT.js');
3129
+ deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-DSJJXM2F.js');
2687
3130
  } catch {
2688
- core.logger.error(chalk.red(cliM("cli_deploy_module_missing")));
2689
- core.logger.info(chalk.cyan(" pnpm add @h-ai/deploy"));
3131
+ writeCliError(chalk.red(cliM("cli_deploy_module_missing")));
3132
+ writeCliMessage(chalk.cyan(" pnpm add -D @h-ai/deploy"));
2690
3133
  throw new Error(cliM("cli_deploy_module_missing"));
2691
3134
  }
2692
3135
  const { deploy } = deployModule;
@@ -2695,6 +3138,15 @@ async function deployCommand(options) {
2695
3138
  throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
2696
3139
  const scan = scanResult.data;
2697
3140
  appDir = scan.appDir ?? appDir;
3141
+ let buildRoot = appDir;
3142
+ while (!existsSync(resolve(buildRoot, "hai.build.json")) && dirname(buildRoot) !== buildRoot)
3143
+ buildRoot = dirname(buildRoot);
3144
+ const buildFile = resolve(buildRoot, "hai.build.json");
3145
+ const buildConfig = existsSync(buildFile) ? JSON.parse(readFileSync(buildFile, "utf8")) : {};
3146
+ const names = [...buildConfig.images?.map((image) => image.name) ?? ["app", "web"], ...(scan.middlewares ?? []).filter((item) => !["sqlite", "local", "memory", "disabled", "console", "lancedb"].includes(item.backend)).map((item) => item.service)];
3147
+ const available = [...new Set(names)];
3148
+ if (selectedServices?.some((name) => !available.includes(name)))
3149
+ throw new Error(cliM("cli_deploy_services_invalid", { params: { services: available.join(", ") } }));
2698
3150
  const environment = deployModule.loadDeploymentEnvironment({ environment: environmentName, envDir: scan.envDir ?? appDir });
2699
3151
  if (!environment.success)
2700
3152
  throw new Error(environment.error.message);
@@ -2709,7 +3161,7 @@ async function deployCommand(options) {
2709
3161
  const configPath = resolve(appDir, "config", "_deploy.yml");
2710
3162
  if (!existsSync(configPath)) {
2711
3163
  spinner.fail(chalk.red(cliM("cli_deploy_config_missing", { params: { path: configPath } })));
2712
- core.logger.info(chalk.cyan(cliM("cli_deploy_config_hint")));
3164
+ writeCliMessage(chalk.cyan(cliM("cli_deploy_config_hint")));
2713
3165
  throw new Error(cliM("cli_deploy_config_missing", { params: { path: configPath } }));
2714
3166
  }
2715
3167
  const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath, process4.env);
@@ -2717,9 +3169,51 @@ async function deployCommand(options) {
2717
3169
  throw new Error(loadedConfig.error.message);
2718
3170
  const deployConfig = loadedConfig.data;
2719
3171
  spinner.succeed(cliM("cli_deploy_config_loaded"));
3172
+ if (!options.build && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
3173
+ let artifactDirectory = options.artifactDir ? resolve(cwd, options.artifactDir) : resolve(buildRoot, ".hai/build/docker");
3174
+ if (existsSync(resolve(artifactDirectory, "latest"))) {
3175
+ const pointer = readFileSync(resolve(artifactDirectory, "latest"), "utf8").trim();
3176
+ if (!/^[a-z0-9][\w.+-]*$/i.test(pointer))
3177
+ throw new Error(cliM("cli_deploy_artifact_invalid"));
3178
+ artifactDirectory = resolve(artifactDirectory, pointer);
3179
+ }
3180
+ if (existsSync(resolve(artifactDirectory, "manifest.json"))) {
3181
+ try {
3182
+ const artifact = await deployModule.readDeploymentArtifact(artifactDirectory);
3183
+ if (artifact.partial)
3184
+ selectedServices = artifact.images.map((image) => image.name);
3185
+ } catch {
3186
+ throw new Error(cliM("cli_deploy_artifact_invalid"));
3187
+ }
3188
+ }
3189
+ }
2720
3190
  await configureDeployTarget(deployModule, deployConfig, environment.data, options.yes, options.bundleOnly);
3191
+ if (configObject(deployConfig.provider).type !== "docker-ssh" && (selectedServices || options.build))
3192
+ throw new Error(cliM("cli_deploy_bundle_provider"));
3193
+ if (!options.yes && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
3194
+ if (existsSync(buildFile)) {
3195
+ if (available.length > 1) {
3196
+ const choice = await prompts({ type: "multiselect", name: "value", message: cliM("cli_deploy_choose_services"), choices: available.map((name) => ({ title: name, value: name, selected: true })), min: 1 });
3197
+ if (!Array.isArray(choice.value) || choice.value.length === 0)
3198
+ throw new Error(cliM("cli_deploy_cancelled"));
3199
+ if (choice.value.length !== available.length)
3200
+ selectedServices = choice.value;
3201
+ }
3202
+ }
3203
+ }
2721
3204
  spinner.succeed(cliM("cli_deploy_scanned", { params: { name: scan.appName, sveltekit: String(scan.isSvelteKit), services: scan.requiredServices.join(", ") || cliM("cli_deploy_none") } }));
2722
- let skipMiddleware = options.skipMiddleware;
3205
+ if (options.build) {
3206
+ if (!existsSync(buildFile))
3207
+ throw new Error(cliM("cli_build_images_missing"));
3208
+ const imageNames = selectedServices?.filter((name) => ["app", "web"].includes(name));
3209
+ if (!selectedServices || imageNames?.length) {
3210
+ writeCliMessage(cliM("cli_deploy_build_started"));
3211
+ await buildCommand({ cwd: buildRoot, target: "docker", images: imageNames?.join(","), runtime: options.runtime, network: options.network, sqlitePrebuildMirror: options.sqlitePrebuildMirror, httpProxy: options.httpProxy });
3212
+ }
3213
+ }
3214
+ if (selectedServices)
3215
+ writeCliMessage(cliM("cli_deploy_services", { params: { services: selectedServices.join(", ") } }));
3216
+ let skipMiddleware = options.skipMiddleware || selectedServices?.every((name) => ["app", "web"].includes(name));
2723
3217
  if (!skipMiddleware && !options.yes && scan.requiredServices.length) {
2724
3218
  const action = await prompts({
2725
3219
  type: "select",
@@ -2732,8 +3226,8 @@ async function deployCommand(options) {
2732
3226
  skipMiddleware = action.value === "skip";
2733
3227
  }
2734
3228
  if (skipMiddleware)
2735
- core.logger.info(cliM("cli_deploy_middleware_skipped"));
2736
- const serviceModes = skipMiddleware ? Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) : await configureDeployMiddleware(deployModule, scan, environment.data, deployConfig, options.yes);
3229
+ writeCliMessage(cliM("cli_deploy_middleware_skipped"));
3230
+ const serviceModes = skipMiddleware ? Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) : await configureDeployMiddleware(deployModule, { ...scan, middlewares: scan.middlewares?.filter((item) => !selectedServices || selectedServices.includes(item.service)) }, environment.data, deployConfig, options.yes);
2737
3231
  if (skipMiddleware)
2738
3232
  deployConfig.services = void 0;
2739
3233
  for (const key of Object.keys(process4.env)) {
@@ -2748,7 +3242,7 @@ async function deployCommand(options) {
2748
3242
  spinner.start(cliM("cli_deploy_initializing"));
2749
3243
  const validatedConfig = deployModule.DeployConfigSchema.safeParse(deployConfig);
2750
3244
  if (!validatedConfig.success)
2751
- throw new Error(cliM("cli_deploy_init_failed", { params: { message: cliM("cli_deploy_invalid_value") } }));
3245
+ throw new Error(cliM("cli_deploy_config_invalid", { params: { path: configPath, fields: validatedConfig.error.issues.map((issue) => issue.path.join(".")).join(", ") } }));
2752
3246
  const initResult = await deploy.init(validatedConfig.data);
2753
3247
  if (!initResult.success) {
2754
3248
  throw new Error(cliM("cli_deploy_init_failed", { params: { message: initResult.error.message } }));
@@ -2758,6 +3252,9 @@ async function deployCommand(options) {
2758
3252
  spinner.start(cliM("cli_deploy_deploying"));
2759
3253
  const deployResult = await deploy.deployApp(appDir, {
2760
3254
  projectName: options.projectName,
3255
+ selectedServices,
3256
+ runtime: options.runtime,
3257
+ onProgress: (stage) => writeCliMessage(cliM(`cli_deploy_stage_${stage}`)),
2761
3258
  skipMiddleware,
2762
3259
  artifactDir: options.artifactDir ? resolve(cwd, options.artifactDir) : void 0,
2763
3260
  bundleOnly: options.bundleOnly,
@@ -2767,51 +3264,40 @@ async function deployCommand(options) {
2767
3264
  serviceModes
2768
3265
  });
2769
3266
  if (!deployResult.success) {
3267
+ const execution = deployModule.getDeployCommandState(deployResult.error);
3268
+ if (execution) {
3269
+ const localKey = execution.localTermination === "not-started" ? "cli_deploy_local_not_started" : execution.localTermination === "confirmed" ? "cli_deploy_local_confirmed" : "cli_deploy_local_unknown";
3270
+ writeCliMessage(cliM(localKey));
3271
+ if (execution.remoteExecution)
3272
+ writeCliMessage(cliM(execution.remoteExecution === "finished" ? "cli_deploy_remote_finished" : "cli_deploy_remote_unknown"));
3273
+ if (execution.deploymentId && execution.resultFile)
3274
+ writeCliMessage(cliM("cli_deploy_result_check", { params: { id: execution.deploymentId, path: execution.resultFile } }));
3275
+ }
2770
3276
  if (deployResult.error.ext?.recovery) {
2771
- core.logger.warn(deployResult.error.suggestion ?? cliM("cli_deploy_command_failed"), {
2772
- recovery: deployResult.error.ext.recovery
2773
- });
3277
+ writeCliMessage(deployResult.error.suggestion ?? cliM("cli_deploy_command_failed"));
2774
3278
  }
2775
3279
  throw new Error(cliM("cli_deploy_failed", { params: { message: deployResult.error.message } }));
2776
3280
  }
2777
3281
  spinner.succeed(chalk.green(cliM(deployResult.data.status === "packaged" ? "cli_deploy_bundle_ready" : "cli_deploy_success")));
2778
- for (const key of Object.keys(process4.env)) {
2779
- if (originalEnv[key] === void 0)
2780
- delete process4.env[key];
2781
- }
2782
- Object.assign(process4.env, originalEnv);
2783
- const completedEnvironment = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
2784
- if (!completedEnvironment.success)
2785
- throw new Error(completedEnvironment.error.message);
2786
- for (const requirement of scan.middlewares ?? []) {
2787
- if ((serviceModes[requirement.envPrefix] ?? serviceModes[requirement.service]) === "keep")
2788
- continue;
2789
- const value = completedEnvironment.data.values[requirement.envPrefix];
2790
- if (value !== void 0)
2791
- core.logger.info(cliM("cli_deploy_saved", { params: { name: requirement.envPrefix, value: displayDeployValue(requirement.envPrefix, value) } }));
2792
- }
2793
- core.logger.info("");
2794
- if (deployResult.data.bundlePath)
2795
- core.logger.info(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
3282
+ writeCliMessage("");
3283
+ if (deployResult.data.bundlePath && (deployResult.data.status !== "packaged" || !deployResult.data.instructions))
3284
+ writeCliMessage(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
2796
3285
  if (deployResult.data.status === "packaged")
2797
- core.logger.info(deployResult.data.instructions ?? "");
3286
+ writeCliMessage(deployResult.data.instructions ?? "");
2798
3287
  else
2799
- core.logger.info(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
2800
- core.logger.info(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
2801
- if (deployResult.data.envVarsSet.length > 0) {
2802
- core.logger.info(chalk.gray(cliM("cli_deploy_env", { params: { names: deployResult.data.envVarsSet.join(", ") } })));
2803
- }
2804
- core.logger.info("");
3288
+ writeCliMessage(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
3289
+ writeCliMessage(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
3290
+ writeCliMessage("");
2805
3291
  } catch (error) {
2806
3292
  spinner.fail(chalk.red(cliM("cli_deploy_command_failed")));
2807
- core.logger.error(error instanceof Error ? error.message : String(error));
3293
+ writeCliError(error instanceof Error ? error.message : String(error));
2808
3294
  throw error;
2809
3295
  } finally {
2810
3296
  if (closeDeploy) {
2811
3297
  try {
2812
3298
  await closeDeploy();
2813
3299
  } catch (error) {
2814
- core.logger.error("Failed to close deploy module", { error });
3300
+ writeCliError("Failed to close deploy module", error);
2815
3301
  process4.exitCode = 1;
2816
3302
  }
2817
3303
  }
@@ -2820,31 +3306,217 @@ async function deployCommand(options) {
2820
3306
  delete process4.env[key];
2821
3307
  }
2822
3308
  Object.assign(process4.env, originalEnv);
3309
+ core.logger.setLevel(previousLogLevel);
3310
+ restoreProjectLogger?.();
3311
+ }
3312
+ }
3313
+ function appendAt(source, marker, entry) {
3314
+ if (source.split(marker).length !== 2)
3315
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3316
+ if (source.split(/\r?\n/).some((line) => line.trim() === entry.trim()))
3317
+ return source;
3318
+ const moduleName = /^import .+ from ['"](.+)['"]$/.exec(entry)?.[1];
3319
+ if (moduleName) {
3320
+ for (const match of source.matchAll(/^import (?!type\b)[^\r\n]+ from ['"](\.[^'"]+)['"]\r?$/gm)) {
3321
+ if (match[1].localeCompare(moduleName) > 0)
3322
+ return `${source.slice(0, match.index)}${entry}
3323
+ ${source.slice(match.index)}`;
3324
+ }
3325
+ }
3326
+ return source.replace(marker, `${entry}
3327
+ ${marker}`);
3328
+ }
3329
+ async function generateServApi(context, target) {
3330
+ const contracts = target.packages.filter((pkg) => pkg.dependencies["@h-ai/api-contract"] && pkg.dependencies.zod && pkg.name in target.target.dependencies);
3331
+ if (contracts.length !== 1)
3332
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3333
+ const contract = contracts[0];
3334
+ const contractSources = await fse.readdir(path2.join(contract.directory, "src"));
3335
+ const aggregators = [];
3336
+ for (const name of contractSources.filter((name2) => name2.endsWith(".ts"))) {
3337
+ const file = path2.join(contract.directory, "src", name);
3338
+ const content = await fse.readFile(file, "utf8");
3339
+ if (content.includes("// @hai-generator:contracts:entries"))
3340
+ aggregators.push({ path: file, content });
3341
+ }
3342
+ if (aggregators.length !== 1)
3343
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3344
+ const aggregator = aggregators[0];
3345
+ const contractIndex = path2.join(contract.directory, "src/index.ts");
3346
+ const procedureIndex = path2.join(target.target.directory, "src/server/procedures/index.ts");
3347
+ const indexSource = await fse.readFile(contractIndex, "utf8");
3348
+ const procedureSource = await fse.readFile(procedureIndex, "utf8");
3349
+ const contractName = `${context.camelCase}Contract`;
3350
+ const factoryName = `create${context.pascalCase}Procedures`;
3351
+ const domainMarker = `// @hai-generator:domain ${context.camelCase}`;
3352
+ const contractFile = path2.join(contract.directory, "src", `${context.kebabCase}-contract.ts`);
3353
+ const procedureFile = path2.join(target.target.directory, "src/server/procedures", `${context.kebabCase}-procedures.ts`);
3354
+ const importLine = `import { ${contractName} } from './${context.kebabCase}-contract.js'`;
3355
+ const entryLine = ` ${context.camelCase}: ${contractName},`;
3356
+ const factoryImport = `import { ${factoryName} } from './${context.kebabCase}-procedures.js'`;
3357
+ const domainProperty = new RegExp(`^\\s*(?:${context.camelCase}|['"]${context.camelCase}['"]):`, "m");
3358
+ if (domainProperty.test(aggregator.content) || domainProperty.test(procedureSource)) {
3359
+ for (const file of [contractFile, procedureFile]) {
3360
+ if (!await fse.pathExists(file) || !(await fse.readFile(file, "utf8")).includes(domainMarker))
3361
+ throw new Error(cliM("cli_generateDomainExists", { params: { name: context.camelCase } }));
3362
+ }
3363
+ }
3364
+ const contractContent = `${domainMarker}
3365
+ import { apiContract } from '@h-ai/api-contract'
3366
+ import { z } from 'zod'
3367
+
3368
+ /**
3369
+ * ${context.pascalCase} \u793A\u4F8B\u5951\u7EA6\uFF1AGET \u5217\u8868\u4E0E POST \u6821\u9A8C\u56DE\u663E\uFF0C\u4E0D\u6267\u884C\u6301\u4E45\u5199\u5165\u3002
3370
+ * @example client.${context.camelCase}.create({ name: 'Example' })
3371
+ */
3372
+ export const ${contractName} = {
3373
+ list: apiContract
3374
+ .route({ method: 'GET', path: '/${context.kebabCase}', operationId: '${context.camelCase}.list', tags: ['${context.kebabCase}'] })
3375
+ .output(apiContract.haiResultSchema(z.object({ items: z.array(z.object({ name: z.string() })) }))),
3376
+ create: apiContract
3377
+ .route({ method: 'POST', path: '/${context.kebabCase}', operationId: '${context.camelCase}.create', tags: ['${context.kebabCase}'] })
3378
+ .input(z.object({ name: z.string().min(1) }))
3379
+ .output(apiContract.haiResultSchema(z.object({ name: z.string() }))),
3380
+ }
3381
+ `;
3382
+ const procedureContent = `${domainMarker}
3383
+ import type { ServContext } from '@h-ai/serv'
3384
+ import { ok } from '@h-ai/core'
3385
+ import { serv } from '@h-ai/serv'
3386
+ import { ${contractName} } from '${contract.name}'
3387
+
3388
+ /**
3389
+ * \u521B\u5EFA ${context.pascalCase} \u793A\u4F8B\u8FC7\u7A0B\uFF1B\u65B0\u589E\u6301\u4E45\u5199\u5165\u524D\u63A5\u5165\u6388\u6743\u4E0E\u4E1A\u52A1\u670D\u52A1\u3002
3390
+ * @returns \u4E0E\u5171\u4EAB\u5951\u7EA6\u4E00\u81F4\u7684\u516C\u5F00\u8FC7\u7A0B\uFF1B\u793A\u4F8B\u4E0D\u8BBF\u95EE\u7528\u6237\u6570\u636E\u3002
3391
+ * @example const procedures = { ${context.camelCase}: ${factoryName}() }
3392
+ */
3393
+ export function ${factoryName}() {
3394
+ return serv
3395
+ .implement(${contractName})
3396
+ .context<ServContext>()
3397
+ .route('list', () => ok({ items: [] }))
3398
+ .route('create', ({ input }) => ok({ name: input.name }))
3399
+ .build()
3400
+ }
3401
+ `;
3402
+ return [
3403
+ { path: contractFile, content: contractContent },
3404
+ { path: procedureFile, content: procedureContent },
3405
+ { path: aggregator.path, previousContent: aggregator.content, content: appendAt(appendAt(aggregator.content, "// @hai-generator:contracts:imports", importLine), " // @hai-generator:contracts:entries", entryLine) },
3406
+ { path: contractIndex, previousContent: indexSource, content: appendAt(indexSource, "// @hai-generator:contracts:exports", `export * from './${context.kebabCase}-contract.js'`) },
3407
+ { path: procedureIndex, previousContent: procedureSource, content: appendAt(appendAt(procedureSource, "// @hai-generator:procedures:imports", factoryImport), " // @hai-generator:procedures:entries", ` ${context.camelCase}: ${factoryName}(),`) }
3408
+ ];
3409
+ }
3410
+ function isInside(root, file) {
3411
+ const relative2 = path2.relative(root, file);
3412
+ return relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
3413
+ }
3414
+ async function readPackage(directory) {
3415
+ if (!await fse.pathExists(path2.join(directory, "package.json")))
3416
+ return void 0;
3417
+ const value = await fse.readJson(path2.join(directory, "package.json"));
3418
+ if (!value || typeof value !== "object" || Array.isArray(value))
3419
+ throw new Error(cliM("cli_generateUnsupportedProject"));
3420
+ const dependencies = {};
3421
+ for (const key of ["dependencies", "devDependencies"]) {
3422
+ const entries = key in value ? Reflect.get(value, key) : void 0;
3423
+ if (entries && typeof entries === "object" && !Array.isArray(entries)) {
3424
+ for (const [name2, version] of Object.entries(entries)) {
3425
+ if (typeof version === "string")
3426
+ dependencies[name2] = version;
3427
+ }
3428
+ }
3429
+ }
3430
+ const name = "name" in value && typeof value.name === "string" ? value.name : path2.basename(directory);
3431
+ if (!/^(?:@[\w.-]+\/)?[\w.-]+$/.test(name))
3432
+ throw new Error(cliM("cli_generateUnsupportedProject"));
3433
+ return { directory, name, dependencies };
3434
+ }
3435
+ function generateArchitecture(pkg) {
3436
+ if (pkg.dependencies["@sveltejs/kit"])
3437
+ return "sveltekit";
3438
+ if (pkg.dependencies["@h-ai/serv"])
3439
+ return "serv";
3440
+ return pkg.dependencies.svelte ? "svelte" : "other";
3441
+ }
3442
+ async function resolveGenerateTarget(options) {
3443
+ const cwd = await fse.realpath(path2.resolve(options.cwd ?? "."));
3444
+ let root = cwd;
3445
+ for (let current = cwd; ; current = path2.dirname(current)) {
3446
+ if (await fse.pathExists(path2.join(current, "pnpm-workspace.yaml"))) {
3447
+ root = current;
3448
+ break;
3449
+ }
3450
+ if (path2.dirname(current) === current)
3451
+ break;
3452
+ }
3453
+ const packages = [];
3454
+ const addPackage = async (directory) => {
3455
+ if (!isInside(root, directory))
3456
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3457
+ const real = await fse.realpath(directory);
3458
+ if (!isInside(root, real))
3459
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3460
+ const pkg = await readPackage(real);
3461
+ if (pkg && !packages.some((value) => value.directory === real))
3462
+ packages.push(pkg);
3463
+ };
3464
+ await addPackage(root);
3465
+ if (await fse.pathExists(path2.join(root, "pnpm-workspace.yaml"))) {
3466
+ const workspace = parse(await fse.readFile(path2.join(root, "pnpm-workspace.yaml"), "utf8"));
3467
+ const patterns = workspace && typeof workspace === "object" && "packages" in workspace && Array.isArray(workspace.packages) ? workspace.packages : [];
3468
+ for (const pattern of patterns) {
3469
+ if (typeof pattern !== "string" || pattern.startsWith("!") || pattern.split(/[\\/]/).includes("..") || path2.isAbsolute(pattern))
3470
+ throw new Error(cliM("cli_generateUnsupportedProject"));
3471
+ for await (const manifest of glob(`${pattern}/package.json`, { cwd: root, exclude: ["**/node_modules/**"] }))
3472
+ await addPackage(path2.dirname(path2.resolve(root, manifest)));
3473
+ }
3474
+ }
3475
+ if (cwd !== root)
3476
+ await addPackage(cwd);
3477
+ const selected = options.workspace ? packages.filter((pkg) => pkg.name === options.workspace || pkg.directory === path2.resolve(root, options.workspace)) : packages.filter((pkg) => pkg.directory === cwd);
3478
+ const applicable = (pkg) => {
3479
+ const architecture = generateArchitecture(pkg);
3480
+ return options.type === "api" ? architecture === "serv" || architecture === "sveltekit" && Boolean(pkg.dependencies["@h-ai/kit"]) && Boolean(pkg.dependencies.zod) : options.type === "page" ? architecture === "sveltekit" : options.type === "component" ? ["sveltekit", "svelte"].includes(architecture) : Object.keys(pkg.dependencies).some((name) => name.startsWith("@h-ai/"));
3481
+ };
3482
+ const candidates = options.workspace || selected.length === 1 && (cwd !== root || applicable(selected[0])) ? selected.filter(applicable) : packages.filter(applicable);
3483
+ if (candidates.length !== 1)
3484
+ throw new Error(cliM(candidates.length > 1 ? "cli_generateAmbiguousWorkspace" : "cli_generateUnsupportedProject"));
3485
+ const target = candidates[0];
3486
+ if (generateArchitecture(target) === "sveltekit" && (options.type === "page" || options.type === "api")) {
3487
+ for (const name of ["svelte.config.js", "svelte.config.mjs", "svelte.config.cjs", "svelte.config.ts"]) {
3488
+ const configFile = path2.join(target.directory, name);
3489
+ if (await fse.pathExists(configFile) && /\bfiles\s*[:.[(]/.test(await fse.readFile(configFile, "utf8")))
3490
+ throw new Error(cliM("cli_generateCustomRoutes"));
3491
+ }
2823
3492
  }
3493
+ return { root, target, packages };
2824
3494
  }
3495
+
3496
+ // src/commands/cli-generate.ts
2825
3497
  var GENERATORS = {
2826
3498
  page: {
2827
- description: "SvelteKit \u9875\u9762",
3499
+ description: () => cliM("cli_generatePageDescription"),
2828
3500
  defaultPath: "src/routes",
2829
3501
  generate: generatePage
2830
3502
  },
2831
3503
  component: {
2832
- description: "Svelte \u7EC4\u4EF6",
3504
+ description: () => cliM("cli_generateComponentDescription"),
2833
3505
  defaultPath: "src/lib/components",
2834
3506
  generate: generateComponent
2835
3507
  },
2836
3508
  api: {
2837
- description: "API \u7AEF\u70B9",
3509
+ description: () => cliM("cli_generateApiDescription"),
2838
3510
  defaultPath: "src/routes/api",
2839
3511
  generate: generateApi
2840
3512
  },
2841
3513
  model: {
2842
- description: "\u6570\u636E\u6A21\u578B",
3514
+ description: () => cliM("cli_generateModelDescription"),
2843
3515
  defaultPath: "src/lib/models",
2844
3516
  generate: generateModel
2845
3517
  },
2846
3518
  migration: {
2847
- description: "\u6570\u636E\u5E93\u8FC1\u79FB",
3519
+ description: () => cliM("cli_generateMigrationDescription"),
2848
3520
  defaultPath: "migrations",
2849
3521
  generate: generateMigration
2850
3522
  }
@@ -2853,55 +3525,43 @@ async function generate(options) {
2853
3525
  const spinner = ora();
2854
3526
  const cwd = options.cwd ?? ".";
2855
3527
  try {
2856
- const project = await detectProject(cwd);
2857
- if (!project?.isHaiProject) {
2858
- core.logger.warn(chalk.yellow("\u8B66\u544A: \u5F53\u524D\u76EE\u5F55\u4E0D\u662F hai \u9879\u76EE"));
2859
- }
2860
3528
  const resolvedOptions = await resolveGenerateOptions(options);
2861
3529
  const generator = GENERATORS[resolvedOptions.type];
2862
- if (!generator) {
2863
- throw new Error(`\u672A\u77E5\u7684\u751F\u6210\u5668\u7C7B\u578B: ${resolvedOptions.type}`);
2864
- }
2865
3530
  if (!/^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i.test(resolvedOptions.name))
2866
3531
  throw new Error(cliM("cli_invalidGenerateName"));
2867
- const outputDir = path2.resolve(cwd, resolvedOptions.output || generator.defaultPath);
2868
- const resolvedCwd = path2.resolve(cwd);
2869
- if (!outputDir.startsWith(resolvedCwd + path2.sep) && outputDir !== resolvedCwd) {
2870
- throw new Error(cliM("cli_generateOutsideWorkspace"));
3532
+ const target = await resolveGenerateTarget(resolvedOptions);
3533
+ const architecture = generateArchitecture(target.target);
3534
+ const outputDir = path2.resolve(target.target.directory, resolvedOptions.output || generator.defaultPath);
3535
+ if (["page", "api"].includes(resolvedOptions.type) && architecture === "sveltekit") {
3536
+ const routes = path2.join(target.target.directory, "src/routes");
3537
+ if (!outputDir.startsWith(routes + path2.sep) && outputDir !== routes)
3538
+ throw new Error(cliM("cli_generateInvalidRouteOutput"));
2871
3539
  }
3540
+ if (resolvedOptions.type === "api" && architecture === "serv" && resolvedOptions.output)
3541
+ throw new Error(cliM("cli_generateInvalidRouteOutput"));
3542
+ const relativeOutput = path2.relative(target.root, outputDir);
3543
+ if (relativeOutput === ".." || relativeOutput.startsWith(`..${path2.sep}`) || path2.isAbsolute(relativeOutput))
3544
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
2872
3545
  const context = createTemplateContext(resolvedOptions.name, {
2873
- projectName: project?.name
3546
+ projectName: target.target.name
2874
3547
  });
2875
- spinner.start(`\u751F\u6210 ${generator.description}...`);
2876
- const files = await generator.generate(context, outputDir);
2877
- const realCwd = await fse.realpath(resolvedCwd);
2878
- for (const file of files) {
2879
- if (!resolvedOptions.force && await fse.pathExists(file.path))
2880
- throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
2881
- let ancestor = file.path;
2882
- while (!await fse.pathExists(ancestor))
2883
- ancestor = path2.dirname(ancestor);
2884
- const realAncestor = await fse.realpath(ancestor);
2885
- const relative = path2.relative(realCwd, realAncestor);
2886
- if (relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative))
2887
- throw new Error(cliM("cli_generateOutsideWorkspace"));
2888
- }
2889
- for (const file of files) {
2890
- await fse.ensureDir(path2.dirname(file.path));
2891
- await fse.writeFile(file.path, file.content, { encoding: "utf-8", flag: resolvedOptions.force ? "w" : "wx" });
2892
- }
2893
- spinner.succeed();
2894
- core.logger.info("", {});
2895
- core.logger.info(chalk.green("\u2714 \u751F\u6210\u5B8C\u6210\uFF01"));
2896
- core.logger.info("", {});
2897
- core.logger.info("\u521B\u5EFA\u7684\u6587\u4EF6:");
3548
+ spinner.start(cliM("cli_generateProgress", { params: { description: generator.description() } }));
3549
+ const files = resolvedOptions.type === "api" && architecture === "serv" ? await generateServApi(context, target) : await generator.generate(context, outputDir);
3550
+ await writeGeneratedFiles(files, target.root, resolvedOptions.force);
3551
+ spinner.succeed(cliM("cli_generateComplete"));
3552
+ writeCliMessage("");
3553
+ writeCliMessage(cliM("cli_generateFiles"));
2898
3554
  files.forEach((file) => {
2899
- core.logger.info(chalk.cyan(` ${path2.relative(cwd, file.path)}`));
3555
+ writeCliMessage(chalk.cyan(` ${path2.relative(cwd, file.path)}`));
2900
3556
  });
2901
- core.logger.info("", {});
3557
+ writeCliMessage("");
2902
3558
  } catch (error) {
2903
- spinner.fail();
2904
- core.logger.error(chalk.red("\u751F\u6210\u5931\u8D25:"), { error });
3559
+ spinner.fail(cliM("cli_generateFailed"));
3560
+ writeCliError(cliM("cli_generateFailureDetail"), { error });
3561
+ if (error instanceof AggregateError) {
3562
+ for (const cause of error.errors)
3563
+ writeCliError(cliM("cli_generateFailureDetail"), { error: cause });
3564
+ }
2905
3565
  throw error;
2906
3566
  }
2907
3567
  }
@@ -2911,9 +3571,9 @@ async function resolveGenerateOptions(options) {
2911
3571
  questions.push({
2912
3572
  type: "select",
2913
3573
  name: "type",
2914
- message: "\u9009\u62E9\u751F\u6210\u7C7B\u578B:",
3574
+ message: cliM("cli_generateTypePrompt"),
2915
3575
  choices: Object.entries(GENERATORS).map(([key, value]) => ({
2916
- title: `${key} - ${value.description}`,
3576
+ title: `${key} - ${value.description()}`,
2917
3577
  value: key
2918
3578
  }))
2919
3579
  });
@@ -2922,24 +3582,102 @@ async function resolveGenerateOptions(options) {
2922
3582
  questions.push({
2923
3583
  type: "text",
2924
3584
  name: "name",
2925
- message: "\u540D\u79F0:",
3585
+ message: cliM("cli_generateNamePrompt"),
2926
3586
  validate: (value) => {
2927
3587
  if (!value.trim())
2928
- return "\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
3588
+ return cliM("cli_generateNameRequired");
2929
3589
  return true;
2930
3590
  }
2931
3591
  });
2932
3592
  }
2933
- const answers = questions.length > 0 ? await prompts(questions) : {};
3593
+ const answers = questions.length > 0 ? await prompts(questions, { onCancel: () => {
3594
+ throw new Error(cliM("cli_generateCancelled"));
3595
+ } }) : {};
3596
+ const type = options.type || ("type" in answers ? answers.type : void 0);
3597
+ const name = options.name || ("name" in answers ? answers.name : void 0);
3598
+ if (!isGeneratorType(type))
3599
+ throw new Error(cliM("cli_generateUnknownType", { params: { type: String(type ?? "") } }));
3600
+ if (typeof name !== "string" || !name.trim())
3601
+ throw new Error(cliM("cli_generateNameRequired"));
2934
3602
  return {
2935
- type: options.type || answers.type,
2936
- name: options.name || answers.name,
3603
+ type,
3604
+ name,
2937
3605
  output: options.output ?? "",
2938
3606
  force: options.force ?? false,
2939
3607
  verbose: options.verbose ?? false,
2940
- cwd: options.cwd ?? "."
3608
+ cwd: options.cwd ?? ".",
3609
+ workspace: options.workspace ?? ""
2941
3610
  };
2942
3611
  }
3612
+ function isGeneratorType(value) {
3613
+ return typeof value === "string" && Object.hasOwn(GENERATORS, value);
3614
+ }
3615
+ async function writeGeneratedFiles(files, root, force) {
3616
+ if (new Set(files.map((file) => path2.resolve(file.path))).size !== files.length)
3617
+ throw new Error(cliM("cli_generateUnsupportedAggregator"));
3618
+ const previous = /* @__PURE__ */ new Map();
3619
+ const stages = /* @__PURE__ */ new Map();
3620
+ const written = [];
3621
+ const failures = [];
3622
+ for (const file of files) {
3623
+ const relativeFile = path2.relative(root, file.path);
3624
+ if (relativeFile === ".." || relativeFile.startsWith(`..${path2.sep}`) || path2.isAbsolute(relativeFile))
3625
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3626
+ let ancestor = file.path;
3627
+ while (!await fse.pathExists(ancestor))
3628
+ ancestor = path2.dirname(ancestor);
3629
+ const relative2 = path2.relative(root, await fse.realpath(ancestor));
3630
+ if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2))
3631
+ throw new Error(cliM("cli_generateOutsideWorkspace"));
3632
+ const content = await fse.pathExists(file.path) ? await fse.readFile(file.path, "utf8") : void 0;
3633
+ if (file.previousContent !== void 0 ? content !== file.previousContent : content !== void 0 && !force)
3634
+ throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
3635
+ previous.set(file.path, content);
3636
+ }
3637
+ try {
3638
+ for (const file of files) {
3639
+ await fse.ensureDir(path2.dirname(file.path));
3640
+ const stage = path2.join(path2.dirname(file.path), `.${path2.basename(file.path)}.${randomUUID()}.tmp`);
3641
+ stages.set(file.path, stage);
3642
+ await fse.writeFile(stage, file.content, { encoding: "utf8", flag: "wx" });
3643
+ }
3644
+ for (const file of files) {
3645
+ const current = await fse.pathExists(file.path) ? await fse.readFile(file.path, "utf8") : void 0;
3646
+ if (current !== previous.get(file.path))
3647
+ throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
3648
+ await fse.rename(stages.get(file.path), file.path);
3649
+ written.push(file);
3650
+ }
3651
+ } catch (error) {
3652
+ failures.push(error);
3653
+ for (const file of written.reverse()) {
3654
+ try {
3655
+ if (await fse.readFile(file.path, "utf8") !== file.content)
3656
+ throw new Error(cliM("cli_generateExists", { params: { path: file.path } }));
3657
+ const original = previous.get(file.path);
3658
+ if (original === void 0)
3659
+ await fse.unlink(file.path);
3660
+ else
3661
+ await fse.writeFile(file.path, original, "utf8");
3662
+ } catch (rollbackError) {
3663
+ failures.push(rollbackError);
3664
+ }
3665
+ }
3666
+ } finally {
3667
+ for (const stage of stages.values()) {
3668
+ try {
3669
+ await fse.unlink(stage);
3670
+ } catch (error) {
3671
+ if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT")
3672
+ failures.push(error);
3673
+ }
3674
+ }
3675
+ }
3676
+ if (failures.length > 1)
3677
+ throw new AggregateError(failures, cliM("cli_generateRollbackFailed"));
3678
+ if (failures.length === 1)
3679
+ throw failures[0];
3680
+ }
2943
3681
  async function generatePage(context, outputDir) {
2944
3682
  const files = [];
2945
3683
  const pageDir = path2.join(outputDir, context.kebabCase);
@@ -3116,19 +3854,45 @@ export function down() {
3116
3854
  return files;
3117
3855
  }
3118
3856
  var MODULE_CONFIG_MAP = {
3119
- "@h-ai/core": { configKey: "core", label: "Core" },
3120
- "@h-ai/serv": { configKey: "serv", label: "Service" },
3121
- "@h-ai/reldb": { configKey: "db", label: "Database" },
3122
- "@h-ai/cache": { configKey: "cache", label: "Cache" },
3123
- "@h-ai/iam": { configKey: "iam", label: "IAM" },
3124
- "@h-ai/storage": { configKey: "storage", label: "Storage" },
3125
- "@h-ai/ai": { configKey: "ai", label: "AI" },
3126
- "@h-ai/vecdb": { configKey: "vecdb", label: "VecDB" },
3127
- "@h-ai/reach": { configKey: "reach", label: "Reach" },
3128
- "@h-ai/scheduler": { configKey: "scheduler", label: "Scheduler" },
3129
- "@h-ai/audit": { configKey: "audit", label: "Audit" },
3130
- "@h-ai/payment": { configKey: "payment", label: "Payment" },
3131
- "@h-ai/deploy": { configKey: "deploy", label: "Deploy" }
3857
+ "@h-ai/core": { configKey: "core", get label() {
3858
+ return cliM("cli_coreLabel");
3859
+ } },
3860
+ "@h-ai/serv": { configKey: "serv", get label() {
3861
+ return cliM("cli_serviceLabel");
3862
+ } },
3863
+ "@h-ai/reldb": { configKey: "db", get label() {
3864
+ return cliM("cli_featureDbName");
3865
+ } },
3866
+ "@h-ai/cache": { configKey: "cache", get label() {
3867
+ return cliM("cli_featureCacheName");
3868
+ } },
3869
+ "@h-ai/iam": { configKey: "iam", get label() {
3870
+ return cliM("cli_featureIamName");
3871
+ } },
3872
+ "@h-ai/storage": { configKey: "storage", get label() {
3873
+ return cliM("cli_featureStorageName");
3874
+ } },
3875
+ "@h-ai/ai": { configKey: "ai", get label() {
3876
+ return cliM("cli_featureAiName");
3877
+ } },
3878
+ "@h-ai/vecdb": { configKey: "vecdb", get label() {
3879
+ return cliM("cli_featureVecdbName");
3880
+ } },
3881
+ "@h-ai/reach": { configKey: "reach", get label() {
3882
+ return cliM("cli_featureReachName");
3883
+ } },
3884
+ "@h-ai/scheduler": { configKey: "scheduler", get label() {
3885
+ return cliM("cli_featureSchedulerName");
3886
+ } },
3887
+ "@h-ai/audit": { configKey: "audit", get label() {
3888
+ return cliM("cli_featureAuditName");
3889
+ } },
3890
+ "@h-ai/payment": { configKey: "payment", get label() {
3891
+ return cliM("cli_featurePaymentName");
3892
+ } },
3893
+ "@h-ai/deploy": { configKey: "deploy", get label() {
3894
+ return cliM("cli_featureDeployName");
3895
+ } }
3132
3896
  };
3133
3897
  async function validateProject(cwd) {
3134
3898
  const project = await detectProject(cwd);
@@ -3154,49 +3918,49 @@ async function initProject(options) {
3154
3918
  const spinner = ora();
3155
3919
  const cwd = options.cwd ?? ".";
3156
3920
  try {
3157
- spinner.start("\u68C0\u6D4B\u9879\u76EE...");
3921
+ spinner.start(cliM("cli_initDetecting"));
3158
3922
  const result = await validateProject(cwd);
3159
3923
  if (!result) {
3160
- spinner.fail(chalk.red("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\uFF08\u627E\u4E0D\u5230 package.json\uFF09"));
3924
+ spinner.fail(chalk.red(cliM("cli_projectNotFound")));
3161
3925
  return;
3162
3926
  }
3163
3927
  const { project, existing, missing } = result;
3164
- spinner.succeed(`\u68C0\u6D4B\u5230\u9879\u76EE: ${chalk.cyan(project.name)}`);
3928
+ spinner.succeed(cliM("cli_initDetected", { params: { name: chalk.cyan(project.name) } }));
3165
3929
  if (project.isHaiProject) {
3166
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-TSYEQTCY.js');
3930
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-US5CCAJ4.js');
3167
3931
  const generatedFiles = await generateProjectAiSupport(cwd, resolveSkillNamesForPackages(project.haiPackages), {
3168
3932
  overwriteSkills: false,
3169
3933
  overwriteBridgeFiles: false
3170
3934
  });
3171
3935
  for (const generatedFile of generatedFiles) {
3172
- core.logger.info(chalk.gray(` + ${generatedFile}`));
3936
+ writeCliMessage(chalk.gray(` + ${generatedFile}`));
3173
3937
  }
3174
3938
  }
3175
- core.logger.info("");
3176
- core.logger.info(chalk.bold("\u9879\u76EE\u72B6\u6001:"));
3177
- core.logger.info(` \u540D\u79F0: ${chalk.cyan(project.name)}`);
3178
- core.logger.info(` \u7248\u672C: ${project.version}`);
3179
- core.logger.info(` \u5DF2\u5B89\u88C5\u6A21\u5757: ${project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray("\u65E0")}`);
3180
- core.logger.info("");
3939
+ writeCliMessage("");
3940
+ writeCliMessage(chalk.bold(cliM("cli_initStatus")));
3941
+ writeCliMessage(cliM("cli_initName", { params: { name: chalk.cyan(project.name) } }));
3942
+ writeCliMessage(cliM("cli_initVersion", { params: { version: project.version } }));
3943
+ writeCliMessage(cliM("cli_initModules", { params: { modules: project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray(cliM("cli_none")) } }));
3944
+ writeCliMessage("");
3181
3945
  if (existing.length > 0) {
3182
- core.logger.info(chalk.bold("\u5DF2\u6709\u914D\u7F6E:"));
3946
+ writeCliMessage(chalk.bold(cliM("cli_initExisting")));
3183
3947
  for (const key of existing) {
3184
- core.logger.info(chalk.green(` \u2714 config/_${key}.yml`));
3948
+ writeCliMessage(chalk.green(` \u2714 config/_${key}.yml`));
3185
3949
  }
3186
3950
  }
3187
3951
  if (missing.length === 0 && !options.force) {
3188
- core.logger.info("");
3189
- core.logger.info(chalk.green("\u2714 \u6240\u6709\u914D\u7F6E\u6587\u4EF6\u5DF2\u5C31\u7EEA\uFF01"));
3952
+ writeCliMessage("");
3953
+ writeCliMessage(chalk.green(cliM("cli_initReady")));
3190
3954
  return;
3191
3955
  }
3192
3956
  if (missing.length > 0) {
3193
- core.logger.info("");
3194
- core.logger.info(chalk.bold("\u7F3A\u5931\u914D\u7F6E:"));
3957
+ writeCliMessage("");
3958
+ writeCliMessage(chalk.bold(cliM("cli_initMissing")));
3195
3959
  for (const item of missing) {
3196
- core.logger.info(chalk.yellow(` \u2718 config/_${item.configKey}.yml (${item.label})`));
3960
+ writeCliMessage(chalk.yellow(` \u2718 config/_${item.configKey}.yml (${item.label})`));
3197
3961
  }
3198
- core.logger.info("");
3199
- spinner.start("\u751F\u6210\u7F3A\u5931\u914D\u7F6E\u6587\u4EF6...");
3962
+ writeCliMessage("");
3963
+ spinner.start(cliM("cli_initGenerating"));
3200
3964
  let generated = 0;
3201
3965
  for (const item of missing) {
3202
3966
  const configPath = path2.join(cwd, "config", `_${item.configKey}.yml`);
@@ -3204,11 +3968,11 @@ async function initProject(options) {
3204
3968
  await writeFile(configPath, content);
3205
3969
  generated++;
3206
3970
  }
3207
- spinner.succeed(`\u5DF2\u751F\u6210 ${generated} \u4E2A\u914D\u7F6E\u6587\u4EF6`);
3971
+ spinner.succeed(cliM("cli_initGenerated", { params: { count: generated } }));
3208
3972
  }
3209
3973
  if (options.force && existing.length > 0) {
3210
- core.logger.info("");
3211
- spinner.start("\u5F3A\u5236\u91CD\u65B0\u751F\u6210\u6240\u6709\u914D\u7F6E\u6587\u4EF6...");
3974
+ writeCliMessage("");
3975
+ spinner.start(cliM("cli_initRegenerating"));
3212
3976
  let regenerated = 0;
3213
3977
  for (const pkg of project.haiPackages) {
3214
3978
  const mapping = MODULE_CONFIG_MAP[pkg];
@@ -3219,25 +3983,25 @@ async function initProject(options) {
3219
3983
  await writeFile(configPath, content);
3220
3984
  regenerated++;
3221
3985
  }
3222
- spinner.succeed(`\u5DF2\u91CD\u65B0\u751F\u6210 ${regenerated} \u4E2A\u914D\u7F6E\u6587\u4EF6`);
3986
+ spinner.succeed(cliM("cli_initRegenerated", { params: { count: regenerated } }));
3223
3987
  }
3224
3988
  const coreConfigPath = path2.join(cwd, "config", "_core.yml");
3225
3989
  if (!await fileExists(coreConfigPath)) {
3226
- spinner.start("\u751F\u6210\u6838\u5FC3\u914D\u7F6E config/_core.yml...");
3990
+ spinner.start(cliM("cli_initCore"));
3227
3991
  const content = generateConfigFile("core");
3228
3992
  await writeFile(coreConfigPath, content);
3229
3993
  spinner.succeed();
3230
3994
  }
3231
- core.logger.info("");
3232
- core.logger.info(chalk.green("\u2714 \u9879\u76EE\u914D\u7F6E\u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
3233
- core.logger.info("");
3234
- core.logger.info(chalk.gray("\u4E0B\u4E00\u6B65:"));
3235
- core.logger.info(chalk.gray(" 1. \u7F16\u8F91 config/ \u4E2D\u7684\u914D\u7F6E\u6587\u4EF6"));
3236
- core.logger.info(chalk.gray(" 2. \u8FD0\u884C pnpm dev \u542F\u52A8\u5F00\u53D1\u670D\u52A1"));
3237
- core.logger.info("");
3995
+ writeCliMessage("");
3996
+ writeCliMessage(chalk.green(cliM("cli_initSuccess")));
3997
+ writeCliMessage("");
3998
+ writeCliMessage(chalk.gray(cliM("cli_nextSteps")));
3999
+ writeCliMessage(chalk.gray(cliM("cli_initEditConfig")));
4000
+ writeCliMessage(chalk.gray(cliM("cli_initStartDev")));
4001
+ writeCliMessage("");
3238
4002
  } catch (error) {
3239
4003
  spinner.fail();
3240
- core.logger.error(chalk.red("\u521D\u59CB\u5316\u5931\u8D25:"), { error });
4004
+ writeCliError(chalk.red(cliM("cli_initFailed")), { error });
3241
4005
  throw error;
3242
4006
  }
3243
4007
  }
@@ -3245,9 +4009,9 @@ async function initProject(options) {
3245
4009
  // src/cli-main.ts
3246
4010
  var VERSION = getCliVersion();
3247
4011
  var cli = cac("hai");
3248
- cli.option("-v, --verbose", "\u663E\u793A\u8BE6\u7EC6\u8F93\u51FA");
3249
- cli.option("-C, --cwd <path>", "\u5DE5\u4F5C\u76EE\u5F55");
3250
- cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option("--type <type>", "\u5E94\u7528\u7C7B\u578B (admin, website, h5, api, mobile-app, fullstack)").option("--frontends <targets>", "\u524D\u540E\u7AEF\u5206\u79BB\u5DE5\u7A0B\u524D\u7AEF\u76EE\u6807 (\u9017\u53F7\u5206\u9694: web,app,miniapp,desktop)").option("-t, --template <template>", "\u9879\u76EE\u6A21\u677F (minimal, default, full, custom)").option("-f, --features <features>", "\u529F\u80FD\u5217\u8868 (\u9017\u53F7\u5206\u9694: iam,db,cache,ai,storage,crypto)").option("--examples", "\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801").option("--no-examples", "\u4E0D\u6DFB\u52A0\u793A\u4F8B\u4EE3\u7801").option("--no-install", "\u4E0D\u5B89\u88C5\u4F9D\u8D56").option("-p, --package-manager <pm>", "\u5305\u7BA1\u7406\u5668 (pnpm, npm, yarn)").option("--no-git", "\u4E0D\u521D\u59CB\u5316 Git").option("--git-remote <url>", "Git \u8FDC\u7A0B\u4ED3\u5E93\u5730\u5740").option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").action(async (name, options) => {
4012
+ cli.option("-v, --verbose", cliM("cli_help_verbose"));
4013
+ cli.option("-C, --cwd <path>", cliM("cli_help_cwd"));
4014
+ cli.command("create [name]", cliM("cli_help_create")).option("--type <type>", cliM("cli_help_appType")).option("--frontends <targets>", cliM("cli_help_frontends")).option("-t, --template <template>", cliM("cli_help_template")).option("-f, --features <features>", cliM("cli_help_features")).option("--examples", cliM("cli_help_examples")).option("--no-examples", cliM("cli_help_noExamples")).option("--no-install", cliM("cli_help_noInstall")).option("--no-deploy", cliM("cli_help_noDeploy")).option("-p, --package-manager <pm>", cliM("cli_help_packageManager")).option("--no-git", cliM("cli_help_noGit")).option("--git-remote <url>", cliM("cli_help_gitRemote")).option("-y, --yes", cliM("cli_help_yes")).action(async (name, options) => {
3251
4015
  try {
3252
4016
  const features = typeof options.features === "string" ? options.features.split(",").map((f) => f.trim()) : void 0;
3253
4017
  const frontends = typeof options.frontends === "string" ? options.frontends.split(",").map((f) => f.trim()).filter(Boolean) : void 0;
@@ -3257,6 +4021,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
3257
4021
  frontends,
3258
4022
  template: options.template,
3259
4023
  features,
4024
+ deploy: options.deploy,
3260
4025
  examples: options.examples,
3261
4026
  install: options.install,
3262
4027
  packageManager: options.packageManager,
@@ -3270,7 +4035,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
3270
4035
  process4.exitCode = 1;
3271
4036
  }
3272
4037
  });
3273
- cli.command("add [module]", "\u5411\u73B0\u6709\u9879\u76EE\u6DFB\u52A0\u6A21\u5757").option("--no-install", "\u4E0D\u5B89\u88C5\u4F9D\u8D56").action(async (module, options) => {
4038
+ cli.command("add [module]", cliM("cli_help_add")).option("--no-install", cliM("cli_help_noInstall")).action(async (module, options) => {
3274
4039
  try {
3275
4040
  await addModule({
3276
4041
  module,
@@ -3282,7 +4047,7 @@ cli.command("add [module]", "\u5411\u73B0\u6709\u9879\u76EE\u6DFB\u52A0\u6A21\u5
3282
4047
  process4.exit(1);
3283
4048
  }
3284
4049
  });
3285
- cli.command("init", "\u521D\u59CB\u5316/\u6821\u9A8C\u9879\u76EE\u914D\u7F6E").option("-f, --force", "\u5F3A\u5236\u91CD\u65B0\u751F\u6210\u914D\u7F6E").action(async (options) => {
4050
+ cli.command("init", cliM("cli_help_init")).option("-f, --force", cliM("cli_help_forceConfig")).action(async (options) => {
3286
4051
  try {
3287
4052
  await initProject({
3288
4053
  force: options.force,
@@ -3293,12 +4058,13 @@ cli.command("init", "\u521D\u59CB\u5316/\u6821\u9A8C\u9879\u76EE\u914D\u7F6E").o
3293
4058
  process4.exit(1);
3294
4059
  }
3295
4060
  });
3296
- cli.command("generate [type] [name]", "\u751F\u6210\u4EE3\u7801").alias("g").option("-o, --output <path>", "\u8F93\u51FA\u8DEF\u5F84").option("-f, --force", "\u8986\u76D6\u73B0\u6709\u6587\u4EF6").action(async (type, name, options) => {
4061
+ cli.command("generate [type] [name]", cliM("cli_help_generate")).option("-w, --workspace <name>", cliM("cli_help_workspace")).alias("g").option("-o, --output <path>", cliM("cli_help_output")).option("-f, --force", cliM("cli_help_force")).action(async (type, name, options) => {
3297
4062
  try {
3298
4063
  await generate({
3299
4064
  type,
3300
4065
  name: name ?? "",
3301
4066
  output: options.output,
4067
+ ...options.workspace ? { workspace: String(options.workspace) } : {},
3302
4068
  force: options.force,
3303
4069
  verbose: options.verbose,
3304
4070
  cwd: options.cwd
@@ -3307,7 +4073,7 @@ cli.command("generate [type] [name]", "\u751F\u6210\u4EE3\u7801").alias("g").opt
3307
4073
  process4.exit(1);
3308
4074
  }
3309
4075
  });
3310
- cli.command("build", "\u6784\u5EFA\u6E90\u7801\u3001Docker \u955C\u50CF\u6216 Tauri \u5B89\u88C5\u5305\uFF08\u4E0D\u533A\u5206\u90E8\u7F72\u73AF\u5883\uFF09").option("--target <target>", "\u4EA7\u7269\u7C7B\u578B\uFF1Acompile\uFF08\u9ED8\u8BA4\uFF09/ docker / desktop").option("--runtime <runtime>", "\u672C\u5730\u5BB9\u5668\u5F15\u64CE\uFF1Aauto\uFF08\u4F18\u5148 Podman\uFF09/ podman / docker").option("--network <network>", "\u5BB9\u5668\u6784\u5EFA\u7F51\u7EDC\uFF08\u9ED8\u8BA4\u5F15\u64CE\u8BBE\u7F6E\uFF09\uFF0C\u4F8B\u5982 rootless Podman \u7684 pasta").option("--platform <platform>", "Tauri \u76EE\u6807\u7CFB\u7EDF\uFF1Awindows / linux / macos\uFF08\u5728\u5BF9\u5E94\u7CFB\u7EDF\u6784\u5EFA\uFF09").option("--arch <arch>", "Tauri \u76EE\u6807\u67B6\u6784\uFF1Ax64 / arm64").option("--sqlite-prebuild-mirror <url>", "SQLite \u9884\u7F16\u8BD1\u4E8C\u8FDB\u5236\u955C\u50CF\u6839\u5730\u5740").option("--no-http-proxy", "Podman \u6784\u5EFA\u4E0D\u7EE7\u627F\u672C\u673A\u4EE3\u7406").action(async (options) => {
4076
+ cli.command("build", cliM("cli_help_build")).option("--target <target>", cliM("cli_help_target")).option("--images <names>", cliM("cli_help_images")).option("--runtime <runtime>", cliM("cli_help_buildRuntime")).option("--network <network>", cliM("cli_help_buildNetwork")).option("--platform <platform>", cliM("cli_help_platform")).option("--arch <arch>", cliM("cli_help_arch")).option("--sqlite-prebuild-mirror <url>", cliM("cli_help_sqliteMirror")).option("--no-http-proxy", cliM("cli_help_noHttpProxy")).action(async (options) => {
3311
4077
  try {
3312
4078
  await buildCommand(options);
3313
4079
  } catch (error) {
@@ -3315,7 +4081,7 @@ cli.command("build", "\u6784\u5EFA\u6E90\u7801\u3001Docker \u955C\u50CF\u6216 Ta
3315
4081
  process4.exitCode = 1;
3316
4082
  }
3317
4083
  });
3318
- cli.command("deploy [appDir]", "\u90E8\u7F72\u5E94\u7528\u5230\u4E91\u5E73\u53F0").option("--env <environment>", "\u90E8\u7F72\u73AF\u5883\uFF08test / prod \u7B49\uFF0C\u5FC5\u987B\u5B58\u5728\u5BF9\u5E94\u73AF\u5883\u6587\u4EF6\uFF09").option("--env-dir <path>", "\u73AF\u5883\u6587\u4EF6\u6240\u5728\u76EE\u5F55\uFF08\u9ED8\u8BA4\u81EA\u52A8\u67E5\u627E\uFF09").option("-y, --yes", "\u6309\u5F53\u524D\u540E\u7AEF\u9ED8\u8BA4\u914D\u7F6E\u90E8\u7F72\uFF0C\u76EE\u6807\u73AF\u5883\u5DF2\u6709\u5B8C\u6574\u914D\u7F6E\u65F6\u4FDD\u6301\u4E0D\u53D8").option("--project-name <name>", "\u9879\u76EE\u540D\u79F0\uFF08\u9ED8\u8BA4\u670D\u52A1\u5305\u540D\uFF0C\u7F3A\u5931\u65F6\u4E3A\u76EE\u5F55\u540D\uFF1B\u9644\u52A0\u73AF\u5883\u540E\u7F00\uFF09").option("--skip-middleware", "\u914D\u7F6E\u5DF2\u5B8C\u5907\uFF0C\u8DF3\u8FC7\u5168\u90E8\u4E2D\u95F4\u4EF6\u5904\u7406\uFF0C\u76F4\u63A5\u90E8\u7F72").option("--artifact-dir <path>", "\u5DF2\u6709\u955C\u50CF\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\uFF08\u542B manifest.json\uFF09").option("--bundle-only", "\u53EA\u751F\u6210 Compose \u90E8\u7F72 tar\uFF0C\u624B\u5DE5\u642C\u8FD0\uFF1B\u4E0D\u8FDE\u63A5 SSH").option("--output <path>", "\u90E8\u7F72 tar \u8F93\u51FA\u8DEF\u5F84\uFF0C\u81EA\u52A8\u8865\u5E94\u7528\u7248\u672C\u540E\u7F00").action(async (appDir, options) => {
4084
+ cli.command("deploy [appDir]", cliM("cli_help_deploy")).option("--env <environment>", cliM("cli_help_environment")).option("--env-dir <path>", cliM("cli_help_envDir")).option("-y, --yes", cliM("cli_help_deployYes")).option("--project-name <name>", cliM("cli_help_projectName")).option("--skip-middleware", cliM("cli_help_skipMiddleware")).option("--artifact-dir <path>", cliM("cli_help_artifactDir")).option("--services <names>", cliM("cli_help_services")).option("--build", cliM("cli_help_deployBuild")).option("--network <network>", cliM("cli_help_deployNetwork")).option("--sqlite-prebuild-mirror <url>", cliM("cli_help_deploySqliteMirror")).option("--no-http-proxy", cliM("cli_help_deployNoProxy")).option("--runtime <runtime>", cliM("cli_help_deployRuntime")).option("--bundle-only", cliM("cli_help_bundleOnly")).option("--output <path>", cliM("cli_help_bundleOutput")).action(async (appDir, options) => {
3319
4085
  try {
3320
4086
  await deployCommand({
3321
4087
  appDir,
@@ -3325,26 +4091,32 @@ cli.command("deploy [appDir]", "\u90E8\u7F72\u5E94\u7528\u5230\u4E91\u5E73\u53F0
3325
4091
  projectName: options.projectName,
3326
4092
  skipMiddleware: options.skipMiddleware,
3327
4093
  artifactDir: options.artifactDir,
4094
+ services: options.services,
4095
+ build: options.build,
4096
+ network: options.network,
4097
+ sqlitePrebuildMirror: options.sqlitePrebuildMirror,
4098
+ httpProxy: options.httpProxy,
4099
+ runtime: options.runtime,
3328
4100
  bundleOnly: options.bundleOnly,
3329
4101
  output: options.output,
3330
4102
  verbose: options.verbose,
3331
4103
  cwd: options.cwd
3332
4104
  });
3333
4105
  } catch {
3334
- process4.exit(1);
4106
+ process4.exitCode = 1;
3335
4107
  }
3336
4108
  });
3337
- cli.command("g:page <name>", "\u751F\u6210\u9875\u9762").action(async (name, options) => {
3338
- await generate({ type: "page", name, cwd: options.cwd });
4109
+ cli.command("g:page <name>", cliM("cli_help_generatePage")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4110
+ await generate({ type: "page", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3339
4111
  });
3340
- cli.command("g:component <name>", "\u751F\u6210\u7EC4\u4EF6").action(async (name, options) => {
3341
- await generate({ type: "component", name, cwd: options.cwd });
4112
+ cli.command("g:component <name>", cliM("cli_help_generateComponent")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4113
+ await generate({ type: "component", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3342
4114
  });
3343
- cli.command("g:api <name>", "\u751F\u6210 API").action(async (name, options) => {
3344
- await generate({ type: "api", name, cwd: options.cwd });
4115
+ cli.command("g:api <name>", cliM("cli_help_generateApi")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4116
+ await generate({ type: "api", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3345
4117
  });
3346
- cli.command("g:model <name>", "\u751F\u6210\u6570\u636E\u6A21\u578B").action(async (name, options) => {
3347
- await generate({ type: "model", name, cwd: options.cwd });
4118
+ cli.command("g:model <name>", cliM("cli_help_generateModel")).option("-w, --workspace <name>", cliM("cli_help_workspace")).action(async (name, options) => {
4119
+ await generate({ type: "model", name, cwd: options.cwd, ...options.workspace ? { workspace: String(options.workspace) } : {} });
3348
4120
  });
3349
4121
  cli.version(VERSION, "-V, --version");
3350
4122
  cli.help();