@h-ai/cli 0.1.0-alpha.18 → 0.1.0-alpha.20

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 (77) hide show
  1. package/dist/{chunk-BPO34NRL.js → chunk-3BBXDQ4Y.js} +5 -5
  2. package/dist/chunk-3BBXDQ4Y.js.map +1 -0
  3. package/dist/cli-config-templates-IGGO7P45.js +5 -0
  4. package/dist/{cli-config-templates-JCS7T3NU.js.map → cli-config-templates-IGGO7P45.js.map} +1 -1
  5. package/dist/index.js +67 -39
  6. package/dist/index.js.map +1 -1
  7. package/dist/{src-N4NPS7AG.js → src-QNEJJ3BV.js} +261 -103
  8. package/dist/src-QNEJJ3BV.js.map +1 -0
  9. package/package.json +2 -2
  10. package/templates/apps/admin/messages/en-US.json +5 -0
  11. package/templates/apps/admin/messages/zh-CN.json +5 -0
  12. package/templates/apps/admin/src/routes/+layout.svelte.hbs +5 -5
  13. package/templates/apps/admin/src/routes/+page.svelte.hbs +32 -12
  14. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +37 -20
  15. package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +33 -24
  16. package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +16 -13
  17. package/templates/apps/android-app/src/lib/api.ts.hbs +5 -4
  18. package/templates/apps/android-app/src/lib/capacitor.ts +1 -1
  19. package/templates/apps/android-app/src/routes/+layout.svelte.hbs +11 -10
  20. package/templates/apps/android-app/src/routes/+page.svelte.hbs +17 -6
  21. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +4 -1
  22. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +14 -0
  23. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +13 -0
  24. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +3 -0
  25. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server-app.ts.hbs +23 -3
  26. package/templates/apps/h5/messages/en-US.json +3 -0
  27. package/templates/apps/h5/messages/zh-CN.json +3 -0
  28. package/templates/apps/h5/src/routes/+layout.svelte.hbs +30 -29
  29. package/templates/apps/h5/src/routes/+page.svelte.hbs +17 -6
  30. package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +17 -6
  31. package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +17 -6
  32. package/templates/apps/website/src/routes/+layout.svelte.hbs +35 -26
  33. package/templates/apps/website/src/routes/+page.svelte.hbs +28 -10
  34. package/templates/apps/website/src/routes/about/+page.svelte.hbs +11 -6
  35. package/templates/apps/website/src/routes/contact/+page.svelte.hbs +37 -34
  36. package/templates/apps/website/src/routes/services/+page.svelte.hbs +27 -40
  37. package/templates/base/package.json.hbs +30 -24
  38. package/templates/base/src/app.html.hbs +4 -0
  39. package/templates/base/src/hooks.server.ts.hbs +17 -7
  40. package/templates/base/src/lib/server/init.ts.hbs +20 -12
  41. package/templates/base/static/favicon.png +0 -0
  42. package/templates/base/tests/scaffold.test.ts.hbs +9 -1
  43. package/templates/base/vite.config.ts.hbs +3 -3
  44. package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +5 -5
  45. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +34 -32
  46. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +46 -48
  47. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +35 -31
  48. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +41 -38
  49. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +15 -7
  50. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +1 -2
  51. package/templates/skills/AGENTS.md +11 -1
  52. package/templates/skills/CLAUDE.md +2 -1
  53. package/templates/skills/copilot-instructions.md +22 -6
  54. package/templates/skills/hai-ai/SKILL.md +115 -646
  55. package/templates/skills/hai-ai/reference.md +197 -0
  56. package/templates/skills/hai-api-client/SKILL.md +7 -1
  57. package/templates/skills/hai-app-create/SKILL.md +1 -1
  58. package/templates/skills/hai-app-review/SKILL.md +1 -1
  59. package/templates/skills/hai-audit/SKILL.md +7 -2
  60. package/templates/skills/hai-cache/SKILL.md +2 -0
  61. package/templates/skills/hai-capacitor/SKILL.md +11 -9
  62. package/templates/skills/hai-core/SKILL.md +4 -1
  63. package/templates/skills/hai-crypto/SKILL.md +42 -8
  64. package/templates/skills/hai-deploy/SKILL.md +12 -9
  65. package/templates/skills/hai-iam/SKILL.md +2 -0
  66. package/templates/skills/hai-kit/SKILL.md +14 -5
  67. package/templates/skills/hai-payment/SKILL.md +5 -0
  68. package/templates/skills/hai-reach/SKILL.md +3 -1
  69. package/templates/skills/hai-reldb/SKILL.md +2 -0
  70. package/templates/skills/hai-scheduler/SKILL.md +2 -0
  71. package/templates/skills/hai-serv/SKILL.md +5 -0
  72. package/templates/skills/hai-storage/SKILL.md +10 -1
  73. package/templates/skills/hai-ui/SKILL.md +25 -11
  74. package/templates/skills/hai-vecdb/SKILL.md +8 -1
  75. package/dist/chunk-BPO34NRL.js.map +0 -1
  76. package/dist/cli-config-templates-JCS7T3NU.js +0 -5
  77. package/dist/src-N4NPS7AG.js.map +0 -1
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { __export } from './chunk-2H7UOFLK.js';
3
- import { core, ok, err } from '@h-ai/core';
3
+ import { core, err, ok } from '@h-ai/core';
4
4
  import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync, statSync } from 'fs';
5
5
  import { homedir } from 'os';
6
- import { join, dirname, relative } from 'path';
6
+ import { join, dirname, relative, sep } from 'path';
7
7
  import process2 from 'process';
8
8
  import { parse as parse$1, stringify } from 'yaml';
9
9
  import { execSync } from 'child_process';
10
10
  import { createHash } from 'crypto';
11
+ import { Buffer } from 'buffer';
11
12
 
12
13
  // ../../node_modules/zod/v4/classic/external.js
13
14
  var external_exports = {};
@@ -14534,12 +14535,13 @@ var en_US_default = {
14534
14535
  deploy_scanFailed: "Application scan failed: {error}",
14535
14536
  deploy_envVarFailed: "Environment variable setup failed: {error}",
14536
14537
  deploy_credentialError: "Credential read/write failed: {error}",
14538
+ deploy_credentialInvalidKey: "Unsupported credential key: {key}. Only HAI_DEPLOY_* keys are allowed",
14537
14539
  deploy_credentialSaved: "Credentials saved",
14538
14540
  deploy_credentialLoaded: "Credentials loaded",
14539
14541
  deploy_configError: "Deploy config error: {error}",
14540
14542
  deploy_credentialMissing: "Missing credential fields: {fields}",
14541
- deploy_apiError: "{service} API error ({status}): {body}",
14542
- deploy_fileUploadFailed: "File upload failed ({status}): {body}",
14543
+ deploy_apiError: "{service} API error ({status})",
14544
+ deploy_fileUploadFailed: "File upload failed ({status})",
14543
14545
  deploy_provisionNoResult: "No connection info returned from {service}"
14544
14546
  };
14545
14547
 
@@ -14561,12 +14563,13 @@ var zh_CN_default2 = {
14561
14563
  deploy_scanFailed: "\u5E94\u7528\u626B\u63CF\u5931\u8D25\uFF1A{error}",
14562
14564
  deploy_envVarFailed: "\u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E\u5931\u8D25\uFF1A{error}",
14563
14565
  deploy_credentialError: "\u51ED\u8BC1\u8BFB\u5199\u5931\u8D25\uFF1A{error}",
14566
+ deploy_credentialInvalidKey: "\u4E0D\u652F\u6301\u7684\u51ED\u8BC1\u952E\u540D\uFF1A{key}\u3002\u4EC5\u5141\u8BB8 HAI_DEPLOY_*",
14564
14567
  deploy_credentialSaved: "\u51ED\u8BC1\u5DF2\u4FDD\u5B58",
14565
14568
  deploy_credentialLoaded: "\u51ED\u8BC1\u5DF2\u52A0\u8F7D",
14566
14569
  deploy_configError: "\u90E8\u7F72\u914D\u7F6E\u9519\u8BEF\uFF1A{error}",
14567
14570
  deploy_credentialMissing: "\u7F3A\u5C11\u51ED\u8BC1\u5B57\u6BB5\uFF1A{fields}",
14568
- deploy_apiError: "{service} API \u8BF7\u6C42\u5931\u8D25\uFF08{status}\uFF09\uFF1A{body}",
14569
- deploy_fileUploadFailed: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF08{status}\uFF09\uFF1A{body}",
14571
+ deploy_apiError: "{service} API \u8BF7\u6C42\u5931\u8D25\uFF08{status}\uFF09",
14572
+ deploy_fileUploadFailed: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF08{status}\uFF09",
14570
14573
  deploy_provisionNoResult: "{service} \u672A\u8FD4\u56DE\u8FDE\u63A5\u4FE1\u606F"
14571
14574
  };
14572
14575
 
@@ -14628,8 +14631,8 @@ var DeployErrorInfo = {
14628
14631
  PROVISION_FAILED: "007:500",
14629
14632
  ADAPTER_MISSING: "008:500",
14630
14633
  SCAN_FAILED: "009:500",
14631
- ENV_VAR_FAILED: "010:500",
14632
- NOT_INITIALIZED: "011:500",
14634
+ NOT_INITIALIZED: "010:500",
14635
+ ENV_VAR_FAILED: "011:500",
14633
14636
  UNSUPPORTED_TYPE: "012:400",
14634
14637
  CONFIG_ERROR: "013:500",
14635
14638
  CREDENTIAL_ERROR: "014:500"
@@ -14638,6 +14641,35 @@ var HaiDeployError = core.error.buildHaiErrorsDef("deploy", DeployErrorInfo);
14638
14641
 
14639
14642
  // ../deploy/src/deploy-credentials.ts
14640
14643
  var logger = core.logger.child({ module: "deploy", scope: "credentials" });
14644
+ var DEPLOY_CREDENTIAL_KEY_PATTERN = /^HAI_DEPLOY_[A-Z0-9_]+$/;
14645
+ function isAllowedCredentialKey(key) {
14646
+ return DEPLOY_CREDENTIAL_KEY_PATTERN.test(key);
14647
+ }
14648
+ function normalizeCredentialEntries(entries) {
14649
+ const normalized = {};
14650
+ for (const [key, value] of Object.entries(entries)) {
14651
+ if (!isAllowedCredentialKey(key)) {
14652
+ logger.warn("Ignoring unsupported deploy credential key", { key });
14653
+ continue;
14654
+ }
14655
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
14656
+ normalized[key] = String(value);
14657
+ continue;
14658
+ }
14659
+ logger.warn("Ignoring deploy credential with non-scalar value", { key });
14660
+ }
14661
+ return normalized;
14662
+ }
14663
+ function assertAllowedCredentialKey(key) {
14664
+ if (!isAllowedCredentialKey(key)) {
14665
+ throw new Error(deployM("deploy_credentialInvalidKey", { params: { key } }));
14666
+ }
14667
+ }
14668
+ function assertAllowedCredentialEntries(entries) {
14669
+ for (const key of Object.keys(entries)) {
14670
+ assertAllowedCredentialKey(key);
14671
+ }
14672
+ }
14641
14673
  function getCredentialsPath() {
14642
14674
  return join(homedir(), ".hai", "credentials.yml");
14643
14675
  }
@@ -14654,10 +14686,10 @@ function readCredentialsFile() {
14654
14686
  }
14655
14687
  const content = readFileSync(filePath, "utf-8");
14656
14688
  const parsed = parse$1(content);
14657
- if (parsed === null || parsed === void 0 || typeof parsed !== "object") {
14689
+ if (parsed === null || parsed === void 0 || typeof parsed !== "object" || Array.isArray(parsed)) {
14658
14690
  return {};
14659
14691
  }
14660
- return parsed;
14692
+ return normalizeCredentialEntries(parsed);
14661
14693
  }
14662
14694
  function writeCredentialsFile(data) {
14663
14695
  const filePath = getCredentialsPath();
@@ -14688,6 +14720,7 @@ function loadCredentials() {
14688
14720
  }
14689
14721
  function saveCredential(key, value) {
14690
14722
  try {
14723
+ assertAllowedCredentialKey(key);
14691
14724
  const data = readCredentialsFile();
14692
14725
  data[key] = value;
14693
14726
  writeCredentialsFile(data);
@@ -14707,6 +14740,7 @@ function saveCredential(key, value) {
14707
14740
  }
14708
14741
  function saveCredentials(entries) {
14709
14742
  try {
14743
+ assertAllowedCredentialEntries(entries);
14710
14744
  const data = readCredentialsFile();
14711
14745
  for (const [key, value] of Object.entries(entries)) {
14712
14746
  data[key] = value;
@@ -14932,6 +14966,12 @@ var logger4 = core.logger.child({ module: "deploy", scope: "provider-vercel" });
14932
14966
  var VERCEL_API = "https://api.vercel.com";
14933
14967
  var POLL_INTERVAL = 3e3;
14934
14968
  var MAX_POLL_ATTEMPTS = 60;
14969
+ var VercelApiError = class extends Error {
14970
+ constructor(status) {
14971
+ super(deployM("deploy_apiError", { params: { service: "Vercel", status: String(status) } }));
14972
+ this.status = status;
14973
+ }
14974
+ };
14935
14975
  function toDeployError(codeDef, messageKey, error51) {
14936
14976
  return {
14937
14977
  ...codeDef,
@@ -14952,8 +14992,7 @@ async function vercelFetch(token, path, options) {
14952
14992
  }
14953
14993
  });
14954
14994
  if (!response.ok) {
14955
- const body = await response.text();
14956
- throw new Error(deployM("deploy_apiError", { params: { service: "Vercel", status: String(response.status), body } }));
14995
+ throw new VercelApiError(response.status);
14957
14996
  }
14958
14997
  return response.json();
14959
14998
  }
@@ -14965,7 +15004,7 @@ function collectFiles(dir, baseDir) {
14965
15004
  if (entry.isDirectory()) {
14966
15005
  results.push(...collectFiles(fullPath, baseDir));
14967
15006
  } else {
14968
- results.push(relative(baseDir, fullPath));
15007
+ results.push(relative(baseDir, fullPath).split(sep).join("/"));
14969
15008
  }
14970
15009
  }
14971
15010
  return results;
@@ -15004,19 +15043,33 @@ function createVercelProvider() {
15004
15043
  logger4.info("Vercel project found", { projectName, projectId: existingId });
15005
15044
  return ok(existingId);
15006
15045
  }
15007
- const project = await vercelFetch(
15008
- token,
15009
- "/v10/projects",
15010
- {
15011
- method: "POST",
15012
- body: JSON.stringify({
15013
- name: projectName,
15014
- framework: "sveltekit"
15015
- })
15046
+ try {
15047
+ const project = await vercelFetch(
15048
+ token,
15049
+ "/v11/projects",
15050
+ {
15051
+ method: "POST",
15052
+ body: JSON.stringify({
15053
+ name: projectName,
15054
+ framework: "sveltekit"
15055
+ })
15056
+ }
15057
+ );
15058
+ logger4.info("Vercel project created", { projectName, projectId: project.id });
15059
+ return ok(project.id);
15060
+ } catch (error51) {
15061
+ if (error51 instanceof VercelApiError && error51.status === 409) {
15062
+ const concurrentProjectId = await findExistingProject(token, projectName);
15063
+ if (concurrentProjectId !== null) {
15064
+ logger4.info("Vercel project reused after create conflict", {
15065
+ projectName,
15066
+ projectId: concurrentProjectId
15067
+ });
15068
+ return ok(concurrentProjectId);
15069
+ }
15016
15070
  }
15017
- );
15018
- logger4.info("Vercel project created", { projectName, projectId: project.id });
15019
- return ok(project.id);
15071
+ throw error51;
15072
+ }
15020
15073
  } catch (error51) {
15021
15074
  logger4.error("Vercel project creation failed", { projectName, error: error51 });
15022
15075
  return err(toDeployError(HaiDeployError.PROJECT_CREATE_FAILED, "deploy_projectCreateFailed", error51));
@@ -15039,7 +15092,7 @@ function createVercelProvider() {
15039
15092
  }));
15040
15093
  await vercelFetch(
15041
15094
  token,
15042
- `/v10/projects/${projectId}/env`,
15095
+ `/v10/projects/${projectId}/env?upsert=true`,
15043
15096
  {
15044
15097
  method: "POST",
15045
15098
  body: JSON.stringify(envEntries)
@@ -15108,12 +15161,16 @@ async function findExistingProject(token, projectName) {
15108
15161
  try {
15109
15162
  const project = await vercelFetch(token, `/v10/projects/${projectName}`);
15110
15163
  return project.id;
15111
- } catch {
15112
- return null;
15164
+ } catch (error51) {
15165
+ if (error51 instanceof VercelApiError && error51.status === 404) {
15166
+ return null;
15167
+ }
15168
+ throw error51;
15113
15169
  }
15114
15170
  }
15115
15171
  async function uploadFile(token, content, sha) {
15116
15172
  const url2 = `${VERCEL_API}/v2/files`;
15173
+ const uploadContent = new Uint8Array(content);
15117
15174
  const response = await fetch(url2, {
15118
15175
  method: "POST",
15119
15176
  headers: {
@@ -15121,11 +15178,10 @@ async function uploadFile(token, content, sha) {
15121
15178
  "Content-Type": "application/octet-stream",
15122
15179
  "x-vercel-digest": sha
15123
15180
  },
15124
- body: new Blob([content.buffer])
15181
+ body: new Blob([uploadContent])
15125
15182
  });
15126
15183
  if (!response.ok && response.status !== 409) {
15127
- const body = await response.text();
15128
- throw new Error(deployM("deploy_fileUploadFailed", { params: { status: String(response.status), body } }));
15184
+ throw new Error(deployM("deploy_fileUploadFailed", { params: { status: String(response.status) } }));
15129
15185
  }
15130
15186
  }
15131
15187
  async function pollDeploymentStatus(token, deploymentId) {
@@ -15196,6 +15252,56 @@ function createAliyunProvisioner() {
15196
15252
  }
15197
15253
  var logger6 = core.logger.child({ module: "deploy", scope: "provisioner-neon" });
15198
15254
  var NEON_API = "https://console.neon.tech/api/v2";
15255
+ var DEFAULT_NEON_DATABASE_NAME = "neondb";
15256
+ var DEFAULT_NEON_ROLE_NAME = "neondb_owner";
15257
+ async function neonFetch(token, path, options) {
15258
+ const response = await fetch(`${NEON_API}${path}`, {
15259
+ ...options,
15260
+ headers: {
15261
+ "Accept": "application/json",
15262
+ "Authorization": `Bearer ${token}`,
15263
+ "Content-Type": "application/json",
15264
+ ...options?.headers
15265
+ }
15266
+ });
15267
+ if (!response.ok) {
15268
+ throw new Error(deployM("deploy_apiError", { params: { service: "Neon", status: String(response.status) } }));
15269
+ }
15270
+ return response.json();
15271
+ }
15272
+ async function listNeonProjects(token) {
15273
+ const data = await neonFetch(token, "/projects");
15274
+ return data.projects ?? [];
15275
+ }
15276
+ async function findExistingNeonProject(token, projectName) {
15277
+ const projects = await listNeonProjects(token);
15278
+ return projects.find((project) => project.name === projectName) ?? null;
15279
+ }
15280
+ async function getNeonConnectionUri(token, projectId) {
15281
+ const query = new URLSearchParams({
15282
+ database_name: DEFAULT_NEON_DATABASE_NAME,
15283
+ role_name: DEFAULT_NEON_ROLE_NAME
15284
+ });
15285
+ const data = await neonFetch(
15286
+ token,
15287
+ `/projects/${projectId}/connection_uri?${query.toString()}`,
15288
+ { headers: { "Content-Type": "application/json" } }
15289
+ );
15290
+ if (!data.uri) {
15291
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
15292
+ }
15293
+ return data.uri;
15294
+ }
15295
+ function buildNeonProvisionResult(projectId, connectionUri) {
15296
+ return {
15297
+ serviceType: "db",
15298
+ provisionerName: "neon",
15299
+ envVars: {
15300
+ HAI_RELDB_URL: connectionUri
15301
+ },
15302
+ resourceInfo: `neon-project:${projectId}`
15303
+ };
15304
+ }
15199
15305
  function createNeonProvisioner() {
15200
15306
  let token = null;
15201
15307
  return {
@@ -15208,12 +15314,7 @@ function createNeonProvisioner() {
15208
15314
  if (!apiToken) {
15209
15315
  throw new Error(deployM("deploy_credentialMissing", { params: { fields: "token" } }));
15210
15316
  }
15211
- const res = await fetch(`${NEON_API}/projects`, {
15212
- headers: { Authorization: `Bearer ${apiToken}` }
15213
- });
15214
- if (!res.ok) {
15215
- throw new Error(deployM("deploy_apiError", { params: { service: "Neon", status: String(res.status), body: await res.text() } }));
15216
- }
15317
+ await listNeonProjects(apiToken);
15217
15318
  token = apiToken;
15218
15319
  logger6.info("Neon authenticated");
15219
15320
  return ok("neon-user");
@@ -15237,36 +15338,32 @@ function createNeonProvisioner() {
15237
15338
  }
15238
15339
  logger6.debug("Provisioning Neon database", { appName });
15239
15340
  try {
15240
- const res = await fetch(`${NEON_API}/projects`, {
15341
+ const projectName = `${appName}-db`;
15342
+ const existingProject = await findExistingNeonProject(token, projectName);
15343
+ if (existingProject !== null) {
15344
+ const connectionUri2 = await getNeonConnectionUri(token, existingProject.id);
15345
+ logger6.info("Neon project reused", {
15346
+ projectId: existingProject.id,
15347
+ projectName
15348
+ });
15349
+ return ok(buildNeonProvisionResult(existingProject.id, connectionUri2));
15350
+ }
15351
+ const data = await neonFetch(token, "/projects", {
15241
15352
  method: "POST",
15242
- headers: {
15243
- "Authorization": `Bearer ${token}`,
15244
- "Content-Type": "application/json"
15245
- },
15246
15353
  body: JSON.stringify({
15247
15354
  project: {
15248
- name: `${appName}-db`,
15355
+ name: projectName,
15249
15356
  pg_version: 16
15250
15357
  }
15251
15358
  })
15252
15359
  });
15253
- if (!res.ok) {
15254
- throw new Error(deployM("deploy_apiError", { params: { service: "Neon", status: String(res.status), body: await res.text() } }));
15255
- }
15256
- const data = await res.json();
15257
- const connectionUri = data.connection_uris?.[0]?.connection_uri;
15258
- if (!connectionUri) {
15360
+ const projectId = data.project?.id ?? "";
15361
+ const connectionUri = data.connection_uris?.[0]?.connection_uri ?? "";
15362
+ if (!projectId || !connectionUri) {
15259
15363
  throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
15260
15364
  }
15261
- logger6.info("Neon database provisioned", { projectId: data.project.id });
15262
- return ok({
15263
- serviceType: "db",
15264
- provisionerName: "neon",
15265
- envVars: {
15266
- HAI_RELDB_URL: connectionUri
15267
- },
15268
- resourceInfo: `neon-project:${data.project.id}`
15269
- });
15365
+ logger6.info("Neon database provisioned", { projectId });
15366
+ return ok(buildNeonProvisionResult(projectId, connectionUri));
15270
15367
  } catch (error51) {
15271
15368
  logger6.error("Neon provisioning failed", { appName, error: error51 });
15272
15369
  return err(
@@ -15303,7 +15400,7 @@ function createR2Provisioner() {
15303
15400
  headers: { Authorization: `Bearer ${apiTok}` }
15304
15401
  });
15305
15402
  if (!res.ok) {
15306
- throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(res.status), body: await res.text() } }));
15403
+ throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(res.status) } }));
15307
15404
  }
15308
15405
  accountId = acctId;
15309
15406
  token = apiTok;
@@ -15342,7 +15439,7 @@ function createR2Provisioner() {
15342
15439
  }
15343
15440
  );
15344
15441
  if (!createRes.ok && createRes.status !== 409) {
15345
- throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(createRes.status), body: await createRes.text() } }));
15442
+ throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(createRes.status) } }));
15346
15443
  }
15347
15444
  const tokenRes = await fetch(
15348
15445
  `${CF_API}/user/tokens`,
@@ -15362,12 +15459,14 @@ function createR2Provisioner() {
15362
15459
  })
15363
15460
  }
15364
15461
  );
15365
- let accessKey = "";
15366
- let secretKey = "";
15367
- if (tokenRes.ok) {
15368
- const tokenData = await tokenRes.json();
15369
- accessKey = tokenData.result.id;
15370
- secretKey = tokenData.result.value;
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" } }));
15371
15470
  }
15372
15471
  logger7.info("R2 bucket provisioned", { bucketName });
15373
15472
  return ok({
@@ -15415,7 +15514,7 @@ function createResendProvisioner() {
15415
15514
  headers: { Authorization: `Bearer ${apiToken}` }
15416
15515
  });
15417
15516
  if (!res.ok) {
15418
- throw new Error(deployM("deploy_apiError", { params: { service: "Resend", status: String(res.status), body: await res.text() } }));
15517
+ throw new Error(deployM("deploy_apiError", { params: { service: "Resend", status: String(res.status) } }));
15419
15518
  }
15420
15519
  token = apiToken;
15421
15520
  logger8.info("Resend authenticated");
@@ -15452,6 +15551,69 @@ function createResendProvisioner() {
15452
15551
  }
15453
15552
  var logger9 = core.logger.child({ module: "deploy", scope: "provisioner-upstash" });
15454
15553
  var UPSTASH_API = "https://api.upstash.com";
15554
+ function createUpstashAuthHeader(email3, apiKey) {
15555
+ return `Basic ${Buffer.from(`${email3}:${apiKey}`).toString("base64")}`;
15556
+ }
15557
+ async function upstashFetch(email3, apiKey, path, options) {
15558
+ const response = await fetch(`${UPSTASH_API}${path}`, {
15559
+ ...options,
15560
+ headers: {
15561
+ "Authorization": createUpstashAuthHeader(email3, apiKey),
15562
+ "Content-Type": "application/json",
15563
+ ...options?.headers
15564
+ }
15565
+ });
15566
+ if (!response.ok) {
15567
+ throw new Error(deployM("deploy_apiError", { params: { service: "Upstash", status: String(response.status) } }));
15568
+ }
15569
+ return response.json();
15570
+ }
15571
+ async function listUpstashDatabases(email3, apiKey) {
15572
+ return upstashFetch(email3, apiKey, "/v2/redis/databases");
15573
+ }
15574
+ async function findExistingUpstashDatabase(email3, apiKey, databaseName) {
15575
+ const databases = await listUpstashDatabases(email3, apiKey);
15576
+ return databases.find((database) => database.database_name === databaseName) ?? null;
15577
+ }
15578
+ async function getUpstashDatabase(email3, apiKey, databaseId) {
15579
+ return upstashFetch(email3, apiKey, `/v2/redis/database/${databaseId}`);
15580
+ }
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) {
15601
+ const databaseId = database.database_id ?? "";
15602
+ const restUrl = resolveUpstashRestUrl(database);
15603
+ const restToken = resolveUpstashRestToken(database);
15604
+ if (!databaseId || !restUrl || !restToken) {
15605
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Upstash" } }));
15606
+ }
15607
+ return {
15608
+ serviceType: "cache",
15609
+ provisionerName: "upstash",
15610
+ envVars: {
15611
+ HAI_CACHE_UPSTASH_URL: restUrl,
15612
+ HAI_CACHE_UPSTASH_TOKEN: restToken
15613
+ },
15614
+ resourceInfo: `upstash-db:${databaseId}`
15615
+ };
15616
+ }
15455
15617
  function createUpstashProvisioner() {
15456
15618
  let apiKey = null;
15457
15619
  let email3 = null;
@@ -15466,14 +15628,7 @@ function createUpstashProvisioner() {
15466
15628
  if (!userEmail || !userKey) {
15467
15629
  throw new Error(deployM("deploy_credentialMissing", { params: { fields: "email, api_key" } }));
15468
15630
  }
15469
- const res = await fetch(`${UPSTASH_API}/v2/redis/databases`, {
15470
- headers: {
15471
- Authorization: `Basic ${btoa(`${userEmail}:${userKey}`)}`
15472
- }
15473
- });
15474
- if (!res.ok) {
15475
- throw new Error(deployM("deploy_apiError", { params: { service: "Upstash", status: String(res.status), body: await res.text() } }));
15476
- }
15631
+ await listUpstashDatabases(userEmail, userKey);
15477
15632
  email3 = userEmail;
15478
15633
  apiKey = userKey;
15479
15634
  logger9.info("Upstash authenticated", { email: userEmail });
@@ -15498,32 +15653,27 @@ function createUpstashProvisioner() {
15498
15653
  }
15499
15654
  logger9.debug("Provisioning Upstash Redis", { appName });
15500
15655
  try {
15501
- const res = await fetch(`${UPSTASH_API}/v2/redis/database`, {
15656
+ const databaseName = `${appName}-cache`;
15657
+ const existingDatabase = await findExistingUpstashDatabase(email3, apiKey, databaseName);
15658
+ if (existingDatabase !== null) {
15659
+ const databaseId = existingDatabase.database_id ?? "";
15660
+ if (!databaseId) {
15661
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Upstash" } }));
15662
+ }
15663
+ const detail = await getUpstashDatabase(email3, apiKey, databaseId);
15664
+ logger9.info("Upstash Redis reused", { databaseId, databaseName });
15665
+ return ok(buildUpstashProvisionResult({ ...existingDatabase, ...detail }));
15666
+ }
15667
+ const data = await upstashFetch(email3, apiKey, "/v2/redis/database", {
15502
15668
  method: "POST",
15503
- headers: {
15504
- "Authorization": `Basic ${btoa(`${email3}:${apiKey}`)}`,
15505
- "Content-Type": "application/json"
15506
- },
15507
15669
  body: JSON.stringify({
15508
- name: `${appName}-cache`,
15670
+ name: databaseName,
15509
15671
  region: "global",
15510
15672
  tls: true
15511
15673
  })
15512
15674
  });
15513
- if (!res.ok) {
15514
- throw new Error(deployM("deploy_apiError", { params: { service: "Upstash", status: String(res.status), body: await res.text() } }));
15515
- }
15516
- const data = await res.json();
15517
15675
  logger9.info("Upstash Redis provisioned", { databaseId: data.database_id });
15518
- return ok({
15519
- serviceType: "cache",
15520
- provisionerName: "upstash",
15521
- envVars: {
15522
- HAI_CACHE_UPSTASH_URL: data.rest_url,
15523
- HAI_CACHE_UPSTASH_TOKEN: data.rest_token
15524
- },
15525
- resourceInfo: `upstash-db:${data.database_id}`
15526
- });
15676
+ return ok(buildUpstashProvisionResult(data));
15527
15677
  } catch (error51) {
15528
15678
  logger9.error("Upstash provisioning failed", { appName, error: error51 });
15529
15679
  return err(
@@ -15584,7 +15734,14 @@ function extractCredentials(serviceConfig) {
15584
15734
  }
15585
15735
  return result;
15586
15736
  }
15737
+ var deployCredentials = {
15738
+ getPath: () => getCredentialsPath(),
15739
+ load: () => loadCredentials(),
15740
+ save: (key, value) => saveCredential(key, value),
15741
+ saveAll: (entries) => saveCredentials(entries)
15742
+ };
15587
15743
  var deploy = {
15744
+ credentials: deployCredentials,
15588
15745
  async init(config2) {
15589
15746
  if (initInProgress) {
15590
15747
  logger10.warn("Deploy init already in progress, skipping concurrent call");
@@ -15627,16 +15784,17 @@ var deploy = {
15627
15784
  const credentials = extractCredentials(serviceConfig);
15628
15785
  const provAuthResult = await provisioner.authenticate(credentials);
15629
15786
  if (!provAuthResult.success) {
15630
- logger10.warn("Provisioner authentication failed", {
15787
+ logger10.error("Provisioner authentication failed", {
15631
15788
  service: serviceType,
15632
15789
  provisioner: serviceConfig.provisioner
15633
15790
  });
15634
- } else {
15635
- logger10.info("Provisioner authenticated", {
15636
- service: serviceType,
15637
- user: provAuthResult.data
15638
- });
15791
+ currentProvisioners.clear();
15792
+ return err(provAuthResult.error);
15639
15793
  }
15794
+ logger10.info("Provisioner authenticated", {
15795
+ service: serviceType,
15796
+ user: provAuthResult.data
15797
+ });
15640
15798
  currentProvisioners.set(serviceType, provisioner);
15641
15799
  }
15642
15800
  }
@@ -15771,6 +15929,6 @@ var deploy = {
15771
15929
  }
15772
15930
  };
15773
15931
 
15774
- export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy, getCredentialsPath, loadCredentials, saveCredential, saveCredentials, scanApp };
15775
- //# sourceMappingURL=src-N4NPS7AG.js.map
15776
- //# sourceMappingURL=src-N4NPS7AG.js.map
15932
+ export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy };
15933
+ //# sourceMappingURL=src-QNEJJ3BV.js.map
15934
+ //# sourceMappingURL=src-QNEJJ3BV.js.map