diff --git a/.github/scripts/update-template-versions.mjs b/.github/scripts/update-template-versions.mjs new file mode 100644 index 0000000..4de6724 --- /dev/null +++ b/.github/scripts/update-template-versions.mjs @@ -0,0 +1,80 @@ +// Syncs template dependency versions to the versions declared in the openui +// monorepo checkout (OPENUI_DIR) — the repo is the source of truth for what +// was published (npm registry metadata can lag right after a publish). +// Regenerates template lockfiles; the workflow opens the diff as a PR. +import { execSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +// True when a > b, comparing plain x.y.z triples. +function isNewer(a, b) { + const pa = a.split(".").map(Number); + const pb = b.split(".").map(Number); + for (let i = 0; i < 3; i++) { + if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0); + } + return false; +} + +const openuiDir = process.env.OPENUI_DIR; +if (!openuiDir) { + console.error("OPENUI_DIR env var is required (path to a thesysdev/openui checkout)."); + process.exit(1); +} + +// map of every @openuidev/* workspace package. +const workspaceVersions = new Map(); +for (const dir of fs.readdirSync(path.join(openuiDir, "packages"))) { + const pkgPath = path.join(openuiDir, "packages", dir, "package.json"); + if (!fs.existsSync(pkgPath)) continue; + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); + if (!pkg.name?.startsWith("@openuidev/") || !pkg.version || pkg.private) continue; + workspaceVersions.set(pkg.name, pkg.version); +} + +// index.json is this repo's source of truth for which templates exist. +const index = JSON.parse(fs.readFileSync("index.json", "utf8")); + +const changes = []; +for (const { name: template } of index.templates) { + const pkgPath = path.join(template, "package.json"); + if (!fs.existsSync(pkgPath)) continue; + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); + + let templateChanged = false; + for (const section of ["dependencies", "devDependencies"]) { + const deps = pkg[section]; + if (!deps) continue; + for (const [name, range] of Object.entries(deps)) { + const target = workspaceVersions.get(name); + if (!target) continue; + // Only plain versions and simple ^/~ ranges; leave link:/file:/etc alone. + if (!/^[\^~]?\d/.test(range)) continue; + const prefix = /^[\^~]/.test(range) ? range[0] : ""; + const current = range.replace(/^[\^~]/, ""); + if (current === target) continue; + // Never downgrade: a template can be deliberately ahead of the + // workspace (hotfix landed here first). + if (!isNewer(target, current)) continue; + deps[name] = `${prefix}${target}`; + templateChanged = true; + changes.push(`${template}: ${name} ${range} -> ${deps[name]}`); + } + } + if (!templateChanged) continue; + + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); + if (fs.existsSync(path.join(template, "package-lock.json"))) { + execSync("npm install --package-lock-only --ignore-scripts --no-audit --no-fund", { + cwd: template, + stdio: "inherit", + }); + } +} + +if (changes.length === 0) { + console.log("Templates already match the openui workspace versions."); +} else { + console.log("Updated:"); + for (const change of changes) console.log(` ${change}`); +} diff --git a/.github/workflows/update-template-versions.yml b/.github/workflows/update-template-versions.yml new file mode 100644 index 0000000..f4fb916 --- /dev/null +++ b/.github/workflows/update-template-versions.yml @@ -0,0 +1,51 @@ +name: Update Template Versions + +# Event-based sync: openui's publish workflow sends a repository_dispatch +# here after a successful package publish. The openui checkout is pinned to +# the publish commit (client_payload.sha) so versions match exactly what +# shipped. The PR is opened in this repo with the default token. +on: + repository_dispatch: + types: [package-published] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + bump-template-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + # openui is public — a read-only checkout needs no token. + # .openui-checkout is gitignored so create-pull-request ignores it. + - uses: actions/checkout@v6 + with: + repository: thesysdev/openui + ref: ${{ github.event.client_payload.sha || 'main' }} + path: .openui-checkout + + - uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Sync template versions to openui workspace versions + env: + OPENUI_DIR: .openui-checkout + run: node .github/scripts/update-template-versions.mjs + + - name: Open PR with the bumps + uses: peter-evans/create-pull-request@v7 + with: + branch: bot/update-template-versions + title: "chore: bump template versions to latest published packages" + commit-message: "chore: bump template versions to latest published packages" + body: | + Automated follow-up to a package publish in thesysdev/openui. + + - Syncs `@openuidev/*` versions in the templates to the published workspace versions + - Regenerates template `package-lock.json` files + - Merging this ships the new versions to `openui create` (templates are fetched from main) + delete-branch: true diff --git a/.gitignore b/.gitignore index 28cd5e1..ac86662 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ build/ next-env.d.ts *.tsbuildinfo -*/**/pnpm-lock.yaml \ No newline at end of file +*/**/pnpm-lock.yaml +# Nested openui checkout used by the version-sync workflow +.openui-checkout/