fix(hwlab): cache yaml in node tools image (#808)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-24 10:34:05 +08:00
committed by GitHub
parent fc7047d467
commit cac291d7af
2 changed files with 10 additions and 49 deletions
+2
View File
@@ -152,6 +152,8 @@ targets:
- COPY --from=python-runtime /usr/local /usr/local
- COPY --from=docker-cli-runtime /usr/local/bin/docker /usr/local/bin/docker
- RUN ln -sf /usr/local/bin/bun /usr/local/bin/bunx
- ENV NODE_PATH=/usr/local/lib/node_modules
- RUN npm install -g --omit=dev --ignore-scripts --no-audit --no-fund yaml@2.8.3 && NODE_PATH=/usr/local/lib/node_modules node -e "require.resolve('yaml')"
- RUN node --version && npm --version && bun --version && git --version && python3 --version && docker --version && ssh -V
buildArgs:
NODE_IMAGE: docker.io/library/node:22-bookworm-slim
+8 -49
View File
@@ -4948,60 +4948,18 @@ function nodeRuntimePipelinePostprocessScript(): string[] {
"};",
"const stepEnv = { ...proxyEnv, ...dockerProxyEnv, ...(overlay.stepEnv || {}) };",
"function prepareSourceDependencyScript() {",
" const registry = String(overlay.npmRegistry || 'https://registry.npmjs.org/');",
" const timeoutSeconds = Math.max(15, Math.ceil(Number(overlay.npmFetchTimeoutMs || 120000) / 1000));",
" const retryCount = Math.max(0, Math.floor(Number(overlay.npmRetries || 3)));",
" return `prepare_source_dependencies_started_ms=\"$(ci_now_ms)\"",
"node <<'NODE_UNIDESK_YAML_DEPENDENCY'",
"const { spawnSync } = require('node:child_process');",
"const fs = require('node:fs');",
"const os = require('node:os');",
"const path = require('node:path');",
"const registry = ${JSON.stringify(registry)};",
"const timeoutMs = ${JSON.stringify(timeoutSeconds * 1000)};",
"const timeoutSeconds = ${JSON.stringify(timeoutSeconds)};",
"const retryCount = ${JSON.stringify(retryCount)};",
"const dependency = 'yaml';",
"const version = '2.8.3';",
"function emit(status, extra = {}) { console.error(JSON.stringify({ event: 'prepare-source-dependencies', status, dependency, ...extra })); }",
"function hasYaml() { try { require.resolve('yaml'); return true; } catch { return false; } }",
"function run(command, args) {",
" const result = spawnSync(command, args, { stdio: 'inherit', env: process.env, timeout: timeoutMs });",
" if (result.error) console.error(JSON.stringify({ event: 'prepare-source-dependencies', status: 'command-error', command, error: result.error.message }));",
" return result.status === 0;",
"try {",
" const resolved = require.resolve('yaml');",
" emit('cached', { manager: 'tools-image', resolved });",
" process.exit(0);",
"} catch (error) {",
" emit('failed', { reason: 'tools-image-missing-yaml', message: error && error.message ? error.message : String(error) });",
" process.exit(34);",
"}",
"function tailNpmLog() {",
" const dir = path.join(process.env.HOME || '/tmp', '.npm', '_logs');",
" if (!fs.existsSync(dir)) return;",
" const files = fs.readdirSync(dir).filter((name) => name.includes('debug') && name.endsWith('.log')).sort();",
" const file = files[files.length - 1];",
" if (!file) return;",
" const full = path.join(dir, file);",
" console.error(JSON.stringify({ event: 'prepare-source-dependencies', status: 'npm-debug-log', path: full }));",
" const lines = fs.readFileSync(full, 'utf8').split(String.fromCharCode(10));",
" console.error(lines.slice(-80).join(String.fromCharCode(10)));",
"}",
"if (hasYaml()) { emit('cached'); process.exit(0); }",
"fs.mkdirSync('node_modules/yaml', { recursive: true });",
"let registryBase = registry;",
"while (registryBase.endsWith('/')) registryBase = registryBase.slice(0, -1);",
"const tarball = registryBase + '/yaml/-/yaml-' + version + '.tgz';",
"const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hwlab-yaml-'));",
"const tgz = path.join(tmpDir, 'yaml.tgz');",
"if (run('curl', ['-fsSL', '--retry', String(retryCount), '--connect-timeout', '10', '--max-time', String(timeoutSeconds), '-o', tgz, tarball]) && run('tar', ['-xzf', tgz, '-C', 'node_modules/yaml', '--strip-components=1'])) emit('installed', { manager: 'tarball' });",
"else emit('tarball-failed', { manager: 'tarball' });",
"fs.rmSync(tmpDir, { recursive: true, force: true });",
"if (!hasYaml()) {",
" fs.rmSync('node_modules/yaml', { recursive: true, force: true });",
" if (run('bun', ['add', '--no-save', '--ignore-scripts', '--registry', registry, 'yaml@' + version]) && hasYaml()) emit('installed', { manager: 'bun' });",
" else emit('bun-failed', { manager: 'bun' });",
"}",
"if (!hasYaml()) {",
" fs.rmSync('node_modules/yaml', { recursive: true, force: true });",
" if (run('npm', ['install', '--package-lock=false', '--no-save', '--ignore-scripts', '--no-audit', '--no-fund', '--omit=dev', '--registry', registry, 'yaml@' + version]) && hasYaml()) emit('installed', { manager: 'npm' });",
" else { emit('npm-failed', { manager: 'npm' }); tailNpmLog(); process.exit(34); }",
"}",
"if (!hasYaml()) { emit('failed', { reason: 'unresolved' }); process.exit(34); }",
"NODE_UNIDESK_YAML_DEPENDENCY",
"ci_timing_emit prepare-source-dependencies succeeded \"$prepare_source_dependencies_started_ms\"`;",
"}",
@@ -5902,6 +5860,7 @@ function nodeRuntimePipelinePostprocessScript(): string[] {
"if (!structured && !changed && !text.includes(`--gitops-root ${quotedRoot}`) && !text.includes(`--gitops-root ${escapedQuotedRoot}`)) { throw new Error(`generated pipeline missing expected gitops-render invocation in ${pipelinePath}`); }",
"if (text.includes('prepare_source_dependencies_started_ms=\"$(ci_now_ms)\"') && text.includes('npm ci --ignore-scripts --no-audit --prefer-offline')) { throw new Error(`generated pipeline still uses full npm ci prepare-source dependency install in ${pipelinePath}`); }",
"if (text.includes('prepare_source_dependencies_started_ms=\"$(ci_now_ms)\"') && !text.includes('NODE_UNIDESK_YAML_DEPENDENCY')) { throw new Error(`generated pipeline missing UniDesk yaml dependency install in ${pipelinePath}`); }",
"if (text.includes('/yaml/-/yaml-') || text.includes('bun add --no-save --ignore-scripts') || text.includes('npm install --package-lock=false --no-save')) { throw new Error(`generated pipeline still downloads yaml during prepare-source in ${pipelinePath}`); }",
"if (text.includes('npm run gitops:ts:check')) { throw new Error(`generated pipeline still uses npm gitops:ts:check gate in ${pipelinePath}`); }",
"fs.writeFileSync(pipelinePath, text);",
"patchGitMirrorTransportYaml();",