diff --git a/.github/workflows/publish-results-viewer.yml b/.github/workflows/publish-results-viewer.yml index 909d3ed..59a0cf5 100644 --- a/.github/workflows/publish-results-viewer.yml +++ b/.github/workflows/publish-results-viewer.yml @@ -20,7 +20,29 @@ jobs: node-version: "20" registry-url: "https://npm.pkg.github.com" scope: "@ohdsi" + - uses: docker/setup-buildx-action@v3 + # The in-browser R runtime (shinylive-export/ + r-packages/) comes from the + # Dockerfile's r-builder stage (rocker/r-ver pinned to renv.lock's R); + # rv-runtime is a scratch stage exposing just its outputs. With the gha + # cache this is a cache hit unless the R inputs (scripts/, shinylive-app/) + # changed. Without these assets the published package would be a JS shell + # with no viewer runtime — prepublishOnly guards against that. + - name: Build shinylive runtime (Dockerfile rv-runtime stage) + uses: docker/build-push-action@v6 + with: + context: . + target: rv-runtime + outputs: type=local,dest=/tmp/rv-runtime + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Stage runtime into package + run: | + rm -rf shinylive-export r-packages + cp -r /tmp/rv-runtime/shinylive-export shinylive-export + cp -r /tmp/rv-runtime/r-packages r-packages - run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run build:pkg - name: Version stamp (prerelease) run: npm version --no-git-tag-version "0.2.0-$(date -u +%Y%m%d%H%M%S).g${GITHUB_SHA::7}" diff --git a/Dockerfile b/Dockerfile index c999cb1..f6e0d8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -109,6 +109,16 @@ RUN mkdir -p r-packages && \ Rscript scripts/build-shim-packages.R && \ Rscript scripts/build-shinylive-export.R +# --------------------------------------------------------------------------- +# Stage 1b: bare r-builder outputs, extractable without the rocker rootfs via +# `docker buildx build --target rv-runtime --output type=local`. Used by +# .github/workflows/publish-results-viewer.yml to fold the runtime into the +# published @ohdsi/results-viewer npm package. +# --------------------------------------------------------------------------- +FROM scratch AS rv-runtime +COPY --from=r-builder /rv/shinylive-export /shinylive-export +COPY --from=r-builder /rv/r-packages /r-packages + # --------------------------------------------------------------------------- # Stage 2: build the JS sub-plugins and the sibyl shell. # --------------------------------------------------------------------------- diff --git a/plugins/results-viewer/package.json b/plugins/results-viewer/package.json index 69688d1..a632126 100644 --- a/plugins/results-viewer/package.json +++ b/plugins/results-viewer/package.json @@ -16,6 +16,7 @@ "build": "vite build", "build:sibyl": "RESULTS_VIEWER_OUT_DIR=../sibyl/public/plugins/results-viewer vite build", "build:pkg": "RESULTS_VIEWER_OUT_DIR=dist vite build", + "prepublishOnly": "node scripts/verify-pkg-dist.js", "preview": "vite preview", "typecheck": "vue-tsc --noEmit", "test:e2e": "playwright test" diff --git a/plugins/results-viewer/scripts/verify-pkg-dist.js b/plugins/results-viewer/scripts/verify-pkg-dist.js new file mode 100644 index 0000000..8fcc3b6 --- /dev/null +++ b/plugins/results-viewer/scripts/verify-pkg-dist.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node +// prepublishOnly gate: a dist/ without the WebR/shinylive runtime is a JS shell +// that renders a dead viewer. The runtime is produced by the Dockerfile +// r-builder stage (or scripts/build-shinylive-export.R on an R-provisioned +// host) and folded into dist/ by `npm run build:pkg`. +import { existsSync, readdirSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..'); + +const failures = []; + +for (const f of ['dist/index.system.js', 'dist/shinylive/shinylive-sw.js', 'dist/shinylive/app.json']) { + if (!existsSync(join(root, f))) failures.push(`missing ${f}`); +} + +const pkgDir = join(root, 'dist', 'r-packages'); +if (!existsSync(pkgDir) || !readdirSync(pkgDir).some((f) => f.endsWith('.tar.gz'))) { + failures.push('missing dist/r-packages/*.tar.gz (shim packages)'); +} + +if (failures.length) { + console.error('[verify-pkg-dist] refusing to publish a runtime-less package:'); + for (const f of failures) console.error(` - ${f}`); + console.error('[verify-pkg-dist] stage shinylive-export/ + r-packages/ (Dockerfile rv-runtime stage), then run `npm run build:pkg`.'); + process.exit(1); +} +console.log('[verify-pkg-dist] dist contains the shinylive runtime — OK');