@h-ai/cli 0.1.0-alpha.65 → 0.1.0-alpha.66
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/README.en.md +13 -7
- package/README.ja.md +13 -7
- package/README.md +14 -8
- package/dist/{chunk-IPG3QNYK.js → chunk-FWN5B54L.js} +36 -4
- package/dist/chunk-FWN5B54L.js.map +1 -0
- package/dist/{chunk-47V5OT22.js → chunk-QYX6V7H2.js} +17 -43
- package/dist/chunk-QYX6V7H2.js.map +1 -0
- package/dist/cli-config-templates-3XSFIXBP.js +5 -0
- package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-3XSFIXBP.js.map} +1 -1
- package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-TSYEQTCY.js} +3 -3
- package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-TSYEQTCY.js.map} +1 -1
- package/dist/index.js +273 -208
- package/dist/index.js.map +1 -1
- package/dist/{src-6PPBKQ6Q.js → src-RDQ2EIBT.js} +506 -488
- package/dist/src-RDQ2EIBT.js.map +1 -0
- package/package.json +8 -8
- package/templates/apps/api/.dockerignore.hbs +4 -5
- package/templates/apps/api/.gitignore.hbs +2 -1
- package/templates/apps/api/Dockerfile.hbs +2 -15
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +0 -1
- package/templates/apps/api/hai.build.json.hbs +1 -0
- package/templates/apps/api/package.json.hbs +5 -8
- package/templates/apps/fullstack/.dockerignore.hbs +4 -5
- package/templates/apps/fullstack/.gitignore.hbs +2 -1
- package/templates/apps/fullstack/Dockerfile.hbs +3 -15
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +0 -3
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +3 -6
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +2 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +1 -0
- package/templates/apps/fullstack/hai.build.json.hbs +7 -0
- package/templates/apps/fullstack/package.json.hbs +5 -12
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +1 -1
- package/templates/apps/mobile-app/index.html.hbs +1 -0
- package/templates/apps/mobile-app/public/runtime-config.js +1 -0
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
- package/templates/base/.dockerignore.hbs +4 -5
- package/templates/base/.gitignore.hbs +2 -1
- package/templates/base/Dockerfile.hbs +5 -22
- package/templates/base/docker/nginx.conf.hbs +6 -0
- package/templates/base/hai.build.json.hbs +1 -0
- package/templates/base/package.json.hbs +13 -17
- package/templates/base/svelte.config.js.hbs +2 -0
- package/templates/base/vite.config.ts.hbs +6 -2
- package/templates/shared/.gitattributes +5 -0
- package/templates/shared/container-runtime-config.sh +30 -0
- package/templates/shared/run-e2e.mjs +65 -0
- package/templates/shared/vite-runtime-config.ts +54 -0
- package/templates/skills/hai-app-create/development.md +3 -1
- package/templates/skills/hai-app-create/reference.md +2 -2
- package/templates/skills/hai-build/SKILL.md +16 -1
- package/templates/skills/hai-deploy/SKILL.md +52 -260
- package/templates/skills/hai-deploy/middleware.md +8 -14
- package/templates/skills/hai-ui/reference.md +2 -0
- package/dist/chunk-47V5OT22.js.map +0 -1
- package/dist/chunk-IPG3QNYK.js.map +0 -1
- package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
- package/dist/src-6PPBKQ6Q.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { __export } from './chunk-2H7UOFLK.js';
|
|
3
|
-
import {
|
|
4
|
-
import { readFileSync, existsSync, writeFileSync, chmodSync, mkdirSync, statSync, readdirSync } from 'fs';
|
|
3
|
+
import { createHash, randomBytes } from 'crypto';
|
|
4
|
+
import { createReadStream, readFileSync, existsSync, lstatSync, writeFileSync, chmodSync, mkdirSync, mkdtempSync, copyFileSync, rmSync, statSync, readdirSync } from 'fs';
|
|
5
5
|
import { resolve, join, basename, dirname, relative, sep } from 'path';
|
|
6
|
+
import { core, ok, err } from '@h-ai/core';
|
|
6
7
|
import process2 from 'process';
|
|
7
8
|
import { parseEnv } from 'util';
|
|
8
9
|
import { parse as parse$1, stringify, parseAllDocuments } from 'yaml';
|
|
9
10
|
import { homedir } from 'os';
|
|
10
|
-
import {
|
|
11
|
-
import { randomBytes, createHash } from 'crypto';
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
12
|
import { Buffer } from 'buffer';
|
|
13
13
|
|
|
14
14
|
// ../../node_modules/zod/v4/classic/external.js
|
|
@@ -1398,10 +1398,10 @@ function defineLazyInternal(inst, key, compute) {
|
|
|
1398
1398
|
Object.defineProperty(this, key, { configurable: true, writable: true, value });
|
|
1399
1399
|
broke = broke || outer;
|
|
1400
1400
|
return value;
|
|
1401
|
-
} catch (
|
|
1401
|
+
} catch (err15) {
|
|
1402
1402
|
delete this[key];
|
|
1403
1403
|
broke = broke || outer;
|
|
1404
|
-
throw
|
|
1404
|
+
throw err15;
|
|
1405
1405
|
}
|
|
1406
1406
|
},
|
|
1407
1407
|
set(value) {
|
|
@@ -1856,18 +1856,18 @@ var validateAsync = async (schema, value, _ctx) => {
|
|
|
1856
1856
|
return result.issues.length === 0;
|
|
1857
1857
|
};
|
|
1858
1858
|
var _encode = (_Err) => {
|
|
1859
|
-
const
|
|
1859
|
+
const parse7 = _parse(_Err);
|
|
1860
1860
|
const fn = (schema, value, _ctx, _params) => {
|
|
1861
1861
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
1862
|
-
return
|
|
1862
|
+
return parse7(schema, value, ctx, finalizeParams(fn, _params));
|
|
1863
1863
|
};
|
|
1864
1864
|
return fn;
|
|
1865
1865
|
};
|
|
1866
1866
|
var encode = /* @__PURE__ */ _encode($ZodRealError);
|
|
1867
1867
|
var _decode = (_Err) => {
|
|
1868
|
-
const
|
|
1868
|
+
const parse7 = _parse(_Err);
|
|
1869
1869
|
const fn = (schema, value, _ctx, _params) => {
|
|
1870
|
-
return
|
|
1870
|
+
return parse7(schema, value, _ctx, finalizeParams(fn, _params));
|
|
1871
1871
|
};
|
|
1872
1872
|
return fn;
|
|
1873
1873
|
};
|
|
@@ -5037,7 +5037,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
5037
5037
|
var NONE = 0;
|
|
5038
5038
|
var ASSUMED = 1;
|
|
5039
5039
|
var PROVEN = 2;
|
|
5040
|
-
function isRecursive(inst, stack,
|
|
5040
|
+
function isRecursive(inst, stack, resolve6) {
|
|
5041
5041
|
const cached2 = recursive.get(inst);
|
|
5042
5042
|
if (cached2 !== void 0)
|
|
5043
5043
|
return cached2 ? PROVEN : NONE;
|
|
@@ -5047,7 +5047,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
5047
5047
|
let result = NONE;
|
|
5048
5048
|
const check2 = (child) => {
|
|
5049
5049
|
if (result !== PROVEN && child?._zod) {
|
|
5050
|
-
const answer = isRecursive(child, stack,
|
|
5050
|
+
const answer = isRecursive(child, stack, resolve6);
|
|
5051
5051
|
if (answer > result)
|
|
5052
5052
|
result = answer;
|
|
5053
5053
|
}
|
|
@@ -5058,7 +5058,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
5058
5058
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
5059
5059
|
if (!desc.enumerable)
|
|
5060
5060
|
continue;
|
|
5061
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
5061
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve6) : NONE;
|
|
5062
5062
|
if (child > answer)
|
|
5063
5063
|
answer = child;
|
|
5064
5064
|
}
|
|
@@ -5122,7 +5122,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
5122
5122
|
break;
|
|
5123
5123
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
5124
5124
|
case "lazy": {
|
|
5125
|
-
const inner = def._cachedInner ?? (
|
|
5125
|
+
const inner = def._cachedInner ?? (resolve6 ? inst._zod.innerType : void 0);
|
|
5126
5126
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
5127
5127
|
break;
|
|
5128
5128
|
}
|
|
@@ -12940,9 +12940,9 @@ function compile(schema, options) {
|
|
|
12940
12940
|
const clone2 = withParser(schema, parser);
|
|
12941
12941
|
clone2._zod.bag.validator = compileValidator(schema, parser);
|
|
12942
12942
|
return clone2;
|
|
12943
|
-
} catch (
|
|
12943
|
+
} catch (err15) {
|
|
12944
12944
|
if (options?.strict)
|
|
12945
|
-
throw
|
|
12945
|
+
throw err15;
|
|
12946
12946
|
return schema;
|
|
12947
12947
|
}
|
|
12948
12948
|
}
|
|
@@ -13032,8 +13032,8 @@ ${code}
|
|
|
13032
13032
|
try {
|
|
13033
13033
|
const factory = new F(...constantNames, factoryCode);
|
|
13034
13034
|
fn = factory(...constantValues);
|
|
13035
|
-
} catch (
|
|
13036
|
-
throw new ZodCompileUnsupportedError(`this schema (generated code failed to evaluate: ${
|
|
13035
|
+
} catch (err15) {
|
|
13036
|
+
throw new ZodCompileUnsupportedError(`this schema (generated code failed to evaluate: ${err15.message})`);
|
|
13037
13037
|
}
|
|
13038
13038
|
if (options?.debug) {
|
|
13039
13039
|
fn.code = fullCode;
|
|
@@ -13071,9 +13071,9 @@ function compileChild(doc, ctx, schema, accessor, needsValue = true) {
|
|
|
13071
13071
|
const varCounter = ctx.varCounter;
|
|
13072
13072
|
try {
|
|
13073
13073
|
return generateCheck(doc, ctx, schema, accessor, needsValue);
|
|
13074
|
-
} catch (
|
|
13075
|
-
if (!(
|
|
13076
|
-
throw
|
|
13074
|
+
} catch (err15) {
|
|
13075
|
+
if (!(err15 instanceof ZodCompileUnsupportedError) || !err15.islandable)
|
|
13076
|
+
throw err15;
|
|
13077
13077
|
doc.content.length = contentLen;
|
|
13078
13078
|
if (ctx.constants.size > constantCount) {
|
|
13079
13079
|
const trailing = Array.from(ctx.constants.keys()).slice(constantCount);
|
|
@@ -19668,6 +19668,44 @@ function date4(params) {
|
|
|
19668
19668
|
return _coercedDate(ZodDate, params);
|
|
19669
19669
|
}
|
|
19670
19670
|
|
|
19671
|
+
// ../deploy/src/deploy-artifact.ts
|
|
19672
|
+
var DeploymentArtifactSchema = external_exports.object({
|
|
19673
|
+
/** 清单格式版本,与应用版本和部署环境无关。 */
|
|
19674
|
+
version: external_exports.literal(2),
|
|
19675
|
+
/** 应用根 package.json 的版本,用于归档、Compose 与桌面交付;最长 80 字符。 */
|
|
19676
|
+
appVersion: external_exports.string().max(80).regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9a-z-]+(?:\.[0-9a-z-]+)*)?(?:\+[0-9a-z-]+(?:\.[0-9a-z-]+)*)?$/i),
|
|
19677
|
+
/** 一次完整构建的标识,app/web 必须来自同一份清单。 */
|
|
19678
|
+
buildId: external_exports.string().regex(/^[a-z0-9][\w.+-]*$/i),
|
|
19679
|
+
/** API/独立应用和可选 Web 的镜像归档,至少包含一个 app。 */
|
|
19680
|
+
images: external_exports.array(external_exports.object({
|
|
19681
|
+
/** app 为服务或独立应用,web 为同源 API 代理的静态前端。 */
|
|
19682
|
+
name: external_exports.enum(["app", "web"]),
|
|
19683
|
+
/** 相对于清单目录的 tar 文件名。 */
|
|
19684
|
+
file: external_exports.string().regex(/^[\w.+-]+\.tar$/),
|
|
19685
|
+
/** 归档导入后可直接使用的不可变镜像引用。 */
|
|
19686
|
+
imageRef: external_exports.string().regex(/^[a-z0-9][a-z0-9./:_-]+$/),
|
|
19687
|
+
/** tar 内容的 SHA-256,用于部署前校验。 */
|
|
19688
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
19689
|
+
/** 容器内部监听端口;服务器公开端口由部署选择。 */
|
|
19690
|
+
containerPort: external_exports.number().int().min(1).max(65535).optional()
|
|
19691
|
+
})).min(1).max(2).refine((images) => images.filter((image) => image.name === "app").length === 1 && new Set(images.map((image) => image.name)).size === images.length)
|
|
19692
|
+
}).refine((artifact) => artifact.images.every((image) => image.file === `${image.name}-${artifact.appVersion}.tar`));
|
|
19693
|
+
async function artifactChecksum(file2) {
|
|
19694
|
+
const hash2 = createHash("sha256");
|
|
19695
|
+
for await (const chunk of createReadStream(file2))
|
|
19696
|
+
hash2.update(chunk);
|
|
19697
|
+
return hash2.digest("hex");
|
|
19698
|
+
}
|
|
19699
|
+
async function readDeploymentArtifact(directory) {
|
|
19700
|
+
const artifact = DeploymentArtifactSchema.parse(JSON.parse(readFileSync(resolve(directory, "manifest.json"), "utf8")));
|
|
19701
|
+
for (const image of artifact.images) {
|
|
19702
|
+
const file2 = resolve(directory, image.file);
|
|
19703
|
+
if (!existsSync(file2) || !lstatSync(file2).isFile() || lstatSync(file2).isSymbolicLink() || await artifactChecksum(file2) !== image.sha256)
|
|
19704
|
+
throw new Error(`Invalid artifact: ${image.file}`);
|
|
19705
|
+
}
|
|
19706
|
+
return artifact;
|
|
19707
|
+
}
|
|
19708
|
+
|
|
19671
19709
|
// ../deploy/messages/en-US.json
|
|
19672
19710
|
var en_US_default = {
|
|
19673
19711
|
deploy_notInitialized: "Deploy module not initialized, call deploy.init() first",
|
|
@@ -19678,7 +19716,6 @@ var en_US_default = {
|
|
|
19678
19716
|
deploy_authRequired: "Authentication required",
|
|
19679
19717
|
deploy_authFailed: "Authentication failed: {error}",
|
|
19680
19718
|
deploy_projectCreateFailed: "Project creation failed: {error}",
|
|
19681
|
-
deploy_buildFailed: "Application build failed: {error}",
|
|
19682
19719
|
deploy_uploadFailed: "Build output upload failed: {error}",
|
|
19683
19720
|
deploy_deployFailed: "Deployment failed: {error}",
|
|
19684
19721
|
deploy_provisionFailed: "Service provisioning failed: {service} - {error}",
|
|
@@ -19698,8 +19735,6 @@ var en_US_default = {
|
|
|
19698
19735
|
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.",
|
|
19699
19736
|
deploy_commandTimeout: "Command timed out: {command}",
|
|
19700
19737
|
deploy_commandFailed: "Command failed ({command}): {error}",
|
|
19701
|
-
deploy_containerRuntimeNotFound: "No usable container runtime found: {runtime}",
|
|
19702
|
-
deploy_containerBuildFailed: "Container image build failed: {error}",
|
|
19703
19738
|
deploy_remoteConnectFailed: "Cannot connect to remote host {host}: {error}",
|
|
19704
19739
|
deploy_remoteRuntimeMissing: "Remote host missing container runtime or compose: {runtime}",
|
|
19705
19740
|
deploy_imageTransferFailed: "Image transfer failed: {error}",
|
|
@@ -19720,7 +19755,13 @@ var en_US_default = {
|
|
|
19720
19755
|
deploy_purposeLdap: "Directory account authentication; the default directory has no business users",
|
|
19721
19756
|
deploy_purposeConsole: "Messages go to application logs without delivery",
|
|
19722
19757
|
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"
|
|
19758
|
+
deploy_purposeSms: "SMS through Aliyun credentials; requires an existing signature and quota",
|
|
19759
|
+
deploy_webProxyInvalid: "The API prefix must be an absolute path containing only letters, numbers, underscores, hyphens and slashes.",
|
|
19760
|
+
deploy_artifactInvalid: "Build artifacts are missing, corrupt, or could not be packaged. Run pnpm build --target docker at the project root and point --artifact-dir to a complete build directory.",
|
|
19761
|
+
deploy_sshRequired: "Automatic deployment requires SSH settings. Use --bundle-only for manual transfer when the server is unreachable.",
|
|
19762
|
+
deploy_bundleInstructions: "This bundle contains private configuration. Transfer it securely and restrict access.\n1. Copy {file} to Linux using removable media, a private network or scp.\n2. Install Docker/Podman with compose, tar, sha256sum and sh. Load middleware images using docker load/podman load with the exact tags below.\n3. Create a private release directory: mkdir -p release && chmod 700 release\n4. From the bundle directory run tar -xf {file} -C release\n5. Run cd release && sh {start}. Set HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME=docker or podman to select the runtime explicitly.\n6. Check docker compose -f {compose} --env-file .env.runtime ps (or logs; use podman instead of docker for Podman), Web and /health after startup. Bundle generation does not perform server health checks.\nRequired images (app/web are included; prepare middleware separately):\n{images}\nBuilt images can be reused across environments; this bundle contains configuration for the selected environment only.",
|
|
19763
|
+
deploy_bundleMetadata: "Application version: {version}\nBuild ID: {buildId}\nCompose file: {compose}",
|
|
19764
|
+
deploy_bundleOnlyContainer: "Compose bundle generation is supported only by docker-ssh."
|
|
19724
19765
|
};
|
|
19725
19766
|
|
|
19726
19767
|
// ../deploy/messages/zh-CN.json
|
|
@@ -19733,7 +19774,6 @@ var zh_CN_default2 = {
|
|
|
19733
19774
|
deploy_authRequired: "\u8BF7\u5148\u5B8C\u6210\u8BA4\u8BC1",
|
|
19734
19775
|
deploy_authFailed: "\u8BA4\u8BC1\u5931\u8D25\uFF1A{error}",
|
|
19735
19776
|
deploy_projectCreateFailed: "\u9879\u76EE\u521B\u5EFA\u5931\u8D25\uFF1A{error}",
|
|
19736
|
-
deploy_buildFailed: "\u5E94\u7528\u6784\u5EFA\u5931\u8D25\uFF1A{error}",
|
|
19737
19777
|
deploy_uploadFailed: "\u6784\u5EFA\u4EA7\u7269\u4E0A\u4F20\u5931\u8D25\uFF1A{error}",
|
|
19738
19778
|
deploy_deployFailed: "\u90E8\u7F72\u5931\u8D25\uFF1A{error}",
|
|
19739
19779
|
deploy_provisionFailed: "\u57FA\u7840\u8BBE\u65BD\u5F00\u901A\u5931\u8D25\uFF1A{service} - {error}",
|
|
@@ -19753,8 +19793,6 @@ var zh_CN_default2 = {
|
|
|
19753
19793
|
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",
|
|
19754
19794
|
deploy_commandTimeout: "\u547D\u4EE4\u6267\u884C\u8D85\u65F6\uFF1A{command}",
|
|
19755
19795
|
deploy_commandFailed: "\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF08{command}\uFF09\uFF1A{error}",
|
|
19756
|
-
deploy_containerRuntimeNotFound: "\u672A\u627E\u5230\u53EF\u7528\u7684\u5BB9\u5668\u8FD0\u884C\u65F6\uFF1A{runtime}",
|
|
19757
|
-
deploy_containerBuildFailed: "\u5BB9\u5668\u955C\u50CF\u6784\u5EFA\u5931\u8D25\uFF1A{error}",
|
|
19758
19796
|
deploy_remoteConnectFailed: "\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B\u4E3B\u673A {host}\uFF1A{error}",
|
|
19759
19797
|
deploy_remoteRuntimeMissing: "\u8FDC\u7A0B\u4E3B\u673A\u7F3A\u5C11\u5BB9\u5668\u8FD0\u884C\u65F6\u6216 compose\uFF1A{runtime}",
|
|
19760
19798
|
deploy_imageTransferFailed: "\u955C\u50CF\u4F20\u8F93\u5931\u8D25\uFF1A{error}",
|
|
@@ -19775,7 +19813,13 @@ var zh_CN_default2 = {
|
|
|
19775
19813
|
deploy_purposeLdap: "\u4F01\u4E1A\u76EE\u5F55\u8D26\u53F7\u8BA4\u8BC1\uFF1B\u9ED8\u8BA4\u76EE\u5F55\u4E0D\u5305\u542B\u4E1A\u52A1\u7528\u6237",
|
|
19776
19814
|
deploy_purposeConsole: "\u6D88\u606F\u8F93\u51FA\u5230\u5E94\u7528\u65E5\u5FD7\uFF0C\u4E0D\u5B9E\u9645\u6295\u9012",
|
|
19777
19815
|
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"
|
|
19816
|
+
deploy_purposeSms: "\u901A\u8FC7\u963F\u91CC\u4E91\u51ED\u8BC1\u63A5\u5165\u77ED\u4FE1\u670D\u52A1\uFF0C\u9700\u5DF2\u6709\u7B7E\u540D\u4E0E\u989D\u5EA6",
|
|
19817
|
+
deploy_webProxyInvalid: "API \u524D\u7F00\u5FC5\u987B\u662F\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u8FDE\u5B57\u7B26\u548C\u659C\u6760\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
|
|
19818
|
+
deploy_artifactInvalid: "\u6784\u5EFA\u4EA7\u7269\u7F3A\u5931\u3001\u635F\u574F\u6216\u90E8\u7F72\u6253\u5305\u5931\u8D25\u3002\u8BF7\u5148\u5728\u5DE5\u7A0B\u6839\u8FD0\u884C pnpm build --target docker\uFF0C\u5E76\u68C0\u67E5 --artifact-dir \u6307\u5411\u5B8C\u6574\u7684\u6784\u5EFA\u76EE\u5F55\u3002",
|
|
19819
|
+
deploy_sshRequired: "\u81EA\u52A8\u90E8\u7F72\u9700\u8981 SSH \u914D\u7F6E\uFF1B\u65E0\u7F51\u7EDC\u8FDE\u63A5\u65F6\u4F7F\u7528 --bundle-only \u751F\u6210\u90E8\u7F72\u5305\u5E76\u624B\u5DE5\u642C\u8FD0\u3002",
|
|
19820
|
+
deploy_bundleInstructions: "\u6B64\u5305\u5305\u542B\u79C1\u6709\u914D\u7F6E\uFF0C\u8BF7\u5B89\u5168\u642C\u8FD0\u5E76\u9650\u5236\u8BBF\u95EE\u6743\u9650\u3002\n1. \u5C06 {file} \u590D\u5236\u5230 Linux \u670D\u52A1\u5668\uFF08U \u76D8\u3001\u5185\u7F51\u4F20\u8F93\u6216 scp \u5747\u53EF\uFF09\u3002\n2. \u51C6\u5907 Docker/Podman\u3001\u5BF9\u5E94 compose \u547D\u4EE4\u3001tar\u3001sha256sum \u548C sh\uFF1B\u5C06\u6240\u9700\u4E2D\u95F4\u4EF6\u955C\u50CF\u7528 docker load/podman load \u5BFC\u5165\uFF0C\u955C\u50CF\u6807\u7B7E\u987B\u4E0E\u4E0B\u5217\u6E05\u5355\u5B8C\u5168\u4E00\u81F4\u3002\n3. \u65B0\u5EFA\u4EC5\u90E8\u7F72\u7528\u6237\u53EF\u8BBF\u95EE\u7684\u76EE\u5F55\uFF1Amkdir -p release && chmod 700 release\n4. \u5728\u5305\u6240\u5728\u76EE\u5F55\u6267\u884C tar -xf {file} -C release\n5. \u6267\u884C cd release && sh {start}\uFF1B\u53EF\u7528 HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME=docker \u6216 podman \u663E\u5F0F\u9009\u62E9\u8FD0\u884C\u65F6\u3002\n6. \u542F\u52A8\u540E\u7528 docker compose -f {compose} --env-file .env.runtime ps\uFF08\u6216 logs\uFF1BPodman \u5C06 docker \u6362\u4E3A podman\uFF09\u68C0\u67E5\u72B6\u6001\u5E76\u9A8C\u8BC1 Web\u3001/health\uFF1B\u4EC5\u6253\u5305\u4E0D\u4F1A\u6267\u884C\u670D\u52A1\u5668\u5065\u5EB7\u68C0\u67E5\u3002\n\u6240\u9700\u955C\u50CF\uFF08app/web \u5DF2\u542B\u5728\u5305\u4E2D\uFF0C\u4E2D\u95F4\u4EF6\u9700\u53E6\u884C\u51C6\u5907\uFF09\uFF1A\n{images}\n\u6784\u5EFA\u955C\u50CF\u53EF\u7528\u4E8E\u591A\u73AF\u5883\uFF1B\u6B64\u5305\u7684\u73AF\u5883\u914D\u7F6E\u4EC5\u7528\u4E8E\u672C\u6B21\u6240\u9009\u73AF\u5883\u3002",
|
|
19821
|
+
deploy_bundleMetadata: "\u5E94\u7528\u7248\u672C\uFF1A{version}\n\u6784\u5EFA\u7F16\u53F7\uFF1A{buildId}\nCompose \u6587\u4EF6\uFF1A{compose}",
|
|
19822
|
+
deploy_bundleOnlyContainer: "\u4EC5\u751F\u6210 Compose tar \u53EA\u652F\u6301 docker-ssh\u3002"
|
|
19779
19823
|
};
|
|
19780
19824
|
|
|
19781
19825
|
// ../deploy/src/deploy-i18n.ts
|
|
@@ -19816,7 +19860,7 @@ var ExposeSchema = PortExposeSchema;
|
|
|
19816
19860
|
var DockerSshProviderConfigSchema = external_exports.object({
|
|
19817
19861
|
type: external_exports.literal("docker-ssh"),
|
|
19818
19862
|
/** SSH 连接参数 */
|
|
19819
|
-
ssh: SshConfigSchema,
|
|
19863
|
+
ssh: SshConfigSchema.optional(),
|
|
19820
19864
|
/** 远程主机运行参数 */
|
|
19821
19865
|
remote: external_exports.object({
|
|
19822
19866
|
/** 远程应用根目录 */
|
|
@@ -19870,24 +19914,15 @@ var DeployConfigSchema = external_exports.object({
|
|
|
19870
19914
|
environment: external_exports.string().regex(/^[a-z0-9][\w-]*$/i).refine((value) => !["local", "example"].includes(value.toLowerCase())).optional(),
|
|
19871
19915
|
provider: ProviderConfigSchema,
|
|
19872
19916
|
services: ServicesConfigSchema,
|
|
19873
|
-
/**
|
|
19917
|
+
/** Docker Compose 部署参数;构建配置位于项目 hai.build.json。 */
|
|
19874
19918
|
container: external_exports.object({
|
|
19875
|
-
runtime: external_exports.enum(["auto", "docker", "podman"]).default("auto"),
|
|
19876
|
-
/** Podman 构建是否继承代理;容器不能访问宿主机 localhost 代理时设为 false。 */
|
|
19877
|
-
httpProxy: external_exports.boolean().default(true),
|
|
19878
19919
|
/** 自建 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()
|
|
19920
|
+
s3Engine: external_exports.enum(["rustfs", "minio"]).default("rustfs")
|
|
19885
19921
|
}).optional()
|
|
19886
19922
|
});
|
|
19887
19923
|
var DeployErrorInfo = {
|
|
19888
19924
|
DEPLOY_FAILED: "001:500",
|
|
19889
19925
|
PROJECT_CREATE_FAILED: "002:500",
|
|
19890
|
-
BUILD_FAILED: "003:500",
|
|
19891
19926
|
UPLOAD_FAILED: "004:500",
|
|
19892
19927
|
AUTH_REQUIRED: "005:401",
|
|
19893
19928
|
AUTH_FAILED: "006:401",
|
|
@@ -19899,8 +19934,6 @@ var DeployErrorInfo = {
|
|
|
19899
19934
|
UNSUPPORTED_TYPE: "012:400",
|
|
19900
19935
|
CONFIG_ERROR: "013:500",
|
|
19901
19936
|
CREDENTIAL_ERROR: "014:500",
|
|
19902
|
-
CONTAINER_RUNTIME_NOT_FOUND: "015:500",
|
|
19903
|
-
CONTAINER_BUILD_FAILED: "016:500",
|
|
19904
19937
|
REMOTE_CONNECT_FAILED: "017:500",
|
|
19905
19938
|
REMOTE_COMMAND_FAILED: "018:500",
|
|
19906
19939
|
IMAGE_TRANSFER_FAILED: "019:500",
|
|
@@ -19909,9 +19942,16 @@ var DeployErrorInfo = {
|
|
|
19909
19942
|
var HaiDeployError = core.error.buildHaiErrorsDef("deploy", DeployErrorInfo);
|
|
19910
19943
|
|
|
19911
19944
|
// ../deploy/src/deploy-environment.ts
|
|
19945
|
+
function environmentValue(value) {
|
|
19946
|
+
try {
|
|
19947
|
+
return parse$1(value) ?? value;
|
|
19948
|
+
} catch {
|
|
19949
|
+
return value;
|
|
19950
|
+
}
|
|
19951
|
+
}
|
|
19912
19952
|
function resolveConfig(value, prefix, env) {
|
|
19913
19953
|
if (env[prefix] !== void 0)
|
|
19914
|
-
return
|
|
19954
|
+
return environmentValue(env[prefix]);
|
|
19915
19955
|
if (Array.isArray(value))
|
|
19916
19956
|
return value.map((item, index) => resolveConfig(item, `${prefix}_${index}`, env));
|
|
19917
19957
|
if (value && typeof value === "object")
|
|
@@ -19919,7 +19959,7 @@ function resolveConfig(value, prefix, env) {
|
|
|
19919
19959
|
if (typeof value !== "string")
|
|
19920
19960
|
return value;
|
|
19921
19961
|
const resolved = value.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (_match, name, fallback) => env[name] ?? fallback ?? "");
|
|
19922
|
-
return /^\$\{[^}]+\}$/.test(value) ?
|
|
19962
|
+
return /^\$\{[^}]+\}$/.test(value) ? environmentValue(resolved) : resolved;
|
|
19923
19963
|
}
|
|
19924
19964
|
function loadDeploymentConfig(name, path, values) {
|
|
19925
19965
|
try {
|
|
@@ -20187,39 +20227,25 @@ function saveCredentials(entries) {
|
|
|
20187
20227
|
);
|
|
20188
20228
|
}
|
|
20189
20229
|
}
|
|
20190
|
-
|
|
20230
|
+
function resolveArtifactDirectory(appDir) {
|
|
20231
|
+
let directory = resolve(appDir);
|
|
20232
|
+
while (true) {
|
|
20233
|
+
if (existsSync(join(directory, "hai.build.json")) || existsSync(join(directory, "pnpm-lock.yaml")))
|
|
20234
|
+
return join(directory, ".hai/build/docker");
|
|
20235
|
+
const parent = dirname(directory);
|
|
20236
|
+
if (parent === directory)
|
|
20237
|
+
return join(appDir, ".hai/build/docker");
|
|
20238
|
+
directory = parent;
|
|
20239
|
+
}
|
|
20240
|
+
}
|
|
20191
20241
|
function usesDefaultMiddleware(scan, service, options) {
|
|
20192
20242
|
const requirements = scan.middlewares?.filter((item) => item.service === service) ?? [];
|
|
20193
20243
|
return requirements.length ? requirements.some((item) => (options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.[service] ?? "default") === "default") : (options?.serviceModes?.[service] ?? "default") === "default";
|
|
20194
20244
|
}
|
|
20195
|
-
function buildApp(appDir, buildCommand, envVars = {}) {
|
|
20196
|
-
logger2.info("Building application", { appDir, buildCommand });
|
|
20197
|
-
try {
|
|
20198
|
-
execSync(buildCommand, {
|
|
20199
|
-
cwd: appDir,
|
|
20200
|
-
stdio: "inherit",
|
|
20201
|
-
env: { ...process2.env, ...envVars }
|
|
20202
|
-
});
|
|
20203
|
-
return ok(void 0);
|
|
20204
|
-
} catch (error62) {
|
|
20205
|
-
return err(
|
|
20206
|
-
HaiDeployError.BUILD_FAILED,
|
|
20207
|
-
deployM("deploy_buildFailed", {
|
|
20208
|
-
params: { error: error62 instanceof Error ? error62.message : String(error62) }
|
|
20209
|
-
}),
|
|
20210
|
-
error62
|
|
20211
|
-
);
|
|
20212
|
-
}
|
|
20213
|
-
}
|
|
20214
20245
|
function resolveOutputDir(appDir, providerType) {
|
|
20215
20246
|
switch (providerType) {
|
|
20216
|
-
case "vercel":
|
|
20217
|
-
|
|
20218
|
-
if (existsSync(vercelOutput)) {
|
|
20219
|
-
return vercelOutput;
|
|
20220
|
-
}
|
|
20221
|
-
return join(appDir, "build");
|
|
20222
|
-
}
|
|
20247
|
+
case "vercel":
|
|
20248
|
+
return join(appDir, ".vercel", "output");
|
|
20223
20249
|
default:
|
|
20224
20250
|
return join(appDir, "build");
|
|
20225
20251
|
}
|
|
@@ -20250,7 +20276,7 @@ function recoveryFailure(error62, recovery) {
|
|
|
20250
20276
|
ext: { recovery }
|
|
20251
20277
|
});
|
|
20252
20278
|
}
|
|
20253
|
-
var
|
|
20279
|
+
var logger2 = core.logger.child({ module: "deploy", scope: "scanner" });
|
|
20254
20280
|
function record2(value) {
|
|
20255
20281
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
20256
20282
|
}
|
|
@@ -20369,15 +20395,170 @@ async function scanApp(inputDir, options) {
|
|
|
20369
20395
|
adapterInstalled: graph.build.has("@sveltejs/adapter-vercel"),
|
|
20370
20396
|
nodeAdapterInstalled: graph.build.has("@sveltejs/adapter-node"),
|
|
20371
20397
|
requiredServices: [...new Set(middlewares.map((item) => item.service))],
|
|
20372
|
-
serviceBackends: Object.fromEntries(middlewares.map((item) => [item.service, item.backend]))
|
|
20373
|
-
buildCommand: "pnpm build"
|
|
20398
|
+
serviceBackends: Object.fromEntries(middlewares.map((item) => [item.service, item.backend]))
|
|
20374
20399
|
};
|
|
20375
|
-
|
|
20400
|
+
logger2.info("Application scanned", { appName: scan.appName, haiModules: scan.haiModules, requiredServices: scan.requiredServices, serviceBackends: scan.serviceBackends });
|
|
20376
20401
|
return ok(scan);
|
|
20377
20402
|
} catch {
|
|
20378
20403
|
return err(HaiDeployError.SCAN_FAILED, deployM("deploy_scanFailed", { params: { error: "Invalid or ambiguous application/configuration/lockfile; specify the application directory and run pnpm install" } }));
|
|
20379
20404
|
}
|
|
20380
20405
|
}
|
|
20406
|
+
var logger3 = core.logger.child({ module: "deploy", scope: "command" });
|
|
20407
|
+
function maskSecrets(text, secrets) {
|
|
20408
|
+
if (!secrets || secrets.length === 0)
|
|
20409
|
+
return text;
|
|
20410
|
+
let masked = text;
|
|
20411
|
+
for (const secret of secrets) {
|
|
20412
|
+
if (secret && secret.length > 0)
|
|
20413
|
+
masked = masked.split(secret).join("***");
|
|
20414
|
+
}
|
|
20415
|
+
return masked;
|
|
20416
|
+
}
|
|
20417
|
+
function runCommand(command, args, options = {}) {
|
|
20418
|
+
return new Promise((resolve6) => {
|
|
20419
|
+
const child = spawn(command, args, {
|
|
20420
|
+
cwd: options.cwd,
|
|
20421
|
+
env: options.env ? { ...process2.env, ...options.env } : process2.env,
|
|
20422
|
+
shell: false
|
|
20423
|
+
});
|
|
20424
|
+
let stdout = "";
|
|
20425
|
+
let stderr = "";
|
|
20426
|
+
let settled = false;
|
|
20427
|
+
let timer;
|
|
20428
|
+
const finish = (result) => {
|
|
20429
|
+
if (settled)
|
|
20430
|
+
return;
|
|
20431
|
+
settled = true;
|
|
20432
|
+
if (timer)
|
|
20433
|
+
clearTimeout(timer);
|
|
20434
|
+
resolve6(result);
|
|
20435
|
+
};
|
|
20436
|
+
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
20437
|
+
timer = setTimeout(() => {
|
|
20438
|
+
child.kill("SIGKILL");
|
|
20439
|
+
logger3.error("Command timed out", { command, timeoutMs: options.timeoutMs });
|
|
20440
|
+
finish(err(
|
|
20441
|
+
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
20442
|
+
deployM("deploy_commandTimeout", { params: { command } })
|
|
20443
|
+
));
|
|
20444
|
+
}, options.timeoutMs);
|
|
20445
|
+
}
|
|
20446
|
+
child.stdout.on("data", (chunk) => {
|
|
20447
|
+
stdout += chunk.toString("utf-8");
|
|
20448
|
+
});
|
|
20449
|
+
child.stderr.on("data", (chunk) => {
|
|
20450
|
+
stderr += chunk.toString("utf-8");
|
|
20451
|
+
});
|
|
20452
|
+
child.on("error", (error62) => {
|
|
20453
|
+
logger3.error("Command failed to start", { command, error: maskSecrets(String(error62), options.secrets) });
|
|
20454
|
+
finish(err(
|
|
20455
|
+
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
20456
|
+
deployM("deploy_commandFailed", {
|
|
20457
|
+
params: { command, error: maskSecrets(error62.message, options.secrets) }
|
|
20458
|
+
}),
|
|
20459
|
+
error62
|
|
20460
|
+
));
|
|
20461
|
+
});
|
|
20462
|
+
child.on("close", (code) => {
|
|
20463
|
+
const exitCode = code ?? -1;
|
|
20464
|
+
if (exitCode === 0) {
|
|
20465
|
+
finish(ok({ code: exitCode, stdout, stderr }));
|
|
20466
|
+
return;
|
|
20467
|
+
}
|
|
20468
|
+
logger3.error("Command exited with non-zero code", {
|
|
20469
|
+
command,
|
|
20470
|
+
code: exitCode,
|
|
20471
|
+
stderr: maskSecrets(stderr, options.secrets).slice(-500),
|
|
20472
|
+
stdout: maskSecrets(stdout, options.secrets).slice(-3e3)
|
|
20473
|
+
});
|
|
20474
|
+
finish(err(
|
|
20475
|
+
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
20476
|
+
deployM("deploy_commandFailed", {
|
|
20477
|
+
params: { command, error: maskSecrets(`${stdout.slice(-2500)}
|
|
20478
|
+
${stderr || `exit ${exitCode}`}`, options.secrets).slice(-3e3) }
|
|
20479
|
+
})
|
|
20480
|
+
));
|
|
20481
|
+
});
|
|
20482
|
+
if (options.input !== void 0) {
|
|
20483
|
+
child.stdin.write(options.input);
|
|
20484
|
+
child.stdin.end();
|
|
20485
|
+
} else {
|
|
20486
|
+
child.stdin.end();
|
|
20487
|
+
}
|
|
20488
|
+
});
|
|
20489
|
+
}
|
|
20490
|
+
|
|
20491
|
+
// ../deploy/src/container/deploy-bundle.ts
|
|
20492
|
+
function shellArgument(value) {
|
|
20493
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
20494
|
+
}
|
|
20495
|
+
async function createDeploymentBundle(options) {
|
|
20496
|
+
const { directory, artifactDir, artifact, projectName, environment, runtime, coreEnv } = options;
|
|
20497
|
+
const stem = basename(options.output).replace(/\.tar$/i, "");
|
|
20498
|
+
const versionSuffix = `-${artifact.appVersion}`;
|
|
20499
|
+
const output2 = resolve(dirname(options.output), `${stem.endsWith(versionSuffix) ? stem : `${stem}${versionSuffix}`}.tar`);
|
|
20500
|
+
const composeName = `docker-compose-${artifact.appVersion}.yml`;
|
|
20501
|
+
mkdirSync(dirname(output2), { recursive: true, mode: 448 });
|
|
20502
|
+
const staging = mkdtempSync(join(directory, ".bundle-"));
|
|
20503
|
+
chmodSync(staging, 448);
|
|
20504
|
+
const temporaryTar = `${output2}.partial`;
|
|
20505
|
+
const startName = `start-${environment}.sh`;
|
|
20506
|
+
try {
|
|
20507
|
+
mkdirSync(join(staging, "images"));
|
|
20508
|
+
for (const image of artifact.images)
|
|
20509
|
+
copyFileSync(join(artifactDir, image.file), join(staging, "images", image.file));
|
|
20510
|
+
for (const file2 of [composeName, ".env.runtime", ...artifact.images.some((image) => image.name === "web") ? ["web.conf"] : []])
|
|
20511
|
+
copyFileSync(join(directory, file2), join(staging, file2));
|
|
20512
|
+
const compose = parse$1(readFileSync(join(directory, composeName), "utf8"));
|
|
20513
|
+
const images = [...new Set(Object.values(compose.services).flatMap((service) => service.image ? [service.image] : []))];
|
|
20514
|
+
const start = `#!/bin/sh
|
|
20515
|
+
set -eu
|
|
20516
|
+
cd "$(dirname "$0")"
|
|
20517
|
+
umask 077
|
|
20518
|
+
export HAI_DEPLOY_ENVIRONMENT=${shellArgument(environment)}
|
|
20519
|
+
export HAI_CORE_ENV=${shellArgument(coreEnv)}
|
|
20520
|
+
runtime=${shellArgument(runtime)}
|
|
20521
|
+
if [ -n "\${HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME:-}" ]; then runtime="$HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME"; fi
|
|
20522
|
+
if [ "$runtime" = auto ]; then
|
|
20523
|
+
if command -v podman >/dev/null 2>&1 && podman version >/dev/null 2>&1; then runtime=podman; else runtime=docker; fi
|
|
20524
|
+
fi
|
|
20525
|
+
case "$runtime" in docker|podman) ;; *) echo 'HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME must be docker or podman' >&2; exit 1;; esac
|
|
20526
|
+
"$runtime" version >/dev/null
|
|
20527
|
+
"$runtime" compose version >/dev/null
|
|
20528
|
+
sha256sum -c SHA256SUMS
|
|
20529
|
+
chmod 600 .env.runtime
|
|
20530
|
+
${artifact.images.map((image) => `"$runtime" load -i ${shellArgument(`images/${image.file}`)}`).join("\n")}
|
|
20531
|
+
# \u79BB\u7EBF\u670D\u52A1\u5668\u5E94\u5148\u5BFC\u5165\u5168\u90E8\u4E2D\u95F4\u4EF6\u955C\u50CF\uFF1B\u7981\u6B62\u9759\u9ED8\u5C1D\u8BD5\u8054\u7F51\u3002
|
|
20532
|
+
for image in ${images.map(shellArgument).join(" ")}; do
|
|
20533
|
+
"$runtime" image inspect "$image" >/dev/null 2>&1 || { echo "Missing image: $image. Load the matching image archive before retrying." >&2; exit 1; }
|
|
20534
|
+
done
|
|
20535
|
+
exec "$runtime" compose -f ${shellArgument(composeName)} --env-file .env.runtime -p ${shellArgument(`hai-${projectName}`)} up -d
|
|
20536
|
+
`;
|
|
20537
|
+
writeFileSync(join(directory, startName), start, { mode: 448 });
|
|
20538
|
+
writeFileSync(join(staging, startName), start, { mode: 448 });
|
|
20539
|
+
const instructions = deployM("deploy_bundleInstructions", { params: { file: basename(output2), start: startName, images: images.join("\n"), compose: composeName } });
|
|
20540
|
+
const metadata = deployM("deploy_bundleMetadata", { params: { version: artifact.appVersion, buildId: artifact.buildId, compose: composeName } });
|
|
20541
|
+
writeFileSync(join(staging, "README.txt"), `${metadata}
|
|
20542
|
+
${instructions}
|
|
20543
|
+
`);
|
|
20544
|
+
copyFileSync(join(artifactDir, "manifest.json"), join(staging, "manifest.json"));
|
|
20545
|
+
const checksums = [];
|
|
20546
|
+
for (const file2 of [composeName, "manifest.json", ".env.runtime", startName, ...artifact.images.some((image) => image.name === "web") ? ["web.conf"] : [], ...artifact.images.map((image) => `images/${image.file}`)])
|
|
20547
|
+
checksums.push(`${await artifactChecksum(join(staging, file2))} ${file2}`);
|
|
20548
|
+
writeFileSync(join(staging, "SHA256SUMS"), `${checksums.join("\n")}
|
|
20549
|
+
`);
|
|
20550
|
+
const tar = await runCommand("tar", ["-cf", temporaryTar, "-C", staging, "."]);
|
|
20551
|
+
if (!tar.success)
|
|
20552
|
+
throw new Error(tar.error.message);
|
|
20553
|
+
chmodSync(temporaryTar, 384);
|
|
20554
|
+
const { renameSync } = await import('fs');
|
|
20555
|
+
renameSync(temporaryTar, output2);
|
|
20556
|
+
return { bundlePath: output2, instructions, startName };
|
|
20557
|
+
} finally {
|
|
20558
|
+
rmSync(staging, { recursive: true, force: true });
|
|
20559
|
+
rmSync(temporaryTar, { force: true });
|
|
20560
|
+
}
|
|
20561
|
+
}
|
|
20381
20562
|
var IMAGES = {
|
|
20382
20563
|
postgres: "postgres:16-alpine",
|
|
20383
20564
|
mysql: "mysql:8.4",
|
|
@@ -20651,6 +20832,7 @@ function generateComposeFile(options) {
|
|
|
20651
20832
|
const { projectName, imageRef, hostPort, containerPort, sidecar } = options;
|
|
20652
20833
|
const app = {
|
|
20653
20834
|
image: imageRef,
|
|
20835
|
+
labels: { "org.opencontainers.image.version": options.appVersion },
|
|
20654
20836
|
container_name: `hai-${projectName}-app`,
|
|
20655
20837
|
restart: "unless-stopped",
|
|
20656
20838
|
// 镜像已由 load 注入本地,禁止 compose 再从 registry 拉取
|
|
@@ -20664,11 +20846,28 @@ function generateComposeFile(options) {
|
|
|
20664
20846
|
if (sidecar.dependsOn.length > 0)
|
|
20665
20847
|
app.depends_on = sidecar.dependsOn;
|
|
20666
20848
|
const services = { app, ...sidecar.composeServices };
|
|
20849
|
+
if (options.web) {
|
|
20850
|
+
delete app.ports;
|
|
20851
|
+
services.web = {
|
|
20852
|
+
image: options.web.imageRef,
|
|
20853
|
+
labels: { "org.opencontainers.image.version": options.appVersion },
|
|
20854
|
+
container_name: `hai-${projectName}-web`,
|
|
20855
|
+
restart: "unless-stopped",
|
|
20856
|
+
pull_policy: "never",
|
|
20857
|
+
ports: [`${hostPort}:${options.web.containerPort}`],
|
|
20858
|
+
environment: { PUBLIC_API_BASE: "" },
|
|
20859
|
+
healthcheck: { test: ["CMD", "wget", "-Y", "off", "-q", "-O", "/dev/null", `http://127.0.0.1:${options.web.containerPort}/_hai/web-health`], interval: "30s", timeout: "5s", retries: 3 },
|
|
20860
|
+
volumes: ["./web.conf:/etc/nginx/conf.d/default.conf:ro"],
|
|
20861
|
+
depends_on: ["app"],
|
|
20862
|
+
networks: ["hai-app"]
|
|
20863
|
+
};
|
|
20864
|
+
}
|
|
20667
20865
|
if (services["storage-init"])
|
|
20668
20866
|
services["storage-init"] = { ...services["storage-init"], image: imageRef };
|
|
20669
20867
|
const compose = {
|
|
20868
|
+
"x-hai-version": options.appVersion,
|
|
20670
20869
|
services,
|
|
20671
|
-
networks: {
|
|
20870
|
+
"networks": {
|
|
20672
20871
|
"hai-app": { name: `hai-${projectName}` }
|
|
20673
20872
|
}
|
|
20674
20873
|
};
|
|
@@ -20697,8 +20896,43 @@ function generateComposeFile(options) {
|
|
|
20697
20896
|
"storage-init": { condition: "service_completed_successfully" }
|
|
20698
20897
|
};
|
|
20699
20898
|
}
|
|
20899
|
+
for (const service of Object.values(services))
|
|
20900
|
+
service.pull_policy = "never";
|
|
20700
20901
|
return stringify(compose);
|
|
20701
20902
|
}
|
|
20903
|
+
function generateWebProxyConfig(port, apiPrefix, webPort = 8080) {
|
|
20904
|
+
if (!/^\/[\w/-]+$/.test(apiPrefix) || ![port, webPort].every((value) => Number.isInteger(value) && value >= 1 && value <= 65535))
|
|
20905
|
+
throw new Error("Invalid web proxy target");
|
|
20906
|
+
return `server {
|
|
20907
|
+
listen ${webPort};
|
|
20908
|
+
server_name _;
|
|
20909
|
+
root /usr/share/nginx/html;
|
|
20910
|
+
location / {
|
|
20911
|
+
try_files $uri $uri/ /index.html;
|
|
20912
|
+
}
|
|
20913
|
+
location = /runtime-config.js {
|
|
20914
|
+
add_header Cache-Control "no-store";
|
|
20915
|
+
default_type application/javascript;
|
|
20916
|
+
return 200 'globalThis.__HAI_PUBLIC_CONFIG__={apiBase:globalThis.location.origin+"${apiPrefix}"};';
|
|
20917
|
+
}
|
|
20918
|
+
location = /_hai/web-health {
|
|
20919
|
+
access_log off;
|
|
20920
|
+
return 200 'ok';
|
|
20921
|
+
}
|
|
20922
|
+
location = /health {
|
|
20923
|
+
proxy_pass http://app:${port};
|
|
20924
|
+
proxy_set_header Host $http_host;
|
|
20925
|
+
}
|
|
20926
|
+
location ${apiPrefix.replace(/\/$/, "")}/ {
|
|
20927
|
+
proxy_pass http://app:${port};
|
|
20928
|
+
proxy_http_version 1.1;
|
|
20929
|
+
proxy_set_header Host $http_host;
|
|
20930
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
20931
|
+
proxy_buffering off;
|
|
20932
|
+
}
|
|
20933
|
+
}
|
|
20934
|
+
`;
|
|
20935
|
+
}
|
|
20702
20936
|
function serializeEnvFile(env) {
|
|
20703
20937
|
return `${Object.entries(env).map(([key, value]) => {
|
|
20704
20938
|
let literal2 = value;
|
|
@@ -20714,194 +20948,7 @@ function serializeEnvFile(env) {
|
|
|
20714
20948
|
}).join("\n")}
|
|
20715
20949
|
`;
|
|
20716
20950
|
}
|
|
20717
|
-
|
|
20718
|
-
return `**/node_modules
|
|
20719
|
-
**/dist
|
|
20720
|
-
**/build
|
|
20721
|
-
**/.turbo
|
|
20722
|
-
**/.svelte-kit
|
|
20723
|
-
**/coverage
|
|
20724
|
-
**/test-results
|
|
20725
|
-
**/playwright-report
|
|
20726
|
-
**/.hai/deploy
|
|
20727
|
-
**/.env.local
|
|
20728
|
-
**/.env.*.local
|
|
20729
|
-
**/.env.runtime
|
|
20730
|
-
.git
|
|
20731
|
-
`;
|
|
20732
|
-
}
|
|
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";
|
|
20738
|
-
return `# syntax=docker/dockerfile:1.7
|
|
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
|
|
20742
|
-
ENV PNPM_HOME=/pnpm
|
|
20743
|
-
ENV PATH=$PNPM_HOME:$PATH
|
|
20744
|
-
ENV CI=true
|
|
20745
|
-
ENV HAI_DEPLOY_ENVIRONMENT=${environment}
|
|
20746
|
-
ENV HAI_CORE_ENV=${coreEnv}
|
|
20747
|
-
WORKDIR /workspace
|
|
20748
|
-
RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
|
|
20749
|
-
COPY . .
|
|
20750
|
-
RUN pnpm install --frozen-lockfile --ignore-scripts
|
|
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
|
|
20753
|
-
RUN pnpm pm deploy --filter ${packageName} --prod /prod/app
|
|
20754
|
-
|
|
20755
|
-
FROM node:24-bookworm-slim AS runtime
|
|
20756
|
-
ENV NODE_ENV=production
|
|
20757
|
-
ENV HAI_DEPLOY_ENVIRONMENT=${environment}
|
|
20758
|
-
ENV HAI_CORE_ENV=${coreEnv}
|
|
20759
|
-
ENV HOST=0.0.0.0
|
|
20760
|
-
ENV PORT=${containerPort}
|
|
20761
|
-
WORKDIR /app
|
|
20762
|
-
COPY --from=build --chown=node:node /prod/app ./
|
|
20763
|
-
RUN mkdir -p data && chown node:node data
|
|
20764
|
-
USER node
|
|
20765
|
-
EXPOSE ${containerPort}
|
|
20766
|
-
CMD ["node", ${JSON.stringify(options.entrypoint ?? "build")}]
|
|
20767
|
-
`;
|
|
20768
|
-
}
|
|
20769
|
-
var logger4 = core.logger.child({ module: "deploy", scope: "command" });
|
|
20770
|
-
function maskSecrets(text, secrets) {
|
|
20771
|
-
if (!secrets || secrets.length === 0)
|
|
20772
|
-
return text;
|
|
20773
|
-
let masked = text;
|
|
20774
|
-
for (const secret of secrets) {
|
|
20775
|
-
if (secret && secret.length > 0)
|
|
20776
|
-
masked = masked.split(secret).join("***");
|
|
20777
|
-
}
|
|
20778
|
-
return masked;
|
|
20779
|
-
}
|
|
20780
|
-
function runCommand(command, args, options = {}) {
|
|
20781
|
-
return new Promise((resolve3) => {
|
|
20782
|
-
const child = spawn(command, args, {
|
|
20783
|
-
cwd: options.cwd,
|
|
20784
|
-
env: options.env ? { ...process2.env, ...options.env } : process2.env,
|
|
20785
|
-
shell: false
|
|
20786
|
-
});
|
|
20787
|
-
let stdout = "";
|
|
20788
|
-
let stderr = "";
|
|
20789
|
-
let settled = false;
|
|
20790
|
-
let timer;
|
|
20791
|
-
const finish = (result) => {
|
|
20792
|
-
if (settled)
|
|
20793
|
-
return;
|
|
20794
|
-
settled = true;
|
|
20795
|
-
if (timer)
|
|
20796
|
-
clearTimeout(timer);
|
|
20797
|
-
resolve3(result);
|
|
20798
|
-
};
|
|
20799
|
-
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
20800
|
-
timer = setTimeout(() => {
|
|
20801
|
-
child.kill("SIGKILL");
|
|
20802
|
-
logger4.error("Command timed out", { command, timeoutMs: options.timeoutMs });
|
|
20803
|
-
finish(err(
|
|
20804
|
-
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
20805
|
-
deployM("deploy_commandTimeout", { params: { command } })
|
|
20806
|
-
));
|
|
20807
|
-
}, options.timeoutMs);
|
|
20808
|
-
}
|
|
20809
|
-
child.stdout.on("data", (chunk) => {
|
|
20810
|
-
stdout += chunk.toString("utf-8");
|
|
20811
|
-
});
|
|
20812
|
-
child.stderr.on("data", (chunk) => {
|
|
20813
|
-
stderr += chunk.toString("utf-8");
|
|
20814
|
-
});
|
|
20815
|
-
child.on("error", (error62) => {
|
|
20816
|
-
logger4.error("Command failed to start", { command, error: maskSecrets(String(error62), options.secrets) });
|
|
20817
|
-
finish(err(
|
|
20818
|
-
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
20819
|
-
deployM("deploy_commandFailed", {
|
|
20820
|
-
params: { command, error: maskSecrets(error62.message, options.secrets) }
|
|
20821
|
-
}),
|
|
20822
|
-
error62
|
|
20823
|
-
));
|
|
20824
|
-
});
|
|
20825
|
-
child.on("close", (code) => {
|
|
20826
|
-
const exitCode = code ?? -1;
|
|
20827
|
-
if (exitCode === 0) {
|
|
20828
|
-
finish(ok({ code: exitCode, stdout, stderr }));
|
|
20829
|
-
return;
|
|
20830
|
-
}
|
|
20831
|
-
logger4.error("Command exited with non-zero code", {
|
|
20832
|
-
command,
|
|
20833
|
-
code: exitCode,
|
|
20834
|
-
stderr: maskSecrets(stderr, options.secrets).slice(-500),
|
|
20835
|
-
stdout: maskSecrets(stdout, options.secrets).slice(-3e3)
|
|
20836
|
-
});
|
|
20837
|
-
finish(err(
|
|
20838
|
-
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
20839
|
-
deployM("deploy_commandFailed", {
|
|
20840
|
-
params: { command, error: maskSecrets(`${stdout.slice(-2500)}
|
|
20841
|
-
${stderr || `exit ${exitCode}`}`, options.secrets).slice(-3e3) }
|
|
20842
|
-
})
|
|
20843
|
-
));
|
|
20844
|
-
});
|
|
20845
|
-
if (options.input !== void 0) {
|
|
20846
|
-
child.stdin.write(options.input);
|
|
20847
|
-
child.stdin.end();
|
|
20848
|
-
} else {
|
|
20849
|
-
child.stdin.end();
|
|
20850
|
-
}
|
|
20851
|
-
});
|
|
20852
|
-
}
|
|
20853
|
-
|
|
20854
|
-
// ../deploy/src/container/deploy-container-runtime.ts
|
|
20855
|
-
var logger5 = core.logger.child({ module: "deploy", scope: "container-runtime" });
|
|
20856
|
-
async function detectLocalRuntime(preferred = "auto") {
|
|
20857
|
-
const candidates = preferred === "auto" ? ["podman", "docker"] : [preferred];
|
|
20858
|
-
for (const runtime of candidates) {
|
|
20859
|
-
const probe = await runCommand(runtime, ["version"], { timeoutMs: 15e3 });
|
|
20860
|
-
if (probe.success) {
|
|
20861
|
-
logger5.info("Local container runtime detected", { runtime });
|
|
20862
|
-
return ok(runtime);
|
|
20863
|
-
}
|
|
20864
|
-
}
|
|
20865
|
-
return err(
|
|
20866
|
-
HaiDeployError.CONTAINER_RUNTIME_NOT_FOUND,
|
|
20867
|
-
deployM("deploy_containerRuntimeNotFound", { params: { runtime: candidates.join(" / ") } })
|
|
20868
|
-
);
|
|
20869
|
-
}
|
|
20870
|
-
async function buildImage(runtime, options) {
|
|
20871
|
-
const args = ["build", "-f", options.dockerfilePath, "-t", options.imageRef];
|
|
20872
|
-
if (runtime === "podman")
|
|
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}`);
|
|
20880
|
-
args.push(options.contextDir);
|
|
20881
|
-
logger5.info("Building container image", { runtime, imageRef: options.imageRef });
|
|
20882
|
-
const result = await runCommand(runtime, args, { cwd: options.contextDir });
|
|
20883
|
-
if (!result.success) {
|
|
20884
|
-
return err(
|
|
20885
|
-
HaiDeployError.CONTAINER_BUILD_FAILED,
|
|
20886
|
-
deployM("deploy_containerBuildFailed", { params: { error: result.error.message } }),
|
|
20887
|
-
result.error
|
|
20888
|
-
);
|
|
20889
|
-
}
|
|
20890
|
-
return ok(void 0);
|
|
20891
|
-
}
|
|
20892
|
-
async function saveImage(runtime, imageRef, tarPath) {
|
|
20893
|
-
logger5.info("Saving container image", { runtime, imageRef, tarPath });
|
|
20894
|
-
const result = await runCommand(runtime, ["save", "-o", tarPath, imageRef], {});
|
|
20895
|
-
if (!result.success) {
|
|
20896
|
-
return err(
|
|
20897
|
-
HaiDeployError.CONTAINER_BUILD_FAILED,
|
|
20898
|
-
deployM("deploy_containerBuildFailed", { params: { error: result.error.message } }),
|
|
20899
|
-
result.error
|
|
20900
|
-
);
|
|
20901
|
-
}
|
|
20902
|
-
return ok(void 0);
|
|
20903
|
-
}
|
|
20904
|
-
var logger6 = core.logger.child({ module: "deploy", scope: "ssh" });
|
|
20951
|
+
var logger4 = core.logger.child({ module: "deploy", scope: "ssh" });
|
|
20905
20952
|
function buildConnectOptions(config2) {
|
|
20906
20953
|
const args = ["-o", "BatchMode=yes"];
|
|
20907
20954
|
args.push("-o", `ConnectTimeout=${config2.connectTimeoutSeconds}`);
|
|
@@ -20952,7 +20999,7 @@ function createSshExecutor(config2) {
|
|
|
20952
20999
|
return ok(void 0);
|
|
20953
21000
|
};
|
|
20954
21001
|
const ping = async () => {
|
|
20955
|
-
|
|
21002
|
+
logger4.debug("SSH preflight", { host: config2.host, port: config2.port });
|
|
20956
21003
|
const result = await exec("true", { timeoutMs: 2e4 });
|
|
20957
21004
|
if (!result.success) {
|
|
20958
21005
|
return err(
|
|
@@ -20967,52 +21014,24 @@ function createSshExecutor(config2) {
|
|
|
20967
21014
|
}
|
|
20968
21015
|
|
|
20969
21016
|
// ../deploy/src/providers/deploy-provider-docker-ssh.ts
|
|
20970
|
-
var
|
|
21017
|
+
var logger5 = core.logger.child({ module: "deploy", scope: "provider-docker-ssh" });
|
|
20971
21018
|
var HEALTH_POLL_INTERVAL = 3e3;
|
|
20972
21019
|
var HEALTH_MAX_ATTEMPTS = 40;
|
|
20973
|
-
function shellArgument(value) {
|
|
20974
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
20975
|
-
}
|
|
20976
21020
|
function makeDeploymentId() {
|
|
20977
21021
|
const now = /* @__PURE__ */ new Date();
|
|
20978
21022
|
const pad = (n) => String(n).padStart(2, "0");
|
|
20979
21023
|
const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
20980
21024
|
return `${stamp}-${randomBytes(3).toString("hex")}`;
|
|
20981
21025
|
}
|
|
20982
|
-
function parseLoadedImageRef(stdout, fallback) {
|
|
20983
|
-
const match = stdout.match(/Loaded image(?:\(s\))?:\s*(\S+)/);
|
|
20984
|
-
return match ? match[1] : fallback;
|
|
20985
|
-
}
|
|
20986
21026
|
function sleep(ms) {
|
|
20987
|
-
return new Promise((
|
|
20988
|
-
}
|
|
20989
|
-
function readPackageName(appDir) {
|
|
20990
|
-
try {
|
|
20991
|
-
const pkg = JSON.parse(readFileSync(join(appDir, "package.json"), "utf-8"));
|
|
20992
|
-
return pkg.name ?? "app";
|
|
20993
|
-
} catch {
|
|
20994
|
-
return "app";
|
|
20995
|
-
}
|
|
20996
|
-
}
|
|
20997
|
-
function resolveWorkspaceRoot(appDir) {
|
|
20998
|
-
if (existsSync(join(appDir, "pnpm-workspace.yaml")))
|
|
20999
|
-
return appDir;
|
|
21000
|
-
let current = appDir;
|
|
21001
|
-
let parent = dirname(current);
|
|
21002
|
-
while (parent !== current) {
|
|
21003
|
-
if (existsSync(join(parent, "pnpm-workspace.yaml")))
|
|
21004
|
-
return parent;
|
|
21005
|
-
current = parent;
|
|
21006
|
-
parent = dirname(parent);
|
|
21007
|
-
}
|
|
21008
|
-
return appDir;
|
|
21027
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
21009
21028
|
}
|
|
21010
21029
|
function createDockerSshProvider(config2, container) {
|
|
21011
|
-
const
|
|
21012
|
-
const httpProxy = container?.httpProxy ?? true;
|
|
21013
|
-
const ssh = createSshExecutor(config2.ssh);
|
|
21030
|
+
const ssh = config2.ssh ? createSshExecutor(config2.ssh) : void 0;
|
|
21014
21031
|
const baseDir = config2.remote.baseDir;
|
|
21015
21032
|
async function detectRemoteRuntime() {
|
|
21033
|
+
if (!ssh)
|
|
21034
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_sshRequired"));
|
|
21016
21035
|
const candidates = config2.remote.runtime === "auto" ? ["podman", "docker"] : [config2.remote.runtime];
|
|
21017
21036
|
for (const runtime of candidates) {
|
|
21018
21037
|
const versionResult = await ssh.exec(`${runtime} version`, { timeoutMs: 2e4 });
|
|
@@ -21020,7 +21039,7 @@ function createDockerSshProvider(config2, container) {
|
|
|
21020
21039
|
continue;
|
|
21021
21040
|
const composeResult = await ssh.exec(`${runtime} compose version`, { timeoutMs: 2e4 });
|
|
21022
21041
|
if (composeResult.success) {
|
|
21023
|
-
|
|
21042
|
+
logger5.info("Remote container runtime detected", { runtime });
|
|
21024
21043
|
return ok(runtime);
|
|
21025
21044
|
}
|
|
21026
21045
|
}
|
|
@@ -21044,29 +21063,31 @@ function createDockerSshProvider(config2, container) {
|
|
|
21044
21063
|
const deploy2 = async (context) => {
|
|
21045
21064
|
const { appDir, projectName, scan, provisionEnv, appEnv, options } = context;
|
|
21046
21065
|
const deploymentId = makeDeploymentId();
|
|
21047
|
-
const containerPort = config2.expose.containerPort;
|
|
21048
21066
|
const hostPort = config2.expose.hostPort;
|
|
21049
|
-
const localImageTag = `hai-${projectName}:${deploymentId}`;
|
|
21050
|
-
const runtimeResult = await detectLocalRuntime(localRuntimePref);
|
|
21051
|
-
if (!runtimeResult.success)
|
|
21052
|
-
return err(runtimeResult.error);
|
|
21053
|
-
const localRuntime = runtimeResult.data;
|
|
21054
21067
|
const artifactDir = join(appDir, ".hai", "deploy", options?.environment ?? "prod");
|
|
21055
|
-
mkdirSync(artifactDir, { recursive: true });
|
|
21056
|
-
|
|
21057
|
-
|
|
21058
|
-
|
|
21059
|
-
|
|
21060
|
-
|
|
21061
|
-
|
|
21062
|
-
|
|
21063
|
-
|
|
21064
|
-
writeFileSync(dockerfilePath, generateFallbackDockerfile(readPackageName(appDir), containerPort, { environment: options?.environment, entrypoint: scan.isSvelteKit ? "build" : "dist/index.js" }));
|
|
21065
|
-
writeFileSync(`${dockerfilePath}.dockerignore`, generateDockerignore());
|
|
21066
|
-
contextDir = resolveWorkspaceRoot(appDir);
|
|
21067
|
-
logger7.info("Generated fallback Dockerfile", { dockerfilePath, contextDir });
|
|
21068
|
-
}
|
|
21068
|
+
mkdirSync(artifactDir, { recursive: true, mode: 448 });
|
|
21069
|
+
chmodSync(artifactDir, 448);
|
|
21070
|
+
const artifact = context.artifact;
|
|
21071
|
+
const resolvedBuildDir = context.artifactDir;
|
|
21072
|
+
const images = artifact.images;
|
|
21073
|
+
const web = images.find((image) => image.name === "web");
|
|
21074
|
+
const app = images.find((image) => image.name === "app");
|
|
21075
|
+
const containerPort = app.containerPort ?? config2.expose.containerPort;
|
|
21076
|
+
const url2 = config2.ssh ? `http://${config2.ssh.host}:${hostPort}` : `http://SERVER_HOST:${hostPort}`;
|
|
21069
21077
|
const { env, sidecar } = buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv, container?.s3Engine);
|
|
21078
|
+
let webProxy;
|
|
21079
|
+
if (web) {
|
|
21080
|
+
const server = loadDeploymentConfig("serv", join(appDir, "config", "_serv.yml"), env);
|
|
21081
|
+
if (!server.success)
|
|
21082
|
+
return server;
|
|
21083
|
+
const http = server.data.http;
|
|
21084
|
+
const cors = server.data.cors;
|
|
21085
|
+
const apiPrefix = http?.apiPrefix ?? "/api/v1";
|
|
21086
|
+
if (typeof apiPrefix !== "string" || !/^\/[\w/-]+$/.test(apiPrefix))
|
|
21087
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webProxyInvalid"));
|
|
21088
|
+
webProxy = generateWebProxyConfig(containerPort, apiPrefix, web.containerPort ?? 8080);
|
|
21089
|
+
env.HAI_SERV = JSON.stringify({ ...server.data, cors: { ...cors, origin: (!cors?.origin || cors.origin === "*") && config2.ssh ? url2 : cors?.origin ?? "*" } });
|
|
21090
|
+
}
|
|
21070
21091
|
const envPath = join(artifactDir, ".env.runtime");
|
|
21071
21092
|
const previousEnv = existsSync(envPath) ? parseEnv(readFileSync(envPath, "utf8")) : {};
|
|
21072
21093
|
Object.assign(env, { ...previousEnv, ...env });
|
|
@@ -21079,88 +21100,64 @@ function createDockerSshProvider(config2, container) {
|
|
|
21079
21100
|
if (!saved.success)
|
|
21080
21101
|
return saved;
|
|
21081
21102
|
}
|
|
21082
|
-
const
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21086
|
-
buildArgs: { HAI_DEPLOY_ENVIRONMENT: options?.environment ?? "prod", HAI_CORE_ENV: env.HAI_CORE_ENV ?? "production", SQLITE_PREBUILD_MIRROR: container?.sqlitePrebuildMirror ?? "" },
|
|
21087
|
-
httpProxy
|
|
21088
|
-
});
|
|
21089
|
-
if (!buildResult.success)
|
|
21090
|
-
return err(buildResult.error);
|
|
21091
|
-
const tarPath = join(artifactDir, `${projectName}.tar`);
|
|
21092
|
-
const saveResult = await saveImage(localRuntime, localImageTag, tarPath);
|
|
21093
|
-
if (!saveResult.success)
|
|
21094
|
-
return err(saveResult.error);
|
|
21095
|
-
const pingResult = await ssh.ping();
|
|
21096
|
-
if (!pingResult.success)
|
|
21097
|
-
return err(pingResult.error);
|
|
21098
|
-
const remoteRuntimeResult = await detectRemoteRuntime();
|
|
21099
|
-
if (!remoteRuntimeResult.success)
|
|
21100
|
-
return err(remoteRuntimeResult.error);
|
|
21101
|
-
const remoteRuntime = remoteRuntimeResult.data;
|
|
21102
|
-
const remoteDir = `${baseDir}/${projectName}`;
|
|
21103
|
-
const prepareResult = await ssh.exec(`mkdir -p -- ${shellArgument(remoteDir)} && chmod 700 -- ${shellArgument(remoteDir)}`);
|
|
21104
|
-
if (!prepareResult.success)
|
|
21105
|
-
return err(prepareResult.error);
|
|
21106
|
-
const remoteTar = `${remoteDir}/image.tar`;
|
|
21107
|
-
const uploadTarResult = await ssh.upload(tarPath, remoteTar);
|
|
21108
|
-
if (!uploadTarResult.success)
|
|
21109
|
-
return err(uploadTarResult.error);
|
|
21110
|
-
const loadResult = await ssh.exec(`${remoteRuntime} load -i ${shellArgument(remoteTar)}`, { timeoutMs: 3e5 });
|
|
21111
|
-
if (!loadResult.success)
|
|
21112
|
-
return err(loadResult.error);
|
|
21113
|
-
const loadedRef = parseLoadedImageRef(loadResult.data.stdout, localImageTag);
|
|
21114
|
-
const remoteImageRef = remoteRuntime === "podman" ? `localhost/${localImageTag}` : localImageTag;
|
|
21115
|
-
if (loadedRef !== remoteImageRef) {
|
|
21116
|
-
const tagResult = await ssh.exec(`${remoteRuntime} tag ${shellArgument(loadedRef)} ${shellArgument(remoteImageRef)}`);
|
|
21117
|
-
if (!tagResult.success)
|
|
21118
|
-
return err(tagResult.error);
|
|
21119
|
-
}
|
|
21103
|
+
const composeName = `docker-compose-${artifact.appVersion}.yml`;
|
|
21104
|
+
const latestPath = join(artifactDir, "latest");
|
|
21105
|
+
const previousName = existsSync(latestPath) ? readFileSync(latestPath, "utf8").trim() : "";
|
|
21106
|
+
const previousPath = /^[\w.+-]+\.yml$/.test(previousName) ? join(artifactDir, previousName) : void 0;
|
|
21120
21107
|
const composeContent = generateComposeFile({
|
|
21121
21108
|
projectName,
|
|
21122
|
-
|
|
21109
|
+
appVersion: artifact.appVersion,
|
|
21110
|
+
imageRef: app.imageRef,
|
|
21123
21111
|
hostPort,
|
|
21124
21112
|
containerPort,
|
|
21125
21113
|
sidecar,
|
|
21114
|
+
web: web ? { imageRef: web.imageRef, containerPort: web.containerPort ?? 8080 } : void 0,
|
|
21126
21115
|
serviceModes: options?.serviceModes,
|
|
21127
|
-
previousCompose: existsSync(
|
|
21116
|
+
previousCompose: previousPath && existsSync(previousPath) ? readFileSync(previousPath, "utf8") : void 0
|
|
21128
21117
|
});
|
|
21129
|
-
const composePath = join(artifactDir,
|
|
21118
|
+
const composePath = join(artifactDir, composeName);
|
|
21130
21119
|
writeFileSync(composePath, composeContent);
|
|
21131
|
-
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21143
|
-
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
|
|
21147
|
-
|
|
21148
|
-
|
|
21149
|
-
|
|
21150
|
-
|
|
21151
|
-
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
|
|
21156
|
-
|
|
21120
|
+
if (webProxy)
|
|
21121
|
+
writeFileSync(join(artifactDir, "web.conf"), webProxy);
|
|
21122
|
+
const bundle = await createDeploymentBundle({
|
|
21123
|
+
directory: artifactDir,
|
|
21124
|
+
artifactDir: resolvedBuildDir,
|
|
21125
|
+
artifact,
|
|
21126
|
+
projectName,
|
|
21127
|
+
environment: options?.environment ?? "prod",
|
|
21128
|
+
runtime: config2.remote.runtime,
|
|
21129
|
+
coreEnv: env.HAI_CORE_ENV ?? "production",
|
|
21130
|
+
output: options?.output ?? join(artifactDir, `bundle-${projectName}-${deploymentId}-${artifact.appVersion}.tar`)
|
|
21131
|
+
});
|
|
21132
|
+
writeFileSync(latestPath, `${composeName}
|
|
21133
|
+
`);
|
|
21134
|
+
if (options?.bundleOnly)
|
|
21135
|
+
return ok({ url: "", deploymentId, status: "packaged", provider: "docker-ssh", healthCheck: "skipped", envVarsSet: Object.keys(env), ...bundle });
|
|
21136
|
+
if (!ssh)
|
|
21137
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_sshRequired"));
|
|
21138
|
+
const ping = await ssh.ping();
|
|
21139
|
+
if (!ping.success)
|
|
21140
|
+
return ping;
|
|
21141
|
+
const remoteRuntime = await detectRemoteRuntime();
|
|
21142
|
+
if (!remoteRuntime.success)
|
|
21143
|
+
return remoteRuntime;
|
|
21144
|
+
const remoteDir = `${baseDir}/${projectName}`;
|
|
21145
|
+
const remoteTar = `${remoteDir}/${basename(bundle.bundlePath)}`;
|
|
21146
|
+
const prepared = await ssh.exec(`mkdir -p -- ${shellArgument(remoteDir)} && chmod 700 -- ${shellArgument(remoteDir)}`);
|
|
21147
|
+
if (!prepared.success)
|
|
21148
|
+
return prepared;
|
|
21149
|
+
const uploaded = await ssh.upload(bundle.bundlePath, remoteTar);
|
|
21150
|
+
if (!uploaded.success)
|
|
21151
|
+
return uploaded;
|
|
21152
|
+
const started = await ssh.exec(
|
|
21153
|
+
`cd ${shellArgument(remoteDir)} && chmod 600 ${shellArgument(remoteTar)} && tar -xf ${shellArgument(remoteTar)} && HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME=${shellArgument(remoteRuntime.data)} sh ${shellArgument(bundle.startName)}`,
|
|
21154
|
+
{ timeoutMs: 3e5, secrets: sidecar.secrets }
|
|
21157
21155
|
);
|
|
21158
|
-
if (!
|
|
21159
|
-
return
|
|
21160
|
-
await ssh.exec(`rm -f -- ${shellArgument(remoteTar)}`);
|
|
21161
|
-
|
|
21162
|
-
|
|
21163
|
-
const healthy = await healthCheck(`${url2}${scan.isSvelteKit ? "/" : "/health"}`);
|
|
21156
|
+
if (!started.success)
|
|
21157
|
+
return started;
|
|
21158
|
+
await ssh.exec(`rm -f -- ${shellArgument(remoteTar)} ${images.map((image) => shellArgument(`${remoteDir}/images/${image.file}`)).join(" ")}`);
|
|
21159
|
+
logger5.info("Running health check", { url: url2 });
|
|
21160
|
+
const healthy = await healthCheck(`${url2}${!web && scan.isSvelteKit ? "/" : "/health"}`) && (!web || await healthCheck(`${url2}/_hai/web-health`) && await healthCheck(`${url2}/`));
|
|
21164
21161
|
if (!healthy) {
|
|
21165
21162
|
return err(
|
|
21166
21163
|
HaiDeployError.HEALTH_CHECK_FAILED,
|
|
@@ -21173,14 +21170,23 @@ exec ${remoteRuntime} compose --env-file .env.runtime -p hai-${projectName} up -
|
|
|
21173
21170
|
status: "ready",
|
|
21174
21171
|
provider: "docker-ssh",
|
|
21175
21172
|
healthCheck: "passed",
|
|
21176
|
-
envVarsSet: Object.keys(env)
|
|
21173
|
+
envVarsSet: Object.keys(env),
|
|
21174
|
+
bundlePath: bundle.bundlePath
|
|
21177
21175
|
};
|
|
21178
|
-
|
|
21176
|
+
logger5.info("docker-ssh deployment complete", { url: url2, deploymentId });
|
|
21179
21177
|
return ok(result);
|
|
21180
21178
|
};
|
|
21181
|
-
|
|
21179
|
+
const deploySafely = async (context) => {
|
|
21180
|
+
try {
|
|
21181
|
+
return await deploy2(context);
|
|
21182
|
+
} catch (error62) {
|
|
21183
|
+
logger5.error("Deployment bundle failed", { error: error62 });
|
|
21184
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"), error62);
|
|
21185
|
+
}
|
|
21186
|
+
};
|
|
21187
|
+
return { name: "docker-ssh", deploy: deploySafely };
|
|
21182
21188
|
}
|
|
21183
|
-
var
|
|
21189
|
+
var logger6 = core.logger.child({ module: "deploy", scope: "provider-vercel" });
|
|
21184
21190
|
var VERCEL_API = "https://api.vercel.com";
|
|
21185
21191
|
var POLL_INTERVAL = 3e3;
|
|
21186
21192
|
var MAX_POLL_ATTEMPTS = 60;
|
|
@@ -21233,7 +21239,7 @@ function createVercelProvider() {
|
|
|
21233
21239
|
return {
|
|
21234
21240
|
name: "vercel",
|
|
21235
21241
|
async authenticate(apiToken) {
|
|
21236
|
-
|
|
21242
|
+
logger6.debug("Authenticating with Vercel");
|
|
21237
21243
|
try {
|
|
21238
21244
|
const user = await vercelFetch(
|
|
21239
21245
|
apiToken,
|
|
@@ -21241,10 +21247,10 @@ function createVercelProvider() {
|
|
|
21241
21247
|
);
|
|
21242
21248
|
token = apiToken;
|
|
21243
21249
|
const username = user.user.username || user.user.email;
|
|
21244
|
-
|
|
21250
|
+
logger6.info("Vercel authenticated", { user: username });
|
|
21245
21251
|
return ok(username);
|
|
21246
21252
|
} catch (error62) {
|
|
21247
|
-
|
|
21253
|
+
logger6.error("Vercel authentication failed", { error: error62 });
|
|
21248
21254
|
return err(toDeployError(HaiDeployError.AUTH_FAILED, "deploy_authFailed", error62));
|
|
21249
21255
|
}
|
|
21250
21256
|
},
|
|
@@ -21255,11 +21261,11 @@ function createVercelProvider() {
|
|
|
21255
21261
|
deployM("deploy_authRequired")
|
|
21256
21262
|
);
|
|
21257
21263
|
}
|
|
21258
|
-
|
|
21264
|
+
logger6.debug("Creating Vercel project", { projectName });
|
|
21259
21265
|
try {
|
|
21260
21266
|
const existingId = await findExistingProject(token, projectName);
|
|
21261
21267
|
if (existingId !== null) {
|
|
21262
|
-
|
|
21268
|
+
logger6.info("Vercel project found", { projectName, projectId: existingId });
|
|
21263
21269
|
return ok(existingId);
|
|
21264
21270
|
}
|
|
21265
21271
|
try {
|
|
@@ -21274,13 +21280,13 @@ function createVercelProvider() {
|
|
|
21274
21280
|
})
|
|
21275
21281
|
}
|
|
21276
21282
|
);
|
|
21277
|
-
|
|
21283
|
+
logger6.info("Vercel project created", { projectName, projectId: project.id });
|
|
21278
21284
|
return ok(project.id);
|
|
21279
21285
|
} catch (error62) {
|
|
21280
21286
|
if (error62 instanceof VercelApiError && error62.status === 409) {
|
|
21281
21287
|
const concurrentProjectId = await findExistingProject(token, projectName);
|
|
21282
21288
|
if (concurrentProjectId !== null) {
|
|
21283
|
-
|
|
21289
|
+
logger6.info("Vercel project reused after create conflict", {
|
|
21284
21290
|
projectName,
|
|
21285
21291
|
projectId: concurrentProjectId
|
|
21286
21292
|
});
|
|
@@ -21290,7 +21296,7 @@ function createVercelProvider() {
|
|
|
21290
21296
|
throw error62;
|
|
21291
21297
|
}
|
|
21292
21298
|
} catch (error62) {
|
|
21293
|
-
|
|
21299
|
+
logger6.error("Vercel project creation failed", { projectName, error: error62 });
|
|
21294
21300
|
return err(toDeployError(HaiDeployError.PROJECT_CREATE_FAILED, "deploy_projectCreateFailed", error62));
|
|
21295
21301
|
}
|
|
21296
21302
|
},
|
|
@@ -21301,7 +21307,7 @@ function createVercelProvider() {
|
|
|
21301
21307
|
deployM("deploy_authRequired")
|
|
21302
21308
|
);
|
|
21303
21309
|
}
|
|
21304
|
-
|
|
21310
|
+
logger6.debug("Setting Vercel env vars", { projectId, count: Object.keys(envVars).length });
|
|
21305
21311
|
try {
|
|
21306
21312
|
const envEntries = Object.entries(envVars).map(([key, value]) => ({
|
|
21307
21313
|
key,
|
|
@@ -21317,10 +21323,10 @@ function createVercelProvider() {
|
|
|
21317
21323
|
body: JSON.stringify(envEntries)
|
|
21318
21324
|
}
|
|
21319
21325
|
);
|
|
21320
|
-
|
|
21326
|
+
logger6.info("Vercel env vars set", { projectId, count: envEntries.length });
|
|
21321
21327
|
return ok(void 0);
|
|
21322
21328
|
} catch (error62) {
|
|
21323
|
-
|
|
21329
|
+
logger6.error("Vercel env vars setup failed", { projectId, error: error62 });
|
|
21324
21330
|
return err(toDeployError(HaiDeployError.ENV_VAR_FAILED, "deploy_envVarFailed", error62));
|
|
21325
21331
|
}
|
|
21326
21332
|
},
|
|
@@ -21331,8 +21337,11 @@ function createVercelProvider() {
|
|
|
21331
21337
|
deployM("deploy_authRequired")
|
|
21332
21338
|
);
|
|
21333
21339
|
}
|
|
21334
|
-
|
|
21340
|
+
logger6.debug("Deploying to Vercel", { projectId, outputDir });
|
|
21335
21341
|
try {
|
|
21342
|
+
const outputConfig = JSON.parse(readFileSync(join(outputDir, "config.json"), "utf8"));
|
|
21343
|
+
if (outputConfig.version !== 3)
|
|
21344
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
|
|
21336
21345
|
const filePaths = collectFiles(outputDir, outputDir);
|
|
21337
21346
|
const files = [];
|
|
21338
21347
|
for (const filePath of filePaths) {
|
|
@@ -21341,18 +21350,20 @@ function createVercelProvider() {
|
|
|
21341
21350
|
const sha = createHash("sha1").update(content).digest("hex");
|
|
21342
21351
|
const size = statSync(fullPath).size;
|
|
21343
21352
|
await uploadFile(token, content, sha);
|
|
21344
|
-
files.push({ file: filePath
|
|
21353
|
+
files.push({ file: `.vercel/output/${filePath}`, sha, size });
|
|
21345
21354
|
}
|
|
21346
|
-
|
|
21355
|
+
logger6.debug("Files uploaded", { count: files.length });
|
|
21347
21356
|
const deployment = await vercelFetch(
|
|
21348
21357
|
token,
|
|
21349
|
-
"/v13/deployments",
|
|
21358
|
+
"/v13/deployments?prebuilt=1",
|
|
21350
21359
|
{
|
|
21351
21360
|
method: "POST",
|
|
21352
21361
|
body: JSON.stringify({
|
|
21353
21362
|
name: projectId,
|
|
21354
21363
|
files,
|
|
21355
|
-
|
|
21364
|
+
version: 2,
|
|
21365
|
+
project: projectId,
|
|
21366
|
+
projectSettings: { framework: null, buildCommand: "", installCommand: "" }
|
|
21356
21367
|
})
|
|
21357
21368
|
}
|
|
21358
21369
|
);
|
|
@@ -21363,14 +21374,14 @@ function createVercelProvider() {
|
|
|
21363
21374
|
status: finalStatus,
|
|
21364
21375
|
envVarsSet: []
|
|
21365
21376
|
};
|
|
21366
|
-
|
|
21377
|
+
logger6.info("Vercel deployment complete", {
|
|
21367
21378
|
deploymentId: deployment.id,
|
|
21368
21379
|
url: result.url,
|
|
21369
21380
|
status: finalStatus
|
|
21370
21381
|
});
|
|
21371
21382
|
return ok(result);
|
|
21372
21383
|
} catch (error62) {
|
|
21373
|
-
|
|
21384
|
+
logger6.error("Vercel deployment failed", { projectId, error: error62 });
|
|
21374
21385
|
return err(toDeployError(HaiDeployError.UPLOAD_FAILED, "deploy_uploadFailed", error62));
|
|
21375
21386
|
}
|
|
21376
21387
|
}
|
|
@@ -21415,11 +21426,11 @@ async function pollDeploymentStatus(token, deploymentId) {
|
|
|
21415
21426
|
if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
|
|
21416
21427
|
return "error";
|
|
21417
21428
|
}
|
|
21418
|
-
await new Promise((
|
|
21429
|
+
await new Promise((resolve6) => setTimeout(resolve6, POLL_INTERVAL));
|
|
21419
21430
|
}
|
|
21420
21431
|
return "building";
|
|
21421
21432
|
}
|
|
21422
|
-
var
|
|
21433
|
+
var logger7 = core.logger.child({ module: "deploy", scope: "provisioner-aliyun" });
|
|
21423
21434
|
function createAliyunProvisioner() {
|
|
21424
21435
|
let accessKey = null;
|
|
21425
21436
|
let secretKey = null;
|
|
@@ -21428,7 +21439,7 @@ function createAliyunProvisioner() {
|
|
|
21428
21439
|
name: "aliyun",
|
|
21429
21440
|
serviceType: "sms",
|
|
21430
21441
|
async authenticate(credentials) {
|
|
21431
|
-
|
|
21442
|
+
logger7.debug("Authenticating with Aliyun");
|
|
21432
21443
|
try {
|
|
21433
21444
|
const akId = credentials.accessKeyId ?? credentials.access_key_id ?? credentials.access_key ?? "";
|
|
21434
21445
|
const akSecret = credentials.accessKeySecret ?? credentials.access_key_secret ?? credentials.secret_key ?? "";
|
|
@@ -21438,10 +21449,10 @@ function createAliyunProvisioner() {
|
|
|
21438
21449
|
accessKey = akId;
|
|
21439
21450
|
secretKey = akSecret;
|
|
21440
21451
|
signName = credentials.signName;
|
|
21441
|
-
|
|
21452
|
+
logger7.info("Aliyun authenticated", { accessKeyId: `${akId.slice(0, 6)}***` });
|
|
21442
21453
|
return ok(akId);
|
|
21443
21454
|
} catch (error62) {
|
|
21444
|
-
|
|
21455
|
+
logger7.error("Aliyun authentication failed", { error: error62 });
|
|
21445
21456
|
return err(
|
|
21446
21457
|
HaiDeployError.AUTH_FAILED,
|
|
21447
21458
|
deployM("deploy_authFailed", {
|
|
@@ -21458,7 +21469,7 @@ function createAliyunProvisioner() {
|
|
|
21458
21469
|
deployM("deploy_authRequired")
|
|
21459
21470
|
);
|
|
21460
21471
|
}
|
|
21461
|
-
|
|
21472
|
+
logger7.debug("Provisioning Aliyun SMS service");
|
|
21462
21473
|
return ok({
|
|
21463
21474
|
serviceType: "sms",
|
|
21464
21475
|
provisionerName: "aliyun",
|
|
@@ -21471,7 +21482,7 @@ function createAliyunProvisioner() {
|
|
|
21471
21482
|
}
|
|
21472
21483
|
};
|
|
21473
21484
|
}
|
|
21474
|
-
var
|
|
21485
|
+
var logger8 = core.logger.child({ module: "deploy", scope: "provisioner-neon" });
|
|
21475
21486
|
var NEON_API = "https://console.neon.tech/api/v2";
|
|
21476
21487
|
var DEFAULT_NEON_DATABASE_NAME = "neondb";
|
|
21477
21488
|
var DEFAULT_NEON_ROLE_NAME = "neondb_owner";
|
|
@@ -21530,7 +21541,7 @@ function createNeonProvisioner() {
|
|
|
21530
21541
|
name: "neon",
|
|
21531
21542
|
serviceType: "db",
|
|
21532
21543
|
async authenticate(credentials) {
|
|
21533
|
-
|
|
21544
|
+
logger8.debug("Authenticating with Neon");
|
|
21534
21545
|
try {
|
|
21535
21546
|
const apiToken = credentials.apiKey ?? credentials.token ?? credentials.api_key ?? "";
|
|
21536
21547
|
if (!apiToken) {
|
|
@@ -21538,10 +21549,10 @@ function createNeonProvisioner() {
|
|
|
21538
21549
|
}
|
|
21539
21550
|
await listNeonProjects(apiToken);
|
|
21540
21551
|
token = apiToken;
|
|
21541
|
-
|
|
21552
|
+
logger8.info("Neon authenticated");
|
|
21542
21553
|
return ok("neon-user");
|
|
21543
21554
|
} catch (error62) {
|
|
21544
|
-
|
|
21555
|
+
logger8.error("Neon authentication failed", { error: error62 });
|
|
21545
21556
|
return err(
|
|
21546
21557
|
HaiDeployError.AUTH_FAILED,
|
|
21547
21558
|
deployM("deploy_authFailed", {
|
|
@@ -21558,7 +21569,7 @@ function createNeonProvisioner() {
|
|
|
21558
21569
|
deployM("deploy_authRequired")
|
|
21559
21570
|
);
|
|
21560
21571
|
}
|
|
21561
|
-
|
|
21572
|
+
logger8.debug("Provisioning Neon database", { appName });
|
|
21562
21573
|
let resource;
|
|
21563
21574
|
try {
|
|
21564
21575
|
const projectName = `${appName}-db`;
|
|
@@ -21566,7 +21577,7 @@ function createNeonProvisioner() {
|
|
|
21566
21577
|
if (existingProject !== null) {
|
|
21567
21578
|
resource = { resourceInfo: `neon-project:${existingProject.id}`, resourceStatus: "reused" };
|
|
21568
21579
|
const connectionUri2 = await getNeonConnectionUri(token, existingProject.id);
|
|
21569
|
-
|
|
21580
|
+
logger8.info("Neon project reused", {
|
|
21570
21581
|
projectId: existingProject.id,
|
|
21571
21582
|
projectName
|
|
21572
21583
|
});
|
|
@@ -21588,10 +21599,10 @@ function createNeonProvisioner() {
|
|
|
21588
21599
|
if (!projectId || !connectionUri) {
|
|
21589
21600
|
throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
|
|
21590
21601
|
}
|
|
21591
|
-
|
|
21602
|
+
logger8.info("Neon database provisioned", { projectId });
|
|
21592
21603
|
return ok(buildNeonProvisionResult(projectId, connectionUri, "created"));
|
|
21593
21604
|
} catch (error62) {
|
|
21594
|
-
|
|
21605
|
+
logger8.error("Neon provisioning failed", { appName });
|
|
21595
21606
|
if (resource) {
|
|
21596
21607
|
return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
|
|
21597
21608
|
projectName: appName,
|
|
@@ -21613,7 +21624,7 @@ function createNeonProvisioner() {
|
|
|
21613
21624
|
}
|
|
21614
21625
|
};
|
|
21615
21626
|
}
|
|
21616
|
-
var
|
|
21627
|
+
var logger9 = core.logger.child({ module: "deploy", scope: "provisioner-r2" });
|
|
21617
21628
|
var CF_API = "https://api.cloudflare.com/client/v4";
|
|
21618
21629
|
function createR2Provisioner() {
|
|
21619
21630
|
let token = null;
|
|
@@ -21624,7 +21635,7 @@ function createR2Provisioner() {
|
|
|
21624
21635
|
name: "r2",
|
|
21625
21636
|
serviceType: "storage",
|
|
21626
21637
|
async authenticate(credentials) {
|
|
21627
|
-
|
|
21638
|
+
logger9.debug("Authenticating with Cloudflare");
|
|
21628
21639
|
try {
|
|
21629
21640
|
const acctId = credentials.accountId ?? credentials.account_id ?? "";
|
|
21630
21641
|
const apiTok = credentials.apiToken ?? credentials.api_token ?? credentials.token ?? "";
|
|
@@ -21641,10 +21652,10 @@ function createR2Provisioner() {
|
|
|
21641
21652
|
token = apiTok;
|
|
21642
21653
|
accessKey = credentials.accessKeyId;
|
|
21643
21654
|
secretKey = credentials.secretAccessKey;
|
|
21644
|
-
|
|
21655
|
+
logger9.info("Cloudflare R2 authenticated", { accountId: acctId });
|
|
21645
21656
|
return ok(acctId);
|
|
21646
21657
|
} catch (error62) {
|
|
21647
|
-
|
|
21658
|
+
logger9.error("Cloudflare R2 authentication failed", { error: error62 });
|
|
21648
21659
|
return err(
|
|
21649
21660
|
HaiDeployError.AUTH_FAILED,
|
|
21650
21661
|
deployM("deploy_authFailed", {
|
|
@@ -21662,7 +21673,7 @@ function createR2Provisioner() {
|
|
|
21662
21673
|
);
|
|
21663
21674
|
}
|
|
21664
21675
|
const bucketName = `${appName}-storage`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
21665
|
-
|
|
21676
|
+
logger9.debug("Provisioning R2 bucket", { appName, bucketName });
|
|
21666
21677
|
try {
|
|
21667
21678
|
const createRes = await fetch(
|
|
21668
21679
|
`${CF_API}/accounts/${accountId}/r2/buckets`,
|
|
@@ -21678,7 +21689,7 @@ function createR2Provisioner() {
|
|
|
21678
21689
|
if (!createRes.ok && createRes.status !== 409) {
|
|
21679
21690
|
throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(createRes.status) } }));
|
|
21680
21691
|
}
|
|
21681
|
-
|
|
21692
|
+
logger9.info("R2 bucket provisioned", { bucketName });
|
|
21682
21693
|
return ok({
|
|
21683
21694
|
serviceType: "storage",
|
|
21684
21695
|
provisionerName: "r2",
|
|
@@ -21696,7 +21707,7 @@ function createR2Provisioner() {
|
|
|
21696
21707
|
resourceInfo: `r2-bucket:${bucketName}`
|
|
21697
21708
|
});
|
|
21698
21709
|
} catch (error62) {
|
|
21699
|
-
|
|
21710
|
+
logger9.error("R2 provisioning failed", { appName, error: error62 });
|
|
21700
21711
|
return err(
|
|
21701
21712
|
HaiDeployError.PROVISION_FAILED,
|
|
21702
21713
|
deployM("deploy_provisionFailed", {
|
|
@@ -21711,7 +21722,7 @@ function createR2Provisioner() {
|
|
|
21711
21722
|
}
|
|
21712
21723
|
};
|
|
21713
21724
|
}
|
|
21714
|
-
var
|
|
21725
|
+
var logger10 = core.logger.child({ module: "deploy", scope: "provisioner-resend" });
|
|
21715
21726
|
var RESEND_API = "https://api.resend.com";
|
|
21716
21727
|
function createResendProvisioner() {
|
|
21717
21728
|
let token = null;
|
|
@@ -21720,7 +21731,7 @@ function createResendProvisioner() {
|
|
|
21720
21731
|
name: "resend",
|
|
21721
21732
|
serviceType: "email",
|
|
21722
21733
|
async authenticate(credentials) {
|
|
21723
|
-
|
|
21734
|
+
logger10.debug("Authenticating with Resend");
|
|
21724
21735
|
try {
|
|
21725
21736
|
const apiToken = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
|
|
21726
21737
|
if (!apiToken || !credentials.from) {
|
|
@@ -21734,10 +21745,10 @@ function createResendProvisioner() {
|
|
|
21734
21745
|
}
|
|
21735
21746
|
token = apiToken;
|
|
21736
21747
|
from = credentials.from;
|
|
21737
|
-
|
|
21748
|
+
logger10.info("Resend authenticated");
|
|
21738
21749
|
return ok("resend-user");
|
|
21739
21750
|
} catch (error62) {
|
|
21740
|
-
|
|
21751
|
+
logger10.error("Resend authentication failed", { error: error62 });
|
|
21741
21752
|
return err(
|
|
21742
21753
|
HaiDeployError.AUTH_FAILED,
|
|
21743
21754
|
deployM("deploy_authFailed", {
|
|
@@ -21754,7 +21765,7 @@ function createResendProvisioner() {
|
|
|
21754
21765
|
deployM("deploy_authRequired")
|
|
21755
21766
|
);
|
|
21756
21767
|
}
|
|
21757
|
-
|
|
21768
|
+
logger10.debug("Provisioning Resend email service");
|
|
21758
21769
|
return ok({
|
|
21759
21770
|
serviceType: "email",
|
|
21760
21771
|
provisionerName: "resend",
|
|
@@ -21767,7 +21778,7 @@ function createResendProvisioner() {
|
|
|
21767
21778
|
}
|
|
21768
21779
|
};
|
|
21769
21780
|
}
|
|
21770
|
-
var
|
|
21781
|
+
var logger11 = core.logger.child({ module: "deploy", scope: "provisioner-upstash" });
|
|
21771
21782
|
var UPSTASH_API = "https://api.upstash.com";
|
|
21772
21783
|
function createUpstashAuthHeader(email3, apiKey) {
|
|
21773
21784
|
return `Basic ${Buffer.from(`${email3}:${apiKey}`).toString("base64")}`;
|
|
@@ -21818,7 +21829,7 @@ function createUpstashProvisioner() {
|
|
|
21818
21829
|
name: "upstash",
|
|
21819
21830
|
serviceType: "cache",
|
|
21820
21831
|
async authenticate(credentials) {
|
|
21821
|
-
|
|
21832
|
+
logger11.debug("Authenticating with Upstash");
|
|
21822
21833
|
try {
|
|
21823
21834
|
const userEmail = credentials.email ?? "";
|
|
21824
21835
|
const userKey = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
|
|
@@ -21828,10 +21839,10 @@ function createUpstashProvisioner() {
|
|
|
21828
21839
|
await listUpstashDatabases(userEmail, userKey);
|
|
21829
21840
|
email3 = userEmail;
|
|
21830
21841
|
apiKey = userKey;
|
|
21831
|
-
|
|
21842
|
+
logger11.info("Upstash authenticated", { email: userEmail });
|
|
21832
21843
|
return ok(userEmail);
|
|
21833
21844
|
} catch (error62) {
|
|
21834
|
-
|
|
21845
|
+
logger11.error("Upstash authentication failed", { error: error62 });
|
|
21835
21846
|
return err(
|
|
21836
21847
|
HaiDeployError.AUTH_FAILED,
|
|
21837
21848
|
deployM("deploy_authFailed", {
|
|
@@ -21848,7 +21859,7 @@ function createUpstashProvisioner() {
|
|
|
21848
21859
|
deployM("deploy_authRequired")
|
|
21849
21860
|
);
|
|
21850
21861
|
}
|
|
21851
|
-
|
|
21862
|
+
logger11.debug("Provisioning Upstash Redis", { appName });
|
|
21852
21863
|
let resource;
|
|
21853
21864
|
try {
|
|
21854
21865
|
const databaseName = `${appName}-cache`;
|
|
@@ -21860,7 +21871,7 @@ function createUpstashProvisioner() {
|
|
|
21860
21871
|
}
|
|
21861
21872
|
resource = { resourceInfo: `upstash-db:${databaseId}`, resourceStatus: "reused" };
|
|
21862
21873
|
const detail = await getUpstashDatabase(email3, apiKey, databaseId);
|
|
21863
|
-
|
|
21874
|
+
logger11.info("Upstash Redis reused", { databaseId, databaseName });
|
|
21864
21875
|
return ok(buildUpstashProvisionResult({ ...existingDatabase, ...detail }, "reused"));
|
|
21865
21876
|
}
|
|
21866
21877
|
const data = await upstashFetch(email3, apiKey, "/v2/redis/database", {
|
|
@@ -21873,10 +21884,10 @@ function createUpstashProvisioner() {
|
|
|
21873
21884
|
});
|
|
21874
21885
|
if (data.database_id)
|
|
21875
21886
|
resource = { resourceInfo: `upstash-db:${data.database_id}`, resourceStatus: "created" };
|
|
21876
|
-
|
|
21887
|
+
logger11.info("Upstash Redis provisioned", { databaseId: data.database_id });
|
|
21877
21888
|
return ok(buildUpstashProvisionResult(data, "created"));
|
|
21878
21889
|
} catch (error62) {
|
|
21879
|
-
|
|
21890
|
+
logger11.error("Upstash provisioning failed", { appName });
|
|
21880
21891
|
if (resource) {
|
|
21881
21892
|
return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
|
|
21882
21893
|
projectName: appName,
|
|
@@ -21900,7 +21911,7 @@ function createUpstashProvisioner() {
|
|
|
21900
21911
|
}
|
|
21901
21912
|
|
|
21902
21913
|
// ../deploy/src/deploy-main.ts
|
|
21903
|
-
var
|
|
21914
|
+
var logger12 = core.logger.child({ module: "deploy", scope: "main" });
|
|
21904
21915
|
var currentConfig = null;
|
|
21905
21916
|
var currentProvider = null;
|
|
21906
21917
|
var currentContainerProvider = null;
|
|
@@ -21985,7 +21996,7 @@ var deploy = {
|
|
|
21985
21996
|
credentials: deployCredentials,
|
|
21986
21997
|
async init(config2) {
|
|
21987
21998
|
if (initInProgress) {
|
|
21988
|
-
|
|
21999
|
+
logger12.warn("Deploy init already in progress, skipping concurrent call");
|
|
21989
22000
|
return err(
|
|
21990
22001
|
HaiDeployError.CONFIG_ERROR,
|
|
21991
22002
|
deployM("deploy_configError", { params: { error: "init already in progress" } })
|
|
@@ -21994,13 +22005,13 @@ var deploy = {
|
|
|
21994
22005
|
initInProgress = true;
|
|
21995
22006
|
try {
|
|
21996
22007
|
if (currentProvider || currentContainerProvider) {
|
|
21997
|
-
|
|
22008
|
+
logger12.warn("Deploy module is already initialized, reinitializing");
|
|
21998
22009
|
await deploy.close();
|
|
21999
22010
|
}
|
|
22000
|
-
|
|
22011
|
+
logger12.info("Initializing deploy module");
|
|
22001
22012
|
const parseResult = DeployConfigSchema.safeParse(config2);
|
|
22002
22013
|
if (!parseResult.success) {
|
|
22003
|
-
|
|
22014
|
+
logger12.error("Deploy config validation failed", { error: parseResult.error.message });
|
|
22004
22015
|
return err(
|
|
22005
22016
|
HaiDeployError.CONFIG_ERROR,
|
|
22006
22017
|
deployM("deploy_configError", { params: { error: parseResult.error.message } }),
|
|
@@ -22013,14 +22024,14 @@ var deploy = {
|
|
|
22013
22024
|
let containerProvider = null;
|
|
22014
22025
|
if (parsed.provider.type === "docker-ssh") {
|
|
22015
22026
|
containerProvider = createDockerSshProvider(parsed.provider, parsed.container);
|
|
22016
|
-
|
|
22027
|
+
logger12.info("Container deploy provider ready", { provider: "docker-ssh", host: parsed.provider.ssh?.host });
|
|
22017
22028
|
} else {
|
|
22018
22029
|
provider = createProviderByType(parsed.provider.type);
|
|
22019
22030
|
const authResult = await provider.authenticate(parsed.provider.token);
|
|
22020
22031
|
if (!authResult.success) {
|
|
22021
22032
|
return err(authResult.error);
|
|
22022
22033
|
}
|
|
22023
|
-
|
|
22034
|
+
logger12.info("Provider authenticated", { provider: parsed.provider.type, user: authResult.data });
|
|
22024
22035
|
}
|
|
22025
22036
|
currentProvisioners.clear();
|
|
22026
22037
|
if (parsed.services) {
|
|
@@ -22032,14 +22043,14 @@ var deploy = {
|
|
|
22032
22043
|
const credentials = extractCredentials(serviceConfig);
|
|
22033
22044
|
const provAuthResult = await provisioner.authenticate(credentials);
|
|
22034
22045
|
if (!provAuthResult.success) {
|
|
22035
|
-
|
|
22046
|
+
logger12.error("Provisioner authentication failed", {
|
|
22036
22047
|
service: serviceType,
|
|
22037
22048
|
provisioner: serviceConfig.provisioner
|
|
22038
22049
|
});
|
|
22039
22050
|
currentProvisioners.clear();
|
|
22040
22051
|
return err(provAuthResult.error);
|
|
22041
22052
|
}
|
|
22042
|
-
|
|
22053
|
+
logger12.info("Provisioner authenticated", {
|
|
22043
22054
|
service: serviceType,
|
|
22044
22055
|
user: provAuthResult.data
|
|
22045
22056
|
});
|
|
@@ -22049,7 +22060,7 @@ var deploy = {
|
|
|
22049
22060
|
currentProvider = provider;
|
|
22050
22061
|
currentContainerProvider = containerProvider;
|
|
22051
22062
|
currentConfig = parsed;
|
|
22052
|
-
|
|
22063
|
+
logger12.info("Deploy module initialized", {
|
|
22053
22064
|
provider: parsed.provider.type,
|
|
22054
22065
|
provisioners: [...currentProvisioners.keys()]
|
|
22055
22066
|
});
|
|
@@ -22059,7 +22070,7 @@ var deploy = {
|
|
|
22059
22070
|
currentContainerProvider = null;
|
|
22060
22071
|
currentConfig = null;
|
|
22061
22072
|
currentProvisioners.clear();
|
|
22062
|
-
|
|
22073
|
+
logger12.error("Deploy module initialization failed", { error: error62 });
|
|
22063
22074
|
return err(
|
|
22064
22075
|
HaiDeployError.CONFIG_ERROR,
|
|
22065
22076
|
deployM("deploy_configError", {
|
|
@@ -22074,15 +22085,15 @@ var deploy = {
|
|
|
22074
22085
|
},
|
|
22075
22086
|
async close() {
|
|
22076
22087
|
if (!currentProvider && !currentContainerProvider) {
|
|
22077
|
-
|
|
22088
|
+
logger12.info("Deploy module already closed, skipping");
|
|
22078
22089
|
return;
|
|
22079
22090
|
}
|
|
22080
|
-
|
|
22091
|
+
logger12.info("Closing deploy module");
|
|
22081
22092
|
currentProvider = null;
|
|
22082
22093
|
currentContainerProvider = null;
|
|
22083
22094
|
currentConfig = null;
|
|
22084
22095
|
currentProvisioners.clear();
|
|
22085
|
-
|
|
22096
|
+
logger12.info("Deploy module closed");
|
|
22086
22097
|
},
|
|
22087
22098
|
get config() {
|
|
22088
22099
|
return currentConfig;
|
|
@@ -22091,22 +22102,22 @@ var deploy = {
|
|
|
22091
22102
|
return currentProvider !== null || currentContainerProvider !== null;
|
|
22092
22103
|
},
|
|
22093
22104
|
async scan(appDir, options) {
|
|
22094
|
-
|
|
22105
|
+
logger12.debug("Scanning application", { appDir });
|
|
22095
22106
|
return scanApp(appDir, options);
|
|
22096
22107
|
},
|
|
22097
22108
|
async provisionAll(projectName, services) {
|
|
22098
22109
|
if (!currentProvider && !currentContainerProvider) {
|
|
22099
22110
|
return notInitialized.result();
|
|
22100
22111
|
}
|
|
22101
|
-
|
|
22112
|
+
logger12.info("Provisioning all services", { projectName, count: currentProvisioners.size });
|
|
22102
22113
|
const results = [];
|
|
22103
22114
|
for (const [serviceType, provisioner] of currentProvisioners) {
|
|
22104
22115
|
if (services && !services.includes(serviceType))
|
|
22105
22116
|
continue;
|
|
22106
|
-
|
|
22117
|
+
logger12.debug("Provisioning service", { serviceType, provisioner: provisioner.name });
|
|
22107
22118
|
const provResult = await provisioner.provision(projectName);
|
|
22108
22119
|
if (!provResult.success) {
|
|
22109
|
-
|
|
22120
|
+
logger12.error("Service provisioning failed", {
|
|
22110
22121
|
serviceType,
|
|
22111
22122
|
provisioner: provisioner.name,
|
|
22112
22123
|
code: provResult.error.code
|
|
@@ -22123,7 +22134,7 @@ var deploy = {
|
|
|
22123
22134
|
});
|
|
22124
22135
|
}
|
|
22125
22136
|
results.push(provResult.data);
|
|
22126
|
-
|
|
22137
|
+
logger12.info("Service provisioned", { serviceType, provisioner: provisioner.name });
|
|
22127
22138
|
}
|
|
22128
22139
|
return ok(results);
|
|
22129
22140
|
},
|
|
@@ -22131,7 +22142,7 @@ var deploy = {
|
|
|
22131
22142
|
if (!currentProvider && !currentContainerProvider) {
|
|
22132
22143
|
return notInitialized.result();
|
|
22133
22144
|
}
|
|
22134
|
-
|
|
22145
|
+
logger12.info("Starting deployment", { appDir, environment: options?.environment, projectName: options?.projectName });
|
|
22135
22146
|
const scanOptions = { ...options, environment: options?.environment ?? currentConfig?.environment };
|
|
22136
22147
|
const scanResult = await scanApp(appDir, scanOptions);
|
|
22137
22148
|
if (!scanResult.success) {
|
|
@@ -22147,11 +22158,13 @@ var deploy = {
|
|
|
22147
22158
|
options = {
|
|
22148
22159
|
...options,
|
|
22149
22160
|
...scanOptions.environment ? { environment: environment.data.environment } : {},
|
|
22150
|
-
...options?.
|
|
22161
|
+
...options?.skipMiddleware ? { serviceModes: Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) } : {}
|
|
22151
22162
|
};
|
|
22152
22163
|
if (currentContainerProvider) {
|
|
22153
22164
|
return deployContainerApp(currentContainerProvider, appDir, scan, projectName, options, appEnv);
|
|
22154
22165
|
}
|
|
22166
|
+
if (options?.bundleOnly)
|
|
22167
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_bundleOnlyContainer"));
|
|
22155
22168
|
if (!currentProvider) {
|
|
22156
22169
|
return notInitialized.result();
|
|
22157
22170
|
}
|
|
@@ -22163,9 +22176,11 @@ var deploy = {
|
|
|
22163
22176
|
})
|
|
22164
22177
|
);
|
|
22165
22178
|
}
|
|
22179
|
+
if (!existsSync(resolveOutputDir(appDir, "vercel")))
|
|
22180
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
|
|
22166
22181
|
let allEnvVars = appEnv;
|
|
22167
22182
|
const recovery = { projectName, stage: "provision", resources: [] };
|
|
22168
|
-
if (!options?.
|
|
22183
|
+
if (!options?.skipMiddleware) {
|
|
22169
22184
|
const provResults = await deploy.provisionAll(projectName, options?.serviceModes ? scan.requiredServices.filter((service) => usesDefaultMiddleware(scan, service, options)) : void 0);
|
|
22170
22185
|
if (!provResults.success) {
|
|
22171
22186
|
return err(provResults.error);
|
|
@@ -22190,12 +22205,6 @@ var deploy = {
|
|
|
22190
22205
|
return recoveryFailure(envResult.error, { ...recovery, stage: "set-env" });
|
|
22191
22206
|
}
|
|
22192
22207
|
}
|
|
22193
|
-
if (!options?.skipBuild) {
|
|
22194
|
-
const buildResult = buildApp(appDir, scan.buildCommand, allEnvVars);
|
|
22195
|
-
if (!buildResult.success) {
|
|
22196
|
-
return recoveryFailure(buildResult.error, { ...recovery, stage: "build" });
|
|
22197
|
-
}
|
|
22198
|
-
}
|
|
22199
22208
|
const outputDir = resolveOutputDir(appDir, currentConfig?.provider.type ?? "vercel");
|
|
22200
22209
|
const deployResult = await currentProvider.deploy(projectId, outputDir);
|
|
22201
22210
|
if (!deployResult.success) {
|
|
@@ -22205,7 +22214,7 @@ var deploy = {
|
|
|
22205
22214
|
...deployResult.data,
|
|
22206
22215
|
envVarsSet: Object.keys(allEnvVars)
|
|
22207
22216
|
};
|
|
22208
|
-
|
|
22217
|
+
logger12.info("Deployment completed", {
|
|
22209
22218
|
url: result.url,
|
|
22210
22219
|
deploymentId: result.deploymentId,
|
|
22211
22220
|
status: result.status
|
|
@@ -22215,21 +22224,30 @@ var deploy = {
|
|
|
22215
22224
|
};
|
|
22216
22225
|
async function deployContainerApp(provider, appDir, scan, rawProjectName, options, appEnv = {}) {
|
|
22217
22226
|
const projectName = normalizeProjectName(rawProjectName);
|
|
22218
|
-
|
|
22219
|
-
|
|
22220
|
-
|
|
22221
|
-
|
|
22222
|
-
|
|
22227
|
+
let artifact;
|
|
22228
|
+
let artifactDir = options?.artifactDir ?? resolveArtifactDirectory(appDir);
|
|
22229
|
+
try {
|
|
22230
|
+
if (existsSync(join(artifactDir, "latest"))) {
|
|
22231
|
+
const latest = readFileSync(join(artifactDir, "latest"), "utf8").trim();
|
|
22232
|
+
if (!/^[a-z0-9][\w.+-]*$/i.test(latest))
|
|
22233
|
+
throw new Error("Invalid artifact pointer");
|
|
22234
|
+
artifactDir = join(artifactDir, latest);
|
|
22235
|
+
}
|
|
22236
|
+
artifact = await readDeploymentArtifact(artifactDir);
|
|
22237
|
+
} catch {
|
|
22238
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
|
|
22223
22239
|
}
|
|
22240
|
+
if (!options?.bundleOnly && currentConfig?.provider.type === "docker-ssh" && !currentConfig.provider.ssh)
|
|
22241
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_sshRequired"));
|
|
22224
22242
|
let provisionEnv = {};
|
|
22225
|
-
if (!options?.
|
|
22243
|
+
if (!options?.skipMiddleware) {
|
|
22226
22244
|
const provResults = await deploy.provisionAll(projectName, options?.serviceModes ? scan.requiredServices.filter((service) => usesDefaultMiddleware(scan, service, options)) : void 0);
|
|
22227
22245
|
if (!provResults.success) {
|
|
22228
22246
|
return err(provResults.error);
|
|
22229
22247
|
}
|
|
22230
22248
|
provisionEnv = mergeProvisionEnv(provResults.data, scan, options);
|
|
22231
22249
|
}
|
|
22232
|
-
return provider.deploy({ appDir, projectName, scan, provisionEnv, appEnv, options });
|
|
22250
|
+
return provider.deploy({ appDir, artifact, artifactDir, projectName, scan, provisionEnv, appEnv, options });
|
|
22233
22251
|
}
|
|
22234
22252
|
|
|
22235
22253
|
// ../deploy/src/deploy-middleware.ts
|
|
@@ -22277,6 +22295,6 @@ function describeMiddlewareBackend(backend) {
|
|
|
22277
22295
|
return deployM(backend.purpose);
|
|
22278
22296
|
}
|
|
22279
22297
|
|
|
22280
|
-
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy, describeMiddlewareBackend, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, saveDeploymentEnvironment };
|
|
22281
|
-
//# sourceMappingURL=src-
|
|
22282
|
-
//# sourceMappingURL=src-
|
|
22298
|
+
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, deploy, describeMiddlewareBackend, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, readDeploymentArtifact, saveDeploymentEnvironment };
|
|
22299
|
+
//# sourceMappingURL=src-RDQ2EIBT.js.map
|
|
22300
|
+
//# sourceMappingURL=src-RDQ2EIBT.js.map
|