rainskills 0.1.0 → 0.1.2
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.md +156 -420
- package/SKILL.md +37 -7
- package/agents/openai.yaml +2 -2
- package/bin/rainskills-tools.js +712 -0
- package/bin/rainskills.js +1109 -4
- package/install.sh +536 -63
- package/marketplace/rainskills/.claude-plugin/plugin.json +2 -2
- package/marketplace/rainskills/.codex-plugin/plugin.json +5 -5
- package/marketplace/rainskills/skills/rainskills/SKILL.md +38 -8
- package/package.json +14 -5
- package/pi/rainskills-mcp.ts +214 -1850
- package/rainbond-app-assistant/SKILL.md +123 -49
- package/rainbond-app-assistant/evals/01-linked-topology-missing.response.md +3 -3
- package/rainbond-app-assistant/evals/02-linked-topology-building.response.md +3 -3
- package/rainbond-app-assistant/evals/03-runtime-healthy-delivery-unknown.response.md +3 -3
- package/rainbond-app-assistant/evals/04-delivered-stop-without-promotion.expected.yaml +1 -1
- package/rainbond-app-assistant/evals/04-delivered-stop-without-promotion.response.md +1 -1
- package/rainbond-app-assistant/evals/05-delivered-explicit-dev-to-test.response.md +3 -3
- package/rainbond-app-assistant/evals/06-manual-validation-stop.expected.yaml +1 -1
- package/rainbond-app-assistant/evals/06-manual-validation-stop.response.md +3 -3
- package/rainbond-app-assistant/evals/07-cluster-capacity-blocked.response.md +4 -4
- package/rainbond-app-assistant/evals/08-code-build-handoff-stop.response.md +2 -2
- package/rainbond-app-assistant/evals/09-source-backed-failure-no-fallback.response.md +2 -2
- package/rainbond-app-assistant/evals/10-identity-ambiguous-stop.response.md +3 -3
- package/rainbond-app-assistant/evals/12-complex-suite-needs-descriptor-stop.response.md +2 -2
- package/rainbond-app-assistant/evals/13-explicit-git-url-locks-source.response.md +2 -2
- package/rainbond-app-assistant/evals/14-strategy-switch-cleans-residue.response.md +2 -2
- package/rainbond-app-assistant/evals/15-turn-discipline-reuse-alias.response.md +2 -2
- package/rainbond-app-assistant/references/operational-reference.md +77 -0
- package/rainbond-app-assistant/references/output-contract.md +454 -0
- package/rainbond-app-assistant/references/product-object-model.md +1558 -0
- package/rainbond-app-assistant/references/workflow-rules.md +343 -0
- package/rainbond-app-assistant/schemas/app-assistant-result.schema.yaml +6 -1
- package/rainbond-app-version-assistant/SKILL.md +57 -18
- package/rainbond-delivery-verifier/SKILL.md +86 -13
- package/rainbond-env-sync/SKILL.md +52 -11
- package/rainbond-fullstack-bootstrap/SKILL.md +79 -11
- package/rainbond-fullstack-bootstrap/evals/01-reuse-only.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/02-deferred-dependency.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/03-capacity-blocked.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/04-template-skip.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/05-source-ambiguity-stop.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/06-external-artifact-unreachable.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/07-provider-connection-contract.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/08-explicit-dependency-after-manual-components.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/09-inferred-provider-consumer-gate.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/10-compose-service-name-not-hostname.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/11-reverse-proxy-not-dropped.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/12-optional-services-disclose-not-deploy.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/13-env-reference-dependency-completeness.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/14-client-package-upload.expected.yaml +1 -1
- package/rainbond-fullstack-bootstrap/evals/14-client-package-upload.response.md +3 -3
- package/rainbond-fullstack-bootstrap/evals/15-unfinished-dockerfile-recovery.expected.yaml +25 -0
- package/rainbond-fullstack-bootstrap/evals/15-unfinished-dockerfile-recovery.response.md +44 -0
- package/rainbond-fullstack-bootstrap/evals/16-complete-cnb-recreate-confirmed.expected.yaml +25 -0
- package/rainbond-fullstack-bootstrap/evals/16-complete-cnb-recreate-confirmed.response.md +44 -0
- package/rainbond-fullstack-bootstrap/modules/10-context-loading.md +8 -8
- package/rainbond-fullstack-bootstrap/modules/30-creation-rules.md +10 -10
- package/rainbond-fullstack-bootstrap/modules/40-source-and-package-rules.md +13 -9
- package/rainbond-fullstack-bootstrap/modules/50-workflow-and-convergence.md +6 -6
- package/rainbond-fullstack-bootstrap/modules/60-verification-and-handoffs.md +4 -4
- package/rainbond-fullstack-bootstrap/modules/70-output-contract.md +3 -3
- package/rainbond-fullstack-bootstrap/references/quick-reference.md +6 -6
- package/rainbond-fullstack-bootstrap/references/source-build-parameter-guide.md +6 -6
- package/rainbond-fullstack-bootstrap/references/sql-init-recipe.md +3 -3
- package/rainbond-fullstack-bootstrap/schemas/bootstrap-result.schema.yaml +3 -2
- package/rainbond-fullstack-bootstrap/scripts/upload_local_package.py +1 -1
- package/rainbond-fullstack-bootstrap/scripts/validate_bootstrap_output.py +2 -2
- package/rainbond-fullstack-troubleshooter/SKILL.md +103 -13
- package/rainbond-fullstack-troubleshooter/evals/10-config-file-configmap-missing.expected.yaml +21 -0
- package/rainbond-fullstack-troubleshooter/evals/10-config-file-configmap-missing.response.md +51 -0
- package/rainbond-fullstack-troubleshooter/references/operational-reference.md +70 -0
- package/rainbond-fullstack-troubleshooter/references/output-contract.md +259 -0
- package/rainbond-fullstack-troubleshooter/references/root-cause-rules.md +210 -0
- package/rainbond-fullstack-troubleshooter/schemas/troubleshoot-result.schema.yaml +3 -1
- package/rainbond-fullstack-troubleshooter/scripts/validate_troubleshoot_output.py +22 -1
- package/rainbond-platform-installer/SKILL.md +73 -9
- package/rainbond-platform-installer/agents/openai.yaml +2 -2
- package/rainbond-platform-installer/references/installation-policy.json +35 -1
- package/rainbond-platform-installer/references/installation-policy.md +33 -8
- package/rainbond-platform-installer/references/troubleshooting.md +3 -2
- package/rainbond-platform-installer/scripts/auto-update.js +495 -0
- package/rainbond-platform-installer/scripts/console-origin.js +159 -0
- package/rainbond-platform-installer/scripts/environment-credentials.js +225 -0
- package/rainbond-platform-installer/scripts/environment-registry.js +349 -0
- package/rainbond-platform-installer/scripts/existing-kubernetes-installer.js +1370 -0
- package/rainbond-platform-installer/scripts/host-cluster-installer.js +2143 -0
- package/rainbond-platform-installer/scripts/local-runtime-commands.js +180 -0
- package/rainbond-platform-installer/scripts/local-runtime.js +18 -0
- package/rainbond-platform-installer/scripts/mcp-router.js +212 -0
- package/rainbond-platform-installer/scripts/mcp-server.js +79 -0
- package/rainbond-platform-installer/scripts/platform-installer.js +694 -255
- package/rainbond-platform-installer/scripts/platform-routing.js +326 -0
- package/rainbond-platform-installer/scripts/runtime-credentials.js +163 -0
- package/rainbond-platform-installer/scripts/runtime-intents.js +251 -0
- package/rainbond-platform-installer/scripts/runtime-operations.js +383 -0
- package/rainbond-platform-installer/scripts/runtime-state.js +656 -0
- package/rainbond-platform-installer/scripts/secure-state.js +59 -13
- package/rainbond-platform-installer/scripts/ssh-key-setup.js +242 -0
- package/rainbond-platform-installer/scripts/telemetry.js +244 -0
- package/rainbond-platform-installer/scripts/user-message.js +125 -0
- package/rainbond-platform-installer/scripts/windows-auth.js +1 -1
- package/rainbond-platform-installer/scripts/windows-client-config.js +101 -22
- package/rainbond-platform-installer/scripts/windows-onboarding.js +179 -83
- package/rainbond-platform-installer/scripts/windows-platform.js +3 -3
- package/rainbond-platform-installer/scripts/windows-platform.ps1 +264 -28
- package/rainbond-platform-installer/scripts/windows-read-user-environment.ps1 +16 -0
- package/rainbond-platform-query/SKILL.md +88 -0
- package/rainbond-platform-query/agents/openai.yaml +4 -0
- package/rainbond-platform-query/evals/01-current-enterprise.expected.yaml +10 -0
- package/rainbond-project-init/SKILL.md +68 -9
- package/rainbond-project-init/references/manifest-rules.md +487 -0
- package/rainbond-project-init/references/operational-reference.md +74 -0
- package/rainbond-project-init/references/output-contract.md +272 -0
- package/rainbond-project-init/references/workflow-and-verification.md +85 -0
- package/rainbond-template-installer/SKILL.md +80 -16
package/bin/rainskills.js
CHANGED
|
@@ -1,10 +1,972 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const path = require("node:path");
|
|
4
|
+
const crypto = require("node:crypto");
|
|
5
|
+
const os = require("node:os");
|
|
4
6
|
const { spawn } = require("node:child_process");
|
|
7
|
+
const { isDeepStrictEqual } = require("node:util");
|
|
5
8
|
const {
|
|
6
9
|
detectControlEnvironment,
|
|
7
10
|
} = require("../rainbond-platform-installer/scripts/control-environment.js");
|
|
11
|
+
const {
|
|
12
|
+
normalizeConsoleOrigin,
|
|
13
|
+
} = require("../rainbond-platform-installer/scripts/console-origin.js");
|
|
14
|
+
const {
|
|
15
|
+
renderCatalogUserMessage,
|
|
16
|
+
} = require("../rainbond-platform-installer/scripts/user-message.js");
|
|
17
|
+
|
|
18
|
+
const AUTO_UPDATE_FALLBACK_EXIT_CODE = 75;
|
|
19
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
20
|
+
const RUNTIME_CHILD_ENVIRONMENT_KEYS = Object.freeze([
|
|
21
|
+
"HOME", "PATH", "SHELL", "TMPDIR", "TEMP", "TMP", "USER", "LOGNAME", "LANG", "LC_ALL",
|
|
22
|
+
"TERM", "COLORTERM", "XDG_CONFIG_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME",
|
|
23
|
+
"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy",
|
|
24
|
+
"SSL_CERT_FILE", "SSL_CERT_DIR", "CURL_CA_BUNDLE", "NODE_EXTRA_CA_CERTS",
|
|
25
|
+
"RAINBOND_LOGIN_TIMEOUT", "RAINSKILLS_NO_BROWSER",
|
|
26
|
+
"RAINSKILLS_INSTALL_ATTEMPT_ID", "RAINSKILLS_PACKAGE_VERSION",
|
|
27
|
+
"RAINSKILLS_TELEMETRY_OPERATION_ID", "RAINSKILLS_TELEMETRY_INSTALLATION_ID",
|
|
28
|
+
"RAINSKILLS_TELEMETRY_TARGET", "RAINSKILLS_TELEMETRY_CONTROL_MODE",
|
|
29
|
+
"RAINSKILLS_TELEMETRY_CLIENT", "RAINSKILLS_TELEMETRY_DIR",
|
|
30
|
+
"RAINSKILLS_INSTALL_REPORT_URL", "RAINSKILLS_LIFECYCLE_REPORT_URL",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
function runtimeChildEnvironment(source = process.env, extra = {}, expectedOrigin = "") {
|
|
34
|
+
const environment = {};
|
|
35
|
+
for (const key of RUNTIME_CHILD_ENVIRONMENT_KEYS) {
|
|
36
|
+
if (typeof source[key] === "string") environment[key] = source[key];
|
|
37
|
+
}
|
|
38
|
+
for (const key of Object.keys(extra)) {
|
|
39
|
+
if (!["RAINSKILLS_RUNTIME_CONNECT_COMPLETION", "RAINSKILLS_RUNTIME_OPERATION_ID", "RAINSKILLS_RUNTIME_STATE_SCOPE"].includes(key)) {
|
|
40
|
+
throw new Error("runtime child environment 包含未知字段");
|
|
41
|
+
}
|
|
42
|
+
environment[key] = extra[key];
|
|
43
|
+
}
|
|
44
|
+
if (
|
|
45
|
+
expectedOrigin
|
|
46
|
+
&& typeof source.RAINBOND_JWT === "string"
|
|
47
|
+
&& /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(source.RAINBOND_JWT)
|
|
48
|
+
&& typeof source.RAINBOND_URL === "string"
|
|
49
|
+
) {
|
|
50
|
+
try {
|
|
51
|
+
if (normalizeConsoleOrigin(source.RAINBOND_URL) === normalizeConsoleOrigin(expectedOrigin)) {
|
|
52
|
+
environment.RAINBOND_JWT = source.RAINBOND_JWT;
|
|
53
|
+
environment.RAINBOND_URL = normalizeConsoleOrigin(expectedOrigin);
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// An inherited credential is optional. Invalid or origin-drifted pairs are discarded.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return environment;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function requireFixedValue(args, index, option) {
|
|
63
|
+
const value = args[index + 1];
|
|
64
|
+
if (typeof value !== "string" || !value || value.startsWith("--")) {
|
|
65
|
+
throw new Error(`${option} 需要一个值`);
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseRuntimeAssertConnectArgs(args) {
|
|
71
|
+
if (
|
|
72
|
+
args.length !== 10
|
|
73
|
+
|| args[0] !== "runtime"
|
|
74
|
+
|| args[1] !== "assert-connect"
|
|
75
|
+
|| args[2] !== "--onboarding-id"
|
|
76
|
+
|| args[4] !== "--target"
|
|
77
|
+
|| args[6] !== "--environment-kind"
|
|
78
|
+
|| args[8] !== "--console-origin"
|
|
79
|
+
) {
|
|
80
|
+
throw new Error("runtime connect 内部门禁参数无效");
|
|
81
|
+
}
|
|
82
|
+
if (!UUID_PATTERN.test(args[3] || "")) throw new Error("runtime connect operation 无效");
|
|
83
|
+
if (!["codex", "claude", "all"].includes(args[5])) throw new Error("runtime connect target 无效");
|
|
84
|
+
if (!["saas", "private"].includes(args[7])) throw new Error("runtime connect environment kind 无效");
|
|
85
|
+
return {
|
|
86
|
+
operationId: args[3],
|
|
87
|
+
targetClient: args[5],
|
|
88
|
+
environmentKind: args[7],
|
|
89
|
+
consoleOrigin: normalizeConsoleOrigin(args[9]),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function assertConnectingState(current, expected) {
|
|
94
|
+
if (
|
|
95
|
+
!current
|
|
96
|
+
|| current.state !== "connecting"
|
|
97
|
+
|| current.operation_id !== expected.operationId
|
|
98
|
+
|| current.target_client !== expected.targetClient
|
|
99
|
+
|| current.environment_kind !== expected.environmentKind
|
|
100
|
+
|| current.console_origin !== expected.consoleOrigin
|
|
101
|
+
) {
|
|
102
|
+
throw new Error("runtime connect 内部门禁与 protected connecting state 不匹配");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function assertExactConnectedState(current, expected) {
|
|
107
|
+
if (
|
|
108
|
+
!current
|
|
109
|
+
|| current.state !== "connected"
|
|
110
|
+
|| current.operation_id !== expected.operation_id
|
|
111
|
+
|| current.target_client !== expected.target_client
|
|
112
|
+
|| current.environment_kind !== expected.environment_kind
|
|
113
|
+
|| current.console_origin !== expected.console_origin
|
|
114
|
+
|| !isDeepStrictEqual(current.intent, expected.intent)
|
|
115
|
+
) {
|
|
116
|
+
throw new Error("runtime reconnect 完成状态与 protected connection 不匹配");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseRuntimeConnectArgs(args) {
|
|
121
|
+
if (args[0] !== "runtime" || args[1] !== "connect") {
|
|
122
|
+
throw new Error("不是 runtime connect 命令");
|
|
123
|
+
}
|
|
124
|
+
const targetClient = args[2];
|
|
125
|
+
if (!["codex", "claude", "all"].includes(targetClient)) {
|
|
126
|
+
throw new Error("runtime connect 需要固定目标 codex、claude 或 all");
|
|
127
|
+
}
|
|
128
|
+
let environmentChoice = "";
|
|
129
|
+
let rainbondUrl = "";
|
|
130
|
+
let allowInsecureHttp = false;
|
|
131
|
+
let onboardingId = "";
|
|
132
|
+
let intentInput = "";
|
|
133
|
+
let privateLocation = "";
|
|
134
|
+
for (let index = 3; index < args.length; index += 1) {
|
|
135
|
+
const argument = args[index];
|
|
136
|
+
if (argument === "--saas" || argument === "--install-private") {
|
|
137
|
+
if (environmentChoice) throw new Error("runtime connect 的环境选择必须互斥");
|
|
138
|
+
environmentChoice = argument === "--saas" ? "saas" : "install-private";
|
|
139
|
+
} else if (argument === "--rainbond-url") {
|
|
140
|
+
if (environmentChoice) throw new Error("runtime connect 的环境选择必须互斥");
|
|
141
|
+
rainbondUrl = requireFixedValue(args, index, argument);
|
|
142
|
+
environmentChoice = "private-existing";
|
|
143
|
+
index += 1;
|
|
144
|
+
} else if (argument === "--allow-insecure-http") {
|
|
145
|
+
allowInsecureHttp = true;
|
|
146
|
+
} else if (argument === "--location") {
|
|
147
|
+
privateLocation = requireFixedValue(args, index, argument);
|
|
148
|
+
if (!["local", "server"].includes(privateLocation)) {
|
|
149
|
+
throw new Error("runtime connect 私有部署位置只支持 local 或 server");
|
|
150
|
+
}
|
|
151
|
+
index += 1;
|
|
152
|
+
} else if (argument === "--onboarding-id") {
|
|
153
|
+
onboardingId = requireFixedValue(args, index, argument);
|
|
154
|
+
index += 1;
|
|
155
|
+
} else if (argument === "--intent-json") {
|
|
156
|
+
intentInput = requireFixedValue(args, index, argument);
|
|
157
|
+
index += 1;
|
|
158
|
+
} else {
|
|
159
|
+
throw new Error("runtime connect 包含未知参数");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!environmentChoice) throw new Error("runtime connect 必须选择一个应用运行环境");
|
|
163
|
+
if (!intentInput || intentInput.length > 16384) throw new Error("runtime connect 需要受限的 intent JSON");
|
|
164
|
+
let rawIntent;
|
|
165
|
+
try {
|
|
166
|
+
rawIntent = JSON.parse(intentInput);
|
|
167
|
+
} catch {
|
|
168
|
+
throw new Error("intent JSON 无效");
|
|
169
|
+
}
|
|
170
|
+
const { assertIntentCanInstallNewPlatform, validateIntent } = require(
|
|
171
|
+
"../rainbond-platform-installer/scripts/runtime-intents.js"
|
|
172
|
+
);
|
|
173
|
+
const intent = environmentChoice === "install-private"
|
|
174
|
+
? assertIntentCanInstallNewPlatform(rawIntent)
|
|
175
|
+
: validateIntent(rawIntent);
|
|
176
|
+
if (onboardingId && !UUID_PATTERN.test(onboardingId)) throw new Error("onboarding id 无效");
|
|
177
|
+
if (allowInsecureHttp && environmentChoice !== "private-existing") {
|
|
178
|
+
throw new Error("--allow-insecure-http 只适用于明确的私有 Console 地址");
|
|
179
|
+
}
|
|
180
|
+
if (privateLocation && environmentChoice !== "install-private") {
|
|
181
|
+
throw new Error("--location 只适用于 install-private 私有平台安装");
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
targetClient,
|
|
185
|
+
environmentChoice,
|
|
186
|
+
rainbondUrl,
|
|
187
|
+
allowInsecureHttp,
|
|
188
|
+
onboardingId,
|
|
189
|
+
intent,
|
|
190
|
+
...(privateLocation ? { privateLocation } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function runtimeConnectionInvocation(options, origin) {
|
|
195
|
+
const installerPath = path.resolve(__dirname, "..", "install.sh");
|
|
196
|
+
const args = [installerPath, "connect", options.targetClient];
|
|
197
|
+
if (options.environmentChoice === "saas") args.push("--saas");
|
|
198
|
+
else args.push("--self-hosted", "--rainbond-url", origin);
|
|
199
|
+
if (options.allowInsecureHttp) args.push("--allow-insecure-http");
|
|
200
|
+
return {
|
|
201
|
+
executable: "bash",
|
|
202
|
+
args,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function runtimeConnectRetryAction(options, origin, operationId) {
|
|
207
|
+
const argv = ["runtime", "connect", options.targetClient];
|
|
208
|
+
if (options.environmentChoice === "saas") argv.push("--saas");
|
|
209
|
+
else argv.push("--rainbond-url", origin);
|
|
210
|
+
if (options.allowInsecureHttp) argv.push("--allow-insecure-http");
|
|
211
|
+
argv.push("--onboarding-id", operationId, "--intent-json", JSON.stringify(options.intent));
|
|
212
|
+
return {
|
|
213
|
+
schema: "rainskills.next-action.v1",
|
|
214
|
+
action: "retry-runtime-connect",
|
|
215
|
+
onboarding_id: operationId,
|
|
216
|
+
argv,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function runtimeReconnectRetryAction(operationId) {
|
|
221
|
+
return {
|
|
222
|
+
schema: "rainskills.next-action.v1",
|
|
223
|
+
action: "retry-runtime-reconnect",
|
|
224
|
+
onboarding_id: operationId,
|
|
225
|
+
argv: ["runtime", "reconnect", "--onboarding-id", operationId],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function parseRuntimeFailureArgs(args) {
|
|
230
|
+
if (
|
|
231
|
+
args.length !== 8
|
|
232
|
+
|| args[0] !== "runtime"
|
|
233
|
+
|| args[1] !== "record-failure"
|
|
234
|
+
|| args[2] !== "--onboarding-id"
|
|
235
|
+
|| args[4] !== "--step"
|
|
236
|
+
|| args[6] !== "--reason"
|
|
237
|
+
) {
|
|
238
|
+
throw new Error("runtime record-failure 参数无效");
|
|
239
|
+
}
|
|
240
|
+
if (!UUID_PATTERN.test(args[3] || "")) throw new Error("runtime failure onboarding operation 无效");
|
|
241
|
+
if (!args[5] || args[5].startsWith("--")) throw new Error("runtime failure step 无效");
|
|
242
|
+
if (!["credential-expired", "permission-denied"].includes(args[7])) {
|
|
243
|
+
throw new Error("runtime failure reason 无效");
|
|
244
|
+
}
|
|
245
|
+
return { operationId: args[3], step: args[5], reason: args[7] };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parseRuntimeReconnectArgs(args) {
|
|
249
|
+
if (
|
|
250
|
+
args.length !== 4
|
|
251
|
+
|| args[0] !== "runtime"
|
|
252
|
+
|| args[1] !== "reconnect"
|
|
253
|
+
|| args[2] !== "--onboarding-id"
|
|
254
|
+
|| !UUID_PATTERN.test(args[3] || "")
|
|
255
|
+
) {
|
|
256
|
+
throw new Error("runtime reconnect 参数无效");
|
|
257
|
+
}
|
|
258
|
+
return { operationId: args[3] };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function createEnvironmentRuntimeServices() {
|
|
262
|
+
const { createEnvironmentRegistry } = require(
|
|
263
|
+
"../rainbond-platform-installer/scripts/environment-registry.js"
|
|
264
|
+
);
|
|
265
|
+
const { createRuntimeOperationStore } = require(
|
|
266
|
+
"../rainbond-platform-installer/scripts/runtime-operations.js"
|
|
267
|
+
);
|
|
268
|
+
const { createEnvironmentCredentialStore } = require(
|
|
269
|
+
"../rainbond-platform-installer/scripts/environment-credentials.js"
|
|
270
|
+
);
|
|
271
|
+
let operations;
|
|
272
|
+
const registry = createEnvironmentRegistry({
|
|
273
|
+
activeOperationIds: (environmentId) => operations?.activeOperationIds(environmentId) || [],
|
|
274
|
+
});
|
|
275
|
+
operations = createRuntimeOperationStore({ registry });
|
|
276
|
+
return {
|
|
277
|
+
environmentCredentialStore: createEnvironmentCredentialStore(),
|
|
278
|
+
environmentRegistry: registry,
|
|
279
|
+
operationStore: operations,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function parseEnvironmentMutationArgs(args) {
|
|
284
|
+
const action = args[1];
|
|
285
|
+
if (!new Set(["rename", "set-default", "remove"]).has(action)) {
|
|
286
|
+
throw new Error("environment action 无效");
|
|
287
|
+
}
|
|
288
|
+
if (args[2] !== "--environment-id" || !UUID_PATTERN.test(args[3] || "")) {
|
|
289
|
+
throw new Error("环境 ID 无效");
|
|
290
|
+
}
|
|
291
|
+
if (action === "rename") {
|
|
292
|
+
if (args.length !== 6 || args[4] !== "--name" || !args[5] || args[5].startsWith("--")) {
|
|
293
|
+
throw new Error("environment rename 参数无效");
|
|
294
|
+
}
|
|
295
|
+
return { action, environmentId: args[3], name: args[5] };
|
|
296
|
+
}
|
|
297
|
+
if (args.length !== 4) throw new Error(`environment ${action} 参数无效`);
|
|
298
|
+
return { action, environmentId: args[3] };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function parseOperationBeginArgs(args) {
|
|
302
|
+
if (args[0] !== "operation" || args[1] !== "begin") {
|
|
303
|
+
throw new Error("operation begin 参数无效");
|
|
304
|
+
}
|
|
305
|
+
let operationId = "";
|
|
306
|
+
let environmentId = "";
|
|
307
|
+
let intentInput = "";
|
|
308
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
309
|
+
const argument = args[index];
|
|
310
|
+
if (argument === "--operation-id") {
|
|
311
|
+
operationId = requireFixedValue(args, index, argument);
|
|
312
|
+
index += 1;
|
|
313
|
+
} else if (argument === "--environment-id") {
|
|
314
|
+
environmentId = requireFixedValue(args, index, argument);
|
|
315
|
+
index += 1;
|
|
316
|
+
} else if (argument === "--intent-json") {
|
|
317
|
+
intentInput = requireFixedValue(args, index, argument);
|
|
318
|
+
index += 1;
|
|
319
|
+
} else {
|
|
320
|
+
throw new Error("operation begin 包含未知参数");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (!UUID_PATTERN.test(operationId)) throw new Error("operation ID 无效");
|
|
324
|
+
if (environmentId && !UUID_PATTERN.test(environmentId)) throw new Error("环境 ID 无效");
|
|
325
|
+
if (!intentInput || intentInput.length > 16384) throw new Error("operation begin 缺少 intent JSON");
|
|
326
|
+
let intent;
|
|
327
|
+
try {
|
|
328
|
+
intent = JSON.parse(intentInput);
|
|
329
|
+
} catch {
|
|
330
|
+
throw new Error("operation begin intent JSON 无效");
|
|
331
|
+
}
|
|
332
|
+
return { operationId, environmentId: environmentId || undefined, intent };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function parseMcpServeArgs(args) {
|
|
336
|
+
if (
|
|
337
|
+
args.length !== 4
|
|
338
|
+
|| args[0] !== "mcp"
|
|
339
|
+
|| args[1] !== "serve"
|
|
340
|
+
|| args[2] !== "--client"
|
|
341
|
+
|| !["codex", "claude", "pi", "generic"].includes(args[3])
|
|
342
|
+
) {
|
|
343
|
+
throw new Error("mcp serve 参数无效");
|
|
344
|
+
}
|
|
345
|
+
return { client: args[3] };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function defaultMcpServerRunner({
|
|
349
|
+
environmentRegistry,
|
|
350
|
+
environmentCredentialStore,
|
|
351
|
+
operationStore,
|
|
352
|
+
}) {
|
|
353
|
+
const { createMcpRouter } = require(
|
|
354
|
+
"../rainbond-platform-installer/scripts/mcp-router.js"
|
|
355
|
+
);
|
|
356
|
+
const { serveStdio } = require(
|
|
357
|
+
"../rainbond-platform-installer/scripts/mcp-server.js"
|
|
358
|
+
);
|
|
359
|
+
return serveStdio({
|
|
360
|
+
router: createMcpRouter({
|
|
361
|
+
registry: environmentRegistry,
|
|
362
|
+
credentialStore: environmentCredentialStore,
|
|
363
|
+
operationStore,
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function runAttached(executable, args, { env = process.env } = {}) {
|
|
369
|
+
return new Promise((resolve, reject) => {
|
|
370
|
+
const child = spawn(executable, args, { env, stdio: "inherit" });
|
|
371
|
+
child.once("error", () => reject(new Error("无法启动 RainSkills 运行环境连接器")));
|
|
372
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function defaultConnectionRunner(invocation, {
|
|
377
|
+
completeWithCredential,
|
|
378
|
+
control,
|
|
379
|
+
options,
|
|
380
|
+
origin,
|
|
381
|
+
operationId,
|
|
382
|
+
}) {
|
|
383
|
+
if (control.mode === "windows-native") {
|
|
384
|
+
const { authorizeAndConfigure } = require(
|
|
385
|
+
"../rainbond-platform-installer/scripts/windows-onboarding.js"
|
|
386
|
+
);
|
|
387
|
+
await authorizeAndConfigure({
|
|
388
|
+
target: options.targetClient,
|
|
389
|
+
baseUrl: origin,
|
|
390
|
+
onConfiguredCredential: completeWithCredential,
|
|
391
|
+
});
|
|
392
|
+
return { code: 0, completesRuntimeState: true };
|
|
393
|
+
}
|
|
394
|
+
const childEnvironment = {
|
|
395
|
+
RAINSKILLS_RUNTIME_CONNECT_COMPLETION: "1",
|
|
396
|
+
RAINSKILLS_RUNTIME_OPERATION_ID: operationId,
|
|
397
|
+
};
|
|
398
|
+
if (options.intent?.type === "environment-add") {
|
|
399
|
+
childEnvironment.RAINSKILLS_RUNTIME_STATE_SCOPE = "operation";
|
|
400
|
+
}
|
|
401
|
+
const result = await runAttached(invocation.executable, invocation.args, {
|
|
402
|
+
env: runtimeChildEnvironment(process.env, childEnvironment, origin),
|
|
403
|
+
});
|
|
404
|
+
return { ...result, completesRuntimeState: true };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function defaultPrivateInstallerScheduler({ control, intent, operationId, target, privateLocation }) {
|
|
408
|
+
const {
|
|
409
|
+
createNextAction,
|
|
410
|
+
createOnboardingCheckpoint,
|
|
411
|
+
} = require("../rainbond-platform-installer/scripts/windows-onboarding.js");
|
|
412
|
+
const packageVersion = require("../package.json").version;
|
|
413
|
+
createOnboardingCheckpoint({
|
|
414
|
+
home: os.homedir(),
|
|
415
|
+
target,
|
|
416
|
+
packageVersion,
|
|
417
|
+
control,
|
|
418
|
+
operationId,
|
|
419
|
+
intent,
|
|
420
|
+
});
|
|
421
|
+
return createNextAction(operationId, privateLocation);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function runBuiltin(args, {
|
|
425
|
+
runtimeStateManager,
|
|
426
|
+
environmentCredentialStore,
|
|
427
|
+
environmentRegistry,
|
|
428
|
+
operationStore,
|
|
429
|
+
write = (value) => process.stdout.write(value),
|
|
430
|
+
control = detectControlEnvironment(),
|
|
431
|
+
originInspector,
|
|
432
|
+
connectionRunner = defaultConnectionRunner,
|
|
433
|
+
privateInstallerScheduler = defaultPrivateInstallerScheduler,
|
|
434
|
+
credentialEnvironment = process.env,
|
|
435
|
+
credentialPersister,
|
|
436
|
+
connectedCredentialReader,
|
|
437
|
+
mcpServerRunner = defaultMcpServerRunner,
|
|
438
|
+
} = {}) {
|
|
439
|
+
let environmentServices;
|
|
440
|
+
const getEnvironmentServices = () => {
|
|
441
|
+
environmentServices ||= createEnvironmentRuntimeServices();
|
|
442
|
+
return environmentServices;
|
|
443
|
+
};
|
|
444
|
+
const getEnvironmentRegistry = () => environmentRegistry
|
|
445
|
+
|| getEnvironmentServices().environmentRegistry;
|
|
446
|
+
const getOperationStore = () => operationStore || getEnvironmentServices().operationStore;
|
|
447
|
+
const getEnvironmentCredentialStore = () => environmentCredentialStore
|
|
448
|
+
|| getEnvironmentServices().environmentCredentialStore;
|
|
449
|
+
const getRuntimeStateManager = (operationId, { scoped = false } = {}) => {
|
|
450
|
+
if (runtimeStateManager) return runtimeStateManager;
|
|
451
|
+
const operationScoped = scoped || (
|
|
452
|
+
credentialEnvironment.RAINSKILLS_RUNTIME_STATE_SCOPE === "operation"
|
|
453
|
+
&& credentialEnvironment.RAINSKILLS_RUNTIME_OPERATION_ID === operationId
|
|
454
|
+
);
|
|
455
|
+
return require(
|
|
456
|
+
"../rainbond-platform-installer/scripts/runtime-state.js"
|
|
457
|
+
).createRuntimeStateManager(operationScoped ? { operationId } : {});
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
if (args[0] === "mcp" && args[1] === "serve") {
|
|
461
|
+
const { client } = parseMcpServeArgs(args);
|
|
462
|
+
await mcpServerRunner({
|
|
463
|
+
client,
|
|
464
|
+
environmentRegistry: getEnvironmentRegistry(),
|
|
465
|
+
environmentCredentialStore: getEnvironmentCredentialStore(),
|
|
466
|
+
operationStore: getOperationStore(),
|
|
467
|
+
});
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (args[0] === "environment" && args[1] === "list") {
|
|
472
|
+
if (args.length !== 3 || args[2] !== "--json") {
|
|
473
|
+
throw new Error("environment list 只支持固定参数 --json");
|
|
474
|
+
}
|
|
475
|
+
const current = getEnvironmentRegistry().read();
|
|
476
|
+
write(`${JSON.stringify({
|
|
477
|
+
schema: "rainskills.environment-list.v1",
|
|
478
|
+
default_environment_id: current.default_environment_id,
|
|
479
|
+
environments: current.environments,
|
|
480
|
+
})}\n`);
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
if (args[0] === "environment" && ["rename", "set-default", "remove"].includes(args[1])) {
|
|
484
|
+
const input = parseEnvironmentMutationArgs(args);
|
|
485
|
+
const registry = getEnvironmentRegistry();
|
|
486
|
+
let environment;
|
|
487
|
+
let action;
|
|
488
|
+
if (input.action === "rename") {
|
|
489
|
+
environment = registry.rename(input.environmentId, input.name);
|
|
490
|
+
action = "renamed";
|
|
491
|
+
} else if (input.action === "set-default") {
|
|
492
|
+
environment = registry.setDefault(input.environmentId);
|
|
493
|
+
action = "default-changed";
|
|
494
|
+
} else {
|
|
495
|
+
environment = registry.remove(input.environmentId);
|
|
496
|
+
getEnvironmentCredentialStore().remove(input.environmentId);
|
|
497
|
+
action = "removed";
|
|
498
|
+
}
|
|
499
|
+
write(`${JSON.stringify({
|
|
500
|
+
schema: "rainskills.environment-result.v1",
|
|
501
|
+
action,
|
|
502
|
+
environment,
|
|
503
|
+
})}\n`);
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
if (args[0] === "operation" && args[1] === "begin") {
|
|
507
|
+
const input = parseOperationBeginArgs(args);
|
|
508
|
+
const operation = getOperationStore().begin(input);
|
|
509
|
+
write(`${JSON.stringify({
|
|
510
|
+
schema: "rainskills.operation-begin-result.v1",
|
|
511
|
+
operation_id: operation.operation_id,
|
|
512
|
+
environment_id: operation.environment_id,
|
|
513
|
+
intent: operation.intent,
|
|
514
|
+
stage: operation.stage,
|
|
515
|
+
})}\n`);
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
if (args[0] === "operation" && args[1] === "complete") {
|
|
519
|
+
if (
|
|
520
|
+
args.length !== 4
|
|
521
|
+
|| args[2] !== "--operation-id"
|
|
522
|
+
|| !UUID_PATTERN.test(args[3] || "")
|
|
523
|
+
) {
|
|
524
|
+
throw new Error("operation complete 参数无效");
|
|
525
|
+
}
|
|
526
|
+
const operation = getOperationStore().complete(args[3]);
|
|
527
|
+
write(`${JSON.stringify({
|
|
528
|
+
schema: "rainskills.operation-complete-result.v1",
|
|
529
|
+
operation_id: operation.operation_id,
|
|
530
|
+
environment_id: operation.environment_id,
|
|
531
|
+
stage: operation.stage,
|
|
532
|
+
})}\n`);
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
if (args[0] === "runtime" && args[1] === "message") {
|
|
536
|
+
if (args.length !== 4 || args[2] !== "--id") {
|
|
537
|
+
throw new Error("runtime message 只支持固定参数 --id");
|
|
538
|
+
}
|
|
539
|
+
write(renderCatalogUserMessage(args[3], {
|
|
540
|
+
controlPlatform: control.controlPlatform || control.hostPlatform,
|
|
541
|
+
}));
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
if (args[0] === "runtime" && args[1] === "status") {
|
|
545
|
+
if (args.length !== 3 || args[2] !== "--json") {
|
|
546
|
+
throw new Error("runtime status 只支持固定参数 --json");
|
|
547
|
+
}
|
|
548
|
+
const manager = runtimeStateManager || require(
|
|
549
|
+
"../rainbond-platform-installer/scripts/runtime-state.js"
|
|
550
|
+
).createRuntimeStateManager();
|
|
551
|
+
write(`${JSON.stringify(await manager.status())}\n`);
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
if (args[0] === "runtime" && args[1] === "record-failure") {
|
|
555
|
+
const failure = parseRuntimeFailureArgs(args);
|
|
556
|
+
const manager = runtimeStateManager || require(
|
|
557
|
+
"../rainbond-platform-installer/scripts/runtime-state.js"
|
|
558
|
+
).createRuntimeStateManager();
|
|
559
|
+
const state = manager.recordFailure(failure);
|
|
560
|
+
write(`${JSON.stringify({
|
|
561
|
+
schema: "rainskills.runtime-failure-record.v1",
|
|
562
|
+
onboarding_id: failure.operationId,
|
|
563
|
+
failure_category: state.last_failure_category,
|
|
564
|
+
retry_available: state.retry_budget === 1,
|
|
565
|
+
})}\n`);
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
if (args[0] === "runtime" && args[1] === "reconnect") {
|
|
569
|
+
const { operationId } = parseRuntimeReconnectArgs(args);
|
|
570
|
+
const multiEnvironmentEnabled = Boolean(
|
|
571
|
+
environmentRegistry
|
|
572
|
+
|| operationStore
|
|
573
|
+
|| environmentCredentialStore
|
|
574
|
+
|| !runtimeStateManager
|
|
575
|
+
);
|
|
576
|
+
let reconnectedEnvironment = null;
|
|
577
|
+
const manager = runtimeStateManager || require(
|
|
578
|
+
"../rainbond-platform-installer/scripts/runtime-state.js"
|
|
579
|
+
).createRuntimeStateManager();
|
|
580
|
+
const reconnectResult = await manager.withReconnectLease(operationId, async (connection) => {
|
|
581
|
+
try {
|
|
582
|
+
const inspect = originInspector || require(
|
|
583
|
+
"../rainbond-platform-installer/scripts/console-origin.js"
|
|
584
|
+
).inspectConsoleOrigin;
|
|
585
|
+
const inspection = await inspect(connection.console_origin);
|
|
586
|
+
if (inspection.pendingRedirectOrigin || inspection.origin !== connection.console_origin) {
|
|
587
|
+
throw new Error("protected Console origin 在重连检查中发生变化");
|
|
588
|
+
}
|
|
589
|
+
const options = {
|
|
590
|
+
targetClient: connection.target_client,
|
|
591
|
+
environmentChoice: connection.environment_kind === "saas" ? "saas" : "private-existing",
|
|
592
|
+
rainbondUrl: connection.console_origin,
|
|
593
|
+
allowInsecureHttp: connection.console_origin.startsWith("http://"),
|
|
594
|
+
onboardingId: operationId,
|
|
595
|
+
intent: connection.intent,
|
|
596
|
+
};
|
|
597
|
+
const invocation = runtimeConnectionInvocation(options, connection.console_origin);
|
|
598
|
+
let completedWithCredential = false;
|
|
599
|
+
const completeWithCredential = async (credential) => {
|
|
600
|
+
const priorToken = process.env.RAINBOND_JWT;
|
|
601
|
+
try {
|
|
602
|
+
process.env.RAINBOND_JWT = credential;
|
|
603
|
+
await manager.markConnected(connection);
|
|
604
|
+
if (multiEnvironmentEnabled) {
|
|
605
|
+
const protectedOperation = getOperationStore().read(operationId);
|
|
606
|
+
if (!protectedOperation?.environment_id) {
|
|
607
|
+
throw new Error("runtime reconnect 缺少已锁定的运行环境");
|
|
608
|
+
}
|
|
609
|
+
const registration = require(
|
|
610
|
+
"../rainbond-platform-installer/scripts/environment-credentials.js"
|
|
611
|
+
).registerConnectedEnvironment({
|
|
612
|
+
registry: getEnvironmentRegistry(),
|
|
613
|
+
credentialStore: getEnvironmentCredentialStore(),
|
|
614
|
+
origin: connection.console_origin,
|
|
615
|
+
token: credential,
|
|
616
|
+
kind: connection.environment_kind,
|
|
617
|
+
});
|
|
618
|
+
if (registration.environment.id !== protectedOperation.environment_id) {
|
|
619
|
+
throw new Error("runtime reconnect 不能切换已锁定的运行环境");
|
|
620
|
+
}
|
|
621
|
+
reconnectedEnvironment = registration.environment;
|
|
622
|
+
}
|
|
623
|
+
completedWithCredential = true;
|
|
624
|
+
} finally {
|
|
625
|
+
if (priorToken === undefined) delete process.env.RAINBOND_JWT;
|
|
626
|
+
else process.env.RAINBOND_JWT = priorToken;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
const result = await connectionRunner(invocation, {
|
|
630
|
+
completeWithCredential,
|
|
631
|
+
control,
|
|
632
|
+
options,
|
|
633
|
+
origin: connection.console_origin,
|
|
634
|
+
operationId,
|
|
635
|
+
});
|
|
636
|
+
if (result.signal || result.code !== 0) {
|
|
637
|
+
throw new Error("RainSkills 运行环境重新授权未完成");
|
|
638
|
+
}
|
|
639
|
+
if (result.completesRuntimeState) {
|
|
640
|
+
assertExactConnectedState(manager.read(), connection);
|
|
641
|
+
} else {
|
|
642
|
+
if (completedWithCredential) throw new Error("运行环境重连器返回了矛盾状态");
|
|
643
|
+
await manager.markConnected(connection);
|
|
644
|
+
assertExactConnectedState(manager.read(), connection);
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
environmentKind: connection.environment_kind,
|
|
648
|
+
consoleOrigin: connection.console_origin,
|
|
649
|
+
};
|
|
650
|
+
} catch {
|
|
651
|
+
write(`${JSON.stringify(runtimeReconnectRetryAction(operationId))}\n`);
|
|
652
|
+
throw new Error("RainSkills 运行环境重新授权失败");
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
if (multiEnvironmentEnabled) {
|
|
656
|
+
const operations = getOperationStore();
|
|
657
|
+
const protectedOperation = operations.read(operationId);
|
|
658
|
+
if (!protectedOperation || !protectedOperation.environment_id) {
|
|
659
|
+
throw new Error("runtime reconnect 缺少已锁定的运行环境");
|
|
660
|
+
}
|
|
661
|
+
if (!reconnectedEnvironment) {
|
|
662
|
+
if (connectedCredentialReader) {
|
|
663
|
+
const credential = await connectedCredentialReader(reconnectResult.consoleOrigin);
|
|
664
|
+
reconnectedEnvironment = require(
|
|
665
|
+
"../rainbond-platform-installer/scripts/environment-credentials.js"
|
|
666
|
+
).registerConnectedEnvironment({
|
|
667
|
+
registry: getEnvironmentRegistry(),
|
|
668
|
+
credentialStore: getEnvironmentCredentialStore(),
|
|
669
|
+
origin: reconnectResult.consoleOrigin,
|
|
670
|
+
token: credential.token,
|
|
671
|
+
kind: reconnectResult.environmentKind,
|
|
672
|
+
}).environment;
|
|
673
|
+
} else {
|
|
674
|
+
const existing = getEnvironmentRegistry().findByOrigin(reconnectResult.consoleOrigin);
|
|
675
|
+
if (!existing || !getEnvironmentCredentialStore().has(existing.id)) {
|
|
676
|
+
throw new Error("runtime reconnect 未写入目标环境凭据");
|
|
677
|
+
}
|
|
678
|
+
getEnvironmentCredentialStore().read({
|
|
679
|
+
environmentId: existing.id,
|
|
680
|
+
expectedOrigin: reconnectResult.consoleOrigin,
|
|
681
|
+
});
|
|
682
|
+
reconnectedEnvironment = existing;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (reconnectedEnvironment.id !== protectedOperation.environment_id) {
|
|
686
|
+
throw new Error("runtime reconnect 不能切换已锁定的运行环境");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
write(`${JSON.stringify({
|
|
690
|
+
schema: "rainskills.runtime-reconnect-result.v1",
|
|
691
|
+
state: "connected",
|
|
692
|
+
onboarding_id: operationId,
|
|
693
|
+
environment_kind: reconnectResult.environmentKind,
|
|
694
|
+
...(reconnectedEnvironment ? {
|
|
695
|
+
environment_id: reconnectedEnvironment.id,
|
|
696
|
+
environment_name: reconnectedEnvironment.name,
|
|
697
|
+
} : {}),
|
|
698
|
+
})}\n`);
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
if (args[0] === "runtime" && args[1] === "assert-connect") {
|
|
702
|
+
const expected = parseRuntimeAssertConnectArgs(args);
|
|
703
|
+
const manager = getRuntimeStateManager(expected.operationId);
|
|
704
|
+
assertConnectingState(manager.read(), expected);
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
if (args[0] === "runtime" && args[1] === "persist-connect-credential") {
|
|
708
|
+
if (
|
|
709
|
+
args.length !== 4
|
|
710
|
+
|| args[2] !== "--onboarding-id"
|
|
711
|
+
|| !UUID_PATTERN.test(args[3] || "")
|
|
712
|
+
) {
|
|
713
|
+
throw new Error("runtime connect credential writer 参数无效");
|
|
714
|
+
}
|
|
715
|
+
const manager = getRuntimeStateManager(args[3]);
|
|
716
|
+
const current = manager.read();
|
|
717
|
+
if (current.state !== "connecting" || current.operation_id !== args[3]) {
|
|
718
|
+
throw new Error("runtime connect credential writer 与 connecting operation 不匹配");
|
|
719
|
+
}
|
|
720
|
+
const token = credentialEnvironment.RAINBOND_JWT;
|
|
721
|
+
if (typeof token !== "string" || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) {
|
|
722
|
+
throw new Error("runtime connect credential writer 缺少有效凭据");
|
|
723
|
+
}
|
|
724
|
+
if (credentialPersister) {
|
|
725
|
+
await credentialPersister({ token, baseUrl: current.console_origin });
|
|
726
|
+
} else if (
|
|
727
|
+
environmentRegistry
|
|
728
|
+
|| operationStore
|
|
729
|
+
|| environmentCredentialStore
|
|
730
|
+
|| !runtimeStateManager
|
|
731
|
+
) {
|
|
732
|
+
const registration = require(
|
|
733
|
+
"../rainbond-platform-installer/scripts/environment-credentials.js"
|
|
734
|
+
).registerConnectedEnvironment({
|
|
735
|
+
registry: getEnvironmentRegistry(),
|
|
736
|
+
credentialStore: getEnvironmentCredentialStore(),
|
|
737
|
+
origin: current.console_origin,
|
|
738
|
+
token,
|
|
739
|
+
kind: current.environment_kind,
|
|
740
|
+
});
|
|
741
|
+
const operations = getOperationStore();
|
|
742
|
+
const operation = operations.read(args[3]);
|
|
743
|
+
if (!operation) throw new Error("runtime connect operation 不存在");
|
|
744
|
+
if (operation.environment_id === null) {
|
|
745
|
+
operations.bindEnvironment(args[3], registration.environment.id);
|
|
746
|
+
} else if (operation.environment_id !== registration.environment.id) {
|
|
747
|
+
throw new Error("runtime connect 不能改变已锁定的运行环境");
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
750
|
+
if (typeof manager.persistConnectingCredential !== "function") {
|
|
751
|
+
throw new Error("runtime connect credential writer 不可用");
|
|
752
|
+
}
|
|
753
|
+
await manager.persistConnectingCredential({ operationId: args[3], token });
|
|
754
|
+
}
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
if (args[0] === "runtime" && args[1] === "connect") {
|
|
758
|
+
const options = parseRuntimeConnectArgs(args);
|
|
759
|
+
const operationId = options.onboardingId || crypto.randomUUID();
|
|
760
|
+
const multiEnvironmentEnabled = Boolean(
|
|
761
|
+
environmentRegistry
|
|
762
|
+
|| operationStore
|
|
763
|
+
|| environmentCredentialStore
|
|
764
|
+
|| !runtimeStateManager
|
|
765
|
+
);
|
|
766
|
+
let operations;
|
|
767
|
+
if (multiEnvironmentEnabled) {
|
|
768
|
+
operations = getOperationStore();
|
|
769
|
+
const existingOperation = operations.read(operationId);
|
|
770
|
+
if (!existingOperation) {
|
|
771
|
+
operations.createPending({ operationId, intent: options.intent });
|
|
772
|
+
} else if (
|
|
773
|
+
existingOperation.stage !== "awaiting-environment"
|
|
774
|
+
|| !isDeepStrictEqual(existingOperation.intent, options.intent)
|
|
775
|
+
) {
|
|
776
|
+
throw new Error("runtime connect operation 与受保护的原始 intent 不匹配");
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (options.environmentChoice === "install-private") {
|
|
780
|
+
const nextAction = privateInstallerScheduler({
|
|
781
|
+
control,
|
|
782
|
+
intent: options.intent,
|
|
783
|
+
operationId,
|
|
784
|
+
target: options.targetClient,
|
|
785
|
+
privateLocation: options.privateLocation,
|
|
786
|
+
});
|
|
787
|
+
write(`${JSON.stringify(nextAction)}\n`);
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const inspect = originInspector || require(
|
|
792
|
+
"../rainbond-platform-installer/scripts/console-origin.js"
|
|
793
|
+
).inspectConsoleOrigin;
|
|
794
|
+
const requestedOrigin = options.environmentChoice === "saas"
|
|
795
|
+
? "https://run.rainbond.com"
|
|
796
|
+
: options.rainbondUrl;
|
|
797
|
+
const inspection = await inspect(requestedOrigin);
|
|
798
|
+
if (inspection.pendingRedirectOrigin) {
|
|
799
|
+
throw new Error(`Console 请求切换到新的 origin;请确认后改用:${inspection.pendingRedirectOrigin}`);
|
|
800
|
+
}
|
|
801
|
+
if (inspection.httpConfirmationRequired && !options.allowInsecureHttp) {
|
|
802
|
+
throw new Error("明文 HTTP 需要单独显式确认;确认可信内网后使用 --allow-insecure-http");
|
|
803
|
+
}
|
|
804
|
+
const manager = getRuntimeStateManager(operationId, {
|
|
805
|
+
scoped: options.intent.type === "environment-add",
|
|
806
|
+
});
|
|
807
|
+
const connection = {
|
|
808
|
+
target_client: options.targetClient,
|
|
809
|
+
environment_kind: options.environmentChoice === "saas" ? "saas" : "private",
|
|
810
|
+
console_origin: inspection.origin,
|
|
811
|
+
intent: options.intent,
|
|
812
|
+
operation_id: operationId,
|
|
813
|
+
};
|
|
814
|
+
let registeredEnvironment = null;
|
|
815
|
+
manager.startConnecting(connection);
|
|
816
|
+
try {
|
|
817
|
+
const invocation = runtimeConnectionInvocation(options, inspection.origin);
|
|
818
|
+
let completedWithCredential = false;
|
|
819
|
+
const completeWithCredential = async (credential) => {
|
|
820
|
+
const priorToken = process.env.RAINBOND_JWT;
|
|
821
|
+
try {
|
|
822
|
+
process.env.RAINBOND_JWT = credential;
|
|
823
|
+
await manager.markConnected(connection);
|
|
824
|
+
if (multiEnvironmentEnabled) {
|
|
825
|
+
registeredEnvironment = require(
|
|
826
|
+
"../rainbond-platform-installer/scripts/environment-credentials.js"
|
|
827
|
+
).registerConnectedEnvironment({
|
|
828
|
+
registry: getEnvironmentRegistry(),
|
|
829
|
+
credentialStore: getEnvironmentCredentialStore(),
|
|
830
|
+
origin: inspection.origin,
|
|
831
|
+
token: credential,
|
|
832
|
+
kind: connection.environment_kind,
|
|
833
|
+
}).environment;
|
|
834
|
+
const pending = operations.read(operationId);
|
|
835
|
+
if (pending.environment_id === null) {
|
|
836
|
+
operations.bindEnvironment(operationId, registeredEnvironment.id);
|
|
837
|
+
} else if (pending.environment_id !== registeredEnvironment.id) {
|
|
838
|
+
throw new Error("runtime connect 不能改变已锁定的运行环境");
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
completedWithCredential = true;
|
|
842
|
+
} finally {
|
|
843
|
+
if (priorToken === undefined) delete process.env.RAINBOND_JWT;
|
|
844
|
+
else process.env.RAINBOND_JWT = priorToken;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
const result = await connectionRunner(invocation, {
|
|
848
|
+
completeWithCredential,
|
|
849
|
+
control,
|
|
850
|
+
options,
|
|
851
|
+
origin: inspection.origin,
|
|
852
|
+
operationId,
|
|
853
|
+
});
|
|
854
|
+
if (result.signal || result.code !== 0) {
|
|
855
|
+
throw new Error("RainSkills 运行环境连接或授权未完成");
|
|
856
|
+
}
|
|
857
|
+
if (result.completesRuntimeState) {
|
|
858
|
+
const state = manager.read();
|
|
859
|
+
if (state.state !== "connected" || state.operation_id !== operationId) {
|
|
860
|
+
throw new Error("运行环境连接器未完成 live probe");
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
if (completedWithCredential) throw new Error("运行环境连接器返回了矛盾状态");
|
|
864
|
+
await manager.markConnected(connection);
|
|
865
|
+
}
|
|
866
|
+
} catch (error) {
|
|
867
|
+
write(`${JSON.stringify(runtimeConnectRetryAction(options, inspection.origin, operationId))}\n`);
|
|
868
|
+
throw error;
|
|
869
|
+
}
|
|
870
|
+
if (multiEnvironmentEnabled) {
|
|
871
|
+
if (!registeredEnvironment) {
|
|
872
|
+
if (connectedCredentialReader) {
|
|
873
|
+
const credential = await connectedCredentialReader(inspection.origin);
|
|
874
|
+
if (!credential || credential.origin !== inspection.origin) {
|
|
875
|
+
throw new Error("运行环境凭据与已验证 Console origin 不匹配");
|
|
876
|
+
}
|
|
877
|
+
registeredEnvironment = require(
|
|
878
|
+
"../rainbond-platform-installer/scripts/environment-credentials.js"
|
|
879
|
+
).registerConnectedEnvironment({
|
|
880
|
+
registry: getEnvironmentRegistry(),
|
|
881
|
+
credentialStore: getEnvironmentCredentialStore(),
|
|
882
|
+
origin: inspection.origin,
|
|
883
|
+
token: credential.token,
|
|
884
|
+
kind: connection.environment_kind,
|
|
885
|
+
}).environment;
|
|
886
|
+
} else {
|
|
887
|
+
const existing = getEnvironmentRegistry().findByOrigin(inspection.origin);
|
|
888
|
+
if (!existing || !getEnvironmentCredentialStore().has(existing.id)) {
|
|
889
|
+
throw new Error("runtime connect 未写入目标环境凭据");
|
|
890
|
+
}
|
|
891
|
+
getEnvironmentCredentialStore().read({
|
|
892
|
+
environmentId: existing.id,
|
|
893
|
+
expectedOrigin: inspection.origin,
|
|
894
|
+
});
|
|
895
|
+
registeredEnvironment = existing;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const protectedOperation = operations.read(operationId);
|
|
899
|
+
if (protectedOperation.environment_id === null) {
|
|
900
|
+
operations.bindEnvironment(operationId, registeredEnvironment.id);
|
|
901
|
+
} else if (protectedOperation.environment_id !== registeredEnvironment.id) {
|
|
902
|
+
throw new Error("runtime connect 不能改变已锁定的运行环境");
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const environmentSnapshot = registeredEnvironment && options.intent.type === "environment-add"
|
|
906
|
+
? getEnvironmentRegistry().read()
|
|
907
|
+
: null;
|
|
908
|
+
write(`${JSON.stringify({
|
|
909
|
+
schema: "rainskills.runtime-connect-result.v1",
|
|
910
|
+
state: "connected",
|
|
911
|
+
onboarding_id: operationId,
|
|
912
|
+
environment_kind: connection.environment_kind,
|
|
913
|
+
...(registeredEnvironment ? {
|
|
914
|
+
environment_id: registeredEnvironment.id,
|
|
915
|
+
environment_name: registeredEnvironment.name,
|
|
916
|
+
} : {}),
|
|
917
|
+
...(environmentSnapshot ? {
|
|
918
|
+
default_environment_id: environmentSnapshot.default_environment_id,
|
|
919
|
+
environments: environmentSnapshot.environments,
|
|
920
|
+
user_message: require(
|
|
921
|
+
"../rainbond-platform-installer/scripts/user-message.js"
|
|
922
|
+
).renderEnvironmentConnectedList({
|
|
923
|
+
environments: environmentSnapshot.environments,
|
|
924
|
+
defaultEnvironmentId: environmentSnapshot.default_environment_id,
|
|
925
|
+
addedEnvironmentId: registeredEnvironment.id,
|
|
926
|
+
}),
|
|
927
|
+
} : {}),
|
|
928
|
+
})}\n`);
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
if (args[0] === "runtime" && args[1] === "complete-connect") {
|
|
932
|
+
if (args.length !== 4 || args[2] !== "--onboarding-id" || !UUID_PATTERN.test(args[3] || "")) {
|
|
933
|
+
throw new Error("runtime complete-connect 参数无效");
|
|
934
|
+
}
|
|
935
|
+
const manager = getRuntimeStateManager(args[3]);
|
|
936
|
+
const current = manager.read();
|
|
937
|
+
if (current.state !== "connecting" || current.operation_id !== args[3]) {
|
|
938
|
+
throw new Error("runtime connecting operation 不匹配");
|
|
939
|
+
}
|
|
940
|
+
await manager.markConnected({
|
|
941
|
+
target_client: current.target_client,
|
|
942
|
+
environment_kind: current.environment_kind,
|
|
943
|
+
console_origin: current.console_origin,
|
|
944
|
+
intent: current.intent,
|
|
945
|
+
operation_id: current.operation_id,
|
|
946
|
+
});
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
if (args[0] === "intent" && args[1] === "resume") {
|
|
950
|
+
if (args.length !== 4 || args[2] !== "--onboarding-id" || !args[3]) {
|
|
951
|
+
throw new Error("intent resume 需要固定参数 --onboarding-id <uuid>");
|
|
952
|
+
}
|
|
953
|
+
const manager = runtimeStateManager || require(
|
|
954
|
+
"../rainbond-platform-installer/scripts/runtime-state.js"
|
|
955
|
+
).createRuntimeStateManager();
|
|
956
|
+
manager.assertContinuationEligible(args[3]);
|
|
957
|
+
const status = await manager.status();
|
|
958
|
+
if (status.state !== "connected" || status.usable !== true) {
|
|
959
|
+
throw new Error("runtime 尚未 connected 且通过 live probe,不能恢复 intent");
|
|
960
|
+
}
|
|
961
|
+
const runtime = manager.prepareContinuation(args[3]);
|
|
962
|
+
const { createIntentContinuation } = require(
|
|
963
|
+
"../rainbond-platform-installer/scripts/runtime-intents.js"
|
|
964
|
+
);
|
|
965
|
+
write(`${JSON.stringify(createIntentContinuation(runtime.intent, runtime.failed_step || undefined))}\n`);
|
|
966
|
+
return true;
|
|
967
|
+
}
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
8
970
|
|
|
9
971
|
function classifyNodeMajor(major) {
|
|
10
972
|
if (major < 18) {
|
|
@@ -36,12 +998,29 @@ function resolveInvocation(args, {
|
|
|
36
998
|
"windows-onboarding.js"
|
|
37
999
|
);
|
|
38
1000
|
|
|
1001
|
+
if (args[0] === "tools") {
|
|
1002
|
+
return {
|
|
1003
|
+
executable: execPath,
|
|
1004
|
+
args: [path.resolve(__dirname, "rainskills-tools.js"), ...args.slice(1)],
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
39
1008
|
if (args[0] === "platform" && args[1] === "install") {
|
|
40
1009
|
return {
|
|
41
1010
|
executable: execPath,
|
|
42
1011
|
args: [platformInstallerPath, "install", ...args.slice(2)],
|
|
43
1012
|
};
|
|
44
1013
|
}
|
|
1014
|
+
if (args[0] === "ssh" && ["prepare", "prepare-cluster"].includes(args[1])) {
|
|
1015
|
+
return {
|
|
1016
|
+
executable: execPath,
|
|
1017
|
+
args: [
|
|
1018
|
+
path.resolve(__dirname, "..", "rainbond-platform-installer", "scripts", "ssh-key-setup.js"),
|
|
1019
|
+
args[1],
|
|
1020
|
+
...args.slice(2),
|
|
1021
|
+
],
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
45
1024
|
if (args[0] === "resume") {
|
|
46
1025
|
return {
|
|
47
1026
|
executable: execPath,
|
|
@@ -60,7 +1039,98 @@ function resolveInvocation(args, {
|
|
|
60
1039
|
};
|
|
61
1040
|
}
|
|
62
1041
|
|
|
63
|
-
function
|
|
1042
|
+
async function runAutoUpdatePhase(args, {
|
|
1043
|
+
currentVersion = require("../package.json").version,
|
|
1044
|
+
env = process.env,
|
|
1045
|
+
home = os.homedir(),
|
|
1046
|
+
platform = process.platform,
|
|
1047
|
+
packageRoot = path.resolve(__dirname, ".."),
|
|
1048
|
+
checkForUpdate,
|
|
1049
|
+
synchronizeSkills,
|
|
1050
|
+
updateState,
|
|
1051
|
+
delegate,
|
|
1052
|
+
} = {}) {
|
|
1053
|
+
const autoUpdate = require(
|
|
1054
|
+
"../rainbond-platform-installer/scripts/auto-update.js"
|
|
1055
|
+
);
|
|
1056
|
+
let state = updateState;
|
|
1057
|
+
const getState = () => {
|
|
1058
|
+
state ||= autoUpdate.createAutoUpdateState({ home, platform });
|
|
1059
|
+
return state;
|
|
1060
|
+
};
|
|
1061
|
+
if (env.RAINSKILLS_AUTO_UPDATE_HOP === "1") {
|
|
1062
|
+
try {
|
|
1063
|
+
if (
|
|
1064
|
+
env.RAINSKILLS_AUTO_UPDATE_TARGET !== currentVersion
|
|
1065
|
+
|| !autoUpdate.isStableVersion(env.RAINSKILLS_AUTO_UPDATE_FROM)
|
|
1066
|
+
|| !autoUpdate.isStableVersion(currentVersion)
|
|
1067
|
+
) {
|
|
1068
|
+
throw new Error("自动升级委托版本不匹配");
|
|
1069
|
+
}
|
|
1070
|
+
(synchronizeSkills || autoUpdate.synchronizeInstalledSkills)({
|
|
1071
|
+
packageRoot,
|
|
1072
|
+
home,
|
|
1073
|
+
platform,
|
|
1074
|
+
updateState: getState(),
|
|
1075
|
+
});
|
|
1076
|
+
getState().recordApplied(currentVersion);
|
|
1077
|
+
return { handled: false, reason: "delegated-sync-complete" };
|
|
1078
|
+
} catch {
|
|
1079
|
+
try { getState().recordFailure(); } catch { /* the old version remains authoritative */ }
|
|
1080
|
+
return { handled: true, code: AUTO_UPDATE_FALLBACK_EXIT_CODE, signal: null };
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
let lease = null;
|
|
1084
|
+
try {
|
|
1085
|
+
if (autoUpdate.isStableVersion(currentVersion) && autoUpdate.isSafeAutoUpdateEntry(args)) {
|
|
1086
|
+
lease = getState().acquireLease?.() || null;
|
|
1087
|
+
}
|
|
1088
|
+
} catch {
|
|
1089
|
+
return { handled: false, reason: "update-busy" };
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
const decision = await (checkForUpdate || autoUpdate.checkForStableUpdate)({
|
|
1093
|
+
args,
|
|
1094
|
+
currentVersion,
|
|
1095
|
+
env,
|
|
1096
|
+
home,
|
|
1097
|
+
platform,
|
|
1098
|
+
...(state ? { updateState: state } : {}),
|
|
1099
|
+
});
|
|
1100
|
+
if (decision.action !== "delegate") {
|
|
1101
|
+
return { handled: false, reason: decision.reason };
|
|
1102
|
+
}
|
|
1103
|
+
const invocation = autoUpdate.buildStableUpdateInvocation(decision.version, args, { platform });
|
|
1104
|
+
const environment = autoUpdate.buildStableUpdateEnvironment(env, {
|
|
1105
|
+
fromVersion: currentVersion,
|
|
1106
|
+
targetVersion: decision.version,
|
|
1107
|
+
});
|
|
1108
|
+
let result;
|
|
1109
|
+
try {
|
|
1110
|
+
result = await (delegate || ((nextInvocation, nextEnvironment) => runAttached(
|
|
1111
|
+
nextInvocation.executable,
|
|
1112
|
+
nextInvocation.args,
|
|
1113
|
+
{ env: nextEnvironment }
|
|
1114
|
+
)))(invocation, environment);
|
|
1115
|
+
} catch {
|
|
1116
|
+
try { getState().recordFailure(); } catch { /* best effort only */ }
|
|
1117
|
+
return { handled: false, reason: "delegated-update-failed" };
|
|
1118
|
+
}
|
|
1119
|
+
if (result.code === AUTO_UPDATE_FALLBACK_EXIT_CODE && !result.signal) {
|
|
1120
|
+
try { getState().recordFailure(); } catch { /* best effort only */ }
|
|
1121
|
+
return { handled: false, reason: "delegated-update-failed" };
|
|
1122
|
+
}
|
|
1123
|
+
return {
|
|
1124
|
+
handled: true,
|
|
1125
|
+
code: result.code === null ? 1 : result.code,
|
|
1126
|
+
signal: result.signal || null,
|
|
1127
|
+
};
|
|
1128
|
+
} finally {
|
|
1129
|
+
lease?.release();
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async function run() {
|
|
64
1134
|
const major = Number.parseInt(process.versions.node.split(".", 1)[0], 10);
|
|
65
1135
|
const support = classifyNodeMajor(major);
|
|
66
1136
|
|
|
@@ -81,7 +1151,18 @@ function run() {
|
|
|
81
1151
|
);
|
|
82
1152
|
}
|
|
83
1153
|
|
|
84
|
-
const
|
|
1154
|
+
const args = process.argv.slice(2);
|
|
1155
|
+
const autoUpdateResult = await runAutoUpdatePhase(args);
|
|
1156
|
+
if (autoUpdateResult.handled) {
|
|
1157
|
+
if (autoUpdateResult.signal) {
|
|
1158
|
+
process.kill(process.pid, autoUpdateResult.signal);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
process.exitCode = autoUpdateResult.code;
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (await runBuiltin(args)) return;
|
|
1165
|
+
const invocation = resolveInvocation(args);
|
|
85
1166
|
const child = spawn(invocation.executable, invocation.args, {
|
|
86
1167
|
env: process.env,
|
|
87
1168
|
stdio: "inherit",
|
|
@@ -107,12 +1188,36 @@ function run() {
|
|
|
107
1188
|
process.kill(process.pid, signal);
|
|
108
1189
|
return;
|
|
109
1190
|
}
|
|
1191
|
+
if (!spawnFailed && code === 0) {
|
|
1192
|
+
try {
|
|
1193
|
+
require("../rainbond-platform-installer/scripts/auto-update.js")
|
|
1194
|
+
.recordSkillInstallDestinations(args);
|
|
1195
|
+
} catch {
|
|
1196
|
+
// Skill installation already succeeded. Canonical roots remain discoverable on the next run.
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
110
1199
|
process.exitCode = spawnFailed ? 1 : code === null ? 1 : code;
|
|
111
1200
|
});
|
|
112
1201
|
}
|
|
113
1202
|
|
|
114
|
-
module.exports = {
|
|
1203
|
+
module.exports = {
|
|
1204
|
+
AUTO_UPDATE_FALLBACK_EXIT_CODE,
|
|
1205
|
+
classifyNodeMajor,
|
|
1206
|
+
parseRuntimeFailureArgs,
|
|
1207
|
+
parseRuntimeReconnectArgs,
|
|
1208
|
+
parseRuntimeConnectArgs,
|
|
1209
|
+
resolveInvocation,
|
|
1210
|
+
runAutoUpdatePhase,
|
|
1211
|
+
runBuiltin,
|
|
1212
|
+
runtimeChildEnvironment,
|
|
1213
|
+
runtimeConnectRetryAction,
|
|
1214
|
+
runtimeConnectionInvocation,
|
|
1215
|
+
runtimeReconnectRetryAction,
|
|
1216
|
+
};
|
|
115
1217
|
|
|
116
1218
|
if (require.main === module) {
|
|
117
|
-
run()
|
|
1219
|
+
run().catch((error) => {
|
|
1220
|
+
console.error(`错误:${error.message}`);
|
|
1221
|
+
process.exitCode = 1;
|
|
1222
|
+
});
|
|
118
1223
|
}
|