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

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 (104) hide show
  1. package/README.en.md +20 -4
  2. package/README.ja.md +20 -4
  3. package/README.md +28 -12
  4. package/dist/chunk-47V5OT22.js +5639 -0
  5. package/dist/chunk-47V5OT22.js.map +1 -0
  6. package/dist/{chunk-X3RSYNBG.js → chunk-IPG3QNYK.js} +62 -4
  7. package/dist/chunk-IPG3QNYK.js.map +1 -0
  8. package/dist/cli-config-templates-SJ2MBIR7.js +5 -0
  9. package/dist/{cli-config-templates-QRF6B5T4.js.map → cli-config-templates-SJ2MBIR7.js.map} +1 -1
  10. package/dist/{cli-skill-templates-EZFW7D43.js → cli-skill-templates-A5VIR5J6.js} +3 -3
  11. package/dist/{cli-skill-templates-EZFW7D43.js.map → cli-skill-templates-A5VIR5J6.js.map} +1 -1
  12. package/dist/index.js +524 -66
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-DUGLX4YB.js → src-6PPBKQ6Q.js} +666 -290
  15. package/dist/src-6PPBKQ6Q.js.map +1 -0
  16. package/package.json +9 -7
  17. package/templates/apps/admin/src/routes/+layout.svelte.hbs +4 -2
  18. package/templates/apps/api/.dockerignore.hbs +5 -0
  19. package/templates/apps/api/.gitignore.hbs +4 -4
  20. package/templates/apps/api/Dockerfile.hbs +11 -1
  21. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +2 -0
  22. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +2 -2
  23. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +4 -4
  24. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +9 -2
  25. package/templates/apps/api/package.json.hbs +9 -0
  26. package/templates/apps/fullstack/.dockerignore.hbs +5 -3
  27. package/templates/apps/fullstack/.gitignore.hbs +22 -9
  28. package/templates/apps/fullstack/Dockerfile.hbs +11 -1
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +6 -0
  30. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +7 -3
  31. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +5 -0
  32. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +7 -3
  33. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +6 -0
  34. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +7 -3
  35. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +93 -1
  36. package/templates/apps/fullstack/package.json.hbs +12 -0
  37. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +2 -2
  38. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +2 -0
  39. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +1 -1
  40. package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +8 -14
  41. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +6 -4
  42. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -8
  43. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +3 -3
  44. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +9 -2
  45. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +4 -4
  46. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +6 -2
  47. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +2 -0
  48. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +19 -13
  49. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +1 -1
  50. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +46 -0
  51. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +17 -3
  52. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +15 -5
  53. package/templates/apps/fullstack/playwright.config.ts.hbs +6 -1
  54. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +0 -1
  55. package/templates/apps/h5/src/routes/+layout.svelte.hbs +13 -13
  56. package/templates/apps/mobile-app/src/App.svelte.hbs +46 -117
  57. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +58 -0
  58. package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -0
  59. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +16 -0
  60. package/templates/base/.dockerignore.hbs +5 -0
  61. package/templates/base/.gitignore.hbs +18 -4
  62. package/templates/base/Dockerfile.hbs +17 -1
  63. package/templates/base/e2e/app.spec.ts.hbs +14 -0
  64. package/templates/base/package.json.hbs +14 -2
  65. package/templates/base/tests/scaffold.test.ts.hbs +9 -3
  66. package/templates/base/vite.config.ts.hbs +6 -4
  67. package/templates/shared/hash-router.svelte.ts +83 -0
  68. package/templates/skills/AGENTS.md +15 -1
  69. package/templates/skills/bridges/admin/AGENTS.md +17 -1
  70. package/templates/skills/bridges/api/AGENTS.md +17 -3
  71. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +35 -11
  72. package/templates/skills/bridges/h5/AGENTS.md +17 -1
  73. package/templates/skills/bridges/mobile-app/AGENTS.md +19 -3
  74. package/templates/skills/bridges/website/AGENTS.md +17 -1
  75. package/templates/skills/hai-ai/reference.md +2 -2
  76. package/templates/skills/hai-app-create/SKILL.md +5 -3
  77. package/templates/skills/hai-app-create/development.md +126 -2
  78. package/templates/skills/hai-app-create/reference.md +2 -2
  79. package/templates/skills/hai-app-review/SKILL.md +2 -2
  80. package/templates/skills/hai-app-tests/SKILL.md +2 -1
  81. package/templates/skills/hai-app-tests/reference.md +1 -1
  82. package/templates/skills/hai-core/SKILL.md +2 -1
  83. package/templates/skills/hai-deploy/SKILL.md +42 -34
  84. package/templates/skills/hai-deploy/middleware.md +94 -0
  85. package/templates/skills/hai-framework-sync/SKILL.md +1 -1
  86. package/templates/skills/hai-framework-sync/sync.md +1 -1
  87. package/templates/skills/hai-reach/reference.md +8 -8
  88. package/templates/skills/hai-spec-implement/SKILL.md +12 -9
  89. package/templates/skills/hai-spec-implement/changes.md +67 -0
  90. package/templates/skills/hai-spec-implement/templates.md +25 -3
  91. package/templates/skills/hai-spec-implement/workflow.md +7 -6
  92. package/templates/skills/hai-spec-review/SKILL.md +14 -5
  93. package/templates/skills/hai-spec-review/checklist.md +4 -3
  94. package/templates/skills/hai-storage/SKILL.md +2 -0
  95. package/templates/skills/hai-ui/SKILL.md +4 -0
  96. package/dist/chunk-WW2MIO6T.js +0 -992
  97. package/dist/chunk-WW2MIO6T.js.map +0 -1
  98. package/dist/chunk-X3RSYNBG.js.map +0 -1
  99. package/dist/cli-config-templates-QRF6B5T4.js +0 -5
  100. package/dist/src-DUGLX4YB.js.map +0 -1
  101. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +0 -57
  102. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/AuditManagement.svelte.hbs → pages/audit.svelte.hbs} +0 -0
  103. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/RoleManagement.svelte.hbs → pages/roles.svelte.hbs} +0 -0
  104. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/UserManagement.svelte.hbs → pages/users.svelte.hbs} +0 -0
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { generateConfigFile } from './chunk-WW2MIO6T.js';
3
- import { cliM } from './chunk-X3RSYNBG.js';
2
+ import { generateConfigFile, runtimeEnvironmentVariables, renderEnvironment, moduleEnvironmentVariables, parameterizeConfig } from './chunk-47V5OT22.js';
3
+ import { cliM } from './chunk-IPG3QNYK.js';
4
4
  import './chunk-2H7UOFLK.js';
5
- import process4 from 'process';
5
+ import process3 from 'process';
6
6
  import { cac } from 'cac';
7
7
  import chalk from 'chalk';
8
- import path2, { resolve } from 'path';
9
- import { fileURLToPath } from 'url';
8
+ import path2, { resolve, join } from 'path';
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';
@@ -16,24 +16,25 @@ import prompts from 'prompts';
16
16
  import { Buffer } from 'buffer';
17
17
  import { execSync, execFileSync } from 'child_process';
18
18
  import { randomBytes } from 'crypto';
19
- import { stringify, parse } from 'yaml';
20
- import { existsSync, readFileSync } from 'fs';
19
+ import { stringify, parseDocument } from 'yaml';
20
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
21
+ import { createRequire } from 'module';
21
22
 
22
23
  var CAMEL_CASE_SEPARATOR_REGEX = /[-_\s]+(.)?/g;
23
24
  var LEADING_CHAR_REGEX = /^./;
24
25
  function writeCliMessage(message) {
25
- process4.stdout.write(`${message}
26
+ process3.stdout.write(`${message}
26
27
  `);
27
28
  }
28
29
  function writeCliError(message, error) {
29
- process4.stderr.write(`${message}
30
+ process3.stderr.write(`${message}
30
31
  `);
31
32
  if (error !== void 0)
32
- process4.stderr.write(`${error instanceof Error ? error.message : String(error)}
33
+ process3.stderr.write(`${error instanceof Error ? error.message : String(error)}
33
34
  `);
34
35
  }
35
36
  function formatPromptDescription(description) {
36
- return wrapAnsi(description, Math.max(1, (process4.stdout.columns || 80) - 32), { hard: true });
37
+ return wrapAnsi(description, Math.max(1, (process3.stdout.columns || 80) - 32), { hard: true });
37
38
  }
38
39
  function toCamelCase(str) {
39
40
  return str.replace(CAMEL_CASE_SEPARATOR_REGEX, (_, c) => c ? c.toUpperCase() : "").replace(LEADING_CHAR_REGEX, (c) => c.toLowerCase());
@@ -121,7 +122,7 @@ Handlebars2.registerHelper("yaml", (key, value) => stringify({ [key]: value }).t
121
122
  Handlebars2.registerHelper("jsString", (value) => `'${JSON.stringify(value).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'")}'`);
122
123
  Handlebars2.registerHelper("moduleConfig", function(moduleKey, enabled) {
123
124
  const content = generateConfigFile(moduleKey, this.moduleConfigs).trimEnd();
124
- const configured = this.appType === "fullstack" && moduleKey === "iam" ? content.replace("seedDefaultData: true", "seedDefaultData: false") : content;
125
+ const configured = this.appType === "fullstack" && moduleKey === "iam" ? content.replace("HAI_IAM_SEEDDEFAULTDATA:true", "HAI_IAM_SEEDDEFAULTDATA:false").replace("variant: card", "variant: split") : content;
125
126
  return enabled ? configured : `# # \u672A\u542F\u7528\uFF1A\u5B89\u88C5\u5BF9\u5E94\u6A21\u5757\u5E76\u5728\u670D\u52A1\u7AEF runtime \u4E2D\u521D\u59CB\u5316\uFF0C\u518D\u53D6\u6D88\u6240\u9700\u914D\u7F6E\u7684\u6CE8\u91CA\u3002
126
127
  ${configured.split("\n").map((line) => line ? `# ${line}` : "").join("\n")}`;
127
128
  });
@@ -465,7 +466,7 @@ function buildAppHaiCatalogPackages(options) {
465
466
  packageNames.push("@h-ai/capacitor");
466
467
  return pickCatalogPackages(packageNames, HAI_CATALOG_PACKAGES);
467
468
  }
468
- function buildFullstackCatalogPackages(frontends) {
469
+ function buildFullstackCatalogPackages(frontends, deploy = false) {
469
470
  const packageNames = [
470
471
  ...FULLSTACK_ROOT_CATALOG_PACKAGE_NAMES,
471
472
  ...FULLSTACK_CONTRACT_CATALOG_PACKAGE_NAMES,
@@ -478,6 +479,8 @@ function buildFullstackCatalogPackages(frontends) {
478
479
  packageNames.push(...FULLSTACK_APP_CATALOG_PACKAGE_NAMES);
479
480
  if (frontends.includes("desktop"))
480
481
  packageNames.push(...FULLSTACK_DESKTOP_CATALOG_PACKAGE_NAMES);
482
+ if (deploy)
483
+ packageNames.push("@h-ai/deploy");
481
484
  return pickCatalogPackages(packageNames, FULLSTACK_CATALOG_PACKAGES);
482
485
  }
483
486
  var FEATURE_ROUTE_DIRS = {
@@ -594,7 +597,7 @@ function buildTemplateContext(options) {
594
597
  const selectedFrontends = options.frontends ?? DEFAULT_FULLSTACK_FRONTENDS;
595
598
  let haiCatalogPackages;
596
599
  if (isFullstack) {
597
- haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends);
600
+ haiCatalogPackages = buildFullstackCatalogPackages(selectedFrontends, featureMap.deploy);
598
601
  } else if (isApi) {
599
602
  haiCatalogPackages = buildApiWorkspaceCatalogPackages(featureMap);
600
603
  } else {
@@ -769,6 +772,10 @@ async function generateFromTemplates(projectPath, context) {
769
772
  }
770
773
  }
771
774
  await renderDynamicFiles(root, projectPath, context);
775
+ if (context.isSvelteOnlyApp || context.appType === "fullstack") {
776
+ const navigationDir = context.appType === "fullstack" ? path2.join(projectPath, "packages", `${context.projectName}-shared`, "src", "lib", "navigation") : path2.join(projectPath, "src", "lib", "navigation");
777
+ await fse.copy(path2.join(root, "shared", "hash-router.svelte.ts"), path2.join(navigationDir, "hash-router.svelte.ts"));
778
+ }
772
779
  if (context.hasI18n) {
773
780
  const i18nDir = path2.join(root, "i18n");
774
781
  if (await fse.pathExists(i18nDir)) {
@@ -839,7 +846,8 @@ async function renderHbsInDir(dir, destRoot, context) {
839
846
  continue;
840
847
  }
841
848
  const destPath = path2.join(destRoot, outRelPath);
842
- const normalizedRendered = normalizeRenderedContent(rendered, outRelPath);
849
+ const configMatch = /(?:^|[/\\])config[/\\]_([^/\\]+)\.ya?ml$/.exec(outRelPath);
850
+ const normalizedRendered = normalizeRenderedContent(configMatch ? parameterizeConfig(configMatch[1], rendered) : rendered, outRelPath);
843
851
  await fse.ensureDir(path2.dirname(destPath));
844
852
  await fse.writeFile(destPath, normalizedRendered, "utf-8");
845
853
  }
@@ -1124,7 +1132,7 @@ async function createProject(options) {
1124
1132
  });
1125
1133
  await generateFromTemplates(projectPath, context);
1126
1134
  {
1127
- const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-QRF6B5T4.js');
1135
+ const { generateConfigFile: generateConfigFile2, generateEnvExample } = await import('./cli-config-templates-SJ2MBIR7.js');
1128
1136
  const configs = resolvedOptions.moduleConfigs;
1129
1137
  const isApiWorkspace = resolvedOptions.appType === "api";
1130
1138
  const isFullstack = resolvedOptions.appType === "fullstack";
@@ -1145,26 +1153,60 @@ async function createProject(options) {
1145
1153
  generateConfigFile2("core", configs)
1146
1154
  );
1147
1155
  }
1148
- const envExample = isApiWorkspace ? `${generateEnvExample(resolvedOptions.features, configs)}
1149
- # =============================================================================
1150
- # Service Runtime
1151
- # =============================================================================
1152
- PORT=3000
1153
- ` : isFullstack ? `${generateEnvExample(resolvedOptions.features, configs, true)}
1154
- # First-start administrator password. The CLI generates a random value in .env.
1156
+ const configContents = {};
1157
+ for (const file of await fse.readdir(configDir)) {
1158
+ if (/^_.*\.yml$/.test(file))
1159
+ configContents[file.slice(1, -4)] = await fse.readFile(path2.join(configDir, file), "utf8");
1160
+ }
1161
+ const envFeatures = resolvedOptions.features;
1162
+ const envContents = generateEnvExample(envFeatures, configs, isFullstack, configContents);
1163
+ let envExample = isApiWorkspace ? envContents : isFullstack ? `${envContents}
1164
+ # First-start administrator password; must satisfy the IAM password policy.
1155
1165
  HAI_ADMIN_DEFAULT_PASSWORD=
1156
1166
 
1157
- # Local development API. Use your public API origin for deployment/native clients.
1167
+ # Public API origin; never include credentials.
1158
1168
  PUBLIC_API_BASE=http://127.0.0.1:3000
1159
- ` : generateEnvExample(resolvedOptions.features, configs);
1169
+ ` : context.isSvelteOnlyApp ? `${envContents}
1170
+ # Public API URL including /api/v1; never include credentials.
1171
+ PUBLIC_API_BASE=http://10.0.2.2:3000/api/v1
1172
+ ` : envContents;
1173
+ const runtimeVariables = /* @__PURE__ */ new Map();
1174
+ for (const file of await fse.readdir(projectPath, { recursive: true, encoding: "utf8" })) {
1175
+ if (!/\.(?:ts|js|mjs|svelte|envsh)$/.test(file))
1176
+ continue;
1177
+ for (const variable of runtimeEnvironmentVariables(await fse.readFile(path2.join(projectPath, file), "utf8")))
1178
+ runtimeVariables.set(variable.name, variable);
1179
+ }
1180
+ const existingNames = new Set([...envExample.matchAll(/^(?:#\s*)?([A-Z][A-Z0-9_]*)=/gm)].map((match) => match[1]));
1181
+ const runtimeExample = renderEnvironment([...runtimeVariables.values()].filter((variable) => !existingNames.has(variable.name)));
1182
+ if (runtimeExample)
1183
+ envExample += `
1184
+ # \u53EF\u9009\u8FD0\u884C\u5165\u53E3\uFF08\u6309\u7528\u9014\u6CE8\u5165\uFF0C\u4E0D\u8FDB\u5165\u516C\u5171\u9ED8\u8BA4\u503C\uFF09\uFF1A
1185
+ ${runtimeExample.split("\n").map((line) => line && !line.startsWith("#") ? `# ${line}` : line).join("\n")}
1186
+ `;
1160
1187
  await writeFile(envExamplePath, envExample);
1188
+ const envDirectory = path2.dirname(envExamplePath);
1189
+ const sharedVariables = envExample.split("\n").filter((line) => /^[A-Z][A-Z0-9_]*=/.test(line));
1190
+ await writeFile(path2.join(envDirectory, ".env"), `# \u516C\u5171\u57FA\u7840\u914D\u7F6E\uFF08\u53EF\u63D0\u4EA4\uFF09\uFF1B\u5B8C\u6574\u5B57\u6BB5\u8BF4\u660E\u89C1 .env.example\u3002
1191
+ ${sharedVariables.join("\n")}
1192
+ `);
1193
+ for (const [mode, environment] of [["dev", "development"], ["test", "test"], ["prod", "production"]]) {
1194
+ await writeFile(path2.join(envDirectory, `.env.${mode}`), `# \u516C\u5171 ${mode} \u73AF\u5883\u914D\u7F6E\uFF1B\u672C\u673A\u79C1\u6709\u503C\u653E .env.local\uFF0C\u8FDB\u7A0B\u73AF\u5883\u53D8\u91CF\u4F18\u5148\u3002
1195
+ HAI_CORE_ENV=${environment}
1196
+ HAI_CORE_DEBUG=false
1197
+ HAI_CORE_LOGGING_LEVEL=${mode === "test" ? "warn" : mode === "dev" ? "debug" : "info"}
1198
+ `);
1199
+ }
1161
1200
  if (isFullstack) {
1162
1201
  const password = `Aa1!${randomBytes(24).toString("base64url")}`;
1163
- await fse.writeFile(path2.join(projectPath, ".env"), envExample.replace("HAI_ADMIN_DEFAULT_PASSWORD=\n", `HAI_ADMIN_DEFAULT_PASSWORD=${password}
1164
- `), { mode: 384 });
1202
+ await fse.writeFile(path2.join(projectPath, ".env.local"), `# \u672C\u673A\u79C1\u6709\u914D\u7F6E\uFF0C\u4E0D\u63D0\u4EA4 Git\u3002
1203
+ HAI_ADMIN_DEFAULT_PASSWORD=${password}
1204
+ `, { mode: 384 });
1205
+ } else {
1206
+ 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");
1165
1207
  }
1166
1208
  }
1167
- const { generateSkillFiles } = await import('./cli-skill-templates-EZFW7D43.js');
1209
+ const { generateSkillFiles } = await import('./cli-skill-templates-A5VIR5J6.js');
1168
1210
  await generateSkillFiles(
1169
1211
  projectPath,
1170
1212
  resolvedOptions.features,
@@ -1211,7 +1253,7 @@ async function resolveOptions(options) {
1211
1253
  writeCliMessage("");
1212
1254
  const onCancel = () => {
1213
1255
  writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1214
- process4.exit(1);
1256
+ process3.exit(1);
1215
1257
  };
1216
1258
  let projectName = options.name;
1217
1259
  if (!projectName && useDefaults) {
@@ -1277,7 +1319,7 @@ async function resolveOptions(options) {
1277
1319
  let selectedFeatures = [];
1278
1320
  const appTypeDefaults = APP_TYPES[selectedAppType].defaultFeatures;
1279
1321
  if (selectedAppType === "fullstack") {
1280
- selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
1322
+ selectedFeatures = resolveFeatureDependencies([...appTypeDefaults, ...options.features?.includes("deploy") ? ["deploy"] : []]);
1281
1323
  } else if (selectedTemplate === "custom" && !options.features && useDefaults) {
1282
1324
  selectedFeatures = resolveFeatureDependencies(appTypeDefaults);
1283
1325
  } else if (selectedTemplate === "custom" && !options.features) {
@@ -1467,7 +1509,7 @@ async function promptModuleConfigs(features, projectName) {
1467
1509
  const configs = {};
1468
1510
  const onCancel = () => {
1469
1511
  writeCliMessage(chalk.red("\n\u5DF2\u53D6\u6D88"));
1470
- process4.exit(1);
1512
+ process3.exit(1);
1471
1513
  };
1472
1514
  writeCliMessage("");
1473
1515
  writeCliMessage(chalk.bold(" \u2699 \u6A21\u5757\u914D\u7F6E"));
@@ -1685,16 +1727,34 @@ function generateAiDevelopmentReadme() {
1685
1727
 
1686
1728
  \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
1687
1729
 
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
+
1688
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
1689
1733
 
1690
- \u6839\u636E\u6587\u6863\u89C4\u5212\u3001\u5B9E\u65BD\u6216\u8FD4\u4FEE\u65F6\uFF0C\u4F7F\u7528 [\u6587\u6863\u5B9E\u65BD Skill](./.agents/skills/hai-spec-implement/SKILL.md)\uFF1A\u6309\u53EF\u9A8C\u6536\u529F\u80FD\u62C6\u5206\uFF0C\u7EF4\u62A4 docs/task-list.md \u4E0E docs/tasks/ \u8BB0\u5F55\uFF0C\u6BCF\u9879\u5728\u72EC\u7ACB\u5206\u652F\u8D70 TDD\u3001\u9A8C\u8BC1\u4E0E\u672C\u5730\u5408\u5E76\uFF1B\u8FD4\u4FEE\u6CBF\u7528\u539F\u4EFB\u52A1\u4E0E\u5206\u652F\u5E76\u8FFD\u52A0\u8BC1\u636E\u3002\u4EC5\u89C4\u5212\u4E0D\u5F00\u59CB\u5B9E\u73B0\uFF0C\u6307\u5B9A\u4EFB\u52A1\u5148\u6838\u9A8C\u4F9D\u8D56\u3002
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
1691
1735
 
1692
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
1693
1737
 
1694
- \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\u666E\u901A\u5F00\u53D1\u5165\u53E3\u4E0E\u89C4\u8303\u663E\u5F0F\u5408\u5E76\u5230\u65E7\u6307\u5F15\u3002
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
1695
1739
 
1696
1740
  `;
1697
1741
  }
1742
+ function generateEnvironmentReadme() {
1743
+ return `## \u5206\u73AF\u5883\u914D\u7F6E
1744
+
1745
+ \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
+
1747
+ | \u7528\u9014 | \u547D\u4EE4 |
1748
+ | --- | --- |
1749
+ | \u5F00\u53D1 / \u6D4B\u8BD5\u6A21\u5F0F / \u751F\u4EA7\u6A21\u5F0F\u9884\u89C8 | \`pnpm dev\` / \`dev:test\` / \`dev:prod\` |
1750
+ | \u7C7B\u578B\u68C0\u67E5\uFF08check \u4E3A\u522B\u540D\uFF09 | \`pnpm typecheck\` |
1751
+ | \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\` |
1753
+ | \u5355\u5143\u6D4B\u8BD5 / \u76D1\u542C / \u6D4F\u89C8\u5668\u9A8C\u6536 | \`pnpm test\` / \`test:watch\` / \`test:e2e\` |
1754
+ | Node \u670D\u52A1\u6309\u73AF\u5883\u542F\u52A8\uFF08\u6784\u5EFA\u540E\uFF09 | \`pnpm start\` / \`start:test\` / \`start:prod\` |
1755
+
1756
+ 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
+ }
1698
1758
  function generateReadme(name, appTypeLabel, pm, appType) {
1699
1759
  const install = getInstallCommand(pm);
1700
1760
  const isStaticImage = appType === "mobile-app";
@@ -1718,6 +1778,10 @@ ${getRunCommand(pm, "podman:build:data")}`;
1718
1778
 
1719
1779
  \u57FA\u4E8E hai Agent Framework \u6784\u5EFA\u7684${appTypeLabel}\u5E94\u7528\u3002
1720
1780
 
1781
+ ## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1782
+
1783
+ \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
+
1721
1785
  ## \u5F00\u53D1
1722
1786
 
1723
1787
  \`\`\`bash
@@ -1725,6 +1789,8 @@ ${install}
1725
1789
  ${getRunCommand(pm, "dev")}
1726
1790
  \`\`\`
1727
1791
 
1792
+ ${generateEnvironmentReadme()}
1793
+
1728
1794
  ## \u6784\u5EFA
1729
1795
 
1730
1796
  \`\`\`bash
@@ -1782,6 +1848,8 @@ ${pm} install
1782
1848
  ${pm} dev
1783
1849
  \`\`\`
1784
1850
 
1851
+ ${generateEnvironmentReadme()}
1852
+
1785
1853
  ## \u8D28\u91CF\u95E8\u7981
1786
1854
 
1787
1855
  \`\`\`bash
@@ -1883,11 +1951,17 @@ curl --fail http://127.0.0.1:8080/runtime-config.js
1883
1951
 
1884
1952
  \u5DF2\u542F\u7528\u524D\u7AEF\uFF1A${enabledFrontends || "Web / App / Desktop"}
1885
1953
 
1954
+ ## \u9875\u9762\u5E03\u5C40\u4E0E\u8DEF\u7531
1955
+
1956
+ 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
+
1886
1958
  ## \u9ED8\u8BA4\u7BA1\u7406\u529F\u80FD
1887
1959
 
1888
- \u9ED8\u8BA4\u542F\u7528 IAM\u3001reldb\u3001cache\u3001audit\u3001storage \u548C crypto\u3002\u7BA1\u7406\u5458\u53EF\u4EE5\u7BA1\u7406\u7528\u6237\uFF08\u65B0\u589E\u3001\u7F16\u8F91\u3001\u7981\u7528\u3001\u5220\u9664\u3001\u89D2\u8272\u5206\u914D\u3001\u5934\u50CF\u4E0A\u4F20\uFF09\u3001\u67E5\u770B\u4E24\u79CD\u56FA\u5B9A\u89D2\u8272\u53CA\u4EBA\u6570\u3001\u7EF4\u62A4\u89D2\u8272\u8BF4\u660E\u3001\u5206\u9875\u7B5B\u9009\u5BA1\u8BA1\u65E5\u5FD7\uFF1B\u666E\u901A\u7528\u6237\u53EA\u80FD\u7EF4\u62A4\u4E2A\u4EBA\u8D44\u6599\u3001\u5934\u50CF\u3001\u8BED\u8A00\u3001\u4E3B\u9898\u548C\u5BC6\u7801\u3002\u5BC6\u7801\u4FEE\u6539\u540E\u5168\u90E8\u4F1A\u8BDD\u5931\u6548\uFF0C\u9700\u8981\u91CD\u65B0\u767B\u5F55\u3002
1960
+ \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
+
1962
+ \u9ED8\u8BA4\u542F\u7528 IAM\u3001reldb\u3001cache\u3001audit\u3001storage \u548C crypto\u3002\u521D\u59CB\u7BA1\u7406\u5458\u53EF\u4EE5\u7BA1\u7406\u7528\u6237\uFF08\u65B0\u589E\u3001\u7F16\u8F91\u3001\u7981\u7528\u3001\u5220\u9664\u3001\u89D2\u8272\u5206\u914D\u3001\u5934\u50CF\u4E0A\u4F20\uFF09\u3001\u67E5\u770B admin/user \u89D2\u8272\u53CA\u4EBA\u6570\u3001\u7EF4\u62A4\u89D2\u8272\u8BF4\u660E\u3001\u5206\u9875\u7B5B\u9009\u5BA1\u8BA1\u65E5\u5FD7\uFF1B\u521D\u59CB\u666E\u901A\u7528\u6237\u8FDB\u5165\u4E2A\u4EBA\u8D44\u6599\u3001\u5934\u50CF\u3001\u8BED\u8A00\u3001\u4E3B\u9898\u548C\u5BC6\u7801\u8BBE\u7F6E\u3002\u9ED8\u8BA4\u5BC6\u7801\u4FEE\u6539\u540E\u5168\u90E8\u4F1A\u8BDD\u5931\u6548\uFF0C\u9700\u8981\u91CD\u65B0\u767B\u5F55\uFF1B\u89D2\u8272\u6A21\u578B\u3001\u8BBF\u95EE\u8303\u56F4\u548C\u4F1A\u8BDD\u7B56\u7565\u7531\u4E1A\u52A1\u9700\u6C42\u51B3\u5B9A\u3002
1889
1963
 
1890
- CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env\`\uFF0C\u5176\u4E2D \`HAI_ADMIN_DEFAULT_PASSWORD\` \u662F\u6BCF\u4E2A\u9879\u76EE\u72EC\u7ACB\u751F\u6210\u7684\u968F\u673A\u5F3A\u5BC6\u7801\u3002\u76F4\u63A5\u8FD0\u884C \`dev\` / \`start\` \u5373\u53EF\uFF0C\u9996\u6B21\u767B\u5F55\u4F7F\u7528\u7528\u6237\u540D \`admin\` \u548C\u8BE5\u5BC6\u7801\uFF1B\u4E5F\u53EF\u901A\u8FC7\u540C\u540D\u73AF\u5883\u53D8\u91CF\u8986\u76D6\u3002\u5BC6\u7801\u987B\u6EE1\u8DB3 \`_iam.yml\` \u7B56\u7565\uFF0C\u542F\u52A8\u5931\u8D25\u4F1A\u663E\u793A\u5177\u4F53\u6821\u9A8C\u539F\u56E0\u53CA\u9519\u8BEF\u7801\u3002\`.env\` \u5DF2\u88AB Git \u548C\u5BB9\u5668\u6784\u5EFA\u5FFD\u7565\uFF0C\`.env.example\` \u4E0D\u542B\u771F\u5B9E\u5BC6\u7801\uFF1B\u90E8\u7F72\u65F6\u8BBE\u7F6E\u76EE\u6807\u73AF\u5883\u81EA\u5DF1\u7684\u5BC6\u7801\u3002\u5DF2\u6709\u7528\u6237\u65F6\u4E0D\u4F1A\u91CD\u590D\u521B\u5EFA\u7BA1\u7406\u5458\u6216\u91CD\u8BBE\u5BC6\u7801\u3002
1964
+ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env.local\`\uFF0C\u5176\u4E2D \`HAI_ADMIN_DEFAULT_PASSWORD\` \u662F\u6BCF\u4E2A\u9879\u76EE\u72EC\u7ACB\u751F\u6210\u7684\u968F\u673A\u5F3A\u5BC6\u7801\u3002\u76F4\u63A5\u8FD0\u884C \`dev\` / \`start\` \u5373\u53EF\uFF0C\u9996\u6B21\u767B\u5F55\u4F7F\u7528\u7528\u6237\u540D \`admin\` \u548C\u8BE5\u5BC6\u7801\uFF1B\u4E5F\u53EF\u901A\u8FC7\u540C\u540D\u73AF\u5883\u53D8\u91CF\u8986\u76D6\u3002\u5BC6\u7801\u987B\u6EE1\u8DB3 \`_iam.yml\` \u7B56\u7565\uFF0C\u542F\u52A8\u5931\u8D25\u4F1A\u663E\u793A\u5177\u4F53\u6821\u9A8C\u539F\u56E0\u53CA\u9519\u8BEF\u7801\u3002\`.env.local\` \u5DF2\u88AB Git \u548C\u5BB9\u5668\u6784\u5EFA\u5FFD\u7565\uFF0C\`.env.example\` \u4E0D\u542B\u771F\u5B9E\u5BC6\u7801\uFF1B\u90E8\u7F72\u65F6\u8BBE\u7F6E\u76EE\u6807\u73AF\u5883\u81EA\u5DF1\u7684\u5BC6\u7801\u3002\u5DF2\u6709\u7528\u6237\u65F6\u4E0D\u4F1A\u91CD\u590D\u521B\u5EFA\u7BA1\u7406\u5458\u6216\u91CD\u8BBE\u5BC6\u7801\u3002
1891
1965
 
1892
1966
  \u672C\u5730\u5F00\u53D1\u9ED8\u8BA4 \`PUBLIC_API_BASE=http://127.0.0.1:3000\`\uFF0CVite \u4F7F\u7528\u540C\u6E90\u4EE3\u7406\uFF1B\u90E8\u7F72\u65F6\u6539\u4E3A\u5B9E\u9645\u516C\u7F51 API Origin\uFF0CAndroid \u6A21\u62DF\u5668\u8BBF\u95EE\u5BBF\u4E3B\u673A\u65F6\u4F7F\u7528 \`http://10.0.2.2:3000\`\u3002\u4E0D\u8981\u5C06\u5BC6\u94A5\u653E\u5165\u4EFB\u4F55 \`PUBLIC_\` \u6216 \`VITE_\` \u53D8\u91CF\u3002
1893
1967
 
@@ -1899,12 +1973,14 @@ CLI \u5DF2\u5728\u6839\u76EE\u5F55\u751F\u6210 \`.env\`\uFF0C\u5176\u4E2D \`HAI_
1899
1973
 
1900
1974
  \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
1901
1975
 
1902
- \u81EA\u52A8\u751F\u6210\u7684 \`.env\` \u9ED8\u8BA4\u53EA\u542F\u7528\u521D\u59CB\u7BA1\u7406\u5458\u5BC6\u7801\u548C \`PUBLIC_API_BASE\`\uFF0C\u6A21\u5757\u73AF\u5883\u8986\u76D6\u4FDD\u7559\u6CE8\u91CA\uFF0CYAML \u662F\u9ED8\u8BA4\u503C\u6765\u6E90\u3002\u9700\u8981\u73AF\u5883\u8986\u76D6\u65F6\u663E\u5F0F\u53D6\u6D88\u5BF9\u5E94\u53D8\u91CF\u7684\u6CE8\u91CA\uFF1B\u7F16\u8F91 YAML \u65F6\u540C\u6B65\u6838\u5BF9\u5DF2\u6709\u8986\u76D6\u3002
1976
+ \u81EA\u52A8\u751F\u6210\u7684 \`.env\` \u5305\u542B\u5DF2\u542F\u7528\u6A21\u5757\u7684\u73AF\u5883\u53C2\u6570\uFF1BYAML \u663E\u5F0F\u5F15\u7528\u76F8\u540C\u53D8\u91CF\u5E76\u4FDD\u7559\u672C\u5730\u56DE\u9000\u3002\u7F16\u8F91\u73AF\u5883\u53C2\u6570\u4F18\u5148\u4FEE\u6539\u5BF9\u5E94 \`.env\`\uFF1B\u672A\u542F\u7528\u6A21\u5757\u4ECD\u4FDD\u7559\u6CE8\u91CA\u3002
1903
1977
 
1904
1978
  \u6269\u5C55\u65F6\u5148\u5728\u670D\u52A1\u7AEF workspace \u5B89\u88C5\u9700\u8981\u7684\u5305\uFF0C\u5728 \`src/server/runtime.ts\` \u6309\u6A21\u5757\u516C\u5F00 Schema \u6821\u9A8C\u914D\u7F6E\u3001\u521D\u59CB\u5316\u5E76\u53CD\u5E8F\u5173\u95ED\uFF0C\u518D\u63A5\u5165 contract/procedure \u548C\u754C\u9762\u3002\u6574\u4EFD\u6CE8\u91CA\u6587\u4EF6\u5148\u53BB\u6389\u4E00\u5C42\u5916\u5C42\u6CE8\u91CA\uFF0C\u53EF\u9009\u5757\u7EE7\u7EED\u6309\u9700\u5F00\u542F\uFF1B\u914D\u7F6E\u6587\u4EF6\u5B58\u5728\u4E0D\u4EE3\u8868\u4E1A\u52A1\u529F\u80FD\u5DF2\u63A5\u5165\u3002\`crypto.init()\` \u65E0\u914D\u7F6E\u53C2\u6570\uFF0Cdatapipe \u4E3A\u7EAF\u51FD\u6570\uFF0C\u4E0D\u751F\u6210\u865A\u6784 YAML\u3002
1905
1979
 
1906
1980
  \u73AF\u5883\u53D8\u91CF\u53EA\u8986\u76D6 YAML \u5DF2\u6709\u8DEF\u5F84\uFF0C\u6CE8\u91CA\u952E\u987B\u5148\u542F\u7528\uFF0C\u6216\u7528 \`HAI_DB\` / \`HAI_CACHE\` \u7B49\u6574\u4F53\u5BF9\u8C61\u8986\u76D6\u3002camelCase \u4E0D\u62C6\u8BCD\uFF0C\u4F8B\u5982 \`connectTimeout\` \u5BF9\u5E94 \`HAI_CACHE_CONNECTTIMEOUT\`\u3002\u4FEE\u6539\u914D\u7F6E\u540E\u91CD\u542F\u670D\u52A1\uFF1B\u8BED\u8A00\u5217\u8868\u5728\u5404\u524D\u7AEF/shared \u7684 \`project.inlang/settings.json\` \u4E2D\u540C\u6B65\u3002\`**/data/\` \u5FFD\u7565\u4EFB\u610F\u5C42\u7EA7\u7684\u6570\u636E\u76EE\u5F55\u53CA \`.gitkeep\`\uFF0C\u8FD0\u884C\u6570\u636E\u4E0D\u5165 Git\uFF0C\u8FD0\u884C\u65F6\u81EA\u52A8\u521B\u5EFA\u76EE\u5F55\u3002
1907
1981
 
1982
+ ${generateEnvironmentReadme()}
1983
+
1908
1984
  ## \u5F00\u53D1\u3001\u7F16\u8BD1\u4E0E\u6253\u5305
1909
1985
 
1910
1986
  \`\`\`bash
@@ -1942,7 +2018,7 @@ ${webBuildCommands}
1942
2018
 
1943
2019
  \`\`\`bash
1944
2020
  # Service\uFF1B\u4F7F\u7528 Podman \u65F6\u5C06 docker \u66FF\u6362\u4E3A podman
1945
- docker run --rm -d --name ${name} -p 3000:3000 --env-file .env \\
2021
+ docker run --rm -d --name ${name} -p 3000:3000 --env-file .env --env-file .env.prod --env-file .env.local \\
1946
2022
  --mount type=bind,source=/absolute/path/to/config,target=/app/config,readonly \\
1947
2023
  --mount type=volume,source=${name}-data,target=/app/data \\
1948
2024
  ${name}:latest
@@ -1957,6 +2033,7 @@ curl --fail http://127.0.0.1:3000/ready
1957
2033
  ${webTestCommands}docker stop ${containerNames}
1958
2034
  \`\`\`
1959
2035
 
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
1960
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
1961
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
1962
2039
  - Service \u7531 \`@h-ai/serv\` \u76F4\u63A5\u5BF9\u5916\u76D1\u542C\uFF0C\u4E0D\u989D\u5916\u542F\u52A8 Web \u8F6C\u53D1\u5C42\u3002
@@ -2118,7 +2195,7 @@ async function addModule(options) {
2118
2195
  core.logger.info(`\u53EF\u7528\u6A21\u5757: ${Object.keys(MODULE_MAP).join(", ")}`);
2119
2196
  return;
2120
2197
  }
2121
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-EZFW7D43.js');
2198
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-A5VIR5J6.js');
2122
2199
  if (moduleDef.packages.every((p) => project.haiPackages.includes(p))) {
2123
2200
  const generatedFiles2 = await generateProjectAiSupport(
2124
2201
  cwd,
@@ -2224,21 +2301,355 @@ async function addModule(options) {
2224
2301
  throw error;
2225
2302
  }
2226
2303
  }
2304
+ function displayDeployValue(name, value) {
2305
+ 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
+ return cliM("cli_deploy_secret");
2307
+ try {
2308
+ const parsed2 = JSON.parse(value);
2309
+ if (parsed2 && typeof parsed2 === "object") {
2310
+ const sanitize = (object) => {
2311
+ if (Array.isArray(object))
2312
+ return object.map(sanitize);
2313
+ if (object && typeof object === "object")
2314
+ return Object.fromEntries(Object.entries(object).map(([key, item]) => [key, /PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(key.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(key) ? cliM("cli_deploy_secret") : typeof item === "string" ? displayDeployValue(key, item) : sanitize(item)]));
2315
+ return object;
2316
+ };
2317
+ return JSON.stringify(sanitize(parsed2));
2318
+ }
2319
+ } catch {
2320
+ }
2321
+ try {
2322
+ const url = new URL(value);
2323
+ if (url.username || url.password) {
2324
+ url.username = "***";
2325
+ url.password = "***";
2326
+ }
2327
+ for (const key of url.searchParams.keys()) {
2328
+ if (/password|secret|token|key|authorization/i.test(key))
2329
+ url.searchParams.set(key, "***");
2330
+ }
2331
+ return url.toString();
2332
+ } catch {
2333
+ return value;
2334
+ }
2335
+ }
2336
+ var CLOUD_FIELDS = {
2337
+ "neon": ["apiKey"],
2338
+ "upstash": ["email", "apiKey"],
2339
+ "cloudflare-r2": ["accountId", "apiToken", "accessKeyId", "secretAccessKey"],
2340
+ "resend": ["apiKey", "from"],
2341
+ "aliyun": ["accessKeyId", "accessKeySecret", "signName"]
2342
+ };
2343
+ function targetConfig(requirement, environment) {
2344
+ const result = {};
2345
+ const parent = environment.targetValues[requirement.envPrefix];
2346
+ if (parent) {
2347
+ try {
2348
+ Object.assign(result, JSON.parse(parent));
2349
+ } catch {
2350
+ return {};
2351
+ }
2352
+ }
2353
+ for (const [key, value] of Object.entries(environment.targetValues)) {
2354
+ if (key.startsWith(`${requirement.envPrefix}_`))
2355
+ result[key.slice(requirement.envPrefix.length + 1).toLowerCase()] = value;
2356
+ }
2357
+ return result;
2358
+ }
2359
+ function canKeepMiddleware(requirement, backend, environment) {
2360
+ const target = targetConfig(requirement, environment);
2361
+ if (requirement.service === "ldap" && backend.backend === "disabled")
2362
+ return environment.targetValues.HAI_IAM_LOGIN_LDAP === "false";
2363
+ if (requirement.service === "ldap") {
2364
+ if (!Object.keys(target).length)
2365
+ return false;
2366
+ } else if (target.type !== backend.backend) {
2367
+ return false;
2368
+ }
2369
+ return backend.fields.filter((field) => field.required && !field.secret).every((field) => {
2370
+ if ((target.url || requirement.config.url && Object.keys(target).length) && ["host", "port"].includes(field.name))
2371
+ return true;
2372
+ return target[field.name] !== void 0 && target[field.name] !== "" || target[field.name.toLowerCase()] !== void 0 && target[field.name.toLowerCase()] !== "";
2373
+ }) && backend.fields.filter((field) => field.required && field.secret).every((field) => Boolean(requirement.config[field.name] || environment.values[`${requirement.envPrefix}_${field.name.toUpperCase()}`]));
2374
+ }
2375
+ async function ask(question) {
2376
+ const answer = await prompts({ ...question, name: "value" });
2377
+ if (answer.value === void 0)
2378
+ throw new Error(cliM("cli_deploy_cancelled"));
2379
+ return answer.value;
2380
+ }
2381
+ function validateField(value, field) {
2382
+ if (value === "")
2383
+ return field.required ? cliM("cli_deploy_required") : true;
2384
+ if (field.kind === "port")
2385
+ return /^\d+$/.test(value) && Number(value) >= 1 && Number(value) <= 65535 || cliM("cli_deploy_invalid_value");
2386
+ if (field.kind === "number")
2387
+ return /^\d+$/.test(value) && Number(value) >= (field.minimum ?? 0) && Number(value) <= (field.maximum ?? Number.MAX_SAFE_INTEGER) || cliM("cli_deploy_invalid_value");
2388
+ if (field.kind === "boolean")
2389
+ return ["true", "false"].includes(value) || cliM("cli_deploy_invalid_value");
2390
+ if (field.kind === "url") {
2391
+ try {
2392
+ return Boolean(new URL(value).protocol);
2393
+ } catch {
2394
+ return cliM("cli_deploy_invalid_value");
2395
+ }
2396
+ }
2397
+ if (field.name === "ssl") {
2398
+ const parsed2 = parseDocument(value);
2399
+ const ssl = parsed2.errors.length ? void 0 : parsed2.toJS();
2400
+ return typeof ssl === "boolean" || ["require", "prefer", "allow", "disable"].includes(String(ssl)) || ssl !== null && typeof ssl === "object" && !Array.isArray(ssl) || cliM("cli_deploy_invalid_value");
2401
+ }
2402
+ if (field.name === "headers") {
2403
+ try {
2404
+ const headers = JSON.parse(value);
2405
+ return headers !== null && typeof headers === "object" && !Array.isArray(headers) && Object.values(headers).every((item) => typeof item === "string") || cliM("cli_deploy_invalid_value");
2406
+ } catch {
2407
+ return cliM("cli_deploy_invalid_value");
2408
+ }
2409
+ }
2410
+ if (field.name === "method")
2411
+ return ["POST", "PUT"].includes(value) || cliM("cli_deploy_invalid_value");
2412
+ return true;
2413
+ }
2414
+ function ensureConfigNode(appDir, requirement, backend) {
2415
+ if (requirement.service === "ldap" && backend === "disabled")
2416
+ return;
2417
+ const directory = join(appDir, "config");
2418
+ mkdirSync(directory, { recursive: true });
2419
+ const path7 = join(directory, `_${requirement.configName}.yml`);
2420
+ const document = parseDocument(existsSync(path7) ? readFileSync(path7, "utf8") : "{}\n");
2421
+ if (document.errors.length)
2422
+ throw new Error(cliM("cli_deploy_invalid_value"));
2423
+ if (requirement.configPath.length && !document.hasIn(requirement.configPath))
2424
+ document.setIn(requirement.configPath, requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service, type: backend } : {});
2425
+ if (requirement.service === "ldap") {
2426
+ if (!document.hasIn(["login"]))
2427
+ document.setIn(["login"], {});
2428
+ if (!document.hasIn(["login", "ldap"]))
2429
+ document.setIn(["login", "ldap"], `\${HAI_IAM_LOGIN_LDAP:false}`);
2430
+ }
2431
+ const content = document.toString();
2432
+ if (!existsSync(path7) || readFileSync(path7, "utf8") !== content)
2433
+ writeFileSync(path7, content);
2434
+ }
2435
+ function connectionEnv(requirement, backend, config) {
2436
+ if (requirement.service === "ldap")
2437
+ return backend === "disabled" ? { HAI_IAM_LOGIN_LDAP: "false" } : { HAI_IAM_LOGIN_LDAP: "true", [requirement.envPrefix]: JSON.stringify(config) };
2438
+ return { [requirement.envPrefix]: JSON.stringify({ ...config, type: backend }) };
2439
+ }
2440
+ async function configureDeployMiddleware(module, scan, environment, config, yes = false) {
2441
+ const modes = {};
2442
+ const services = {};
2443
+ const existingServices = config.services && typeof config.services === "object" ? config.services : {};
2444
+ const provider = config.provider;
2445
+ for (const requirement of scan.middlewares ?? []) {
2446
+ const choices = module.DEPLOY_MIDDLEWARE[requirement.service];
2447
+ const current = choices.find((item) => item.backend === requirement.backend);
2448
+ if (!current)
2449
+ 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 } }));
2451
+ const canKeep = canKeepMiddleware(requirement, current, environment);
2452
+ const mode = yes ? canKeep ? "keep" : "default" : await ask({
2453
+ type: "select",
2454
+ name: "value",
2455
+ message: cliM("cli_deploy_action", { params: { service: requirement.service } }),
2456
+ choices: [
2457
+ { title: cliM("cli_deploy_default"), value: "default" },
2458
+ { title: cliM("cli_deploy_custom"), value: "custom" },
2459
+ ...canKeep ? [{ title: cliM("cli_deploy_keep"), value: "keep" }] : []
2460
+ ]
2461
+ });
2462
+ modes[requirement.envPrefix] = mode;
2463
+ const previous = modes[requirement.service];
2464
+ modes[requirement.service] = previous === "default" || mode === "default" ? "default" : previous === "custom" || mode === "custom" ? "custom" : "keep";
2465
+ if (mode === "keep") {
2466
+ if (!canKeep)
2467
+ throw new Error(cliM("cli_deploy_missing_keep", { params: { service: requirement.service } }));
2468
+ continue;
2469
+ }
2470
+ const backendName = yes ? current.backend : String(await ask({ type: "select", name: "value", message: cliM("cli_deploy_backend", { params: { service: requirement.service } }), initial: choices.indexOf(current), choices: choices.map((item) => ({ title: `${item.label} \u2014 ${module.describeMiddlewareBackend(item)}`, value: item.backend })) }));
2471
+ const backend = choices.find((item) => item.backend === backendName);
2472
+ if (!backend)
2473
+ throw new Error(cliM("cli_deploy_invalid_value"));
2474
+ requirement.backend = backendName;
2475
+ const updates = {};
2476
+ if (mode === "default" && backendName === "s3" && provider?.type === "docker-ssh") {
2477
+ const container = config.container && typeof config.container === "object" ? config.container : {};
2478
+ const currentEngine = container.s3Engine === "minio" ? "minio" : "rustfs";
2479
+ const engine = yes ? currentEngine : String(await ask({ type: "select", name: "value", message: cliM("cli_deploy_s3_engine"), initial: currentEngine === "rustfs" ? 0 : 1, choices: [{ title: "RustFS", value: "rustfs" }, { title: "MinIO", value: "minio" }] }));
2480
+ if (!["rustfs", "minio"].includes(engine))
2481
+ throw new Error(cliM("cli_deploy_invalid_value"));
2482
+ config.container = { ...container, s3Engine: engine };
2483
+ updates.HAI_DEPLOY_CONTAINER_S3ENGINE = engine;
2484
+ const deploymentFile = join(scan.appDir ?? ".", "config", "_deploy.yml");
2485
+ const deployment = parseDocument(readFileSync(deploymentFile, "utf8"));
2486
+ if (deployment.errors.length)
2487
+ throw new Error(cliM("cli_deploy_invalid_value"));
2488
+ if (!deployment.hasIn(["container", "s3Engine"])) {
2489
+ if (!deployment.hasIn(["container"]))
2490
+ deployment.setIn(["container"], {});
2491
+ deployment.setIn(["container", "s3Engine"], `\${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}`);
2492
+ writeFileSync(deploymentFile, deployment.toString());
2493
+ }
2494
+ }
2495
+ if (mode === "default" && provider?.type === "vercel") {
2496
+ if (!backend.provisioner)
2497
+ throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: backend.label } }));
2498
+ const prior = existingServices[requirement.service];
2499
+ const priorConfig = prior && typeof prior === "object" ? prior : {};
2500
+ const cloud = { provisioner: backend.provisioner };
2501
+ for (const name of CLOUD_FIELDS[backend.provisioner]) {
2502
+ const key = `HAI_DEPLOY_SERVICES_${requirement.service.toUpperCase()}_${name.toUpperCase()}`;
2503
+ 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") }));
2505
+ if (!value)
2506
+ throw new Error(`${key}: ${cliM("cli_deploy_required")}`);
2507
+ cloud[name] = value;
2508
+ updates[key] = value;
2509
+ }
2510
+ services[requirement.service] = cloud;
2511
+ } else {
2512
+ if (mode === "default" && (requirement.service === "sms" || backendName === "api"))
2513
+ throw new Error(cliM("cli_deploy_cloud_unsupported", { params: { backend: backend.label } }));
2514
+ const connection = mode === "custom" && backendName === current.backend ? { ...requirement.config } : requirement.configName === "reach" ? { name: requirement.config.name ?? requirement.service } : {};
2515
+ for (const field of backend.fields) {
2516
+ const previous2 = backendName === current.backend ? requirement.config[field.name] : void 0;
2517
+ const initial = previous2 === void 0 ? field.defaultValue ?? "" : typeof previous2 === "object" ? JSON.stringify(previous2) : String(previous2);
2518
+ const variable = `${requirement.envPrefix}_${field.name.toUpperCase()}`;
2519
+ const info = moduleEnvironmentVariables(requirement.configName).find((item) => item.name === variable);
2520
+ const value = mode === "custom" && !yes ? String(await ask({ type: field.secret ? "password" : "text", name: "value", message: cliM("cli_deploy_field", { params: { name: variable, description: info?.description ?? field.name, constraint: info?.constraint ?? field.kind ?? "string" } }), initial, validate: (input) => validateField(String(input), field) })) : initial;
2521
+ if (value !== "") {
2522
+ connection[field.name] = field.kind === "number" || field.kind === "port" ? Number(value) : field.kind === "boolean" ? value === "true" : field.name === "headers" || field.name === "ssl" ? parseDocument(value).toJS() : value;
2523
+ } else {
2524
+ delete connection[field.name];
2525
+ }
2526
+ }
2527
+ Object.assign(updates, connectionEnv(requirement, backendName, connection));
2528
+ }
2529
+ ensureConfigNode(scan.appDir ?? ".", requirement, backendName);
2530
+ const saved = module.saveDeploymentEnvironment(environment, updates);
2531
+ if (!saved.success)
2532
+ throw new Error(saved.error.message);
2533
+ for (const [name, value] of Object.entries(updates)) {
2534
+ core.logger.info(cliM("cli_deploy_saved", { params: { name, value: displayDeployValue(name, value) } }));
2535
+ }
2536
+ }
2537
+ config.services = Object.keys(services).length ? services : void 0;
2538
+ return modes;
2539
+ }
2540
+
2541
+ // src/commands/cli-deploy.ts
2542
+ function configObject(value) {
2543
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2544
+ }
2545
+ async function configureDeployTarget(module, config, environment, nonInteractive) {
2546
+ const current = module.ProviderConfigSchema.safeParse(config.provider);
2547
+ if (current.success) {
2548
+ 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" } }));
2550
+ return;
2551
+ }
2552
+ if (nonInteractive)
2553
+ throw new Error(cliM("cli_deploy_target_missing", { params: { fields: current.error.issues.map((issue) => `provider.${issue.path.join(".")}`).join(", ") } }));
2554
+ const original = configObject(config.provider);
2555
+ const platform = await prompts({
2556
+ type: "select",
2557
+ name: "value",
2558
+ message: cliM("cli_deploy_target_platform"),
2559
+ choices: [{ title: cliM("cli_deploy_target_ssh"), value: "docker-ssh" }, { title: "Vercel", value: "vercel" }],
2560
+ initial: original.type === "vercel" ? 1 : 0
2561
+ });
2562
+ if (!platform.value)
2563
+ throw new Error(cliM("cli_deploy_cancelled"));
2564
+ const input = async (name, initial, options = {}) => {
2565
+ const answer = await prompts({
2566
+ type: options.secret ? "password" : "text",
2567
+ name: "value",
2568
+ message: cliM("cli_deploy_target_field", { params: { name } }),
2569
+ 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")
2571
+ });
2572
+ if (answer.value === void 0)
2573
+ throw new Error(cliM("cli_deploy_cancelled"));
2574
+ return String(answer.value).trim();
2575
+ };
2576
+ let provider;
2577
+ if (platform.value === "docker-ssh") {
2578
+ core.logger.info(cliM("cli_deploy_target_ssh_hint"));
2579
+ const ssh = configObject(original.ssh);
2580
+ const remote = configObject(original.remote);
2581
+ const expose = configObject(original.expose);
2582
+ const host = await input("HAI_DEPLOY_PROVIDER_SSH_HOST", ssh.host);
2583
+ const port = Number(await input("HAI_DEPLOY_PROVIDER_SSH_PORT", ssh.port ?? 22, { port: true }));
2584
+ const username = await input("HAI_DEPLOY_PROVIDER_SSH_USERNAME", ssh.username);
2585
+ const identityFile = await input("HAI_DEPLOY_PROVIDER_SSH_IDENTITYFILE", ssh.identityFile, { optional: true });
2586
+ const knownHostsFile = await input("HAI_DEPLOY_PROVIDER_SSH_KNOWNHOSTSFILE", ssh.knownHostsFile, { optional: true });
2587
+ const baseDir = await input("HAI_DEPLOY_PROVIDER_REMOTE_BASEDIR", remote.baseDir || "/opt/hai/apps");
2588
+ const runtime = await prompts({ type: "select", name: "value", message: cliM("cli_deploy_target_runtime"), choices: ["auto", "podman", "docker"].map((value) => ({ title: value, value })), initial: ["auto", "podman", "docker"].indexOf(String(remote.runtime ?? "auto")) });
2589
+ if (!runtime.value)
2590
+ throw new Error(cliM("cli_deploy_cancelled"));
2591
+ const hostPort = Number(await input("HAI_DEPLOY_PROVIDER_EXPOSE_HOSTPORT", expose.hostPort || 3e3, { port: true }));
2592
+ const containerPort = Number(await input("HAI_DEPLOY_PROVIDER_EXPOSE_CONTAINERPORT", expose.containerPort || 3e3, { port: true }));
2593
+ provider = {
2594
+ type: "docker-ssh",
2595
+ ssh: { ...ssh, host, port, username, identityFile: identityFile || void 0, knownHostsFile: knownHostsFile || void 0 },
2596
+ remote: { ...remote, baseDir, runtime: runtime.value },
2597
+ expose: { type: "port", hostPort, containerPort }
2598
+ };
2599
+ } else {
2600
+ provider = { type: "vercel", token: await input("HAI_DEPLOY_PROVIDER_TOKEN", void 0, { secret: true }) };
2601
+ }
2602
+ const validated = module.ProviderConfigSchema.safeParse(provider);
2603
+ if (!validated.success)
2604
+ throw new Error(cliM("cli_deploy_invalid_value"));
2605
+ const saved = module.saveDeploymentEnvironment(environment, { HAI_DEPLOY_PROVIDER: JSON.stringify(validated.data) }, { private: true });
2606
+ if (!saved.success)
2607
+ throw new Error(saved.error.message);
2608
+ config.provider = validated.data;
2609
+ core.logger.info(cliM("cli_deploy_target_saved"));
2610
+ }
2227
2611
  async function deployCommand(options) {
2228
2612
  const spinner = ora();
2229
- const cwd = options.cwd ?? process4.cwd();
2230
- const appDir = resolve(cwd, options.appDir ?? ".");
2613
+ const cwd = options.cwd ?? process3.cwd();
2614
+ let appDir = resolve(cwd, options.appDir ?? ".");
2231
2615
  let closeDeploy = null;
2616
+ const originalEnv = { ...process3.env };
2232
2617
  try {
2618
+ let environmentName = options.env;
2619
+ if (!environmentName) {
2620
+ if (!process3.stdin.isTTY)
2621
+ throw new Error(cliM("cli_deploy_environment_hint"));
2622
+ const answer = await prompts({ type: "text", name: "environment", message: cliM("cli_deploy_environment"), initial: "prod" });
2623
+ environmentName = answer.environment;
2624
+ if (!environmentName)
2625
+ throw new Error(cliM("cli_deploy_cancelled"));
2626
+ }
2627
+ if (!options.yes && !process3.stdin.isTTY)
2628
+ throw new Error(cliM("cli_deploy_environment_hint"));
2233
2629
  let deployModule;
2234
2630
  try {
2235
- deployModule = await import('./src-DUGLX4YB.js');
2631
+ const localRequire = createRequire(resolve(appDir, "package.json"));
2632
+ let localModule;
2633
+ try {
2634
+ localModule = localRequire.resolve("@h-ai/deploy");
2635
+ } catch {
2636
+ }
2637
+ deployModule = localModule ? await import(pathToFileURL(localModule).href) : await import('./src-6PPBKQ6Q.js');
2236
2638
  } catch {
2237
2639
  core.logger.error(chalk.red(cliM("cli_deploy_module_missing")));
2238
2640
  core.logger.info(chalk.cyan(" pnpm add @h-ai/deploy"));
2239
2641
  throw new Error(cliM("cli_deploy_module_missing"));
2240
2642
  }
2241
2643
  const { deploy } = deployModule;
2644
+ const scanResult = await deploy.scan(appDir, { environment: environmentName, envDir: options.envDir ? resolve(cwd, options.envDir) : void 0 });
2645
+ if (!scanResult.success)
2646
+ throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
2647
+ const scan = scanResult.data;
2648
+ appDir = scan.appDir ?? appDir;
2649
+ const environment = deployModule.loadDeploymentEnvironment({ environment: environmentName, envDir: scan.envDir ?? appDir });
2650
+ if (!environment.success)
2651
+ throw new Error(environment.error.message);
2652
+ Object.assign(process3.env, environment.data.values);
2242
2653
  spinner.start(cliM("cli_deploy_load_credentials"));
2243
2654
  const credResult = deploy.credentials.load();
2244
2655
  if (!credResult.success) {
@@ -2252,19 +2663,44 @@ async function deployCommand(options) {
2252
2663
  core.logger.info(chalk.cyan(cliM("cli_deploy_config_hint")));
2253
2664
  throw new Error(cliM("cli_deploy_config_missing", { params: { path: configPath } }));
2254
2665
  }
2255
- const configContent = readFileSync(configPath, "utf-8");
2256
- const rawConfig = interpolateEnvFallback(configContent);
2257
- const deployConfig = parse(rawConfig);
2666
+ const loadedConfig = deployModule.loadDeploymentConfig("deploy", configPath, process3.env);
2667
+ if (!loadedConfig.success)
2668
+ throw new Error(loadedConfig.error.message);
2669
+ const deployConfig = loadedConfig.data;
2258
2670
  spinner.succeed(cliM("cli_deploy_config_loaded"));
2259
- spinner.start(cliM("cli_deploy_scanning"));
2260
- const scanResult = await deploy.scan(appDir);
2261
- if (!scanResult.success) {
2262
- throw new Error(cliM("cli_deploy_scan_failed", { params: { message: scanResult.error.message } }));
2263
- }
2264
- const scan = scanResult.data;
2671
+ await configureDeployTarget(deployModule, deployConfig, environment.data, options.yes);
2265
2672
  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;
2674
+ if (!skipMiddleware && !options.yes && scan.requiredServices.length) {
2675
+ const action = await prompts({
2676
+ type: "select",
2677
+ name: "value",
2678
+ message: cliM("cli_deploy_middleware_plan"),
2679
+ choices: [{ title: cliM("cli_deploy_middleware_configure"), value: "configure" }, { title: cliM("cli_deploy_middleware_skip"), value: "skip" }]
2680
+ });
2681
+ if (!action.value)
2682
+ throw new Error(cliM("cli_deploy_cancelled"));
2683
+ skipMiddleware = action.value === "skip";
2684
+ }
2685
+ 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);
2688
+ if (skipMiddleware)
2689
+ deployConfig.services = void 0;
2690
+ for (const key of Object.keys(process3.env)) {
2691
+ if (originalEnv[key] === void 0)
2692
+ delete process3.env[key];
2693
+ }
2694
+ Object.assign(process3.env, originalEnv);
2695
+ const refreshed = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
2696
+ if (!refreshed.success)
2697
+ throw new Error(refreshed.error.message);
2698
+ Object.assign(process3.env, refreshed.data.values);
2266
2699
  spinner.start(cliM("cli_deploy_initializing"));
2267
- const initResult = await deploy.init(deployConfig);
2700
+ const validatedConfig = deployModule.DeployConfigSchema.safeParse(deployConfig);
2701
+ if (!validatedConfig.success)
2702
+ throw new Error(cliM("cli_deploy_init_failed", { params: { message: cliM("cli_deploy_invalid_value") } }));
2703
+ const initResult = await deploy.init(validatedConfig.data);
2268
2704
  if (!initResult.success) {
2269
2705
  throw new Error(cliM("cli_deploy_init_failed", { params: { message: initResult.error.message } }));
2270
2706
  }
@@ -2273,8 +2709,11 @@ async function deployCommand(options) {
2273
2709
  spinner.start(cliM("cli_deploy_deploying"));
2274
2710
  const deployResult = await deploy.deployApp(appDir, {
2275
2711
  projectName: options.projectName,
2276
- skipProvision: options.skipProvision,
2277
- skipBuild: options.skipBuild
2712
+ skipProvision: skipMiddleware,
2713
+ skipBuild: options.skipBuild,
2714
+ environment: environment.data.environment,
2715
+ envDir: environment.data.envDir,
2716
+ serviceModes
2278
2717
  });
2279
2718
  if (!deployResult.success) {
2280
2719
  if (deployResult.error.ext?.recovery) {
@@ -2285,6 +2724,21 @@ async function deployCommand(options) {
2285
2724
  throw new Error(cliM("cli_deploy_failed", { params: { message: deployResult.error.message } }));
2286
2725
  }
2287
2726
  spinner.succeed(chalk.green(cliM("cli_deploy_success")));
2727
+ for (const key of Object.keys(process3.env)) {
2728
+ if (originalEnv[key] === void 0)
2729
+ delete process3.env[key];
2730
+ }
2731
+ Object.assign(process3.env, originalEnv);
2732
+ const completedEnvironment = deployModule.loadDeploymentEnvironment({ environment: environment.data.environment, envDir: environment.data.envDir });
2733
+ if (!completedEnvironment.success)
2734
+ throw new Error(completedEnvironment.error.message);
2735
+ for (const requirement of scan.middlewares ?? []) {
2736
+ if ((serviceModes[requirement.envPrefix] ?? serviceModes[requirement.service]) === "keep")
2737
+ continue;
2738
+ const value = completedEnvironment.data.values[requirement.envPrefix];
2739
+ if (value !== void 0)
2740
+ core.logger.info(cliM("cli_deploy_saved", { params: { name: requirement.envPrefix, value: displayDeployValue(requirement.envPrefix, value) } }));
2741
+ }
2288
2742
  core.logger.info("");
2289
2743
  core.logger.info(chalk.cyan(cliM("cli_deploy_url", { params: { url: deployResult.data.url } })));
2290
2744
  core.logger.info(chalk.gray(cliM("cli_deploy_id", { params: { id: deployResult.data.deploymentId } })));
@@ -2302,16 +2756,16 @@ async function deployCommand(options) {
2302
2756
  await closeDeploy();
2303
2757
  } catch (error) {
2304
2758
  core.logger.error("Failed to close deploy module", { error });
2305
- process4.exitCode = 1;
2759
+ process3.exitCode = 1;
2306
2760
  }
2307
2761
  }
2762
+ for (const key of Object.keys(process3.env)) {
2763
+ if (originalEnv[key] === void 0)
2764
+ delete process3.env[key];
2765
+ }
2766
+ Object.assign(process3.env, originalEnv);
2308
2767
  }
2309
2768
  }
2310
- function interpolateEnvFallback(content) {
2311
- return content.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (_match, key, defaultValue) => {
2312
- return process4.env[key] ?? defaultValue ?? "";
2313
- });
2314
- }
2315
2769
  var GENERATORS = {
2316
2770
  page: {
2317
2771
  description: "SvelteKit \u9875\u9762",
@@ -2653,7 +3107,7 @@ async function initProject(options) {
2653
3107
  const { project, existing, missing } = result;
2654
3108
  spinner.succeed(`\u68C0\u6D4B\u5230\u9879\u76EE: ${chalk.cyan(project.name)}`);
2655
3109
  if (project.isHaiProject) {
2656
- const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-EZFW7D43.js');
3110
+ const { generateProjectAiSupport, resolveSkillNamesForPackages } = await import('./cli-skill-templates-A5VIR5J6.js');
2657
3111
  const generatedFiles = await generateProjectAiSupport(cwd, resolveSkillNamesForPackages(project.haiPackages), {
2658
3112
  overwriteSkills: false,
2659
3113
  overwriteBridgeFiles: false
@@ -2757,7 +3211,7 @@ cli.command("create [name]", "\u521B\u5EFA\u65B0\u7684 hai \u9879\u76EE").option
2757
3211
  cwd: options.cwd
2758
3212
  });
2759
3213
  } catch {
2760
- process4.exitCode = 1;
3214
+ process3.exitCode = 1;
2761
3215
  }
2762
3216
  });
2763
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) => {
@@ -2769,7 +3223,7 @@ cli.command("add [module]", "\u5411\u73B0\u6709\u9879\u76EE\u6DFB\u52A0\u6A21\u5
2769
3223
  cwd: options.cwd
2770
3224
  });
2771
3225
  } catch {
2772
- process4.exit(1);
3226
+ process3.exit(1);
2773
3227
  }
2774
3228
  });
2775
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) => {
@@ -2780,7 +3234,7 @@ cli.command("init", "\u521D\u59CB\u5316/\u6821\u9A8C\u9879\u76EE\u914D\u7F6E").o
2780
3234
  cwd: options.cwd
2781
3235
  });
2782
3236
  } catch {
2783
- process4.exit(1);
3237
+ process3.exit(1);
2784
3238
  }
2785
3239
  });
2786
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) => {
@@ -2794,21 +3248,25 @@ cli.command("generate [type] [name]", "\u751F\u6210\u4EE3\u7801").alias("g").opt
2794
3248
  cwd: options.cwd
2795
3249
  });
2796
3250
  } catch {
2797
- process4.exit(1);
3251
+ process3.exit(1);
2798
3252
  }
2799
3253
  });
2800
- cli.command("deploy [appDir]", "\u90E8\u7F72\u5E94\u7528\u5230\u4E91\u5E73\u53F0").option("--project-name <name>", "\u9879\u76EE\u540D\u79F0\uFF08\u9ED8\u8BA4\u4ECE package.json \u8BFB\u53D6\uFF09").option("--skip-provision", "\u8DF3\u8FC7\u57FA\u7840\u8BBE\u65BD\u5F00\u901A").option("--skip-build", "\u8DF3\u8FC7\u6784\u5EFA").action(async (appDir, options) => {
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) => {
2801
3255
  try {
2802
3256
  await deployCommand({
2803
3257
  appDir,
3258
+ env: options.env,
3259
+ envDir: options.envDir,
3260
+ yes: options.yes,
2804
3261
  projectName: options.projectName,
2805
3262
  skipProvision: options.skipProvision,
3263
+ skipMiddleware: options.skipMiddleware,
2806
3264
  skipBuild: options.skipBuild,
2807
3265
  verbose: options.verbose,
2808
3266
  cwd: options.cwd
2809
3267
  });
2810
3268
  } catch {
2811
- process4.exit(1);
3269
+ process3.exit(1);
2812
3270
  }
2813
3271
  });
2814
3272
  cli.command("g:page <name>", "\u751F\u6210\u9875\u9762").action(async (name, options) => {