huaweicloud-devkit 1.1.2-next.0 → 1.1.2-next.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/.agents/plugins/marketplace.json +18 -18
- package/README.md +182 -38
- package/README.zh-CN.md +180 -36
- package/bin/dsh-postinstall.cjs +77 -0
- package/bin/setup.cjs +7 -4
- package/cordis.patch.yml +13 -0
- package/integrations/atomcode/hooks/huaweicloud-telemetry.js +181 -0
- package/integrations/atomcode/hooks.json +9 -0
- package/integrations/dsh/hook-plugin.mjs +148 -0
- package/integrations/hermes/hooks/huaweicloud-telemetry.py +238 -0
- package/integrations/hermes/manifest.yaml +43 -0
- package/integrations/opencode/hooks/skill-tracker.js +115 -0
- package/integrations/opencode/opencode.json +1 -4
- package/integrations/workbuddy/hooks/telemetry-tracker.py +226 -0
- package/package.json +74 -43
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +23 -33
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
- package/plugins/huaweicloud-core/.mcp.json +2 -5
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
- package/plugins/huaweicloud-core/assets/icon.png +0 -0
- package/plugins/huaweicloud-core/assets/logo-dark.png +0 -0
- package/plugins/huaweicloud-core/assets/logo.png +0 -0
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +104 -28
- package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
- package/plugins/huaweicloud-core/safety/policy.json +75 -99
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +196 -35
- package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +38 -34
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +38 -32
- package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
- package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +40 -31
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +45 -37
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +57 -6
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +44 -39
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
- package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +93 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/architectures.md +83 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +107 -0
- package/plugins/huaweicloud-core/skills/huawei-iac/references/state-file.md +104 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +46 -45
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +71 -61
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +776 -44
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +84 -0
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +100 -0
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +36 -29
- package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +57 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +22 -15
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +23 -20
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +21 -2
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +30 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +68 -54
- package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +36 -34
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +142 -5
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +217 -5
- package/plugins/huaweicloud-core/src/auth/reconcile.mjs +174 -0
- package/plugins/huaweicloud-core/src/auth/service.mjs +57 -2
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/detect-framework.mjs +366 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +203 -16
- package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
- package/plugins/huaweicloud-core/src/mcp-protocol.mjs +88 -0
- package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +107 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +154 -99
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +48 -12
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +25 -26
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +943 -10
- package/plugins/huaweicloud-core/src/sandbox/uninstall-cleanup.mjs +50 -0
- package/plugins/huaweicloud-core/src/search-market.mjs +49 -14
- package/plugins/huaweicloud-core/src/setup-cli.mjs +3091 -498
- package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +40 -0
- package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +328 -0
- package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +378 -0
- package/plugins/huaweicloud-core/src/tools.mjs +1272 -146
- package/plugins/huaweicloud-core/src/update-check.mjs +383 -0
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +40 -13
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// Version-update detection & auto-upgrade for huaweicloud-devkit (session-level).
|
|
2
|
+
// Spec: docs/superpowers/specs/2026-09-07-version-upgrade-design.md (internal, not committed).
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
5
|
+
import { join, dirname, resolve } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
10
|
+
const NPM_BIN = IS_WINDOWS ? 'npm.cmd' : 'npm';
|
|
11
|
+
const NPX_BIN = IS_WINDOWS ? 'npx.cmd' : 'npx';
|
|
12
|
+
const TTL_MS = 60 * 60 * 1000;
|
|
13
|
+
const FAIL_THROTTLE_MS = 5 * 60 * 1000;
|
|
14
|
+
const COOLDOWN_DAYS = 3;
|
|
15
|
+
|
|
16
|
+
export function semverParse(input) {
|
|
17
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(String(input).trim());
|
|
18
|
+
if (!m) return null;
|
|
19
|
+
const pre = m[4] ? m[4].split('.') : null;
|
|
20
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre, raw: String(input).trim() };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function comparePre(a, b) {
|
|
24
|
+
if (a === null && b === null) return 0;
|
|
25
|
+
if (a === null) return 1; // 无 prerelease(正式版)更大
|
|
26
|
+
if (b === null) return -1;
|
|
27
|
+
const length = Math.max(a.length, b.length);
|
|
28
|
+
for (let i = 0; i < length; i++) {
|
|
29
|
+
const x = a[i];
|
|
30
|
+
const y = b[i];
|
|
31
|
+
if (x === undefined) return -1;
|
|
32
|
+
if (y === undefined) return 1;
|
|
33
|
+
const xNum = /^\d+$/.test(x);
|
|
34
|
+
const yNum = /^\d+$/.test(y);
|
|
35
|
+
if (xNum && yNum) {
|
|
36
|
+
const dx = Number(x);
|
|
37
|
+
const dy = Number(y);
|
|
38
|
+
if (dx !== dy) return dx < dy ? -1 : 1;
|
|
39
|
+
} else if (xNum) {
|
|
40
|
+
return -1; // 数字标识符 < 字母标识符
|
|
41
|
+
} else if (yNum) {
|
|
42
|
+
return 1;
|
|
43
|
+
} else if (x !== y) {
|
|
44
|
+
return x < y ? -1 : 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function semverCompare(a, b) {
|
|
51
|
+
const A = semverParse(a);
|
|
52
|
+
const B = semverParse(b);
|
|
53
|
+
if (!A || !B) {
|
|
54
|
+
const sa = String(a);
|
|
55
|
+
const sb = String(b);
|
|
56
|
+
return sa === sb ? 0 : sa < sb ? -1 : 1;
|
|
57
|
+
}
|
|
58
|
+
if (A.major !== B.major) return A.major < B.major ? -1 : 1;
|
|
59
|
+
if (A.minor !== B.minor) return A.minor < B.minor ? -1 : 1;
|
|
60
|
+
if (A.patch !== B.patch) return A.patch < B.patch ? -1 : 1;
|
|
61
|
+
return comparePre(A.pre, B.pre);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function hasPrerelease(v) {
|
|
65
|
+
const parsed = semverParse(v);
|
|
66
|
+
return Boolean(parsed && parsed.pre);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function determineTarget(current, distTags = {}) {
|
|
70
|
+
const isPre = hasPrerelease(current);
|
|
71
|
+
const candidates = [];
|
|
72
|
+
if (distTags.latest) candidates.push(distTags.latest);
|
|
73
|
+
if (isPre && distTags.next) candidates.push(distTags.next);
|
|
74
|
+
if (!candidates.length) return null;
|
|
75
|
+
return candidates.slice().sort(semverCompare).pop();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function parseDistTagsOutput(stdout) {
|
|
79
|
+
try {
|
|
80
|
+
const text = String(stdout || '').trim();
|
|
81
|
+
if (!text) return null; // 空输出视为 npm view 失败
|
|
82
|
+
const parsed = JSON.parse(text);
|
|
83
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
|
|
84
|
+
return {
|
|
85
|
+
latest: typeof parsed.latest === 'string' ? parsed.latest : null,
|
|
86
|
+
next: typeof parsed.next === 'string' ? parsed.next : null,
|
|
87
|
+
};
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildResult(current, distTags, extra = {}) {
|
|
94
|
+
const result = {
|
|
95
|
+
currentVersion: current,
|
|
96
|
+
latestStable: distTags?.latest ?? null,
|
|
97
|
+
latestNext: distTags?.next ?? null,
|
|
98
|
+
targetVersion: extra.target ?? null,
|
|
99
|
+
updateAvailable: extra.result === 'update_available',
|
|
100
|
+
dismissed: extra.result === 'dismissed',
|
|
101
|
+
dismissExpiresAt: extra.dismissExpiresAt ?? null,
|
|
102
|
+
result: extra.result ?? 'up_to_date',
|
|
103
|
+
};
|
|
104
|
+
if (extra.note) result.note = extra.note;
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function judgeUpdate(current, distTags, skipState, now = Date.now()) {
|
|
109
|
+
if (process.env.HUAWEICLOUD_DEVKIT_SKIP_UPDATE === '1') {
|
|
110
|
+
return buildResult(current, distTags ?? null);
|
|
111
|
+
}
|
|
112
|
+
if (!distTags) {
|
|
113
|
+
return buildResult(current, null, { result: 'check_failed', note: '检测失败,不影响使用' });
|
|
114
|
+
}
|
|
115
|
+
const target = determineTarget(current, distTags);
|
|
116
|
+
if (!target || semverCompare(target, current) <= 0) {
|
|
117
|
+
return buildResult(current, distTags, { result: 'up_to_date', target: target ?? null });
|
|
118
|
+
}
|
|
119
|
+
const expiresAt = skipState?.expireAt ? new Date(skipState.expireAt).getTime() : 0;
|
|
120
|
+
const inCooldown =
|
|
121
|
+
Boolean(skipState) && now < expiresAt && semverCompare(target, String(skipState.dismissedVersion)) <= 0;
|
|
122
|
+
if (inCooldown) {
|
|
123
|
+
return buildResult(current, distTags, { result: 'dismissed', target, dismissExpiresAt: skipState.expireAt });
|
|
124
|
+
}
|
|
125
|
+
return buildResult(current, distTags, { result: 'update_available', target });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function selfDir() {
|
|
129
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function readInstalledVersion() {
|
|
133
|
+
const pluginRoot = resolve(selfDir(), '..');
|
|
134
|
+
const packageRoot = resolve(pluginRoot, '..', '..');
|
|
135
|
+
for (const base of [pluginRoot, packageRoot]) {
|
|
136
|
+
try {
|
|
137
|
+
const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
|
|
138
|
+
if (typeof version === 'string' && version) return version;
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function skipFilePath() {
|
|
145
|
+
return join(resolve(selfDir(), '..'), '.update-skip.json');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function fallbackSkipFilePath() {
|
|
149
|
+
const base = process.env.HUAWEICLOUD_HOME || homedir();
|
|
150
|
+
return join(base, '.config', 'huaweicloud', 'devkit-skip.json');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// A1 定稿: 标准 agent 用插件目录副本(有 package.json); codex 等无副本时回退共享文件
|
|
154
|
+
export function resolveSkipFilePath() {
|
|
155
|
+
try {
|
|
156
|
+
if (existsSync(join(dirname(skipFilePath()), 'package.json'))) return skipFilePath();
|
|
157
|
+
} catch {}
|
|
158
|
+
return fallbackSkipFilePath();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function readSkipState(file) {
|
|
162
|
+
if (!existsSync(file)) return null;
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
165
|
+
if (!parsed || typeof parsed.dismissedVersion !== 'string' || !parsed.dismissedAt || !parsed.expireAt) return null;
|
|
166
|
+
return parsed;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function writeSkipState(file, dismissedVersion, { at = Date.now(), days = COOLDOWN_DAYS } = {}) {
|
|
173
|
+
const state = {
|
|
174
|
+
dismissedVersion: String(dismissedVersion),
|
|
175
|
+
dismissedAt: new Date(at).toISOString(),
|
|
176
|
+
expireAt: new Date(at + days * 24 * 60 * 60 * 1000).toISOString(),
|
|
177
|
+
};
|
|
178
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
179
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
180
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf8');
|
|
181
|
+
try {
|
|
182
|
+
renameSync(tmp, file);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
rmSync(tmp, { force: true });
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
return state;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function queryDistTagsSync({ timeoutMs = 5000, cwd } = {}) {
|
|
191
|
+
try {
|
|
192
|
+
const result = spawnSync(NPM_BIN, ['view', 'huaweicloud-devkit', 'dist-tags', '--json'], {
|
|
193
|
+
encoding: 'utf8',
|
|
194
|
+
timeout: timeoutMs,
|
|
195
|
+
windowsHide: true,
|
|
196
|
+
cwd,
|
|
197
|
+
});
|
|
198
|
+
if (result.status !== 0) return null;
|
|
199
|
+
return parseDistTagsOutput(result.stdout);
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function queryDistTags({ timeoutMs = 5000, cwd } = {}) {
|
|
206
|
+
return new Promise((resolve) => {
|
|
207
|
+
let child;
|
|
208
|
+
try {
|
|
209
|
+
child = spawn(NPM_BIN, ['view', 'huaweicloud-devkit', 'dist-tags', '--json'], {
|
|
210
|
+
windowsHide: true,
|
|
211
|
+
cwd,
|
|
212
|
+
});
|
|
213
|
+
} catch {
|
|
214
|
+
resolve(null);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const timer = setTimeout(() => {
|
|
218
|
+
try {
|
|
219
|
+
child.kill();
|
|
220
|
+
} catch {}
|
|
221
|
+
resolve(null);
|
|
222
|
+
}, timeoutMs);
|
|
223
|
+
let stdout = '';
|
|
224
|
+
child.stdout.on('data', (d) => {
|
|
225
|
+
stdout += String(d);
|
|
226
|
+
});
|
|
227
|
+
child.on('error', () => {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
resolve(null);
|
|
230
|
+
});
|
|
231
|
+
child.on('close', (code) => {
|
|
232
|
+
clearTimeout(timer);
|
|
233
|
+
if (code === 0) resolve(parseDistTagsOutput(stdout));
|
|
234
|
+
else resolve(null);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let cachedDistTags = null;
|
|
240
|
+
let cachedAt = 0;
|
|
241
|
+
let failedAt = 0;
|
|
242
|
+
let inflightQuery = null;
|
|
243
|
+
let lastHint = null;
|
|
244
|
+
|
|
245
|
+
export function invalidateUpdateCache() {
|
|
246
|
+
cachedDistTags = null;
|
|
247
|
+
cachedAt = 0;
|
|
248
|
+
failedAt = 0;
|
|
249
|
+
inflightQuery = null;
|
|
250
|
+
lastHint = null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function cacheValid() {
|
|
254
|
+
return Boolean(cachedDistTags) && Date.now() - cachedAt <= TTL_MS;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function getCachedUpdateInfo(current, { doQuery = queryDistTags } = {}) {
|
|
258
|
+
if (process.env.HUAWEICLOUD_DEVKIT_SKIP_UPDATE === '1') {
|
|
259
|
+
lastHint = judgeUpdate(current, null);
|
|
260
|
+
return lastHint;
|
|
261
|
+
}
|
|
262
|
+
const skipState = readSkipState(resolveSkipFilePath());
|
|
263
|
+
if (!cacheValid()) {
|
|
264
|
+
if (!cachedDistTags && Date.now() - failedAt < FAIL_THROTTLE_MS) {
|
|
265
|
+
lastHint = judgeUpdate(current, null, skipState);
|
|
266
|
+
return lastHint;
|
|
267
|
+
}
|
|
268
|
+
if (!inflightQuery) {
|
|
269
|
+
inflightQuery = doQuery()
|
|
270
|
+
.then((distTags) => {
|
|
271
|
+
if (distTags) {
|
|
272
|
+
cachedDistTags = distTags;
|
|
273
|
+
cachedAt = Date.now();
|
|
274
|
+
} else {
|
|
275
|
+
failedAt = Date.now();
|
|
276
|
+
}
|
|
277
|
+
return distTags;
|
|
278
|
+
})
|
|
279
|
+
.finally(() => {
|
|
280
|
+
inflightQuery = null;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const distTags = await inflightQuery;
|
|
284
|
+
lastHint = judgeUpdate(current, distTags, skipState);
|
|
285
|
+
return lastHint;
|
|
286
|
+
}
|
|
287
|
+
lastHint = judgeUpdate(current, cachedDistTags, skipState);
|
|
288
|
+
return lastHint;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function peekCachedUpdateInfo() {
|
|
292
|
+
return lastHint && lastHint.updateAvailable && lastHint.targetVersion ? lastHint : null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function getUpdateDistTags(current) {
|
|
296
|
+
const result = await getCachedUpdateInfo(current);
|
|
297
|
+
if (!result) return null;
|
|
298
|
+
return { latest: result.latestStable ?? null, next: result.latestNext ?? null };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function applyUpdateHint(result, name, hint) {
|
|
302
|
+
if (!hint || !hint.updateAvailable || !hint.targetVersion) return result;
|
|
303
|
+
if (name === 'huaweicloud_check_update' || name === 'huaweicloud_upgrade') return result;
|
|
304
|
+
return {
|
|
305
|
+
...result,
|
|
306
|
+
_updateInfo: { currentVersion: hint.currentVersion, latestVersion: hint.targetVersion },
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function defaultSpawn(command, args, options) {
|
|
311
|
+
return spawnSync(command, args, options);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function restartMessage(target) {
|
|
315
|
+
if (target === 'officeace') {
|
|
316
|
+
return '升级完成,请打开连接器 → 我的连接器 → huaweicloud-devkit → 重新连接后使用新版本。';
|
|
317
|
+
}
|
|
318
|
+
return '升级完成,请重启当前会话使新版本生效。';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export async function upgradePackage({ target = 'all', version = 'latest' } = {}, options = {}) {
|
|
322
|
+
if (version !== 'latest') {
|
|
323
|
+
return { success: false, error: 'version 参数仅支持 latest。目标版本由插件自动判定。' };
|
|
324
|
+
}
|
|
325
|
+
const { doQuery = queryDistTags, spawnFn = defaultSpawn } = options;
|
|
326
|
+
const previousVersion = readInstalledVersion();
|
|
327
|
+
let distTags;
|
|
328
|
+
try {
|
|
329
|
+
distTags = await doQuery();
|
|
330
|
+
} catch (error) {
|
|
331
|
+
return {
|
|
332
|
+
success: false,
|
|
333
|
+
error: error?.message || '无法确认最新版本(registry 查询失败)。',
|
|
334
|
+
manual: `npx --yes huaweicloud-devkit@latest update --target ${target}`,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const targetVersion = determineTarget(previousVersion, distTags ?? {});
|
|
338
|
+
if (distTags === null) {
|
|
339
|
+
return {
|
|
340
|
+
success: false,
|
|
341
|
+
error: '无法确认最新版本(registry 查询失败)。',
|
|
342
|
+
manual: `npx --yes huaweicloud-devkit@latest update --target ${target}`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (!targetVersion) {
|
|
346
|
+
return { success: false, message: '已是最新版本,无需升级。', requiresRestart: false };
|
|
347
|
+
}
|
|
348
|
+
const isNextTarget = Boolean(distTags.next && semverCompare(targetVersion, distTags.next) === 0);
|
|
349
|
+
const tag = isNextTarget ? 'next' : 'latest';
|
|
350
|
+
const command = ['--yes', `huaweicloud-devkit@${tag}`, 'update', '--target', String(target)];
|
|
351
|
+
let execResult;
|
|
352
|
+
try {
|
|
353
|
+
execResult = spawnFn(NPX_BIN, command, { encoding: 'utf8', timeout: 300000, windowsHide: true });
|
|
354
|
+
} catch (error) {
|
|
355
|
+
return {
|
|
356
|
+
success: false,
|
|
357
|
+
error: error?.message || '升级命令执行失败。',
|
|
358
|
+
manual: `npx --yes huaweicloud-devkit@${tag} update --target ${target}`,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (execResult.status === 0) {
|
|
362
|
+
invalidateUpdateCache();
|
|
363
|
+
return {
|
|
364
|
+
success: true,
|
|
365
|
+
previousVersion,
|
|
366
|
+
installedVersion: targetVersion,
|
|
367
|
+
requiresRestart: true,
|
|
368
|
+
message: restartMessage(target),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const errObj = execResult.error;
|
|
372
|
+
const stderr = String(execResult.stderr || '').trim();
|
|
373
|
+
const reason = errObj?.message
|
|
374
|
+
? errObj.message
|
|
375
|
+
: stderr
|
|
376
|
+
? stderr.split(/\r?\n/).filter(Boolean).slice(-2).join(' ')
|
|
377
|
+
: `exit ${execResult.status}`;
|
|
378
|
+
return {
|
|
379
|
+
success: false,
|
|
380
|
+
error: reason,
|
|
381
|
+
manual: `npx --yes huaweicloud-devkit@${tag} update --target ${target}`,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
import {
|
|
2
11
|
DEFAULT_TIMEOUT_MS,
|
|
3
12
|
WebSocketExecError,
|
|
@@ -93,9 +102,11 @@ function createHwlinkTerminal(options = {}) {
|
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
function handleClose() {
|
|
96
|
-
settleReady(
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
settleReady(
|
|
106
|
+
new WebSocketExecError('hwlink terminal closed before ready', 1, {
|
|
107
|
+
phase: 'opening',
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
99
110
|
if (closed) return;
|
|
100
111
|
closed = true;
|
|
101
112
|
if (onClose) onClose();
|
|
@@ -177,10 +188,12 @@ class HwlinkTerminalExecSession {
|
|
|
177
188
|
});
|
|
178
189
|
|
|
179
190
|
this.readyTimeout = setTimeout(() => {
|
|
180
|
-
this.fail(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
this.fail(
|
|
192
|
+
new WebSocketExecError(`hwlink terminal ready timeout after ${this.timeoutMs}ms`, 124, {
|
|
193
|
+
partialOutput: this.readyBuffer,
|
|
194
|
+
phase: 'opening',
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
184
197
|
}, this.timeoutMs);
|
|
185
198
|
|
|
186
199
|
try {
|
|
@@ -198,10 +211,12 @@ class HwlinkTerminalExecSession {
|
|
|
198
211
|
this.term = new HwlinkTerminalChannel(username);
|
|
199
212
|
this.term.onData((data) => this.handleTerminalData(data));
|
|
200
213
|
this.term.onError((error) => {
|
|
201
|
-
this.fail(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
this.fail(
|
|
215
|
+
new WebSocketExecError(`hwlink terminal error: ${error.message}`, 1, {
|
|
216
|
+
cause: error,
|
|
217
|
+
phase: this.state,
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
205
220
|
});
|
|
206
221
|
this.term.onReady(() => {
|
|
207
222
|
if (Number.isFinite(this.initialCols) && Number.isFinite(this.initialRows)) {
|
|
@@ -211,24 +226,30 @@ class HwlinkTerminalExecSession {
|
|
|
211
226
|
});
|
|
212
227
|
this.term.onClose(() => {
|
|
213
228
|
if (this.state !== 'closed') {
|
|
214
|
-
this.fail(
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
this.fail(
|
|
230
|
+
new WebSocketExecError('hwlink terminal closed before completion marker', 1, {
|
|
231
|
+
phase: this.state,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
217
234
|
}
|
|
218
235
|
});
|
|
219
236
|
this.mux.onClose = () => {
|
|
220
237
|
if (this.state !== 'closed') {
|
|
221
|
-
this.fail(
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
this.fail(
|
|
239
|
+
new WebSocketExecError('hwlink websocket closed before completion marker', 1, {
|
|
240
|
+
phase: this.state,
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
224
243
|
}
|
|
225
244
|
};
|
|
226
245
|
this.mux.onError = (error) => {
|
|
227
246
|
if (this.state !== 'closed') {
|
|
228
|
-
this.fail(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
this.fail(
|
|
248
|
+
new WebSocketExecError(`hwlink websocket error: ${error.message}`, 1, {
|
|
249
|
+
cause: error,
|
|
250
|
+
phase: this.state,
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
232
253
|
}
|
|
233
254
|
};
|
|
234
255
|
|
|
@@ -259,16 +280,20 @@ class HwlinkTerminalExecSession {
|
|
|
259
280
|
this.pending = null;
|
|
260
281
|
|
|
261
282
|
if (wasOpening) {
|
|
262
|
-
this.rejectReady(
|
|
263
|
-
|
|
264
|
-
|
|
283
|
+
this.rejectReady(
|
|
284
|
+
new WebSocketExecError('hwlink terminal session closed before ready', 1, {
|
|
285
|
+
phase: 'opening',
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
265
288
|
}
|
|
266
289
|
|
|
267
290
|
if (pending) {
|
|
268
291
|
clearTimeout(pending.timeout);
|
|
269
|
-
pending.reject(
|
|
270
|
-
|
|
271
|
-
|
|
292
|
+
pending.reject(
|
|
293
|
+
new WebSocketExecError('hwlink terminal session closed before completion marker', 1, {
|
|
294
|
+
phase: 'running',
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
272
297
|
}
|
|
273
298
|
|
|
274
299
|
this.term.close();
|
|
@@ -328,9 +353,11 @@ class HwlinkTerminalExecSession {
|
|
|
328
353
|
|
|
329
354
|
runExec(command, options = {}) {
|
|
330
355
|
if (this.state !== 'ready') {
|
|
331
|
-
return Promise.reject(
|
|
332
|
-
|
|
333
|
-
|
|
356
|
+
return Promise.reject(
|
|
357
|
+
new WebSocketExecError('hwlink terminal exec session is not ready', 1, {
|
|
358
|
+
phase: this.state,
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
334
361
|
}
|
|
335
362
|
|
|
336
363
|
const shellCommand = normalizeCommand(command);
|
|
@@ -347,10 +374,12 @@ class HwlinkTerminalExecSession {
|
|
|
347
374
|
reject,
|
|
348
375
|
resolve,
|
|
349
376
|
timeout: setTimeout(() => {
|
|
350
|
-
this.fail(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
this.fail(
|
|
378
|
+
new WebSocketExecError(`hwlink terminal exec timeout after ${timeoutMs}ms`, 124, {
|
|
379
|
+
partialOutput: this.pending ? this.pending.buffer : '',
|
|
380
|
+
phase: 'running',
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
354
383
|
}, timeoutMs),
|
|
355
384
|
};
|
|
356
385
|
|
|
@@ -363,10 +392,15 @@ class HwlinkTerminalExecSession {
|
|
|
363
392
|
const pending = this.pending;
|
|
364
393
|
if (!pending) return;
|
|
365
394
|
|
|
366
|
-
const
|
|
367
|
-
|
|
395
|
+
const doneMarker = pending.markers.doneMarker;
|
|
396
|
+
const markerIndex = pending.buffer.lastIndexOf(doneMarker);
|
|
397
|
+
if (markerIndex === -1) return;
|
|
398
|
+
|
|
399
|
+
const afterMarker = pending.buffer.slice(markerIndex + doneMarker.length);
|
|
400
|
+
const exitMatch = afterMarker.match(/^(\d+)/);
|
|
401
|
+
if (!exitMatch) return;
|
|
368
402
|
|
|
369
|
-
const rawOutput = pending.buffer.slice(0,
|
|
403
|
+
const rawOutput = pending.buffer.slice(0, markerIndex);
|
|
370
404
|
const stdout = cleanCommandOutput(rawOutput, {
|
|
371
405
|
inputEchoed: this.inputEchoed,
|
|
372
406
|
command: pending.command,
|
|
@@ -377,7 +411,7 @@ class HwlinkTerminalExecSession {
|
|
|
377
411
|
this.pending = null;
|
|
378
412
|
pending.resolve({
|
|
379
413
|
stdout,
|
|
380
|
-
exitCode: Number(
|
|
414
|
+
exitCode: Number(exitMatch[1]),
|
|
381
415
|
url: this.url,
|
|
382
416
|
source: this.source,
|
|
383
417
|
username: this.username,
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
const DEFAULT_HIGH_WATERMARK = 8 * 1024 * 1024;
|
|
2
11
|
const DEFAULT_LOW_WATERMARK = 2 * 1024 * 1024;
|
|
3
12
|
|
|
@@ -125,8 +134,4 @@ class FairQueue {
|
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
|
|
128
|
-
export {
|
|
129
|
-
DEFAULT_HIGH_WATERMARK,
|
|
130
|
-
DEFAULT_LOW_WATERMARK,
|
|
131
|
-
FairQueue,
|
|
132
|
-
};
|
|
137
|
+
export { DEFAULT_HIGH_WATERMARK, DEFAULT_LOW_WATERMARK, FairQueue };
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
import { Buffer } from 'node:buffer';
|
|
2
11
|
|
|
3
12
|
import { FairQueue } from './hwlink-fair-queue.js';
|
|
@@ -81,15 +90,10 @@ function sendBinary(ws, data) {
|
|
|
81
90
|
|
|
82
91
|
class HwlinkWebSocketMultiplexer {
|
|
83
92
|
constructor(url, source, options = {}) {
|
|
84
|
-
const {
|
|
85
|
-
WebSocketImpl = globalThis.WebSocket,
|
|
86
|
-
protocol = 'devenv',
|
|
87
|
-
onFrame,
|
|
88
|
-
trace = false,
|
|
89
|
-
} = options;
|
|
93
|
+
const { WebSocketImpl = globalThis.WebSocket, protocol = 'devenv', onFrame, trace = false } = options;
|
|
90
94
|
|
|
91
95
|
if (typeof WebSocketImpl !== 'function') {
|
|
92
|
-
throw new
|
|
96
|
+
throw new TypeError('global WebSocket is unavailable; use Node.js 22+ or pass WebSocketImpl');
|
|
93
97
|
}
|
|
94
98
|
if (!Number.isInteger(source) || source < -0x80000000 || source > 0xffffffff) {
|
|
95
99
|
throw new Error('hwlink source must be an int32 or uint32 number');
|
|
@@ -122,9 +126,7 @@ class HwlinkWebSocketMultiplexer {
|
|
|
122
126
|
});
|
|
123
127
|
|
|
124
128
|
addWsListener(this.ws, 'error', (eventOrError) => {
|
|
125
|
-
const error = eventOrError instanceof Error
|
|
126
|
-
? eventOrError
|
|
127
|
-
: new Error('hwlink websocket error');
|
|
129
|
+
const error = eventOrError instanceof Error ? eventOrError : new Error('hwlink websocket error');
|
|
128
130
|
this.handleError(error);
|
|
129
131
|
});
|
|
130
132
|
|
|
@@ -219,9 +221,4 @@ class HwlinkWebSocketMultiplexer {
|
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
export {
|
|
223
|
-
HwlinkWebSocketMultiplexer,
|
|
224
|
-
addWsListener,
|
|
225
|
-
closeQuietly,
|
|
226
|
-
eventDataToUint8Array,
|
|
227
|
-
};
|
|
224
|
+
export { HwlinkWebSocketMultiplexer, addWsListener, closeQuietly, eventDataToUint8Array };
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
const FIXED_HEADER_LEN = 20;
|
|
2
11
|
const MAX_SEND_CHUNK_SIZE = 8 * 1024;
|
|
3
12
|
|
|
@@ -42,7 +51,7 @@ function isOpTcpTunnelData(op) {
|
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
function isOpFailed(op) {
|
|
45
|
-
return
|
|
54
|
+
return op >> 24 !== 0;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
function isOpCreateTcpTunnel(op) {
|
|
@@ -70,12 +79,7 @@ function hex2(n) {
|
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
function operationToString(op) {
|
|
73
|
-
return [
|
|
74
|
-
hex2((op >> 24) & 0xff),
|
|
75
|
-
hex2((op >> 16) & 0xff),
|
|
76
|
-
hex2((op >> 8) & 0xff),
|
|
77
|
-
hex2(op & 0xff),
|
|
78
|
-
].join(' ');
|
|
82
|
+
return [hex2((op >> 24) & 0xff), hex2((op >> 16) & 0xff), hex2((op >> 8) & 0xff), hex2(op & 0xff)].join(' ');
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
function formatPacketOneLine(packet) {
|
|
@@ -107,15 +111,7 @@ function toUint8Array(bytes) {
|
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
function createPacket(opts) {
|
|
110
|
-
const {
|
|
111
|
-
operation,
|
|
112
|
-
reserve = 0,
|
|
113
|
-
srcPort = 0,
|
|
114
|
-
dstPort = 0,
|
|
115
|
-
identifier = 0,
|
|
116
|
-
source = 0,
|
|
117
|
-
payload = null,
|
|
118
|
-
} = opts;
|
|
114
|
+
const { operation, reserve = 0, srcPort = 0, dstPort = 0, identifier = 0, source = 0, payload = null } = opts;
|
|
119
115
|
|
|
120
116
|
const payloadLen = payload ? payload.length : 0;
|
|
121
117
|
const total = FIXED_HEADER_LEN + payloadLen;
|