@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
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { __export } from './chunk-2H7UOFLK.js';
3
- import { core, err, ok } from '@h-ai/core';
4
- import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync, statSync } from 'fs';
3
+ import { core, ok, err } from '@h-ai/core';
4
+ import { readFileSync, existsSync, writeFileSync, chmodSync, mkdirSync, statSync, readdirSync } from 'fs';
5
+ import { resolve, join, basename, dirname, relative, sep } from 'path';
6
+ import process2 from 'process';
7
+ import { parseEnv } from 'util';
8
+ import { parse as parse$1, stringify, parseAllDocuments } from 'yaml';
5
9
  import { homedir } from 'os';
6
- import { join, dirname, relative, sep } from 'path';
7
- import process from 'process';
8
- import { parse as parse$1, stringify } from 'yaml';
9
10
  import { execSync, spawn } from 'child_process';
10
11
  import { randomBytes, createHash } from 'crypto';
11
12
  import { Buffer } from 'buffer';
@@ -1397,10 +1398,10 @@ function defineLazyInternal(inst, key, compute) {
1397
1398
  Object.defineProperty(this, key, { configurable: true, writable: true, value });
1398
1399
  broke = broke || outer;
1399
1400
  return value;
1400
- } catch (err16) {
1401
+ } catch (err17) {
1401
1402
  delete this[key];
1402
1403
  broke = broke || outer;
1403
- throw err16;
1404
+ throw err17;
1404
1405
  }
1405
1406
  },
1406
1407
  set(value) {
@@ -1855,18 +1856,18 @@ var validateAsync = async (schema, value, _ctx) => {
1855
1856
  return result.issues.length === 0;
1856
1857
  };
1857
1858
  var _encode = (_Err) => {
1858
- const parse5 = _parse(_Err);
1859
+ const parse6 = _parse(_Err);
1859
1860
  const fn = (schema, value, _ctx, _params) => {
1860
1861
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
1861
- return parse5(schema, value, ctx, finalizeParams(fn, _params));
1862
+ return parse6(schema, value, ctx, finalizeParams(fn, _params));
1862
1863
  };
1863
1864
  return fn;
1864
1865
  };
1865
1866
  var encode = /* @__PURE__ */ _encode($ZodRealError);
1866
1867
  var _decode = (_Err) => {
1867
- const parse5 = _parse(_Err);
1868
+ const parse6 = _parse(_Err);
1868
1869
  const fn = (schema, value, _ctx, _params) => {
1869
- return parse5(schema, value, _ctx, finalizeParams(fn, _params));
1870
+ return parse6(schema, value, _ctx, finalizeParams(fn, _params));
1870
1871
  };
1871
1872
  return fn;
1872
1873
  };
@@ -3520,14 +3521,14 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
3520
3521
  const shape = zod.def.shape;
3521
3522
  const propValues = {};
3522
3523
  for (const key in shape) {
3523
- const field = shape[key]._zod;
3524
- if (field.values) {
3524
+ const field2 = shape[key]._zod;
3525
+ if (field2.values) {
3525
3526
  if (!Object.prototype.hasOwnProperty.call(propValues, key)) {
3526
3527
  assignProp(propValues, key, /* @__PURE__ */ new Set());
3527
3528
  }
3528
- for (const v of field.values)
3529
+ for (const v of field2.values)
3529
3530
  propValues[key].add(v);
3530
- if (field.optin !== void 0)
3531
+ if (field2.optin !== void 0)
3531
3532
  propValues[key].add(void 0);
3532
3533
  }
3533
3534
  }
@@ -5036,7 +5037,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
5036
5037
  var NONE = 0;
5037
5038
  var ASSUMED = 1;
5038
5039
  var PROVEN = 2;
5039
- function isRecursive(inst, stack, resolve) {
5040
+ function isRecursive(inst, stack, resolve3) {
5040
5041
  const cached2 = recursive.get(inst);
5041
5042
  if (cached2 !== void 0)
5042
5043
  return cached2 ? PROVEN : NONE;
@@ -5046,7 +5047,7 @@ function isRecursive(inst, stack, resolve) {
5046
5047
  let result = NONE;
5047
5048
  const check2 = (child) => {
5048
5049
  if (result !== PROVEN && child?._zod) {
5049
- const answer = isRecursive(child, stack, resolve);
5050
+ const answer = isRecursive(child, stack, resolve3);
5050
5051
  if (answer > result)
5051
5052
  result = answer;
5052
5053
  }
@@ -5057,7 +5058,7 @@ function isRecursive(inst, stack, resolve) {
5057
5058
  const desc = Object.getOwnPropertyDescriptor(sh, key);
5058
5059
  if (!desc.enumerable)
5059
5060
  continue;
5060
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;
5061
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve3) : NONE;
5061
5062
  if (child > answer)
5062
5063
  answer = child;
5063
5064
  }
@@ -5121,7 +5122,7 @@ function isRecursive(inst, stack, resolve) {
5121
5122
  break;
5122
5123
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
5123
5124
  case "lazy": {
5124
- const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);
5125
+ const inner = def._cachedInner ?? (resolve3 ? inst._zod.innerType : void 0);
5125
5126
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
5126
5127
  break;
5127
5128
  }
@@ -12939,9 +12940,9 @@ function compile(schema, options) {
12939
12940
  const clone2 = withParser(schema, parser);
12940
12941
  clone2._zod.bag.validator = compileValidator(schema, parser);
12941
12942
  return clone2;
12942
- } catch (err16) {
12943
+ } catch (err17) {
12943
12944
  if (options?.strict)
12944
- throw err16;
12945
+ throw err17;
12945
12946
  return schema;
12946
12947
  }
12947
12948
  }
@@ -13031,8 +13032,8 @@ ${code}
13031
13032
  try {
13032
13033
  const factory = new F(...constantNames, factoryCode);
13033
13034
  fn = factory(...constantValues);
13034
- } catch (err16) {
13035
- throw new ZodCompileUnsupportedError(`this schema (generated code failed to evaluate: ${err16.message})`);
13035
+ } catch (err17) {
13036
+ throw new ZodCompileUnsupportedError(`this schema (generated code failed to evaluate: ${err17.message})`);
13036
13037
  }
13037
13038
  if (options?.debug) {
13038
13039
  fn.code = fullCode;
@@ -13070,9 +13071,9 @@ function compileChild(doc, ctx, schema, accessor, needsValue = true) {
13070
13071
  const varCounter = ctx.varCounter;
13071
13072
  try {
13072
13073
  return generateCheck(doc, ctx, schema, accessor, needsValue);
13073
- } catch (err16) {
13074
- if (!(err16 instanceof ZodCompileUnsupportedError) || !err16.islandable)
13075
- throw err16;
13074
+ } catch (err17) {
13075
+ if (!(err17 instanceof ZodCompileUnsupportedError) || !err17.islandable)
13076
+ throw err17;
13076
13077
  doc.content.length = contentLen;
13077
13078
  if (ctx.constants.size > constantCount) {
13078
13079
  const trailing = Array.from(ctx.constants.keys()).slice(constantCount);
@@ -15806,7 +15807,7 @@ function foldObjects(members2) {
15806
15807
  objects.push(member);
15807
15808
  }
15808
15809
  const properties = {};
15809
- const required2 = /* @__PURE__ */ new Set();
15810
+ const required3 = /* @__PURE__ */ new Set();
15810
15811
  for (const object2 of objects) {
15811
15812
  for (const key in object2.properties) {
15812
15813
  if (Object.prototype.hasOwnProperty.call(properties, key))
@@ -15823,11 +15824,11 @@ function foldObjects(members2) {
15823
15824
  assignProp(properties, key, merged);
15824
15825
  }
15825
15826
  for (const key of object2.required ?? [])
15826
- required2.add(key);
15827
+ required3.add(key);
15827
15828
  }
15828
15829
  const folded = { type: "object", properties };
15829
- if (required2.size)
15830
- folded.required = [...required2];
15830
+ if (required3.size)
15831
+ folded.required = [...required3];
15831
15832
  if (objects.every((object2) => object2.additionalProperties === false)) {
15832
15833
  folded.additionalProperties = false;
15833
15834
  } else {
@@ -16445,8 +16446,8 @@ var objectProcessor = (schema, ctx, _json, params) => {
16445
16446
  }
16446
16447
  const requiredKeys = [];
16447
16448
  for (const key of Object.keys(shape)) {
16448
- const field = def.shape[key];
16449
- if (ctx.io === "input" ? inputOptin(field) === void 0 : field._zod.optout === void 0) {
16449
+ const field2 = def.shape[key];
16450
+ if (ctx.io === "input" ? inputOptin(field2) === void 0 : field2._zod.optout === void 0) {
16450
16451
  requiredKeys.push(key);
16451
16452
  }
16452
16453
  }
@@ -16604,8 +16605,8 @@ function rewriteKeyNames(ctx) {
16604
16605
  bySchema.set(entry.schema, entry);
16605
16606
  }
16606
16607
  const rewrites = /* @__PURE__ */ new Map();
16607
- for (const record2 of pendingRecords.get(ctx) ?? []) {
16608
- const seen = ctx.seen.get(record2);
16608
+ for (const record3 of pendingRecords.get(ctx) ?? []) {
16609
+ const seen = ctx.seen.get(record3);
16609
16610
  const names = (seen?.def ?? seen?.schema)?.propertyNames;
16610
16611
  if (!names || names === true || rewrites.has(names))
16611
16612
  continue;
@@ -19702,7 +19703,24 @@ var en_US_default = {
19702
19703
  deploy_remoteConnectFailed: "Cannot connect to remote host {host}: {error}",
19703
19704
  deploy_remoteRuntimeMissing: "Remote host missing container runtime or compose: {runtime}",
19704
19705
  deploy_imageTransferFailed: "Image transfer failed: {error}",
19705
- deploy_healthCheckFailed: "Health check failed, app not ready at {url}"
19706
+ deploy_healthCheckFailed: "Health check failed, app not ready at {url}",
19707
+ deploy_invalidEnvironment: "Use letters, numbers, underscores or hyphens for the environment; local/example are reserved.",
19708
+ deploy_environmentMissing: "Environment file missing: {path}. Create and configure it before deploying.",
19709
+ deploy_environmentReadFailed: "Cannot read deployment environment files; check syntax and permissions.",
19710
+ deploy_environmentWriteFailed: "Cannot save deployment configuration; check file syntax and permissions.",
19711
+ deploy_multipleApplications: "Multiple service packages found; specify the application directory.",
19712
+ deploy_purposeSqlite: "Local relational database persisted in a volume",
19713
+ deploy_purposeDatabase: "Relational database service for business data",
19714
+ deploy_purposeMemory: "Per-process memory cache; cleared on restart, no external service",
19715
+ deploy_purposeLocal: "Local filesystem storage persisted in a volume",
19716
+ deploy_purposeCache: "Shared cache, sessions and distributed locks",
19717
+ deploy_purposeStorage: "S3-compatible object storage for uploaded files",
19718
+ deploy_purposeVector: "Vector indexing and similarity search",
19719
+ deploy_purposeDisabled: "LDAP login is disabled; no directory service is created",
19720
+ deploy_purposeLdap: "Directory account authentication; the default directory has no business users",
19721
+ deploy_purposeConsole: "Messages go to application logs without delivery",
19722
+ deploy_purposeEmail: "Email/HTTP delivery; the default SMTP container only captures test messages",
19723
+ deploy_purposeSms: "SMS through Aliyun credentials; requires an existing signature and quota"
19706
19724
  };
19707
19725
 
19708
19726
  // ../deploy/messages/zh-CN.json
@@ -19740,7 +19758,24 @@ var zh_CN_default2 = {
19740
19758
  deploy_remoteConnectFailed: "\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B\u4E3B\u673A {host}\uFF1A{error}",
19741
19759
  deploy_remoteRuntimeMissing: "\u8FDC\u7A0B\u4E3B\u673A\u7F3A\u5C11\u5BB9\u5668\u8FD0\u884C\u65F6\u6216 compose\uFF1A{runtime}",
19742
19760
  deploy_imageTransferFailed: "\u955C\u50CF\u4F20\u8F93\u5931\u8D25\uFF1A{error}",
19743
- deploy_healthCheckFailed: "\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF0C\u5E94\u7528\u672A\u5728 {url} \u5C31\u7EEA"
19761
+ deploy_healthCheckFailed: "\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF0C\u5E94\u7528\u672A\u5728 {url} \u5C31\u7EEA",
19762
+ deploy_invalidEnvironment: "\u73AF\u5883\u540D\u79F0\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26\uFF0C\u4E14\u4E0D\u80FD\u4F7F\u7528 local/example\u3002",
19763
+ deploy_environmentMissing: "\u7F3A\u5C11\u73AF\u5883\u914D\u7F6E {path}\uFF1B\u8BF7\u5148\u521B\u5EFA\u8BE5\u6587\u4EF6\u5E76\u5B8C\u6210\u914D\u7F6E\uFF0C\u518D\u91CD\u65B0\u90E8\u7F72\u3002",
19764
+ deploy_environmentReadFailed: "\u90E8\u7F72\u73AF\u5883\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u683C\u5F0F\u4E0E\u6743\u9650\u3002",
19765
+ deploy_environmentWriteFailed: "\u90E8\u7F72\u914D\u7F6E\u5199\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u683C\u5F0F\u4E0E\u6743\u9650\u3002",
19766
+ deploy_multipleApplications: "\u53D1\u73B0\u591A\u4E2A\u670D\u52A1\u5305\uFF0C\u8BF7\u6307\u5B9A\u8981\u90E8\u7F72\u7684\u5E94\u7528\u76EE\u5F55\u3002",
19767
+ deploy_purposeSqlite: "\u672C\u5730\u6587\u4EF6\u5173\u7CFB\u6570\u636E\u5E93\uFF0C\u4F7F\u7528\u6301\u4E45\u5316\u5377",
19768
+ deploy_purposeDatabase: "\u72EC\u7ACB\u5173\u7CFB\u6570\u636E\u5E93\u670D\u52A1\uFF0C\u4FDD\u5B58\u4E1A\u52A1\u6570\u636E",
19769
+ deploy_purposeMemory: "\u5355\u8FDB\u7A0B\u5185\u5B58\u7F13\u5B58\uFF0C\u91CD\u542F\u540E\u6E05\u7A7A\uFF0C\u4E0D\u521B\u5EFA\u5916\u90E8\u670D\u52A1",
19770
+ deploy_purposeLocal: "\u672C\u5730\u6587\u4EF6\u5B58\u50A8\uFF0C\u4F7F\u7528\u6301\u4E45\u5316\u5377",
19771
+ deploy_purposeCache: "\u5171\u4EAB\u7F13\u5B58\u3001\u4F1A\u8BDD\u4E0E\u5206\u5E03\u5F0F\u9501",
19772
+ deploy_purposeStorage: "S3 \u517C\u5BB9\u5BF9\u8C61\u5B58\u50A8\uFF0C\u4FDD\u5B58\u4E0A\u4F20\u6587\u4EF6",
19773
+ deploy_purposeVector: "\u5411\u91CF\u7D22\u5F15\u4E0E\u76F8\u4F3C\u5EA6\u641C\u7D22",
19774
+ deploy_purposeDisabled: "LDAP \u767B\u5F55\u672A\u542F\u7528\uFF0C\u4E0D\u521B\u5EFA\u76EE\u5F55\u670D\u52A1",
19775
+ deploy_purposeLdap: "\u4F01\u4E1A\u76EE\u5F55\u8D26\u53F7\u8BA4\u8BC1\uFF1B\u9ED8\u8BA4\u76EE\u5F55\u4E0D\u5305\u542B\u4E1A\u52A1\u7528\u6237",
19776
+ deploy_purposeConsole: "\u6D88\u606F\u8F93\u51FA\u5230\u5E94\u7528\u65E5\u5FD7\uFF0C\u4E0D\u5B9E\u9645\u6295\u9012",
19777
+ deploy_purposeEmail: "\u90AE\u4EF6\u6216 HTTP \u901A\u77E5\u6295\u9012\uFF1B\u9ED8\u8BA4 SMTP \u5BB9\u5668\u4EC5\u7528\u4E8E\u6D4B\u8BD5\u63A5\u6536",
19778
+ deploy_purposeSms: "\u901A\u8FC7\u963F\u91CC\u4E91\u51ED\u8BC1\u63A5\u5165\u77ED\u4FE1\u670D\u52A1\uFF0C\u9700\u5DF2\u6709\u7B7E\u540D\u4E0E\u989D\u5EA6"
19744
19779
  };
19745
19780
 
19746
19781
  // ../deploy/src/deploy-i18n.ts
@@ -19773,9 +19808,9 @@ var SshConfigSchema = external_exports.object({
19773
19808
  var PortExposeSchema = external_exports.object({
19774
19809
  type: external_exports.literal("port").default("port"),
19775
19810
  /** 容器内监听端口(adapter-node 默认 3000) */
19776
- containerPort: external_exports.number().int().positive().default(3e3),
19811
+ containerPort: external_exports.number().int().min(1).max(65535).default(3e3),
19777
19812
  /** 宿主机映射端口 */
19778
- hostPort: external_exports.number().int().positive()
19813
+ hostPort: external_exports.number().int().min(1).max(65535)
19779
19814
  });
19780
19815
  var ExposeSchema = PortExposeSchema;
19781
19816
  var DockerSshProviderConfigSchema = external_exports.object({
@@ -19831,11 +19866,22 @@ var ServicesConfigSchema = external_exports.object({
19831
19866
  sms: external_exports.discriminatedUnion("provisioner", [AliyunSmsServiceSchema]).optional()
19832
19867
  }).optional();
19833
19868
  var DeployConfigSchema = external_exports.object({
19869
+ /** 部署环境文件模式;dev/test/prod 或自定义模式,禁止 local/example。 */
19870
+ environment: external_exports.string().regex(/^[a-z0-9][\w-]*$/i).refine((value) => !["local", "example"].includes(value.toLowerCase())).optional(),
19834
19871
  provider: ProviderConfigSchema,
19835
19872
  services: ServicesConfigSchema,
19836
19873
  /** 本地容器构建参数(docker-ssh 使用;auto 优先 podman) */
19837
19874
  container: external_exports.object({
19838
- runtime: external_exports.enum(["auto", "docker", "podman"]).default("auto")
19875
+ runtime: external_exports.enum(["auto", "docker", "podman"]).default("auto"),
19876
+ /** Podman 构建是否继承代理;容器不能访问宿主机 localhost 代理时设为 false。 */
19877
+ httpProxy: external_exports.boolean().default(true),
19878
+ /** 自建 S3 服务实现;协议仍为 storage.type=s3。 */
19879
+ s3Engine: external_exports.enum(["rustfs", "minio"]).default("rustfs"),
19880
+ /** SQLite 预编译包镜像根地址;省略时从官方 GitHub Release 下载,不允许嵌入凭据。 */
19881
+ sqlitePrebuildMirror: external_exports.url().refine((value) => {
19882
+ const url2 = new URL(value);
19883
+ return ["http:", "https:"].includes(url2.protocol) && !url2.username && !url2.password && !url2.search && !url2.hash;
19884
+ }).optional()
19839
19885
  }).optional()
19840
19886
  });
19841
19887
  var DeployErrorInfo = {
@@ -19862,7 +19908,165 @@ var DeployErrorInfo = {
19862
19908
  };
19863
19909
  var HaiDeployError = core.error.buildHaiErrorsDef("deploy", DeployErrorInfo);
19864
19910
 
19865
- // ../deploy/src/deploy-credentials.ts
19911
+ // ../deploy/src/deploy-environment.ts
19912
+ function resolveConfig(value, prefix, env) {
19913
+ if (env[prefix] !== void 0)
19914
+ return parse$1(env[prefix]) ?? env[prefix];
19915
+ if (Array.isArray(value))
19916
+ return value.map((item, index) => resolveConfig(item, `${prefix}_${index}`, env));
19917
+ if (value && typeof value === "object")
19918
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, resolveConfig(nested, `${prefix}_${key.toUpperCase()}`, env)]));
19919
+ if (typeof value !== "string")
19920
+ return value;
19921
+ const resolved = value.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (_match, name, fallback) => env[name] ?? fallback ?? "");
19922
+ return /^\$\{[^}]+\}$/.test(value) ? parse$1(resolved) ?? resolved : resolved;
19923
+ }
19924
+ function loadDeploymentConfig(name, path, values) {
19925
+ try {
19926
+ const config2 = resolveConfig(parse$1(readFileSync(path, "utf8")), `HAI_${name.toUpperCase()}`, values);
19927
+ return ok(config2 && typeof config2 === "object" && !Array.isArray(config2) ? config2 : {});
19928
+ } catch {
19929
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_environmentReadFailed"));
19930
+ }
19931
+ }
19932
+ function loadDeploymentEnvironment(options) {
19933
+ const environment = options.environment === "production" ? "prod" : options.environment === "development" ? "dev" : options.environment;
19934
+ if (!/^[a-z0-9][\w-]*$/i.test(environment) || ["local", "example"].includes(environment.toLowerCase()))
19935
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_invalidEnvironment"));
19936
+ const envDir = resolve(options.envDir);
19937
+ const target = join(envDir, `.env.${environment}`);
19938
+ if (options.requireFile !== false && !existsSync(target))
19939
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_environmentMissing", { params: { path: target } }));
19940
+ const loaded = core.config.loadEnvFiles({ mode: environment, envDir });
19941
+ if (!loaded.success)
19942
+ return loaded;
19943
+ try {
19944
+ const values = { ...loaded.data };
19945
+ for (const [name, value] of Object.entries(process2.env)) {
19946
+ if (/^(?:HAI_|PUBLIC_|VITE_)/.test(name) && value !== void 0)
19947
+ values[name] = value;
19948
+ }
19949
+ values.HAI_DEPLOY_ENVIRONMENT = environment;
19950
+ values.HAI_CORE_ENV ??= environment === "prod" ? "production" : environment === "dev" ? "development" : environment === "test" ? "test" : "staging";
19951
+ const targetValues = {};
19952
+ if (existsSync(target)) {
19953
+ for (const [name, value] of Object.entries(parseEnv(readFileSync(target, "utf8")))) {
19954
+ if (value !== void 0)
19955
+ targetValues[name] = value;
19956
+ }
19957
+ }
19958
+ return ok({ environment, envDir, values, targetValues });
19959
+ } catch {
19960
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_environmentReadFailed"));
19961
+ }
19962
+ }
19963
+ function isSecret(name, value) {
19964
+ if (/PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(name.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(name) || value.includes("PRIVATE KEY"))
19965
+ return true;
19966
+ try {
19967
+ const url2 = new URL(value);
19968
+ if (url2.username || url2.password || [...url2.searchParams.keys()].some((key) => /password|secret|token|key|authorization/i.test(key)))
19969
+ return true;
19970
+ } catch {
19971
+ }
19972
+ try {
19973
+ const parsed = JSON.parse(value);
19974
+ if (parsed && typeof parsed === "object")
19975
+ return Object.entries(parsed).some(([key, nested]) => isSecret(key, typeof nested === "string" ? nested : JSON.stringify(nested)));
19976
+ } catch {
19977
+ }
19978
+ return false;
19979
+ }
19980
+ function publicConfig(value) {
19981
+ if (Array.isArray(value))
19982
+ return value.map(publicConfig);
19983
+ if (value && typeof value === "object")
19984
+ return Object.fromEntries(Object.entries(value).filter(([name, item]) => !isSecret(name, typeof item === "string" ? item : "")).map(([name, item]) => [name, publicConfig(item)]));
19985
+ return value;
19986
+ }
19987
+ function quoteEnv(value) {
19988
+ try {
19989
+ const parsed = JSON.parse(value);
19990
+ if (parsed && typeof parsed === "object")
19991
+ return `'${JSON.stringify(parsed).replaceAll("'", "\\u0027")}'`;
19992
+ } catch {
19993
+ }
19994
+ if (!value.includes("'"))
19995
+ return `'${value}'`;
19996
+ if (!value.includes('"'))
19997
+ return `"${value}"`;
19998
+ if (!value.includes("`"))
19999
+ return `\`${value}\``;
20000
+ if (!/[\r\n#]/.test(value))
20001
+ return value;
20002
+ throw new Error("Unsupported dotenv value");
20003
+ }
20004
+ function updateFile(path, values, privateFile) {
20005
+ let content = existsSync(path) ? readFileSync(path, "utf8") : "# Local private deployment configuration; do not commit.\n";
20006
+ for (const [name, value] of Object.entries(values)) {
20007
+ if (!/^[A-Z][A-Z0-9_]*$/.test(name))
20008
+ throw new Error("Invalid environment variable name");
20009
+ const line = `${name}=${quoteEnv(value)}`;
20010
+ const pattern = new RegExp(`^(?:export\\s+)?${name}\\s*=\\s*(?:'[^']*'|"[^"]*"|\`[^\`]*\`|[^\\r\\n]*)[^\\r\\n]*`, "gm");
20011
+ if (pattern.test(content))
20012
+ content = content.replace(pattern, () => line);
20013
+ else
20014
+ content = `${content.trimEnd()}
20015
+ ${line}
20016
+ `;
20017
+ }
20018
+ writeFileSync(path, content, { mode: privateFile ? 384 : 420 });
20019
+ if (privateFile)
20020
+ chmodSync(path, 384);
20021
+ }
20022
+ function removeOverrides(path, names, privateFile = true) {
20023
+ if (!existsSync(path) || names.length === 0)
20024
+ return;
20025
+ let content = readFileSync(path, "utf8");
20026
+ for (const name of names)
20027
+ content = content.replace(new RegExp(`^(?:export\\s+)?${name}\\s*=\\s*(?:'[^']*'|"[^"]*"|\`[^\`]*\`|[^\\r\\n]*)[^\\r\\n]*(?:\\r?\\n|$)`, "gm"), "");
20028
+ writeFileSync(path, content, { mode: privateFile ? 384 : 420 });
20029
+ chmodSync(path, privateFile ? 384 : 420);
20030
+ }
20031
+ function saveDeploymentEnvironment(environment, values, options = {}) {
20032
+ try {
20033
+ const shared = {};
20034
+ const privateValues = {};
20035
+ for (const [name, value] of Object.entries(values)) {
20036
+ if (options.private) {
20037
+ privateValues[name] = value;
20038
+ } else if (isSecret(name, value)) {
20039
+ privateValues[name] = value;
20040
+ try {
20041
+ const parsed = JSON.parse(value);
20042
+ if (parsed && typeof parsed === "object")
20043
+ shared[name] = JSON.stringify(publicConfig(parsed));
20044
+ } catch {
20045
+ }
20046
+ } else {
20047
+ shared[name] = value;
20048
+ }
20049
+ }
20050
+ if (Object.keys(shared).length)
20051
+ updateFile(join(environment.envDir, `.env.${environment.environment}`), shared, false);
20052
+ if (Object.keys(privateValues).length)
20053
+ updateFile(join(environment.envDir, ".env.local"), privateValues, true);
20054
+ removeOverrides(join(environment.envDir, `.env.${environment.environment}`), Object.keys(privateValues).filter((name) => !(name in shared)), false);
20055
+ removeOverrides(join(environment.envDir, ".env.local"), Object.keys(shared).filter((name) => !(name in privateValues)));
20056
+ if (Object.keys(privateValues).length) {
20057
+ const ignorePath = join(environment.envDir, ".gitignore");
20058
+ const ignore = existsSync(ignorePath) ? readFileSync(ignorePath, "utf8") : "";
20059
+ if (!ignore.split(/\r?\n/).includes(".env.local"))
20060
+ writeFileSync(ignorePath, `${ignore.trimEnd()}
20061
+ .env.local
20062
+ .hai/deploy/
20063
+ `);
20064
+ }
20065
+ return ok(Object.keys(values));
20066
+ } catch {
20067
+ return err(HaiDeployError.ENV_VAR_FAILED, deployM("deploy_environmentWriteFailed"));
20068
+ }
20069
+ }
19866
20070
  var logger = core.logger.child({ module: "deploy", scope: "credentials" });
19867
20071
  var DEPLOY_CREDENTIAL_KEY_PATTERN = /^HAI_DEPLOY_[A-Z0-9_]+$/;
19868
20072
  function isAllowedCredentialKey(key) {
@@ -19926,7 +20130,7 @@ function loadCredentials() {
19926
20130
  const data = readCredentialsFile();
19927
20131
  const keys = Object.keys(data);
19928
20132
  for (const key of keys) {
19929
- process.env[key] = String(data[key]);
20133
+ process2.env[key] = String(data[key]);
19930
20134
  }
19931
20135
  logger.debug("Credentials loaded", { count: keys.length });
19932
20136
  return ok(keys);
@@ -19947,7 +20151,7 @@ function saveCredential(key, value) {
19947
20151
  const data = readCredentialsFile();
19948
20152
  data[key] = value;
19949
20153
  writeCredentialsFile(data);
19950
- process.env[key] = value;
20154
+ process2.env[key] = value;
19951
20155
  logger.debug("Credential saved", { key });
19952
20156
  return ok(void 0);
19953
20157
  } catch (error62) {
@@ -19967,7 +20171,7 @@ function saveCredentials(entries) {
19967
20171
  const data = readCredentialsFile();
19968
20172
  for (const [key, value] of Object.entries(entries)) {
19969
20173
  data[key] = value;
19970
- process.env[key] = value;
20174
+ process2.env[key] = value;
19971
20175
  }
19972
20176
  writeCredentialsFile(data);
19973
20177
  logger.debug("Credentials saved", { count: Object.keys(entries).length });
@@ -19984,13 +20188,17 @@ function saveCredentials(entries) {
19984
20188
  }
19985
20189
  }
19986
20190
  var logger2 = core.logger.child({ module: "deploy", scope: "functions" });
20191
+ function usesDefaultMiddleware(scan, service, options) {
20192
+ const requirements = scan.middlewares?.filter((item) => item.service === service) ?? [];
20193
+ return requirements.length ? requirements.some((item) => (options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.[service] ?? "default") === "default") : (options?.serviceModes?.[service] ?? "default") === "default";
20194
+ }
19987
20195
  function buildApp(appDir, buildCommand, envVars = {}) {
19988
20196
  logger2.info("Building application", { appDir, buildCommand });
19989
20197
  try {
19990
20198
  execSync(buildCommand, {
19991
20199
  cwd: appDir,
19992
20200
  stdio: "inherit",
19993
- env: { ...process.env, ...envVars }
20201
+ env: { ...process2.env, ...envVars }
19994
20202
  });
19995
20203
  return ok(void 0);
19996
20204
  } catch (error62) {
@@ -20043,195 +20251,144 @@ function recoveryFailure(error62, recovery) {
20043
20251
  });
20044
20252
  }
20045
20253
  var logger3 = core.logger.child({ module: "deploy", scope: "scanner" });
20046
- function readYaml(filePath) {
20047
- if (!existsSync(filePath)) {
20048
- return null;
20049
- }
20050
- try {
20051
- const content = readFileSync(filePath, "utf-8");
20052
- const parsed = parse$1(content);
20053
- if (parsed === null || parsed === void 0 || typeof parsed !== "object") {
20054
- return null;
20055
- }
20056
- return parsed;
20057
- } catch {
20058
- return null;
20059
- }
20060
- }
20061
- function readJson(filePath) {
20062
- if (!existsSync(filePath)) {
20063
- return null;
20064
- }
20065
- try {
20066
- const content = readFileSync(filePath, "utf-8");
20067
- return JSON.parse(content);
20068
- } catch {
20069
- return null;
20070
- }
20071
- }
20072
- function hasDependency(pkg, name) {
20073
- const deps = pkg.dependencies;
20074
- const devDeps = pkg.devDependencies;
20075
- return deps !== void 0 && name in deps || devDeps !== void 0 && name in devDeps;
20076
- }
20077
- function detectRequiredServices(configDir) {
20078
- const services = [];
20079
- const backends = {};
20080
- if (!existsSync(configDir)) {
20081
- return { services, backends };
20082
- }
20083
- const files = readdirSync(configDir);
20084
- const dbFile = files.find((f) => f.startsWith("_db."));
20085
- if (dbFile) {
20086
- const dbConfig = readYaml(join(configDir, dbFile));
20087
- if (dbConfig !== null) {
20088
- services.push("db");
20089
- backends.db = dbConfig.type ?? "postgresql";
20090
- }
20091
- }
20092
- const cacheFile = files.find((f) => f.startsWith("_cache."));
20093
- if (cacheFile) {
20094
- const cacheConfig = readYaml(join(configDir, cacheFile));
20095
- if (cacheConfig !== null && cacheConfig.type === "redis") {
20096
- services.push("cache");
20097
- backends.cache = "redis";
20098
- }
20099
- }
20100
- const storageFile = files.find((f) => f.startsWith("_storage."));
20101
- if (storageFile) {
20102
- const storageConfig = readYaml(join(configDir, storageFile));
20103
- if (storageConfig !== null && detectStorageIsS3(storageConfig)) {
20104
- services.push("storage");
20105
- backends.storage = "s3";
20106
- }
20107
- }
20108
- const vecdbFile = files.find((f) => f.startsWith("_vecdb."));
20109
- if (vecdbFile) {
20110
- const vecdbConfig = readYaml(join(configDir, vecdbFile));
20111
- if (vecdbConfig !== null) {
20112
- services.push("vecdb");
20113
- backends.vecdb = vecdbConfig.type ?? "qdrant";
20114
- }
20115
- }
20116
- const reachFile = files.find((f) => f.startsWith("_reach."));
20117
- if (reachFile) {
20118
- const reachConfig = readYaml(join(configDir, reachFile));
20119
- if (reachConfig !== null) {
20120
- detectReachServices(reachConfig, services);
20121
- }
20122
- }
20123
- return { services, backends };
20124
- }
20125
- function detectStorageIsS3(config2) {
20126
- if (config2.type === "s3") {
20127
- return true;
20128
- }
20129
- const providers = config2.providers;
20130
- if (providers === void 0) {
20131
- return false;
20132
- }
20133
- return Object.values(providers).some((p) => p?.type === "s3");
20134
- }
20135
- function detectReachServices(config2, services) {
20136
- const providers = config2.providers;
20137
- if (!Array.isArray(providers)) {
20138
- return;
20139
- }
20140
- let hasEmail = false;
20141
- let hasSms = false;
20142
- for (const provider of providers) {
20143
- const providerType = provider.type;
20144
- if (!hasEmail && (providerType === "smtp" || providerType === "api")) {
20145
- hasEmail = true;
20146
- services.push("email");
20147
- }
20148
- if (!hasSms && providerType === "aliyun-sms") {
20149
- hasSms = true;
20150
- services.push("sms");
20254
+ function record2(value) {
20255
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
20256
+ }
20257
+ function runtimeDependencies(appDir) {
20258
+ let root = appDir;
20259
+ while (!existsSync(join(root, "pnpm-lock.yaml"))) {
20260
+ const parent = dirname(root);
20261
+ if (parent === root)
20262
+ throw new Error("Missing pnpm lockfile; run pnpm install");
20263
+ root = parent;
20264
+ }
20265
+ const importerKey = relative(root, appDir).replaceAll("\\", "/") || ".";
20266
+ const documents = parseAllDocuments(readFileSync(join(root, "pnpm-lock.yaml"), "utf8"));
20267
+ if (documents.some((document) => document.errors.length))
20268
+ throw new Error("Invalid pnpm lockfile");
20269
+ const lock = documents.map((document) => record2(document.toJS())).reverse().find((document) => importerKey in record2(document.importers));
20270
+ if (!lock)
20271
+ throw new Error("Application is missing from pnpm lockfile; run pnpm install");
20272
+ const importers = record2(lock.importers);
20273
+ const packages = record2(lock.packages);
20274
+ const snapshots = record2(lock.snapshots);
20275
+ const names = /* @__PURE__ */ new Set();
20276
+ const visited = /* @__PURE__ */ new Set();
20277
+ const visit2 = (id, node2, importer) => {
20278
+ if (visited.has(id))
20279
+ return;
20280
+ visited.add(id);
20281
+ const metadata = importer === void 0 ? record2(packages[id.split("(")[0]]) : {};
20282
+ const peers = record2(metadata.peerDependencies);
20283
+ for (const [name, reference] of Object.entries({ ...record2(node2.dependencies), ...record2(node2.optionalDependencies) })) {
20284
+ if (name in peers)
20285
+ continue;
20286
+ const version2 = typeof reference === "string" ? reference : record2(reference).version;
20287
+ if (typeof version2 !== "string")
20288
+ throw new Error("Invalid pnpm dependency reference");
20289
+ if (version2.startsWith("link:")) {
20290
+ if (importer === void 0)
20291
+ throw new Error("Invalid workspace dependency");
20292
+ const target = relative(root, resolve(root, importer, version2.slice(5))).replaceAll("\\", "/") || ".";
20293
+ const linked = record2(importers[target]);
20294
+ if (!Object.keys(linked).length)
20295
+ throw new Error("Missing linked workspace importer");
20296
+ names.add(name);
20297
+ visit2(`workspace:${target}`, linked, target);
20298
+ continue;
20299
+ }
20300
+ const key = version2 in snapshots || version2 in packages ? version2 : `${name}@${version2}`;
20301
+ names.add(key.match(/^(@[^/]+\/[^@]+|[^@]+)@/)?.[1] ?? name);
20302
+ const snapshot = record2(snapshots[key] ?? packages[key]);
20303
+ if (!Object.keys(snapshot).length && !(key in snapshots) && !(key in packages))
20304
+ throw new Error("Missing pnpm dependency snapshot");
20305
+ visit2(key, snapshot);
20151
20306
  }
20152
- }
20307
+ };
20308
+ visit2(`workspace:${importerKey}`, record2(importers[importerKey]), importerKey);
20309
+ return { runtime: names, build: /* @__PURE__ */ new Set([...names, ...Object.keys(record2(record2(importers[importerKey]).devDependencies))]), root };
20153
20310
  }
20154
- async function scanApp(appDir) {
20155
- logger3.debug("Scanning application", { appDir });
20311
+ async function scanApp(inputDir, options) {
20156
20312
  try {
20157
- const pkg = readJson(join(appDir, "package.json"));
20158
- if (pkg === null) {
20159
- return err(
20160
- HaiDeployError.SCAN_FAILED,
20161
- deployM("deploy_scanFailed", { params: { error: "package.json not found" } })
20162
- );
20163
- }
20164
- const appName = extractAppName(pkg);
20165
- const isSvelteKit = existsSync(join(appDir, "svelte.config.js")) || existsSync(join(appDir, "svelte.config.ts"));
20166
- const adapterInstalled = hasDependency(pkg, "@sveltejs/adapter-vercel");
20167
- const nodeAdapterInstalled = hasDependency(pkg, "@sveltejs/adapter-node");
20168
- const configDir = join(appDir, "config");
20169
- const configDetection = detectRequiredServices(configDir);
20170
- const depServices = detectServicesFromDependencies(pkg);
20171
- const requiredServices = Array.from(/* @__PURE__ */ new Set([...configDetection.services, ...depServices]));
20172
- const scripts = pkg.scripts;
20173
- const buildCommand = scripts?.build ?? "pnpm build";
20174
- const scanResult = {
20175
- appName,
20176
- isSvelteKit,
20177
- adapterInstalled,
20178
- nodeAdapterInstalled,
20179
- requiredServices,
20180
- serviceBackends: configDetection.backends,
20181
- buildCommand
20313
+ const appDir = resolve(inputDir);
20314
+ const graph = runtimeDependencies(appDir);
20315
+ const envDir = options?.envDir ? resolve(options.envDir) : existsSync(join(appDir, ".env")) ? appDir : graph.root;
20316
+ const loaded = loadDeploymentEnvironment({ environment: options?.environment ?? "prod", envDir, requireFile: options?.environment !== void 0 });
20317
+ if (!loaded.success)
20318
+ return loaded;
20319
+ const env = loaded.data.values;
20320
+ const deps = graph.runtime;
20321
+ const configs = {};
20322
+ for (const [name, owner] of Object.entries({ db: "reldb", cache: "cache", storage: "storage", vecdb: "vecdb", iam: "iam", reach: "reach" })) {
20323
+ configs[name] = {};
20324
+ if (!deps.has(`@h-ai/${owner}`))
20325
+ continue;
20326
+ const configFile = ["yml", "yaml"].map((ext) => join(appDir, "config", `_${name}.${ext}`)).find(existsSync);
20327
+ const config2 = configFile ? loadDeploymentConfig(name, configFile, env) : void 0;
20328
+ if (config2 && !config2.success)
20329
+ return config2;
20330
+ configs[name] = config2?.data ?? {};
20331
+ }
20332
+ const middlewares = [];
20333
+ const add = (service, backend, configName = service, configPath = [], config2 = configs[configName] ?? {}, source = `_${configName}.yml`) => {
20334
+ middlewares.push({ service, backend, source, configName, configPath, envPrefix: ["HAI", configName, ...configPath].map((part) => part.toUpperCase()).join("_"), config: config2 });
20182
20335
  };
20183
- logger3.info("Application scanned", {
20184
- appName,
20336
+ const iam = deps.has("@h-ai/iam");
20337
+ if (deps.has("@h-ai/reldb"))
20338
+ add("db", String(configs.db.type ?? "sqlite"), "db", [], configs.db, "@h-ai/reldb / _db.yml");
20339
+ if (deps.has("@h-ai/cache"))
20340
+ add("cache", String(configs.cache.type ?? "memory"), "cache", [], configs.cache, "@h-ai/cache / _cache.yml");
20341
+ if (deps.has("@h-ai/storage"))
20342
+ add("storage", String(configs.storage.type ?? "local"));
20343
+ if (deps.has("@h-ai/vecdb"))
20344
+ add("vecdb", String(configs.vecdb.type ?? "lancedb"));
20345
+ if (iam) {
20346
+ const ldap = record2(configs.iam.ldap);
20347
+ add("ldap", record2(configs.iam.login).ldap === true ? "ldap" : "disabled", "iam", ["ldap"], ldap, "@h-ai/iam (LDAP)");
20348
+ }
20349
+ const providers = configs.reach.providers;
20350
+ if (deps.has("@h-ai/reach") && Array.isArray(providers)) {
20351
+ for (const [index, item] of providers.entries()) {
20352
+ const provider = record2(item);
20353
+ const type = String(provider.type ?? "");
20354
+ const service = type === "aliyun-sms" ? "sms" : ["smtp", "api", "console"].includes(type) ? "email" : void 0;
20355
+ if (service)
20356
+ add(service, type, "reach", ["providers", String(index)], provider, "@h-ai/reach");
20357
+ }
20358
+ }
20359
+ if (deps.has("@h-ai/reach") && !middlewares.some((item) => item.configName === "reach"))
20360
+ add("email", "console", "reach", ["providers", "0"], { name: "email", type: "console" }, "@h-ai/reach");
20361
+ const isSvelteKit = graph.build.has("@sveltejs/kit");
20362
+ const scan = {
20363
+ appDir,
20364
+ envDir,
20365
+ middlewares,
20366
+ haiModules: [...deps].filter((name) => name.startsWith("@h-ai/")).sort(),
20367
+ appName: basename(appDir),
20185
20368
  isSvelteKit,
20186
- adapterInstalled,
20187
- nodeAdapterInstalled,
20188
- requiredServices,
20189
- serviceBackends: configDetection.backends
20190
- });
20191
- return ok(scanResult);
20192
- } catch (error62) {
20193
- logger3.error("Application scan failed", { appDir, error: error62 });
20194
- return err(
20195
- HaiDeployError.SCAN_FAILED,
20196
- deployM("deploy_scanFailed", {
20197
- params: { error: error62 instanceof Error ? error62.message : String(error62) }
20198
- }),
20199
- error62
20200
- );
20201
- }
20202
- }
20203
- function detectServicesFromDependencies(pkg) {
20204
- const depMap = {
20205
- "@h-ai/reldb": "db",
20206
- "@h-ai/cache": "cache",
20207
- "@h-ai/storage": "storage",
20208
- "@h-ai/vecdb": "vecdb"
20209
- };
20210
- const services = [];
20211
- for (const [depName, serviceType] of Object.entries(depMap)) {
20212
- if (hasDependency(pkg, depName)) {
20213
- services.push(serviceType);
20214
- }
20215
- }
20216
- return services;
20217
- }
20218
- function extractAppName(pkg) {
20219
- const name = pkg.name ?? "app";
20220
- const slashIndex = name.indexOf("/");
20221
- if (slashIndex >= 0) {
20222
- return name.slice(slashIndex + 1);
20369
+ adapterInstalled: graph.build.has("@sveltejs/adapter-vercel"),
20370
+ nodeAdapterInstalled: graph.build.has("@sveltejs/adapter-node"),
20371
+ requiredServices: [...new Set(middlewares.map((item) => item.service))],
20372
+ serviceBackends: Object.fromEntries(middlewares.map((item) => [item.service, item.backend])),
20373
+ buildCommand: "pnpm build"
20374
+ };
20375
+ logger3.info("Application scanned", { appName: scan.appName, haiModules: scan.haiModules, requiredServices: scan.requiredServices, serviceBackends: scan.serviceBackends });
20376
+ return ok(scan);
20377
+ } catch {
20378
+ return err(HaiDeployError.SCAN_FAILED, deployM("deploy_scanFailed", { params: { error: "Invalid or ambiguous application/configuration/lockfile; specify the application directory and run pnpm install" } }));
20223
20379
  }
20224
- return name;
20225
20380
  }
20226
20381
  var IMAGES = {
20227
20382
  postgres: "postgres:16-alpine",
20228
20383
  mysql: "mysql:8.4",
20229
20384
  redis: "redis:7-alpine",
20230
- minio: "minio/minio:RELEASE.2025-09-07T16-13-09Z",
20231
- mc: "minio/mc:RELEASE.2025-08-13T08-35-41Z",
20385
+ rustfs: "ghcr.io/rustfs/rustfs:1.0.1",
20386
+ minio: "ghcr.io/coollabsio/minio:RELEASE.2025-10-15T17-29-55Z",
20232
20387
  qdrant: "qdrant/qdrant:v1.12.4",
20233
20388
  pgvector: "pgvector/pgvector:pg17",
20234
- chroma: "chromadb/chroma:0.5.23"
20389
+ chroma: "chromadb/chroma:1.5.5",
20390
+ ldap: "docker.io/osixia/openldap:1.5.0",
20391
+ smtp: "axllent/mailpit:v1.27.4"
20235
20392
  };
20236
20393
  function generatePassword() {
20237
20394
  return randomBytes(24).toString("base64url");
@@ -20246,7 +20403,8 @@ function resolveSidecarSecrets(appDir, projectName) {
20246
20403
  }
20247
20404
  const secrets = {
20248
20405
  dbPassword: generatePassword(),
20249
- storagePassword: generatePassword()
20406
+ storagePassword: generatePassword(),
20407
+ ldapPassword: generatePassword()
20250
20408
  };
20251
20409
  mkdirSync(dirname(secretFile), { recursive: true });
20252
20410
  writeFileSync(secretFile, JSON.stringify(secrets), { mode: 384 });
@@ -20307,38 +20465,57 @@ function planRedisCache(plan, projectName, net) {
20307
20465
  };
20308
20466
  plan.env.HAI_CACHE = JSON.stringify({ type: "redis", url: "redis://cache:6379" });
20309
20467
  }
20310
- function planMinioStorage(plan, projectName, password, net) {
20468
+ function planS3Storage(plan, projectName, password, net, engine) {
20311
20469
  plan.services.push("storage");
20312
20470
  plan.dependsOn.push("storage");
20313
20471
  plan.composeServices.storage = {
20314
- image: IMAGES.minio,
20472
+ image: IMAGES[engine],
20315
20473
  container_name: `hai-${projectName}-storage`,
20316
20474
  restart: "unless-stopped",
20317
- command: ["server", "/data"],
20475
+ ...engine === "minio" ? { command: ["server", "/data"] } : {},
20318
20476
  env_file: [".env.runtime"],
20319
20477
  volumes: [`${projectName}-storage-data:/data`],
20320
20478
  networks: net
20321
20479
  };
20322
20480
  plan.composeServices["storage-init"] = {
20323
- image: IMAGES.mc,
20324
20481
  depends_on: ["storage"],
20325
20482
  env_file: [".env.runtime"],
20326
20483
  entrypoint: [
20327
- "sh",
20328
- "-c",
20329
- 'until mc alias set s3 http://storage:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; do sleep 1; done; mc mb -p s3/hai; exit 0'
20484
+ "node",
20485
+ "--input-type=module",
20486
+ "-e",
20487
+ // 复用应用 @h-ai/storage 已依赖的 SDK;不额外拉取失效的 mc 镜像,不把密钥拼进命令。
20488
+ `import { S3Client, HeadBucketCommand, CreateBucketCommand } from '@aws-sdk/client-s3';
20489
+ const config = JSON.parse(process.env.HAI_STORAGE);
20490
+ const client = new S3Client({ endpoint: config.endpoint, region: config.region, forcePathStyle: config.forcePathStyle, credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey } });
20491
+ let ready = false;
20492
+ for (let attempt = 0; attempt < 120; attempt++) {
20493
+ try {
20494
+ try { await client.send(new HeadBucketCommand({ Bucket: config.bucket })); }
20495
+ catch (error) {
20496
+ if (error.$$metadata?.httpStatusCode !== 404) throw error;
20497
+ await client.send(new CreateBucketCommand({ Bucket: config.bucket }));
20498
+ }
20499
+ ready = true; break;
20500
+ } catch { await new Promise(resolve => setTimeout(resolve, 1000)); }
20501
+ }
20502
+ client.destroy();
20503
+ if (!ready) throw new Error('S3 bucket initialization failed');`
20330
20504
  ],
20331
20505
  networks: net
20332
20506
  };
20333
20507
  plan.volumes[`${projectName}-storage-data`] = null;
20334
- plan.env.MINIO_ROOT_USER = "hai";
20335
- plan.env.MINIO_ROOT_PASSWORD = password;
20508
+ if (engine === "rustfs") {
20509
+ Object.assign(plan.env, { RUSTFS_ACCESS_KEY: "hai-storage", RUSTFS_SECRET_KEY: password, RUSTFS_ADDRESS: "0.0.0.0:9000", RUSTFS_VOLUMES: "/data", RUSTFS_CONSOLE_ENABLE: "false" });
20510
+ } else {
20511
+ Object.assign(plan.env, { MINIO_ROOT_USER: "hai-storage", MINIO_ROOT_PASSWORD: password });
20512
+ }
20336
20513
  plan.env.HAI_STORAGE = JSON.stringify({
20337
20514
  type: "s3",
20338
20515
  endpoint: "http://storage:9000",
20339
20516
  bucket: "hai",
20340
20517
  region: "us-east-1",
20341
- accessKeyId: "hai",
20518
+ accessKeyId: "hai-storage",
20342
20519
  secretAccessKey: password,
20343
20520
  forcePathStyle: true
20344
20521
  });
@@ -20388,7 +20565,7 @@ function planChromaVecdb(plan, projectName, net) {
20388
20565
  plan.volumes[`${projectName}-vecdb-data`] = null;
20389
20566
  plan.env.HAI_VECDB = JSON.stringify({ type: "chroma", url: "http://vecdb:8000" });
20390
20567
  }
20391
- function planSidecars(appDir, projectName, scan, provisionEnv) {
20568
+ function planSidecars(appDir, projectName, scan, provisionEnv, options, s3Engine = "rustfs") {
20392
20569
  const plan = {
20393
20570
  services: [],
20394
20571
  composeServices: {},
@@ -20398,10 +20575,14 @@ function planSidecars(appDir, projectName, scan, provisionEnv) {
20398
20575
  dependsOn: [],
20399
20576
  secrets: []
20400
20577
  };
20578
+ if (!scan.requiredServices.some((service) => usesDefaultMiddleware(scan, service, options)))
20579
+ return plan;
20401
20580
  const secrets = resolveSidecarSecrets(appDir, projectName);
20402
20581
  const net = ["hai-app"];
20403
20582
  const backends = scan.serviceBackends;
20404
20583
  for (const service of scan.requiredServices) {
20584
+ if (!usesDefaultMiddleware(scan, service, options))
20585
+ continue;
20405
20586
  if (service === "db" && !provisionEnv.HAI_DB) {
20406
20587
  const backend = backends.db ?? "postgresql";
20407
20588
  if (backend === "mysql")
@@ -20411,9 +20592,13 @@ function planSidecars(appDir, projectName, scan, provisionEnv) {
20411
20592
  else
20412
20593
  planPostgresDb(plan, projectName, secrets.dbPassword, net);
20413
20594
  } else if (service === "cache" && !provisionEnv.HAI_CACHE) {
20414
- planRedisCache(plan, projectName, net);
20595
+ if ((backends.cache ?? "redis") === "redis")
20596
+ planRedisCache(plan, projectName, net);
20415
20597
  } else if (service === "storage" && !provisionEnv.HAI_STORAGE) {
20416
- planMinioStorage(plan, projectName, secrets.storagePassword, net);
20598
+ if ((backends.storage ?? "s3") === "s3")
20599
+ planS3Storage(plan, projectName, secrets.storagePassword, net, s3Engine);
20600
+ else
20601
+ addAppDataVolume(plan, projectName);
20417
20602
  } else if (service === "vecdb" && !provisionEnv.HAI_VECDB) {
20418
20603
  const backend = backends.vecdb ?? "qdrant";
20419
20604
  if (backend === "pgvector")
@@ -20424,16 +20609,39 @@ function planSidecars(appDir, projectName, scan, provisionEnv) {
20424
20609
  addAppDataVolume(plan, projectName);
20425
20610
  else
20426
20611
  planQdrantVecdb(plan, projectName, net);
20612
+ } else if (service === "ldap" && backends.ldap === "ldap" && !provisionEnv.HAI_IAM_LDAP) {
20613
+ const password = secrets.ldapPassword ?? secrets.dbPassword;
20614
+ plan.services.push("ldap");
20615
+ plan.dependsOn.push("ldap");
20616
+ plan.composeServices.ldap = { image: IMAGES.ldap, restart: "unless-stopped", env_file: [".env.runtime"], networks: net, volumes: [`${projectName}-ldap-data:/var/lib/ldap`, `${projectName}-ldap-config:/etc/ldap/slapd.d`] };
20617
+ plan.volumes[`${projectName}-ldap-data`] = null;
20618
+ plan.volumes[`${projectName}-ldap-config`] = null;
20619
+ Object.assign(plan.env, { LDAP_DOMAIN: "example.org", LDAP_ORGANISATION: "HAI", LDAP_ADMIN_PASSWORD: password, LDAP_TLS: "false", HAI_IAM_LOGIN_LDAP: "true", HAI_IAM_LDAP: JSON.stringify({ url: "ldap://ldap:389", bindDn: "cn=admin,dc=example,dc=org", bindPassword: password, searchBase: "dc=example,dc=org" }) });
20620
+ plan.secrets.push(password);
20621
+ } else if (service === "email" && (scan.middlewares ? scan.middlewares.some((item) => item.service === "email" && item.backend === "smtp" && (options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.email ?? "default") === "default") : backends.email === "smtp") && !provisionEnv.HAI_REACH_PROVIDERS) {
20622
+ plan.services.push("email");
20623
+ plan.dependsOn.push("email");
20624
+ plan.composeServices.email = { image: IMAGES.smtp, restart: "unless-stopped", networks: net };
20625
+ const requirements = scan.middlewares?.filter((item) => item.service === "email" && item.backend === "smtp" && ["default", void 0].includes(options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.email));
20626
+ if (requirements?.length) {
20627
+ for (const requirement of requirements)
20628
+ plan.env[requirement.envPrefix] = JSON.stringify({ name: requirement.config.name ?? "email", type: "smtp", host: "email", port: 1025, secure: false, from: "noreply@example.com" });
20629
+ } else if (!scan.middlewares) {
20630
+ plan.env.HAI_REACH_PROVIDERS_0 = JSON.stringify({ name: "email", type: "smtp", host: "email", port: 1025, secure: false, from: "noreply@example.com" });
20631
+ }
20427
20632
  }
20428
20633
  }
20429
20634
  return plan;
20430
20635
  }
20431
- function buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort) {
20432
- const sidecar = planSidecars(appDir, projectName, scan, provisionEnv);
20636
+ function buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv = {}, s3Engine = "rustfs") {
20637
+ const sidecar = planSidecars(appDir, projectName, scan, provisionEnv, options, s3Engine);
20433
20638
  const env = {
20434
20639
  NODE_ENV: "production",
20435
20640
  HOST: "0.0.0.0",
20436
20641
  PORT: String(containerPort),
20642
+ HAI_SERV_SERVER_HOST: "0.0.0.0",
20643
+ HAI_SERV_SERVER_PORT: String(containerPort),
20644
+ ...appEnv,
20437
20645
  ...sidecar.env,
20438
20646
  ...provisionEnv
20439
20647
  };
@@ -20455,21 +20663,55 @@ function generateComposeFile(options) {
20455
20663
  app.volumes = sidecar.appVolumes;
20456
20664
  if (sidecar.dependsOn.length > 0)
20457
20665
  app.depends_on = sidecar.dependsOn;
20666
+ const services = { app, ...sidecar.composeServices };
20667
+ if (services["storage-init"])
20668
+ services["storage-init"] = { ...services["storage-init"], image: imageRef };
20458
20669
  const compose = {
20459
- services: {
20460
- app,
20461
- ...sidecar.composeServices
20462
- },
20670
+ services,
20463
20671
  networks: {
20464
20672
  "hai-app": { name: `hai-${projectName}` }
20465
20673
  }
20466
20674
  };
20467
20675
  if (Object.keys(sidecar.volumes).length > 0)
20468
20676
  compose.volumes = sidecar.volumes;
20677
+ if (options.previousCompose && options.serviceModes) {
20678
+ const previous = parse$1(options.previousCompose);
20679
+ for (const [service, mode] of Object.entries(options.serviceModes)) {
20680
+ if (mode !== "keep")
20681
+ continue;
20682
+ for (const name of [service, `${service}-init`]) {
20683
+ if (previous.services?.[name]) {
20684
+ services[name] = previous.services[name];
20685
+ app.depends_on = [.../* @__PURE__ */ new Set([...app.depends_on ?? [], service])];
20686
+ }
20687
+ }
20688
+ }
20689
+ const previousMounts = previous.services?.app?.volumes;
20690
+ if (Array.isArray(previousMounts) && Object.values(options.serviceModes).includes("keep"))
20691
+ app.volumes = [.../* @__PURE__ */ new Set([...app.volumes ?? [], ...previousMounts])];
20692
+ compose.volumes = { ...previous.volumes, ...sidecar.volumes };
20693
+ }
20694
+ if (services["storage-init"]) {
20695
+ app.depends_on = {
20696
+ ...Object.fromEntries((app.depends_on ?? []).map((name) => [name, { condition: "service_started" }])),
20697
+ "storage-init": { condition: "service_completed_successfully" }
20698
+ };
20699
+ }
20469
20700
  return stringify(compose);
20470
20701
  }
20471
20702
  function serializeEnvFile(env) {
20472
- return `${Object.entries(env).map(([key, value]) => `${key}=${value}`).join("\n")}
20703
+ return `${Object.entries(env).map(([key, value]) => {
20704
+ let literal2 = value;
20705
+ if (/[\s#$'"]/.test(value)) {
20706
+ if (value.startsWith("{") || value.startsWith("["))
20707
+ literal2 = `'${value.replaceAll("'", "\\u0027")}'`;
20708
+ else if (!value.includes("'"))
20709
+ literal2 = `'${value}'`;
20710
+ else
20711
+ literal2 = JSON.stringify(value).replaceAll("$", "$$");
20712
+ }
20713
+ return `${key}=${literal2}`;
20714
+ }).join("\n")}
20473
20715
  `;
20474
20716
  }
20475
20717
  function generateDockerignore() {
@@ -20482,31 +20724,46 @@ function generateDockerignore() {
20482
20724
  **/test-results
20483
20725
  **/playwright-report
20484
20726
  **/.hai/deploy
20727
+ **/.env.local
20728
+ **/.env.*.local
20729
+ **/.env.runtime
20485
20730
  .git
20486
20731
  `;
20487
20732
  }
20488
- function generateFallbackDockerfile(packageName, containerPort) {
20733
+ function generateFallbackDockerfile(packageName, containerPort, options = {}) {
20734
+ if (!/^@?[a-z0-9][\w./-]*$/i.test(packageName) || !/^[a-z0-9][\w-]*$/i.test(options.environment ?? "prod"))
20735
+ throw new Error("Invalid container build target");
20736
+ const environment = options.environment ?? "prod";
20737
+ const coreEnv = environment === "prod" ? "production" : environment === "dev" ? "development" : environment === "test" ? "test" : "staging";
20489
20738
  return `# syntax=docker/dockerfile:1.7
20490
20739
  FROM node:24-bookworm-slim AS build
20740
+ ARG SQLITE_PREBUILD_MIRROR
20741
+ ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
20491
20742
  ENV PNPM_HOME=/pnpm
20492
20743
  ENV PATH=$PNPM_HOME:$PATH
20493
20744
  ENV CI=true
20745
+ ENV HAI_DEPLOY_ENVIRONMENT=${environment}
20746
+ ENV HAI_CORE_ENV=${coreEnv}
20494
20747
  WORKDIR /workspace
20495
20748
  RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
20496
20749
  COPY . .
20497
20750
  RUN pnpm install --frozen-lockfile --ignore-scripts
20498
- RUN pnpm --filter ${packageName} build
20751
+ RUN pnpm --filter ${packageName}... build
20752
+ RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
20499
20753
  RUN pnpm pm deploy --filter ${packageName} --prod /prod/app
20500
20754
 
20501
20755
  FROM node:24-bookworm-slim AS runtime
20502
20756
  ENV NODE_ENV=production
20757
+ ENV HAI_DEPLOY_ENVIRONMENT=${environment}
20758
+ ENV HAI_CORE_ENV=${coreEnv}
20503
20759
  ENV HOST=0.0.0.0
20504
20760
  ENV PORT=${containerPort}
20505
20761
  WORKDIR /app
20506
20762
  COPY --from=build --chown=node:node /prod/app ./
20763
+ RUN mkdir -p data && chown node:node data
20507
20764
  USER node
20508
20765
  EXPOSE ${containerPort}
20509
- CMD ["node", "build"]
20766
+ CMD ["node", ${JSON.stringify(options.entrypoint ?? "build")}]
20510
20767
  `;
20511
20768
  }
20512
20769
  var logger4 = core.logger.child({ module: "deploy", scope: "command" });
@@ -20521,10 +20778,10 @@ function maskSecrets(text, secrets) {
20521
20778
  return masked;
20522
20779
  }
20523
20780
  function runCommand(command, args, options = {}) {
20524
- return new Promise((resolve) => {
20781
+ return new Promise((resolve3) => {
20525
20782
  const child = spawn(command, args, {
20526
20783
  cwd: options.cwd,
20527
- env: options.env ? { ...process.env, ...options.env } : process.env,
20784
+ env: options.env ? { ...process2.env, ...options.env } : process2.env,
20528
20785
  shell: false
20529
20786
  });
20530
20787
  let stdout = "";
@@ -20537,7 +20794,7 @@ function runCommand(command, args, options = {}) {
20537
20794
  settled = true;
20538
20795
  if (timer)
20539
20796
  clearTimeout(timer);
20540
- resolve(result);
20797
+ resolve3(result);
20541
20798
  };
20542
20799
  if (options.timeoutMs && options.timeoutMs > 0) {
20543
20800
  timer = setTimeout(() => {
@@ -20574,12 +20831,14 @@ function runCommand(command, args, options = {}) {
20574
20831
  logger4.error("Command exited with non-zero code", {
20575
20832
  command,
20576
20833
  code: exitCode,
20577
- stderr: maskSecrets(stderr, options.secrets).slice(-500)
20834
+ stderr: maskSecrets(stderr, options.secrets).slice(-500),
20835
+ stdout: maskSecrets(stdout, options.secrets).slice(-3e3)
20578
20836
  });
20579
20837
  finish(err(
20580
20838
  HaiDeployError.REMOTE_COMMAND_FAILED,
20581
20839
  deployM("deploy_commandFailed", {
20582
- params: { command, error: maskSecrets(stderr || `exit ${exitCode}`, options.secrets).slice(-500) }
20840
+ params: { command, error: maskSecrets(`${stdout.slice(-2500)}
20841
+ ${stderr || `exit ${exitCode}`}`, options.secrets).slice(-3e3) }
20583
20842
  })
20584
20843
  ));
20585
20844
  });
@@ -20612,6 +20871,12 @@ async function buildImage(runtime, options) {
20612
20871
  const args = ["build", "-f", options.dockerfilePath, "-t", options.imageRef];
20613
20872
  if (runtime === "podman")
20614
20873
  args.push("--format", "docker", "--jobs", "1");
20874
+ if (runtime === "podman" && existsSync(`${options.dockerfilePath}.dockerignore`))
20875
+ args.push("--ignorefile", `${options.dockerfilePath}.dockerignore`);
20876
+ if (runtime === "podman" && options.httpProxy === false)
20877
+ args.push("--http-proxy=false");
20878
+ for (const [key, value] of Object.entries(options.buildArgs ?? {}))
20879
+ args.push("--build-arg", `${key}=${value}`);
20615
20880
  args.push(options.contextDir);
20616
20881
  logger5.info("Building container image", { runtime, imageRef: options.imageRef });
20617
20882
  const result = await runCommand(runtime, args, { cwd: options.contextDir });
@@ -20705,6 +20970,9 @@ function createSshExecutor(config2) {
20705
20970
  var logger7 = core.logger.child({ module: "deploy", scope: "provider-docker-ssh" });
20706
20971
  var HEALTH_POLL_INTERVAL = 3e3;
20707
20972
  var HEALTH_MAX_ATTEMPTS = 40;
20973
+ function shellArgument(value) {
20974
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
20975
+ }
20708
20976
  function makeDeploymentId() {
20709
20977
  const now = /* @__PURE__ */ new Date();
20710
20978
  const pad = (n) => String(n).padStart(2, "0");
@@ -20716,7 +20984,7 @@ function parseLoadedImageRef(stdout, fallback) {
20716
20984
  return match ? match[1] : fallback;
20717
20985
  }
20718
20986
  function sleep(ms) {
20719
- return new Promise((resolve) => setTimeout(resolve, ms));
20987
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
20720
20988
  }
20721
20989
  function readPackageName(appDir) {
20722
20990
  try {
@@ -20739,7 +21007,9 @@ function resolveWorkspaceRoot(appDir) {
20739
21007
  }
20740
21008
  return appDir;
20741
21009
  }
20742
- function createDockerSshProvider(config2, localRuntimePref = "auto") {
21010
+ function createDockerSshProvider(config2, container) {
21011
+ const localRuntimePref = container?.runtime ?? "auto";
21012
+ const httpProxy = container?.httpProxy ?? true;
20743
21013
  const ssh = createSshExecutor(config2.ssh);
20744
21014
  const baseDir = config2.remote.baseDir;
20745
21015
  async function detectRemoteRuntime() {
@@ -20772,7 +21042,7 @@ function createDockerSshProvider(config2, localRuntimePref = "auto") {
20772
21042
  return false;
20773
21043
  }
20774
21044
  const deploy2 = async (context) => {
20775
- const { appDir, projectName, scan, provisionEnv } = context;
21045
+ const { appDir, projectName, scan, provisionEnv, appEnv, options } = context;
20776
21046
  const deploymentId = makeDeploymentId();
20777
21047
  const containerPort = config2.expose.containerPort;
20778
21048
  const hostPort = config2.expose.hostPort;
@@ -20781,7 +21051,7 @@ function createDockerSshProvider(config2, localRuntimePref = "auto") {
20781
21051
  if (!runtimeResult.success)
20782
21052
  return err(runtimeResult.error);
20783
21053
  const localRuntime = runtimeResult.data;
20784
- const artifactDir = join(appDir, ".hai", "deploy");
21054
+ const artifactDir = join(appDir, ".hai", "deploy", options?.environment ?? "prod");
20785
21055
  mkdirSync(artifactDir, { recursive: true });
20786
21056
  const appDockerfile = join(appDir, "Dockerfile");
20787
21057
  let dockerfilePath;
@@ -20791,21 +21061,33 @@ function createDockerSshProvider(config2, localRuntimePref = "auto") {
20791
21061
  contextDir = appDir;
20792
21062
  } else {
20793
21063
  dockerfilePath = join(artifactDir, "Dockerfile");
20794
- writeFileSync(dockerfilePath, generateFallbackDockerfile(readPackageName(appDir), containerPort));
21064
+ writeFileSync(dockerfilePath, generateFallbackDockerfile(readPackageName(appDir), containerPort, { environment: options?.environment, entrypoint: scan.isSvelteKit ? "build" : "dist/index.js" }));
20795
21065
  writeFileSync(`${dockerfilePath}.dockerignore`, generateDockerignore());
20796
21066
  contextDir = resolveWorkspaceRoot(appDir);
20797
21067
  logger7.info("Generated fallback Dockerfile", { dockerfilePath, contextDir });
20798
21068
  }
21069
+ const { env, sidecar } = buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv, container?.s3Engine);
21070
+ const envPath = join(artifactDir, ".env.runtime");
21071
+ const previousEnv = existsSync(envPath) ? parseEnv(readFileSync(envPath, "utf8")) : {};
21072
+ Object.assign(env, { ...previousEnv, ...env });
21073
+ writeFileSync(envPath, serializeEnvFile(env), { mode: 384 });
21074
+ if (options?.environment) {
21075
+ const environment = loadDeploymentEnvironment({ environment: options.environment, envDir: options.envDir ?? scan.envDir ?? appDir });
21076
+ if (!environment.success)
21077
+ return environment;
21078
+ const saved = saveDeploymentEnvironment(environment.data, { HAI_DEPLOY_ENVIRONMENT: environment.data.environment, HAI_CORE_ENV: environment.data.values.HAI_CORE_ENV, ...sidecar.env, ...provisionEnv });
21079
+ if (!saved.success)
21080
+ return saved;
21081
+ }
20799
21082
  const buildResult = await buildImage(localRuntime, {
20800
21083
  contextDir,
20801
21084
  dockerfilePath,
20802
- imageRef: localImageTag
21085
+ imageRef: localImageTag,
21086
+ buildArgs: { HAI_DEPLOY_ENVIRONMENT: options?.environment ?? "prod", HAI_CORE_ENV: env.HAI_CORE_ENV ?? "production", SQLITE_PREBUILD_MIRROR: container?.sqlitePrebuildMirror ?? "" },
21087
+ httpProxy
20803
21088
  });
20804
21089
  if (!buildResult.success)
20805
21090
  return err(buildResult.error);
20806
- const { env, sidecar } = buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort);
20807
- const envPath = join(artifactDir, ".env.runtime");
20808
- writeFileSync(envPath, serializeEnvFile(env), { mode: 384 });
20809
21091
  const tarPath = join(artifactDir, `${projectName}.tar`);
20810
21092
  const saveResult = await saveImage(localRuntime, localImageTag, tarPath);
20811
21093
  if (!saveResult.success)
@@ -20818,20 +21100,20 @@ function createDockerSshProvider(config2, localRuntimePref = "auto") {
20818
21100
  return err(remoteRuntimeResult.error);
20819
21101
  const remoteRuntime = remoteRuntimeResult.data;
20820
21102
  const remoteDir = `${baseDir}/${projectName}`;
20821
- const prepareResult = await ssh.exec(`mkdir -p ${remoteDir} && chmod 700 ${remoteDir}`);
21103
+ const prepareResult = await ssh.exec(`mkdir -p -- ${shellArgument(remoteDir)} && chmod 700 -- ${shellArgument(remoteDir)}`);
20822
21104
  if (!prepareResult.success)
20823
21105
  return err(prepareResult.error);
20824
21106
  const remoteTar = `${remoteDir}/image.tar`;
20825
21107
  const uploadTarResult = await ssh.upload(tarPath, remoteTar);
20826
21108
  if (!uploadTarResult.success)
20827
21109
  return err(uploadTarResult.error);
20828
- const loadResult = await ssh.exec(`${remoteRuntime} load -i ${remoteTar}`, { timeoutMs: 3e5 });
21110
+ const loadResult = await ssh.exec(`${remoteRuntime} load -i ${shellArgument(remoteTar)}`, { timeoutMs: 3e5 });
20829
21111
  if (!loadResult.success)
20830
21112
  return err(loadResult.error);
20831
21113
  const loadedRef = parseLoadedImageRef(loadResult.data.stdout, localImageTag);
20832
21114
  const remoteImageRef = remoteRuntime === "podman" ? `localhost/${localImageTag}` : localImageTag;
20833
21115
  if (loadedRef !== remoteImageRef) {
20834
- const tagResult = await ssh.exec(`${remoteRuntime} tag ${loadedRef} ${remoteImageRef}`);
21116
+ const tagResult = await ssh.exec(`${remoteRuntime} tag ${shellArgument(loadedRef)} ${shellArgument(remoteImageRef)}`);
20835
21117
  if (!tagResult.success)
20836
21118
  return err(tagResult.error);
20837
21119
  }
@@ -20840,7 +21122,9 @@ function createDockerSshProvider(config2, localRuntimePref = "auto") {
20840
21122
  imageRef: remoteImageRef,
20841
21123
  hostPort,
20842
21124
  containerPort,
20843
- sidecar
21125
+ sidecar,
21126
+ serviceModes: options?.serviceModes,
21127
+ previousCompose: existsSync(join(artifactDir, "compose.yml")) ? readFileSync(join(artifactDir, "compose.yml"), "utf8") : void 0
20844
21128
  });
20845
21129
  const composePath = join(artifactDir, "compose.yml");
20846
21130
  writeFileSync(composePath, composeContent);
@@ -20850,20 +21134,33 @@ function createDockerSshProvider(config2, localRuntimePref = "auto") {
20850
21134
  const uploadEnvResult = await ssh.upload(envPath, `${remoteDir}/.env.runtime`);
20851
21135
  if (!uploadEnvResult.success)
20852
21136
  return err(uploadEnvResult.error);
21137
+ const startScript = `#!/bin/sh
21138
+ set -eu
21139
+ cd "$(dirname "$0")"
21140
+ export HAI_DEPLOY_ENVIRONMENT=${options?.environment ?? "prod"}
21141
+ export HAI_CORE_ENV=${shellArgument(env.HAI_CORE_ENV ?? "production")}
21142
+ exec ${remoteRuntime} compose --env-file .env.runtime -p hai-${projectName} up -d
21143
+ `;
21144
+ const startName = `start-${options?.environment ?? "prod"}.sh`;
21145
+ const startPath = join(artifactDir, startName);
21146
+ writeFileSync(startPath, startScript, { mode: 448 });
21147
+ const uploadStart = await ssh.upload(startPath, `${remoteDir}/${startName}`);
21148
+ if (!uploadStart.success)
21149
+ return uploadStart;
20853
21150
  const secretMask = sidecar.secrets;
20854
- const chmodResult = await ssh.exec(`chmod 600 ${remoteDir}/.env.runtime`, { secrets: secretMask });
21151
+ const chmodResult = await ssh.exec(`chmod 600 -- ${shellArgument(`${remoteDir}/.env.runtime`)}`, { secrets: secretMask });
20855
21152
  if (!chmodResult.success)
20856
21153
  return err(chmodResult.error);
20857
21154
  const upResult = await ssh.exec(
20858
- `cd ${remoteDir} && ${remoteRuntime} compose -p hai-${projectName} up -d`,
21155
+ `cd ${shellArgument(remoteDir)} && sh ${shellArgument(startName)}`,
20859
21156
  { timeoutMs: 3e5, secrets: secretMask }
20860
21157
  );
20861
21158
  if (!upResult.success)
20862
21159
  return err(upResult.error);
20863
- await ssh.exec(`rm -f ${remoteTar}`);
21160
+ await ssh.exec(`rm -f -- ${shellArgument(remoteTar)}`);
20864
21161
  const url2 = `http://${config2.ssh.host}:${hostPort}`;
20865
21162
  logger7.info("Running health check", { url: url2 });
20866
- const healthy = await healthCheck(`${url2}/`);
21163
+ const healthy = await healthCheck(`${url2}${scan.isSvelteKit ? "/" : "/health"}`);
20867
21164
  if (!healthy) {
20868
21165
  return err(
20869
21166
  HaiDeployError.HEALTH_CHECK_FAILED,
@@ -21118,7 +21415,7 @@ async function pollDeploymentStatus(token, deploymentId) {
21118
21415
  if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
21119
21416
  return "error";
21120
21417
  }
21121
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
21418
+ await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL));
21122
21419
  }
21123
21420
  return "building";
21124
21421
  }
@@ -21656,15 +21953,31 @@ function normalizeProjectName(name) {
21656
21953
  const normalized = name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
21657
21954
  return normalized || "app";
21658
21955
  }
21659
- function mergeProvisionEnv(results) {
21956
+ function mergeProvisionEnv(results, scan, options) {
21660
21957
  let env = {};
21661
- const reachProviders = [];
21958
+ const reachRequirements = scan?.middlewares?.filter((item) => item.configName === "reach") ?? [];
21959
+ const reachProviders = reachRequirements.map((item) => item.config);
21960
+ let hasReachOutput = false;
21662
21961
  for (const prov of results) {
21663
- if (prov.envVars.HAI_REACH_PROVIDERS)
21664
- reachProviders.push(...JSON.parse(prov.envVars.HAI_REACH_PROVIDERS));
21962
+ if (prov.envVars.HAI_REACH_PROVIDERS) {
21963
+ hasReachOutput = true;
21964
+ const supplied = JSON.parse(prov.envVars.HAI_REACH_PROVIDERS);
21965
+ for (const provider of supplied) {
21966
+ const targets = reachRequirements.filter((item) => {
21967
+ const mode = options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.[item.service];
21968
+ return item.service === prov.serviceType && (!mode || mode === "default");
21969
+ });
21970
+ if (targets.length) {
21971
+ for (const target of targets)
21972
+ reachProviders[reachRequirements.indexOf(target)] = { ...provider, name: target.config.name ?? provider.name };
21973
+ } else {
21974
+ reachProviders.push(provider);
21975
+ }
21976
+ }
21977
+ }
21665
21978
  env = { ...env, ...prov.envVars };
21666
21979
  }
21667
- if (reachProviders.length > 0)
21980
+ if (hasReachOutput)
21668
21981
  env.HAI_REACH_PROVIDERS = JSON.stringify(reachProviders);
21669
21982
  return env;
21670
21983
  }
@@ -21699,7 +22012,7 @@ var deploy = {
21699
22012
  let provider = null;
21700
22013
  let containerProvider = null;
21701
22014
  if (parsed.provider.type === "docker-ssh") {
21702
- containerProvider = createDockerSshProvider(parsed.provider, parsed.container?.runtime);
22015
+ containerProvider = createDockerSshProvider(parsed.provider, parsed.container);
21703
22016
  logger14.info("Container deploy provider ready", { provider: "docker-ssh", host: parsed.provider.ssh.host });
21704
22017
  } else {
21705
22018
  provider = createProviderByType(parsed.provider.type);
@@ -21777,17 +22090,19 @@ var deploy = {
21777
22090
  get isInitialized() {
21778
22091
  return currentProvider !== null || currentContainerProvider !== null;
21779
22092
  },
21780
- async scan(appDir) {
22093
+ async scan(appDir, options) {
21781
22094
  logger14.debug("Scanning application", { appDir });
21782
- return scanApp(appDir);
22095
+ return scanApp(appDir, options);
21783
22096
  },
21784
- async provisionAll(projectName) {
22097
+ async provisionAll(projectName, services) {
21785
22098
  if (!currentProvider && !currentContainerProvider) {
21786
22099
  return notInitialized.result();
21787
22100
  }
21788
22101
  logger14.info("Provisioning all services", { projectName, count: currentProvisioners.size });
21789
22102
  const results = [];
21790
22103
  for (const [serviceType, provisioner] of currentProvisioners) {
22104
+ if (services && !services.includes(serviceType))
22105
+ continue;
21791
22106
  logger14.debug("Provisioning service", { serviceType, provisioner: provisioner.name });
21792
22107
  const provResult = await provisioner.provision(projectName);
21793
22108
  if (!provResult.success) {
@@ -21816,15 +22131,26 @@ var deploy = {
21816
22131
  if (!currentProvider && !currentContainerProvider) {
21817
22132
  return notInitialized.result();
21818
22133
  }
21819
- logger14.info("Starting deployment", { appDir, options });
21820
- const scanResult = await scanApp(appDir);
22134
+ logger14.info("Starting deployment", { appDir, environment: options?.environment, projectName: options?.projectName });
22135
+ const scanOptions = { ...options, environment: options?.environment ?? currentConfig?.environment };
22136
+ const scanResult = await scanApp(appDir, scanOptions);
21821
22137
  if (!scanResult.success) {
21822
22138
  return err(scanResult.error);
21823
22139
  }
21824
22140
  const scan = scanResult.data;
21825
- const projectName = options?.projectName ?? scan.appName;
22141
+ appDir = scan.appDir ?? appDir;
22142
+ const environment = loadDeploymentEnvironment({ environment: scanOptions.environment ?? "prod", envDir: scan.envDir ?? appDir, requireFile: scanOptions.environment !== void 0 });
22143
+ if (!environment.success)
22144
+ return environment;
22145
+ const appEnv = Object.fromEntries(Object.entries(environment.data.values).filter(([key]) => !key.startsWith("HAI_CLI_") && (!key.startsWith("HAI_DEPLOY_") || key === "HAI_DEPLOY_ENVIRONMENT")));
22146
+ const projectName = `${options?.projectName ?? scan.appName}${scanOptions.environment ? `-${environment.data.environment}` : ""}`;
22147
+ options = {
22148
+ ...options,
22149
+ ...scanOptions.environment ? { environment: environment.data.environment } : {},
22150
+ ...options?.skipProvision ? { serviceModes: Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) } : {}
22151
+ };
21826
22152
  if (currentContainerProvider) {
21827
- return deployContainerApp(currentContainerProvider, appDir, scan, projectName, options);
22153
+ return deployContainerApp(currentContainerProvider, appDir, scan, projectName, options, appEnv);
21828
22154
  }
21829
22155
  if (!currentProvider) {
21830
22156
  return notInitialized.result();
@@ -21837,15 +22163,20 @@ var deploy = {
21837
22163
  })
21838
22164
  );
21839
22165
  }
21840
- let allEnvVars = {};
22166
+ let allEnvVars = appEnv;
21841
22167
  const recovery = { projectName, stage: "provision", resources: [] };
21842
22168
  if (!options?.skipProvision) {
21843
- const provResults = await deploy.provisionAll(projectName);
22169
+ const provResults = await deploy.provisionAll(projectName, options?.serviceModes ? scan.requiredServices.filter((service) => usesDefaultMiddleware(scan, service, options)) : void 0);
21844
22170
  if (!provResults.success) {
21845
22171
  return err(provResults.error);
21846
22172
  }
21847
22173
  recovery.resources = provResults.data.map(({ envVars: _envVars, ...resource }) => resource);
21848
- allEnvVars = mergeProvisionEnv(provResults.data);
22174
+ allEnvVars = { ...allEnvVars, ...mergeProvisionEnv(provResults.data, scan, options) };
22175
+ if (options?.environment) {
22176
+ const saved = saveDeploymentEnvironment(environment.data, mergeProvisionEnv(provResults.data, scan, options));
22177
+ if (!saved.success)
22178
+ return recoveryFailure(saved.error, { ...recovery, stage: "save-env" });
22179
+ }
21849
22180
  }
21850
22181
  const projectResult = await currentProvider.createProject(projectName);
21851
22182
  if (!projectResult.success) {
@@ -21882,7 +22213,7 @@ var deploy = {
21882
22213
  return ok(result);
21883
22214
  }
21884
22215
  };
21885
- async function deployContainerApp(provider, appDir, scan, rawProjectName, options) {
22216
+ async function deployContainerApp(provider, appDir, scan, rawProjectName, options, appEnv = {}) {
21886
22217
  const projectName = normalizeProjectName(rawProjectName);
21887
22218
  if (scan.isSvelteKit && !scan.nodeAdapterInstalled) {
21888
22219
  return err(
@@ -21892,15 +22223,60 @@ async function deployContainerApp(provider, appDir, scan, rawProjectName, option
21892
22223
  }
21893
22224
  let provisionEnv = {};
21894
22225
  if (!options?.skipProvision) {
21895
- const provResults = await deploy.provisionAll(projectName);
22226
+ const provResults = await deploy.provisionAll(projectName, options?.serviceModes ? scan.requiredServices.filter((service) => usesDefaultMiddleware(scan, service, options)) : void 0);
21896
22227
  if (!provResults.success) {
21897
22228
  return err(provResults.error);
21898
22229
  }
21899
- provisionEnv = mergeProvisionEnv(provResults.data);
22230
+ provisionEnv = mergeProvisionEnv(provResults.data, scan, options);
21900
22231
  }
21901
- return provider.deploy({ appDir, projectName, scan, provisionEnv });
22232
+ return provider.deploy({ appDir, projectName, scan, provisionEnv, appEnv, options });
22233
+ }
22234
+
22235
+ // ../deploy/src/deploy-middleware.ts
22236
+ var field = (name, defaultValue, kind, secret = false) => ({ name, defaultValue, kind, secret });
22237
+ var required2 = (name, defaultValue, kind) => ({ ...field(name, defaultValue, kind), required: true });
22238
+ var DEPLOY_MIDDLEWARE = {
22239
+ db: [
22240
+ { backend: "sqlite", label: "SQLite", purpose: "deploy_purposeSqlite", fields: [required2("database", "./data/app.db")] },
22241
+ ...["postgresql", "mysql"].map((backend) => ({
22242
+ backend,
22243
+ label: backend === "mysql" ? "MySQL" : "PostgreSQL",
22244
+ purpose: "deploy_purposeDatabase",
22245
+ provisioner: backend === "postgresql" ? "neon" : void 0,
22246
+ fields: [field("url", "", "url", true), required2("host", "localhost"), required2("port", backend === "mysql" ? "3306" : "5432", "port"), required2("database", "hai"), field("user", "hai"), field("password", "", void 0, true), field("ssl", "false")]
22247
+ }))
22248
+ ],
22249
+ cache: [
22250
+ { backend: "memory", label: "Memory", purpose: "deploy_purposeMemory", fields: [] },
22251
+ { backend: "redis", label: "Redis", purpose: "deploy_purposeCache", provisioner: "upstash", fields: [field("url", "", "url", true), required2("host", "localhost"), required2("port", "6379", "port"), field("password", "", void 0, true), { ...field("db", "0", "number"), maximum: 15 }, field("tls", "false", "boolean"), field("keyPrefix"), field("connectTimeout", "10000", "number"), field("commandTimeout", "5000", "number")] }
22252
+ ],
22253
+ storage: [
22254
+ { backend: "local", label: "Local filesystem", purpose: "deploy_purposeLocal", fields: [required2("root", "./data/storage")] },
22255
+ { backend: "s3", label: "S3 / RustFS / MinIO / Cloudflare R2", purpose: "deploy_purposeStorage", provisioner: "cloudflare-r2", fields: [field("endpoint", "", "url"), required2("bucket", "hai"), required2("region", "us-east-1"), required2("accessKeyId"), { ...required2("secretAccessKey"), secret: true }, field("forcePathStyle", "true", "boolean")] }
22256
+ ],
22257
+ vecdb: [
22258
+ { backend: "lancedb", label: "LanceDB", purpose: "deploy_purposeLocal", fields: [required2("path", "./data/vectors")] },
22259
+ { backend: "qdrant", label: "Qdrant", purpose: "deploy_purposeVector", fields: [required2("url", "http://localhost:6333", "url"), field("apiKey", "", void 0, true)] },
22260
+ { backend: "pgvector", label: "PostgreSQL / pgvector", purpose: "deploy_purposeVector", fields: [field("url", "", "url", true), required2("host", "localhost"), required2("port", "5432", "port"), required2("database", "hai"), field("user", "hai"), field("password", "", void 0, true)] },
22261
+ { backend: "chroma", label: "Chroma", purpose: "deploy_purposeVector", fields: [required2("url", "http://localhost:8000", "url")] }
22262
+ ],
22263
+ ldap: [
22264
+ { backend: "disabled", label: "Disabled", purpose: "deploy_purposeDisabled", fields: [] },
22265
+ { backend: "ldap", label: "LDAP / OpenLDAP", purpose: "deploy_purposeLdap", fields: [required2("url", "ldap://localhost:389", "url"), required2("bindDn", "cn=admin,dc=example,dc=org"), { ...required2("bindPassword"), secret: true }, required2("searchBase", "dc=example,dc=org"), field("searchFilter", "(uid={{username}})"), field("usernameAttribute", "uid"), field("emailAttribute", "mail"), field("displayNameAttribute", "cn"), field("useTls", "false", "boolean"), { ...field("connectTimeout", "5000", "number"), minimum: 1e3 }] }
22266
+ ],
22267
+ email: [
22268
+ { backend: "console", label: "Console", purpose: "deploy_purposeConsole", fields: [] },
22269
+ { backend: "smtp", label: "SMTP", purpose: "deploy_purposeEmail", provisioner: "resend", fields: [required2("host", "localhost"), required2("port", "465", "port"), field("secure", "true", "boolean"), field("user"), field("pass", "", void 0, true), required2("from", "noreply@example.com")] },
22270
+ { backend: "api", label: "HTTP notification API", purpose: "deploy_purposeEmail", fields: [required2("url", "", "url"), field("method", "POST"), field("headers"), field("timeout", "10000", "number")] }
22271
+ ],
22272
+ sms: [
22273
+ { backend: "aliyun-sms", label: "Aliyun SMS", purpose: "deploy_purposeSms", provisioner: "aliyun", fields: [required2("accessKeyId"), { ...required2("accessKeySecret"), secret: true }, required2("signName"), field("endpoint", "dysmsapi.aliyuncs.com")] }
22274
+ ]
22275
+ };
22276
+ function describeMiddlewareBackend(backend) {
22277
+ return deployM(backend.purpose);
21902
22278
  }
21903
22279
 
21904
- export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy, getDeployRecovery };
21905
- //# sourceMappingURL=src-DUGLX4YB.js.map
21906
- //# sourceMappingURL=src-DUGLX4YB.js.map
22280
+ export { DEPLOY_MIDDLEWARE, DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy, describeMiddlewareBackend, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, saveDeploymentEnvironment };
22281
+ //# sourceMappingURL=src-6PPBKQ6Q.js.map
22282
+ //# sourceMappingURL=src-6PPBKQ6Q.js.map