@h-ai/deploy 0.1.0-alpha.67 → 0.1.0-alpha.68
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 +28 -5
- package/README.ja.md +28 -5
- package/README.md +38 -15
- package/dist/index.d.ts +466 -61
- package/dist/index.js +930 -158
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { createHash, randomBytes } from 'crypto';
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
2
2
|
import { createReadStream, readFileSync, existsSync, lstatSync, writeFileSync, chmodSync, mkdirSync, mkdtempSync, copyFileSync, renameSync, rmSync, statSync, readdirSync } from 'fs';
|
|
3
3
|
import { resolve, join, basename, dirname, relative, sep } from 'path';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { core, err, ok } from '@h-ai/core';
|
|
6
|
-
import
|
|
6
|
+
import process3 from 'process';
|
|
7
7
|
import { parseEnv } from 'util';
|
|
8
8
|
import { LineCounter, parseDocument, parse, stringify, parseAllDocuments } from 'yaml';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import { spawn } from 'child_process';
|
|
11
|
+
import { readdir, readFile } from 'fs/promises';
|
|
12
|
+
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
11
13
|
import { Buffer } from 'buffer';
|
|
12
14
|
|
|
13
15
|
// src/deploy-artifact.ts
|
|
@@ -109,8 +111,17 @@ var en_US_default = {
|
|
|
109
111
|
deploy_webProxyInvalid: "The API prefix must be an absolute path containing only letters, numbers, underscores, hyphens and slashes.",
|
|
110
112
|
deploy_artifactInvalid: "Image artifacts are missing or invalid. Run pnpm build --target docker, select valid --artifact-dir, or use pnpm run deploy --build.",
|
|
111
113
|
deploy_sshRequired: "Automatic deployment requires SSH settings. Use --bundle-only for manual transfer when the server is unreachable.",
|
|
112
|
-
deploy_bundleInstructions: "Bundle: {file}\nIncludes all images needed by selected services, including self-hosted middleware.\nWith SSH access, run deploy.ps1 (Windows) or deploy.sh (Linux/macOS)
|
|
114
|
+
deploy_bundleInstructions: "Bundle: {file}\nIncludes all images needed by selected services, including self-hosted middleware.\nWith SSH access, first edit deploy-target.json in the adjacent deployment directory using its _comments, then run deploy.ps1 (Windows) or deploy.sh (Linux/macOS) there. Both read this JSON; the sh entry also requires local awk.\nFor isolated servers, copy and extract the tar into a separate directory, then run sh apply.sh (uses the configured target directory).\nLinux requires Docker/Podman, Compose, sh, tar, sha256sum, awk, curl, util-linux flock and filesystem/container permissions on a filesystem supporting flock.\nNever delete or replace .deploy.lock; its lock releases after every holder exits or the server restarts. Inspect execution and recovery state after forced termination.\nPartial updates require a successful full installation with matching ports/layout. Steps return nonzero on failure; result-DEPLOYMENT_ID.json records the outcome.\nThe bundle contains private runtime configuration; transfer it securely.\nIncluded images:\n{images}",
|
|
113
115
|
deploy_bundleMetadata: "Application version: {version}\nBuild ID: {buildId}\nCompose file: {compose}",
|
|
116
|
+
deploy_targetHost: "Required: Linux server address or SSH alias. Replace SERVER_HOST before running either upload script.",
|
|
117
|
+
deploy_targetPort: "SSH port, integer 1-65535; default 22.",
|
|
118
|
+
deploy_targetUsername: "SSH login username; default deploy. Requires write access to directory and container runtime permissions.",
|
|
119
|
+
deploy_targetIdentityFile: "Optional private key path on the computer running the upload script. Prefer an absolute path; empty uses ssh-agent/default keys. HAI_DEPLOY_IDENTITY_FILE overrides it.",
|
|
120
|
+
deploy_targetKnownHostsFile: "Optional known_hosts path on the computer running the upload script. Prefer an absolute path; empty uses OpenSSH defaults. HAI_DEPLOY_KNOWN_HOSTS_FILE overrides it.",
|
|
121
|
+
deploy_targetStrictHostKeyChecking: "Boolean, default true: trust new host keys and reject changed keys. false disables host key checking.",
|
|
122
|
+
deploy_targetConnectTimeoutSeconds: "SSH connection timeout in seconds, integer 1-2147483647; default 10.",
|
|
123
|
+
deploy_targetDirectory: "Required absolute application directory on the Linux server; contains incoming packages, releases and successful current state. Parent-directory traversal is forbidden.",
|
|
124
|
+
deploy_targetBundle: "Required tar filename in the parent of this deployment directory. Keep the tar and this entire directory together when moving them.",
|
|
114
125
|
deploy_bundleOnlyContainer: "Compose bundle generation is supported only by docker-ssh.",
|
|
115
126
|
deploy_selectedContainerOnly: "Selected-service deployment requires a docker-ssh Compose destination.",
|
|
116
127
|
deploy_selectionInvalid: "Cannot deploy {service}: check the service name and build its image first.",
|
|
@@ -121,7 +132,102 @@ var en_US_default = {
|
|
|
121
132
|
deploy_configFileUnreadable: "Cannot read configuration file {path}. Check its path and permissions.",
|
|
122
133
|
deploy_bundleNameInvalid: "Bundle filenames may contain letters, numbers, dots, plus signs, hyphens and underscores; parent directories may contain spaces.",
|
|
123
134
|
deploy_secretsInvalid: "Middleware credentials are damaged or incomplete: {path}. Restore a backup; passwords are not regenerated because existing data may become inaccessible.",
|
|
124
|
-
deploy_storageArtifactRequired: "Storage initialization requires the application image. Select a complete artifact containing app with --artifact-dir, or run pnpm build --target docker --images app first."
|
|
135
|
+
deploy_storageArtifactRequired: "Storage initialization requires the application image. Select a complete artifact containing app with --artifact-dir, or run pnpm build --target docker --images app first.",
|
|
136
|
+
deploy_localStopped: "The local command process tree has been confirmed exited.",
|
|
137
|
+
deploy_localUnknown: "Local process tree termination is unconfirmed; inspect local execution state.",
|
|
138
|
+
deploy_localNotStarted: "The local command was not started.",
|
|
139
|
+
deploy_commandOutputFailed: "Command output or callback failed",
|
|
140
|
+
deploy_commandInputFailed: "Command input failed",
|
|
141
|
+
deploy_commandTreeActive: "Child processes remain active after the main command exited",
|
|
142
|
+
deploy_commandExecutionFailed: "Command execution failed",
|
|
143
|
+
deploy_remoteUnknown: "Remote execution is unknown. Local SSH termination does not prove remote deployment stopped; inspect the server result and actual containers.",
|
|
144
|
+
deploy_remoteFinished: "A matching server result confirms the deployment script and recovery finished: stage {stage}, exit {code}. Verify actual service health."
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// messages/ja-JP.json
|
|
148
|
+
var ja_JP_default = {
|
|
149
|
+
deploy_notInitialized: "\u30C7\u30D7\u30ED\u30A4\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bdeploy.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
150
|
+
deploy_initFailed: "\u30C7\u30D7\u30ED\u30A4\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
151
|
+
deploy_unsupportedProviderType: "\u672A\u5BFE\u5FDC\u306E\u30C7\u30D7\u30ED\u30A4\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306E\u7A2E\u985E\u3067\u3059: {type}",
|
|
152
|
+
deploy_unsupportedProvisionerType: "\u672A\u5BFE\u5FDC\u306E\u30B5\u30FC\u30D3\u30B9\u30D7\u30ED\u30D3\u30B8\u30E7\u30CA\u30FC\u306E\u7A2E\u985E\u3067\u3059: {type}",
|
|
153
|
+
deploy_configTokenRequired: "\u30C7\u30D7\u30ED\u30A4\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306E\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059",
|
|
154
|
+
deploy_authRequired: "\u8A8D\u8A3C\u304C\u5FC5\u8981\u3067\u3059",
|
|
155
|
+
deploy_authFailed: "\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
156
|
+
deploy_projectCreateFailed: "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
157
|
+
deploy_uploadFailed: "\u30D3\u30EB\u30C9\u6210\u679C\u7269\u306E\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
158
|
+
deploy_deployFailed: "\u30C7\u30D7\u30ED\u30A4\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
159
|
+
deploy_provisionFailed: "\u30B5\u30FC\u30D3\u30B9\u306E\u6E96\u5099\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {service} - {error}",
|
|
160
|
+
deploy_adapterMissing: "\u5BFE\u8C61\u30D7\u30E9\u30C3\u30C8\u30D5\u30A9\u30FC\u30E0\u306E\u30A2\u30C0\u30D7\u30BF\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002{adapter}\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
161
|
+
deploy_scanFailed: "\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u30B9\u30AD\u30E3\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
162
|
+
deploy_envVarFailed: "\u74B0\u5883\u5909\u6570\u306E\u8A2D\u5B9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
163
|
+
deploy_credentialError: "\u8A8D\u8A3C\u60C5\u5831\u306E\u8AAD\u307F\u66F8\u304D\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
164
|
+
deploy_credentialInvalidKey: "\u672A\u5BFE\u5FDC\u306E\u8A8D\u8A3C\u60C5\u5831\u30AD\u30FC\u3067\u3059: {key}\u3002HAI_DEPLOY_*\u30AD\u30FC\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059",
|
|
165
|
+
deploy_credentialSaved: "\u8A8D\u8A3C\u60C5\u5831\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
|
|
166
|
+
deploy_credentialLoaded: "\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F",
|
|
167
|
+
deploy_configError: "\u30C7\u30D7\u30ED\u30A4\u8A2D\u5B9A\u30A8\u30E9\u30FC: {error}",
|
|
168
|
+
deploy_credentialMissing: "\u8A8D\u8A3C\u60C5\u5831\u306E\u9805\u76EE\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: {fields}",
|
|
169
|
+
deploy_apiError: "{service} API\u30A8\u30E9\u30FC\uFF08{status}\uFF09",
|
|
170
|
+
deploy_fileUploadFailed: "\u30D5\u30A1\u30A4\u30EB\u306E\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08{status}\uFF09",
|
|
171
|
+
deploy_provisionNoResult: "{service}\u304B\u3089\u63A5\u7D9A\u60C5\u5831\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F",
|
|
172
|
+
deploy_recoveryFailure: "{stage}\u3067\u30C7\u30D7\u30ED\u30A4\u3092\u505C\u6B62\u3057\u307E\u3057\u305F\u3002\u5FA9\u65E7\u60C5\u5831\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
173
|
+
deploy_recoverySuggestion: "\u540C\u3058\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D\u3067\u518D\u8A66\u884C\u3059\u308B\u524D\u306B\u3001\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306E\u30B3\u30F3\u30BD\u30FC\u30EB\u3067\u8A18\u8F09\u3055\u308C\u305F\u30EA\u30BD\u30FC\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u300C\u4E0D\u660E\u300D\u306F\u3001\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u3088\u3063\u3066\u30EA\u30BD\u30FC\u30B9\u304C\u4F5C\u6210\u3055\u308C\u305F\u53EF\u80FD\u6027\u3092\u793A\u3057\u307E\u3059\u3002\u518D\u5229\u7528\u3057\u305F\u30EA\u30BD\u30FC\u30B9\u306F\u81EA\u52D5\u524A\u9664\u3055\u308C\u307E\u305B\u3093\u3002\u65B0\u898F\u30EA\u30BD\u30FC\u30B9\u306F\u4F7F\u7528\u72B6\u6CC1\u3092\u78BA\u8A8D\u3057\u3066\u304B\u3089\u624B\u52D5\u3067\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
174
|
+
deploy_commandTimeout: "\u30B3\u30DE\u30F3\u30C9\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F: {command}",
|
|
175
|
+
deploy_commandFailed: "\u30B3\u30DE\u30F3\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08{command}\uFF09: {error}",
|
|
176
|
+
deploy_remoteConnectFailed: "\u30EA\u30E2\u30FC\u30C8\u30DB\u30B9\u30C8{host}\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093: {error}",
|
|
177
|
+
deploy_remoteRuntimeMissing: "\u30EA\u30E2\u30FC\u30C8\u30DB\u30B9\u30C8\u306B\u30B3\u30F3\u30C6\u30CA\u30FC\u30E9\u30F3\u30BF\u30A4\u30E0\u307E\u305F\u306Fcompose\u304C\u3042\u308A\u307E\u305B\u3093: {runtime}",
|
|
178
|
+
deploy_imageTransferFailed: "\u30A4\u30E1\u30FC\u30B8\u306E\u8EE2\u9001\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
179
|
+
deploy_healthCheckFailed: "\u30D8\u30EB\u30B9\u30C1\u30A7\u30C3\u30AF\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002{url}\u3067\u30A2\u30D7\u30EA\u306E\u6E96\u5099\u304C\u5B8C\u4E86\u3057\u3066\u3044\u307E\u305B\u3093",
|
|
180
|
+
deploy_invalidEnvironment: "\u74B0\u5883\u540D\u306B\u306F\u82F1\u6570\u5B57\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3001\u30CF\u30A4\u30D5\u30F3\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002local/example\u306F\u4E88\u7D04\u3055\u308C\u3066\u3044\u307E\u3059\u3002",
|
|
181
|
+
deploy_environmentMissing: "\u74B0\u5883\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093: {path}\u3002\u30C7\u30D7\u30ED\u30A4\u524D\u306B\u4F5C\u6210\u3057\u3066\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
182
|
+
deploy_environmentReadFailed: "\u30C7\u30D7\u30ED\u30A4\u7528\u306E\u74B0\u5883\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3002\u69CB\u6587\u3068\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
183
|
+
deploy_environmentWriteFailed: "\u30C7\u30D7\u30ED\u30A4\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3002\u30D5\u30A1\u30A4\u30EB\u306E\u69CB\u6587\u3068\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
184
|
+
deploy_multipleApplications: "\u30B5\u30FC\u30D3\u30B9\u30D1\u30C3\u30B1\u30FC\u30B8\u304C\u8907\u6570\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3002\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
185
|
+
deploy_purposeSqlite: "\u30DC\u30EA\u30E5\u30FC\u30E0\u306B\u6C38\u7D9A\u5316\u3059\u308B\u30ED\u30FC\u30AB\u30EB\u30EA\u30EC\u30FC\u30B7\u30E7\u30CA\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9",
|
|
186
|
+
deploy_purposeDatabase: "\u696D\u52D9\u30C7\u30FC\u30BF\u7528\u306E\u30EA\u30EC\u30FC\u30B7\u30E7\u30CA\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u30B5\u30FC\u30D3\u30B9",
|
|
187
|
+
deploy_purposeMemory: "\u30D7\u30ED\u30BB\u30B9\u5358\u4F4D\u306E\u30E1\u30E2\u30EA\u30FC\u30AD\u30E3\u30C3\u30B7\u30E5\u3002\u518D\u8D77\u52D5\u6642\u306B\u6D88\u53BB\u3055\u308C\u3001\u5916\u90E8\u30B5\u30FC\u30D3\u30B9\u306F\u4F7F\u7528\u3057\u307E\u305B\u3093",
|
|
188
|
+
deploy_purposeLocal: "\u30DC\u30EA\u30E5\u30FC\u30E0\u306B\u6C38\u7D9A\u5316\u3059\u308B\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u30B9\u30C8\u30EC\u30FC\u30B8",
|
|
189
|
+
deploy_purposeCache: "\u5171\u6709\u30AD\u30E3\u30C3\u30B7\u30E5\u3001\u30BB\u30C3\u30B7\u30E7\u30F3\u3001\u5206\u6563\u30ED\u30C3\u30AF",
|
|
190
|
+
deploy_purposeStorage: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30D5\u30A1\u30A4\u30EB\u7528\u306ES3\u4E92\u63DB\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u30B9\u30C8\u30EC\u30FC\u30B8",
|
|
191
|
+
deploy_purposeVector: "\u30D9\u30AF\u30C8\u30EB\u306E\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u4F5C\u6210\u3068\u985E\u4F3C\u691C\u7D22",
|
|
192
|
+
deploy_purposeDisabled: "LDAP\u30ED\u30B0\u30A4\u30F3\u306F\u7121\u52B9\u3067\u3059\u3002\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u30B5\u30FC\u30D3\u30B9\u306F\u4F5C\u6210\u3057\u307E\u305B\u3093",
|
|
193
|
+
deploy_purposeLdap: "\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u30A2\u30AB\u30A6\u30F3\u30C8\u8A8D\u8A3C\u3002\u65E2\u5B9A\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u306F\u696D\u52D9\u30E6\u30FC\u30B6\u30FC\u306F\u3044\u307E\u305B\u3093",
|
|
194
|
+
deploy_purposeConsole: "\u914D\u4FE1\u305B\u305A\u3001\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u30ED\u30B0\u306B\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059",
|
|
195
|
+
deploy_purposeEmail: "\u30E1\u30FC\u30EB/HTTP\u914D\u4FE1\u3002\u65E2\u5B9A\u306ESMTP\u30B3\u30F3\u30C6\u30CA\u30FC\u306F\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u306E\u307F\u53D7\u4FE1\u3057\u307E\u3059",
|
|
196
|
+
deploy_purposeSms: "Aliyun\u306E\u8A8D\u8A3C\u60C5\u5831\u306B\u3088\u308BSMS\u3002\u65E2\u5B58\u306E\u7F72\u540D\u3068\u5229\u7528\u67A0\u304C\u5FC5\u8981\u3067\u3059",
|
|
197
|
+
deploy_webProxyInvalid: "API\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u306F\u3001\u82F1\u6570\u5B57\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3001\u30CF\u30A4\u30D5\u30F3\u3001\u30B9\u30E9\u30C3\u30B7\u30E5\u306E\u307F\u3092\u542B\u3080\u7D76\u5BFE\u30D1\u30B9\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
198
|
+
deploy_artifactInvalid: "\u30A4\u30E1\u30FC\u30B8\u6210\u679C\u7269\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u3001\u7121\u52B9\u3067\u3059\u3002pnpm build --target docker \u3092\u5B9F\u884C\u3059\u308B\u304B\u3001--artifact-dir \u3067\u6709\u52B9\u306A\u6210\u679C\u7269\u3092\u9078\u629E\u3059\u308B\u304B\u3001pnpm run deploy --build \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
199
|
+
deploy_sshRequired: "\u81EA\u52D5\u30C7\u30D7\u30ED\u30A4\u306B\u306FSSH\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059\u3002\u30B5\u30FC\u30D0\u30FC\u306B\u63A5\u7D9A\u3067\u304D\u306A\u3044\u5834\u5408\u306F--bundle-only\u3067\u624B\u52D5\u8EE2\u9001\u7528\u306E\u30D0\u30F3\u30C9\u30EB\u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
200
|
+
deploy_bundleInstructions: "\u30D0\u30F3\u30C9\u30EB\uFF1A{file}\n\u9078\u629E\u3057\u305F\u30B5\u30FC\u30D3\u30B9\u306B\u5FC5\u8981\u306A\u3059\u3079\u3066\u306E\u30A4\u30E1\u30FC\u30B8\u3092\u542B\u307F\u307E\u3059\uFF08\u30BB\u30EB\u30D5\u30DB\u30B9\u30C8\u306E\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u3092\u542B\u3080\uFF09\u3002\nSSH \u3092\u4F7F\u7528\u3059\u308B\u5834\u5408\u3001\u96A3\u63A5\u3059\u308B\u30C7\u30D7\u30ED\u30A4\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E deploy-target.json \u3092 _comments \u306B\u5F93\u3063\u3066\u7DE8\u96C6\u3057\u3001\u540C\u3058\u5834\u6240\u3067 deploy.ps1\uFF08Windows\uFF09\u307E\u305F\u306F deploy.sh\uFF08Linux/macOS\uFF09\u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4E21\u65B9\u3068\u3082\u3053\u306E JSON \u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3059\u3002sh \u306E\u5B9F\u884C\u306B\u306F\u30ED\u30FC\u30AB\u30EB\u306E awk \u3082\u5FC5\u8981\u3067\u3059\u3002\n\u9694\u96E2\u3055\u308C\u305F\u30B5\u30FC\u30D0\u30FC\u3067\u306F\u3001tar \u3092\u5225\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3078\u30B3\u30D4\u30FC\u3057\u3066\u5C55\u958B\u3057\u3001sh apply.sh \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u8A2D\u5B9A\u3055\u308C\u305F\u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u4F7F\u7528\u3057\u307E\u3059\uFF09\u3002\nLinux \u306B\u306F Docker/Podman\u3001Compose\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\u3001util-linux \u306E flock \u304C\u5FC5\u8981\u3067\u3059\u3002flock \u306B\u5BFE\u5FDC\u3057\u305F\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0\u4E0A\u3067\u3001\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0\u3068\u30B3\u30F3\u30C6\u30CA\u30FC\u306E\u64CD\u4F5C\u6A29\u9650\u3082\u5FC5\u8981\u3067\u3059\u3002\n.deploy.lock \u306F\u524A\u9664\u3082\u7F6E\u63DB\u3082\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u3059\u3079\u3066\u306E\u4FDD\u6301\u30D7\u30ED\u30BB\u30B9\u304C\u7D42\u4E86\u3059\u308B\u304B\u3001\u30B5\u30FC\u30D0\u30FC\u304C\u518D\u8D77\u52D5\u3059\u308B\u3068\u30ED\u30C3\u30AF\u304C\u89E3\u653E\u3055\u308C\u307E\u3059\u3002\u5F37\u5236\u7D42\u4E86\u5F8C\u306F\u5B9F\u884C\u72B6\u614B\u3068\u5FA9\u65E7\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\n\u90E8\u5206\u66F4\u65B0\u306B\u306F\u30DD\u30FC\u30C8\u3068\u69CB\u6210\u304C\u4E00\u81F4\u3059\u308B\u6B63\u5E38\u306A\u5B8C\u5168\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u304C\u5FC5\u8981\u3067\u3059\u3002\u5404\u624B\u9806\u306F\u5931\u6557\u6642\u306B\u975E\u30BC\u30ED\u3067\u7D42\u4E86\u3057\u3001result-DEPLOYMENT_ID.json \u306B\u7D50\u679C\u3092\u8A18\u9332\u3057\u307E\u3059\u3002\n\u30D0\u30F3\u30C9\u30EB\u306B\u306F\u975E\u516C\u958B\u306E\u5B9F\u884C\u6642\u8A2D\u5B9A\u304C\u542B\u307E\u308C\u307E\u3059\u3002\u5B89\u5168\u306B\u8EE2\u9001\u3057\u3066\u304F\u3060\u3055\u3044\u3002\n\u540C\u68B1\u30A4\u30E1\u30FC\u30B8\uFF1A\n{images}",
|
|
201
|
+
deploy_bundleMetadata: "\u30A2\u30D7\u30EA\u306E\u30D0\u30FC\u30B8\u30E7\u30F3: {version}\n\u30D3\u30EB\u30C9ID: {buildId}\nCompose\u30D5\u30A1\u30A4\u30EB: {compose}",
|
|
202
|
+
deploy_targetHost: "\u5FC5\u9808\uFF1ALinux \u30B5\u30FC\u30D0\u30FC\u306E\u30A2\u30C9\u30EC\u30B9\u307E\u305F\u306F SSH \u30A8\u30A4\u30EA\u30A2\u30B9\u3002\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u5B9F\u884C\u3059\u308B\u524D\u306B SERVER_HOST \u3092\u7F6E\u304D\u63DB\u3048\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
203
|
+
deploy_targetPort: "SSH \u30DD\u30FC\u30C8\u30021\u301C65535\u306E\u6574\u6570\u3002\u65E2\u5B9A\u5024\u306F22\u3067\u3059\u3002",
|
|
204
|
+
deploy_targetUsername: "SSH \u30ED\u30B0\u30A4\u30F3\u30E6\u30FC\u30B6\u30FC\u540D\u3002\u65E2\u5B9A\u5024\u306F deploy \u3067\u3059\u3002\u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3078\u306E\u66F8\u304D\u8FBC\u307F\u6A29\u9650\u3068\u30B3\u30F3\u30C6\u30CA\u30FC\u30E9\u30F3\u30BF\u30A4\u30E0\u306E\u64CD\u4F5C\u6A29\u9650\u304C\u5FC5\u8981\u3067\u3059\u3002",
|
|
205
|
+
deploy_targetIdentityFile: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u5B9F\u884C\u3059\u308B\u7AEF\u672B\u4E0A\u306E\u79D8\u5BC6\u9375\u30D1\u30B9\uFF08\u7701\u7565\u53EF\uFF09\u3002\u7D76\u5BFE\u30D1\u30B9\u3092\u63A8\u5968\u3057\u307E\u3059\u3002\u7A7A\u6B04\u3067\u306F ssh-agent \u307E\u305F\u306F\u65E2\u5B9A\u306E\u9375\u3092\u4F7F\u7528\u3057\u307E\u3059\u3002HAI_DEPLOY_IDENTITY_FILE \u3067\u4E0A\u66F8\u304D\u3067\u304D\u307E\u3059\u3002",
|
|
206
|
+
deploy_targetKnownHostsFile: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u5B9F\u884C\u3059\u308B\u7AEF\u672B\u4E0A\u306E known_hosts \u30D1\u30B9\uFF08\u7701\u7565\u53EF\uFF09\u3002\u7D76\u5BFE\u30D1\u30B9\u3092\u63A8\u5968\u3057\u307E\u3059\u3002\u7A7A\u6B04\u3067\u306F OpenSSH \u306E\u65E2\u5B9A\u5024\u3092\u4F7F\u7528\u3057\u307E\u3059\u3002HAI_DEPLOY_KNOWN_HOSTS_FILE \u3067\u4E0A\u66F8\u304D\u3067\u304D\u307E\u3059\u3002",
|
|
207
|
+
deploy_targetStrictHostKeyChecking: "\u771F\u507D\u5024\u3002\u65E2\u5B9A\u5024\u306E true \u306F\u65B0\u3057\u3044\u30DB\u30B9\u30C8\u9375\u3092\u4FE1\u983C\u3057\u3001\u5909\u66F4\u3055\u308C\u305F\u9375\u3092\u62D2\u5426\u3057\u307E\u3059\u3002false \u306F\u30DB\u30B9\u30C8\u9375\u306E\u691C\u8A3C\u3092\u7121\u52B9\u306B\u3057\u307E\u3059\u3002",
|
|
208
|
+
deploy_targetConnectTimeoutSeconds: "SSH \u63A5\u7D9A\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\uFF08\u79D2\uFF09\u30021\u301C2147483647\u306E\u6574\u6570\u3002\u65E2\u5B9A\u5024\u306F10\u3067\u3059\u3002",
|
|
209
|
+
deploy_targetDirectory: "\u5FC5\u9808\uFF1ALinux \u30B5\u30FC\u30D0\u30FC\u4E0A\u306E\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u7D76\u5BFE\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3002\u53D7\u4FE1\u30D1\u30C3\u30B1\u30FC\u30B8\u3001\u30EA\u30EA\u30FC\u30B9\u3001\u6210\u529F\u6642\u306E current \u72B6\u614B\u3092\u4FDD\u5B58\u3057\u307E\u3059\u3002\u89AA\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3078\u306E\u79FB\u52D5\u306F\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3002",
|
|
210
|
+
deploy_targetBundle: "\u5FC5\u9808\uFF1A\u3053\u306E\u30C7\u30D7\u30ED\u30A4\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u89AA\u306B\u3042\u308B tar \u30D5\u30A1\u30A4\u30EB\u540D\u3002\u79FB\u52D5\u6642\u306F tar \u3068\u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u5168\u4F53\u3092\u4E00\u7DD2\u306B\u79FB\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
211
|
+
deploy_bundleOnlyContainer: "Compose\u30D0\u30F3\u30C9\u30EB\u306E\u751F\u6210\u306Fdocker-ssh\u306E\u307F\u3067\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u307E\u3059\u3002",
|
|
212
|
+
deploy_selectedContainerOnly: "\u30B5\u30FC\u30D3\u30B9\u3092\u9078\u629E\u3057\u305F\u30C7\u30D7\u30ED\u30A4\u306B\u306F docker-ssh Compose \u306E\u30C7\u30D7\u30ED\u30A4\u5148\u304C\u5FC5\u8981\u3067\u3059\u3002",
|
|
213
|
+
deploy_selectionInvalid: "{service} \u3092\u30C7\u30D7\u30ED\u30A4\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D3\u30B9\u540D\u3092\u78BA\u8A8D\u3057\u3001\u5148\u306B\u30A4\u30E1\u30FC\u30B8\u3092\u30D3\u30EB\u30C9\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
214
|
+
deploy_imageVersionRequired: "\u30A4\u30E1\u30FC\u30B8\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002latest \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF1A{image}",
|
|
215
|
+
deploy_bundleRuntimeRequired: "\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u30A4\u30E1\u30FC\u30B8\u3092\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3059\u308B\u306B\u306F\u30ED\u30FC\u30AB\u30EB\u306E Podman/Docker \u3092\u8D77\u52D5\u3059\u308B\u304B\u3001\u30D3\u30EB\u30C9\u7AEF\u672B\u3067\u5B8C\u5168\u306A\u30AA\u30D5\u30E9\u30A4\u30F3\u30D0\u30F3\u30C9\u30EB\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
216
|
+
deploy_bundleFailed: "\u30D1\u30C3\u30B1\u30FC\u30B8\u306E\u6E96\u5099\u307E\u305F\u306F\u30C7\u30D7\u30ED\u30A4\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{reason}",
|
|
217
|
+
deploy_configFileInvalid: "{path} \u306E {line} \u884C\u76EE\u306E YAML \u304C\u7121\u52B9\u3067\u3059\u3002\u30A4\u30F3\u30C7\u30F3\u30C8\u3001\u30B3\u30ED\u30F3\u3001\u5F15\u7528\u7B26\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
218
|
+
deploy_configFileUnreadable: "\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB {path} \u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3002\u30D1\u30B9\u3068\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
219
|
+
deploy_bundleNameInvalid: "\u30D0\u30F3\u30C9\u30EB\u306E\u30D5\u30A1\u30A4\u30EB\u540D\u306B\u306F\u82F1\u6570\u5B57\u3001\u30C9\u30C3\u30C8\u3001\u30D7\u30E9\u30B9\u3001\u30CF\u30A4\u30D5\u30F3\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3092\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002\u89AA\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u306F\u7A7A\u767D\u3092\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
|
|
220
|
+
deploy_secretsInvalid: "\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u306E\u8A8D\u8A3C\u60C5\u5831\u304C\u7834\u640D\u3057\u3066\u3044\u308B\u304B\u3001\u4E0D\u5B8C\u5168\u3067\u3059\uFF1A{path}\u3002\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u3092\u5FA9\u5143\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u65E2\u5B58\u30C7\u30FC\u30BF\u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u306A\u304F\u306A\u308B\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u3001\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u518D\u751F\u6210\u3057\u307E\u305B\u3093\u3002",
|
|
221
|
+
deploy_storageArtifactRequired: "\u30B9\u30C8\u30EC\u30FC\u30B8\u306E\u521D\u671F\u5316\u306B\u306F\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u30A4\u30E1\u30FC\u30B8\u304C\u5FC5\u8981\u3067\u3059\u3002--artifact-dir \u3067 app \u3092\u542B\u3080\u5B8C\u5168\u306A\u6210\u679C\u7269\u3092\u9078\u629E\u3059\u308B\u304B\u3001\u5148\u306B pnpm build --target docker --images app \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
222
|
+
deploy_localStopped: "\u30ED\u30FC\u30AB\u30EB\u30B3\u30DE\u30F3\u30C9\u306E\u30D7\u30ED\u30BB\u30B9\u30C4\u30EA\u30FC\u306E\u7D42\u4E86\u3092\u78BA\u8A8D\u3057\u307E\u3057\u305F\u3002",
|
|
223
|
+
deploy_localUnknown: "\u30ED\u30FC\u30AB\u30EB\u30D7\u30ED\u30BB\u30B9\u30C4\u30EA\u30FC\u306E\u7D42\u4E86\u306F\u672A\u78BA\u8A8D\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u306E\u5B9F\u884C\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
224
|
+
deploy_localNotStarted: "\u30ED\u30FC\u30AB\u30EB\u30B3\u30DE\u30F3\u30C9\u306F\u8D77\u52D5\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
225
|
+
deploy_commandOutputFailed: "\u30B3\u30DE\u30F3\u30C9\u51FA\u529B\u307E\u305F\u306F\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
226
|
+
deploy_commandInputFailed: "\u30B3\u30DE\u30F3\u30C9\u5165\u529B\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
227
|
+
deploy_commandTreeActive: "\u30E1\u30A4\u30F3\u30B3\u30DE\u30F3\u30C9\u7D42\u4E86\u5F8C\u3082\u5B50\u30D7\u30ED\u30BB\u30B9\u304C\u52D5\u4F5C\u3057\u3066\u3044\u307E\u3059",
|
|
228
|
+
deploy_commandExecutionFailed: "\u30B3\u30DE\u30F3\u30C9\u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
229
|
+
deploy_remoteUnknown: "\u30EA\u30E2\u30FC\u30C8\u306E\u5B9F\u884C\u72B6\u614B\u306F\u4E0D\u660E\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB SSH \u306E\u7D42\u4E86\u306F\u30EA\u30E2\u30FC\u30C8\u30C7\u30D7\u30ED\u30A4\u306E\u505C\u6B62\u3092\u610F\u5473\u3057\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u7D50\u679C\u3068\u5B9F\u969B\u306E\u30B3\u30F3\u30C6\u30CA\u30FC\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
230
|
+
deploy_remoteFinished: "\u5BFE\u5FDC\u3059\u308B\u30B5\u30FC\u30D0\u30FC\u306E\u7D50\u679C\u306B\u3088\u308A\u3001\u30C7\u30D7\u30ED\u30A4\u30B9\u30AF\u30EA\u30D7\u30C8\u3068\u5FA9\u65E7\u51E6\u7406\u306E\u7D42\u4E86\u3092\u78BA\u8A8D\u3057\u307E\u3057\u305F\uFF1A\u6BB5\u968E {stage}\u3001\u7D42\u4E86\u30B3\u30FC\u30C9 {code}\u3002\u5B9F\u969B\u306E\u30B5\u30FC\u30D3\u30B9\u306E\u7A3C\u50CD\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
125
231
|
};
|
|
126
232
|
|
|
127
233
|
// messages/zh-CN.json
|
|
@@ -177,8 +283,17 @@ var zh_CN_default = {
|
|
|
177
283
|
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",
|
|
178
284
|
deploy_artifactInvalid: "\u955C\u50CF\u4EA7\u7269\u7F3A\u5931\u6216\u6821\u9A8C\u5931\u8D25\u3002\u8BF7\u5148\u6267\u884C pnpm build --target docker\uFF0C\u6216\u7528 --artifact-dir \u6307\u5B9A\u6709\u6548\u4EA7\u7269\uFF1B\u4E5F\u53EF\u7528 pnpm run deploy --build \u4E00\u6B21\u5B8C\u6210\u3002",
|
|
179
285
|
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",
|
|
180
|
-
deploy_bundleInstructions: "\u90E8\u7F72\u5305\uFF1A{file}\n\u5305\u4E2D\u5DF2\u5305\u542B\u672C\u6B21\u9009\u62E9\u670D\u52A1\u9700\u8981\u7684\u955C\u50CF\uFF0C\u5305\u62EC\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u3002\n\u6709 SSH \u8FDE\u63A5\u65F6\uFF0C\
|
|
286
|
+
deploy_bundleInstructions: "\u90E8\u7F72\u5305\uFF1A{file}\n\u5305\u4E2D\u5DF2\u5305\u542B\u672C\u6B21\u9009\u62E9\u670D\u52A1\u9700\u8981\u7684\u955C\u50CF\uFF0C\u5305\u62EC\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u3002\n\u6709 SSH \u8FDE\u63A5\u65F6\uFF0C\u5148\u6309 _comments \u8BF4\u660E\u4FEE\u6539\u65C1\u8FB9\u90E8\u7F72\u76EE\u5F55\u4E2D\u7684 deploy-target.json\uFF0C\u518D\u8FD0\u884C\u540C\u76EE\u5F55\u7684 deploy.ps1\uFF08Windows\uFF09\u6216 deploy.sh\uFF08Linux/macOS\uFF09\uFF1B\u4E24\u8005\u5747\u8BFB\u53D6\u6B64 JSON\uFF0Csh \u5165\u53E3\u8FD8\u9700\u672C\u673A\u5B89\u88C5 awk\u3002\n\u5B8C\u5168\u9694\u79BB\u65F6\uFF0C\u590D\u5236 tar \u5230 Linux \u670D\u52A1\u5668\uFF0C\u5728\u72EC\u7ACB\u76EE\u5F55\u89E3\u5305\uFF0C\u518D\u6267\u884C sh apply.sh\uFF08\u4F7F\u7528\u5305\u4E2D\u914D\u7F6E\u7684\u76EE\u6807\u76EE\u5F55\uFF09\u3002\n\u670D\u52A1\u5668\u9700\u8981 Docker/Podman\u3001Compose\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\u3001util-linux flock\uFF0C\u4EE5\u53CA\u652F\u6301 flock \u7684\u6587\u4EF6\u7CFB\u7EDF\u548C\u76EE\u5F55\u3001\u5BB9\u5668\u8FD0\u884C\u6743\u9650\u3002\n\u56FA\u5B9A\u9501\u6587\u4EF6 .deploy.lock \u7981\u6B62\u5220\u9664\u6216\u66FF\u6362\uFF1B\u5168\u90E8\u6301\u6709\u8FDB\u7A0B\u9000\u51FA\u6216\u91CD\u542F\u540E\u81EA\u52A8\u91CA\u653E\u3002\u5F3A\u6740\u540E\u7684\u6267\u884C\u4E0E\u6062\u590D\u72B6\u6001\u987B\u4EBA\u5DE5\u6838\u5BF9\u3002\n\u90E8\u5206\u66F4\u65B0\u8981\u6C42\u670D\u52A1\u5668\u5DF2\u6709\u6210\u529F\u7684\u5B8C\u6574\u90E8\u7F72\uFF0C\u7AEF\u53E3\u53CA\u5E94\u7528\u5E03\u5C40\u4FDD\u6301\u4E00\u81F4\u3002\u6BCF\u6B65\u5931\u8D25\u4F1A\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u7801\uFF1B\u7ED3\u679C\u8BB0\u5F55\u5728\u90E8\u7F72\u76EE\u5F55 result-\u90E8\u7F72ID.json\u3002\n\u5305\u5305\u542B\u79C1\u6709\u8FD0\u884C\u914D\u7F6E\uFF0C\u8BF7\u5B89\u5168\u4F20\u8F93\u3002\n\u6240\u542B\u955C\u50CF\uFF1A\n{images}",
|
|
181
287
|
deploy_bundleMetadata: "\u5E94\u7528\u7248\u672C\uFF1A{version}\n\u6784\u5EFA\u7F16\u53F7\uFF1A{buildId}\nCompose \u6587\u4EF6\uFF1A{compose}",
|
|
288
|
+
deploy_targetHost: "\u5FC5\u586B\uFF1ALinux \u670D\u52A1\u5668\u5730\u5740\u6216 SSH \u522B\u540D\u3002\u6267\u884C\u4EFB\u4E00\u4E0A\u4F20\u811A\u672C\u524D\uFF0C\u5C06 SERVER_HOST \u6539\u4E3A\u5B9E\u9645\u5730\u5740\u3002",
|
|
289
|
+
deploy_targetPort: "SSH \u7AEF\u53E3\uFF0C1-65535 \u7684\u6574\u6570\uFF0C\u9ED8\u8BA4 22\u3002",
|
|
290
|
+
deploy_targetUsername: "SSH \u767B\u5F55\u7528\u6237\u540D\uFF0C\u9ED8\u8BA4 deploy\uFF1B\u9700\u8981\u76EE\u6807\u76EE\u5F55\u5199\u5165\u6743\u9650\u53CA\u5BB9\u5668\u8FD0\u884C\u6743\u9650\u3002",
|
|
291
|
+
deploy_targetIdentityFile: "\u53EF\u9009\uFF1A\u8FD0\u884C\u4E0A\u4F20\u811A\u672C\u7684\u7535\u8111\u4E0A\u7684\u79C1\u94A5\u8DEF\u5F84\uFF0C\u5EFA\u8BAE\u7EDD\u5BF9\u8DEF\u5F84\uFF1B\u7559\u7A7A\u4F7F\u7528 ssh-agent \u6216\u9ED8\u8BA4\u5BC6\u94A5\u3002HAI_DEPLOY_IDENTITY_FILE \u53EF\u8986\u76D6\u3002",
|
|
292
|
+
deploy_targetKnownHostsFile: "\u53EF\u9009\uFF1A\u8FD0\u884C\u4E0A\u4F20\u811A\u672C\u7684\u7535\u8111\u4E0A\u7684 known_hosts \u8DEF\u5F84\uFF0C\u5EFA\u8BAE\u7EDD\u5BF9\u8DEF\u5F84\uFF1B\u7559\u7A7A\u4F7F\u7528 OpenSSH \u9ED8\u8BA4\u6587\u4EF6\u3002HAI_DEPLOY_KNOWN_HOSTS_FILE \u53EF\u8986\u76D6\u3002",
|
|
293
|
+
deploy_targetStrictHostKeyChecking: "\u5E03\u5C14\u503C\uFF0C\u9ED8\u8BA4 true\uFF1A\u9996\u6B21\u4FE1\u4EFB\u65B0\u4E3B\u673A\u5BC6\u94A5\uFF0C\u62D2\u7EDD\u5DF2\u53D8\u66F4\u5BC6\u94A5\uFF1Bfalse \u5173\u95ED\u4E3B\u673A\u5BC6\u94A5\u68C0\u67E5\u3002",
|
|
294
|
+
deploy_targetConnectTimeoutSeconds: "SSH \u8FDE\u63A5\u8D85\u65F6\u79D2\u6570\uFF0C1-2147483647 \u7684\u6574\u6570\uFF0C\u9ED8\u8BA4 10\u3002",
|
|
295
|
+
deploy_targetDirectory: "\u5FC5\u586B\uFF1ALinux \u670D\u52A1\u5668\u4E0A\u7684\u5E94\u7528\u7EDD\u5BF9\u76EE\u5F55\uFF0C\u4FDD\u5B58 incoming \u90E8\u7F72\u5305\u3001releases \u53CA\u6210\u529F\u72B6\u6001 current\uFF1B\u7981\u6B62 .. \u7236\u76EE\u5F55\u8DF3\u8F6C\u3002",
|
|
296
|
+
deploy_targetBundle: "\u5FC5\u586B\uFF1A\u672C\u90E8\u7F72\u64CD\u4F5C\u76EE\u5F55\u4E0A\u4E00\u7EA7\u7684 tar \u6587\u4EF6\u540D\uFF1B\u642C\u8FD0\u65F6\u8BF7\u5C06 tar \u548C\u672C\u76EE\u5F55\u5B8C\u6574\u653E\u5728\u4E00\u8D77\u3002",
|
|
182
297
|
deploy_bundleOnlyContainer: "\u4EC5\u751F\u6210 Compose tar \u53EA\u652F\u6301 docker-ssh\u3002",
|
|
183
298
|
deploy_selectedContainerOnly: "\u6309\u670D\u52A1\u5C40\u90E8\u90E8\u7F72\u4EC5\u9002\u7528\u4E8E docker-ssh Compose \u76EE\u6807\u3002",
|
|
184
299
|
deploy_selectionInvalid: "\u65E0\u6CD5\u90E8\u7F72\u670D\u52A1 {service}\uFF1A\u8BF7\u68C0\u67E5\u670D\u52A1\u540D\u79F0\uFF0C\u5E76\u5148\u6784\u5EFA\u5BF9\u5E94\u955C\u50CF\u3002",
|
|
@@ -189,13 +304,23 @@ var zh_CN_default = {
|
|
|
189
304
|
deploy_configFileInvalid: "\u914D\u7F6E\u6587\u4EF6 {path} \u7B2C {line} \u884C\u683C\u5F0F\u6709\u8BEF\u3002\u8BF7\u68C0\u67E5\u7F29\u8FDB\u3001\u5192\u53F7\u548C\u5F15\u53F7\u540E\u91CD\u8BD5\u3002",
|
|
190
305
|
deploy_configFileUnreadable: "\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6 {path}\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u8DEF\u5F84\u4E0E\u8BFB\u53D6\u6743\u9650\u3002",
|
|
191
306
|
deploy_bundleNameInvalid: "\u90E8\u7F72\u5305\u6587\u4EF6\u540D\u53EA\u652F\u6301\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u52A0\u53F7\u3001\u77ED\u6A2A\u7EBF\u548C\u4E0B\u5212\u7EBF\uFF1B\u6240\u5728\u76EE\u5F55\u53EF\u4EE5\u5305\u542B\u7A7A\u683C\u3002",
|
|
192
|
-
deploy_secretsInvalid: "\u4E2D\u95F4\u4EF6\u51ED\u636E\u6587\u4EF6\u635F\u574F\u6216\u7F3A\u5C11\u5B57\u6BB5\uFF1A{path}\u3002\u8BF7\u4ECE\u5907\u4EFD\u6062\u590D\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u66F4\u6362\u5BC6\u7801\uFF0C\u4EE5\u514D\u5DF2\u6709\u6570\u636E\u65E0\u6CD5\u8BBF\u95EE\u3002"
|
|
307
|
+
deploy_secretsInvalid: "\u4E2D\u95F4\u4EF6\u51ED\u636E\u6587\u4EF6\u635F\u574F\u6216\u7F3A\u5C11\u5B57\u6BB5\uFF1A{path}\u3002\u8BF7\u4ECE\u5907\u4EFD\u6062\u590D\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u66F4\u6362\u5BC6\u7801\uFF0C\u4EE5\u514D\u5DF2\u6709\u6570\u636E\u65E0\u6CD5\u8BBF\u95EE\u3002",
|
|
308
|
+
deploy_localStopped: "\u5DF2\u786E\u8BA4\u672C\u5730\u547D\u4EE4\u8FDB\u7A0B\u6811\u5168\u90E8\u9000\u51FA\u3002",
|
|
309
|
+
deploy_localUnknown: "\u65E0\u6CD5\u786E\u8BA4\u672C\u5730\u547D\u4EE4\u8FDB\u7A0B\u6811\u5DF2\u5168\u90E8\u9000\u51FA\uFF1B\u8BF7\u68C0\u67E5\u672C\u673A\u6267\u884C\u72B6\u6001\u3002",
|
|
310
|
+
deploy_localNotStarted: "\u672C\u5730\u547D\u4EE4\u5C1A\u672A\u542F\u52A8\u3002",
|
|
311
|
+
deploy_commandOutputFailed: "\u547D\u4EE4\u8F93\u51FA\u6216\u56DE\u8C03\u5931\u8D25",
|
|
312
|
+
deploy_commandInputFailed: "\u547D\u4EE4\u8F93\u5165\u5931\u8D25",
|
|
313
|
+
deploy_commandTreeActive: "\u4E3B\u547D\u4EE4\u9000\u51FA\u540E\u4ECD\u6709\u6D3B\u8DC3\u7684\u5B50\u8FDB\u7A0B",
|
|
314
|
+
deploy_commandExecutionFailed: "\u547D\u4EE4\u6267\u884C\u5931\u8D25",
|
|
315
|
+
deploy_remoteUnknown: "\u8FDC\u7AEF\u6267\u884C\u72B6\u6001\u672A\u77E5\uFF1B\u672C\u5730 SSH \u7ED3\u675F\u4E0D\u8BC1\u660E\u8FDC\u7AEF\u90E8\u7F72\u5DF2\u505C\u6B62\uFF0C\u8BF7\u6838\u5BF9\u670D\u52A1\u5668\u7ED3\u679C\u4E0E\u5B9E\u9645\u5BB9\u5668\u72B6\u6001\u3002",
|
|
316
|
+
deploy_remoteFinished: "\u5339\u914D\u7684\u670D\u52A1\u5668\u7ED3\u679C\u786E\u8BA4\u90E8\u7F72\u811A\u672C\u53CA\u6062\u590D\u5DF2\u7ED3\u675F\uFF1A\u9636\u6BB5 {stage}\uFF0C\u9000\u51FA\u7801 {code}\u3002\u8BF7\u6838\u5BF9\u5B9E\u9645\u670D\u52A1\u5065\u5EB7\u72B6\u6001\u3002"
|
|
193
317
|
};
|
|
194
318
|
|
|
195
319
|
// src/deploy-i18n.ts
|
|
196
320
|
var deployM = core.i18n.createMessageGetter({
|
|
197
321
|
"zh-CN": zh_CN_default,
|
|
198
|
-
"en-US": en_US_default
|
|
322
|
+
"en-US": en_US_default,
|
|
323
|
+
"ja-JP": ja_JP_default
|
|
199
324
|
});
|
|
200
325
|
|
|
201
326
|
// src/deploy-config.ts
|
|
@@ -358,7 +483,7 @@ function loadDeploymentEnvironment(options) {
|
|
|
358
483
|
return loaded;
|
|
359
484
|
try {
|
|
360
485
|
const values = { ...loaded.data };
|
|
361
|
-
for (const [name, value] of Object.entries(
|
|
486
|
+
for (const [name, value] of Object.entries(process3.env)) {
|
|
362
487
|
if (/^(?:HAI_|PUBLIC_|VITE_)/.test(name) && value !== void 0)
|
|
363
488
|
values[name] = value;
|
|
364
489
|
}
|
|
@@ -546,7 +671,7 @@ function loadCredentials() {
|
|
|
546
671
|
const data = readCredentialsFile();
|
|
547
672
|
const keys = Object.keys(data);
|
|
548
673
|
for (const key of keys) {
|
|
549
|
-
|
|
674
|
+
process3.env[key] = String(data[key]);
|
|
550
675
|
}
|
|
551
676
|
logger.debug("Credentials loaded", { count: keys.length });
|
|
552
677
|
return ok(keys);
|
|
@@ -567,7 +692,7 @@ function saveCredential(key, value) {
|
|
|
567
692
|
const data = readCredentialsFile();
|
|
568
693
|
data[key] = value;
|
|
569
694
|
writeCredentialsFile(data);
|
|
570
|
-
|
|
695
|
+
process3.env[key] = value;
|
|
571
696
|
logger.debug("Credential saved", { key });
|
|
572
697
|
return ok(void 0);
|
|
573
698
|
} catch (error) {
|
|
@@ -587,7 +712,7 @@ function saveCredentials(entries) {
|
|
|
587
712
|
const data = readCredentialsFile();
|
|
588
713
|
for (const [key, value] of Object.entries(entries)) {
|
|
589
714
|
data[key] = value;
|
|
590
|
-
|
|
715
|
+
process3.env[key] = value;
|
|
591
716
|
}
|
|
592
717
|
writeCredentialsFile(data);
|
|
593
718
|
logger.debug("Credentials saved", { count: Object.keys(entries).length });
|
|
@@ -603,17 +728,20 @@ function saveCredentials(entries) {
|
|
|
603
728
|
);
|
|
604
729
|
}
|
|
605
730
|
}
|
|
606
|
-
function
|
|
731
|
+
function resolveProjectDirectory(appDir) {
|
|
607
732
|
let directory = resolve(appDir);
|
|
608
733
|
while (true) {
|
|
609
734
|
if (existsSync(join(directory, "hai.build.json")) || existsSync(join(directory, "pnpm-lock.yaml")))
|
|
610
|
-
return
|
|
735
|
+
return directory;
|
|
611
736
|
const parent = dirname(directory);
|
|
612
737
|
if (parent === directory)
|
|
613
|
-
return
|
|
738
|
+
return resolve(appDir);
|
|
614
739
|
directory = parent;
|
|
615
740
|
}
|
|
616
741
|
}
|
|
742
|
+
function resolveArtifactDirectory(appDir) {
|
|
743
|
+
return join(resolveProjectDirectory(appDir), ".hai/build/docker");
|
|
744
|
+
}
|
|
617
745
|
function usesDefaultMiddleware(scan, service, options) {
|
|
618
746
|
const requirements = scan.middlewares?.filter((item) => item.service === service) ?? [];
|
|
619
747
|
return requirements.length ? requirements.some((item) => (options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.[service] ?? "default") === "default") : (options?.serviceModes?.[service] ?? "default") === "default";
|
|
@@ -637,6 +765,19 @@ var RecoverySchema = z.object({
|
|
|
637
765
|
resourceStatus: z.enum(["created", "reused", "unknown"])
|
|
638
766
|
}))
|
|
639
767
|
});
|
|
768
|
+
var CommandStateSchema = z.object({
|
|
769
|
+
reason: z.enum(["timeout", "output", "input", "spawn", "exit", "configuration"]),
|
|
770
|
+
localTermination: z.enum(["confirmed", "unknown", "not-started"]),
|
|
771
|
+
remoteExecution: z.enum(["unknown", "finished"]).optional(),
|
|
772
|
+
deploymentId: z.string().optional(),
|
|
773
|
+
resultFile: z.string().optional(),
|
|
774
|
+
remoteExitCode: z.number().int().optional(),
|
|
775
|
+
remoteStage: z.string().optional()
|
|
776
|
+
});
|
|
777
|
+
function getDeployCommandState(error) {
|
|
778
|
+
const parsed = CommandStateSchema.safeParse(error.ext?.commandExecution);
|
|
779
|
+
return parsed.success ? parsed.data : void 0;
|
|
780
|
+
}
|
|
640
781
|
function getDeployRecovery(error) {
|
|
641
782
|
const parsed = RecoverySchema.safeParse(error.ext?.recovery);
|
|
642
783
|
return parsed.success ? parsed.data : void 0;
|
|
@@ -753,7 +894,7 @@ async function scanApp(inputDir, options) {
|
|
|
753
894
|
for (const [index, item] of providers.entries()) {
|
|
754
895
|
const provider = record(item);
|
|
755
896
|
const type = String(provider.type ?? "");
|
|
756
|
-
const service = type === "aliyun-sms" ? "sms" : ["smtp", "api", "console"].includes(type) ? "email" : void 0;
|
|
897
|
+
const service = type === "aliyun-sms" || ["console", "api"].includes(type) && provider.name === "sms" ? "sms" : ["smtp", "api", "console"].includes(type) ? "email" : void 0;
|
|
757
898
|
if (service)
|
|
758
899
|
add(service, type, "reach", ["providers", String(index)], provider, "@h-ai/reach");
|
|
759
900
|
}
|
|
@@ -779,91 +920,575 @@ async function scanApp(inputDir, options) {
|
|
|
779
920
|
return err(HaiDeployError.SCAN_FAILED, deployM("deploy_scanFailed", { params: { error: "Invalid or ambiguous application/configuration/lockfile; specify the application directory and run pnpm install" } }));
|
|
780
921
|
}
|
|
781
922
|
}
|
|
782
|
-
var
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
923
|
+
var nativeJob = String.raw`
|
|
924
|
+
using System;
|
|
925
|
+
using System.ComponentModel;
|
|
926
|
+
using System.Runtime.InteropServices;
|
|
927
|
+
using System.Text;
|
|
928
|
+
using System.Threading;
|
|
929
|
+
|
|
930
|
+
public static class HaiCommandJob {
|
|
931
|
+
[StructLayout(LayoutKind.Sequential)] struct BasicLimits {
|
|
932
|
+
public long ProcessTime, JobTime;
|
|
933
|
+
public uint Flags;
|
|
934
|
+
public UIntPtr MinimumWorkingSet, MaximumWorkingSet;
|
|
935
|
+
public uint ActiveProcessLimit;
|
|
936
|
+
public UIntPtr Affinity;
|
|
937
|
+
public uint PriorityClass, SchedulingClass;
|
|
938
|
+
}
|
|
939
|
+
[StructLayout(LayoutKind.Sequential)] struct IoCounters {
|
|
940
|
+
public ulong ReadOperations, WriteOperations, OtherOperations, ReadBytes, WriteBytes, OtherBytes;
|
|
941
|
+
}
|
|
942
|
+
[StructLayout(LayoutKind.Sequential)] struct ExtendedLimits {
|
|
943
|
+
public BasicLimits Basic;
|
|
944
|
+
public IoCounters Io;
|
|
945
|
+
public UIntPtr ProcessMemory, JobMemory, PeakProcessMemory, PeakJobMemory;
|
|
946
|
+
}
|
|
947
|
+
[StructLayout(LayoutKind.Sequential)] struct Accounting {
|
|
948
|
+
public long UserTime, KernelTime, PeriodUserTime, PeriodKernelTime;
|
|
949
|
+
public uint Faults, Total, Active, Terminated;
|
|
950
|
+
}
|
|
951
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct Startup {
|
|
952
|
+
public uint Size;
|
|
953
|
+
public string Reserved, Desktop, Title;
|
|
954
|
+
public uint X, Y, Width, Height, XChars, YChars, Fill, Flags;
|
|
955
|
+
public ushort Show, ReservedSize;
|
|
956
|
+
public IntPtr ReservedData, Input, Output, Error;
|
|
957
|
+
}
|
|
958
|
+
[StructLayout(LayoutKind.Sequential)] struct ProcessInfo {
|
|
959
|
+
public IntPtr Process, Thread;
|
|
960
|
+
public uint ProcessId, ThreadId;
|
|
961
|
+
}
|
|
962
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr CreateJobObject(IntPtr attributes, string name);
|
|
963
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr OpenJobObject(uint access, bool inherit, string name);
|
|
964
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool SetInformationJobObject(IntPtr job, int kind, ref ExtendedLimits information, uint size);
|
|
965
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool QueryInformationJobObject(IntPtr job, int kind, out Accounting information, uint size, IntPtr length);
|
|
966
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
|
967
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern bool CreateProcess(string application, StringBuilder command, IntPtr processAttributes, IntPtr threadAttributes, bool inherit, uint flags, IntPtr environment, string directory, ref Startup startup, out ProcessInfo information);
|
|
968
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern uint ResumeThread(IntPtr thread);
|
|
969
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateProcess(IntPtr process, uint code);
|
|
970
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateJobObject(IntPtr job, uint code);
|
|
971
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
|
972
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool GetExitCodeProcess(IntPtr process, out uint code);
|
|
973
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr handle);
|
|
974
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int kind);
|
|
975
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool SetHandleInformation(IntPtr handle, uint mask, uint flags);
|
|
976
|
+
|
|
977
|
+
static void Check(bool success) { if (!success) throw new Win32Exception(Marshal.GetLastWin32Error()); }
|
|
978
|
+
static uint Active(IntPtr job) {
|
|
979
|
+
Accounting information;
|
|
980
|
+
Check(QueryInformationJobObject(job, 1, out information, (uint)Marshal.SizeOf(typeof(Accounting)), IntPtr.Zero));
|
|
981
|
+
return information.Active;
|
|
982
|
+
}
|
|
983
|
+
// Windows argv 编码:引号前与结尾的反斜杠加倍,其余字符原样传递,不经过命令 shell。
|
|
984
|
+
static string Quote(string value) {
|
|
985
|
+
StringBuilder result = new StringBuilder("\"");
|
|
986
|
+
int slashes = 0;
|
|
987
|
+
foreach (char current in value) {
|
|
988
|
+
if (current == '\\') { slashes++; continue; }
|
|
989
|
+
if (current == '"') result.Append('\\', slashes * 2 + 1);
|
|
990
|
+
else result.Append('\\', slashes);
|
|
991
|
+
result.Append(current);
|
|
992
|
+
slashes = 0;
|
|
993
|
+
}
|
|
994
|
+
result.Append('\\', slashes * 2).Append('"');
|
|
995
|
+
return result.ToString();
|
|
996
|
+
}
|
|
997
|
+
public static int Run(string name, string command, string[] arguments) {
|
|
998
|
+
IntPtr job = CreateJobObject(IntPtr.Zero, name);
|
|
999
|
+
if (job == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
1000
|
+
ProcessInfo process = new ProcessInfo();
|
|
1001
|
+
bool assigned = false;
|
|
1002
|
+
try {
|
|
1003
|
+
ExtendedLimits limits = new ExtendedLimits();
|
|
1004
|
+
limits.Basic.Flags = 0x2000; // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;不允许 breakaway。
|
|
1005
|
+
Check(SetInformationJobObject(job, 9, ref limits, (uint)Marshal.SizeOf(typeof(ExtendedLimits))));
|
|
1006
|
+
StringBuilder line = new StringBuilder(Quote(command));
|
|
1007
|
+
foreach (string argument in arguments) line.Append(' ').Append(Quote(argument));
|
|
1008
|
+
Startup startup = new Startup();
|
|
1009
|
+
startup.Size = (uint)Marshal.SizeOf(typeof(Startup));
|
|
1010
|
+
startup.Flags = 0x100;
|
|
1011
|
+
startup.Input = GetStdHandle(-10); startup.Output = GetStdHandle(-11); startup.Error = GetStdHandle(-12);
|
|
1012
|
+
Check(SetHandleInformation(startup.Input, 1, 1));
|
|
1013
|
+
Check(SetHandleInformation(startup.Output, 1, 1));
|
|
1014
|
+
Check(SetHandleInformation(startup.Error, 1, 1));
|
|
1015
|
+
Check(CreateProcess(null, line, IntPtr.Zero, IntPtr.Zero, true, 0x08000004, IntPtr.Zero, null, ref startup, out process));
|
|
1016
|
+
Check(AssignProcessToJobObject(job, process.Process));
|
|
1017
|
+
assigned = true;
|
|
1018
|
+
if (ResumeThread(process.Thread) == 0xFFFFFFFF) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
1019
|
+
if (WaitForSingleObject(process.Process, 0xFFFFFFFF) != 0) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
1020
|
+
uint code;
|
|
1021
|
+
Check(GetExitCodeProcess(process.Process, out code));
|
|
1022
|
+
while (Active(job) != 0) Thread.Sleep(20);
|
|
1023
|
+
return unchecked((int)code);
|
|
1024
|
+
}
|
|
1025
|
+
finally {
|
|
1026
|
+
if (process.Process != IntPtr.Zero) {
|
|
1027
|
+
// 分配失败时进程仍挂起,直接终止,不允许裸进程恢复;Job 在其他异常时同样清空。
|
|
1028
|
+
if (!assigned) TerminateProcess(process.Process, 125);
|
|
1029
|
+
else TerminateJobObject(job, 125);
|
|
1030
|
+
WaitForSingleObject(process.Process, 5000);
|
|
1031
|
+
}
|
|
1032
|
+
if (process.Thread != IntPtr.Zero) CloseHandle(process.Thread);
|
|
1033
|
+
if (process.Process != IntPtr.Zero) CloseHandle(process.Process);
|
|
1034
|
+
CloseHandle(job);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
public static bool Empty(string name) {
|
|
1038
|
+
IntPtr job = OpenJobObject(4, false, name); // JOB_OBJECT_QUERY
|
|
1039
|
+
if (job == IntPtr.Zero) {
|
|
1040
|
+
if (Marshal.GetLastWin32Error() == 2) return true;
|
|
1041
|
+
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
1042
|
+
}
|
|
1043
|
+
try { return Active(job) == 0; }
|
|
1044
|
+
finally { CloseHandle(job); }
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
`;
|
|
1048
|
+
function powershell(script) {
|
|
1049
|
+
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")];
|
|
1050
|
+
}
|
|
1051
|
+
function windowsCommand(command, args) {
|
|
1052
|
+
const name = `Local\\hai-deploy-${randomUUID()}`;
|
|
1053
|
+
const key = `__HAI_DEPLOY_JOB_${randomUUID().replaceAll("-", "")}`;
|
|
1054
|
+
const script = `$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
|
|
1055
|
+
try {
|
|
1056
|
+
Add-Type -TypeDefinition @'
|
|
1057
|
+
${nativeJob}
|
|
1058
|
+
'@
|
|
1059
|
+
$inputConfig = [Environment]::GetEnvironmentVariable('${key}') | ConvertFrom-Json
|
|
1060
|
+
[Environment]::SetEnvironmentVariable('${key}', $null)
|
|
1061
|
+
exit ([HaiCommandJob]::Run([string]$inputConfig.name, [string]$inputConfig.command, [string[]]$inputConfig.args))
|
|
1062
|
+
} catch { [Console]::Error.WriteLine('Windows command job failed'); exit 125 }`;
|
|
1063
|
+
return { name, args: powershell(script), env: { [key]: JSON.stringify({ name, command, args }) } };
|
|
1064
|
+
}
|
|
1065
|
+
function windowsJobProbe(name) {
|
|
1066
|
+
return powershell(`$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
|
|
1067
|
+
try {
|
|
1068
|
+
Add-Type -TypeDefinition @'
|
|
1069
|
+
${nativeJob}
|
|
1070
|
+
'@
|
|
1071
|
+
if ([HaiCommandJob]::Empty('${name}')) { exit 0 } else { exit 1 }
|
|
1072
|
+
} catch { exit 2 }`);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// src/container/deploy-command-process.ts
|
|
1076
|
+
async function groupExited(pid) {
|
|
1077
|
+
try {
|
|
1078
|
+
process3.kill(-pid, 0);
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
return error instanceof Error && "code" in error && error.code === "ESRCH";
|
|
1081
|
+
}
|
|
1082
|
+
if (process3.platform !== "linux")
|
|
1083
|
+
return false;
|
|
1084
|
+
try {
|
|
1085
|
+
for (const entry of await readdir("/proc")) {
|
|
1086
|
+
if (!/^\d+$/.test(entry))
|
|
1087
|
+
continue;
|
|
1088
|
+
let stat;
|
|
1089
|
+
try {
|
|
1090
|
+
stat = await readFile(`/proc/${entry}/stat`, "utf8");
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
1093
|
+
continue;
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
1097
|
+
if (Number(fields[2]) === pid && fields[0] !== "Z" && fields[0] !== "X")
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
return true;
|
|
1101
|
+
} catch {
|
|
1102
|
+
return false;
|
|
790
1103
|
}
|
|
791
|
-
return masked;
|
|
792
1104
|
}
|
|
793
|
-
function
|
|
1105
|
+
async function helper(command, args, timeoutMs) {
|
|
1106
|
+
if (timeoutMs <= 0)
|
|
1107
|
+
return false;
|
|
794
1108
|
return new Promise((resolve6) => {
|
|
795
|
-
const child = spawn(command, args, {
|
|
796
|
-
windowsHide: true,
|
|
797
|
-
cwd: options.cwd,
|
|
798
|
-
env: options.env ? { ...process2.env, ...options.env } : process2.env,
|
|
799
|
-
shell: false
|
|
800
|
-
});
|
|
801
|
-
let stdout = "";
|
|
802
|
-
let stderr = "";
|
|
1109
|
+
const child = spawn(command, args, { windowsHide: true, shell: false, stdio: "ignore" });
|
|
803
1110
|
let settled = false;
|
|
804
1111
|
let timer;
|
|
805
|
-
const finish = (
|
|
1112
|
+
const finish = (success) => {
|
|
806
1113
|
if (settled)
|
|
807
1114
|
return;
|
|
808
1115
|
settled = true;
|
|
809
1116
|
if (timer)
|
|
810
1117
|
clearTimeout(timer);
|
|
811
|
-
resolve6(
|
|
1118
|
+
resolve6(success);
|
|
812
1119
|
};
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
child.
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1120
|
+
timer = globalThis.setTimeout(() => {
|
|
1121
|
+
child.kill("SIGKILL");
|
|
1122
|
+
child.unref();
|
|
1123
|
+
finish(false);
|
|
1124
|
+
}, timeoutMs);
|
|
1125
|
+
child.once("error", () => finish(false));
|
|
1126
|
+
child.once("close", (code) => finish(code === 0));
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
function spawnCommand(command, args, options) {
|
|
1130
|
+
const job = process3.platform === "win32" ? windowsCommand(command, args) : void 0;
|
|
1131
|
+
const child = spawn(job ? "powershell.exe" : command, job?.args ?? args, {
|
|
1132
|
+
...options,
|
|
1133
|
+
env: job ? { ...options.env, ...job.env } : options.env,
|
|
1134
|
+
windowsHide: true,
|
|
1135
|
+
shell: false,
|
|
1136
|
+
detached: !job
|
|
1137
|
+
});
|
|
1138
|
+
let exited = false;
|
|
1139
|
+
child.once("exit", () => {
|
|
1140
|
+
exited = true;
|
|
1141
|
+
});
|
|
1142
|
+
child.once("close", () => {
|
|
1143
|
+
exited = true;
|
|
1144
|
+
});
|
|
1145
|
+
let termination;
|
|
1146
|
+
const treeExited = async () => {
|
|
1147
|
+
if (!child.pid)
|
|
1148
|
+
return true;
|
|
1149
|
+
if (job)
|
|
1150
|
+
return exited && helper("powershell.exe", windowsJobProbe(job.name), 5e3);
|
|
1151
|
+
return groupExited(child.pid);
|
|
1152
|
+
};
|
|
1153
|
+
const terminate = (timeoutMs) => {
|
|
1154
|
+
termination ??= (async () => {
|
|
1155
|
+
const deadline = performance.now() + timeoutMs;
|
|
1156
|
+
let windowsKillConfirmed = !job || exited;
|
|
1157
|
+
if (child.pid) {
|
|
1158
|
+
if (job) {
|
|
1159
|
+
if (!exited) {
|
|
1160
|
+
windowsKillConfirmed = await helper("taskkill", ["/pid", String(child.pid), "/t", "/f"], Math.max(1, deadline - performance.now()));
|
|
1161
|
+
child.kill("SIGKILL");
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
try {
|
|
1165
|
+
process3.kill(-child.pid, "SIGKILL");
|
|
1166
|
+
} catch {
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
845
1169
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
child.stdin.
|
|
1170
|
+
while (performance.now() < deadline) {
|
|
1171
|
+
if (exited)
|
|
1172
|
+
break;
|
|
1173
|
+
await setTimeout$1(20);
|
|
1174
|
+
}
|
|
1175
|
+
let verified = false;
|
|
1176
|
+
if (exited && job) {
|
|
1177
|
+
verified = await helper("powershell.exe", windowsJobProbe(job.name), deadline - performance.now());
|
|
1178
|
+
verified &&= windowsKillConfirmed || child.exitCode !== null;
|
|
1179
|
+
} else if (exited) {
|
|
1180
|
+
while (performance.now() < deadline) {
|
|
1181
|
+
if (await treeExited()) {
|
|
1182
|
+
verified = true;
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
await setTimeout$1(20);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
child.stdin.destroy();
|
|
1189
|
+
child.stdout.destroy();
|
|
1190
|
+
child.stderr.destroy();
|
|
1191
|
+
if (!verified)
|
|
1192
|
+
child.unref();
|
|
1193
|
+
return verified ? "confirmed" : "unknown";
|
|
1194
|
+
})();
|
|
1195
|
+
return termination;
|
|
1196
|
+
};
|
|
1197
|
+
return { child, treeExited, terminate };
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/container/deploy-secret-masker.ts
|
|
1201
|
+
function createSecretMasker(secrets = []) {
|
|
1202
|
+
const values = [...new Set(secrets.filter(Boolean))].sort((a, b) => b.length - a.length);
|
|
1203
|
+
if (values.reduce((total, value) => total + value.length, 0) > 1048576)
|
|
1204
|
+
throw new RangeError("Secret redaction input is too large");
|
|
1205
|
+
const pattern = values.length ? new RegExp(values.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g") : void 0;
|
|
1206
|
+
const tailLength = Math.max(0, (values[0]?.length ?? 1) - 1);
|
|
1207
|
+
let pending = "";
|
|
1208
|
+
let ended = false;
|
|
1209
|
+
function drain(final) {
|
|
1210
|
+
let end = final ? pending.length : Math.max(0, pending.length - tailLength);
|
|
1211
|
+
if (end > 0 && end < pending.length && /[\uD800-\uDBFF]/.test(pending[end - 1]) && /[\uDC00-\uDFFF]/.test(pending[end]))
|
|
1212
|
+
end--;
|
|
1213
|
+
if (end === 0)
|
|
1214
|
+
return "";
|
|
1215
|
+
let cursor = 0;
|
|
1216
|
+
let output = "";
|
|
1217
|
+
if (pattern) {
|
|
1218
|
+
pattern.lastIndex = 0;
|
|
1219
|
+
let match = pattern.exec(pending);
|
|
1220
|
+
while (match && match.index < end) {
|
|
1221
|
+
output += `${pending.slice(cursor, match.index)}***`;
|
|
1222
|
+
cursor = pattern.lastIndex;
|
|
1223
|
+
end = Math.max(end, cursor);
|
|
1224
|
+
match = pattern.exec(pending);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
output += pending.slice(cursor, end);
|
|
1228
|
+
pending = pending.slice(end);
|
|
1229
|
+
return output;
|
|
1230
|
+
}
|
|
1231
|
+
return {
|
|
1232
|
+
write(text) {
|
|
1233
|
+
if (ended)
|
|
1234
|
+
throw new Error("Secret masker has ended");
|
|
1235
|
+
pending += text;
|
|
1236
|
+
return drain(false);
|
|
1237
|
+
},
|
|
1238
|
+
end() {
|
|
1239
|
+
if (ended)
|
|
1240
|
+
return "";
|
|
1241
|
+
ended = true;
|
|
1242
|
+
return drain(true);
|
|
865
1243
|
}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// src/container/deploy-command.ts
|
|
1248
|
+
var logger3 = core.logger.child({ module: "deploy", scope: "command" });
|
|
1249
|
+
function maskSecrets(text, secrets = []) {
|
|
1250
|
+
const masker = createSecretMasker(secrets);
|
|
1251
|
+
return masker.write(text) + masker.end();
|
|
1252
|
+
}
|
|
1253
|
+
async function runCommand(command, args, options = {}) {
|
|
1254
|
+
const failure = (message, state) => err({
|
|
1255
|
+
...HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
1256
|
+
message: `${message}
|
|
1257
|
+
${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state.localTermination === "not-started" ? "deploy_localNotStarted" : "deploy_localUnknown")}`,
|
|
1258
|
+
ext: { commandExecution: state }
|
|
866
1259
|
});
|
|
1260
|
+
try {
|
|
1261
|
+
return await new Promise((resolve6) => {
|
|
1262
|
+
if (typeof command !== "string" || !command || command.includes("\0") || args.some((argument) => typeof argument !== "string" || argument.includes("\0")))
|
|
1263
|
+
throw new TypeError("Invalid command arguments");
|
|
1264
|
+
if (options.input !== void 0 && typeof options.input !== "string")
|
|
1265
|
+
throw new TypeError("Invalid command input");
|
|
1266
|
+
const maxCapture = options.maxCaptureChars ?? 1048576;
|
|
1267
|
+
if (!Number.isSafeInteger(maxCapture) || maxCapture <= 0 || maxCapture > 16777216)
|
|
1268
|
+
throw new RangeError("Invalid command capture limit");
|
|
1269
|
+
const terminationTimeout = options.terminationTimeoutMs ?? 1e4;
|
|
1270
|
+
if (!Number.isSafeInteger(terminationTimeout) || terminationTimeout <= 0 || terminationTimeout > 6e4)
|
|
1271
|
+
throw new RangeError("Invalid command termination timeout");
|
|
1272
|
+
if (options.timeoutMs !== void 0 && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0 || options.timeoutMs > 2147483647))
|
|
1273
|
+
throw new RangeError("Invalid command timeout");
|
|
1274
|
+
const secrets = [...options.secrets ?? []];
|
|
1275
|
+
const safeCommand = maskSecrets(command, secrets);
|
|
1276
|
+
const outputMasker = createSecretMasker(secrets);
|
|
1277
|
+
const errorMasker = createSecretMasker(secrets);
|
|
1278
|
+
const control = spawnCommand(command, args, { cwd: options.cwd, env: options.env ? { ...process3.env, ...options.env } : process3.env });
|
|
1279
|
+
const child = control.child;
|
|
1280
|
+
child.stdout.setEncoding("utf8");
|
|
1281
|
+
child.stderr.setEncoding("utf8");
|
|
1282
|
+
let stdout = "";
|
|
1283
|
+
let stderr = "";
|
|
1284
|
+
let stdoutTruncated = false;
|
|
1285
|
+
let stderrTruncated = false;
|
|
1286
|
+
let settled = false;
|
|
1287
|
+
let interrupted = false;
|
|
1288
|
+
let interruption;
|
|
1289
|
+
let timer;
|
|
1290
|
+
const finish = (result) => {
|
|
1291
|
+
if (settled)
|
|
1292
|
+
return;
|
|
1293
|
+
settled = true;
|
|
1294
|
+
if (timer)
|
|
1295
|
+
clearTimeout(timer);
|
|
1296
|
+
resolve6(result);
|
|
1297
|
+
};
|
|
1298
|
+
const interrupt = (reason, message) => {
|
|
1299
|
+
interrupted = true;
|
|
1300
|
+
interruption ??= (async () => {
|
|
1301
|
+
if (timer)
|
|
1302
|
+
clearTimeout(timer);
|
|
1303
|
+
const localTermination = child.pid ? await control.terminate(terminationTimeout) : "not-started";
|
|
1304
|
+
logger3.error("Command termination checked", { command: safeCommand, reason, localTermination });
|
|
1305
|
+
finish(failure(message, { reason, localTermination }));
|
|
1306
|
+
})();
|
|
1307
|
+
return interruption;
|
|
1308
|
+
};
|
|
1309
|
+
const publish = async (text) => {
|
|
1310
|
+
if (!text || settled || interrupted)
|
|
1311
|
+
return;
|
|
1312
|
+
stdoutTruncated ||= stdout.length + text.length > maxCapture;
|
|
1313
|
+
stdout = (stdout + text).slice(-maxCapture);
|
|
1314
|
+
try {
|
|
1315
|
+
await options.onOutput?.(text);
|
|
1316
|
+
} catch {
|
|
1317
|
+
await interrupt("output", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandOutputFailed") } }));
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
const captureError = (text) => {
|
|
1321
|
+
stderrTruncated ||= stderr.length + text.length > maxCapture;
|
|
1322
|
+
stderr = (stderr + text).slice(-maxCapture);
|
|
1323
|
+
};
|
|
1324
|
+
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
1325
|
+
timer = setTimeout(() => {
|
|
1326
|
+
logger3.error("Command timed out", { command: safeCommand, timeoutMs: options.timeoutMs });
|
|
1327
|
+
void interrupt("timeout", deployM("deploy_commandTimeout", { params: { command: safeCommand } }));
|
|
1328
|
+
}, options.timeoutMs);
|
|
1329
|
+
}
|
|
1330
|
+
const outputTask = (async () => {
|
|
1331
|
+
try {
|
|
1332
|
+
for await (const chunk of child.stdout) {
|
|
1333
|
+
if (settled || interrupted)
|
|
1334
|
+
return;
|
|
1335
|
+
if (typeof chunk !== "string")
|
|
1336
|
+
throw new TypeError("Command output must be UTF-8 decoded");
|
|
1337
|
+
await publish(outputMasker.write(chunk));
|
|
1338
|
+
}
|
|
1339
|
+
if (!settled && !interrupted)
|
|
1340
|
+
await publish(outputMasker.end());
|
|
1341
|
+
} catch {
|
|
1342
|
+
await interrupt("output", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandOutputFailed") } }));
|
|
1343
|
+
}
|
|
1344
|
+
})();
|
|
1345
|
+
const errorTask = (async () => {
|
|
1346
|
+
try {
|
|
1347
|
+
for await (const chunk of child.stderr) {
|
|
1348
|
+
if (settled || interrupted)
|
|
1349
|
+
return;
|
|
1350
|
+
if (typeof chunk !== "string")
|
|
1351
|
+
throw new TypeError("Command error output must be UTF-8 decoded");
|
|
1352
|
+
captureError(errorMasker.write(chunk));
|
|
1353
|
+
}
|
|
1354
|
+
if (!settled && !interrupted)
|
|
1355
|
+
captureError(errorMasker.end());
|
|
1356
|
+
} catch {
|
|
1357
|
+
await interrupt("output", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandOutputFailed") } }));
|
|
1358
|
+
}
|
|
1359
|
+
})();
|
|
1360
|
+
child.stdin.on("error", () => {
|
|
1361
|
+
void interrupt("input", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandInputFailed") } }));
|
|
1362
|
+
});
|
|
1363
|
+
child.on("error", (error) => {
|
|
1364
|
+
const safeError = maskSecrets(error.message, secrets);
|
|
1365
|
+
logger3.error("Command failed to start", { command: safeCommand, error: safeError });
|
|
1366
|
+
void interrupt("spawn", deployM("deploy_commandFailed", { params: { command: safeCommand, error: safeError } }));
|
|
1367
|
+
});
|
|
1368
|
+
child.on("close", async (code) => {
|
|
1369
|
+
await Promise.all([outputTask, errorTask]);
|
|
1370
|
+
if (settled || interrupted)
|
|
1371
|
+
return;
|
|
1372
|
+
if (timer)
|
|
1373
|
+
clearTimeout(timer);
|
|
1374
|
+
if (!await control.treeExited()) {
|
|
1375
|
+
await interrupt("exit", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandTreeActive") } }));
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const exitCode = code ?? -1;
|
|
1379
|
+
if (exitCode === 0) {
|
|
1380
|
+
finish(ok({ code: exitCode, stdout, stderr, stdoutTruncated, stderrTruncated }));
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
logger3.error("Command exited with non-zero code", { command: safeCommand, code: exitCode, stderr: maskSecrets(stderr, secrets).slice(-500), stdout: maskSecrets(stdout, secrets).slice(-3e3), stdoutTruncated, stderrTruncated });
|
|
1384
|
+
finish(failure(deployM("deploy_commandFailed", { params: { command: safeCommand, error: maskSecrets(`${stdout.slice(-2500)}
|
|
1385
|
+
${stderr || `exit ${exitCode}`}`, secrets).slice(-3e3) } }), { reason: "exit", localTermination: "confirmed" }));
|
|
1386
|
+
});
|
|
1387
|
+
if (options.input !== void 0)
|
|
1388
|
+
child.stdin.write(options.input);
|
|
1389
|
+
child.stdin.end();
|
|
1390
|
+
});
|
|
1391
|
+
} catch {
|
|
1392
|
+
return failure(deployM("deploy_commandFailed", { params: { command: "<command>", error: deployM("deploy_commandExecutionFailed") } }), { reason: "configuration", localTermination: "not-started" });
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/container/deploy-target.ts
|
|
1397
|
+
function createTargetReader() {
|
|
1398
|
+
return String.raw`function fail(message) {
|
|
1399
|
+
print "Invalid deploy-target.json: " message > "/dev/stderr"
|
|
1400
|
+
exit 2
|
|
1401
|
+
}
|
|
1402
|
+
function whitespace() {
|
|
1403
|
+
while (substr(input, position, 1) ~ /^[ \t\r\n]$/) position++
|
|
1404
|
+
}
|
|
1405
|
+
function hexadecimal(text, result, offset, digit) {
|
|
1406
|
+
result = 0
|
|
1407
|
+
if (length(text) != 4) fail("invalid Unicode escape")
|
|
1408
|
+
for (offset = 1; offset <= 4; offset++) {
|
|
1409
|
+
digit = index("0123456789abcdef", tolower(substr(text, offset, 1))) - 1
|
|
1410
|
+
if (digit < 0) fail("invalid Unicode escape")
|
|
1411
|
+
result = result * 16 + digit
|
|
1412
|
+
}
|
|
1413
|
+
return result
|
|
1414
|
+
}
|
|
1415
|
+
function utf8(code) {
|
|
1416
|
+
if (code < 128) return sprintf("%c", code)
|
|
1417
|
+
if (code < 2048) return sprintf("%c%c", 192 + int(code / 64), 128 + code % 64)
|
|
1418
|
+
if (code < 65536) return sprintf("%c%c%c", 224 + int(code / 4096), 128 + int(code / 64) % 64, 128 + code % 64)
|
|
1419
|
+
return sprintf("%c%c%c%c", 240 + int(code / 262144), 128 + int(code / 4096) % 64, 128 + int(code / 64) % 64, 128 + code % 64)
|
|
1420
|
+
}
|
|
1421
|
+
function string_value( result, character, escaped, code, low) {
|
|
1422
|
+
if (substr(input, position++, 1) != "\"") fail("expected string")
|
|
1423
|
+
result = ""
|
|
1424
|
+
while (position <= length(input)) {
|
|
1425
|
+
character = substr(input, position++, 1)
|
|
1426
|
+
if (character == "\"") return result
|
|
1427
|
+
if (character ~ /[[:cntrl:]]/) fail("control character in string")
|
|
1428
|
+
if (character != "\\") { result = result character; continue }
|
|
1429
|
+
escaped = substr(input, position++, 1)
|
|
1430
|
+
if (escaped == "\"" || escaped == "\\" || escaped == "/") result = result escaped
|
|
1431
|
+
else if (escaped == "b") result = result sprintf("%c", 8)
|
|
1432
|
+
else if (escaped == "f") result = result sprintf("%c", 12)
|
|
1433
|
+
else if (escaped == "n") result = result "\n"
|
|
1434
|
+
else if (escaped == "r") result = result "\r"
|
|
1435
|
+
else if (escaped == "t") result = result "\t"
|
|
1436
|
+
else if (escaped == "u") {
|
|
1437
|
+
code = hexadecimal(substr(input, position, 4)); position += 4
|
|
1438
|
+
if (code >= 55296 && code <= 56319) {
|
|
1439
|
+
if (substr(input, position, 2) != "\\u") fail("missing low surrogate")
|
|
1440
|
+
position += 2
|
|
1441
|
+
low = hexadecimal(substr(input, position, 4)); position += 4
|
|
1442
|
+
if (low < 56320 || low > 57343) fail("invalid low surrogate")
|
|
1443
|
+
code = 65536 + (code - 55296) * 1024 + low - 56320
|
|
1444
|
+
} else if (code >= 56320 && code <= 57343) fail("unexpected low surrogate")
|
|
1445
|
+
if (code == 0) fail("NUL is not allowed")
|
|
1446
|
+
result = result utf8(code)
|
|
1447
|
+
} else fail("invalid escape")
|
|
1448
|
+
}
|
|
1449
|
+
fail("unterminated string")
|
|
1450
|
+
}
|
|
1451
|
+
function object_value(depth, key, character, value, kind) {
|
|
1452
|
+
if (depth > 16) fail("nesting is too deep")
|
|
1453
|
+
position++
|
|
1454
|
+
whitespace()
|
|
1455
|
+
if (substr(input, position, 1) == "}") { position++; return }
|
|
1456
|
+
while (position <= length(input)) {
|
|
1457
|
+
whitespace(); key = string_value(); whitespace()
|
|
1458
|
+
if (substr(input, position++, 1) != ":") fail("expected colon")
|
|
1459
|
+
whitespace(); character = substr(input, position, 1)
|
|
1460
|
+
if (character == "{") { object_value(depth + 1); value = ""; kind = "object" }
|
|
1461
|
+
else if (character == "\"") { value = string_value(); kind = "string" }
|
|
1462
|
+
else if (substr(input, position, 4) == "true") { value = "true"; kind = "boolean"; position += 4 }
|
|
1463
|
+
else if (substr(input, position, 5) == "false") { value = "false"; kind = "boolean"; position += 5 }
|
|
1464
|
+
else if (substr(input, position, 4) == "null") { value = ""; kind = "null"; position += 4 }
|
|
1465
|
+
else if (match(substr(input, position), /^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/)) {
|
|
1466
|
+
value = substr(input, position, RLENGTH); position += RLENGTH; kind = "number"
|
|
1467
|
+
} else fail("unsupported value")
|
|
1468
|
+
if (depth == 1) {
|
|
1469
|
+
if (key in values) fail("duplicate field: " key)
|
|
1470
|
+
values[key] = value; kinds[key] = kind
|
|
1471
|
+
}
|
|
1472
|
+
whitespace(); character = substr(input, position++, 1)
|
|
1473
|
+
if (character == "}") return
|
|
1474
|
+
if (character != ",") fail("expected comma or closing brace")
|
|
1475
|
+
}
|
|
1476
|
+
fail("unterminated object")
|
|
1477
|
+
}
|
|
1478
|
+
{ input = input $0 "\n" }
|
|
1479
|
+
END {
|
|
1480
|
+
position = 1
|
|
1481
|
+
if (substr(input, 1, 3) == sprintf("%c%c%c", 239, 187, 191)) position = 4
|
|
1482
|
+
whitespace()
|
|
1483
|
+
if (substr(input, position, 1) != "{") fail("expected object")
|
|
1484
|
+
object_value(1); whitespace()
|
|
1485
|
+
if (position <= length(input)) fail("trailing content")
|
|
1486
|
+
if (!(field in values) || kinds[field] == "null") { print fallback; exit }
|
|
1487
|
+
if (kinds[field] != expected) fail("wrong type for " field)
|
|
1488
|
+
if (values[field] ~ /[[:cntrl:]]/) fail("control character in " field)
|
|
1489
|
+
print values[field]
|
|
1490
|
+
}
|
|
1491
|
+
`;
|
|
867
1492
|
}
|
|
868
1493
|
|
|
869
1494
|
// src/container/deploy-scripts.ts
|
|
@@ -906,6 +1531,7 @@ previous=''
|
|
|
906
1531
|
restore=''
|
|
907
1532
|
changed=false
|
|
908
1533
|
locked=false
|
|
1534
|
+
interrupted=false
|
|
909
1535
|
started=$(date +%s)
|
|
910
1536
|
step_started=$started
|
|
911
1537
|
step() {
|
|
@@ -985,15 +1611,22 @@ finish() {
|
|
|
985
1611
|
outcome=passed
|
|
986
1612
|
if [ "$code" -ne 0 ]; then outcome=failed; fi
|
|
987
1613
|
printf '{"stage":"%s","status":"%s","durationSeconds":%s}\n' "$stage" "$outcome" "$(( $(date +%s) - step_started ))" >> "$root/steps-$id.jsonl"
|
|
988
|
-
|
|
989
|
-
|
|
1614
|
+
# 先完整写入再同目录原子发布,SSH 重连核对不能读取到半份终态结果。
|
|
1615
|
+
printf '{"deploymentId":"%s","stage":"%s","exitCode":%s,"durationSeconds":%s}\n' "$id" "$stage" "$code" "$elapsed" > "$root/result-$id.json.next"
|
|
1616
|
+
mv "$root/result-$id.json.next" "$root/result-$id.json"
|
|
1617
|
+
# 前台命令及同步恢复已结束;后台服务可继续运行,但不能使成功终态永久占锁。
|
|
1618
|
+
# 中断时仍可能有活跃子命令,必须由全部描述符持有者退出后释放。
|
|
1619
|
+
if [ "$interrupted" = false ]; then
|
|
1620
|
+
flock -u 9
|
|
1621
|
+
exec 9>&-
|
|
1622
|
+
fi
|
|
990
1623
|
fi
|
|
991
1624
|
exit "$code"
|
|
992
1625
|
}
|
|
993
1626
|
trap finish EXIT
|
|
994
|
-
trap 'exit 130' HUP INT TERM
|
|
1627
|
+
trap 'interrupted=true; exit 130' HUP INT TERM
|
|
995
1628
|
step preflight 'Checking deployment tools and package'
|
|
996
|
-
for tool in sha256sum tar awk curl; do command -v "$tool" >/dev/null || { printf 'Required command missing: %s\n' "$tool" >&2; exit 1; }; done
|
|
1629
|
+
for tool in sha256sum tar awk curl flock; do command -v "$tool" >/dev/null || { printf 'Required command missing: %s\n' "$tool" >&2; exit 1; }; done
|
|
997
1630
|
if [ "$runtime" = auto ]; then
|
|
998
1631
|
if command -v podman >/dev/null 2>&1 && podman version >/dev/null 2>&1; then runtime=podman; else runtime=docker; fi
|
|
999
1632
|
fi
|
|
@@ -1001,7 +1634,20 @@ case "$runtime" in docker|podman) ;; *) printf 'Runtime must be docker or podman
|
|
|
1001
1634
|
"$runtime" version >/dev/null
|
|
1002
1635
|
"$runtime" compose version >/dev/null
|
|
1003
1636
|
"$runtime" compose up --help | grep -q never || { printf 'Compose is too old. Install Docker Compose v2 or podman-compose 1.6.0 or newer (supports --pull never).\n' >&2; exit 1; }
|
|
1004
|
-
|
|
1637
|
+
# 内核锁绑定同一文件描述符;绝不删除/替换锁文件,避免新 inode 绕过活跃部署。
|
|
1638
|
+
# 子命令继承 FD 9:即使父 shell 被强杀,仍执行的命令继续持锁;全部退出或重启后自动释放。
|
|
1639
|
+
exec 9>"$root/.deploy.lock"
|
|
1640
|
+
if flock -n 9; then
|
|
1641
|
+
:
|
|
1642
|
+
else
|
|
1643
|
+
lock_code=$?
|
|
1644
|
+
if [ "$lock_code" -eq 1 ]; then
|
|
1645
|
+
printf 'Another deployment is running. Wait for its commands to finish; do not remove %s/.deploy.lock.\n' "$root" >&2
|
|
1646
|
+
else
|
|
1647
|
+
printf 'Cannot acquire deployment lock (exit %s). Check filesystem flock support and permissions.\n' "$lock_code" >&2
|
|
1648
|
+
fi
|
|
1649
|
+
exit "$lock_code"
|
|
1650
|
+
fi
|
|
1005
1651
|
locked=true
|
|
1006
1652
|
cd "$source_dir"
|
|
1007
1653
|
sha256sum -c SHA256SUMS
|
|
@@ -1093,70 +1739,129 @@ mv "$root/current.next" "$root/current"
|
|
|
1093
1739
|
printf '[PASS] %s\n' "$id"
|
|
1094
1740
|
`.replace(/@@([A-Z]+)@@/g, (_match, name) => values[name]);
|
|
1095
1741
|
}
|
|
1096
|
-
function createUploadScripts(
|
|
1097
|
-
const ssh = config.ssh;
|
|
1098
|
-
const host = ssh?.host ?? "SERVER_HOST";
|
|
1099
|
-
const user = ssh?.username ?? "deploy";
|
|
1100
|
-
const remote = `${config.remote.baseDir}/${projectName}`;
|
|
1101
|
-
const port = ssh?.port ?? 22;
|
|
1742
|
+
function createUploadScripts() {
|
|
1102
1743
|
const sh = String.raw`#!/bin/sh
|
|
1103
1744
|
set -eu
|
|
1104
|
-
cd "$(dirname "$0")"
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1745
|
+
source_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
1746
|
+
cd "$source_dir/.."
|
|
1747
|
+
command -v awk >/dev/null || { printf 'Install POSIX awk to read deploy-target.json.\n' >&2; exit 1; }
|
|
1748
|
+
read_target() {
|
|
1749
|
+
LC_ALL=C awk -v field="$1" -v expected="$2" -v fallback="$3" -f "$source_dir/read-target.awk" "$source_dir/deploy-target.json"
|
|
1750
|
+
}
|
|
1751
|
+
host=$(read_target host string SERVER_HOST)
|
|
1752
|
+
user=$(read_target username string deploy)
|
|
1753
|
+
port=$(read_target port number 22)
|
|
1754
|
+
remote=$(read_target directory string '')
|
|
1755
|
+
bundle=$(read_target bundle string '')
|
|
1756
|
+
identity=$(read_target identityFile string '')
|
|
1757
|
+
known_hosts=$(read_target knownHostsFile string '')
|
|
1758
|
+
strict=$(read_target strictHostKeyChecking boolean true)
|
|
1759
|
+
timeout=$(read_target connectTimeoutSeconds number 10)
|
|
1110
1760
|
if [ "$#" -ge 1 ]; then host=$1; fi
|
|
1111
1761
|
if [ "$#" -ge 2 ]; then port=$2; fi
|
|
1112
1762
|
if [ "$#" -ge 3 ]; then user=$3; fi
|
|
1113
|
-
|
|
1114
|
-
if [ -
|
|
1763
|
+
if value=$(printenv HAI_DEPLOY_IDENTITY_FILE) && [ -n "$value" ]; then identity=$value; fi
|
|
1764
|
+
if value=$(printenv HAI_DEPLOY_KNOWN_HOSTS_FILE) && [ -n "$value" ]; then known_hosts=$value; fi
|
|
1765
|
+
invalid_target() { printf 'Invalid deployment target. Edit deploy-target.json before running this script.\n' >&2; exit 1; }
|
|
1115
1766
|
test "$host" != SERVER_HOST || { printf 'Specify the server: sh deploy.sh SERVER [PORT] [USER].\n' >&2; exit 1; }
|
|
1767
|
+
case "$host" in ''|*[!a-zA-Z0-9.:-]*|[!a-zA-Z0-9]*) invalid_target;; esac
|
|
1768
|
+
case "$user" in ''|*[!a-zA-Z0-9_-]*|[!a-zA-Z_]*) invalid_target;; esac
|
|
1769
|
+
case "$port" in ''|*[!0-9]*|???????????*) invalid_target;; esac
|
|
1770
|
+
test "$port" -ge 1 && test "$port" -le 65535 || invalid_target
|
|
1771
|
+
case "$timeout" in ''|*[!0-9]*|???????????*) invalid_target;; esac
|
|
1772
|
+
test "$timeout" -ge 1 && test "$timeout" -le 2147483647 || invalid_target
|
|
1773
|
+
case "$remote" in /*) ;; *) invalid_target;; esac
|
|
1774
|
+
case "$remote" in *[!a-zA-Z0-9/_.\ -]*|*/../*|*/..|/..) invalid_target;; esac
|
|
1775
|
+
case "$bundle" in *.tar) ;; *) invalid_target;; esac
|
|
1776
|
+
case "$bundle" in ''|*[!a-zA-Z0-9_.+-]*) invalid_target;; esac
|
|
1777
|
+
case "$strict" in true) strict=accept-new;; false) strict=no;; *) invalid_target;; esac
|
|
1778
|
+
test -f "./$bundle" || { printf 'Bundle missing: %s\n' "$bundle" >&2; exit 1; }
|
|
1116
1779
|
for tool in ssh scp; do command -v "$tool" >/dev/null || { printf 'Install OpenSSH (%s).\n' "$tool" >&2; exit 1; }; done
|
|
1117
|
-
set -- -o BatchMode=yes -o StrictHostKeyChecking
|
|
1118
|
-
if [ -n "$identity" ]; then set -- "$@" -i "$identity"; fi
|
|
1119
|
-
known_hosts
|
|
1120
|
-
|
|
1121
|
-
|
|
1780
|
+
set -- -o BatchMode=yes -o "StrictHostKeyChecking=$strict" -o "ConnectTimeout=$timeout"
|
|
1781
|
+
if [ -n "$identity" ]; then set -- "$@" -i "$identity" -o IdentitiesOnly=yes; fi
|
|
1782
|
+
if [ -n "$known_hosts" ]; then
|
|
1783
|
+
known_hosts=$(printf '%s' "$known_hosts" | sed 's/[\\"]/\\&/g')
|
|
1784
|
+
set -- "$@" -o "UserKnownHostsFile=\"$known_hosts\""
|
|
1785
|
+
fi
|
|
1122
1786
|
printf '[1/3] Connecting to %s\n' "$host"
|
|
1123
1787
|
ssh "$@" -p "$port" "$user@$host" "mkdir -p '$remote/incoming' && chmod 700 '$remote' '$remote/incoming'"
|
|
1124
1788
|
printf '[2/3] Uploading %s\n' "$bundle"
|
|
1125
1789
|
scp_host=$host
|
|
1126
1790
|
case "$host" in *:*) scp_host="[$host]";; esac
|
|
1127
|
-
scp "$@" -P "$port" "
|
|
1791
|
+
scp "$@" -P "$port" "./$bundle" "$user@$scp_host:$remote/incoming/$bundle"
|
|
1128
1792
|
printf '[3/3] Applying package and checking HTTP access\n'
|
|
1129
|
-
ssh "$@" -p "$port" "$user@$host" "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote'"
|
|
1793
|
+
if ssh "$@" -p "$port" "$user@$host" "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote'"; then
|
|
1794
|
+
:
|
|
1795
|
+
else
|
|
1796
|
+
code=$?
|
|
1797
|
+
printf '[UNKNOWN] Remote execution may continue. Inspect %s/result-<deploymentId>.json (ID in manifest.json), current and containers before retrying.\n' "$remote" >&2
|
|
1798
|
+
exit "$code"
|
|
1799
|
+
fi
|
|
1130
1800
|
`;
|
|
1131
1801
|
const ps1 = String.raw`param([string]$IdentityFile = $env:HAI_DEPLOY_IDENTITY_FILE)
|
|
1132
1802
|
$ErrorActionPreference = 'Stop'
|
|
1133
|
-
Set-Location -LiteralPath $PSScriptRoot
|
|
1134
|
-
$target = Get-Content -LiteralPath '
|
|
1803
|
+
Set-Location -LiteralPath (Split-Path -Parent $PSScriptRoot)
|
|
1804
|
+
$target = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'deploy-target.json') -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
1135
1805
|
if ($target.host -eq 'SERVER_HOST') { throw 'Set the server host in deploy-target.json.' }
|
|
1136
|
-
if ($target.host -notmatch '^[a-zA-Z0-9][a-zA-Z0-9.:-]*$' -or $target.username -notmatch '^[a-zA-Z_][a-zA-Z0-9_-]*$' -or $target.directory -notmatch '^/[a-zA-Z0-9/_. -]
|
|
1137
|
-
$
|
|
1138
|
-
if ($
|
|
1806
|
+
if ($target.host -isnot [string] -or $target.host -notmatch '^[a-zA-Z0-9][a-zA-Z0-9.:-]*$' -or $target.username -isnot [string] -or $target.username -notmatch '^[a-zA-Z_][a-zA-Z0-9_-]*$' -or $target.directory -isnot [string] -or $target.directory -notmatch '^/[a-zA-Z0-9/_. -]*$' -or $target.directory -match '(^|/)\.\.(/|$)' -or $target.bundle -isnot [string] -or $target.bundle -notmatch '^[a-zA-Z0-9_.+-]+\.tar$') { throw 'Invalid deployment target.' }
|
|
1807
|
+
$port = if ($null -eq $target.port) { 22 } else { $target.port }
|
|
1808
|
+
$timeout = if ($null -eq $target.connectTimeoutSeconds) { 10 } else { $target.connectTimeoutSeconds }
|
|
1809
|
+
if ($port -is [string] -or [string]$port -notmatch '^[0-9]+$' -or $port -lt 1 -or $port -gt 65535 -or $timeout -is [string] -or [string]$timeout -notmatch '^[0-9]+$' -or $timeout -lt 1 -or $timeout -gt 2147483647) { throw 'Invalid deployment port or timeout.' }
|
|
1810
|
+
if ($null -ne $target.strictHostKeyChecking -and $target.strictHostKeyChecking -isnot [bool]) { throw 'Invalid host key policy.' }
|
|
1811
|
+
if (($null -ne $target.identityFile -and $target.identityFile -isnot [string]) -or ($null -ne $target.knownHostsFile -and $target.knownHostsFile -isnot [string])) { throw 'Invalid SSH file path.' }
|
|
1812
|
+
$strict = if ($target.strictHostKeyChecking -eq $false) { 'no' } else { 'accept-new' }
|
|
1813
|
+
$options = @('-o', 'BatchMode=yes', '-o', "StrictHostKeyChecking=$strict", '-o', "ConnectTimeout=$timeout")
|
|
1139
1814
|
if (-not $IdentityFile) { $IdentityFile = $target.identityFile }
|
|
1140
|
-
if ($IdentityFile) { $options += @('-i', $IdentityFile) }
|
|
1141
|
-
if ($
|
|
1815
|
+
if ($IdentityFile) { $options += @('-i', $IdentityFile, '-o', 'IdentitiesOnly=yes') }
|
|
1816
|
+
$knownHosts = if ($env:HAI_DEPLOY_KNOWN_HOSTS_FILE) { $env:HAI_DEPLOY_KNOWN_HOSTS_FILE } else { $target.knownHostsFile }
|
|
1817
|
+
if ($knownHosts) {
|
|
1818
|
+
$knownHosts = $knownHosts.Replace('\', '\\').Replace('"', '\"')
|
|
1819
|
+
$options += @('-o', ('UserKnownHostsFile="' + $knownHosts + '"'))
|
|
1820
|
+
}
|
|
1142
1821
|
$destination = "$($target.username)@$($target.host)"
|
|
1143
1822
|
$scpDestination = if ($target.host.Contains(':')) { "$($target.username)@[$($target.host)]" } else { $destination }
|
|
1144
1823
|
$remote = $target.directory
|
|
1145
1824
|
$bundle = $target.bundle
|
|
1825
|
+
if (-not (Test-Path -LiteralPath "./$bundle" -PathType Leaf)) { throw "Bundle missing: $bundle" }
|
|
1826
|
+
function ConvertTo-NativeArgument([string]$Value) {
|
|
1827
|
+
if ($Value.Length -gt 0 -and $Value -notmatch '[\s"]') { return $Value }
|
|
1828
|
+
$escaped = [regex]::Replace($Value, '(\\*)"', '$1$1\"')
|
|
1829
|
+
$escaped = [regex]::Replace($escaped, '(\\+)$', '$1$1')
|
|
1830
|
+
return '"' + $escaped + '"'
|
|
1831
|
+
}
|
|
1146
1832
|
function Invoke-Step([string]$Name, [string]$Command, [string[]]$Arguments) {
|
|
1147
1833
|
Write-Host "[$Name]"
|
|
1148
|
-
|
|
1149
|
-
if ($
|
|
1834
|
+
$nativeCommand = Get-Command $Command -ErrorAction Stop
|
|
1835
|
+
if ($PSVersionTable.PSVersion.Major -lt 6 -and $nativeCommand.CommandType -eq 'Application') {
|
|
1836
|
+
$start = New-Object System.Diagnostics.ProcessStartInfo
|
|
1837
|
+
$start.FileName = $nativeCommand.Source
|
|
1838
|
+
$start.WorkingDirectory = (Get-Location).ProviderPath
|
|
1839
|
+
$start.UseShellExecute = $false
|
|
1840
|
+
$start.CreateNoWindow = $true
|
|
1841
|
+
$start.Arguments = ($Arguments | ForEach-Object { ConvertTo-NativeArgument $_ }) -join ' '
|
|
1842
|
+
$child = [System.Diagnostics.Process]::Start($start)
|
|
1843
|
+
try { $child.WaitForExit(); $code = $child.ExitCode } finally { $child.Dispose() }
|
|
1844
|
+
} else {
|
|
1845
|
+
& $Command @Arguments
|
|
1846
|
+
$code = $LASTEXITCODE
|
|
1847
|
+
}
|
|
1848
|
+
$global:LASTEXITCODE = $code
|
|
1849
|
+
if ($code -ne 0) { throw "$Name failed (exit $code)." }
|
|
1150
1850
|
}
|
|
1851
|
+
$applying = $false
|
|
1151
1852
|
try {
|
|
1152
1853
|
foreach ($tool in @('ssh', 'scp')) { Get-Command $tool -ErrorAction Stop | Out-Null }
|
|
1153
|
-
Invoke-Step '1/3 Connect' 'ssh' ($options + @('-p', [string]$
|
|
1154
|
-
Invoke-Step '2/3 Upload' 'scp' ($options + @('-P', [string]$
|
|
1155
|
-
|
|
1156
|
-
|
|
1854
|
+
Invoke-Step '1/3 Connect' 'ssh' ($options + @('-p', [string]$port, $destination, "mkdir -p '$remote/incoming' && chmod 700 '$remote' '$remote/incoming'"))
|
|
1855
|
+
Invoke-Step '2/3 Upload' 'scp' ($options + @('-P', [string]$port, "./$bundle", "$($scpDestination):$remote/incoming/$bundle"))
|
|
1856
|
+
$applying = $true
|
|
1857
|
+
Invoke-Step '3/3 Apply and verify HTTP' 'ssh' ($options + @('-p', [string]$port, $destination, "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote'"))
|
|
1858
|
+
} catch {
|
|
1859
|
+
if ($applying) { [Console]::Error.WriteLine("[UNKNOWN] Remote execution may continue. Inspect $remote/result-<deploymentId>.json (ID in manifest.json), current and containers before retrying.") }
|
|
1860
|
+
Write-Error $_
|
|
1861
|
+
exit 1
|
|
1862
|
+
}
|
|
1157
1863
|
`;
|
|
1158
|
-
|
|
1159
|
-
return { sh: sh.replace(/@@([A-Z]+)@@/g, (_match, name) => values[name]), ps1 };
|
|
1864
|
+
return { sh, ps1, reader: createTargetReader() };
|
|
1160
1865
|
}
|
|
1161
1866
|
|
|
1162
1867
|
// src/container/deploy-bundle.ts
|
|
@@ -1247,7 +1952,8 @@ async function createDeploymentBundle(options) {
|
|
|
1247
1952
|
if (selected.includes("storage-init"))
|
|
1248
1953
|
put("initialize-storage", "");
|
|
1249
1954
|
put("apply.sh", createApplyScript({ deploymentId: options.deploymentId, projectName, environment, directory: `${config.remote.baseDir}/${projectName}`, runtime: config.remote.runtime, partial: options.partial, selected: selected.filter((name) => name !== "storage-init"), hostPort: config.expose.hostPort, healthPaths: options.healthPaths }));
|
|
1250
|
-
const
|
|
1955
|
+
const instructionParams = { file: basename(output), start: "apply.sh", images: [...references].join("\n"), compose: "compose.json" };
|
|
1956
|
+
const instructions = deployM("deploy_bundleInstructions", { params: instructionParams });
|
|
1251
1957
|
put("README.txt", `${deployM("deploy_bundleMetadata", { params: { version: artifact.appVersion, buildId: artifact.buildId, compose: "compose.json" } })}
|
|
1252
1958
|
${instructions}
|
|
1253
1959
|
`);
|
|
@@ -1261,15 +1967,38 @@ ${instructions}
|
|
|
1261
1967
|
throw new Error(tar.error.message);
|
|
1262
1968
|
chmodSync(temporaryTar, 384);
|
|
1263
1969
|
renameSync(temporaryTar, output);
|
|
1264
|
-
const upload = createUploadScripts(
|
|
1970
|
+
const upload = createUploadScripts();
|
|
1265
1971
|
const scripts = `${output.slice(0, -4)}-deploy`;
|
|
1266
1972
|
mkdirSync(scripts, { recursive: true, mode: 448 });
|
|
1267
|
-
writeFileSync(join(scripts, "deploy.sh"), upload.sh
|
|
1268
|
-
writeFileSync(join(scripts, "deploy.ps1"), upload.ps1
|
|
1269
|
-
writeFileSync(join(scripts, "
|
|
1973
|
+
writeFileSync(join(scripts, "deploy.sh"), upload.sh, { mode: 448 });
|
|
1974
|
+
writeFileSync(join(scripts, "deploy.ps1"), upload.ps1);
|
|
1975
|
+
writeFileSync(join(scripts, "read-target.awk"), upload.reader);
|
|
1976
|
+
const target = {
|
|
1977
|
+
host: config.ssh?.host ?? "SERVER_HOST",
|
|
1978
|
+
port: config.ssh?.port ?? 22,
|
|
1979
|
+
username: config.ssh?.username ?? "deploy",
|
|
1980
|
+
identityFile: config.ssh?.identityFile ?? "",
|
|
1981
|
+
knownHostsFile: config.ssh?.knownHostsFile ?? "",
|
|
1982
|
+
strictHostKeyChecking: config.ssh?.strictHostKeyChecking ?? true,
|
|
1983
|
+
connectTimeoutSeconds: config.ssh?.connectTimeoutSeconds ?? 10,
|
|
1984
|
+
directory: `${config.remote.baseDir}/${projectName}`,
|
|
1985
|
+
bundle: basename(output)
|
|
1986
|
+
};
|
|
1987
|
+
const comments = {
|
|
1988
|
+
host: deployM("deploy_targetHost"),
|
|
1989
|
+
port: deployM("deploy_targetPort"),
|
|
1990
|
+
username: deployM("deploy_targetUsername"),
|
|
1991
|
+
identityFile: deployM("deploy_targetIdentityFile"),
|
|
1992
|
+
knownHostsFile: deployM("deploy_targetKnownHostsFile"),
|
|
1993
|
+
strictHostKeyChecking: deployM("deploy_targetStrictHostKeyChecking"),
|
|
1994
|
+
connectTimeoutSeconds: deployM("deploy_targetConnectTimeoutSeconds"),
|
|
1995
|
+
directory: deployM("deploy_targetDirectory"),
|
|
1996
|
+
bundle: deployM("deploy_targetBundle")
|
|
1997
|
+
};
|
|
1998
|
+
writeFileSync(join(scripts, "deploy-target.json"), `${JSON.stringify({ ...target, _comments: comments }, null, 2)}
|
|
1270
1999
|
`);
|
|
1271
2000
|
writeFileSync(join(scripts, "README.txt"), instructions);
|
|
1272
|
-
return { bundlePath: output, instructions: `${
|
|
2001
|
+
return { bundlePath: output, instructions: `${deployM("deploy_bundleInstructions", { params: { ...instructionParams, file: output } })}
|
|
1273
2002
|
${scripts}`, startName: "apply.sh" };
|
|
1274
2003
|
} finally {
|
|
1275
2004
|
rmSync(staging, { recursive: true, force: true });
|
|
@@ -1633,6 +2362,7 @@ function generateWebProxyConfig(port, apiPrefix, webPort = 8080) {
|
|
|
1633
2362
|
listen ${webPort};
|
|
1634
2363
|
server_name _;
|
|
1635
2364
|
root /usr/share/nginx/html;
|
|
2365
|
+
large_client_header_buffers 4 32k;
|
|
1636
2366
|
location / {
|
|
1637
2367
|
try_files $uri $uri/ /index.html;
|
|
1638
2368
|
}
|
|
@@ -1650,11 +2380,15 @@ function generateWebProxyConfig(port, apiPrefix, webPort = 8080) {
|
|
|
1650
2380
|
proxy_set_header Host $http_host;
|
|
1651
2381
|
}
|
|
1652
2382
|
location ${apiPrefix.replace(/\/$/, "")}/ {
|
|
2383
|
+
client_max_body_size 2129920;
|
|
1653
2384
|
proxy_pass http://app:${port};
|
|
1654
2385
|
proxy_http_version 1.1;
|
|
1655
2386
|
proxy_set_header Host $http_host;
|
|
1656
2387
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
2388
|
+
proxy_request_buffering off;
|
|
1657
2389
|
proxy_buffering off;
|
|
2390
|
+
proxy_buffer_size 32k;
|
|
2391
|
+
proxy_buffers 8 32k;
|
|
1658
2392
|
}
|
|
1659
2393
|
}
|
|
1660
2394
|
`;
|
|
@@ -1679,8 +2413,10 @@ function buildConnectOptions(config) {
|
|
|
1679
2413
|
const args = ["-o", "BatchMode=yes"];
|
|
1680
2414
|
args.push("-o", `ConnectTimeout=${config.connectTimeoutSeconds}`);
|
|
1681
2415
|
args.push("-o", `StrictHostKeyChecking=${config.strictHostKeyChecking ? "accept-new" : "no"}`);
|
|
1682
|
-
if (config.knownHostsFile)
|
|
1683
|
-
|
|
2416
|
+
if (config.knownHostsFile) {
|
|
2417
|
+
const path = config.knownHostsFile.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
2418
|
+
args.push("-o", `UserKnownHostsFile="${path}"`);
|
|
2419
|
+
}
|
|
1684
2420
|
if (config.identityFile) {
|
|
1685
2421
|
args.push("-i", config.identityFile);
|
|
1686
2422
|
args.push("-o", "IdentitiesOnly=yes");
|
|
@@ -1707,21 +2443,23 @@ function buildScpUploadArgs(config, localPath, remotePath) {
|
|
|
1707
2443
|
}
|
|
1708
2444
|
function createSshExecutor(config) {
|
|
1709
2445
|
const exec = async (remoteCommand, options) => {
|
|
1710
|
-
|
|
2446
|
+
const result = await runCommand("ssh", buildSshExecArgs(config, remoteCommand), {
|
|
1711
2447
|
timeoutMs: options?.timeoutMs,
|
|
2448
|
+
terminationTimeoutMs: options?.terminationTimeoutMs,
|
|
2449
|
+
maxCaptureChars: options?.maxCaptureChars,
|
|
1712
2450
|
secrets: options?.secrets,
|
|
1713
2451
|
onOutput: options?.onOutput
|
|
1714
2452
|
});
|
|
2453
|
+
if (result.success)
|
|
2454
|
+
return result;
|
|
2455
|
+
const state = getDeployCommandState(result.error);
|
|
2456
|
+
return err({ ...result.error, suggestion: deployM("deploy_remoteUnknown"), ext: { ...result.error.ext, commandExecution: { ...state, reason: state?.reason ?? "exit", localTermination: state?.localTermination ?? "unknown", remoteExecution: "unknown" } } });
|
|
1715
2457
|
};
|
|
1716
2458
|
const upload = async (localPath, remotePath) => {
|
|
1717
2459
|
const args = buildScpUploadArgs(config, localPath, remotePath);
|
|
1718
|
-
const result = await runCommand("scp", args, {});
|
|
2460
|
+
const result = await runCommand("scp", args, { timeoutMs: 6e5 });
|
|
1719
2461
|
if (!result.success) {
|
|
1720
|
-
return err(
|
|
1721
|
-
HaiDeployError.IMAGE_TRANSFER_FAILED,
|
|
1722
|
-
deployM("deploy_imageTransferFailed", { params: { error: result.error.message } }),
|
|
1723
|
-
result.error
|
|
1724
|
-
);
|
|
2462
|
+
return err({ ...HaiDeployError.IMAGE_TRANSFER_FAILED, message: deployM("deploy_imageTransferFailed", { params: { error: result.error.message } }), ext: result.error.ext });
|
|
1725
2463
|
}
|
|
1726
2464
|
return ok(void 0);
|
|
1727
2465
|
};
|
|
@@ -1729,11 +2467,7 @@ function createSshExecutor(config) {
|
|
|
1729
2467
|
logger4.debug("SSH preflight", { host: config.host, port: config.port });
|
|
1730
2468
|
const result = await exec("true", { timeoutMs: 2e4 });
|
|
1731
2469
|
if (!result.success) {
|
|
1732
|
-
return err(
|
|
1733
|
-
HaiDeployError.REMOTE_CONNECT_FAILED,
|
|
1734
|
-
deployM("deploy_remoteConnectFailed", { params: { host: config.host, error: result.error.message } }),
|
|
1735
|
-
result.error
|
|
1736
|
-
);
|
|
2470
|
+
return err({ ...HaiDeployError.REMOTE_CONNECT_FAILED, message: deployM("deploy_remoteConnectFailed", { params: { host: config.host, error: result.error.message } }), ext: result.error.ext });
|
|
1737
2471
|
}
|
|
1738
2472
|
return ok(void 0);
|
|
1739
2473
|
};
|
|
@@ -1744,6 +2478,12 @@ function createSshExecutor(config) {
|
|
|
1744
2478
|
var logger5 = core.logger.child({ module: "deploy", scope: "provider-docker-ssh" });
|
|
1745
2479
|
var HEALTH_POLL_INTERVAL = 3e3;
|
|
1746
2480
|
var HEALTH_MAX_ATTEMPTS = 40;
|
|
2481
|
+
var ServerResultSchema = z.object({
|
|
2482
|
+
deploymentId: z.string(),
|
|
2483
|
+
stage: z.enum(["preflight", "images", "dependencies", "start", "health", "complete"]),
|
|
2484
|
+
exitCode: z.number().int().min(0).max(255),
|
|
2485
|
+
durationSeconds: z.number().int().nonnegative()
|
|
2486
|
+
});
|
|
1747
2487
|
function makeDeploymentId() {
|
|
1748
2488
|
const now = /* @__PURE__ */ new Date();
|
|
1749
2489
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -1791,7 +2531,7 @@ function createDockerSshProvider(config, container) {
|
|
|
1791
2531
|
const { appDir, projectName, scan, provisionEnv, appEnv, options } = context;
|
|
1792
2532
|
const deploymentId = makeDeploymentId();
|
|
1793
2533
|
const hostPort = config.expose.hostPort;
|
|
1794
|
-
const artifactDir = join(appDir, ".hai", "deploy", options?.environment ?? "prod");
|
|
2534
|
+
const artifactDir = join(resolveProjectDirectory(appDir), ".hai", "deploy", options?.environment ?? "prod");
|
|
1795
2535
|
mkdirSync(artifactDir, { recursive: true, mode: 448 });
|
|
1796
2536
|
chmodSync(artifactDir, 448);
|
|
1797
2537
|
const artifact = context.artifact;
|
|
@@ -1900,18 +2640,43 @@ function createDockerSshProvider(config, container) {
|
|
|
1900
2640
|
const started = await ssh.exec(
|
|
1901
2641
|
`mkdir -p ${shellArgument(`${remoteTar}.contents`)} && chmod 600 ${shellArgument(remoteTar)} && tar -xf ${shellArgument(remoteTar)} -C ${shellArgument(`${remoteTar}.contents`)} && sh ${shellArgument(`${remoteTar}.contents/${bundle.startName}`)} ${shellArgument(remoteDir)}`,
|
|
1902
2642
|
{ timeoutMs: 6e5, secrets: sidecar.secrets, onOutput: (output) => {
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2643
|
+
const parts = output.split("\n");
|
|
2644
|
+
for (let index = 0; index < parts.length; index++) {
|
|
2645
|
+
pendingOutput += parts[index].slice(0, 512 - pendingOutput.length);
|
|
2646
|
+
if (index === parts.length - 1)
|
|
2647
|
+
break;
|
|
2648
|
+
const line = pendingOutput;
|
|
2649
|
+
pendingOutput = "";
|
|
1907
2650
|
const stage = /^\[(preflight|images|dependencies|start|health|complete)\]/.exec(line)?.[1];
|
|
1908
2651
|
if (stage)
|
|
1909
2652
|
options?.onProgress?.(stage);
|
|
1910
2653
|
}
|
|
1911
2654
|
} }
|
|
1912
2655
|
);
|
|
1913
|
-
if (!started.success)
|
|
1914
|
-
|
|
2656
|
+
if (!started.success) {
|
|
2657
|
+
const resultFile = `${remoteDir}/result-${deploymentId}.json`;
|
|
2658
|
+
const state = getDeployCommandState(started.error);
|
|
2659
|
+
const checked = await ssh.exec(`if [ -f ${shellArgument(resultFile)} ]; then cat -- ${shellArgument(resultFile)}; fi`, { timeoutMs: 2e4, maxCaptureChars: 8192 });
|
|
2660
|
+
let terminal;
|
|
2661
|
+
if (checked.success && !checked.data.stdoutTruncated) {
|
|
2662
|
+
try {
|
|
2663
|
+
const parsed = ServerResultSchema.safeParse(JSON.parse(checked.data.stdout));
|
|
2664
|
+
if (parsed.success && parsed.data.deploymentId === deploymentId)
|
|
2665
|
+
terminal = parsed.data;
|
|
2666
|
+
} catch {
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
return err({ ...started.error, message: `${started.error.message}
|
|
2670
|
+
${terminal ? deployM("deploy_remoteFinished", { params: { stage: terminal.stage, code: String(terminal.exitCode) } }) : deployM("deploy_remoteUnknown")}`, suggestion: terminal ? deployM("deploy_remoteFinished", { params: { stage: terminal.stage, code: String(terminal.exitCode) } }) : deployM("deploy_remoteUnknown"), ext: { ...started.error.ext, commandExecution: {
|
|
2671
|
+
...state,
|
|
2672
|
+
reason: state?.reason ?? "exit",
|
|
2673
|
+
localTermination: state?.localTermination ?? "unknown",
|
|
2674
|
+
remoteExecution: terminal ? "finished" : "unknown",
|
|
2675
|
+
deploymentId,
|
|
2676
|
+
resultFile,
|
|
2677
|
+
...terminal ? { remoteExitCode: terminal.exitCode, remoteStage: terminal.stage } : {}
|
|
2678
|
+
} } });
|
|
2679
|
+
}
|
|
1915
2680
|
logger5.info("Running health check", { url });
|
|
1916
2681
|
const healthy = await healthCheck(`${url}${!hasWeb && scan.isSvelteKit ? "/" : "/health"}`) && (!hasWeb || await healthCheck(`${url}/_hai/web-health`) && await healthCheck(`${url}/`));
|
|
1917
2682
|
if (!healthy) {
|
|
@@ -1936,9 +2701,14 @@ function createDockerSshProvider(config, container) {
|
|
|
1936
2701
|
try {
|
|
1937
2702
|
return await deploy2(context);
|
|
1938
2703
|
} catch (error) {
|
|
1939
|
-
logger5.error("Deployment bundle failed", { error });
|
|
1940
2704
|
const reason = error instanceof Error ? error.message : String(error);
|
|
1941
|
-
|
|
2705
|
+
let safeReason = "Deployment bundle failed";
|
|
2706
|
+
try {
|
|
2707
|
+
safeReason = maskSecrets(reason, Object.values(context.appEnv ?? {}).filter(Boolean));
|
|
2708
|
+
} catch {
|
|
2709
|
+
}
|
|
2710
|
+
logger5.error("Deployment bundle failed", { reason: safeReason });
|
|
2711
|
+
return err(HaiDeployError.DEPLOY_FAILED, deployM("deploy_bundleFailed", { params: { reason: safeReason } }));
|
|
1942
2712
|
}
|
|
1943
2713
|
};
|
|
1944
2714
|
return { name: "docker-ssh", deploy: deploySafely };
|
|
@@ -3064,13 +3834,15 @@ var DEPLOY_MIDDLEWARE = {
|
|
|
3064
3834
|
{ backend: "api", label: "HTTP notification API", purpose: "deploy_purposeEmail", fields: [required("url", "", "url"), field("method", "POST"), field("headers"), field("timeout", "10000", "number")] }
|
|
3065
3835
|
],
|
|
3066
3836
|
sms: [
|
|
3067
|
-
{ backend: "
|
|
3837
|
+
{ backend: "console", label: "Console", purpose: "deploy_purposeConsole", fields: [] },
|
|
3838
|
+
{ backend: "aliyun-sms", label: "Aliyun SMS", purpose: "deploy_purposeSms", provisioner: "aliyun", fields: [required("accessKeyId"), { ...required("accessKeySecret"), secret: true }, required("signName"), field("endpoint", "dysmsapi.aliyuncs.com")] },
|
|
3839
|
+
{ backend: "api", label: "HTTP notification API", purpose: "deploy_purposeSms", fields: [required("url", "", "url"), field("method", "POST"), field("headers"), field("timeout", "10000", "number")] }
|
|
3068
3840
|
]
|
|
3069
3841
|
};
|
|
3070
3842
|
function describeMiddlewareBackend(backend) {
|
|
3071
3843
|
return deployM(backend.purpose);
|
|
3072
3844
|
}
|
|
3073
3845
|
|
|
3074
|
-
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, deploy, describeMiddlewareBackend, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, readDeploymentArtifact, saveDeploymentEnvironment };
|
|
3846
|
+
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, readDeploymentArtifact, saveDeploymentEnvironment };
|
|
3075
3847
|
//# sourceMappingURL=index.js.map
|
|
3076
3848
|
//# sourceMappingURL=index.js.map
|