diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index db27130..9ac6419 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -25,6 +25,9 @@ concurrency: env: CDN: https://cdn.pcbjam.com BUCKET: pcbjam-cdn + # KiCad library snapshot the demo points at (published once by publish-libs.yml + # to libs/kicad//). Bump when moving to a newer KiCad library release. + LIB_TAG: "10.0.3" PAGES_PROJECT: pcbjam-demo # MUST be the Pages project's PRODUCTION branch — any other value makes # `wrangler pages deploy` a PREVIEW deploy and demo.pcbjam.com won't update. @@ -82,9 +85,13 @@ jobs: node scripts/deploy/publish-content.mjs --tag "${{ steps.tag.outputs.tag }}" --gallery deploy/demo/gallery.json --driver r2 --bucket "$BUCKET" --remote - # 3) Build the standalone pinned to the CDN + this tag's manifests. + # 3) Build the standalone pinned to the CDN + this tag's manifests. The + # full KiCad library set is served read-only from libs/kicad/$LIB_TAG + # (published once by publish-libs.yml — run it first for a new lib tag). - name: Build demo - run: node scripts/deploy/build-demo.mjs --tag "${{ steps.tag.outputs.tag }}" --cdn "$CDN" + run: > + node scripts/deploy/build-demo.mjs --tag "${{ steps.tag.outputs.tag }}" + --cdn "$CDN" --lib-tag "$LIB_TAG" # 4) Ensure the Pages project exists (first deploy creates it; no-op after). # Its production branch must equal PAGES_PROD_BRANCH or deploys land as diff --git a/.github/workflows/publish-libs.yml b/.github/workflows/publish-libs.yml new file mode 100644 index 0000000..1066004 --- /dev/null +++ b/.github/workflows/publish-libs.yml @@ -0,0 +1,61 @@ +name: publish-libs + +# On-demand, decoupled: clone the full upstream KiCad symbol + footprint +# libraries at a tag and publish them to the CDN (cdn.pcbjam.com, R2 pcbjam-cdn) +# as version-pinned r2-idb-sync STATIC ORIGINS — one immutable manifest+bundle +# per lib under libs/kicad//. The demo's cdnLibsSource opens these +# read-only + IDB-cached. See docs/features/r2-idb-sync + the demo-adjustments +# feature; wasm/libs/cdn-source.ts (client) + scripts/deploy/publish-libs.ts. +# +# Keyed by the KiCad library tag and idempotent: if the snapshot already exists +# the script SKIPS the whole run, so re-running for the same tag is a near no-op. +# Run this ONLY when the library tag changes; deploy-demo.yml just pins the demo +# build at the chosen tag (build-demo --lib-tag). +on: + workflow_dispatch: + inputs: + lib_tag: + description: "KiCad library tag to publish (kicad-symbols/footprints), e.g. 10.0.3" + required: true + default: "10.0.3" + force: + description: "Re-publish even if the snapshot already exists (true/false)" + default: "false" + +concurrency: + group: publish-libs + cancel-in-progress: false + +env: + BUCKET: pcbjam-cdn + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + # publish-libs.ts shells wrangler (R2) via cdn-store; fetch it on demand. + WRANGLER_CMD: npx --yes wrangler@4 + +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + # Only the MIT pcbjam-shared submodule is needed (publish-libs imports the + # self-contained sync-wire codecs from it); kicad/wxwidgets are not. + - uses: actions/checkout@v4 + with: + submodules: false + - name: Init pcbjam-shared submodule + run: git submodule update --init --depth 1 web/pcbjam-shared + - uses: actions/setup-node@v4 + with: + node-version: 20 + + # Clone the full libs at , extract every lib, build the static + # origins, upload to R2. The import chain (extract-libs parsers + + # sync-wire) is dependency-free, so no workspace install is needed. + - name: Publish libs to CDN + run: > + npx --yes tsx scripts/deploy/publish-libs.ts + --lib-tag "${{ github.event.inputs.lib_tag }}" + --clone "$RUNNER_TEMP/kicad-libs" + --driver r2 --bucket "$BUCKET" --remote + ${{ github.event.inputs.force == 'true' && '--force' || '' }} diff --git a/scripts/deploy/publish-libs.ts b/scripts/deploy/publish-libs.ts index 29863b1..c9d49e8 100644 --- a/scripts/deploy/publish-libs.ts +++ b/scripts/deploy/publish-libs.ts @@ -19,14 +19,22 @@ // + top `//manifest.json` { schema, tag, libs:[{id,name,kind,itemCount}] } // All immutable (content is pinned by the tag). +import { execFileSync } from "node:child_process"; +import { existsSync, mkdirSync } from "node:fs"; +import { dirname, join } from "node:path"; import { extractAllLibs } from "../../web/backend/src/extract/extract-libs.js"; import { encodeBundle, type SyncManifest } from "../../web/pcbjam-shared/src/sync-wire.js"; import { IMMUTABLE, makeStore, putJSON, sha256hex } from "./lib/cdn-store.mjs"; +// Upstream KiCad library repos (full set lives here; tagged per KiCad release). +const SYMBOLS_URL = "https://gitlab.com/kicad/libraries/kicad-symbols.git"; +const FOOTPRINTS_URL = "https://gitlab.com/kicad/libraries/kicad-footprints.git"; + interface Args { libTag: string | null; symbolsSrc: string | null; footprintsSrc: string | null; + clone: string | null; driver: string; out: string | null; bucket: string; @@ -40,6 +48,7 @@ function parseArgs(argv: string[]): Args { libTag: null, symbolsSrc: null, footprintsSrc: null, + clone: null, driver: "local", out: null, bucket: "pcbjam-cdn", @@ -53,6 +62,9 @@ function parseArgs(argv: string[]): Args { case "--lib-tag": a.libTag = next(); break; case "--symbols-src": a.symbolsSrc = next(); break; case "--footprints-src": a.footprintsSrc = next(); break; + // Clone both upstream repos at --lib-tag into /{kicad-symbols, + // kicad-footprints} (full, shallow) and use them as the sources. + case "--clone": a.clone = next(); break; case "--driver": a.driver = next(); break; case "--out": a.out = next(); break; case "--bucket": a.bucket = next(); break; @@ -63,12 +75,25 @@ function parseArgs(argv: string[]): Args { } } if (!a.libTag) throw new Error("--lib-tag is required"); - if (!a.symbolsSrc && !a.footprintsSrc) - throw new Error("at least one of --symbols-src / --footprints-src is required"); + if (!a.symbolsSrc && !a.footprintsSrc && !a.clone) + throw new Error("need --clone , or --symbols-src / --footprints-src"); if (a.driver === "local" && !a.out) a.out = ".cdn-out"; return a; } +/** Shallow full clone of a lib repo at a tag (idempotent: skip if present). */ +function cloneFull(url: string, dest: string, ref: string): void { + if (existsSync(dest)) { + console.log(`clone: ${dest} present — reusing`); + return; + } + mkdirSync(dirname(dest), { recursive: true }); + console.log(`clone: ${url} @ ${ref} → ${dest}`); + execFileSync("git", ["clone", "--depth", "1", "--branch", ref, url, dest], { + stdio: "inherit", + }); +} + async function main(): Promise { const a = parseArgs(process.argv); const store = makeStore(a.driver, a); @@ -82,6 +107,18 @@ async function main(): Promise { } console.log(`publish-libs: tag=${a.libTag} driver=${store.kind} → ${a.prefix}/${a.libTag}/`); + + // Source the full set from upstream when asked (CI path); --symbols-src / + // --footprints-src still win if also given (local checkouts). + if (a.clone) { + const symDest = join(a.clone, "kicad-symbols"); + const fpDest = join(a.clone, "kicad-footprints"); + cloneFull(SYMBOLS_URL, symDest, a.libTag); + cloneFull(FOOTPRINTS_URL, fpDest, a.libTag); + a.symbolsSrc ??= symDest; + a.footprintsSrc ??= fpDest; + } + const libs = await extractAllLibs({ symbolsSrc: a.symbolsSrc ?? undefined, footprintsSrc: a.footprintsSrc ?? undefined,