@h-ai/cli 0.1.0-alpha.65 → 0.1.0-alpha.67

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 (141) hide show
  1. package/README.en.md +66 -251
  2. package/README.ja.md +67 -252
  3. package/README.md +66 -251
  4. package/dist/{chunk-47V5OT22.js → chunk-7DSURTJ4.js} +32 -115
  5. package/dist/chunk-7DSURTJ4.js.map +1 -0
  6. package/dist/{chunk-IPG3QNYK.js → chunk-AOQ4N657.js} +212 -8
  7. package/dist/chunk-AOQ4N657.js.map +1 -0
  8. package/dist/cli-config-templates-6AHNUGBN.js +5 -0
  9. package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
  10. package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-FC3QRKDO.js} +3 -3
  11. package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
  12. package/dist/index.js +644 -391
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-6PPBKQ6Q.js → src-2V6VP4I5.js} +972 -520
  15. package/dist/src-2V6VP4I5.js.map +1 -0
  16. package/package.json +8 -8
  17. package/templates/README.md +48 -145
  18. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
  19. package/templates/apps/api/.dockerignore.hbs +18 -12
  20. package/templates/apps/api/.gitignore.hbs +9 -1
  21. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  22. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
  23. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -3
  24. package/templates/apps/api/hai.build.json.hbs +1 -0
  25. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  26. package/templates/apps/api/package.json.hbs +10 -8
  27. package/templates/apps/fullstack/.dockerignore.hbs +18 -14
  28. package/templates/apps/fullstack/.gitignore.hbs +9 -1
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -8
  30. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  31. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
  32. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  33. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +3 -3
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +25 -10
  35. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
  36. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
  37. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
  38. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
  39. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  40. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -10
  41. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
  42. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -3
  43. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +12 -1
  44. package/templates/apps/fullstack/hai.build.json.hbs +7 -0
  45. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  46. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  47. package/templates/apps/fullstack/package.json.hbs +7 -12
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  49. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +28 -7
  50. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
  51. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  52. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +33 -12
  53. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
  54. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
  55. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
  56. package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
  57. package/templates/apps/mobile-app/index.html.hbs +1 -0
  58. package/templates/apps/mobile-app/public/runtime-config.js +1 -0
  59. package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
  60. package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
  61. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
  62. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  63. package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
  64. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  65. package/templates/base/.dockerignore.hbs +18 -13
  66. package/templates/base/.gitignore.hbs +9 -1
  67. package/templates/base/e2e/app.spec.ts.hbs +10 -0
  68. package/templates/base/hai.build.json.hbs +1 -0
  69. package/templates/base/images/app.Dockerfile.hbs +64 -0
  70. package/templates/base/{docker → images/web}/nginx.conf.hbs +6 -0
  71. package/templates/base/package.json.hbs +20 -19
  72. package/templates/base/svelte.config.js.hbs +2 -0
  73. package/templates/base/vite.config.ts.hbs +9 -3
  74. package/templates/shared/.gitattributes +11 -0
  75. package/templates/shared/container-runtime-config.sh +30 -0
  76. package/templates/shared/run-e2e.mjs +65 -0
  77. package/templates/shared/vite-runtime-config.ts +54 -0
  78. package/templates/skills/AGENTS.md +3 -3
  79. package/templates/skills/bridges/admin/AGENTS.md +1 -1
  80. package/templates/skills/bridges/api/AGENTS.md +1 -1
  81. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
  82. package/templates/skills/bridges/h5/AGENTS.md +1 -1
  83. package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
  84. package/templates/skills/bridges/website/AGENTS.md +1 -1
  85. package/templates/skills/hai-api-client/SKILL.md +14 -161
  86. package/templates/skills/hai-api-client/reference.md +166 -0
  87. package/templates/skills/hai-api-contract/SKILL.md +15 -273
  88. package/templates/skills/hai-api-contract/reference.md +278 -0
  89. package/templates/skills/hai-app-create/SKILL.md +2 -2
  90. package/templates/skills/hai-app-create/development.md +6 -4
  91. package/templates/skills/hai-app-create/reference.md +5 -4
  92. package/templates/skills/hai-app-review/SKILL.md +1 -1
  93. package/templates/skills/hai-app-tests/SKILL.md +4 -3
  94. package/templates/skills/hai-app-tests/reference.md +2 -2
  95. package/templates/skills/hai-app-tests/validation.md +31 -0
  96. package/templates/skills/hai-audit/SKILL.md +17 -142
  97. package/templates/skills/hai-audit/reference.md +145 -0
  98. package/templates/skills/hai-build/SKILL.md +24 -3
  99. package/templates/skills/hai-build/reference.md +10 -9
  100. package/templates/skills/hai-cache/SKILL.md +15 -182
  101. package/templates/skills/hai-cache/reference.md +187 -0
  102. package/templates/skills/hai-capacitor/SKILL.md +4 -3
  103. package/templates/skills/hai-capacitor/reference.md +6 -4
  104. package/templates/skills/hai-ci/SKILL.md +2 -0
  105. package/templates/skills/hai-core/SKILL.md +3 -3
  106. package/templates/skills/hai-crypto/SKILL.md +16 -252
  107. package/templates/skills/hai-crypto/reference.md +256 -0
  108. package/templates/skills/hai-datapipe/SKILL.md +15 -196
  109. package/templates/skills/hai-datapipe/reference.md +202 -0
  110. package/templates/skills/hai-deploy/SKILL.md +28 -271
  111. package/templates/skills/hai-deploy/middleware.md +6 -18
  112. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  113. package/templates/skills/hai-framework-sync/sync.md +1 -1
  114. package/templates/skills/hai-iam/SKILL.md +1 -3
  115. package/templates/skills/hai-kit/SKILL.md +12 -167
  116. package/templates/skills/hai-kit/reference.md +172 -0
  117. package/templates/skills/hai-payment/SKILL.md +19 -251
  118. package/templates/skills/hai-payment/reference.md +256 -0
  119. package/templates/skills/hai-pr-review/SKILL.md +4 -4
  120. package/templates/skills/hai-reach/SKILL.md +2 -2
  121. package/templates/skills/hai-reldb/SKILL.md +1 -1
  122. package/templates/skills/hai-scheduler/SKILL.md +15 -195
  123. package/templates/skills/hai-scheduler/reference.md +202 -0
  124. package/templates/skills/hai-serv/SKILL.md +2 -2
  125. package/templates/skills/hai-spec-implement/SKILL.md +1 -1
  126. package/templates/skills/hai-spec-implement/workflow.md +3 -3
  127. package/templates/skills/hai-storage/SKILL.md +17 -238
  128. package/templates/skills/hai-storage/reference.md +243 -0
  129. package/templates/skills/hai-ui/SKILL.md +1 -1
  130. package/templates/skills/hai-ui/reference.md +11 -5
  131. package/templates/skills/hai-vecdb/SKILL.md +2 -2
  132. package/dist/chunk-47V5OT22.js.map +0 -1
  133. package/dist/chunk-IPG3QNYK.js.map +0 -1
  134. package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
  135. package/dist/src-6PPBKQ6Q.js.map +0 -1
  136. package/templates/apps/api/Dockerfile.hbs +0 -48
  137. package/templates/apps/fullstack/Dockerfile.hbs +0 -69
  138. package/templates/base/Dockerfile.hbs +0 -79
  139. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  140. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  141. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
package/dist/index.js CHANGED
@@ -1,40 +1,42 @@
1
1
  #!/usr/bin/env node
2
- import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-47V5OT22.js';
3
- import { cliM } from './chunk-IPG3QNYK.js';
2
+ import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-7DSURTJ4.js';
3
+ import { cliM } from './chunk-AOQ4N657.js';
4
4
  import './chunk-2H7UOFLK.js';
5
- import process3 from 'process';
5
+ import process4 from 'process';
6
6
  import { cac } from 'cac';
7
7
  import chalk from 'chalk';
8
- import path2, { resolve, join } from 'path';
8
+ import path2, { resolve, join, dirname, relative } from 'path';
9
9
  import { fileURLToPath, pathToFileURL } from 'url';
10
10
  import { wrapAnsi } from 'fast-wrap-ansi';
11
11
  import fse from 'fs-extra';
12
12
  import Handlebars2 from 'handlebars';
13
- import { core } from '@h-ai/core';
14
13
  import ora from 'ora';
15
14
  import prompts from 'prompts';
16
15
  import { Buffer } from 'buffer';
17
- import { execSync, execFileSync } from 'child_process';
18
- import { randomBytes } from 'crypto';
19
- import { stringify, parseDocument } from 'yaml';
20
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
16
+ import { execSync, execFileSync, spawn } from 'child_process';
17
+ import { randomBytes, createHash } from 'crypto';
18
+ import { stringify, parseDocument, isScalar, isMap } from 'yaml';
19
+ import { readFileSync, existsSync, mkdirSync, mkdtempSync, createReadStream, writeFileSync, renameSync, rmSync, readdirSync, copyFileSync } from 'fs';
21
20
  import { createRequire } from 'module';
21
+ import { core } from '@h-ai/core';
22
22
 
23
23
  var CAMEL_CASE_SEPARATOR_REGEX = /[-_\s]+(.)?/g;
24
24
  var LEADING_CHAR_REGEX = /^./;
25
25
  function writeCliMessage(message) {
26
- process3.stdout.write(`${message}
26
+ process4.stdout.write(`${message}
27
27
  `);
28
28
  }
29
29
  function writeCliError(message, error) {
30
- process3.stderr.write(`${message}
30
+ process4.stderr.write(`${message}
31
31
  `);
32
+ if (error && typeof error === "object" && "error" in error)
33
+ error = error.error;
32
34
  if (error !== void 0)
33
- process3.stderr.write(`${error instanceof Error ? error.message : String(error)}
35
+ process4.stderr.write(`${error instanceof Error ? error.message : String(error)}
34
36
  `);
35
37
  }
36
38
  function formatPromptDescription(description) {
37
- return wrapAnsi(description, Math.max(1, (process3.stdout.columns || 80) - 32), { hard: true });
39
+ return wrapAnsi(description, Math.max(1, (process4.stdout.columns || 80) - 32), { hard: true });
38
40
  }
39
41
  function toCamelCase(str) {
40
42
  return str.replace(CAMEL_CASE_SEPARATOR_REGEX, (_, c) => c ? c.toUpperCase() : "").replace(LEADING_CHAR_REGEX, (c) => c.toLowerCase());
@@ -133,56 +135,56 @@ var CAPACITOR_SKIPPED_SERVER_FILES = /* @__PURE__ */ new Set([
133
135
  "src/lib/server/init.ts"
134
136
  ]);
135
137
  var VERSIONS = {
136
- antfuEslintConfig: "^9.5.1",
137
- capacitorSecureStorage: "^8.0.1",
138
+ antfuEslintConfig: "9.5.1",
139
+ capacitorSecureStorage: "8.0.1",
138
140
  // UI / Icon
139
- bitsUi: "^2.19.5",
140
- iconifyJsonTabler: "^1.2.42",
141
- iconifyTailwind: "^1.2.3",
141
+ bitsUi: "2.19.5",
142
+ iconifyJsonTabler: "1.2.42",
143
+ iconifyTailwind: "1.2.3",
142
144
  // 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",
145
+ capacitorAndroid: "8.5.3",
146
+ capacitorApp: "8.1.2",
147
+ capacitorCamera: "8.2.5",
148
+ capacitorCli: "8.5.3",
149
+ capacitorCore: "8.5.3",
150
+ capacitorDevice: "8.0.3",
151
+ capacitorIos: "8.5.3",
152
+ capacitorPreferences: "8.0.1",
153
+ capacitorPushNotifications: "8.1.3",
154
+ capacitorStatusBar: "8.0.4",
153
155
  // Tailwind / DaisyUI
154
- daisyui: "^5.7.47",
155
- tailwindcss: "^4.3.3",
156
- tailwindcssVite: "^4.3.3",
156
+ daisyui: "5.7.47",
157
+ tailwindcss: "4.3.3",
158
+ tailwindcssVite: "4.3.3",
157
159
  // i18n
158
- inlangParaglide: "^2.27.0",
159
- inlangMessageFormat: "^4.5.0",
160
+ inlangParaglide: "2.27.0",
161
+ inlangMessageFormat: "4.5.0",
160
162
  // 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",
163
+ svelte: "5.57.2",
164
+ svelteKit: "2.70.3",
165
+ svelteAdapterNode: "5.5.7",
166
+ svelteCheck: "4.7.6",
167
+ sveltePlugin: "7.3.1",
166
168
  // Build / Test
167
- vite: "^8.3.4",
168
- vitest: "^4.1.11",
169
- tsup: "^8.5.1",
170
- rimraf: "^6.1.3",
169
+ vite: "8.3.4",
170
+ vitest: "4.1.11",
171
+ tsup: "8.5.1",
172
+ rimraf: "6.1.3",
171
173
  // ESLint
172
- eslint: "^10.12.0",
173
- eslintPluginFormat: "^2.0.1",
174
- eslintPluginSvelte: "^3.23.1",
175
- svelteEslintParser: "^1.9.0",
174
+ eslint: "10.12.0",
175
+ eslintPluginFormat: "2.0.1",
176
+ eslintPluginSvelte: "3.23.1",
177
+ svelteEslintParser: "1.9.0",
176
178
  // Node / TS
177
- nodeTypes: "^26.6.5",
179
+ nodeTypes: "26.6.5",
178
180
  // Svelte / ESLint / tsup 继续使用稳定的 JavaScript 编译 API。
179
- typescript: "^6.0.3",
181
+ typescript: "6.0.3",
180
182
  // E2E
181
- playwright: "^1.64.0",
183
+ playwright: "1.64.0",
182
184
  // Validation
183
- zod: "^4.6.5",
185
+ zod: "4.6.5",
184
186
  // Tauri
185
- tauriCli: "^2.12.1"
187
+ tauriCli: "2.12.1"
186
188
  };
187
189
  var FRONTEND_PORTS_DEFAULT = {
188
190
  web: 4173,
@@ -208,6 +210,7 @@ var HAI_CATALOG_PACKAGE_NAMES = [
208
210
  "@h-ai/audit",
209
211
  "@h-ai/cache",
210
212
  "@h-ai/capacitor",
213
+ "@h-ai/cli",
211
214
  "@h-ai/core",
212
215
  "@h-ai/crypto",
213
216
  "@h-ai/datapipe",
@@ -388,6 +391,7 @@ function buildFullstackPackageSpecifiers(useCatalogProtocol) {
388
391
  function buildApiWorkspaceCatalogPackages(featureMap) {
389
392
  const packageNames = [
390
393
  ...API_WORKSPACE_EXTERNAL_CATALOG_PACKAGE_NAMES,
394
+ "@h-ai/cli",
391
395
  "@h-ai/api-client",
392
396
  "@h-ai/api-contract",
393
397
  "@h-ai/core",
@@ -422,7 +426,7 @@ function buildApiWorkspaceCatalogPackages(featureMap) {
422
426
  return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
423
427
  }
424
428
  function buildAppHaiCatalogPackages(options) {
425
- const packageNames = [];
429
+ const packageNames = ["@h-ai/cli"];
426
430
  if (options.isSvelteOnlyApp) {
427
431
  packageNames.push("@h-ai/api-client", "@h-ai/api-contract", "@h-ai/capacitor", "@h-ai/ui");
428
432
  return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
@@ -469,6 +473,7 @@ function buildAppHaiCatalogPackages(options) {
469
473
  function buildFullstackCatalogPackages(frontends, deploy = false) {
470
474
  const packageNames = [
471
475
  ...FULLSTACK_ROOT_CATALOG_PACKAGE_NAMES,
476
+ "@h-ai/cli",
472
477
  ...FULLSTACK_CONTRACT_CATALOG_PACKAGE_NAMES,
473
478
  ...FULLSTACK_SERV_CATALOG_PACKAGE_NAMES,
474
479
  ...FULLSTACK_SHARED_CATALOG_PACKAGE_NAMES
@@ -577,6 +582,7 @@ function buildTemplateContext(options) {
577
582
  for (const f of options.features) {
578
583
  featureMap[f] = true;
579
584
  }
585
+ featureMap.deploy = options.deploy !== false;
580
586
  if (options.appType === "fullstack") {
581
587
  for (const feature of ["iam", "db", "cache", "crypto", "storage", "audit"]) featureMap[feature] = true;
582
588
  }
@@ -772,6 +778,14 @@ async function generateFromTemplates(projectPath, context) {
772
778
  }
773
779
  }
774
780
  await renderDynamicFiles(root, projectPath, context);
781
+ await fse.copy(path2.join(root, "shared", "run-e2e.mjs"), path2.join(projectPath, "scripts", "run-e2e.mjs"));
782
+ await fse.ensureDir(path2.join(projectPath, "images", "dependencies"));
783
+ await fse.writeFile(path2.join(projectPath, "images", "dependencies", ".gitkeep"), "");
784
+ await fse.copy(path2.join(root, "shared", ".gitattributes"), path2.join(projectPath, ".gitattributes"));
785
+ if (context.isSvelteOnlyApp) {
786
+ await fse.copy(path2.join(root, "shared", "vite-runtime-config.ts"), path2.join(projectPath, "scripts", "vite-runtime-config.ts"));
787
+ await fse.copy(path2.join(root, "shared", "container-runtime-config.sh"), path2.join(projectPath, "images", "web", "19-runtime-config.sh"));
788
+ }
775
789
  if (context.isSvelteOnlyApp || context.appType === "fullstack") {
776
790
  const navigationDir = context.appType === "fullstack" ? path2.join(projectPath, "packages", `${context.projectName}-shared`, "src", "lib", "navigation") : path2.join(projectPath, "src", "lib", "navigation");
777
791
  await fse.copy(path2.join(root, "shared", "hash-router.svelte.ts"), path2.join(navigationDir, "hash-router.svelte.ts"));
@@ -1053,7 +1067,6 @@ var SELECTABLE_FEATURES = [
1053
1067
  "vecdb",
1054
1068
  "datapipe",
1055
1069
  "scheduler",
1056
- "deploy",
1057
1070
  "api-client",
1058
1071
  "capacitor"
1059
1072
  ];
@@ -1126,13 +1139,14 @@ async function createProject(options) {
1126
1139
  name: resolvedOptions.name,
1127
1140
  appType: resolvedOptions.appType,
1128
1141
  features: resolvedOptions.features,
1142
+ deploy: resolvedOptions.deploy,
1129
1143
  frontends: resolvedOptions.frontends,
1130
1144
  moduleConfigs: resolvedOptions.moduleConfigs,
1131
1145
  packageManager: resolvedOptions.packageManager
1132
1146
  });
1133
1147
  await generateFromTemplates(projectPath, context);
1134
1148
  {
1135
- const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-SJ2MBIR7.js');
1149
+ const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-6AHNUGBN.js');
1136
1150
  const configs = resolvedOptions.moduleConfigs;
1137
1151
  const isApiWorkspace = resolvedOptions.appType === "api";
1138
1152
  const isFullstack = resolvedOptions.appType === "fullstack";
@@ -1206,7 +1220,7 @@ HAI_ADMIN_DEFAULT_PASSWORD=${password}
1206
1220
  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");
1207
1221
  }
1208
1222
  }
1209
- const { generateSkillFiles } = await import('./cli-skill-templates-A5VIR5J6.js');
1223
+ const { generateSkillFiles } = await import('./cli-skill-templates-FC3QRKDO.js');
1210
1224
  await generateSkillFiles(
1211
1225
  projectPath,
1212
1226
  resolvedOptions.features,
@@ -1217,7 +1231,7 @@ HAI_ADMIN_DEFAULT_PASSWORD=${password}
1217
1231
  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);
1218
1232
  await writeFile(
1219
1233
  path2.join(projectPath, "README.md"),
1220
- readmeContent
1234
+ 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
1221
1235
  );
1222
1236
  spinner.succeed();
1223
1237
  if (resolvedOptions.git) {
@@ -1253,7 +1267,7 @@ async function resolveOptions(options) {
1253
1267
  writeCliMessage("");
1254
1268
  const onCancel = () => {
1255
1269
  writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1256
- process3.exit(1);
1270
+ process4.exit(1);
1257
1271
  };
1258
1272
  let projectName = options.name;
1259
1273
  if (!projectName && useDefaults) {
@@ -1319,7 +1333,7 @@ async function resolveOptions(options) {
1319
1333
  let selectedFeatures = [];
1320
1334
  const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
1321
1335
  if (selectedAppType === "fullstack") {
1322
- selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features?.includes("deploy") ? ["deploy"] : []]);
1336
+ selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
1323
1337
  } else if (selectedTemplate === "custom" && !options.features && useDefaults) {
1324
1338
  selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
1325
1339
  } else if (selectedTemplate === "custom" && !options.features) {
@@ -1346,6 +1360,9 @@ async function resolveOptions(options) {
1346
1360
  const merged = /* @__PURE__ */ new Set([...templateFeatures, ...appTypeDefaults]);
1347
1361
  selectedFeatures = resolveFeatureDependencies(Array.from(merged));
1348
1362
  }
1363
+ selectedFeatures = selectedFeatures.filter((feature) => feature !== "deploy");
1364
+ if (options.deploy !== false)
1365
+ selectedFeatures.push("deploy");
1349
1366
  const moduleConfigs = selectedAppType === "fullstack" ? { ...buildDefaultModuleConfigs(selectedFeatures, projectName), ...options.moduleConfigs } : options.moduleConfigs ?? (useDefaults ? buildDefaultModuleConfigs(selectedFeatures, projectName) : await promptModuleConfigs(selectedFeatures, projectName));
1350
1367
  let addExamples = options.examples;
1351
1368
  if (addExamples === void 0 && useDefaults) {
@@ -1427,6 +1444,7 @@ async function resolveOptions(options) {
1427
1444
  frontends: selectedFrontends,
1428
1445
  template: selectedTemplate ?? "default",
1429
1446
  features: selectedFeatures,
1447
+ deploy: options.deploy !== false,
1430
1448
  moduleConfigs,
1431
1449
  examples: addExamples ?? true,
1432
1450
  install: install ?? true,
@@ -1509,7 +1527,7 @@ async function promptModuleConfigs(features, projectName) {
1509
1527
  const configs = {};
1510
1528
  const onCancel = () => {
1511
1529
  writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1512
- process3.exit(1);
1530
+ process4.exit(1);
1513
1531
  };
1514
1532
  writeCliMessage("");
1515
1533
  writeCliMessage(chalk.bold(" \u2699 \u6A21\u5757\u914D\u7F6E"));
@@ -1723,24 +1741,20 @@ function normalizeChildOutput(output) {
1723
1741
  return "";
1724
1742
  }
1725
1743
  function generateAiDevelopmentReadme() {
1726
- return `## AI \u8F85\u52A9\u5F00\u53D1
1744
+ return `### AI \u8F85\u52A9\u5F00\u53D1
1727
1745
 
1728
1746
  \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
1729
1747
 
1730
- \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
1731
-
1732
- 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
1733
-
1734
- \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
1748
+ \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
1735
1749
 
1736
- \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
1750
+ \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
1737
1751
 
1738
- \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
1752
+ \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
1739
1753
 
1740
1754
  `;
1741
1755
  }
1742
1756
  function generateEnvironmentReadme() {
1743
- return `## \u5206\u73AF\u5883\u914D\u7F6E
1757
+ return `### \u5206\u73AF\u5883\u914D\u7F6E
1744
1758
 
1745
1759
  \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
1746
1760
 
@@ -1749,40 +1763,63 @@ function generateEnvironmentReadme() {
1749
1763
  | \u5F00\u53D1 / \u6D4B\u8BD5\u6A21\u5F0F / \u751F\u4EA7\u6A21\u5F0F\u9884\u89C8 | \`pnpm dev\` / \`dev:test\` / \`dev:prod\` |
1750
1764
  | \u7C7B\u578B\u68C0\u67E5\uFF08check \u4E3A\u522B\u540D\uFF09 | \`pnpm typecheck\` |
1751
1765
  | \u68C0\u67E5 / \u81EA\u52A8\u4FEE\u590D\u683C\u5F0F | \`pnpm lint\` / \`lint:fix\` |
1752
- | \u6784\u5EFA / \u751F\u4EA7\u6A21\u5F0F\u6784\u5EFA | \`pnpm build\` / \`build:prod\` |
1766
+ | \u6E90\u7801\u6784\u5EFA / \u955C\u50CF\u6784\u5EFA | \`pnpm build\` / \`pnpm build --target docker\` |
1753
1767
  | \u5355\u5143\u6D4B\u8BD5 / \u76D1\u542C / \u6D4F\u89C8\u5668\u9A8C\u6536 | \`pnpm test\` / \`test:watch\` / \`test:e2e\` |
1754
1768
  | Node \u670D\u52A1\u6309\u73AF\u5883\u542F\u52A8\uFF08\u6784\u5EFA\u540E\uFF09 | \`pnpm start\` / \`start:test\` / \`start:prod\` |
1755
1769
 
1756
1770
  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`;
1757
1771
  }
1772
+ function generateBuildDeploymentReadme(pm, appDir) {
1773
+ return `### \u6784\u5EFA\u4E0E\u90E8\u7F72
1774
+
1775
+ \u521B\u5EFA\u65F6\u9ED8\u8BA4\u5305\u542B\u90E8\u7F72\u5DE5\u5177\uFF1B\u4E0D\u9700\u8981\u65F6\u4F7F\u7528 hai create --no-deploy\u3002
1776
+ \u6BCF\u4E2A\u955C\u50CF\u5BF9\u5E94 images/ \u4E2D\u4E00\u4E2A Dockerfile\uFF0Chai.build.json \u7528 dockerfile \u6307\u5B9A\u6587\u4EF6\u3002
1777
+ \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
1778
+ \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
1779
+
1780
+ \`\`\`sh
1781
+ ${pm} build
1782
+ ${pm} build --target docker
1783
+ # \u6784\u5EFA\u548C\u90E8\u7F72\u4E00\u6B21\u5B8C\u6210
1784
+ ${pm} exec hai deploy ${appDir} --env test --build
1785
+ # \u590D\u7528\u5DF2\u6709\u955C\u50CF\uFF1B\u53EA\u751F\u6210\u5B8C\u6574\u79BB\u7EBF\u5305\u53CA\u8DE8\u5E73\u53F0\u90E8\u7F72\u811A\u672C
1786
+ ${pm} exec hai deploy ${appDir} --env prod --bundle-only
1787
+ # \u53EA\u66F4\u65B0\u524D\u7AEF\uFF08\u5DE5\u7A0B\u5B58\u5728 web \u955C\u50CF\u65F6\uFF09
1788
+ ${pm} build --target docker --images web
1789
+ ${pm} exec hai deploy ${appDir} --env prod --services web
1790
+ \`\`\`
1791
+
1792
+ \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
1793
+ \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
1794
+ \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
1795
+ \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\uFF0C\u542F\u52A8\u9636\u6BB5\u4E0D\u8054\u7F51\u62C9\u53D6\u3002
1796
+ 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
1797
+ \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
1798
+ \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
1799
+ \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
1800
+ `;
1801
+ }
1758
1802
  function generateReadme(name, appTypeLabel, pm, appType) {
1759
1803
  const install = getInstallCommand(pm);
1760
- const isStaticImage = appType === "mobile-app";
1761
- const containerPort = isStaticImage ? 8080 : 3e3;
1762
- const runtimeOptions = isStaticImage ? "" : ` --env-file .env --mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly --mount type=volume,source=${name}-data,target=/app/data`;
1763
- const embeddedDataBuild = isStaticImage ? "" : `
1764
- # \u5E26 data/ \u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
1765
- ${getRunCommand(pm, "docker:build:data")}
1766
- ${getRunCommand(pm, "podman:build:data")}`;
1767
- const runtimeNotes = isStaticImage ? "- \u8BE5\u6A21\u677F\u751F\u6210\u9759\u6001 Web \u955C\u50CF\uFF0C\u4E0D\u5305\u542B\u53EF\u5199\u6570\u636E\u5E93\u76EE\u5F55\u3002" : `\u4EE5\u4E0A\u4E09\u4E2A\u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
1768
-
1769
- | \u53C2\u6570 | \u7528\u9014 | \u53EF\u9009\u6027 |
1770
- | --- | --- | --- |
1771
- | \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
1772
- | \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
1773
- | \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
1774
-
1775
- - \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A\u5F53\u524D \`data/\`\uFF08\u4F8B\u5982\u9884\u7F6E SQLite \u6587\u4EF6\uFF09\u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u5FEB\u7167\uFF1B\u8FD0\u884C\u540E\u4EA7\u751F\u7684\u65B0\u6570\u636E\u4ECD\u5E94\u5199\u5165 \`/app/data\` volume\u3002`;
1776
- const testPath = isStaticImage ? "/health" : "/";
1777
1804
  return `# ${name}
1778
1805
 
1806
+ ## \u7B80\u4ECB
1807
+
1779
1808
  \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684${appTypeLabel}\u5E94\u7528\u3002
1780
1809
 
1781
- ## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1810
+ ## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
1811
+
1812
+ - \u5F00\u53D1\u670D\u52A1\u5668\u3001\u6E90\u7801\u7F16\u8BD1\u4E0E\u6D4F\u89C8\u5668\u6D4B\u8BD5\u3002
1813
+ - \u6309\u6240\u9009\u6A21\u5757\u63D0\u4F9B\u9875\u9762\u548C\u670D\u52A1\u914D\u7F6E\u3002
1814
+ - \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\uFF0C\u4FBF\u4E8E\u4EA4\u4ED8\u3002
1815
+
1816
+ ## \u4F7F\u7528\u5165\u95E8
1817
+
1818
+ ### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1782
1819
 
1783
1820
  \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"}
1784
1821
 
1785
- ## \u5F00\u53D1
1822
+ ### \u5F00\u53D1
1786
1823
 
1787
1824
  \`\`\`bash
1788
1825
  ${install}
@@ -1791,57 +1828,57 @@ ${getRunCommand(pm, "dev")}
1791
1828
 
1792
1829
  ${generateEnvironmentReadme()}
1793
1830
 
1794
- ## \u6784\u5EFA
1831
+ ### \u6784\u5EFA
1795
1832
 
1796
1833
  \`\`\`bash
1797
1834
  ${getRunCommand(pm, "build")}
1798
- ${getRunCommand(pm, "i18n:compile")}
1835
+ ${getRunCommand(pm, "paraglide:compile")}
1799
1836
  ${getRunCommand(pm, "preview")}
1800
1837
  \`\`\`
1801
1838
 
1802
- ## \u5BB9\u5668\u8FD0\u884C
1839
+ ${generateBuildDeploymentReadme(pm, `.`)}
1803
1840
 
1804
- ### \u6784\u5EFA\u955C\u50CF
1841
+ ${generateAiDevelopmentReadme()}### \u6587\u6863
1805
1842
 
1806
- \`\`\`bash
1807
- # \u666E\u901A\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
1808
- ${getRunCommand(pm, "docker:build")}
1809
- ${getRunCommand(pm, "podman:build")}${embeddedDataBuild}
1810
- \`\`\`
1811
-
1812
- ### \u8FD0\u884C\u955C\u50CF
1813
-
1814
- \`\`\`bash
1815
- # Docker\uFF1B\u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
1816
- docker run --rm -d --name ${name} -p ${containerPort}:${containerPort}${runtimeOptions} ${name}:latest
1817
- \`\`\`
1843
+ - [hai Agent Framework](https://github.com/hai-series/hai-framework)
1844
+ - [Svelte 5](https://svelte.dev/)
1818
1845
 
1819
- ### \u6D4B\u8BD5\u955C\u50CF
1846
+ ## \u5F00\u53D1\u6D4B\u8BD5
1820
1847
 
1821
- \`\`\`bash
1822
- curl --fail http://127.0.0.1:${containerPort}${testPath}
1823
- docker stop ${name}
1848
+ \`\`\`sh
1849
+ ${getRunCommand(pm, "typecheck")}
1850
+ ${getRunCommand(pm, "lint")}
1851
+ ${getRunCommand(pm, "build")}
1852
+ ${getRunCommand(pm, "test")}
1853
+ ${getRunCommand(pm, "test:e2e")}
1824
1854
  \`\`\`
1825
1855
 
1826
- ${runtimeNotes}
1827
-
1828
- ${generateAiDevelopmentReadme()}## \u6587\u6863
1856
+ ## \u8BB8\u53EF\u8BC1
1829
1857
 
1830
- - [hai Agent Framework](https://github.com/hai-series/hai-framework)
1831
- - [Svelte 5](https://svelte.dev/)
1858
+ Apache-2.0
1832
1859
  `;
1833
1860
  }
1834
1861
  function generateApiWorkspaceReadme(name, pm) {
1835
1862
  return `# ${name}
1836
1863
 
1837
- \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684 typed API workspace\u3002
1864
+ ## \u7B80\u4ECB
1865
+
1866
+ \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
1867
+
1868
+ ## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
1838
1869
 
1839
- ## \u5DE5\u7A0B\u7ED3\u6784
1870
+ - \u5171\u4EAB contract\u3001\u8F93\u5165\u8F93\u51FA\u6821\u9A8C\u4E0E\u670D\u52A1 procedures\u3002
1871
+ - \u6A21\u5757\u914D\u7F6E\u3001\u5065\u5EB7\u68C0\u67E5\u548C HTTP \u670D\u52A1\u3002
1872
+ - \u72EC\u7ACB\u955C\u50CF\u6784\u5EFA\u4E0E\u79BB\u7EBF\u4EA4\u4ED8\u3002
1873
+
1874
+ ## \u4F7F\u7528\u5165\u95E8
1875
+
1876
+ ### \u5DE5\u7A0B\u7ED3\u6784
1840
1877
 
1841
1878
  - \`apps/${name}-contract\`\uFF1A\u5171\u4EAB API contract\u3001Zod schema \u4E0E\u7C7B\u578B
1842
1879
  - \`apps/${name}-service\`\uFF1A\u540E\u7AEF API service\uFF0C\u57FA\u4E8E \`@h-ai/serv\` \u66B4\u9732 HTTP \u5E94\u7528
1843
1880
 
1844
- ## \u5F00\u53D1
1881
+ ### \u5F00\u53D1
1845
1882
 
1846
1883
  \`\`\`bash
1847
1884
  ${pm} install
@@ -1850,96 +1887,55 @@ ${pm} dev
1850
1887
 
1851
1888
  ${generateEnvironmentReadme()}
1852
1889
 
1853
- ## \u8D28\u91CF\u95E8\u7981
1854
-
1855
- \`\`\`bash
1856
- ${pm} typecheck
1857
- ${pm} lint
1858
- ${pm} build
1859
- ${pm} test
1860
- ${pm} test:e2e
1861
- \`\`\`
1862
-
1863
- ## \u5BB9\u5668\u8FD0\u884C
1864
-
1865
- ### \u6784\u5EFA\u955C\u50CF
1866
-
1867
- \`\`\`bash
1868
- # \u666E\u901A\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
1869
- ${pm} docker:build
1870
- ${pm} podman:build
1871
-
1872
- # \u5C06 apps/${name}-service/data \u4F5C\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
1873
- ${pm} docker:build:data
1874
- ${pm} podman:build:data
1875
- \`\`\`
1876
-
1877
- ### \u8FD0\u884C\u955C\u50CF
1878
-
1879
- \`\`\`bash
1880
- # \u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
1881
- docker run --rm -d --name ${name} -p 3000:3000 --env-file .env \\
1882
- --mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly \\
1883
- --mount type=volume,source=${name}-data,target=/app/data \\
1884
- ${name}:latest
1885
- \`\`\`
1886
-
1887
- ### \u6D4B\u8BD5\u955C\u50CF
1888
-
1889
- \`\`\`bash
1890
- curl --fail http://127.0.0.1:3000/health
1891
- curl --fail http://127.0.0.1:3000/ready
1892
- curl --fail --request POST http://127.0.0.1:3000/api/v1/app/info \\
1893
- --header "Content-Type: application/json" --data '{}'
1894
- docker stop ${name}
1895
- \`\`\`
1896
-
1897
- - \u914D\u7F6E\u9ED8\u8BA4\u6765\u81EA \`apps/${name}-service/config\`\uFF1B\u8FD0\u884C\u65F6\u53EF\u53EA\u8BFB\u6302\u8F7D\u5230 \`/app/config\` \u8986\u76D6\uFF0C\u5E76\u7528 YAML \u7684 \`\${ENV_NAME:default}\` \u5360\u4F4D\u7B26\u63A5\u6536\u73AF\u5883\u53D8\u91CF\u3002
1898
- - \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A \`apps/${name}-service/data\` \u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\uFF1B\u751F\u4EA7\u5199\u5165\u4ECD\u5EFA\u8BAE\u4F7F\u7528 \`/app/data\` volume\u3002
1899
-
1900
- \u4EE5\u4E0A\u4E09\u4E2A\u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
1901
1890
 
1902
- | \u53C2\u6570 | \u7528\u9014 | \u53EF\u9009\u6027 |
1903
- | --- | --- | --- |
1904
- | \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
1905
- | \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
1906
- | \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
1891
+ ${generateBuildDeploymentReadme(pm, `apps/${name}-service`)}
1907
1892
 
1908
- ## \u6269\u5C55\u5EFA\u8BAE
1893
+ ### \u6269\u5C55\u5EFA\u8BAE
1909
1894
 
1910
1895
  - \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
1911
1896
  - \u5728 \`${name}-service\` \u4E2D\u6269\u5C55 procedures\u3001\u6A21\u5757\u521D\u59CB\u5316\u4E0E\u914D\u7F6E\u3002
1912
1897
  - \u9700\u8981 IAM / storage / ai \u65F6\uFF0C\u4F18\u5148\u590D\u7528 \`@h-ai/api-contract\` \u4E0E \`@h-ai/serv/features/*\`\u3002
1913
1898
 
1914
- ${generateAiDevelopmentReadme()}## \u6587\u6863
1899
+ ${generateAiDevelopmentReadme()}### \u6587\u6863
1915
1900
 
1916
1901
  - [hai Agent Framework](https://github.com/hai-series/hai-framework)
1917
1902
  - [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
1918
1903
  - [@h-ai/api-contract](./.agents/skills/hai-api-contract/SKILL.md)
1919
1904
  - [@h-ai/api-client](./.agents/skills/hai-api-client/SKILL.md)
1905
+
1906
+ ## \u5F00\u53D1\u6D4B\u8BD5
1907
+
1908
+ \`\`\`bash
1909
+ ${pm} typecheck
1910
+ ${pm} lint
1911
+ ${pm} build
1912
+ ${pm} test
1913
+ ${pm} test:e2e
1914
+ \`\`\`
1915
+
1916
+ ## \u8BB8\u53EF\u8BC1
1917
+
1918
+ Apache-2.0
1920
1919
  `;
1921
1920
  }
1922
1921
  function generateFullstackReadme(name, pm, frontends) {
1923
1922
  const enabledFrontends = frontends.map((target) => FRONTEND_TARGETS[target].name).join(" / ");
1924
- const webBuildCommands = frontends.includes("web") ? `
1925
- # Web \u9759\u6001\u955C\u50CF\uFF08\u4E8C\u9009\u4E00\uFF09
1926
- ${pm} docker:build:web
1927
- ${pm} podman:build:web` : "";
1928
- const webRunCommand = frontends.includes("web") ? `
1929
-
1930
- # Web\uFF1BPUBLIC_API_BASE \u5728\u5BB9\u5668\u542F\u52A8\u65F6\u6CE8\u5165\uFF0C\u4E0D\u9700\u8981\u91CD\u65B0\u6784\u5EFA\u955C\u50CF
1931
- docker run --rm -d --name ${name}-web -p 8080:8080 \\
1932
- -e PUBLIC_API_BASE=https://api.example.com \\
1933
- ${name}-web:latest` : "";
1934
- const webTestCommands = frontends.includes("web") ? `curl --fail http://127.0.0.1:8080/health
1935
- curl --fail http://127.0.0.1:8080/runtime-config.js
1936
- ` : "";
1937
- const containerNames = frontends.includes("web") ? `${name} ${name}-web` : name;
1938
1923
  return `# ${name}
1939
1924
 
1940
- \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684\u524D\u540E\u7AEF\u5206\u79BB\u591A\u5305\u5DE5\u7A0B\u3002
1925
+ ## \u7B80\u4ECB
1941
1926
 
1942
- ## \u5DE5\u7A0B\u7ED3\u6784
1927
+ \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
1928
+
1929
+ ## \u6838\u5FC3\u529F\u80FD\u6E05\u5355
1930
+
1931
+ - API \u670D\u52A1\u3001\u5171\u4EAB\u754C\u9762\u4E0E\u6309\u9700\u751F\u6210\u7684\u5BA2\u6237\u7AEF\u3002
1932
+ - \u7528\u6237\u7BA1\u7406\u3001\u89D2\u8272\u3001\u5BA1\u8BA1\u548C\u4E2A\u4EBA\u8BBE\u7F6E\u793A\u4F8B\u3002
1933
+ - \u767B\u5F55\u3001\u4E3B\u9898\u3001\u8BED\u8A00\u3001\u65B0\u624B\u5F15\u5BFC\u548C\u5E2E\u52A9\u4E2D\u5FC3\u3002
1934
+ - API/Web \u72EC\u7ACB Dockerfile \u4E0E\u955C\u50CF\u5F52\u6863\u3002
1935
+
1936
+ ## \u4F7F\u7528\u5165\u95E8
1937
+
1938
+ ### \u5DE5\u7A0B\u7ED3\u6784
1943
1939
 
1944
1940
  - \`packages/${name}-contract\`\uFF1A\u524D\u540E\u7AEF\u5171\u4EAB API \u5951\u7EA6\u3001Zod Schema \u4E0E\u7C7B\u578B
1945
1941
  - \`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
@@ -1951,11 +1947,11 @@ curl --fail http://127.0.0.1:8080/runtime-config.js
1951
1947
 
1952
1948
  \u5DF2\u542F\u7528\u524D\u7AEF\uFF1A${enabledFrontends || "Web / App / Desktop"}
1953
1949
 
1954
- ## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1950
+ ### \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1955
1951
 
1956
1952
  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
1957
1953
 
1958
- ## \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
1954
+ ### \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
1959
1955
 
1960
1956
  \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
1961
1957
 
@@ -1969,7 +1965,7 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
1969
1965
 
1970
1966
  \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
1971
1967
 
1972
- ## \u914D\u7F6E\u6269\u5C55
1968
+ ### \u914D\u7F6E\u6269\u5C55
1973
1969
 
1974
1970
  \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
1975
1971
 
@@ -1981,79 +1977,23 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D
1981
1977
 
1982
1978
  ${generateEnvironmentReadme()}
1983
1979
 
1984
- ## \u5F00\u53D1\u3001\u7F16\u8BD1\u4E0E\u6253\u5305
1980
+ ### \u5F00\u53D1\u4E0E\u6784\u5EFA
1985
1981
 
1986
1982
  \`\`\`bash
1987
1983
  ${pm} install
1988
1984
  ${pm} dev
1989
-
1990
- # \u4E00\u952E\u7F16\u8BD1\uFF1A\u7C7B\u578B\u68C0\u67E5 + \u5168\u5DE5\u4F5C\u533A\u6784\u5EFA
1991
- ${pm} compile
1992
-
1993
- # \u4E00\u952E\u6253\u5305\uFF1AWeb build\u3001App Capacitor sync\u3001Desktop Tauri build\uFF08\u6309\u542F\u7528\u524D\u7AEF\u6267\u884C\uFF09
1994
- ${pm} package
1995
-
1996
- # \u90E8\u7F72\u5165\u53E3\uFF1A\u9ED8\u8BA4\u6267\u884C package\uFF0C\u53EF\u66FF\u6362\u4E3A\u56E2\u961F\u81EA\u5DF1\u7684 CI/CD \u53D1\u5E03\u811A\u672C
1997
- ${pm} deploy
1998
- \`\`\`
1999
-
2000
- ## \u5BB9\u5668\u8FD0\u884C
2001
-
2002
- \u9ED8\u8BA4\u955C\u50CF\u76F4\u63A5\u8FD0\u884C API Service\uFF0C\u5916\u90E8\u7AEF\u53E3\u4E3A 3000\u3002
2003
-
2004
- ### \u6784\u5EFA\u955C\u50CF
2005
-
2006
- \`\`\`bash
2007
- # Service \u666E\u901A\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
2008
- ${pm} docker:build
2009
- ${pm} podman:build
2010
-
2011
- # \u5C06 packages/${name}-serv/data \u4F5C\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\u6784\u5EFA\uFF08\u4E8C\u9009\u4E00\uFF09
2012
- ${pm} docker:build:data
2013
- ${pm} podman:build:data
2014
- ${webBuildCommands}
2015
- \`\`\`
2016
-
2017
- ### \u8FD0\u884C\u955C\u50CF
2018
-
2019
- \`\`\`bash
2020
- # Service\uFF1B\u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
2021
- docker run --rm -d --name ${name} -p 3000:3000 --env-file .env --env-file .env.prod --env-file .env.local \\
2022
- --mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly \\
2023
- --mount type=volume,source=${name}-data,target=/app/data \\
2024
- ${name}:latest
2025
- ${webRunCommand}
2026
- \`\`\`
2027
-
2028
- ### \u6D4B\u8BD5\u955C\u50CF
2029
-
2030
- \`\`\`bash
2031
- curl --fail http://127.0.0.1:3000/health
2032
- curl --fail http://127.0.0.1:3000/ready
2033
- ${webTestCommands}docker stop ${containerNames}
1985
+ ${pm} typecheck
1986
+ ${pm} build
1987
+ ${pm} test
1988
+ ${pm} test:e2e
1989
+ ${frontends.includes("desktop") ? `${pm} build --target desktop --platform windows # Windows \u4E3B\u673A\uFF1BLinux/macOS \u5728\u5BF9\u5E94\u4E3B\u673A\u6784\u5EFA` : ""}
2034
1990
  \`\`\`
2035
1991
 
2036
- - \u4E0A\u4F8B\u6309\u516C\u5171\u57FA\u7840\u3001\u751F\u4EA7\u73AF\u5883\u3001\u672C\u673A\u79C1\u6709\u6587\u4EF6\u7684\u987A\u5E8F\u6CE8\u5165\u53D8\u91CF\uFF1B\u8FD0\u884C\u524D\u5728\u76EE\u6807\u673A\u5668\u521B\u5EFA \`.env.local\` \u5E76\u8BBE\u7F6E\u81EA\u5DF1\u7684\u7BA1\u7406\u5458\u5BC6\u7801\u3002\u90E8\u7F72\u5E73\u53F0\u4E5F\u53EF\u76F4\u63A5\u6CE8\u5165\u540C\u540D\u53D8\u91CF\u3002
2037
- - \u914D\u7F6E\u9ED8\u8BA4\u6765\u81EA \`packages/${name}-serv/config\`\uFF0C\u4E5F\u53EF\u6302\u8F7D\u5230 \`/app/config\`\uFF1BYAML \u4E2D\u7684 \`\${ENV_NAME:default}\` \u53EF\u7531 \`--env-file\` \u6216 \`-e\` \u6CE8\u5165\u3002
2038
- - \u5E26\u6570\u636E\u6784\u5EFA\u4F1A\u628A \`packages/${name}-serv/data\` \u56FA\u5316\u4E3A\u955C\u50CF\u521D\u59CB\u6570\u636E\uFF1B\u6301\u4E45\u5316\u5199\u5165\u4ECD\u5E94\u4F7F\u7528 volume\u3002
2039
- - Service \u7531 \`@h-ai/serv\` \u76F4\u63A5\u5BF9\u5916\u76D1\u542C\uFF0C\u4E0D\u989D\u5916\u542F\u52A8 Web \u8F6C\u53D1\u5C42\u3002
2040
- ${frontends.includes("web") ? `- Web \u4F7F\u7528\u72EC\u7ACB\u9759\u6001\u955C\u50CF\u5E76\u652F\u6301\u4E00\u6B21\u6784\u5EFA\u3001\u591A\u73AF\u5883\u90E8\u7F72\uFF1B\u6784\u5EFA\u53EA\u9700\u8FD0\u884C \`${pm} docker:build:web\`\uFF0C\u542F\u52A8\u5BB9\u5668\u65F6\u901A\u8FC7 \`-e PUBLIC_API_BASE=https://api.example.com\` \u6307\u5411 Service \u5BF9\u5916\u5730\u5740\u3002\u672A\u8BBE\u7F6E\u65F6\u9ED8\u8BA4\u4F7F\u7528 Web \u9875\u9762\u81EA\u8EAB origin\u3002` : ""}
2041
-
2042
- \u4EE5\u4E0A\u4E09\u4E2A Service \u8FD0\u884C\u65F6\u53C2\u6570\u5747\u53EF\u6309\u9700\u5220\u9664\uFF1A
1992
+ 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
2043
1993
 
2044
- | \u53C2\u6570 | \u7528\u9014 | \u53EF\u9009\u6027 |
2045
- | --- | --- | --- |
2046
- | \`--env-file .env\` | \u6279\u91CF\u4F20\u5165\u73AF\u5883\u53D8\u91CF\uFF1B\u4E5F\u53EF\u6539\u7528\u591A\u4E2A \`-e NAME=value\`\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u548C YAML \u9ED8\u8BA4\u503C\u3002 |
2047
- | \`--mount type=bind,...target=/app/config,readonly\` | \u7528\u5BBF\u4E3B\u673A\u914D\u7F6E\u76EE\u5F55\u8986\u76D6\u955C\u50CF\u5185 \`/app/config\`\uFF1B\u5FC5\u987B\u628A \`source\` \u6539\u6210\u771F\u5B9E\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\`readonly\` \u9632\u6B62\u5BB9\u5668\u4FEE\u6539\u914D\u7F6E\u3002 | \u53EF\u9009\uFF1B\u5220\u9664\u540E\u4F7F\u7528\u955C\u50CF\u81EA\u5E26\u914D\u7F6E\u3002 |
2048
- | \`--mount type=volume,...target=/app/data\` | \u521B\u5EFA\u6216\u590D\u7528 named volume\uFF0C\u6301\u4E45\u5316 SQLite \u548C\u672C\u5730\u5B58\u50A8\u3002 | \u53EF\u9009\u4F46\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u4FDD\u7559\uFF1B\u5220\u9664\u540E\u6570\u636E\u53EA\u5199\u5165\u5BB9\u5668\u5C42\uFF0C\u4F7F\u7528 \`--rm\` \u5220\u9664\u5BB9\u5668\u65F6\u4F1A\u4E22\u5931\u3002 |
1994
+ ${generateBuildDeploymentReadme(pm, `packages/${name}-serv`)}
2049
1995
 
2050
- \u524D\u7AEF\u5355\u72EC\u4EA4\u4ED8\u547D\u4EE4\uFF1A
2051
-
2052
- - Web\uFF1A\`${pm} --filter ${name}-web build\` / \`${pm} --filter ${name}-web package\`
2053
- - App\uFF1A\`${pm} --filter ${name}-app package\`\uFF0CAndroid/iOS \u53EF\u7528 \`cap:build:android:debug\` / \`cap:build:android:release\` / \`cap:build:ios\`
2054
- - Desktop\uFF1A\`${pm} --filter ${name}-desktop tauri:dev\` / \`${pm} --filter ${name}-desktop package\`
2055
-
2056
- ## i18n \u4E0E shared \u534F\u540C
1996
+ ### i18n \u4E0E shared \u534F\u540C
2057
1997
 
2058
1998
  - shared\uFF1A\`packages/${name}-shared/project.inlang\` + \`messages/\`\uFF0C\u53EA\u653E\u8DE8\u7AEF\u5171\u4EAB\u7EC4\u4EF6\u6587\u6848\u3002
2059
1999
  - 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
@@ -2061,13 +2001,20 @@ ${frontends.includes("web") ? `- Web \u4F7F\u7528\u72EC\u7ACB\u9759\u6001\u955C\
2061
2001
  - \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
2062
2002
  - \`${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
2063
2003
 
2064
- ## \u81EA\u5B9A\u4E49\u5EFA\u8BAE
2004
+ ### \u81EA\u5B9A\u4E49\u5EFA\u8BAE
2065
2005
 
2066
2006
  - \u6539\u5168\u5C40 Shell/\u4E3B\u9898/\u8BED\u8A00\u5207\u6362\uFF1A\u7F16\u8F91 \`packages/${name}-shared\`\u3002
2067
- - \u6539\u67D0\u4E2A\u524D\u7AEF\u9875\u9762\u548C\u6587\u6848\uFF1A\u7F16\u8F91\u5BF9\u5E94 \`apps/${name}-*/src/routes\` \u4E0E \`apps/${name}-*/messages\`\u3002
2007
+ - \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
2068
2008
  - \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
2069
2009
 
2070
- ## \u8D28\u91CF\u95E8\u7981
2010
+
2011
+ ${generateAiDevelopmentReadme()}### \u6587\u6863
2012
+
2013
+ - [hai Agent Framework](https://github.com/hai-series/hai-framework)
2014
+ - [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
2015
+ - [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
2016
+
2017
+ ## \u5F00\u53D1\u6D4B\u8BD5
2071
2018
 
2072
2019
  \`\`\`bash
2073
2020
  ${pm} typecheck
@@ -2077,11 +2024,9 @@ ${pm} test
2077
2024
  ${pm} test:e2e
2078
2025
  \`\`\`
2079
2026
 
2080
- ${generateAiDevelopmentReadme()}## \u6587\u6863
2027
+ ## \u8BB8\u53EF\u8BC1
2081
2028
 
2082
- - [hai Agent Framework](https://github.com/hai-series/hai-framework)
2083
- - [@h-ai/serv](./.agents/skills/hai-serv/SKILL.md)
2084
- - [@h-ai/ui](./.agents/skills/hai-ui/SKILL.md)
2029
+ Apache-2.0
2085
2030
  `;
2086
2031
  }
2087
2032
  function printCompletionMessage(options) {
@@ -2165,7 +2110,7 @@ async function addModule(options) {
2165
2110
  try {
2166
2111
  const project = await detectProject(cwd);
2167
2112
  if (!project) {
2168
- core.logger.error(chalk.red("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\uFF08\u627E\u4E0D\u5230 package.json\uFF09"));
2113
+ writeCliError(chalk.red("\u672A\u68C0\u6D4B\u5230\u9879\u76EE\uFF08\u627E\u4E0D\u5230 package.json\uFF09"));
2169
2114
  return;
2170
2115
  }
2171
2116
  let moduleName = options.module;
@@ -2176,7 +2121,7 @@ async function addModule(options) {
2176
2121
  value: key
2177
2122
  }));
2178
2123
  if (choices.length === 0) {
2179
- core.logger.info(chalk.green("\u6240\u6709\u6A21\u5757\u5DF2\u5B89\u88C5\uFF01"));
2124
+ writeCliMessage(chalk.green("\u6240\u6709\u6A21\u5757\u5DF2\u5B89\u88C5\uFF01"));
2180
2125
  return;
2181
2126
  }
2182
2127
  const { selected } = await prompts({
@@ -2191,11 +2136,11 @@ async function addModule(options) {
2191
2136
  }
2192
2137
  const moduleDef = MODULE_MAP[moduleName];
2193
2138
  if (!moduleDef) {
2194
- core.logger.error(chalk.red(`\u672A\u77E5\u6A21\u5757: ${moduleName}`));
2195
- core.logger.info(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
2139
+ writeCliError(chalk.red(`\u672A\u77E5\u6A21\u5757: ${moduleName}`));
2140
+ writeCliMessage(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
2196
2141
  return;
2197
2142
  }
2198
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-A5VIR5J6.js');
2143
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-FC3QRKDO.js');
2199
2144
  if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
2200
2145
  const generatedFiles2 = await generateProjectAiSupport(
2201
2146
  cwd,
@@ -2206,12 +2151,12 @@ async function addModule(options) {
2206
2151
  }
2207
2152
  );
2208
2153
  if (generatedFiles2.length > 0) {
2209
- core.logger.info(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5\uFF0C\u5DF2\u8865\u5168 AI \u652F\u6301\u6587\u4EF6`));
2154
+ writeCliMessage(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5\uFF0C\u5DF2\u8865\u5168 AI \u652F\u6301\u6587\u4EF6`));
2210
2155
  for (const generatedFile of generatedFiles2) {
2211
- core.logger.info(chalk.gray(` + ${generatedFile} (AI support)`));
2156
+ writeCliMessage(chalk.gray(` + ${generatedFile} (AI support)`));
2212
2157
  }
2213
2158
  } else {
2214
- core.logger.info(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5`));
2159
+ writeCliMessage(chalk.yellow(`\u6A21\u5757 ${moduleName} \u5DF2\u5B89\u88C5`));
2215
2160
  }
2216
2161
  return;
2217
2162
  }
@@ -2234,7 +2179,10 @@ async function addModule(options) {
2234
2179
  const existingHaiSpecifier = Object.entries(pkg.dependencies).find(([pkgName]) => pkgName.startsWith("@h-ai/"))?.[1];
2235
2180
  const depVersion = existingHaiSpecifier === "catalog:" ? "catalog:" : getCliVersion();
2236
2181
  for (const pkgName of allPackages) {
2237
- if (!pkg.dependencies[pkgName]) {
2182
+ if (pkgName === "@h-ai/deploy") {
2183
+ pkg.devDependencies ??= {};
2184
+ pkg.devDependencies[pkgName] ??= depVersion;
2185
+ } else if (!pkg.dependencies[pkgName]) {
2238
2186
  pkg.dependencies[pkgName] = depVersion;
2239
2187
  }
2240
2188
  }
@@ -2249,7 +2197,7 @@ async function addModule(options) {
2249
2197
  await writeFile(configPath, content);
2250
2198
  spinner.succeed();
2251
2199
  } else {
2252
- core.logger.info(chalk.gray(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: config/_${moduleDef.configKey}.yml`));
2200
+ writeCliMessage(chalk.gray(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: config/_${moduleDef.configKey}.yml`));
2253
2201
  }
2254
2202
  }
2255
2203
  if (moduleDef.deps) {
@@ -2260,7 +2208,7 @@ async function addModule(options) {
2260
2208
  if (!await fileExists(depConfigPath)) {
2261
2209
  const content = generateConfigFile(depDef.configKey);
2262
2210
  await writeFile(depConfigPath, content);
2263
- core.logger.info(chalk.gray(` + config/_${depDef.configKey}.yml (\u4F9D\u8D56)`));
2211
+ writeCliMessage(chalk.gray(` + config/_${depDef.configKey}.yml (\u4F9D\u8D56)`));
2264
2212
  }
2265
2213
  }
2266
2214
  }
@@ -2276,7 +2224,7 @@ async function addModule(options) {
2276
2224
  );
2277
2225
  if (generatedFiles.length > 0) {
2278
2226
  for (const generatedFile of generatedFiles) {
2279
- core.logger.info(chalk.gray(` + ${generatedFile} (AI support)`));
2227
+ writeCliMessage(chalk.gray(` + ${generatedFile} (AI support)`));
2280
2228
  }
2281
2229
  }
2282
2230
  const doInstall = options.install !== false;
@@ -2287,20 +2235,217 @@ async function addModule(options) {
2287
2235
  execSync2(`${pm} install`, { cwd, stdio: "ignore" });
2288
2236
  spinner.succeed();
2289
2237
  }
2290
- core.logger.info("");
2291
- core.logger.info(chalk.green(`\u2714 \u6A21\u5757 ${moduleName} \u6DFB\u52A0\u6210\u529F\uFF01`));
2292
- core.logger.info("");
2293
- core.logger.info("\u5DF2\u6DFB\u52A0\u7684\u5305:");
2238
+ writeCliMessage("");
2239
+ writeCliMessage(chalk.green(`\u2714 \u6A21\u5757 ${moduleName} \u6DFB\u52A0\u6210\u529F\uFF01`));
2240
+ writeCliMessage("");
2241
+ writeCliMessage("\u5DF2\u6DFB\u52A0\u7684\u5305:");
2294
2242
  for (const pkg2 of allPackages) {
2295
- core.logger.info(chalk.cyan(` ${pkg2}`));
2243
+ writeCliMessage(chalk.cyan(` ${pkg2}`));
2296
2244
  }
2297
- core.logger.info("");
2245
+ writeCliMessage("");
2298
2246
  } catch (error) {
2299
2247
  spinner.fail();
2300
- core.logger.error(chalk.red("\u6DFB\u52A0\u6A21\u5757\u5931\u8D25:"), { error });
2248
+ writeCliError(chalk.red("\u6DFB\u52A0\u6A21\u5757\u5931\u8D25:"), { error });
2301
2249
  throw error;
2302
2250
  }
2303
2251
  }
2252
+ async function run(command, args, cwd, quiet = false) {
2253
+ await new Promise((resolve3, reject) => {
2254
+ const env = Object.fromEntries(Object.entries(process4.env).filter(([key]) => !/^(?:HAI_|PUBLIC_|VITE_)/.test(key)));
2255
+ env.NODE_ENV = "production";
2256
+ if (env.CI === "1" || env.CI === "0")
2257
+ env.CI = env.CI === "1" ? "true" : "false";
2258
+ const child = spawn(command, args, { cwd, env, windowsHide: true, stdio: quiet ? "ignore" : "inherit", shell: false });
2259
+ child.once("error", reject);
2260
+ child.once("exit", (code) => code === 0 ? resolve3() : reject(new Error(cliM("cli_build_failed", { params: { command, code: String(code) } }))));
2261
+ });
2262
+ }
2263
+ async function pnpm(args, cwd) {
2264
+ const entry = process4.env.npm_execpath;
2265
+ if (entry && /(?:^|[/\\])pnpm(?:-native)?(?:\.exe|\.[cm]?js)?$/i.test(entry))
2266
+ return /\.[cm]?js$/i.test(entry) ? run(process4.execPath, [entry, ...args], cwd) : run(entry, args, cwd);
2267
+ if (process4.platform === "win32") {
2268
+ const directories = (process4.env.PATH ?? "").split(";");
2269
+ for (const directory of directories) {
2270
+ const executable = join(directory, "pnpm.exe");
2271
+ if (existsSync(executable))
2272
+ return run(executable, args, cwd);
2273
+ for (const relative2 of ["node_modules/pnpm/bin/pnpm.cjs", "pnpm.cjs"]) {
2274
+ const candidate = join(directory, relative2);
2275
+ if (existsSync(candidate))
2276
+ return run(process4.execPath, [candidate, ...args], cwd);
2277
+ }
2278
+ }
2279
+ throw new Error(cliM("cli_build_use_pnpm"));
2280
+ }
2281
+ return run("pnpm", args, cwd);
2282
+ }
2283
+ async function buildCommand(options) {
2284
+ const cwd = resolve(options.cwd ?? process4.cwd());
2285
+ const target = options.target ?? "compile";
2286
+ if (!["compile", "docker", "desktop"].includes(target))
2287
+ throw new Error(cliM("cli_build_target_invalid"));
2288
+ 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)) {
2289
+ throw new Error(cliM("cli_build_options_invalid"));
2290
+ }
2291
+ if (target === "compile") {
2292
+ await pnpm(["run", "compile"], cwd);
2293
+ return;
2294
+ }
2295
+ const metadata = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
2296
+ const appVersion = metadata.version;
2297
+ if (typeof appVersion !== "string" || appVersion.length > 80 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9a-z-]+(?:\.[0-9a-z-]+)*)?(?:\+[0-9a-z-]+(?:\.[0-9a-z-]+)*)?$/i.test(appVersion))
2298
+ throw new Error(cliM("cli_build_version_invalid"));
2299
+ const config = JSON.parse(readFileSync(join(cwd, "hai.build.json"), "utf8"));
2300
+ if (target === "desktop") {
2301
+ const platforms = { win32: "windows", linux: "linux", darwin: "macos" };
2302
+ const host = platforms[process4.platform];
2303
+ const platform = options.platform ?? host;
2304
+ const arch = options.arch ?? process4.arch;
2305
+ if (!platform || platform !== host || !["x64", "arm64"].includes(arch))
2306
+ throw new Error(cliM("cli_build_native_host"));
2307
+ if (!config.desktop?.length)
2308
+ throw new Error(cliM("cli_build_desktop_missing"));
2309
+ const rustTarget = `${arch === "x64" ? "x86_64" : "aarch64"}-${platform === "windows" ? "pc-windows-msvc" : platform === "macos" ? "apple-darwin" : "unknown-linux-gnu"}`;
2310
+ const bundles = platform === "windows" ? "nsis" : platform === "macos" ? "app,dmg" : "deb,appimage";
2311
+ for (const directory of config.desktop)
2312
+ await pnpm(["exec", "tauri", "build", "--target", rustTarget, "--bundles", bundles, "--config", JSON.stringify({ version: appVersion })], resolve(cwd, directory));
2313
+ writeCliMessage(cliM("cli_build_native_ready", { params: { target: rustTarget } }));
2314
+ return;
2315
+ }
2316
+ 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))) {
2317
+ throw new Error(cliM("cli_build_images_missing"));
2318
+ }
2319
+ const selected = options.images?.split(",").map((name) => name.trim());
2320
+ if (selected && (selected.some((name) => !config.images.some((image) => image.name === name)) || new Set(selected).size !== selected.length))
2321
+ throw new Error(cliM("cli_build_selection_invalid"));
2322
+ const images = config.images.filter((image) => !selected || selected.includes(image.name));
2323
+ for (const image of images) {
2324
+ if (!existsSync(resolve(cwd, image.dockerfile)))
2325
+ throw new Error(cliM("cli_build_dockerfile_missing", { params: { path: image.dockerfile } }));
2326
+ }
2327
+ const candidates = options.runtime && options.runtime !== "auto" ? [options.runtime] : ["podman", "docker"];
2328
+ if (candidates.some((runtime2) => !["podman", "docker"].includes(runtime2)))
2329
+ throw new Error(cliM("cli_build_runtime_missing"));
2330
+ let runtime;
2331
+ for (const candidate of candidates) {
2332
+ try {
2333
+ await run(candidate, ["version"], cwd, true);
2334
+ runtime = candidate;
2335
+ break;
2336
+ } catch {
2337
+ }
2338
+ }
2339
+ if (!runtime)
2340
+ throw new Error(cliM("cli_build_runtime_missing"));
2341
+ if (options.sqlitePrebuildMirror) {
2342
+ const url = new URL(options.sqlitePrebuildMirror);
2343
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
2344
+ throw new Error(cliM("cli_build_mirror_invalid"));
2345
+ }
2346
+ const buildId = `${appVersion}-${Date.now()}-${randomBytes(3).toString("hex")}`;
2347
+ const artifact = { version: 3, appVersion, buildId, partial: images.length !== config.images.length, hasWeb: config.images.some((image) => image.name === "web"), images: [] };
2348
+ for (const image of config.images) {
2349
+ if (image.containerPort !== void 0)
2350
+ artifact[image.name === "app" ? "appContainerPort" : "webContainerPort"] = image.containerPort;
2351
+ }
2352
+ const parent = join(cwd, ".hai/build/docker");
2353
+ mkdirSync(parent, { recursive: true });
2354
+ const staging = mkdtempSync(join(parent, ".building-"));
2355
+ const built = [];
2356
+ try {
2357
+ for (const context of new Set(images.map((image) => resolve(cwd, image.context ?? "."))))
2358
+ prepareDependencyInputs(context);
2359
+ for (const image of images) {
2360
+ if (!["app", "web"].includes(image.name))
2361
+ throw new Error(cliM("cli_build_images_missing"));
2362
+ const context = resolve(cwd, image.context ?? ".");
2363
+ const imageRef = `localhost/hai-${basenameSafe(cwd)}-${image.name}:${buildId.toLowerCase().replaceAll("+", "_")}`;
2364
+ const args = ["build", "-f", resolve(cwd, image.dockerfile), "-t", imageRef];
2365
+ if (options.network !== void 0)
2366
+ args.push("--network", options.network);
2367
+ const ignore = `${resolve(cwd, image.dockerfile)}.dockerignore`;
2368
+ if (runtime === "podman" && existsSync(ignore))
2369
+ args.push("--ignorefile", ignore);
2370
+ if (runtime === "podman") {
2371
+ args.push("--format", "docker", "--jobs", "1", ...options.httpProxy === false ? ["--http-proxy=false"] : []);
2372
+ if (options.httpProxy !== false) {
2373
+ for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "ftp_proxy", "no_proxy"]) {
2374
+ if (process4.env[key])
2375
+ args.push("--build-arg", `${key}=${process4.env[key]}`);
2376
+ }
2377
+ }
2378
+ }
2379
+ if (options.sqlitePrebuildMirror)
2380
+ args.push("--build-arg", `SQLITE_PREBUILD_MIRROR=${options.sqlitePrebuildMirror}`);
2381
+ built.push(imageRef);
2382
+ await run(runtime, [...args, context], cwd);
2383
+ const file = `${image.name}-${appVersion}.tar`;
2384
+ await run(runtime, ["save", ...runtime === "podman" ? ["--format", "docker-archive"] : [], "-o", join(staging, file), imageRef], cwd);
2385
+ const hash = createHash("sha256");
2386
+ for await (const chunk of createReadStream(join(staging, file)))
2387
+ hash.update(chunk);
2388
+ artifact.images.push({ name: image.name, file, imageRef, sha256: hash.digest("hex"), ...image.containerPort ? { containerPort: image.containerPort } : {} });
2389
+ }
2390
+ const output = join(parent, buildId);
2391
+ writeFileSync(join(staging, "manifest.json"), `${JSON.stringify(artifact, null, 2)}
2392
+ `);
2393
+ renameSync(staging, output);
2394
+ writeFileSync(join(parent, `latest-${buildId}`), `${buildId}
2395
+ `);
2396
+ renameSync(join(parent, `latest-${buildId}`), join(parent, "latest"));
2397
+ writeCliMessage(cliM("cli_build_ready", { params: { path: output, runtime } }));
2398
+ } catch (error) {
2399
+ for (const imageRef of built) {
2400
+ try {
2401
+ await run(runtime, ["image", "rm", imageRef], cwd, true);
2402
+ } catch {
2403
+ }
2404
+ }
2405
+ throw error;
2406
+ } finally {
2407
+ rmSync(staging, { recursive: true, force: true });
2408
+ }
2409
+ }
2410
+ function prepareDependencyInputs(cwd) {
2411
+ const destination = join(cwd, "images", "dependencies");
2412
+ mkdirSync(destination, { recursive: true });
2413
+ const tracking = join(destination, ".managed.json");
2414
+ const previous = existsSync(tracking) ? JSON.parse(readFileSync(tracking, "utf8")) : [];
2415
+ const files = [];
2416
+ const walk = (directory) => {
2417
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
2418
+ const source = join(directory, entry.name);
2419
+ if (source === destination || ["node_modules", ".git", ".hai", ".turbo", ".svelte-kit", ".pnpm-store", ".cache", "dist", "build", "data", "tmp", "test-results", "playwright-report"].includes(entry.name))
2420
+ continue;
2421
+ if (entry.isDirectory()) {
2422
+ walk(source);
2423
+ continue;
2424
+ }
2425
+ if (!entry.isFile() || !(entry.name === "package.json" || entry.name === ".pnpmfile.cjs" || /\.(?:tgz|patch)$/.test(entry.name)))
2426
+ continue;
2427
+ const file = relative(cwd, source);
2428
+ mkdirSync(dirname(join(destination, file)), { recursive: true });
2429
+ const target = join(destination, file);
2430
+ if (!existsSync(target) || !readFileSync(source).equals(readFileSync(target)))
2431
+ copyFileSync(source, target);
2432
+ files.push(file);
2433
+ }
2434
+ };
2435
+ walk(cwd);
2436
+ for (const file of previous) {
2437
+ const target = resolve(destination, file);
2438
+ if (!files.includes(file) && !relative(destination, target).startsWith(".."))
2439
+ rmSync(target, { force: true });
2440
+ }
2441
+ const content = `${JSON.stringify(files)}
2442
+ `;
2443
+ if (!existsSync(tracking) || readFileSync(tracking, "utf8") !== content)
2444
+ writeFileSync(tracking, content);
2445
+ }
2446
+ function basenameSafe(directory) {
2447
+ return directory.slice(dirname(directory).length + 1).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "") || "app";
2448
+ }
2304
2449
  function displayDeployValue(name, value) {
2305
2450
  if (/PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(name.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(name) || value.includes("PRIVATE KEY"))
2306
2451
  return cliM("cli_deploy_secret");
@@ -2421,10 +2566,10 @@ function ensureConfigNode(appDir, requirement, backend) {
2421
2566
  if (document.errors.length)
2422
2567
  throw new Error(cliM("cli_deploy_invalid_value"));
2423
2568
  if (requirement.configPath.length && !document.hasIn(requirement.configPath))
2424
- document.setIn(requirement.configPath, requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {});
2569
+ document.setIn(requirement.configPath, document.createNode(requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {}));
2425
2570
  if (requirement.service === "ldap") {
2426
2571
  if (!document.hasIn(["login"]))
2427
- document.setIn(["login"], {});
2572
+ document.set("login", document.createNode({}));
2428
2573
  if (!document.hasIn(["login", "ldap"]))
2429
2574
  document.setIn(["login", "ldap"], `\${HAI_IAM_LOGIN_LDAP:false}`);
2430
2575
  }
@@ -2447,7 +2592,7 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2447
2592
  const current = choices.find((item) => item.backend === requirement.backend);
2448
2593
  if (!current)
2449
2594
  throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: requirement.backend } }));
2450
- core.logger.info(cliM("cli_deploy_middleware", { params: { service: requirement.service, backend: current.label, purpose: module.describeMiddlewareBackend(current), source: requirement.source } }));
2595
+ writeCliMessage(cliM("cli_deploy_middleware", { params: { service: requirement.service, backend: current.label, purpose: module.describeMiddlewareBackend(current), source: requirement.source } }));
2451
2596
  const canKeep = canKeepMiddleware(requirement, current, environment);
2452
2597
  const mode = yes ? canKeep ? "keep" : "default" : await ask({
2453
2598
  type: "select",
@@ -2486,8 +2631,11 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2486
2631
  if (deployment.errors.length)
2487
2632
  throw new Error(cliM("cli_deploy_invalid_value"));
2488
2633
  if (!deployment.hasIn(["container", "s3Engine"])) {
2489
- if (!deployment.hasIn(["container"]))
2490
- deployment.setIn(["container"], {});
2634
+ const node = deployment.get("container", true);
2635
+ if (node === void 0 || isScalar(node) && node.value === null)
2636
+ deployment.set("container", deployment.createNode({}));
2637
+ else if (!isMap(node))
2638
+ throw new Error(cliM("cli_deploy_yaml_node", { params: { path: deploymentFile, field: "container" } }));
2491
2639
  deployment.setIn(["container", "s3Engine"], `\${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}`);
2492
2640
  writeFileSync(deploymentFile, deployment.toString());
2493
2641
  }
@@ -2501,9 +2649,10 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2501
2649
  for (const name of CLOUD_FIELDS[backend.provisioner]) {
2502
2650
  const key = `HAI_DEPLOY_SERVICES_${requirement.service.toUpperCase()}_${name.toUpperCase()}`;
2503
2651
  const initial = String(priorConfig[name] ?? environment.values[key] ?? "");
2504
- 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") }));
2652
+ const label = `${backend.label} \u2014 ${cliM(`cli_deploy_cloud_input_${name}`)}`;
2653
+ 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") }));
2505
2654
  if (!value)
2506
- throw new Error(`${key}: ${cliM("cli_deploy_required")}`);
2655
+ throw new Error(`${label}: ${cliM("cli_deploy_required")}`);
2507
2656
  cloud[name] = value;
2508
2657
  updates[key] = value;
2509
2658
  }
@@ -2531,7 +2680,7 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2531
2680
  if (!saved.success)
2532
2681
  throw new Error(saved.error.message);
2533
2682
  for (const [name, value] of Object.entries(updates)) {
2534
- core.logger.info(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
2683
+ writeCliMessage(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
2535
2684
  }
2536
2685
  }
2537
2686
  config.services = Object.keys(services).length ? services : void 0;
@@ -2542,15 +2691,21 @@ async function configureDeployMiddleware(module, scan, environment, config, yes
2542
2691
  function configObject(value) {
2543
2692
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2544
2693
  }
2545
- async function configureDeployTarget(module, config, environment, nonInteractive) {
2694
+ async function configureDeployTarget(module, config, environment, nonInteractive, bundleOnly) {
2695
+ if (bundleOnly) {
2696
+ const existing = configObject(config.provider);
2697
+ if (existing.type && existing.type !== "docker-ssh")
2698
+ throw new Error(cliM("cli_deploy_bundle_provider"));
2699
+ config.provider = { type: "docker-ssh", remote: { runtime: "auto" }, expose: { hostPort: 3e3 }, ...existing };
2700
+ }
2546
2701
  const current = module.ProviderConfigSchema.safeParse(config.provider);
2547
- if (current.success) {
2702
+ if (current.success && (bundleOnly || current.data.type !== "docker-ssh" || current.data.ssh)) {
2548
2703
  config.provider = current.data;
2549
- core.logger.info(cliM("cli_deploy_target_reused", { params: { target: current.data.type === "docker-ssh" ? `${current.data.ssh.username}@${current.data.ssh.host}:${current.data.ssh.port}` : "Vercel" } }));
2704
+ writeCliMessage(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" } }));
2550
2705
  return;
2551
2706
  }
2552
2707
  if (nonInteractive)
2553
- throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
2708
+ throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.success ? "provider.ssh" : current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
2554
2709
  const original = configObject(config.provider);
2555
2710
  const platform = await prompts({
2556
2711
  type: "select",
@@ -2565,9 +2720,9 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2565
2720
  const answer = await prompts({
2566
2721
  type: options.secret ? "password" : "text",
2567
2722
  name: "value",
2568
- message: cliM("cli_deploy_target_field", { params: { name } }),
2723
+ message: cliM(`cli_deploy_input${name.split("_").at(-1)}`),
2569
2724
  initial: options.secret ? void 0 : initial === void 0 ? "" : String(initial),
2570
- 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")
2725
+ 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")
2571
2726
  });
2572
2727
  if (answer.value === void 0)
2573
2728
  throw new Error(cliM("cli_deploy_cancelled"));
@@ -2575,7 +2730,7 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2575
2730
  };
2576
2731
  let provider;
2577
2732
  if (platform.value === "docker-ssh") {
2578
- core.logger.info(cliM("cli_deploy_target_ssh_hint"));
2733
+ writeCliMessage(cliM("cli_deploy_target_ssh_hint"));
2579
2734
  const ssh = configObject(original.ssh);
2580
2735
  const remote = configObject(original.remote);
2581
2736
  const expose = configObject(original.expose);
@@ -2606,38 +2761,59 @@ async function configureDeployTarget(module, config, environment, nonInteractive
2606
2761
  if (!saved.success)
2607
2762
  throw new Error(saved.error.message);
2608
2763
  config.provider = validated.data;
2609
- core.logger.info(cliM("cli_deploy_target_saved"));
2764
+ writeCliMessage(cliM("cli_deploy_target_saved"));
2610
2765
  }
2611
2766
  async function deployCommand(options) {
2612
2767
  const spinner = ora();
2613
- const cwd = options.cwd ?? process3.cwd();
2768
+ const cwd = options.cwd ?? process4.cwd();
2614
2769
  let appDir = resolve(cwd, options.appDir ?? ".");
2615
2770
  let closeDeploy = null;
2616
- const originalEnv = { ...process3.env };
2771
+ let restoreProjectLogger;
2772
+ const originalEnv = { ...process4.env };
2773
+ const previousLogLevel = core.logger.getLevel();
2774
+ core.logger.setLevel("fatal");
2617
2775
  try {
2776
+ if (options.runtime && !["auto", "podman", "docker"].includes(options.runtime))
2777
+ throw new Error(cliM("cli_build_runtime_missing"));
2778
+ if (options.artifactDir && options.build)
2779
+ throw new Error(cliM("cli_deploy_build_artifact_conflict"));
2780
+ if (!options.build && (options.network !== void 0 || options.sqlitePrebuildMirror !== void 0 || options.httpProxy === false))
2781
+ throw new Error(cliM("cli_deploy_build_options_required"));
2782
+ let selectedServices = options.services?.split(",").map((name) => name.trim());
2783
+ if (selectedServices && (!selectedServices.length || selectedServices.some((name) => !/^[a-z][a-z0-9-]*$/.test(name)) || new Set(selectedServices).size !== selectedServices.length))
2784
+ throw new Error(cliM("cli_build_selection_invalid"));
2618
2785
  let environmentName = options.env;
2619
2786
  if (!environmentName) {
2620
- if (!process3.stdin.isTTY)
2787
+ if (!process4.stdin.isTTY)
2621
2788
  throw new Error(cliM("cli_deploy_environment_hint"));
2622
2789
  const answer = await prompts({ type: "text", name: "environment", message: cliM("cli_deploy_environment"), initial: "prod" });
2623
2790
  environmentName = answer.environment;
2624
2791
  if (!environmentName)
2625
2792
  throw new Error(cliM("cli_deploy_cancelled"));
2626
2793
  }
2627
- if (!options.yes && !process3.stdin.isTTY)
2794
+ if (!options.yes && !process4.stdin.isTTY)
2628
2795
  throw new Error(cliM("cli_deploy_environment_hint"));
2629
2796
  let deployModule;
2630
2797
  try {
2631
2798
  const localRequire = createRequire(resolve(appDir, "package.json"));
2799
+ try {
2800
+ const { core: projectCore } = await import(pathToFileURL(localRequire.resolve("@h-ai/core")).href);
2801
+ if (projectCore !== core) {
2802
+ const level = projectCore.logger.getLevel();
2803
+ projectCore.logger.setLevel("fatal");
2804
+ restoreProjectLogger = () => projectCore.logger.setLevel(level);
2805
+ }
2806
+ } catch {
2807
+ }
2632
2808
  let localModule;
2633
2809
  try {
2634
2810
  localModule = localRequire.resolve("@h-ai/deploy");
2635
2811
  } catch {
2636
2812
  }
2637
- deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-6PPBKQ6Q.js');
2813
+ deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-2V6VP4I5.js');
2638
2814
  } catch {
2639
- core.logger.error(chalk.red(cliM("cli_deploy_module_missing")));
2640
- core.logger.info(chalk.cyan(" pnpm add @h-ai/deploy"));
2815
+ writeCliError(chalk.red(cliM("cli_deploy_module_missing")));
2816
+ writeCliMessage(chalk.cyan(" pnpm add -D @h-ai/deploy"));
2641
2817
  throw new Error(cliM("cli_deploy_module_missing"));
2642
2818
  }
2643
2819
  const { deploy } = deployModule;
@@ -2646,10 +2822,19 @@ async function deployCommand(options) {
2646
2822
  throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
2647
2823
  const scan = scanResult.data;
2648
2824
  appDir = scan.appDir ?? appDir;
2825
+ let buildRoot = appDir;
2826
+ while (!existsSync(resolve(buildRoot, "hai.build.json")) && dirname(buildRoot) !== buildRoot)
2827
+ buildRoot = dirname(buildRoot);
2828
+ const buildFile = resolve(buildRoot, "hai.build.json");
2829
+ const buildConfig = existsSync(buildFile) ? JSON.parse(readFileSync(buildFile, "utf8")) : {};
2830
+ 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)];
2831
+ const available = [...new Set(names)];
2832
+ if (selectedServices?.some((name) => !available.includes(name)))
2833
+ throw new Error(cliM("cli_deploy_services_invalid", { params: { services: available.join(", ") } }));
2649
2834
  const environment = deployModule.loadDeploymentEnvironment({ environment: environmentName, envDir: scan.envDir ?? appDir });
2650
2835
  if (!environment.success)
2651
2836
  throw new Error(environment.error.message);
2652
- Object.assign(process3.env, environment.data.values);
2837
+ Object.assign(process4.env, environment.data.values);
2653
2838
  spinner.start(cliM("cli_deploy_load_credentials"));
2654
2839
  const credResult = deploy.credentials.load();
2655
2840
  if (!credResult.success) {
@@ -2660,17 +2845,59 @@ async function deployCommand(options) {
2660
2845
  const configPath = resolve(appDir, "config", "_deploy.yml");
2661
2846
  if (!existsSync(configPath)) {
2662
2847
  spinner.fail(chalk.red(cliM("cli_deploy_config_missing", { params: { path: configPath } })));
2663
- core.logger.info(chalk.cyan(cliM("cli_deploy_config_hint")));
2848
+ writeCliMessage(chalk.cyan(cliM("cli_deploy_config_hint")));
2664
2849
  throw new Error(cliM("cli_deploy_config_missing", { params: { path: configPath } }));
2665
2850
  }
2666
- const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath, process3.env);
2851
+ const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath, process4.env);
2667
2852
  if (!loadedConfig.success)
2668
2853
  throw new Error(loadedConfig.error.message);
2669
2854
  const deployConfig = loadedConfig.data;
2670
2855
  spinner.succeed(cliM("cli_deploy_config_loaded"));
2671
- await configureDeployTarget(deployModule, deployConfig, environment.data, options.yes);
2856
+ if (!options.build && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
2857
+ let artifactDirectory = options.artifactDir ? resolve(cwd, options.artifactDir) : resolve(buildRoot, ".hai/build/docker");
2858
+ if (existsSync(resolve(artifactDirectory, "latest"))) {
2859
+ const pointer = readFileSync(resolve(artifactDirectory, "latest"), "utf8").trim();
2860
+ if (!/^[a-z0-9][\w.+-]*$/i.test(pointer))
2861
+ throw new Error(cliM("cli_deploy_artifact_invalid"));
2862
+ artifactDirectory = resolve(artifactDirectory, pointer);
2863
+ }
2864
+ if (existsSync(resolve(artifactDirectory, "manifest.json"))) {
2865
+ try {
2866
+ const artifact = await deployModule.readDeploymentArtifact(artifactDirectory);
2867
+ if (artifact.partial)
2868
+ selectedServices = artifact.images.map((image) => image.name);
2869
+ } catch {
2870
+ throw new Error(cliM("cli_deploy_artifact_invalid"));
2871
+ }
2872
+ }
2873
+ }
2874
+ await configureDeployTarget(deployModule, deployConfig, environment.data, options.yes, options.bundleOnly);
2875
+ if (configObject(deployConfig.provider).type !== "docker-ssh" && (selectedServices || options.build))
2876
+ throw new Error(cliM("cli_deploy_bundle_provider"));
2877
+ if (!options.yes && !selectedServices && configObject(deployConfig.provider).type === "docker-ssh") {
2878
+ if (existsSync(buildFile)) {
2879
+ if (available.length > 1) {
2880
+ 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 });
2881
+ if (!Array.isArray(choice.value) || choice.value.length === 0)
2882
+ throw new Error(cliM("cli_deploy_cancelled"));
2883
+ if (choice.value.length !== available.length)
2884
+ selectedServices = choice.value;
2885
+ }
2886
+ }
2887
+ }
2672
2888
  spinner.succeed(cliM("cli_deploy_scanned", { params: { name: scan.appName, sveltekit: String(scan.isSvelteKit), services: scan.requiredServices.join(", ") || cliM("cli_deploy_none") } }));
2673
- let skipMiddleware = options.skipMiddleware || options.skipProvision;
2889
+ if (options.build) {
2890
+ if (!existsSync(buildFile))
2891
+ throw new Error(cliM("cli_build_images_missing"));
2892
+ const imageNames = selectedServices?.filter((name) => ["app", "web"].includes(name));
2893
+ if (!selectedServices || imageNames?.length) {
2894
+ writeCliMessage(cliM("cli_deploy_build_started"));
2895
+ await buildCommand({ cwd: buildRoot, target: "docker", images: imageNames?.join(","), runtime: options.runtime, network: options.network, sqlitePrebuildMirror: options.sqlitePrebuildMirror, httpProxy: options.httpProxy });
2896
+ }
2897
+ }
2898
+ if (selectedServices)
2899
+ writeCliMessage(cliM("cli_deploy_services", { params: { services: selectedServices.join(", ") } }));
2900
+ let skipMiddleware = options.skipMiddleware || selectedServices?.every((name) => ["app", "web"].includes(name));
2674
2901
  if (!skipMiddleware && !options.yes && scan.requiredServices.length) {
2675
2902
  const action = await prompts({
2676
2903
  type: "select",
@@ -2683,23 +2910,23 @@ async function deployCommand(options) {
2683
2910
  skipMiddleware = action.value === "skip";
2684
2911
  }
2685
2912
  if (skipMiddleware)
2686
- core.logger.info(cliM("cli_deploy_middleware_skipped"));
2687
- const serviceModes = skipMiddleware ? Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) : await configureDeployMiddleware(deployModule, scan, environment.data, deployConfig, options.yes);
2913
+ writeCliMessage(cliM("cli_deploy_middleware_skipped"));
2914
+ 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);
2688
2915
  if (skipMiddleware)
2689
2916
  deployConfig.services = void 0;
2690
- for (const key of Object.keys(process3.env)) {
2917
+ for (const key of Object.keys(process4.env)) {
2691
2918
  if (originalEnv[key] === void 0)
2692
- delete process3.env[key];
2919
+ delete process4.env[key];
2693
2920
  }
2694
- Object.assign(process3.env, originalEnv);
2921
+ Object.assign(process4.env, originalEnv);
2695
2922
  const refreshed = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
2696
2923
  if (!refreshed.success)
2697
2924
  throw new Error(refreshed.error.message);
2698
- Object.assign(process3.env, refreshed.data.values);
2925
+ Object.assign(process4.env, refreshed.data.values);
2699
2926
  spinner.start(cliM("cli_deploy_initializing"));
2700
2927
  const validatedConfig = deployModule.DeployConfigSchema.safeParse(deployConfig);
2701
2928
  if (!validatedConfig.success)
2702
- throw new Error(cliM("cli_deploy_init_failed", { params: { message: cliM("cli_deploy_invalid_value") } }));
2929
+ throw new Error(cliM("cli_deploy_config_invalid", { params: { path: configPath, fields: validatedConfig.error.issues.map((issue) => issue.path.join(".")).join(", ") } }));
2703
2930
  const initResult = await deploy.init(validatedConfig.data);
2704
2931
  if (!initResult.success) {
2705
2932
  throw new Error(cliM("cli_deploy_init_failed", { params: { message: initResult.error.message } }));
@@ -2709,26 +2936,29 @@ async function deployCommand(options) {
2709
2936
  spinner.start(cliM("cli_deploy_deploying"));
2710
2937
  const deployResult = await deploy.deployApp(appDir, {
2711
2938
  projectName: options.projectName,
2712
- skipProvision: skipMiddleware,
2713
- skipBuild: options.skipBuild,
2939
+ selectedServices,
2940
+ runtime: options.runtime,
2941
+ onProgress: (stage) => writeCliMessage(cliM(`cli_deploy_stage_${stage}`)),
2942
+ skipMiddleware,
2943
+ artifactDir: options.artifactDir ? resolve(cwd, options.artifactDir) : void 0,
2944
+ bundleOnly: options.bundleOnly,
2945
+ output: options.output ? resolve(cwd, options.output) : void 0,
2714
2946
  environment: environment.data.environment,
2715
2947
  envDir: environment.data.envDir,
2716
2948
  serviceModes
2717
2949
  });
2718
2950
  if (!deployResult.success) {
2719
2951
  if (deployResult.error.ext?.recovery) {
2720
- core.logger.warn(deployResult.error.suggestion ?? cliM("cli_deploy_command_failed"), {
2721
- recovery: deployResult.error.ext.recovery
2722
- });
2952
+ writeCliMessage(deployResult.error.suggestion ?? cliM("cli_deploy_command_failed"));
2723
2953
  }
2724
2954
  throw new Error(cliM("cli_deploy_failed", { params: { message: deployResult.error.message } }));
2725
2955
  }
2726
- spinner.succeed(chalk.green(cliM("cli_deploy_success")));
2727
- for (const key of Object.keys(process3.env)) {
2956
+ spinner.succeed(chalk.green(cliM(deployResult.data.status === "packaged" ? "cli_deploy_bundle_ready" : "cli_deploy_success")));
2957
+ for (const key of Object.keys(process4.env)) {
2728
2958
  if (originalEnv[key] === void 0)
2729
- delete process3.env[key];
2959
+ delete process4.env[key];
2730
2960
  }
2731
- Object.assign(process3.env, originalEnv);
2961
+ Object.assign(process4.env, originalEnv);
2732
2962
  const completedEnvironment = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
2733
2963
  if (!completedEnvironment.success)
2734
2964
  throw new Error(completedEnvironment.error.message);
@@ -2737,33 +2967,40 @@ async function deployCommand(options) {
2737
2967
  continue;
2738
2968
  const value = completedEnvironment.data.values[requirement.envPrefix];
2739
2969
  if (value !== void 0)
2740
- core.logger.info(cliM("cli_deploy_saved", { params: { name: requirement.envPrefix, value: displayDeployValue(requirement.envPrefix, value) } }));
2970
+ writeCliMessage(cliM("cli_deploy_saved", { params: { name: requirement.envPrefix, value: displayDeployValue(requirement.envPrefix, value) } }));
2741
2971
  }
2742
- core.logger.info("");
2743
- core.logger.info(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
2744
- core.logger.info(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
2972
+ writeCliMessage("");
2973
+ if (deployResult.data.bundlePath)
2974
+ writeCliMessage(cliM("cli_deploy_bundle_path", { params: { path: deployResult.data.bundlePath } }));
2975
+ if (deployResult.data.status === "packaged")
2976
+ writeCliMessage(deployResult.data.instructions ?? "");
2977
+ else
2978
+ writeCliMessage(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
2979
+ writeCliMessage(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
2745
2980
  if (deployResult.data.envVarsSet.length > 0) {
2746
- core.logger.info(chalk.gray(cliM("cli_deploy_env", { params: { names: deployResult.data.envVarsSet.join(", ") } })));
2981
+ writeCliMessage(chalk.gray(cliM("cli_deploy_env", { params: { names: deployResult.data.envVarsSet.join(", ") } })));
2747
2982
  }
2748
- core.logger.info("");
2983
+ writeCliMessage("");
2749
2984
  } catch (error) {
2750
2985
  spinner.fail(chalk.red(cliM("cli_deploy_command_failed")));
2751
- core.logger.error(error instanceof Error ? error.message : String(error));
2986
+ writeCliError(error instanceof Error ? error.message : String(error));
2752
2987
  throw error;
2753
2988
  } finally {
2754
2989
  if (closeDeploy) {
2755
2990
  try {
2756
2991
  await closeDeploy();
2757
2992
  } catch (error) {
2758
- core.logger.error("Failed to close deploy module", { error });
2759
- process3.exitCode = 1;
2993
+ writeCliError("Failed to close deploy module", error);
2994
+ process4.exitCode = 1;
2760
2995
  }
2761
2996
  }
2762
- for (const key of Object.keys(process3.env)) {
2997
+ for (const key of Object.keys(process4.env)) {
2763
2998
  if (originalEnv[key] === void 0)
2764
- delete process3.env[key];
2999
+ delete process4.env[key];
2765
3000
  }
2766
- Object.assign(process3.env, originalEnv);
3001
+ Object.assign(process4.env, originalEnv);
3002
+ core.logger.setLevel(previousLogLevel);
3003
+ restoreProjectLogger?.();
2767
3004
  }
2768
3005
  }
2769
3006
  var GENERATORS = {
@@ -2799,7 +3036,7 @@ async function generate(options) {
2799
3036
  try {
2800
3037
  const project = await detectProject(cwd);
2801
3038
  if (!project?.isHaiProject) {
2802
- core.logger.warn(chalk.yellow("\u8B66\u544A: \u5F53\u524D\u76EE\u5F55\u4E0D\u662F hai \u9879\u76EE"));
3039
+ writeCliMessage(chalk.yellow("\u8B66\u544A: \u5F53\u524D\u76EE\u5F55\u4E0D\u662F hai \u9879\u76EE"));
2803
3040
  }
2804
3041
  const resolvedOptions = await resolveGenerateOptions(options);
2805
3042
  const generator = GENERATORS[resolvedOptions.type];
@@ -2826,8 +3063,8 @@ async function generate(options) {
2826
3063
  while (!await fse.pathExists(ancestor))
2827
3064
  ancestor = path2.dirname(ancestor);
2828
3065
  const realAncestor = await fse.realpath(ancestor);
2829
- const relative = path2.relative(realCwd, realAncestor);
2830
- if (relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative))
3066
+ const relative2 = path2.relative(realCwd, realAncestor);
3067
+ if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2))
2831
3068
  throw new Error(cliM("cli_generateOutsideWorkspace"));
2832
3069
  }
2833
3070
  for (const file of files) {
@@ -2835,17 +3072,17 @@ async function generate(options) {
2835
3072
  await fse.writeFile(file.path, file.content, { encoding: "utf-8", flag: resolvedOptions.force ? "w" : "wx" });
2836
3073
  }
2837
3074
  spinner.succeed();
2838
- core.logger.info("", {});
2839
- core.logger.info(chalk.green("\u2714 \u751F\u6210\u5B8C\u6210\uFF01"));
2840
- core.logger.info("", {});
2841
- core.logger.info("\u521B\u5EFA\u7684\u6587\u4EF6:");
3075
+ writeCliMessage("");
3076
+ writeCliMessage(chalk.green("\u2714 \u751F\u6210\u5B8C\u6210\uFF01"));
3077
+ writeCliMessage("");
3078
+ writeCliMessage("\u521B\u5EFA\u7684\u6587\u4EF6:");
2842
3079
  files.forEach((file) => {
2843
- core.logger.info(chalk.cyan(` ${path2.relative(cwd, file.path)}`));
3080
+ writeCliMessage(chalk.cyan(` ${path2.relative(cwd, file.path)}`));
2844
3081
  });
2845
- core.logger.info("", {});
3082
+ writeCliMessage("");
2846
3083
  } catch (error) {
2847
3084
  spinner.fail();
2848
- core.logger.error(chalk.red("\u751F\u6210\u5931\u8D25:"), { error });
3085
+ writeCliError(chalk.red("\u751F\u6210\u5931\u8D25:"), { error });
2849
3086
  throw error;
2850
3087
  }
2851
3088
  }
@@ -3107,39 +3344,39 @@ async function initProject(options) {
3107
3344
  const { project, existing, missing } = result;
3108
3345
  spinner.succeed(`\u68C0\u6D4B\u5230\u9879\u76EE: ${chalk.cyan(project.name)}`);
3109
3346
  if (project.isHaiProject) {
3110
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-A5VIR5J6.js');
3347
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-FC3QRKDO.js');
3111
3348
  const generatedFiles = await generateProjectAiSupport(cwd, resolveSkillNamesForPackages(project.haiPackages), {
3112
3349
  overwriteSkills: false,
3113
3350
  overwriteBridgeFiles: false
3114
3351
  });
3115
3352
  for (const generatedFile of generatedFiles) {
3116
- core.logger.info(chalk.gray(` + ${generatedFile}`));
3353
+ writeCliMessage(chalk.gray(` + ${generatedFile}`));
3117
3354
  }
3118
3355
  }
3119
- core.logger.info("");
3120
- core.logger.info(chalk.bold("\u9879\u76EE\u72B6\u6001:"));
3121
- core.logger.info(` \u540D\u79F0: ${chalk.cyan(project.name)}`);
3122
- core.logger.info(` \u7248\u672C: ${project.version}`);
3123
- core.logger.info(` \u5DF2\u5B89\u88C5\u6A21\u5757: ${project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray("\u65E0")}`);
3124
- core.logger.info("");
3356
+ writeCliMessage("");
3357
+ writeCliMessage(chalk.bold("\u9879\u76EE\u72B6\u6001:"));
3358
+ writeCliMessage(` \u540D\u79F0: ${chalk.cyan(project.name)}`);
3359
+ writeCliMessage(` \u7248\u672C: ${project.version}`);
3360
+ writeCliMessage(` \u5DF2\u5B89\u88C5\u6A21\u5757: ${project.haiPackages.length > 0 ? project.haiPackages.map((p) => chalk.cyan(p)).join(", ") : chalk.gray("\u65E0")}`);
3361
+ writeCliMessage("");
3125
3362
  if (existing.length > 0) {
3126
- core.logger.info(chalk.bold("\u5DF2\u6709\u914D\u7F6E:"));
3363
+ writeCliMessage(chalk.bold("\u5DF2\u6709\u914D\u7F6E:"));
3127
3364
  for (const key of existing) {
3128
- core.logger.info(chalk.green(` \u2714 config/_${key}.yml`));
3365
+ writeCliMessage(chalk.green(` \u2714 config/_${key}.yml`));
3129
3366
  }
3130
3367
  }
3131
3368
  if (missing.length === 0 && !options.force) {
3132
- core.logger.info("");
3133
- core.logger.info(chalk.green("\u2714 \u6240\u6709\u914D\u7F6E\u6587\u4EF6\u5DF2\u5C31\u7EEA\uFF01"));
3369
+ writeCliMessage("");
3370
+ writeCliMessage(chalk.green("\u2714 \u6240\u6709\u914D\u7F6E\u6587\u4EF6\u5DF2\u5C31\u7EEA\uFF01"));
3134
3371
  return;
3135
3372
  }
3136
3373
  if (missing.length > 0) {
3137
- core.logger.info("");
3138
- core.logger.info(chalk.bold("\u7F3A\u5931\u914D\u7F6E:"));
3374
+ writeCliMessage("");
3375
+ writeCliMessage(chalk.bold("\u7F3A\u5931\u914D\u7F6E:"));
3139
3376
  for (const item of missing) {
3140
- core.logger.info(chalk.yellow(` \u2718 config/_${item.configKey}.yml (${item.label})`));
3377
+ writeCliMessage(chalk.yellow(` \u2718 config/_${item.configKey}.yml (${item.label})`));
3141
3378
  }
3142
- core.logger.info("");
3379
+ writeCliMessage("");
3143
3380
  spinner.start("\u751F\u6210\u7F3A\u5931\u914D\u7F6E\u6587\u4EF6...");
3144
3381
  let generated = 0;
3145
3382
  for (const item of missing) {
@@ -3151,7 +3388,7 @@ async function initProject(options) {
3151
3388
  spinner.succeed(`\u5DF2\u751F\u6210 ${generated} \u4E2A\u914D\u7F6E\u6587\u4EF6`);
3152
3389
  }
3153
3390
  if (options.force && existing.length > 0) {
3154
- core.logger.info("");
3391
+ writeCliMessage("");
3155
3392
  spinner.start("\u5F3A\u5236\u91CD\u65B0\u751F\u6210\u6240\u6709\u914D\u7F6E\u6587\u4EF6...");
3156
3393
  let regenerated = 0;
3157
3394
  for (const pkg of project.haiPackages) {
@@ -3172,16 +3409,16 @@ async function initProject(options) {
3172
3409
  await writeFile(coreConfigPath, content);
3173
3410
  spinner.succeed();
3174
3411
  }
3175
- core.logger.info("");
3176
- core.logger.info(chalk.green("\u2714 \u9879\u76EE\u914D\u7F6E\u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
3177
- core.logger.info("");
3178
- core.logger.info(chalk.gray("\u4E0B\u4E00\u6B65:"));
3179
- core.logger.info(chalk.gray(" 1. \u7F16\u8F91 config/ \u4E2D\u7684\u914D\u7F6E\u6587\u4EF6"));
3180
- core.logger.info(chalk.gray(" 2. \u8FD0\u884C pnpm dev \u542F\u52A8\u5F00\u53D1\u670D\u52A1"));
3181
- core.logger.info("");
3412
+ writeCliMessage("");
3413
+ writeCliMessage(chalk.green("\u2714 \u9879\u76EE\u914D\u7F6E\u521D\u59CB\u5316\u5B8C\u6210\uFF01"));
3414
+ writeCliMessage("");
3415
+ writeCliMessage(chalk.gray("\u4E0B\u4E00\u6B65:"));
3416
+ writeCliMessage(chalk.gray(" 1. \u7F16\u8F91 config/ \u4E2D\u7684\u914D\u7F6E\u6587\u4EF6"));
3417
+ writeCliMessage(chalk.gray(" 2. \u8FD0\u884C pnpm dev \u542F\u52A8\u5F00\u53D1\u670D\u52A1"));
3418
+ writeCliMessage("");
3182
3419
  } catch (error) {
3183
3420
  spinner.fail();
3184
- core.logger.error(chalk.red("\u521D\u59CB\u5316\u5931\u8D25:"), { error });
3421
+ writeCliError(chalk.red("\u521D\u59CB\u5316\u5931\u8D25:"), { error });
3185
3422
  throw error;
3186
3423
  }
3187
3424
  }
@@ -3189,9 +3426,9 @@ async function initProject(options) {
3189
3426
  // src/cli-main.ts
3190
3427
  var VERSION = getCliVersion();
3191
3428
  var cli = cac("hai");
3192
- cli.option("-v, --verbose", "\u663E\u793A\u8BE6\u7EC6\u8F93\u51FA");
3193
- cli.option("-C, --cwd <path>", "\u5DE5\u4F5C\u76EE\u5F55");
3194
- 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) => {
3429
+ cli.option("-v, --verbose", cliM("cli_help_verbose"));
3430
+ cli.option("-C, --cwd <path>", cliM("cli_help_cwd"));
3431
+ 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) => {
3195
3432
  try {
3196
3433
  const features = typeof options.features === "string" ? options.features.split(",").map((f) => f.trim()) : void 0;
3197
3434
  const frontends = typeof options.frontends === "string" ? options.frontends.split(",").map((f) => f.trim()).filter(Boolean) : void 0;
@@ -3201,6 +3438,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
3201
3438
  frontends,
3202
3439
  template: options.template,
3203
3440
  features,
3441
+ deploy: options.deploy,
3204
3442
  examples: options.examples,
3205
3443
  install: options.install,
3206
3444
  packageManager: options.packageManager,
@@ -3211,10 +3449,10 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
3211
3449
  cwd: options.cwd
3212
3450
  });
3213
3451
  } catch {
3214
- process3.exitCode = 1;
3452
+ process4.exitCode = 1;
3215
3453
  }
3216
3454
  });
3217
- 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) => {
3455
+ cli.command("add [module]", cliM("cli_help_add")).option("--no-install", cliM("cli_help_noInstall")).action(async (module, options) => {
3218
3456
  try {
3219
3457
  await addModule({
3220
3458
  module,
@@ -3223,10 +3461,10 @@ cli.command("add [module]", "\u5411\u73B0\u6709\u9879\u76EE\u6DFB\u52A0\u6A21\u5
3223
3461
  cwd: options.cwd
3224
3462
  });
3225
3463
  } catch {
3226
- process3.exit(1);
3464
+ process4.exit(1);
3227
3465
  }
3228
3466
  });
3229
- 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) => {
3467
+ cli.command("init", cliM("cli_help_init")).option("-f, --force", cliM("cli_help_forceConfig")).action(async (options) => {
3230
3468
  try {
3231
3469
  await initProject({
3232
3470
  force: options.force,
@@ -3234,10 +3472,10 @@ cli.command("init", "\u521D\u59CB\u5316/\u6821\u9A8C\u9879\u76EE\u914D\u7F6E").o
3234
3472
  cwd: options.cwd
3235
3473
  });
3236
3474
  } catch {
3237
- process3.exit(1);
3475
+ process4.exit(1);
3238
3476
  }
3239
3477
  });
3240
- 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) => {
3478
+ cli.command("generate [type] [name]", cliM("cli_help_generate")).alias("g").option("-o, --output <path>", cliM("cli_help_output")).option("-f, --force", cliM("cli_help_force")).action(async (type, name, options) => {
3241
3479
  try {
3242
3480
  await generate({
3243
3481
  type,
@@ -3248,10 +3486,18 @@ cli.command("generate [type] [name]", "\u751F\u6210\u4EE3\u7801").alias("g").opt
3248
3486
  cwd: options.cwd
3249
3487
  });
3250
3488
  } catch {
3251
- process3.exit(1);
3489
+ process4.exit(1);
3252
3490
  }
3253
3491
  });
3254
- 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\u4F7F\u7528\u5E94\u7528\u76EE\u5F55\u540D\uFF09").option("--skip-provision", "\u914D\u7F6E\u5DF2\u5B8C\u5907\uFF0C\u8DF3\u8FC7\u4E2D\u95F4\u4EF6\u914D\u7F6E\u4E0E\u5F00\u901A\uFF0C\u76F4\u63A5\u90E8\u7F72").option("--skip-middleware", "\u914D\u7F6E\u5DF2\u5B8C\u5907\uFF0C\u8DF3\u8FC7\u5168\u90E8\u4E2D\u95F4\u4EF6\u5904\u7406\uFF0C\u76F4\u63A5\u90E8\u7F72").option("--skip-build", "\u8DF3\u8FC7\u6784\u5EFA").action(async (appDir, options) => {
3492
+ 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) => {
3493
+ try {
3494
+ await buildCommand(options);
3495
+ } catch (error) {
3496
+ writeCliMessage(error instanceof Error ? error.message : String(error));
3497
+ process4.exitCode = 1;
3498
+ }
3499
+ });
3500
+ 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) => {
3255
3501
  try {
3256
3502
  await deployCommand({
3257
3503
  appDir,
@@ -3259,29 +3505,36 @@ cli.command("deploy [appDir]", "\u90E8\u7F72\u5E94\u7528\u5230\u4E91\u5E73\u53F0
3259
3505
  envDir: options.envDir,
3260
3506
  yes: options.yes,
3261
3507
  projectName: options.projectName,
3262
- skipProvision: options.skipProvision,
3263
3508
  skipMiddleware: options.skipMiddleware,
3264
- skipBuild: options.skipBuild,
3509
+ artifactDir: options.artifactDir,
3510
+ services: options.services,
3511
+ build: options.build,
3512
+ network: options.network,
3513
+ sqlitePrebuildMirror: options.sqlitePrebuildMirror,
3514
+ httpProxy: options.httpProxy,
3515
+ runtime: options.runtime,
3516
+ bundleOnly: options.bundleOnly,
3517
+ output: options.output,
3265
3518
  verbose: options.verbose,
3266
3519
  cwd: options.cwd
3267
3520
  });
3268
3521
  } catch {
3269
- process3.exit(1);
3522
+ process4.exitCode = 1;
3270
3523
  }
3271
3524
  });
3272
- cli.command("g:page <name>", "\u751F\u6210\u9875\u9762").action(async (name, options) => {
3525
+ cli.command("g:page <name>", cliM("cli_help_generatePage")).action(async (name, options) => {
3273
3526
  await generate({ type: "page", name, cwd: options.cwd });
3274
3527
  });
3275
- cli.command("g:component <name>", "\u751F\u6210\u7EC4\u4EF6").action(async (name, options) => {
3528
+ cli.command("g:component <name>", cliM("cli_help_generateComponent")).action(async (name, options) => {
3276
3529
  await generate({ type: "component", name, cwd: options.cwd });
3277
3530
  });
3278
- cli.command("g:api <name>", "\u751F\u6210 API").action(async (name, options) => {
3531
+ cli.command("g:api <name>", cliM("cli_help_generateApi")).action(async (name, options) => {
3279
3532
  await generate({ type: "api", name, cwd: options.cwd });
3280
3533
  });
3281
- cli.command("g:model <name>", "\u751F\u6210\u6570\u636E\u6A21\u578B").action(async (name, options) => {
3534
+ cli.command("g:model <name>", cliM("cli_help_generateModel")).action(async (name, options) => {
3282
3535
  await generate({ type: "model", name, cwd: options.cwd });
3283
3536
  });
3284
- cli.version(VERSION);
3537
+ cli.version(VERSION, "-V, --version");
3285
3538
  cli.help();
3286
3539
  var parsed = cli.parse();
3287
3540
  if (!cli.matchedCommand && !parsed.options.help && !parsed.options.version) {