rainskills 0.1.19 → 0.1.21
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 +34 -39
- package/SKILL.md +12 -10
- package/bin/rainskills-tools.js +114 -240
- package/bin/rainskills.js +134 -591
- package/install.sh +68 -94
- package/marketplace/rainskills/.claude-plugin/plugin.json +1 -1
- package/marketplace/rainskills/.codex-plugin/plugin.json +1 -1
- package/marketplace/rainskills/skills/rainskills/SKILL.md +13 -11
- package/package.json +2 -2
- package/rainbond-app-assistant/SKILL.md +116 -151
- package/rainbond-app-version-assistant/SKILL.md +98 -98
- package/rainbond-delivery-verifier/SKILL.md +98 -68
- package/rainbond-env-sync/SKILL.md +98 -69
- package/rainbond-fullstack-bootstrap/SKILL.md +101 -89
- package/rainbond-fullstack-bootstrap/modules/10-context-loading.md +1 -1
- package/rainbond-fullstack-troubleshooter/SKILL.md +98 -68
- package/rainbond-opensource-app-deploy/SKILL.md +102 -81
- package/rainbond-platform-installer/SKILL.md +4 -4
- package/rainbond-platform-installer/scripts/auto-update.js +0 -9
- package/rainbond-platform-installer/scripts/installed-version.js +1 -1
- package/rainbond-platform-installer/scripts/platform-installer.js +3 -89
- package/rainbond-platform-installer/scripts/runtime-state.js +14 -251
- package/rainbond-platform-installer/scripts/single-runtime.js +114 -0
- package/rainbond-platform-installer/scripts/user-message.js +3 -49
- package/rainbond-platform-installer/scripts/windows-client-config.js +0 -51
- package/rainbond-platform-installer/scripts/windows-onboarding.js +0 -3
- package/rainbond-platform-installer/scripts/windows-platform.ps1 +1 -1
- package/rainbond-platform-query/SKILL.md +103 -61
- package/rainbond-project-init/SKILL.md +102 -79
- package/rainbond-template-installer/SKILL.md +102 -80
- package/rainbond-platform-installer/scripts/environment-credentials.js +0 -225
- package/rainbond-platform-installer/scripts/environment-registry.js +0 -349
- package/rainbond-platform-installer/scripts/local-runtime-commands.js +0 -180
- package/rainbond-platform-installer/scripts/local-runtime.js +0 -58
- package/rainbond-platform-installer/scripts/runtime-context-resolver.js +0 -189
- package/rainbond-platform-installer/scripts/runtime-credentials.js +0 -163
- package/rainbond-platform-installer/scripts/runtime-intents.js +0 -386
- package/rainbond-platform-installer/scripts/runtime-operations.js +0 -597
- package/rainbond-platform-installer/scripts/windows-client-config.ps1 +0 -9
- package/rainbond-platform-installer/scripts/windows-read-user-environment.ps1 +0 -16
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const crypto = require("node:crypto");
|
|
4
|
-
const fs = require("node:fs");
|
|
5
|
-
const os = require("node:os");
|
|
6
|
-
const path = require("node:path");
|
|
7
|
-
|
|
8
|
-
const { normalizeConsoleOrigin } = require("./console-origin.js");
|
|
9
|
-
const { createSecureStateStore } = require("./secure-state.js");
|
|
10
|
-
|
|
11
|
-
const REGISTRY_SCHEMA = "rainskills.environments.v1";
|
|
12
|
-
const REGISTRY_LOCK_ID = "68e80ada-7946-44ca-b438-59eef580a238";
|
|
13
|
-
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;
|
|
14
|
-
const CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
15
|
-
const ENVIRONMENT_KINDS = new Set(["saas", "private"]);
|
|
16
|
-
const CONNECTION_STATES = new Set(["connected", "needs-reconnect", "unavailable"]);
|
|
17
|
-
const ROOT_FIELDS = new Set([
|
|
18
|
-
"schema", "version", "default_environment_id", "environments",
|
|
19
|
-
]);
|
|
20
|
-
const ENVIRONMENT_FIELDS = new Set([
|
|
21
|
-
"id", "name", "console_origin", "kind", "connection_state",
|
|
22
|
-
"created_at", "updated_at", "last_verified_at",
|
|
23
|
-
]);
|
|
24
|
-
const MAX_ENVIRONMENTS = 128;
|
|
25
|
-
const MAX_NAME_LENGTH = 80;
|
|
26
|
-
|
|
27
|
-
function isIsoTimestamp(value) {
|
|
28
|
-
return typeof value === "string"
|
|
29
|
-
&& /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value)
|
|
30
|
-
&& !Number.isNaN(Date.parse(value));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function normalizeEnvironmentName(value) {
|
|
34
|
-
if (typeof value !== "string") throw new Error("环境名称无效");
|
|
35
|
-
const normalized = value.normalize("NFKC").trim().replace(/\s+/gu, " ");
|
|
36
|
-
if (!normalized || normalized.length > MAX_NAME_LENGTH || CONTROL_PATTERN.test(normalized)) {
|
|
37
|
-
throw new Error(`环境名称必须是长度不超过 ${MAX_NAME_LENGTH} 的安全文本`);
|
|
38
|
-
}
|
|
39
|
-
return normalized;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function nameKey(value) {
|
|
43
|
-
return normalizeEnvironmentName(value).toLocaleLowerCase("zh-CN");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function clone(value) {
|
|
47
|
-
return JSON.parse(JSON.stringify(value));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function createEnvironmentRegistry({
|
|
51
|
-
platform = process.platform,
|
|
52
|
-
home = os.homedir(),
|
|
53
|
-
stateStore = createSecureStateStore({ platform, home }),
|
|
54
|
-
randomUUID = crypto.randomUUID,
|
|
55
|
-
now = () => new Date().toISOString(),
|
|
56
|
-
activeOperationIds = () => [],
|
|
57
|
-
} = {}) {
|
|
58
|
-
const registryPath = path.join(home, ".rainbond", "rainskills", "environments-v1.json");
|
|
59
|
-
|
|
60
|
-
function emptyRegistry() {
|
|
61
|
-
return {
|
|
62
|
-
schema: REGISTRY_SCHEMA,
|
|
63
|
-
version: 1,
|
|
64
|
-
default_environment_id: null,
|
|
65
|
-
environments: [],
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function validateEnvironment(input) {
|
|
70
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
71
|
-
throw new Error("环境记录无效");
|
|
72
|
-
}
|
|
73
|
-
for (const field of Object.keys(input)) {
|
|
74
|
-
if (!ENVIRONMENT_FIELDS.has(field)) throw new Error(`环境记录包含未知字段:${field}`);
|
|
75
|
-
}
|
|
76
|
-
if (Object.keys(input).length !== ENVIRONMENT_FIELDS.size) {
|
|
77
|
-
throw new Error("环境记录字段不完整");
|
|
78
|
-
}
|
|
79
|
-
if (!UUID_PATTERN.test(input.id || "")) throw new Error("环境 ID 无效");
|
|
80
|
-
if (!ENVIRONMENT_KINDS.has(input.kind)) throw new Error("环境类型无效");
|
|
81
|
-
if (!CONNECTION_STATES.has(input.connection_state)) throw new Error("环境连接状态无效");
|
|
82
|
-
if (!isIsoTimestamp(input.created_at) || !isIsoTimestamp(input.updated_at)) {
|
|
83
|
-
throw new Error("环境时间戳无效");
|
|
84
|
-
}
|
|
85
|
-
if (input.last_verified_at !== null && !isIsoTimestamp(input.last_verified_at)) {
|
|
86
|
-
throw new Error("环境验证时间无效");
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
id: input.id,
|
|
90
|
-
name: normalizeEnvironmentName(input.name),
|
|
91
|
-
console_origin: normalizeConsoleOrigin(input.console_origin),
|
|
92
|
-
kind: input.kind,
|
|
93
|
-
connection_state: input.connection_state,
|
|
94
|
-
created_at: input.created_at,
|
|
95
|
-
updated_at: input.updated_at,
|
|
96
|
-
last_verified_at: input.last_verified_at,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function validateRegistry(input) {
|
|
101
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
102
|
-
throw new Error("环境注册表无效");
|
|
103
|
-
}
|
|
104
|
-
for (const field of Object.keys(input)) {
|
|
105
|
-
if (!ROOT_FIELDS.has(field)) throw new Error(`环境注册表包含未知字段:${field}`);
|
|
106
|
-
}
|
|
107
|
-
if (
|
|
108
|
-
Object.keys(input).length !== ROOT_FIELDS.size
|
|
109
|
-
|| input.schema !== REGISTRY_SCHEMA
|
|
110
|
-
|| input.version !== 1
|
|
111
|
-
|| !Array.isArray(input.environments)
|
|
112
|
-
|| input.environments.length > MAX_ENVIRONMENTS
|
|
113
|
-
) {
|
|
114
|
-
throw new Error("环境注册表 schema 无效");
|
|
115
|
-
}
|
|
116
|
-
const environments = input.environments.map(validateEnvironment);
|
|
117
|
-
const ids = new Set();
|
|
118
|
-
const origins = new Set();
|
|
119
|
-
const names = new Set();
|
|
120
|
-
for (const environment of environments) {
|
|
121
|
-
if (ids.has(environment.id)) throw new Error("环境注册表包含重复 ID");
|
|
122
|
-
if (origins.has(environment.console_origin)) throw new Error("环境注册表包含重复 origin");
|
|
123
|
-
const normalizedName = nameKey(environment.name);
|
|
124
|
-
if (names.has(normalizedName)) throw new Error("环境注册表包含重复名称");
|
|
125
|
-
ids.add(environment.id);
|
|
126
|
-
origins.add(environment.console_origin);
|
|
127
|
-
names.add(normalizedName);
|
|
128
|
-
}
|
|
129
|
-
if (
|
|
130
|
-
input.default_environment_id !== null
|
|
131
|
-
&& (!UUID_PATTERN.test(input.default_environment_id) || !ids.has(input.default_environment_id))
|
|
132
|
-
) {
|
|
133
|
-
throw new Error("默认环境 ID 无效");
|
|
134
|
-
}
|
|
135
|
-
if (environments.length > 0 && input.default_environment_id === null) {
|
|
136
|
-
throw new Error("非空环境注册表必须指定默认环境");
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
schema: REGISTRY_SCHEMA,
|
|
140
|
-
version: 1,
|
|
141
|
-
default_environment_id: input.default_environment_id,
|
|
142
|
-
environments,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function read() {
|
|
147
|
-
if (!fs.existsSync(registryPath)) return emptyRegistry();
|
|
148
|
-
return clone(validateRegistry(stateStore.readProtectedJson(registryPath)));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function writeUnlocked(value) {
|
|
152
|
-
const validated = validateRegistry(value);
|
|
153
|
-
stateStore.atomicWriteJson(registryPath, validated);
|
|
154
|
-
return clone(validated);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function withLock(action) {
|
|
158
|
-
let lock;
|
|
159
|
-
try {
|
|
160
|
-
lock = stateStore.acquireOperationLock({ operationId: REGISTRY_LOCK_ID });
|
|
161
|
-
} catch {
|
|
162
|
-
throw new Error("环境注册表正在由另一个进程更新");
|
|
163
|
-
}
|
|
164
|
-
try {
|
|
165
|
-
return action();
|
|
166
|
-
} finally {
|
|
167
|
-
lock.release();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function nextAutomaticName(kind, origin, environments) {
|
|
172
|
-
const hostname = new URL(origin).hostname.replace(/^\[|\]$/g, "");
|
|
173
|
-
const base = kind === "saas" ? "Rainbond Cloud" : `私有环境-${hostname}`;
|
|
174
|
-
const used = new Set(environments.map((entry) => nameKey(entry.name)));
|
|
175
|
-
if (!used.has(nameKey(base))) return base;
|
|
176
|
-
for (let suffix = 2; suffix <= MAX_ENVIRONMENTS + 1; suffix += 1) {
|
|
177
|
-
const candidate = `${base}-${suffix}`;
|
|
178
|
-
if (!used.has(nameKey(candidate))) return candidate;
|
|
179
|
-
}
|
|
180
|
-
throw new Error("无法生成不重复的环境名称");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function add({ kind, consoleOrigin, connectionState, name } = {}) {
|
|
184
|
-
if (!ENVIRONMENT_KINDS.has(kind)) throw new Error("环境类型无效");
|
|
185
|
-
if (!CONNECTION_STATES.has(connectionState)) throw new Error("环境连接状态无效");
|
|
186
|
-
const origin = normalizeConsoleOrigin(consoleOrigin);
|
|
187
|
-
return withLock(() => {
|
|
188
|
-
const current = read();
|
|
189
|
-
const existing = current.environments.find((entry) => entry.console_origin === origin);
|
|
190
|
-
if (existing) {
|
|
191
|
-
if (existing.kind !== kind) throw new Error("相同 Console origin 的环境类型不匹配");
|
|
192
|
-
return { created: false, environment: clone(existing) };
|
|
193
|
-
}
|
|
194
|
-
if (current.environments.length >= MAX_ENVIRONMENTS) throw new Error("环境数量已达到上限");
|
|
195
|
-
const environmentName = name === undefined
|
|
196
|
-
? nextAutomaticName(kind, origin, current.environments)
|
|
197
|
-
: normalizeEnvironmentName(name);
|
|
198
|
-
if (current.environments.some((entry) => nameKey(entry.name) === nameKey(environmentName))) {
|
|
199
|
-
throw new Error("环境名称已存在");
|
|
200
|
-
}
|
|
201
|
-
const id = randomUUID();
|
|
202
|
-
if (!UUID_PATTERN.test(id || "")) throw new Error("生成的环境 ID 无效");
|
|
203
|
-
const timestamp = now();
|
|
204
|
-
const environment = validateEnvironment({
|
|
205
|
-
id,
|
|
206
|
-
name: environmentName,
|
|
207
|
-
console_origin: origin,
|
|
208
|
-
kind,
|
|
209
|
-
connection_state: connectionState,
|
|
210
|
-
created_at: timestamp,
|
|
211
|
-
updated_at: timestamp,
|
|
212
|
-
last_verified_at: connectionState === "connected" ? timestamp : null,
|
|
213
|
-
});
|
|
214
|
-
const next = writeUnlocked({
|
|
215
|
-
...current,
|
|
216
|
-
default_environment_id: current.default_environment_id || environment.id,
|
|
217
|
-
environments: [...current.environments, environment],
|
|
218
|
-
});
|
|
219
|
-
return {
|
|
220
|
-
created: true,
|
|
221
|
-
environment: clone(next.environments.find((entry) => entry.id === environment.id)),
|
|
222
|
-
};
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function rename(environmentId, name) {
|
|
227
|
-
if (!UUID_PATTERN.test(environmentId || "")) throw new Error("环境 ID 无效");
|
|
228
|
-
const normalized = normalizeEnvironmentName(name);
|
|
229
|
-
return withLock(() => {
|
|
230
|
-
const current = read();
|
|
231
|
-
const existing = current.environments.find((entry) => entry.id === environmentId);
|
|
232
|
-
if (!existing) throw new Error("环境不存在");
|
|
233
|
-
if (current.environments.some(
|
|
234
|
-
(entry) => entry.id !== environmentId && nameKey(entry.name) === nameKey(normalized)
|
|
235
|
-
)) {
|
|
236
|
-
throw new Error("环境名称已存在");
|
|
237
|
-
}
|
|
238
|
-
const timestamp = now();
|
|
239
|
-
const next = writeUnlocked({
|
|
240
|
-
...current,
|
|
241
|
-
environments: current.environments.map((entry) => entry.id === environmentId
|
|
242
|
-
? { ...entry, name: normalized, updated_at: timestamp }
|
|
243
|
-
: entry),
|
|
244
|
-
});
|
|
245
|
-
return clone(next.environments.find((entry) => entry.id === environmentId));
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function setDefault(environmentId) {
|
|
250
|
-
if (!UUID_PATTERN.test(environmentId || "")) throw new Error("环境 ID 无效");
|
|
251
|
-
return withLock(() => {
|
|
252
|
-
const current = read();
|
|
253
|
-
if (!current.environments.some((entry) => entry.id === environmentId)) {
|
|
254
|
-
throw new Error("环境不存在");
|
|
255
|
-
}
|
|
256
|
-
writeUnlocked({ ...current, default_environment_id: environmentId });
|
|
257
|
-
return clone(current.environments.find((entry) => entry.id === environmentId));
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function remove(environmentId) {
|
|
262
|
-
if (!UUID_PATTERN.test(environmentId || "")) throw new Error("环境 ID 无效");
|
|
263
|
-
return withLock(() => {
|
|
264
|
-
const current = read();
|
|
265
|
-
const existing = current.environments.find((entry) => entry.id === environmentId);
|
|
266
|
-
if (!existing) throw new Error("环境不存在");
|
|
267
|
-
if (current.default_environment_id === environmentId) {
|
|
268
|
-
throw new Error("删除默认环境前必须先指定新的默认环境");
|
|
269
|
-
}
|
|
270
|
-
const operations = activeOperationIds(environmentId);
|
|
271
|
-
if (!Array.isArray(operations)) throw new Error("活动操作检查结果无效");
|
|
272
|
-
if (operations.length > 0) throw new Error("该环境仍被活动操作使用,不能删除");
|
|
273
|
-
writeUnlocked({
|
|
274
|
-
...current,
|
|
275
|
-
environments: current.environments.filter((entry) => entry.id !== environmentId),
|
|
276
|
-
});
|
|
277
|
-
return clone(existing);
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function updateConnection(environmentId, connectionState) {
|
|
282
|
-
if (!UUID_PATTERN.test(environmentId || "")) throw new Error("环境 ID 无效");
|
|
283
|
-
if (!CONNECTION_STATES.has(connectionState)) throw new Error("环境连接状态无效");
|
|
284
|
-
return withLock(() => {
|
|
285
|
-
const current = read();
|
|
286
|
-
const existing = current.environments.find((entry) => entry.id === environmentId);
|
|
287
|
-
if (!existing) throw new Error("环境不存在");
|
|
288
|
-
const timestamp = now();
|
|
289
|
-
const next = writeUnlocked({
|
|
290
|
-
...current,
|
|
291
|
-
environments: current.environments.map((entry) => entry.id === environmentId
|
|
292
|
-
? {
|
|
293
|
-
...entry,
|
|
294
|
-
connection_state: connectionState,
|
|
295
|
-
updated_at: timestamp,
|
|
296
|
-
last_verified_at: connectionState === "connected"
|
|
297
|
-
? timestamp
|
|
298
|
-
: entry.last_verified_at,
|
|
299
|
-
}
|
|
300
|
-
: entry),
|
|
301
|
-
});
|
|
302
|
-
return clone(next.environments.find((entry) => entry.id === environmentId));
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function list() {
|
|
307
|
-
return read().environments;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function get(environmentId) {
|
|
311
|
-
const environment = read().environments.find((entry) => entry.id === environmentId);
|
|
312
|
-
return environment ? clone(environment) : null;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function findByName(name) {
|
|
316
|
-
const key = nameKey(name);
|
|
317
|
-
const environment = read().environments.find((entry) => nameKey(entry.name) === key);
|
|
318
|
-
return environment ? clone(environment) : null;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function findByOrigin(origin) {
|
|
322
|
-
const normalized = normalizeConsoleOrigin(origin);
|
|
323
|
-
const environment = read().environments.find((entry) => entry.console_origin === normalized);
|
|
324
|
-
return environment ? clone(environment) : null;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return {
|
|
328
|
-
add,
|
|
329
|
-
findByName,
|
|
330
|
-
findByOrigin,
|
|
331
|
-
get,
|
|
332
|
-
list,
|
|
333
|
-
path: registryPath,
|
|
334
|
-
read,
|
|
335
|
-
remove,
|
|
336
|
-
rename,
|
|
337
|
-
setDefault,
|
|
338
|
-
updateConnection,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
module.exports = {
|
|
343
|
-
CONNECTION_STATES,
|
|
344
|
-
ENVIRONMENT_KINDS,
|
|
345
|
-
REGISTRY_LOCK_ID,
|
|
346
|
-
REGISTRY_SCHEMA,
|
|
347
|
-
createEnvironmentRegistry,
|
|
348
|
-
normalizeEnvironmentName,
|
|
349
|
-
};
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const { createEnvironmentCredentialStore } = require("./environment-credentials.js");
|
|
4
|
-
const { createEnvironmentRegistry } = require("./environment-registry.js");
|
|
5
|
-
const { createRuntimeOperationStore } = require("./runtime-operations.js");
|
|
6
|
-
const { renderCatalogUserMessage } = require("./user-message.js");
|
|
7
|
-
|
|
8
|
-
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;
|
|
9
|
-
|
|
10
|
-
function requireFixedValue(args, index, flag) {
|
|
11
|
-
const value = args[index + 1];
|
|
12
|
-
if (!value || value.startsWith("--")) throw new Error(`${flag} 缺少参数值`);
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function createLocalRuntimeServices() {
|
|
17
|
-
let operations;
|
|
18
|
-
const registry = createEnvironmentRegistry({
|
|
19
|
-
activeOperationIds: (environmentId) => operations?.activeOperationIds(environmentId) || [],
|
|
20
|
-
});
|
|
21
|
-
operations = createRuntimeOperationStore({ registry });
|
|
22
|
-
return {
|
|
23
|
-
environmentCredentialStore: createEnvironmentCredentialStore(),
|
|
24
|
-
environmentRegistry: registry,
|
|
25
|
-
operationStore: operations,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function parseEnvironmentMutationArgs(args) {
|
|
30
|
-
const action = args[1];
|
|
31
|
-
if (!new Set(["rename", "set-default", "remove"]).has(action)) {
|
|
32
|
-
throw new Error("environment action 无效");
|
|
33
|
-
}
|
|
34
|
-
if (args[2] !== "--environment-id" || !UUID_PATTERN.test(args[3] || "")) {
|
|
35
|
-
throw new Error("环境 ID 无效");
|
|
36
|
-
}
|
|
37
|
-
if (action === "rename") {
|
|
38
|
-
if (args.length !== 6 || args[4] !== "--name" || !args[5] || args[5].startsWith("--")) {
|
|
39
|
-
throw new Error("environment rename 参数无效");
|
|
40
|
-
}
|
|
41
|
-
return { action, environmentId: args[3], name: args[5] };
|
|
42
|
-
}
|
|
43
|
-
if (args.length !== 4) throw new Error(`environment ${action} 参数无效`);
|
|
44
|
-
return { action, environmentId: args[3] };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function parseOperationBeginArgs(args) {
|
|
48
|
-
if (args[0] !== "operation" || args[1] !== "begin") {
|
|
49
|
-
throw new Error("operation begin 参数无效");
|
|
50
|
-
}
|
|
51
|
-
let operationId = "";
|
|
52
|
-
let environmentId = "";
|
|
53
|
-
let intentInput = "";
|
|
54
|
-
for (let index = 2; index < args.length; index += 1) {
|
|
55
|
-
const argument = args[index];
|
|
56
|
-
if (argument === "--operation-id") {
|
|
57
|
-
operationId = requireFixedValue(args, index, argument);
|
|
58
|
-
index += 1;
|
|
59
|
-
} else if (argument === "--environment-id") {
|
|
60
|
-
environmentId = requireFixedValue(args, index, argument);
|
|
61
|
-
index += 1;
|
|
62
|
-
} else if (argument === "--intent-json") {
|
|
63
|
-
intentInput = requireFixedValue(args, index, argument);
|
|
64
|
-
index += 1;
|
|
65
|
-
} else {
|
|
66
|
-
throw new Error("operation begin 包含未知参数");
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (!UUID_PATTERN.test(operationId)) throw new Error("operation ID 无效");
|
|
70
|
-
if (environmentId && !UUID_PATTERN.test(environmentId)) throw new Error("环境 ID 无效");
|
|
71
|
-
if (!intentInput || intentInput.length > 16384) throw new Error("operation begin 缺少 intent JSON");
|
|
72
|
-
let intent;
|
|
73
|
-
try {
|
|
74
|
-
intent = JSON.parse(intentInput);
|
|
75
|
-
} catch {
|
|
76
|
-
throw new Error("operation begin intent JSON 无效");
|
|
77
|
-
}
|
|
78
|
-
return { operationId, environmentId: environmentId || undefined, intent };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function runLocalRuntimeCommand(args, {
|
|
82
|
-
environmentCredentialStore,
|
|
83
|
-
environmentRegistry,
|
|
84
|
-
operationStore,
|
|
85
|
-
write = (value) => process.stdout.write(value),
|
|
86
|
-
controlPlatform = process.platform,
|
|
87
|
-
} = {}) {
|
|
88
|
-
let services;
|
|
89
|
-
const getServices = () => {
|
|
90
|
-
services ||= createLocalRuntimeServices();
|
|
91
|
-
return services;
|
|
92
|
-
};
|
|
93
|
-
const registry = () => environmentRegistry || getServices().environmentRegistry;
|
|
94
|
-
const operations = () => operationStore || getServices().operationStore;
|
|
95
|
-
const credentials = () => environmentCredentialStore
|
|
96
|
-
|| getServices().environmentCredentialStore;
|
|
97
|
-
|
|
98
|
-
if (args[0] === "environment" && args[1] === "list") {
|
|
99
|
-
if (args.length !== 3 || args[2] !== "--json") {
|
|
100
|
-
throw new Error("environment list 只支持固定参数 --json");
|
|
101
|
-
}
|
|
102
|
-
const current = registry().read();
|
|
103
|
-
write(`${JSON.stringify({
|
|
104
|
-
schema: "rainskills.environment-list.v1",
|
|
105
|
-
default_environment_id: current.default_environment_id,
|
|
106
|
-
environments: current.environments,
|
|
107
|
-
})}\n`);
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (args[0] === "environment" && ["rename", "set-default", "remove"].includes(args[1])) {
|
|
112
|
-
const input = parseEnvironmentMutationArgs(args);
|
|
113
|
-
let environment;
|
|
114
|
-
let action;
|
|
115
|
-
if (input.action === "rename") {
|
|
116
|
-
environment = registry().rename(input.environmentId, input.name);
|
|
117
|
-
action = "renamed";
|
|
118
|
-
} else if (input.action === "set-default") {
|
|
119
|
-
environment = registry().setDefault(input.environmentId);
|
|
120
|
-
action = "default-changed";
|
|
121
|
-
} else {
|
|
122
|
-
environment = registry().remove(input.environmentId);
|
|
123
|
-
credentials().remove(input.environmentId);
|
|
124
|
-
action = "removed";
|
|
125
|
-
}
|
|
126
|
-
write(`${JSON.stringify({
|
|
127
|
-
schema: "rainskills.environment-result.v1",
|
|
128
|
-
action,
|
|
129
|
-
environment,
|
|
130
|
-
})}\n`);
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (args[0] === "operation" && args[1] === "begin") {
|
|
135
|
-
const operation = operations().begin(parseOperationBeginArgs(args));
|
|
136
|
-
write(`${JSON.stringify({
|
|
137
|
-
schema: "rainskills.operation-begin-result.v1",
|
|
138
|
-
operation_id: operation.operation_id,
|
|
139
|
-
environment_id: operation.environment_id,
|
|
140
|
-
intent: operation.intent,
|
|
141
|
-
stage: operation.stage,
|
|
142
|
-
})}\n`);
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (args[0] === "operation" && args[1] === "complete") {
|
|
147
|
-
if (
|
|
148
|
-
args.length !== 4
|
|
149
|
-
|| args[2] !== "--operation-id"
|
|
150
|
-
|| !UUID_PATTERN.test(args[3] || "")
|
|
151
|
-
) {
|
|
152
|
-
throw new Error("operation complete 参数无效");
|
|
153
|
-
}
|
|
154
|
-
const operation = operations().complete(args[3]);
|
|
155
|
-
write(`${JSON.stringify({
|
|
156
|
-
schema: "rainskills.operation-complete-result.v1",
|
|
157
|
-
operation_id: operation.operation_id,
|
|
158
|
-
environment_id: operation.environment_id,
|
|
159
|
-
stage: operation.stage,
|
|
160
|
-
})}\n`);
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (args[0] === "runtime" && args[1] === "message") {
|
|
165
|
-
if (args.length !== 4 || args[2] !== "--id") {
|
|
166
|
-
throw new Error("runtime message 只支持固定参数 --id");
|
|
167
|
-
}
|
|
168
|
-
write(renderCatalogUserMessage(args[3], { controlPlatform }));
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
module.exports = {
|
|
176
|
-
createLocalRuntimeServices,
|
|
177
|
-
parseEnvironmentMutationArgs,
|
|
178
|
-
parseOperationBeginArgs,
|
|
179
|
-
runLocalRuntimeCommand,
|
|
180
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const path = require("node:path");
|
|
5
|
-
const { spawn } = require("node:child_process");
|
|
6
|
-
const { runLocalRuntimeCommand } = require("./local-runtime-commands.js");
|
|
7
|
-
const { backgroundUpdateEnvironment } = require("./auto-update-worker.js");
|
|
8
|
-
|
|
9
|
-
function shouldScheduleUpdate(args, env = process.env) {
|
|
10
|
-
if (env.RAINSKILLS_DISABLE_AUTO_UPDATE === "1") return false;
|
|
11
|
-
return (
|
|
12
|
-
args[0] === "environment"
|
|
13
|
-
&& args[1] === "list"
|
|
14
|
-
&& args[2] === "--json"
|
|
15
|
-
) || (
|
|
16
|
-
args[0] === "operation"
|
|
17
|
-
&& args[1] === "complete"
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function spawnBackgroundUpdate({
|
|
22
|
-
spawnFn = spawn,
|
|
23
|
-
env = process.env,
|
|
24
|
-
execPath = process.execPath,
|
|
25
|
-
} = {}) {
|
|
26
|
-
try {
|
|
27
|
-
const child = spawnFn(execPath, [path.join(__dirname, "auto-update-worker.js")], {
|
|
28
|
-
detached: true,
|
|
29
|
-
env: backgroundUpdateEnvironment(env),
|
|
30
|
-
shell: false,
|
|
31
|
-
stdio: "ignore",
|
|
32
|
-
windowsHide: true,
|
|
33
|
-
});
|
|
34
|
-
child.unref();
|
|
35
|
-
} catch {
|
|
36
|
-
// Automatic updates are best-effort and never affect the local command.
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function main({
|
|
41
|
-
args = process.argv.slice(2),
|
|
42
|
-
runCommand = runLocalRuntimeCommand,
|
|
43
|
-
scheduleUpdate = spawnBackgroundUpdate,
|
|
44
|
-
env = process.env,
|
|
45
|
-
} = {}) {
|
|
46
|
-
const handled = runCommand(args);
|
|
47
|
-
if (!handled) throw new Error("不支持的本地运行环境命令");
|
|
48
|
-
if (shouldScheduleUpdate(args, env)) scheduleUpdate({ env });
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (require.main === module) {
|
|
52
|
-
main().catch((error) => {
|
|
53
|
-
console.error(`错误:${error.message}`);
|
|
54
|
-
process.exitCode = 1;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
module.exports = { main, shouldScheduleUpdate, spawnBackgroundUpdate };
|