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,366 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const FRAMEWORKS = {
|
|
5
|
+
nextjs: {
|
|
6
|
+
type: 'ssr',
|
|
7
|
+
framework: 'Next.js',
|
|
8
|
+
installCmd: 'npm install',
|
|
9
|
+
buildCmd: 'npm run build',
|
|
10
|
+
outputDir: '.next',
|
|
11
|
+
port: 3000,
|
|
12
|
+
serveCmd: 'nohup npm start > /tmp/app.log 2>&1 &',
|
|
13
|
+
checkUrl: 'http://localhost:3000',
|
|
14
|
+
nginxType: 'proxy',
|
|
15
|
+
},
|
|
16
|
+
nuxt: {
|
|
17
|
+
type: 'ssr',
|
|
18
|
+
framework: 'Nuxt',
|
|
19
|
+
installCmd: 'npm install',
|
|
20
|
+
buildCmd: 'npm run build',
|
|
21
|
+
outputDir: '.output',
|
|
22
|
+
port: 3000,
|
|
23
|
+
serveCmd: 'nohup node .output/server/index.mjs > /tmp/app.log 2>&1 &',
|
|
24
|
+
checkUrl: 'http://localhost:3000',
|
|
25
|
+
nginxType: 'proxy',
|
|
26
|
+
},
|
|
27
|
+
vitepress: {
|
|
28
|
+
type: 'ssg',
|
|
29
|
+
framework: 'VitePress',
|
|
30
|
+
installCmd: 'npm install',
|
|
31
|
+
buildCmd: 'npm run docs:build',
|
|
32
|
+
outputDir: '.vitepress/dist',
|
|
33
|
+
port: 8080,
|
|
34
|
+
serveCmd: null,
|
|
35
|
+
checkUrl: 'http://localhost:8080',
|
|
36
|
+
nginxType: 'spa',
|
|
37
|
+
},
|
|
38
|
+
docusaurus: {
|
|
39
|
+
type: 'ssg',
|
|
40
|
+
framework: 'Docusaurus',
|
|
41
|
+
installCmd: 'npm install',
|
|
42
|
+
buildCmd: 'npm run build',
|
|
43
|
+
outputDir: 'build',
|
|
44
|
+
port: 8080,
|
|
45
|
+
serveCmd: null,
|
|
46
|
+
checkUrl: 'http://localhost:8080',
|
|
47
|
+
nginxType: 'spa',
|
|
48
|
+
},
|
|
49
|
+
hugo: {
|
|
50
|
+
type: 'ssg',
|
|
51
|
+
framework: 'Hugo',
|
|
52
|
+
installCmd: null,
|
|
53
|
+
buildCmd: 'hugo',
|
|
54
|
+
outputDir: 'public',
|
|
55
|
+
port: 8080,
|
|
56
|
+
serveCmd: null,
|
|
57
|
+
checkUrl: 'http://localhost:8080',
|
|
58
|
+
nginxType: 'static',
|
|
59
|
+
},
|
|
60
|
+
hexo: {
|
|
61
|
+
type: 'ssg',
|
|
62
|
+
framework: 'Hexo',
|
|
63
|
+
installCmd: 'npm install',
|
|
64
|
+
buildCmd: 'npm run build',
|
|
65
|
+
outputDir: 'public',
|
|
66
|
+
port: 8080,
|
|
67
|
+
serveCmd: null,
|
|
68
|
+
checkUrl: 'http://localhost:8080',
|
|
69
|
+
nginxType: 'static',
|
|
70
|
+
},
|
|
71
|
+
taro: {
|
|
72
|
+
type: 'cross-platform',
|
|
73
|
+
framework: 'Taro',
|
|
74
|
+
installCmd: 'npm install',
|
|
75
|
+
buildCmd: 'npm run build:h5',
|
|
76
|
+
outputDir: 'dist',
|
|
77
|
+
port: 8080,
|
|
78
|
+
serveCmd: null,
|
|
79
|
+
checkUrl: 'http://localhost:8080',
|
|
80
|
+
nginxType: 'spa',
|
|
81
|
+
},
|
|
82
|
+
uniapp: {
|
|
83
|
+
type: 'cross-platform',
|
|
84
|
+
framework: 'uni-app',
|
|
85
|
+
installCmd: 'npm install',
|
|
86
|
+
buildCmd: 'npm run build:h5',
|
|
87
|
+
outputDir: 'dist/build/h5',
|
|
88
|
+
port: 8080,
|
|
89
|
+
serveCmd: null,
|
|
90
|
+
checkUrl: 'http://localhost:8080',
|
|
91
|
+
nginxType: 'spa',
|
|
92
|
+
},
|
|
93
|
+
angular: {
|
|
94
|
+
type: 'spa',
|
|
95
|
+
framework: 'Angular',
|
|
96
|
+
installCmd: 'npm install',
|
|
97
|
+
buildCmd: 'npm run build',
|
|
98
|
+
outputDir: null,
|
|
99
|
+
port: 8080,
|
|
100
|
+
serveCmd: null,
|
|
101
|
+
checkUrl: 'http://localhost:8080',
|
|
102
|
+
nginxType: 'spa',
|
|
103
|
+
},
|
|
104
|
+
vite: {
|
|
105
|
+
type: 'spa',
|
|
106
|
+
framework: 'Vite (React/Vue/Svelte)',
|
|
107
|
+
installCmd: 'npm install',
|
|
108
|
+
buildCmd: 'npm run build',
|
|
109
|
+
outputDir: 'dist',
|
|
110
|
+
port: 8080,
|
|
111
|
+
serveCmd: null,
|
|
112
|
+
checkUrl: 'http://localhost:8080',
|
|
113
|
+
nginxType: 'spa',
|
|
114
|
+
},
|
|
115
|
+
cra: {
|
|
116
|
+
type: 'spa',
|
|
117
|
+
framework: 'Create React App',
|
|
118
|
+
installCmd: 'npm install',
|
|
119
|
+
buildCmd: 'npm run build',
|
|
120
|
+
outputDir: 'build',
|
|
121
|
+
port: 8080,
|
|
122
|
+
serveCmd: null,
|
|
123
|
+
checkUrl: 'http://localhost:8080',
|
|
124
|
+
nginxType: 'spa',
|
|
125
|
+
},
|
|
126
|
+
vueCli: {
|
|
127
|
+
type: 'spa',
|
|
128
|
+
framework: 'Vue CLI',
|
|
129
|
+
installCmd: 'npm install',
|
|
130
|
+
buildCmd: 'npm run build',
|
|
131
|
+
outputDir: 'dist',
|
|
132
|
+
port: 8080,
|
|
133
|
+
serveCmd: null,
|
|
134
|
+
checkUrl: 'http://localhost:8080',
|
|
135
|
+
nginxType: 'spa',
|
|
136
|
+
},
|
|
137
|
+
static: {
|
|
138
|
+
type: 'static',
|
|
139
|
+
framework: 'Static Site',
|
|
140
|
+
installCmd: null,
|
|
141
|
+
buildCmd: null,
|
|
142
|
+
outputDir: '.',
|
|
143
|
+
port: 8080,
|
|
144
|
+
serveCmd: null,
|
|
145
|
+
checkUrl: 'http://localhost:8080',
|
|
146
|
+
nginxType: 'static',
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
function readPkg(projectPath) {
|
|
151
|
+
const pkgPath = join(projectPath, 'package.json');
|
|
152
|
+
if (!existsSync(pkgPath)) return null;
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function detectPackageManager(projectPath, pkg) {
|
|
161
|
+
if (existsSync(join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
162
|
+
if (existsSync(join(projectPath, 'yarn.lock'))) return 'yarn';
|
|
163
|
+
if (existsSync(join(projectPath, 'package-lock.json'))) return 'npm';
|
|
164
|
+
if (existsSync(join(projectPath, 'bun.lockb'))) return 'bun';
|
|
165
|
+
const declared = (pkg?.packageManager || '').split('@')[0];
|
|
166
|
+
if (declared && ['pnpm', 'yarn', 'npm', 'bun'].includes(declared)) return declared;
|
|
167
|
+
return 'npm';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function collectDeps(pkg) {
|
|
171
|
+
if (!pkg) return {};
|
|
172
|
+
return { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function scanSubApps(projectPath) {
|
|
176
|
+
const apps = [];
|
|
177
|
+
for (const dir of ['apps', 'packages']) {
|
|
178
|
+
const dirPath = join(projectPath, dir);
|
|
179
|
+
if (!existsSync(dirPath)) continue;
|
|
180
|
+
try {
|
|
181
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
if (!entry.isDirectory()) continue;
|
|
184
|
+
const subPath = join(dirPath, entry.name);
|
|
185
|
+
const subPkg = readPkg(subPath);
|
|
186
|
+
if (!subPkg) continue;
|
|
187
|
+
const result = detectFramework(subPath);
|
|
188
|
+
if (!result) continue;
|
|
189
|
+
apps.push({
|
|
190
|
+
name: subPkg.name || entry.name,
|
|
191
|
+
path: `${dir}/${entry.name}`,
|
|
192
|
+
framework: result.framework,
|
|
193
|
+
type: result.type,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// skip unreadable directories
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return apps;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function resolveAngularOutput(projectPath, pkg) {
|
|
204
|
+
try {
|
|
205
|
+
const angularJson = join(projectPath, 'angular.json');
|
|
206
|
+
if (existsSync(angularJson)) {
|
|
207
|
+
const config = JSON.parse(readFileSync(angularJson, 'utf8'));
|
|
208
|
+
const defaultProject = config.defaultProject || Object.keys(config.projects || {})[0];
|
|
209
|
+
if (defaultProject && config.projects?.[defaultProject]?.architect?.build?.options?.outputPath) {
|
|
210
|
+
return config.projects[defaultProject].architect.build.options.outputPath;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// ignore parse errors
|
|
215
|
+
}
|
|
216
|
+
const name = pkg?.name || 'app';
|
|
217
|
+
return `dist/${name}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function patchCommands(result, pm) {
|
|
221
|
+
if (result.installCmd && result.installCmd.startsWith('npm install')) {
|
|
222
|
+
if (pm === 'pnpm') result.installCmd = 'pnpm install';
|
|
223
|
+
else if (pm === 'yarn') result.installCmd = 'yarn install';
|
|
224
|
+
}
|
|
225
|
+
if (result.buildCmd && result.buildCmd.startsWith('npm run')) {
|
|
226
|
+
if (pm === 'pnpm') result.buildCmd = result.buildCmd.replace('npm run', 'pnpm run');
|
|
227
|
+
else if (pm === 'yarn') result.buildCmd = result.buildCmd.replace('npm run', 'yarn run');
|
|
228
|
+
}
|
|
229
|
+
if (result.serveCmd && result.serveCmd.includes('npm start')) {
|
|
230
|
+
if (pm === 'pnpm') result.serveCmd = result.serveCmd.replace('npm start', 'pnpm start');
|
|
231
|
+
else if (pm === 'yarn') result.serveCmd = result.serveCmd.replace('npm start', 'yarn start');
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function frameworkResult(fw, pm, projectPath) {
|
|
237
|
+
return patchCommands({ ...fw, packageManager: pm, rootDir: projectPath }, pm);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function readVitepressOutDir(projectPath) {
|
|
241
|
+
const configFiles = ['config.mts', 'config.ts', 'config.mjs', 'config.js'];
|
|
242
|
+
for (const cf of configFiles) {
|
|
243
|
+
const configPath = join(projectPath, '.vitepress', cf);
|
|
244
|
+
if (!existsSync(configPath)) continue;
|
|
245
|
+
try {
|
|
246
|
+
const content = readFileSync(configPath, 'utf8');
|
|
247
|
+
const match = content.match(/outDir\s*:\s*['"`]?([^'"`\s,}]+)['"`]?/);
|
|
248
|
+
if (match) return match[1];
|
|
249
|
+
} catch {}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function detectFramework(projectPath) {
|
|
255
|
+
const pkg = readPkg(projectPath);
|
|
256
|
+
const deps = collectDeps(pkg);
|
|
257
|
+
const pm = detectPackageManager(projectPath, pkg);
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
existsSync(join(projectPath, 'pnpm-workspace.yaml')) ||
|
|
261
|
+
existsSync(join(projectPath, 'turbo.json')) ||
|
|
262
|
+
existsSync(join(projectPath, 'nx.json')) ||
|
|
263
|
+
existsSync(join(projectPath, 'lerna.json'))
|
|
264
|
+
) {
|
|
265
|
+
const monorepoSubApps = scanSubApps(projectPath);
|
|
266
|
+
return {
|
|
267
|
+
type: 'monorepo',
|
|
268
|
+
framework: 'Monorepo',
|
|
269
|
+
monorepoTool: existsSync(join(projectPath, 'turbo.json'))
|
|
270
|
+
? 'Turborepo'
|
|
271
|
+
: existsSync(join(projectPath, 'nx.json'))
|
|
272
|
+
? 'Nx'
|
|
273
|
+
: existsSync(join(projectPath, 'lerna.json'))
|
|
274
|
+
? 'Lerna'
|
|
275
|
+
: 'pnpm Workspace',
|
|
276
|
+
subApps: monorepoSubApps,
|
|
277
|
+
packageManager: pm,
|
|
278
|
+
rootDir: projectPath,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (
|
|
283
|
+
existsSync(join(projectPath, 'next.config.js')) ||
|
|
284
|
+
existsSync(join(projectPath, 'next.config.mjs')) ||
|
|
285
|
+
existsSync(join(projectPath, 'next.config.ts'))
|
|
286
|
+
) {
|
|
287
|
+
return frameworkResult(FRAMEWORKS.nextjs, pm, projectPath);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (
|
|
291
|
+
existsSync(join(projectPath, 'nuxt.config.js')) ||
|
|
292
|
+
existsSync(join(projectPath, 'nuxt.config.mjs')) ||
|
|
293
|
+
existsSync(join(projectPath, 'nuxt.config.ts'))
|
|
294
|
+
) {
|
|
295
|
+
return frameworkResult(FRAMEWORKS.nuxt, pm, projectPath);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if ('@tarojs/taro' in deps) {
|
|
299
|
+
return frameworkResult(FRAMEWORKS.taro, pm, projectPath);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if ('@dcloudio/uni-app' in deps || 'uni-app' in deps) {
|
|
303
|
+
return frameworkResult(FRAMEWORKS.uniapp, pm, projectPath);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (existsSync(join(projectPath, '.vitepress'))) {
|
|
307
|
+
const outDir = readVitepressOutDir(projectPath);
|
|
308
|
+
return frameworkResult(
|
|
309
|
+
outDir ? { ...FRAMEWORKS.vitepress, outputDir: outDir } : FRAMEWORKS.vitepress,
|
|
310
|
+
pm,
|
|
311
|
+
projectPath,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (
|
|
316
|
+
existsSync(join(projectPath, 'docusaurus.config.js')) ||
|
|
317
|
+
existsSync(join(projectPath, 'docusaurus.config.ts')) ||
|
|
318
|
+
existsSync(join(projectPath, 'docusaurus.config.mjs'))
|
|
319
|
+
) {
|
|
320
|
+
return frameworkResult(FRAMEWORKS.docusaurus, pm, projectPath);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (existsSync(join(projectPath, 'hugo.toml')) || existsSync(join(projectPath, 'config.toml'))) {
|
|
324
|
+
return frameworkResult(FRAMEWORKS.hugo, pm, projectPath);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (existsSync(join(projectPath, '_config.yml'))) {
|
|
328
|
+
return frameworkResult(FRAMEWORKS.hexo, pm, projectPath);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (existsSync(join(projectPath, 'angular.json'))) {
|
|
332
|
+
const outputDir = resolveAngularOutput(projectPath, pkg);
|
|
333
|
+
return frameworkResult({ ...FRAMEWORKS.angular, outputDir }, pm, projectPath);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (
|
|
337
|
+
existsSync(join(projectPath, 'vite.config.js')) ||
|
|
338
|
+
existsSync(join(projectPath, 'vite.config.mjs')) ||
|
|
339
|
+
existsSync(join(projectPath, 'vite.config.ts'))
|
|
340
|
+
) {
|
|
341
|
+
return frameworkResult(FRAMEWORKS.vite, pm, projectPath);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if ('@vue/cli-service' in deps) {
|
|
345
|
+
return frameworkResult(FRAMEWORKS.vueCli, pm, projectPath);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (
|
|
349
|
+
'react-scripts' in deps ||
|
|
350
|
+
'react' in deps ||
|
|
351
|
+
'@angular/core' in deps ||
|
|
352
|
+
'vue' in deps ||
|
|
353
|
+
'svelte' in deps ||
|
|
354
|
+
'solid-js' in deps
|
|
355
|
+
) {
|
|
356
|
+
if (existsSync(join(projectPath, 'public', 'index.html')) || existsSync(join(projectPath, 'index.html'))) {
|
|
357
|
+
return frameworkResult(FRAMEWORKS.cra, pm, projectPath);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (existsSync(join(projectPath, 'index.html'))) {
|
|
362
|
+
return frameworkResult(FRAMEWORKS.static, pm, projectPath);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
@@ -1,15 +1,145 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
2
7
|
import { classifyHcloudArgs, redactSecrets, assertAllowed } from './safety-policy.mjs';
|
|
8
|
+
import { getProxySettings } from './proxy/proxy-config.mjs';
|
|
3
9
|
|
|
4
10
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
5
11
|
const DEFAULT_FORCE_KILL_AFTER_MS = 2_000;
|
|
6
12
|
const DEFAULT_MAX_RETRIES = 1;
|
|
13
|
+
const APPROVAL_TTL_MS = 5 * 60_000;
|
|
14
|
+
const LARGE_OUTPUT_THRESHOLD = 50_000;
|
|
15
|
+
const OUTPUT_DIR = join('/tmp', 'huaweicloud-devkit');
|
|
16
|
+
|
|
17
|
+
const approvalStore = new Map();
|
|
18
|
+
|
|
19
|
+
export function createApprovalToken(rawArgs) {
|
|
20
|
+
const token = randomUUID();
|
|
21
|
+
approvalStore.set(token, { rawArgs, createdAt: Date.now() });
|
|
22
|
+
if (approvalStore.size % 20 === 0) {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
for (const [k, v] of approvalStore) {
|
|
25
|
+
if (now - v.createdAt > APPROVAL_TTL_MS) approvalStore.delete(k);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function consumeApprovalToken(token) {
|
|
32
|
+
const entry = approvalStore.get(token);
|
|
33
|
+
if (!entry) return null;
|
|
34
|
+
if (Date.now() - entry.createdAt > APPROVAL_TTL_MS) {
|
|
35
|
+
approvalStore.delete(token);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
approvalStore.delete(token);
|
|
39
|
+
return entry.rawArgs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function saveLargeOutput(rawStdout) {
|
|
43
|
+
if (rawStdout.length <= LARGE_OUTPUT_THRESHOLD) return null;
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
46
|
+
const filePath = join(OUTPUT_DIR, `output-${Date.now()}.json`);
|
|
47
|
+
writeFileSync(filePath, rawStdout, { encoding: 'utf8' });
|
|
48
|
+
return filePath;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function preflightSecurityGroupCheck(normalizedArgs) {
|
|
55
|
+
const service = normalizedArgs[0];
|
|
56
|
+
const operation = normalizedArgs[1];
|
|
57
|
+
if (service !== 'ECS' || !/CreateServers|RunInstances/i.test(operation)) return [];
|
|
58
|
+
|
|
59
|
+
const sgIds = [];
|
|
60
|
+
for (const arg of normalizedArgs) {
|
|
61
|
+
const m = arg.match(/^(?:--security_group_id(?:\.\d+)?|--server\.security_groups(?:\.\d+)?\.id)=(.+)/);
|
|
62
|
+
if (m) sgIds.push(m[1]);
|
|
63
|
+
}
|
|
64
|
+
if (sgIds.length === 0) return [];
|
|
65
|
+
|
|
66
|
+
const regionArg = normalizedArgs.find((a) => a.startsWith('--cli-region='));
|
|
67
|
+
const findings = [];
|
|
68
|
+
for (const sgId of sgIds) {
|
|
69
|
+
try {
|
|
70
|
+
const hcloudBin = process.env.HCLOUD_BIN || 'hcloud';
|
|
71
|
+
const spawnArgs = ['VPC', 'ListSecurityGroupRules', `--security_group_id.1=${sgId}`];
|
|
72
|
+
if (regionArg) spawnArgs.push(regionArg);
|
|
73
|
+
const r = spawnSync(hcloudBin, spawnArgs, {
|
|
74
|
+
shell: false,
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
stdio: 'pipe',
|
|
77
|
+
timeout: 20000,
|
|
78
|
+
});
|
|
79
|
+
const stdout = (r.stdout || '').toString();
|
|
80
|
+
if (r.status !== 0 || !stdout.includes('security_group_rules')) continue;
|
|
81
|
+
|
|
82
|
+
const bracketIdx = stdout.indexOf('{');
|
|
83
|
+
const jsonText = bracketIdx >= 0 ? stdout.slice(bracketIdx) : stdout;
|
|
84
|
+
const data = JSON.parse(jsonText);
|
|
85
|
+
const rules = data?.security_group_rules || [];
|
|
86
|
+
|
|
87
|
+
for (const rule of rules) {
|
|
88
|
+
const direction = rule.direction || '';
|
|
89
|
+
const remoteIp = rule.remote_ip_prefix || rule.remote_address_group_id || '';
|
|
90
|
+
const portMin = String(rule.port_range_min || rule.multiport || '');
|
|
91
|
+
const portMax = String(rule.port_range_max || rule.multiport || '');
|
|
92
|
+
const protocol = (rule.protocol || '').toLowerCase();
|
|
93
|
+
if (direction !== 'ingress') continue;
|
|
94
|
+
if (remoteIp !== '0.0.0.0/0' && remoteIp !== '::/0') continue;
|
|
95
|
+
if (protocol === 'icmp' || protocol === 'icmpv6') {
|
|
96
|
+
findings.push({
|
|
97
|
+
severity: 'warn',
|
|
98
|
+
title: `Security group ${sgId}: ICMP open to public`,
|
|
99
|
+
message: `安全组 ${sgId} 对公网开放了 ICMP (ping),可能被用于探测。是否继续?`,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const sensitivePorts = ['22', '3389', '3306', '5432', '6379', '9200', '27017', '8080', '8443'];
|
|
104
|
+
const port = portMin || portMax;
|
|
105
|
+
if (port && sensitivePorts.includes(port)) {
|
|
106
|
+
findings.push({
|
|
107
|
+
severity: 'deny',
|
|
108
|
+
title: `Security group ${sgId}: port ${port} open to public`,
|
|
109
|
+
message: `安全组 ${sgId} 已对公网开放 ${port} 端口(${protocol || 'TCP'})。确认继续创建 ECS?`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Preflight failed silently; don't block the plan
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function applyPreflightFindings(classification, sgFindings) {
|
|
121
|
+
if (!sgFindings || sgFindings.length === 0) return classification;
|
|
122
|
+
const hasDeny = sgFindings.some((f) => f.severity === 'deny');
|
|
123
|
+
if (hasDeny) {
|
|
124
|
+
return {
|
|
125
|
+
...classification,
|
|
126
|
+
decision: 'deny',
|
|
127
|
+
risk: 'public_exposure',
|
|
128
|
+
reason: sgFindings[0].message,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return classification;
|
|
132
|
+
}
|
|
7
133
|
|
|
8
134
|
export function planHcloudCommand(args, options = {}) {
|
|
9
135
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
|
|
10
136
|
const classification = classifyHcloudArgs(normalizedArgs, options);
|
|
11
|
-
const command = ['hcloud', ...normalizedArgs].map(quoteShellArg).join(' ');
|
|
137
|
+
const command = ['hcloud', ...normalizedArgs].map((arg) => quoteShellArg(arg)).join(' ');
|
|
12
138
|
const warnings = planningWarnings(normalizedArgs);
|
|
139
|
+
const sgFindings = preflightSecurityGroupCheck(normalizedArgs);
|
|
140
|
+
if (sgFindings.length > 0) {
|
|
141
|
+
for (const f of sgFindings) warnings.push(f);
|
|
142
|
+
}
|
|
13
143
|
const paramValidation = validateRequiredParams(normalizedArgs);
|
|
14
144
|
if (paramValidation.missing.length > 0) {
|
|
15
145
|
warnings.push('Missing required parameters: ' + paramValidation.missing.join(', '));
|
|
@@ -23,7 +153,9 @@ export function planHcloudCommand(args, options = {}) {
|
|
|
23
153
|
command: redactOutput(command),
|
|
24
154
|
executableBlock: redactOutput(command),
|
|
25
155
|
warnings,
|
|
26
|
-
classification,
|
|
156
|
+
classification: applyPreflightFindings(classification, sgFindings),
|
|
157
|
+
sgFindings,
|
|
158
|
+
approvalToken: createApprovalToken(normalizedArgs),
|
|
27
159
|
safeToRun: classification.decision === 'allow',
|
|
28
160
|
};
|
|
29
161
|
}
|
|
@@ -40,39 +172,78 @@ export async function runHcloud(args, options = {}) {
|
|
|
40
172
|
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
41
173
|
const result = await runHcloudOnce(plan, options);
|
|
42
174
|
if (result.ok || attempt >= maxRetries || !isRetryableNetworkError(result)) {
|
|
43
|
-
|
|
175
|
+
const merged = {
|
|
44
176
|
...result,
|
|
45
177
|
retries: attempt,
|
|
46
178
|
attempts: attempt + 1,
|
|
47
179
|
};
|
|
180
|
+
if (merged.ok) {
|
|
181
|
+
const warning = await runtimeCurrentMismatchWarning();
|
|
182
|
+
return warning ? { ...merged, authWarning: warning } : merged;
|
|
183
|
+
}
|
|
184
|
+
return merged;
|
|
48
185
|
}
|
|
49
|
-
await wait((options.retryBaseDelayMs ?? 500) *
|
|
186
|
+
await wait((options.retryBaseDelayMs ?? 500) * 2 ** attempt);
|
|
50
187
|
}
|
|
51
188
|
throw new Error('Unreachable retry state.');
|
|
52
189
|
}
|
|
53
190
|
|
|
191
|
+
async function runtimeCurrentMismatchWarning() {
|
|
192
|
+
try {
|
|
193
|
+
const { hasRuntimeCredentials, scanState } = await import('./auth/reconcile.mjs');
|
|
194
|
+
if (!hasRuntimeCredentials()) return null;
|
|
195
|
+
const scan = scanState();
|
|
196
|
+
const { runtimeFingerprint, currentFingerprint } = scan.stores;
|
|
197
|
+
if (runtimeFingerprint && currentFingerprint && runtimeFingerprint !== currentFingerprint) {
|
|
198
|
+
return '会话内临时账号与 KooCLI current 档不一致:hcloud 命令仍使用 current 档账号。如需对齐请用 huaweicloud_auth_switch action=persist。';
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
} catch {
|
|
202
|
+
// reconcile unavailable → no warning, never block the command
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function discoverHcloudPath() {
|
|
208
|
+
if (process.env.HCLOUD_BIN && existsSync(process.env.HCLOUD_BIN)) return process.env.HCLOUD_BIN;
|
|
209
|
+
const candidates =
|
|
210
|
+
process.platform === 'win32'
|
|
211
|
+
? [join(homedir(), 'hcloud', 'hcloud.exe')]
|
|
212
|
+
: [join(homedir(), '.local', 'bin', 'hcloud'), join(homedir(), 'hcloud', 'hcloud')];
|
|
213
|
+
return candidates.find((c) => existsSync(c)) || null;
|
|
214
|
+
}
|
|
215
|
+
|
|
54
216
|
function runHcloudOnce(plan, options) {
|
|
55
217
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
56
218
|
const forceKillAfterMs = options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_AFTER_MS;
|
|
57
|
-
const executable = options.executable || options.env?.HCLOUD_BIN ||
|
|
219
|
+
const executable = options.executable || options.env?.HCLOUD_BIN || discoverHcloudPath() || 'hcloud';
|
|
58
220
|
const executableArgs = Array.isArray(options.executableArgs) ? options.executableArgs.map(String) : [];
|
|
59
221
|
const cwd = options.cwd || undefined;
|
|
222
|
+
const stdin = options.stdin ?? 'y\n';
|
|
60
223
|
|
|
61
224
|
return new Promise((resolve) => {
|
|
225
|
+
const proxySettings = getProxySettings();
|
|
226
|
+
const proxyEnv = {};
|
|
227
|
+
if (proxySettings) {
|
|
228
|
+
if (proxySettings.https_proxy) proxyEnv.HTTPS_PROXY = proxySettings.https_proxy;
|
|
229
|
+
if (proxySettings.http_proxy) proxyEnv.HTTP_PROXY = proxySettings.http_proxy;
|
|
230
|
+
if (proxySettings.no_proxy) proxyEnv.NO_PROXY = proxySettings.no_proxy;
|
|
231
|
+
}
|
|
62
232
|
const child = spawn(executable, [...executableArgs, ...plan.rawArgs], {
|
|
63
233
|
shell: false,
|
|
64
234
|
windowsHide: true,
|
|
65
235
|
cwd,
|
|
66
236
|
env: {
|
|
67
237
|
...process.env,
|
|
238
|
+
...proxyEnv,
|
|
68
239
|
...options.env,
|
|
69
240
|
},
|
|
70
241
|
});
|
|
71
|
-
if (
|
|
72
|
-
if (typeof
|
|
73
|
-
|
|
242
|
+
if (stdin) {
|
|
243
|
+
if (typeof stdin === 'function') {
|
|
244
|
+
stdin(child.stdin);
|
|
74
245
|
} else {
|
|
75
|
-
child.stdin.write(String(
|
|
246
|
+
child.stdin.write(String(stdin));
|
|
76
247
|
child.stdin.end();
|
|
77
248
|
}
|
|
78
249
|
}
|
|
@@ -89,6 +260,13 @@ function runHcloudOnce(plan, options) {
|
|
|
89
260
|
clearTimeout(timer);
|
|
90
261
|
clearTimeout(forceTimer);
|
|
91
262
|
clearTimeout(settleTimer);
|
|
263
|
+
if (result.stdout && String(result.stdout).length > LARGE_OUTPUT_THRESHOLD) {
|
|
264
|
+
const outputFile = saveLargeOutput(stdout);
|
|
265
|
+
if (outputFile) {
|
|
266
|
+
result.outputFile = outputFile;
|
|
267
|
+
result.stdout = String(result.stdout).slice(0, 2000) + `\n...(truncated, full output saved to ${outputFile})`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
92
270
|
resolve(result);
|
|
93
271
|
}
|
|
94
272
|
|
|
@@ -151,7 +329,10 @@ function runHcloudOnce(plan, options) {
|
|
|
151
329
|
return;
|
|
152
330
|
}
|
|
153
331
|
finish({
|
|
154
|
-
ok:
|
|
332
|
+
ok:
|
|
333
|
+
code === 0 ||
|
|
334
|
+
(code !== 0 &&
|
|
335
|
+
/successfully|succ?ess.*\[200\]|create bucket successfully|upload successfully/i.test(stdout + stderr)),
|
|
155
336
|
exitCode: code,
|
|
156
337
|
signal,
|
|
157
338
|
stdout: redactOutput(stdout),
|
|
@@ -165,7 +346,9 @@ function runHcloudOnce(plan, options) {
|
|
|
165
346
|
function isRetryableNetworkError(result) {
|
|
166
347
|
if (result.code === 'TIMEOUT') return false;
|
|
167
348
|
const text = `${result.error || ''}\n${result.stdout || ''}\n${result.stderr || ''}`;
|
|
168
|
-
return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(
|
|
349
|
+
return /\[NETWORK_ERROR\]|connection timed out|ECONNRESET|ETIMEDOUT|temporary failure|TLS handshake timeout/i.test(
|
|
350
|
+
text,
|
|
351
|
+
);
|
|
169
352
|
}
|
|
170
353
|
|
|
171
354
|
function wait(ms) {
|
|
@@ -194,7 +377,11 @@ const REQUIRED_PARAMS = {
|
|
|
194
377
|
'ECS CreateServers': ['server.flavorRef', 'server.imageRef', 'server.nics.1.subnet_id'],
|
|
195
378
|
'VPC CreateVpc': ['vpc.cidr'],
|
|
196
379
|
'VPC CreateSubnet': ['subnet.vpc_id', 'subnet.cidr'],
|
|
197
|
-
'VPC CreateSecurityGroupRule': [
|
|
380
|
+
'VPC CreateSecurityGroupRule': [
|
|
381
|
+
'security_group_rule.security_group_id',
|
|
382
|
+
'security_group_rule.direction',
|
|
383
|
+
'security_group_rule.protocol',
|
|
384
|
+
],
|
|
198
385
|
'EIP CreatePublicip': ['bandwidth.share_type', 'publicip.type'],
|
|
199
386
|
'FunctionGraph CreateFunction': ['func_name', 'runtime', 'handler', 'memory_size', 'package', 'timeout'],
|
|
200
387
|
'FunctionGraph CreateFunctionTrigger': ['function_urn', 'trigger_type_code'],
|
|
@@ -208,9 +395,9 @@ const PARAM_VALUE_HINTS = {
|
|
|
208
395
|
'server.imageRef': 'Run `hcloud IMS ListImages --cli-region=<r> --__imagetype=gold` to find valid image IDs',
|
|
209
396
|
'server.nics.1.subnet_id': 'Run `hcloud VPC ListSubnets --cli-region=<r>` to find subnet IDs',
|
|
210
397
|
'subnet.vpc_id': 'Run `hcloud VPC ListVpcs --cli-region=<r>` to find VPC IDs',
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
398
|
+
func_name: 'Function name must be unique within project',
|
|
399
|
+
runtime: 'Run `hcloud FunctionGraph ListRuntimes` to see available runtimes',
|
|
400
|
+
spec_id: 'APIG spec: BASIC (no public IP) or PROFESSIONAL (requires --loadbalancer_provider)',
|
|
214
401
|
'obs://': 'Bucket name must be globally unique and DNS-compliant (lowercase, numbers, hyphens only)',
|
|
215
402
|
};
|
|
216
403
|
|