hopefully fix build times

This commit is contained in:
KingKDot
2026-07-30 16:09:24 -05:00
parent 68a733ab81
commit d829864527
4 changed files with 150 additions and 29 deletions
+98 -10
View File
@@ -59,7 +59,19 @@ jobs:
build-and-push: build-and-push:
needs: build-backstage-dll needs: build-backstage-dll
runs-on: ubuntu-latest name: Build and push (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
artifact: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
artifact: arm64
permissions: permissions:
contents: read contents: read
packages: write packages: write
@@ -89,7 +101,7 @@ jobs:
exit 1 exit 1
fi fi
if [[ "$client_version" != "$server_version" || "$client_version" != "$package_version" ]]; then if ! { [[ "$client_version" == "$server_version" ]] && [[ "$client_version" == "$package_version" ]]; }; then
echo "Version mismatch detected:" >&2 echo "Version mismatch detected:" >&2
echo " client(config.go): $client_version" >&2 echo " client(config.go): $client_version" >&2
echo " server(version.ts): $server_version" >&2 echo " server(version.ts): $server_version" >&2
@@ -101,9 +113,11 @@ jobs:
rest="${client_version#*.}" rest="${client_version#*.}"
minor="${rest%%.*}" minor="${rest%%.*}"
echo "version=$client_version" >> "$GITHUB_OUTPUT" {
echo "major=$major" >> "$GITHUB_OUTPUT" echo "version=$client_version"
echo "minor=$minor" >> "$GITHUB_OUTPUT" echo "major=$major"
echo "minor=$minor"
} >> "$GITHUB_OUTPUT"
- name: Validate Release Version Matches Source Version - name: Validate Release Version Matches Source Version
shell: bash shell: bash
@@ -144,13 +158,87 @@ jobs:
type=raw,value=latest type=raw,value=latest
- name: Build and push server image - name: Build and push server image
id: build
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
push: true platforms: ${{ matrix.platform }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=gha,scope=server-${{ matrix.artifact }}
cache-to: type=gha,mode=max cache-to: type=gha,mode=max,scope=server-${{ matrix.artifact }}
platforms: linux/amd64,linux/arm64 outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export image digest
shell: bash
run: |
set -euo pipefail
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
test -n "$digest"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload image digest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: image-digest-${{ matrix.artifact }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge-and-publish:
name: Merge and publish multi-architecture image
needs: build-and-push
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: write
steps:
- name: Normalize image name
run: printf 'IMAGE_NAME=%s\n' "${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- name: Download image digests
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: image-digest-*
path: /tmp/digests
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract release tags
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}},value=${{ env.RELEASE_VERSION }}
type=semver,pattern={{major}}.{{minor}},value=${{ env.RELEASE_VERSION }}
type=semver,pattern={{major}},value=${{ env.RELEASE_VERSION }}
type=raw,value=latest
- name: Create and push multi-architecture manifest
working-directory: /tmp/digests
shell: bash
run: |
set -euo pipefail
test "$(find . -maxdepth 1 -type f | wc -l)" -eq 2
mapfile -t tag_args < <(jq -r '.tags[] | "-t", .' <<< "$DOCKER_METADATA_OUTPUT_JSON")
digest_args=()
for digest_file in *; do
digest_args+=("${REGISTRY}/${IMAGE_NAME}@sha256:${digest_file}")
done
docker buildx imagetools create "${tag_args[@]}" "${digest_args[@]}"
- name: Verify published manifest
run: docker buildx imagetools inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${RELEASE_VERSION}"
+8 -4
View File
@@ -110,10 +110,14 @@ RUN if [ -f dist-clients/BackstageCapture.x64.dll ]; then \
echo "WARNING: BackstageCapture DLL not available (build with MSVC on Windows)"; \ echo "WARNING: BackstageCapture DLL not available (build with MSVC on Windows)"; \
fi fi
# Tailwind CSS, vendored frontend assets, minified public assets, and bundled Bun server runtime. # Keep production build phases separate so BuildKit reports the exact slow or
RUN bun run build:public:prod \ # failing phase and can cache each completed phase independently.
&& bun run build:bundle \ RUN bun run build:css
&& test "$(wc -l < ./public/index.html)" -lt 20 \ RUN bun run vendor
RUN MINIFY_CONCURRENCY=4 bun run minify
RUN bun run build:bundle
RUN test "$(wc -l < ./public/index.html)" -lt 20 \
&& test "$(wc -l < ./public/assets/main.js)" -lt 50 \ && test "$(wc -l < ./public/assets/main.js)" -lt 50 \
&& test -s ./public/assets/tailwind.css \ && test -s ./public/assets/tailwind.css \
&& test -d ./public/vendor/fontawesome \ && test -d ./public/vendor/fontawesome \
+39 -15
View File
@@ -34,6 +34,10 @@ const htmlOpts = {
type Stats = { js: number; css: number; html: number; savedBytes: number }; type Stats = { js: number; css: number; html: number; savedBytes: number };
const stats: Stats = { js: 0, css: 0, html: 0, savedBytes: 0 }; const stats: Stats = { js: 0, css: 0, html: 0, savedBytes: 0 };
let failures = 0; let failures = 0;
const requestedConcurrency = Number.parseInt(process.env.MINIFY_CONCURRENCY || "", 10);
const minifyConcurrency = Number.isFinite(requestedConcurrency) && requestedConcurrency > 0
? requestedConcurrency
: 4;
async function collectFiles(dir: string, exts: Set<string>): Promise<string[]> { async function collectFiles(dir: string, exts: Set<string>): Promise<string[]> {
const results: string[] = []; const results: string[] = [];
@@ -86,28 +90,48 @@ async function minifyHTML(filePath: string) {
stats.html++; stats.html++;
} }
async function runPool<T>(
items: T[],
concurrency: number,
worker: (item: T) => Promise<void>,
): Promise<void> {
let nextIndex = 0;
const workers = Array.from(
{ length: Math.min(concurrency, Math.max(1, items.length)) },
async () => {
while (nextIndex < items.length) {
const item = items[nextIndex++];
await worker(item);
}
},
);
await Promise.all(workers);
}
// Collect all files // Collect all files
const jsFiles = await collectFiles(path.join(publicDir, "assets"), new Set([".js"])); const jsFiles = await collectFiles(path.join(publicDir, "assets"), new Set([".js"]));
const cssFiles = await collectFiles(path.join(publicDir, "assets"), new Set([".css"])); const cssFiles = await collectFiles(path.join(publicDir, "assets"), new Set([".css"]));
const htmlFiles = await collectFiles(publicDir, new Set([".html"])); const htmlFiles = await collectFiles(publicDir, new Set([".html"]));
console.log(`Minifying ${jsFiles.length} JS, ${cssFiles.length} CSS, ${htmlFiles.length} HTML files...`); console.log(
`Minifying ${jsFiles.length} JS, ${cssFiles.length} CSS, ${htmlFiles.length} HTML files ` +
`(concurrency=${minifyConcurrency})...`,
);
// Process in parallel batches const jobs = [
await Promise.all([ ...jsFiles.map((file) => ({ kind: "JS", file, minify: minifyJS })),
...jsFiles.map((f) => minifyJS(f).catch((e) => { ...cssFiles.map((file) => ({ kind: "CSS", file, minify: minifyCSS })),
...htmlFiles.map((file) => ({ kind: "HTML", file, minify: minifyHTML })),
];
await runPool(jobs, minifyConcurrency, async ({ kind, file, minify }) => {
try {
await minify(file);
} catch (e) {
failures++; failures++;
console.error(`JS error ${f}: ${e.message}`); const message = e instanceof Error ? e.message : String(e);
})), console.error(`${kind} error ${file}: ${message}`);
...cssFiles.map((f) => minifyCSS(f).catch((e) => { }
failures++; });
console.error(`CSS error ${f}: ${e.message}`);
})),
...htmlFiles.map((f) => minifyHTML(f).catch((e) => {
failures++;
console.error(`HTML error ${f}: ${e.message}`);
})),
]);
console.log( console.log(
`Done: ${stats.js} JS, ${stats.css} CSS, ${stats.html} HTML — saved ${(stats.savedBytes / 1024).toFixed(1)} KB`, `Done: ${stats.js} JS, ${stats.css} CSS, ${stats.html} HTML — saved ${(stats.savedBytes / 1024).toFixed(1)} KB`,
+5
View File
@@ -361,15 +361,20 @@ rmSync(ansiTmp, { force: true });
console.log("Downloading countries GeoJSON ..."); console.log("Downloading countries GeoJSON ...");
const geojsonDest = path.join(VENDOR, "geo-countries", "countries.geojson"); const geojsonDest = path.join(VENDOR, "geo-countries", "countries.geojson");
ensureDir(path.dirname(geojsonDest)); ensureDir(path.dirname(geojsonDest));
const geojsonController = new AbortController();
const geojsonTimeout = setTimeout(() => geojsonController.abort(), 15_000);
try { try {
const resp = await fetch( const resp = await fetch(
"https://cdn.jsdelivr.net/gh/datasets/geo-countries@master/data/countries.geojson", "https://cdn.jsdelivr.net/gh/datasets/geo-countries@master/data/countries.geojson",
{ signal: geojsonController.signal },
); );
if (!resp.ok) throw new Error(`HTTP ${resp.status}`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
await Bun.write(geojsonDest, resp); await Bun.write(geojsonDest, resp);
console.log(" GeoJSON saved (" + ((await Bun.file(geojsonDest).size) / 1024 / 1024).toFixed(1) + " MB)"); console.log(" GeoJSON saved (" + ((await Bun.file(geojsonDest).size) / 1024 / 1024).toFixed(1) + " MB)");
} catch (err) { } catch (err) {
console.warn(" WARNING: Could not download GeoJSON. Map features may not work offline.", err); console.warn(" WARNING: Could not download GeoJSON. Map features may not work offline.", err);
} finally {
clearTimeout(geojsonTimeout);
} }
/* ── done ─────────────────────────────────────────────────────────── */ /* ── done ─────────────────────────────────────────────────────────── */