From 302ce58e98adcff3860e8f690fcbd237831e4e9f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 16 May 2026 07:40:29 +0000 Subject: [PATCH] harden UI builder artifact bootstrap with verified pinned metadata (#9189) * feat: harden UI builder artifact bootstrap with verified pinned metadata * fix: emit tab-indented artifact json to match prettier config * refactor: rewrite artifact json with node instead of python * refactor: simplify bootstrap to flat script, drop test scaffolding --- frontend/scripts/ui_builder_artifact.json | 5 ++ frontend/scripts/untar_ui_builder.js | 73 +++++++++++------------ frontend/use_latest_ui_builder.sh | 30 ++++++---- 3 files changed, 59 insertions(+), 49 deletions(-) create mode 100644 frontend/scripts/ui_builder_artifact.json diff --git a/frontend/scripts/ui_builder_artifact.json b/frontend/scripts/ui_builder_artifact.json new file mode 100644 index 0000000000..1cfc1aef8c --- /dev/null +++ b/frontend/scripts/ui_builder_artifact.json @@ -0,0 +1,5 @@ +{ + "baseUrl": "https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev", + "version": "6715153", + "sha256": "1485930ea5f5309e4bdc09a55aae72eae8230eb74f0928715a0e6fe610703d9b" +} diff --git a/frontend/scripts/untar_ui_builder.js b/frontend/scripts/untar_ui_builder.js index ef31ddbc18..b7f02127ef 100644 --- a/frontend/scripts/untar_ui_builder.js +++ b/frontend/scripts/untar_ui_builder.js @@ -1,55 +1,50 @@ -import path from 'path' -import fs from 'fs' +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import artifact from './ui_builder_artifact.json' with { type: 'json' } -// Check if we're in node_modules (installed as dependency) +// Skip when installed as a dependency or outside the root project if (process.cwd().includes('node_modules')) { - console.log('Skipping postinstall - running as dependency'); - process.exit(0); + console.log('Skipping postinstall - running as dependency') + process.exit(0) } - -// Check if we're in the root project if (process.env.INIT_CWD && process.env.INIT_CWD !== process.cwd()) { - console.log('Skipping postinstall - not root project'); - process.exit(0); + console.log('Skipping postinstall - not root project') + process.exit(0) } -// Your actual postinstall logic here -console.log('Running postinstall for root project'); +console.log('Running postinstall for root project') +const tarUrl = `${artifact.baseUrl}/ui_builder-${artifact.version}.tar.gz` +const response = await fetch(tarUrl) +if (!response.ok) { + throw new Error(`Failed to download ${tarUrl}: ${response.status} ${response.statusText}`) +} -import { x } from 'tar' +const buffer = Buffer.from(await response.arrayBuffer()) +const sha256 = createHash('sha256').update(buffer).digest('hex') +if (sha256 !== artifact.sha256) { + throw new Error( + `UI builder artifact checksum mismatch: expected ${artifact.sha256}, got ${sha256}` + ) +} -const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-6715153.tar.gz' const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz') const extractTo = path.join(process.cwd(), 'static/ui_builder/') -import { fileURLToPath } from 'url' -import { dirname } from 'path' +await fs.promises.mkdir(extractTo, { recursive: true }) +await fs.promises.writeFile(outputTarPath, buffer) -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) - -// Download the tar file -const response = await fetch(tarUrl) -const buffer = await response.arrayBuffer() -await fs.promises.writeFile(outputTarPath, Buffer.from(buffer)) - - -// Create extract directory if it doesn't exist +const { x } = await import('tar') try { - await fs.promises.mkdir(extractTo, { recursive: true }) -} catch (err) { - if (err.code !== 'EEXIST') { - throw err - } + await x({ + file: outputTarPath, + cwd: extractTo, + sync: false, + gzip: true, + preservePaths: false + }) +} finally { + await fs.promises.rm(outputTarPath, { force: true }) } - -await x({ - file: outputTarPath, - cwd: extractTo, - sync: false, - gzip: true -}) - -await fs.promises.unlink(outputTarPath) diff --git a/frontend/use_latest_ui_builder.sh b/frontend/use_latest_ui_builder.sh index c64566b51b..25d9b417ab 100755 --- a/frontend/use_latest_ui_builder.sh +++ b/frontend/use_latest_ui_builder.sh @@ -1,20 +1,30 @@ #!/bin/bash - -# Auto-detect operating system -if [[ "$OSTYPE" == "darwin"* ]]; then - IS_MAC=true -else - IS_MAC=false -fi +set -euo pipefail cd ~/windmill-code-ui-builder HASH=$(git rev-parse --short HEAD) HASH=${HASH::-1} +ARTIFACT_URL="https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-${HASH}.tar.gz" + +TMP_FILE=$(mktemp) +trap 'rm -f "$TMP_FILE"' EXIT echo "Using UI Builder hash: ${HASH}" +curl -fsSL "$ARTIFACT_URL" -o "$TMP_FILE" -if [ "$IS_MAC" = true ]; then - sed -i '' "s/ui_builder-[^.]*\.tar\.gz/ui_builder-${HASH}.tar.gz/" ../windmill/frontend/scripts/untar_ui_builder.js +if command -v sha256sum >/dev/null 2>&1; then + SHA256=$(sha256sum "$TMP_FILE" | awk '{print $1}') else - sed -i "s/ui_builder-[^.]*\.tar\.gz/ui_builder-${HASH}.tar.gz/" ../windmill/frontend/scripts/untar_ui_builder.js + SHA256=$(shasum -a 256 "$TMP_FILE" | awk '{print $1}') fi +echo "Using UI Builder sha256: ${SHA256}" + +node -e ' +const fs = require("fs") +const [version, sha256] = process.argv.slice(1) +const artifactPath = "../windmill/frontend/scripts/ui_builder_artifact.json" +const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8")) +artifact.version = version +artifact.sha256 = sha256 +fs.writeFileSync(artifactPath, JSON.stringify(artifact, null, "\t") + "\n") +' "$HASH" "$SHA256"