Move branding and package install into cargo-run (#4266)

* Move branding asset fetch into cargo-run

* Move frontend npm install into cargo-run

* Rename branding::ensure to branding::setup

* Wording nits

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Timon
2026-06-22 20:21:46 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent b286e89746
commit f798b48b83
11 changed files with 74 additions and 261 deletions
-147
View File
@@ -1,147 +0,0 @@
/* eslint-disable no-console */
import crypto from "crypto";
import fs from "fs";
import http from "http";
import https from "https";
import path from "path";
import { fileURLToPath } from "url";
import zlib from "zlib";
import * as tar from "tar";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const BRANDING_INFO_FILE = path.join(__dirname, "../.branding");
const BRANDING_DIR = path.join(__dirname, "../branding");
const INSTALLED_BRANDING_INFO_FILE = path.join(BRANDING_DIR, ".branding");
const TEMP_FILE = path.join(__dirname, "branding_download.tar.gz");
function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
const protocol = url.startsWith("https") ? https : http;
const request = protocol.get(url, (response) => {
if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 307) {
file.close();
fs.unlink(dest, () => {});
if (response.headers.location) {
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
} else {
reject(new Error("Redirect location missing"));
}
return;
}
if (response.statusCode !== 200) {
file.close();
fs.unlink(dest, () => {});
reject(new Error(`Failed to download: ${response.statusCode}`));
return;
}
response.pipe(file);
file.on("finish", () => {
file.close(resolve);
});
});
request.on("error", (err) => {
fs.unlink(dest, () => {});
reject(err);
});
});
}
async function main() {
if (!fs.existsSync(BRANDING_INFO_FILE)) {
console.error(`Branding info file not found at ${BRANDING_INFO_FILE}`);
process.exit(1);
}
const content = fs.readFileSync(BRANDING_INFO_FILE, "utf8");
if (fs.existsSync(INSTALLED_BRANDING_INFO_FILE)) {
const installedContent = fs.readFileSync(INSTALLED_BRANDING_INFO_FILE, "utf8");
if (content === installedContent) {
console.log("Branding assets are up to date.");
return;
}
}
const lines = content
.split("\n")
.map((l) => l.trim())
.filter((l) => l.length > 0);
if (lines.length < 2) {
console.error("Branding file must contain at least two lines: URL and Hash");
process.exit(1);
}
const url = lines[0];
const expectedHash = lines[1];
console.log(`Downloading branding assets from <${url}>...`);
try {
await downloadFile(url, TEMP_FILE);
} catch (err) {
console.error("Download failed:", err);
process.exit(1);
}
console.log("Download complete. Verifying hash...");
const fileBuffer = fs.readFileSync(TEMP_FILE);
const hashSum = crypto.createHash("sha256");
hashSum.update(fileBuffer);
const hex = hashSum.digest("hex");
if (hex !== expectedHash) {
console.error("Hash mismatch!");
console.error(`Expected: ${expectedHash}`);
console.error(`Actual: ${hex}`);
if (fs.existsSync(TEMP_FILE)) fs.unlinkSync(TEMP_FILE);
process.exit(1);
}
console.log("Hash verified. Extracting...");
if (fs.existsSync(BRANDING_DIR)) {
fs.rmSync(BRANDING_DIR, { recursive: true, force: true });
}
fs.mkdirSync(BRANDING_DIR, { recursive: true });
try {
// Extract the tar.gz file
await new Promise((resolve, reject) => {
fs.createReadStream(TEMP_FILE)
.pipe(zlib.createGunzip())
.pipe(
tar.x({
cwd: BRANDING_DIR,
strip: 1,
}),
)
.on("error", reject)
.on("finish", resolve);
});
fs.copyFileSync(BRANDING_INFO_FILE, INSTALLED_BRANDING_INFO_FILE);
console.log("Extraction complete.");
} catch (error) {
console.error("Failed to extract archive:", error);
} finally {
if (fs.existsSync(TEMP_FILE)) {
fs.unlinkSync(TEMP_FILE);
}
}
}
main()
.then(() => process.exit(0))
.catch((err) => {
console.error("An error occurred:", err);
process.exit(1);
});
-43
View File
@@ -1,43 +0,0 @@
// This script automatically installs the npm packages listed in package-lock.json and runs as part of `npm run setup` (invoked by `cargo run`).
// It skips the installation if this has already run and neither package.json nor package-lock.json has been modified since.
import { execSync } from "child_process";
import { existsSync, statSync, writeFileSync } from "fs";
const INSTALL_TIMESTAMP_FILE = "node_modules/.install-timestamp";
// Checks if the install is needed by comparing modification times
const isInstallNeeded = () => {
if (!existsSync(INSTALL_TIMESTAMP_FILE)) return true;
const timestamp = statSync(INSTALL_TIMESTAMP_FILE).mtime;
return ["package.json", "package-lock.json", "package-installer.js"].some((file) => {
return existsSync(file) && statSync(file).mtime > timestamp;
});
};
// Run `npm ci` if needed and update the install timestamp
if (isInstallNeeded()) {
try {
// eslint-disable-next-line no-console
console.log("Installing npm packages...");
// Check if packages are up to date, doing so quickly by using `npm ci`, preferring local cached packages, and skipping the package audit and other checks.
// The devDependencies are explicitly included because they hold the build tooling (Vite, etc.), which npm would
// otherwise omit in environments that set NODE_ENV=production (like CI does for the sake of the Vite build).
execSync("npm ci --include=dev --prefer-offline --no-audit --no-fund", { stdio: "inherit" });
// Touch the install timestamp file
writeFileSync(INSTALL_TIMESTAMP_FILE, "");
// eslint-disable-next-line no-console
console.log("Finished installing npm packages.");
} catch (_) {
// eslint-disable-next-line no-console
console.error("\n\n--------------------> Failed to install npm packages. Please delete `/frontend/node_modules` then try again.\n\n");
process.exit(1);
}
} else {
// eslint-disable-next-line no-console
console.log("All npm packages are up-to-date.");
}
-64
View File
@@ -26,7 +26,6 @@
"sass": "^1.99.0",
"svelte": "^5.55.1",
"svelte-check": "^4.4.6",
"tar": "^7.5.13",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0",
"vite": "^8.0.3"
@@ -293,19 +292,6 @@
"node": ">=12"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -2058,16 +2044,6 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -4394,19 +4370,6 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@@ -5919,23 +5882,6 @@
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tar": {
"version": "7.5.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz",
"integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -6523,16 +6469,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+1 -3
View File
@@ -7,8 +7,7 @@
"type": "module",
"scripts": {
"check": "svelte-check --fail-on-warnings && eslint",
"fix": "eslint --fix",
"setup": "node package-installer.js && node branding-installer.js"
"fix": "eslint --fix"
},
"//": "NOTE: `source-sans-pro` is never to be upgraded to 3.x because that renders 1px above its intended position.",
"///": "Waiting on <https://github.com/import-js/eslint-plugin-import/issues/3227> before we can update @eslint/js and eslint to 10.x",
@@ -32,7 +31,6 @@
"sass": "^1.99.0",
"svelte": "^5.55.1",
"svelte-check": "^4.4.6",
"tar": "^7.5.13",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0",
"vite": "^8.0.3"