@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.
- package/dist/{chunk-BPO34NRL.js → chunk-3BBXDQ4Y.js} +5 -5
- package/dist/chunk-3BBXDQ4Y.js.map +1 -0
- package/dist/cli-config-templates-IGGO7P45.js +5 -0
- package/dist/{cli-config-templates-JCS7T3NU.js.map → cli-config-templates-IGGO7P45.js.map} +1 -1
- package/dist/index.js +67 -39
- package/dist/index.js.map +1 -1
- package/dist/{src-N4NPS7AG.js → src-QNEJJ3BV.js} +261 -103
- package/dist/src-QNEJJ3BV.js.map +1 -0
- package/package.json +2 -2
- package/templates/apps/admin/messages/en-US.json +5 -0
- package/templates/apps/admin/messages/zh-CN.json +5 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +5 -5
- package/templates/apps/admin/src/routes/+page.svelte.hbs +32 -12
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +37 -20
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +33 -24
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +16 -13
- package/templates/apps/android-app/src/lib/api.ts.hbs +5 -4
- package/templates/apps/android-app/src/lib/capacitor.ts +1 -1
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +11 -10
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +17 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +4 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +14 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +13 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server-app.ts.hbs +23 -3
- package/templates/apps/h5/messages/en-US.json +3 -0
- package/templates/apps/h5/messages/zh-CN.json +3 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +30 -29
- package/templates/apps/h5/src/routes/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +17 -6
- package/templates/apps/website/src/routes/+layout.svelte.hbs +35 -26
- package/templates/apps/website/src/routes/+page.svelte.hbs +28 -10
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +11 -6
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +37 -34
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +27 -40
- package/templates/base/package.json.hbs +30 -24
- package/templates/base/src/app.html.hbs +4 -0
- package/templates/base/src/hooks.server.ts.hbs +17 -7
- package/templates/base/src/lib/server/init.ts.hbs +20 -12
- package/templates/base/static/favicon.png +0 -0
- package/templates/base/tests/scaffold.test.ts.hbs +9 -1
- package/templates/base/vite.config.ts.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +5 -5
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +34 -32
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +46 -48
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +35 -31
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +41 -38
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +15 -7
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +1 -2
- package/templates/skills/AGENTS.md +11 -1
- package/templates/skills/CLAUDE.md +2 -1
- package/templates/skills/copilot-instructions.md +22 -6
- package/templates/skills/hai-ai/SKILL.md +115 -646
- package/templates/skills/hai-ai/reference.md +197 -0
- package/templates/skills/hai-api-client/SKILL.md +7 -1
- package/templates/skills/hai-app-create/SKILL.md +1 -1
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-audit/SKILL.md +7 -2
- package/templates/skills/hai-cache/SKILL.md +2 -0
- package/templates/skills/hai-capacitor/SKILL.md +11 -9
- package/templates/skills/hai-core/SKILL.md +4 -1
- package/templates/skills/hai-crypto/SKILL.md +42 -8
- package/templates/skills/hai-deploy/SKILL.md +12 -9
- package/templates/skills/hai-iam/SKILL.md +2 -0
- package/templates/skills/hai-kit/SKILL.md +14 -5
- package/templates/skills/hai-payment/SKILL.md +5 -0
- package/templates/skills/hai-reach/SKILL.md +3 -1
- package/templates/skills/hai-reldb/SKILL.md +2 -0
- package/templates/skills/hai-scheduler/SKILL.md +2 -0
- package/templates/skills/hai-serv/SKILL.md +5 -0
- package/templates/skills/hai-storage/SKILL.md +10 -1
- package/templates/skills/hai-ui/SKILL.md +25 -11
- package/templates/skills/hai-vecdb/SKILL.md +8 -1
- package/dist/chunk-BPO34NRL.js.map +0 -1
- package/dist/cli-config-templates-JCS7T3NU.js +0 -5
- 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,
|
|
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})
|
|
14542
|
-
deploy_fileUploadFailed: "File upload failed ({status})
|
|
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
|
|
14569
|
-
deploy_fileUploadFailed: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF08{status}\uFF09
|
|
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
|
-
|
|
14632
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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([
|
|
15181
|
+
body: new Blob([uploadContent])
|
|
15125
15182
|
});
|
|
15126
15183
|
if (!response.ok && response.status !== 409) {
|
|
15127
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
15355
|
+
name: projectName,
|
|
15249
15356
|
pg_version: 16
|
|
15250
15357
|
}
|
|
15251
15358
|
})
|
|
15252
15359
|
});
|
|
15253
|
-
|
|
15254
|
-
|
|
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
|
|
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)
|
|
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)
|
|
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
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
|
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:
|
|
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.
|
|
15787
|
+
logger10.error("Provisioner authentication failed", {
|
|
15631
15788
|
service: serviceType,
|
|
15632
15789
|
provisioner: serviceConfig.provisioner
|
|
15633
15790
|
});
|
|
15634
|
-
|
|
15635
|
-
|
|
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
|
|
15775
|
-
//# sourceMappingURL=src-
|
|
15776
|
-
//# sourceMappingURL=src-
|
|
15932
|
+
export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy };
|
|
15933
|
+
//# sourceMappingURL=src-QNEJJ3BV.js.map
|
|
15934
|
+
//# sourceMappingURL=src-QNEJJ3BV.js.map
|