@h-ai/cli 0.1.0-alpha.53 → 0.1.0-alpha.54

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 (36) hide show
  1. package/README.md +14 -0
  2. package/dist/{chunk-U4PA632G.js → chunk-FVDXJYMO.js} +34 -67
  3. package/dist/chunk-FVDXJYMO.js.map +1 -0
  4. package/dist/cli-config-templates-WGN2I23J.js +5 -0
  5. package/dist/{cli-config-templates-GSXKLGU5.js.map → cli-config-templates-WGN2I23J.js.map} +1 -1
  6. package/dist/index.js +174 -105
  7. package/dist/index.js.map +1 -1
  8. package/dist/{src-QNEJJ3BV.js → src-6ZBWDBUB.js} +127 -87
  9. package/dist/{src-QNEJJ3BV.js.map → src-6ZBWDBUB.js.map} +1 -1
  10. package/package.json +13 -4
  11. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +1 -0
  12. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -0
  13. package/templates/apps/api/package.json.hbs +1 -0
  14. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -0
  15. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +1 -0
  16. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +1 -0
  17. package/templates/apps/fullstack/package.json.hbs +1 -0
  18. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +1 -0
  19. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +1 -0
  20. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +1 -0
  21. package/templates/base/eslint.config.js.hbs +1 -0
  22. package/templates/base/package.json.hbs +3 -1
  23. package/templates/skills/hai-ai/reference.md +5 -1
  24. package/templates/skills/hai-app-review/SKILL.md +6 -0
  25. package/templates/skills/hai-build/SKILL.md +8 -0
  26. package/templates/skills/hai-cache/SKILL.md +2 -0
  27. package/templates/skills/hai-capacitor/SKILL.md +2 -0
  28. package/templates/skills/hai-ci/SKILL.md +12 -0
  29. package/templates/skills/hai-core/SKILL.md +2 -0
  30. package/templates/skills/hai-crypto/SKILL.md +2 -0
  31. package/templates/skills/hai-deploy/SKILL.md +12 -0
  32. package/templates/skills/hai-iam/SKILL.md +6 -0
  33. package/templates/skills/hai-scheduler/SKILL.md +12 -0
  34. package/templates/skills/hai-ui/SKILL.md +4 -0
  35. package/dist/chunk-U4PA632G.js.map +0 -1
  36. package/dist/cli-config-templates-GSXKLGU5.js +0 -5
@@ -14542,7 +14542,9 @@ var en_US_default = {
14542
14542
  deploy_credentialMissing: "Missing credential fields: {fields}",
14543
14543
  deploy_apiError: "{service} API error ({status})",
14544
14544
  deploy_fileUploadFailed: "File upload failed ({status})",
14545
- deploy_provisionNoResult: "No connection info returned from {service}"
14545
+ deploy_provisionNoResult: "No connection info returned from {service}",
14546
+ deploy_recoveryFailure: "Deployment stopped at {stage}; inspect the recovery details.",
14547
+ deploy_recoverySuggestion: "Inspect listed resources in the provider console before retrying with the same project name. Unknown means the request may have created a resource. Reused resources are never deleted automatically; remove newly created resources manually only after checking their use."
14546
14548
  };
14547
14549
 
14548
14550
  // ../deploy/messages/zh-CN.json
@@ -14570,7 +14572,9 @@ var zh_CN_default2 = {
14570
14572
  deploy_credentialMissing: "\u7F3A\u5C11\u51ED\u8BC1\u5B57\u6BB5\uFF1A{fields}",
14571
14573
  deploy_apiError: "{service} API \u8BF7\u6C42\u5931\u8D25\uFF08{status}\uFF09",
14572
14574
  deploy_fileUploadFailed: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF08{status}\uFF09",
14573
- deploy_provisionNoResult: "{service} \u672A\u8FD4\u56DE\u8FDE\u63A5\u4FE1\u606F"
14575
+ deploy_provisionNoResult: "{service} \u672A\u8FD4\u56DE\u8FDE\u63A5\u4FE1\u606F",
14576
+ deploy_recoveryFailure: "\u90E8\u7F72\u5728 {stage} \u9636\u6BB5\u505C\u6B62\uFF0C\u8BF7\u67E5\u770B\u6062\u590D\u6E05\u5355\u3002",
14577
+ deploy_recoverySuggestion: "\u91CD\u8BD5\u524D\u5728\u670D\u52A1\u5546\u63A7\u5236\u53F0\u6838\u5BF9\u6E05\u5355\uFF0C\u5E76\u4F7F\u7528\u76F8\u540C\u9879\u76EE\u540D\u3002unknown \u8868\u793A\u8BF7\u6C42\u53EF\u80FD\u5DF2\u521B\u5EFA\u8D44\u6E90\u3002\u590D\u7528\u8D44\u6E90\u4E0D\u4F1A\u81EA\u52A8\u5220\u9664\uFF1B\u65B0\u5EFA\u8D44\u6E90\u4EC5\u5728\u786E\u8BA4\u672A\u88AB\u4F7F\u7528\u540E\u4EBA\u5DE5\u6E05\u7406\u3002"
14574
14578
  };
14575
14579
 
14576
14580
  // ../deploy/src/deploy-i18n.ts
@@ -14598,15 +14602,19 @@ var UpstashServiceSchema = external_exports.object({
14598
14602
  });
14599
14603
  var R2ServiceSchema = external_exports.object({
14600
14604
  provisioner: external_exports.literal("cloudflare-r2"),
14605
+ accessKeyId: external_exports.string().min(1),
14606
+ secretAccessKey: external_exports.string().min(1),
14601
14607
  accountId: external_exports.string().min(1),
14602
14608
  apiToken: external_exports.string().min(1)
14603
14609
  });
14604
14610
  var ResendServiceSchema = external_exports.object({
14605
14611
  provisioner: external_exports.literal("resend"),
14612
+ from: external_exports.string().email(),
14606
14613
  apiKey: external_exports.string().min(1)
14607
14614
  });
14608
14615
  var AliyunSmsServiceSchema = external_exports.object({
14609
14616
  provisioner: external_exports.literal("aliyun"),
14617
+ signName: external_exports.string().min(1),
14610
14618
  accessKeyId: external_exports.string().min(1),
14611
14619
  accessKeySecret: external_exports.string().min(1)
14612
14620
  });
@@ -14761,13 +14769,13 @@ function saveCredentials(entries) {
14761
14769
  }
14762
14770
  }
14763
14771
  var logger2 = core.logger.child({ module: "deploy", scope: "functions" });
14764
- function buildApp(appDir, buildCommand) {
14772
+ function buildApp(appDir, buildCommand, envVars = {}) {
14765
14773
  logger2.info("Building application", { appDir, buildCommand });
14766
14774
  try {
14767
14775
  execSync(buildCommand, {
14768
14776
  cwd: appDir,
14769
14777
  stdio: "inherit",
14770
- env: { ...process2.env }
14778
+ env: { ...process2.env, ...envVars }
14771
14779
  });
14772
14780
  return ok(void 0);
14773
14781
  } catch (error51) {
@@ -14793,6 +14801,32 @@ function resolveOutputDir(appDir, providerType) {
14793
14801
  return join(appDir, "build");
14794
14802
  }
14795
14803
  }
14804
+ var RecoverySchema = external_exports.object({
14805
+ projectName: external_exports.string(),
14806
+ stage: external_exports.string(),
14807
+ platformProjectId: external_exports.string().optional(),
14808
+ resources: external_exports.array(external_exports.object({
14809
+ serviceType: external_exports.enum(["db", "cache", "storage", "email", "sms"]),
14810
+ provisionerName: external_exports.string(),
14811
+ resourceInfo: external_exports.string(),
14812
+ resourceStatus: external_exports.enum(["created", "reused", "unknown"])
14813
+ }))
14814
+ });
14815
+ function getDeployRecovery(error51) {
14816
+ const parsed = RecoverySchema.safeParse(error51.ext?.recovery);
14817
+ return parsed.success ? parsed.data : void 0;
14818
+ }
14819
+ function recoveryFailure(error51, recovery) {
14820
+ return err({
14821
+ code: error51.code,
14822
+ httpStatus: error51.httpStatus,
14823
+ system: error51.system,
14824
+ module: error51.module,
14825
+ message: deployM("deploy_recoveryFailure", { params: { stage: recovery.stage } }),
14826
+ suggestion: deployM("deploy_recoverySuggestion"),
14827
+ ext: { recovery }
14828
+ });
14829
+ }
14796
14830
  var logger3 = core.logger.child({ module: "deploy", scope: "scanner" });
14797
14831
  function readYaml(filePath) {
14798
14832
  if (!existsSync(filePath)) {
@@ -15204,6 +15238,7 @@ var logger5 = core.logger.child({ module: "deploy", scope: "provisioner-aliyun"
15204
15238
  function createAliyunProvisioner() {
15205
15239
  let accessKey = null;
15206
15240
  let secretKey = null;
15241
+ let signName = "";
15207
15242
  return {
15208
15243
  name: "aliyun",
15209
15244
  serviceType: "sms",
@@ -15212,11 +15247,12 @@ function createAliyunProvisioner() {
15212
15247
  try {
15213
15248
  const akId = credentials.accessKeyId ?? credentials.access_key_id ?? credentials.access_key ?? "";
15214
15249
  const akSecret = credentials.accessKeySecret ?? credentials.access_key_secret ?? credentials.secret_key ?? "";
15215
- if (!akId || !akSecret) {
15216
- throw new Error(deployM("deploy_credentialMissing", { params: { fields: "access_key_id, access_key_secret" } }));
15250
+ if (!akId || !akSecret || !credentials.signName) {
15251
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "accessKeyId, accessKeySecret, signName" } }));
15217
15252
  }
15218
15253
  accessKey = akId;
15219
15254
  secretKey = akSecret;
15255
+ signName = credentials.signName;
15220
15256
  logger5.info("Aliyun authenticated", { accessKeyId: `${akId.slice(0, 6)}***` });
15221
15257
  return ok(akId);
15222
15258
  } catch (error51) {
@@ -15241,9 +15277,9 @@ function createAliyunProvisioner() {
15241
15277
  return ok({
15242
15278
  serviceType: "sms",
15243
15279
  provisionerName: "aliyun",
15280
+ resourceStatus: "reused",
15244
15281
  envVars: {
15245
- HAI_REACH_SMS_ACCESS_KEY: accessKey,
15246
- HAI_REACH_SMS_SECRET_KEY: secretKey
15282
+ HAI_REACH_PROVIDERS: JSON.stringify([{ name: "sms", type: "aliyun-sms", accessKeyId: accessKey, accessKeySecret: secretKey, signName }])
15247
15283
  },
15248
15284
  resourceInfo: "aliyun:verify-only"
15249
15285
  });
@@ -15292,12 +15328,13 @@ async function getNeonConnectionUri(token, projectId) {
15292
15328
  }
15293
15329
  return data.uri;
15294
15330
  }
15295
- function buildNeonProvisionResult(projectId, connectionUri) {
15331
+ function buildNeonProvisionResult(projectId, connectionUri, resourceStatus) {
15296
15332
  return {
15297
15333
  serviceType: "db",
15298
15334
  provisionerName: "neon",
15335
+ resourceStatus,
15299
15336
  envVars: {
15300
- HAI_RELDB_URL: connectionUri
15337
+ HAI_DB: JSON.stringify({ type: "postgresql", url: connectionUri, database: decodeURIComponent(new URL(connectionUri).pathname.slice(1)) })
15301
15338
  },
15302
15339
  resourceInfo: `neon-project:${projectId}`
15303
15340
  };
@@ -15337,16 +15374,18 @@ function createNeonProvisioner() {
15337
15374
  );
15338
15375
  }
15339
15376
  logger6.debug("Provisioning Neon database", { appName });
15377
+ let resource;
15340
15378
  try {
15341
15379
  const projectName = `${appName}-db`;
15342
15380
  const existingProject = await findExistingNeonProject(token, projectName);
15343
15381
  if (existingProject !== null) {
15382
+ resource = { resourceInfo: `neon-project:${existingProject.id}`, resourceStatus: "reused" };
15344
15383
  const connectionUri2 = await getNeonConnectionUri(token, existingProject.id);
15345
15384
  logger6.info("Neon project reused", {
15346
15385
  projectId: existingProject.id,
15347
15386
  projectName
15348
15387
  });
15349
- return ok(buildNeonProvisionResult(existingProject.id, connectionUri2));
15388
+ return ok(buildNeonProvisionResult(existingProject.id, connectionUri2, "reused"));
15350
15389
  }
15351
15390
  const data = await neonFetch(token, "/projects", {
15352
15391
  method: "POST",
@@ -15358,14 +15397,23 @@ function createNeonProvisioner() {
15358
15397
  })
15359
15398
  });
15360
15399
  const projectId = data.project?.id ?? "";
15400
+ if (projectId)
15401
+ resource = { resourceInfo: `neon-project:${projectId}`, resourceStatus: "created" };
15361
15402
  const connectionUri = data.connection_uris?.[0]?.connection_uri ?? "";
15362
15403
  if (!projectId || !connectionUri) {
15363
15404
  throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
15364
15405
  }
15365
15406
  logger6.info("Neon database provisioned", { projectId });
15366
- return ok(buildNeonProvisionResult(projectId, connectionUri));
15407
+ return ok(buildNeonProvisionResult(projectId, connectionUri, "created"));
15367
15408
  } catch (error51) {
15368
- logger6.error("Neon provisioning failed", { appName, error: error51 });
15409
+ logger6.error("Neon provisioning failed", { appName });
15410
+ if (resource) {
15411
+ return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
15412
+ projectName: appName,
15413
+ stage: "provision:db",
15414
+ resources: [{ serviceType: "db", provisionerName: "neon", ...resource }]
15415
+ });
15416
+ }
15369
15417
  return err(
15370
15418
  HaiDeployError.PROVISION_FAILED,
15371
15419
  deployM("deploy_provisionFailed", {
@@ -15385,6 +15433,8 @@ var CF_API = "https://api.cloudflare.com/client/v4";
15385
15433
  function createR2Provisioner() {
15386
15434
  let token = null;
15387
15435
  let accountId = null;
15436
+ let accessKey = "";
15437
+ let secretKey = "";
15388
15438
  return {
15389
15439
  name: "r2",
15390
15440
  serviceType: "storage",
@@ -15393,8 +15443,8 @@ function createR2Provisioner() {
15393
15443
  try {
15394
15444
  const acctId = credentials.accountId ?? credentials.account_id ?? "";
15395
15445
  const apiTok = credentials.apiToken ?? credentials.api_token ?? credentials.token ?? "";
15396
- if (!acctId || !apiTok) {
15397
- throw new Error(deployM("deploy_credentialMissing", { params: { fields: "account_id, api_token" } }));
15446
+ if (!acctId || !apiTok || !credentials.accessKeyId || !credentials.secretAccessKey) {
15447
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "accountId, apiToken, accessKeyId, secretAccessKey" } }));
15398
15448
  }
15399
15449
  const res = await fetch(`${CF_API}/accounts/${acctId}/r2/buckets`, {
15400
15450
  headers: { Authorization: `Bearer ${apiTok}` }
@@ -15404,6 +15454,8 @@ function createR2Provisioner() {
15404
15454
  }
15405
15455
  accountId = acctId;
15406
15456
  token = apiTok;
15457
+ accessKey = credentials.accessKeyId;
15458
+ secretKey = credentials.secretAccessKey;
15407
15459
  logger7.info("Cloudflare R2 authenticated", { accountId: acctId });
15408
15460
  return ok(acctId);
15409
15461
  } catch (error51) {
@@ -15441,42 +15493,20 @@ function createR2Provisioner() {
15441
15493
  if (!createRes.ok && createRes.status !== 409) {
15442
15494
  throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(createRes.status) } }));
15443
15495
  }
15444
- const tokenRes = await fetch(
15445
- `${CF_API}/user/tokens`,
15446
- {
15447
- method: "POST",
15448
- headers: {
15449
- "Authorization": `Bearer ${token}`,
15450
- "Content-Type": "application/json"
15451
- },
15452
- body: JSON.stringify({
15453
- name: `${appName}-r2-token`,
15454
- policies: [{
15455
- effect: "allow",
15456
- permission_groups: [{ id: "r2-read-write" }],
15457
- resources: { [`com.cloudflare.edge.r2.bucket.${accountId}_default_${bucketName}`]: "*" }
15458
- }]
15459
- })
15460
- }
15461
- );
15462
- if (!tokenRes.ok) {
15463
- throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(tokenRes.status) } }));
15464
- }
15465
- const tokenData = await tokenRes.json();
15466
- const accessKey = tokenData.result?.id ?? "";
15467
- const secretKey = tokenData.result?.value ?? "";
15468
- if (!accessKey || !secretKey) {
15469
- throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Cloudflare R2" } }));
15470
- }
15471
15496
  logger7.info("R2 bucket provisioned", { bucketName });
15472
15497
  return ok({
15473
15498
  serviceType: "storage",
15474
15499
  provisionerName: "r2",
15500
+ resourceStatus: createRes.status === 409 ? "reused" : "created",
15475
15501
  envVars: {
15476
- HAI_STORAGE_S3_ENDPOINT: `https://${accountId}.r2.cloudflarestorage.com`,
15477
- HAI_STORAGE_S3_BUCKET: bucketName,
15478
- HAI_STORAGE_S3_ACCESS_KEY: accessKey,
15479
- HAI_STORAGE_S3_SECRET_KEY: secretKey
15502
+ HAI_STORAGE: JSON.stringify({
15503
+ type: "s3",
15504
+ endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
15505
+ bucket: bucketName,
15506
+ region: "auto",
15507
+ accessKeyId: accessKey,
15508
+ secretAccessKey: secretKey
15509
+ })
15480
15510
  },
15481
15511
  resourceInfo: `r2-bucket:${bucketName}`
15482
15512
  });
@@ -15500,6 +15530,7 @@ var logger8 = core.logger.child({ module: "deploy", scope: "provisioner-resend"
15500
15530
  var RESEND_API = "https://api.resend.com";
15501
15531
  function createResendProvisioner() {
15502
15532
  let token = null;
15533
+ let from = "";
15503
15534
  return {
15504
15535
  name: "resend",
15505
15536
  serviceType: "email",
@@ -15507,8 +15538,8 @@ function createResendProvisioner() {
15507
15538
  logger8.debug("Authenticating with Resend");
15508
15539
  try {
15509
15540
  const apiToken = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
15510
- if (!apiToken) {
15511
- throw new Error(deployM("deploy_credentialMissing", { params: { fields: "api_key" } }));
15541
+ if (!apiToken || !credentials.from) {
15542
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "apiKey, from" } }));
15512
15543
  }
15513
15544
  const res = await fetch(`${RESEND_API}/domains`, {
15514
15545
  headers: { Authorization: `Bearer ${apiToken}` }
@@ -15517,6 +15548,7 @@ function createResendProvisioner() {
15517
15548
  throw new Error(deployM("deploy_apiError", { params: { service: "Resend", status: String(res.status) } }));
15518
15549
  }
15519
15550
  token = apiToken;
15551
+ from = credentials.from;
15520
15552
  logger8.info("Resend authenticated");
15521
15553
  return ok("resend-user");
15522
15554
  } catch (error51) {
@@ -15541,8 +15573,9 @@ function createResendProvisioner() {
15541
15573
  return ok({
15542
15574
  serviceType: "email",
15543
15575
  provisionerName: "resend",
15576
+ resourceStatus: "reused",
15544
15577
  envVars: {
15545
- HAI_REACH_RESEND_KEY: token
15578
+ HAI_REACH_PROVIDERS: JSON.stringify([{ name: "email", type: "smtp", host: "smtp.resend.com", port: 465, secure: true, user: "resend", pass: token, from }])
15546
15579
  },
15547
15580
  resourceInfo: "resend:verify-only"
15548
15581
  });
@@ -15578,38 +15611,17 @@ async function findExistingUpstashDatabase(email3, apiKey, databaseName) {
15578
15611
  async function getUpstashDatabase(email3, apiKey, databaseId) {
15579
15612
  return upstashFetch(email3, apiKey, `/v2/redis/database/${databaseId}`);
15580
15613
  }
15581
- function resolveUpstashRestUrl(database) {
15582
- if (typeof database.rest_url === "string" && database.rest_url.length > 0) {
15583
- return database.rest_url;
15584
- }
15585
- const endpoint = database.endpoint?.trim() ?? "";
15586
- if (!endpoint) {
15587
- return "";
15588
- }
15589
- if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) {
15590
- return endpoint;
15591
- }
15592
- if (endpoint.includes(".")) {
15593
- return `https://${endpoint}`;
15594
- }
15595
- return `https://${endpoint}.upstash.io`;
15596
- }
15597
- function resolveUpstashRestToken(database) {
15598
- return database.rest_token ?? database.token ?? database.password ?? "";
15599
- }
15600
- function buildUpstashProvisionResult(database) {
15614
+ function buildUpstashProvisionResult(database, resourceStatus) {
15601
15615
  const databaseId = database.database_id ?? "";
15602
- const restUrl = resolveUpstashRestUrl(database);
15603
- const restToken = resolveUpstashRestToken(database);
15604
- if (!databaseId || !restUrl || !restToken) {
15616
+ if (!databaseId || !database.endpoint || !database.password || !database.port) {
15605
15617
  throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Upstash" } }));
15606
15618
  }
15607
15619
  return {
15608
15620
  serviceType: "cache",
15609
15621
  provisionerName: "upstash",
15622
+ resourceStatus,
15610
15623
  envVars: {
15611
- HAI_CACHE_UPSTASH_URL: restUrl,
15612
- HAI_CACHE_UPSTASH_TOKEN: restToken
15624
+ HAI_CACHE: JSON.stringify({ type: "redis", url: `rediss://default:${encodeURIComponent(database.password)}@${database.endpoint}:${database.port}` })
15613
15625
  },
15614
15626
  resourceInfo: `upstash-db:${databaseId}`
15615
15627
  };
@@ -15652,6 +15664,7 @@ function createUpstashProvisioner() {
15652
15664
  );
15653
15665
  }
15654
15666
  logger9.debug("Provisioning Upstash Redis", { appName });
15667
+ let resource;
15655
15668
  try {
15656
15669
  const databaseName = `${appName}-cache`;
15657
15670
  const existingDatabase = await findExistingUpstashDatabase(email3, apiKey, databaseName);
@@ -15660,9 +15673,10 @@ function createUpstashProvisioner() {
15660
15673
  if (!databaseId) {
15661
15674
  throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Upstash" } }));
15662
15675
  }
15676
+ resource = { resourceInfo: `upstash-db:${databaseId}`, resourceStatus: "reused" };
15663
15677
  const detail = await getUpstashDatabase(email3, apiKey, databaseId);
15664
15678
  logger9.info("Upstash Redis reused", { databaseId, databaseName });
15665
- return ok(buildUpstashProvisionResult({ ...existingDatabase, ...detail }));
15679
+ return ok(buildUpstashProvisionResult({ ...existingDatabase, ...detail }, "reused"));
15666
15680
  }
15667
15681
  const data = await upstashFetch(email3, apiKey, "/v2/redis/database", {
15668
15682
  method: "POST",
@@ -15672,10 +15686,19 @@ function createUpstashProvisioner() {
15672
15686
  tls: true
15673
15687
  })
15674
15688
  });
15689
+ if (data.database_id)
15690
+ resource = { resourceInfo: `upstash-db:${data.database_id}`, resourceStatus: "created" };
15675
15691
  logger9.info("Upstash Redis provisioned", { databaseId: data.database_id });
15676
- return ok(buildUpstashProvisionResult(data));
15692
+ return ok(buildUpstashProvisionResult(data, "created"));
15677
15693
  } catch (error51) {
15678
- logger9.error("Upstash provisioning failed", { appName, error: error51 });
15694
+ logger9.error("Upstash provisioning failed", { appName });
15695
+ if (resource) {
15696
+ return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
15697
+ projectName: appName,
15698
+ stage: "provision:cache",
15699
+ resources: [{ serviceType: "cache", provisionerName: "upstash", ...resource }]
15700
+ });
15701
+ }
15679
15702
  return err(
15680
15703
  HaiDeployError.PROVISION_FAILED,
15681
15704
  deployM("deploy_provisionFailed", {
@@ -15856,9 +15879,18 @@ var deploy = {
15856
15879
  logger10.error("Service provisioning failed", {
15857
15880
  serviceType,
15858
15881
  provisioner: provisioner.name,
15859
- error: provResult.error
15882
+ code: provResult.error.code
15883
+ });
15884
+ return recoveryFailure(provResult.error, {
15885
+ projectName,
15886
+ stage: `provision:${serviceType}`,
15887
+ resources: [
15888
+ ...results.map(({ envVars: _envVars, ...resource }) => resource),
15889
+ ...getDeployRecovery(provResult.error)?.resources ?? [
15890
+ { serviceType, provisionerName: provisioner.name, resourceInfo: projectName, resourceStatus: "unknown" }
15891
+ ]
15892
+ ]
15860
15893
  });
15861
- return err(provResult.error);
15862
15894
  }
15863
15895
  results.push(provResult.data);
15864
15896
  logger10.info("Service provisioned", { serviceType, provisioner: provisioner.name });
@@ -15885,36 +15917,44 @@ var deploy = {
15885
15917
  );
15886
15918
  }
15887
15919
  let allEnvVars = {};
15920
+ const recovery = { projectName, stage: "provision", resources: [] };
15888
15921
  if (!options?.skipProvision) {
15889
15922
  const provResults = await deploy.provisionAll(projectName);
15890
15923
  if (!provResults.success) {
15891
15924
  return err(provResults.error);
15892
15925
  }
15926
+ recovery.resources = provResults.data.map(({ envVars: _envVars, ...resource }) => resource);
15927
+ const reachProviders = [];
15893
15928
  for (const prov of provResults.data) {
15929
+ if (prov.envVars.HAI_REACH_PROVIDERS)
15930
+ reachProviders.push(...JSON.parse(prov.envVars.HAI_REACH_PROVIDERS));
15894
15931
  allEnvVars = { ...allEnvVars, ...prov.envVars };
15895
15932
  }
15933
+ if (reachProviders.length > 0)
15934
+ allEnvVars.HAI_REACH_PROVIDERS = JSON.stringify(reachProviders);
15896
15935
  }
15897
15936
  const projectResult = await currentProvider.createProject(projectName);
15898
15937
  if (!projectResult.success) {
15899
- return err(projectResult.error);
15938
+ return recoveryFailure(projectResult.error, { ...recovery, stage: "create-project" });
15900
15939
  }
15901
15940
  const projectId = projectResult.data;
15941
+ recovery.platformProjectId = projectId;
15902
15942
  if (Object.keys(allEnvVars).length > 0) {
15903
15943
  const envResult = await currentProvider.setEnvVars(projectId, allEnvVars);
15904
15944
  if (!envResult.success) {
15905
- return err(envResult.error);
15945
+ return recoveryFailure(envResult.error, { ...recovery, stage: "set-env" });
15906
15946
  }
15907
15947
  }
15908
15948
  if (!options?.skipBuild) {
15909
- const buildResult = buildApp(appDir, scan.buildCommand);
15949
+ const buildResult = buildApp(appDir, scan.buildCommand, allEnvVars);
15910
15950
  if (!buildResult.success) {
15911
- return err(buildResult.error);
15951
+ return recoveryFailure(buildResult.error, { ...recovery, stage: "build" });
15912
15952
  }
15913
15953
  }
15914
15954
  const outputDir = resolveOutputDir(appDir, currentConfig?.provider.type ?? "vercel");
15915
15955
  const deployResult = await currentProvider.deploy(projectId, outputDir);
15916
15956
  if (!deployResult.success) {
15917
- return err(deployResult.error);
15957
+ return recoveryFailure(deployResult.error, { ...recovery, stage: "deploy" });
15918
15958
  }
15919
15959
  const result = {
15920
15960
  ...deployResult.data,
@@ -15929,6 +15969,6 @@ var deploy = {
15929
15969
  }
15930
15970
  };
15931
15971
 
15932
- export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy };
15933
- //# sourceMappingURL=src-QNEJJ3BV.js.map
15934
- //# sourceMappingURL=src-QNEJJ3BV.js.map
15972
+ export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy, getDeployRecovery };
15973
+ //# sourceMappingURL=src-6ZBWDBUB.js.map
15974
+ //# sourceMappingURL=src-6ZBWDBUB.js.map