hopefully fix build times
This commit is contained in:
@@ -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
@@ -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 \
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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 ─────────────────────────────────────────────────────────── */
|
||||||
|
|||||||
Reference in New Issue
Block a user