diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3675cbf4e..d3d610803 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,14 @@ jobs: runs-on: ${{ matrix.os == 'ubuntu-latest' && github.repository == 'voidzero-dev/void' && 'blacksmith-4vcpu-ubuntu-2404' || matrix.os }} permissions: contents: read + # Remote cache uploads authenticate with a GitHub Actions OIDC token. + id-token: write + env: + # Remote task cache, on when the repository variable is set. Only pushes to main upload + # results; pull requests and release runs only download, so unreviewed code can't publish + # entries that main restores. + VP_REMOTE_CACHE_URL: ${{ vars.VP_REMOTE_CACHE_URL }} + VP_REMOTE_CACHE: ${{ !vars.VP_REMOTE_CACHE_URL && 'off' || (github.event_name == 'push' && github.ref == 'refs/heads/main') && 'read-write' || 'read' }} strategy: fail-fast: false matrix: @@ -32,7 +40,7 @@ jobs: - uses: ./.github/actions/test-setup # Build every playground once per OS for complete build/type coverage. # Integration tests build their own selected fixtures in separate checkouts. - - run: vp run build:all + - run: vp run --cache build:all - run: vp run build:docs - run: node test/check-playground-build-artifacts.mjs - run: vp run check diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 37d970b25..f809fd5c2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,6 +30,9 @@ jobs: uses: ./.github/workflows/ci.yml permissions: contents: read + # ci.yml's build job requests an OIDC token for remote cache uploads, so its caller must + # grant it, though release runs only download. + id-token: write platform-ci: needs: validate-release diff --git a/package.json b/package.json index 78288e3f3..928063d6a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "type": "module", "scripts": { - "build": "vp run --filter './packages/**' --parallel build", + "build": "vp run --filter './packages/**' --filter '!./packages/md' build && vp run --filter ./packages/md build", "build:core": "vp run --filter ./packages/deploy-core --filter ./packages/deploy-cloudflare --filter ./packages/edge --filter ./packages/isr --filter ./packages/platform --filter ./packages/void build", "build:docs": "vp run --filter ./docs build", "build:platform": "vp run --filter './platform/packages/**' build", diff --git a/packages/platform/package.json b/packages/platform/package.json index 8e3f2fdcf..1100889a6 100644 --- a/packages/platform/package.json +++ b/packages/platform/package.json @@ -42,7 +42,7 @@ "access": "public" }, "scripts": { - "build": "vp pack && node scripts/build-runtime.mjs", + "build": "vp pack && node scripts/build-runtime.mjs && vp run build:stamp", "dev": "vp pack --watch", "typecheck": "tsc --noEmit" }, @@ -51,6 +51,7 @@ }, "devDependencies": { "@types/node": "catalog:", + "@void/isr": "workspace:*", "typescript": "catalog:", "vite-plus": "catalog:", "wrangler": "catalog:" diff --git a/packages/platform/scripts/build-runtime.mjs b/packages/platform/scripts/build-runtime.mjs index 9ff23ef96..b6e07feb1 100644 --- a/packages/platform/scripts/build-runtime.mjs +++ b/packages/platform/scripts/build-runtime.mjs @@ -5,7 +5,6 @@ import { tmpdir } from 'node:os'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { basename, dirname, join } from 'pathslash'; import { buildSandboxController } from '../../../platform/packages/api/scripts/build-sandbox-controller.mjs'; -import { resolvePlatformSourceRevision } from './source-revision.mjs'; const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..'); const repositoryRoot = join(packageRoot, '..', '..'); @@ -139,7 +138,6 @@ try { }); const packageJson = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')); const database = buildDatabaseManifest(); - const sourceRevision = resolvePlatformSourceRevision({ repositoryRoot }); const manifest = parsePlatformRuntimeManifest({ artifacts, assets: { @@ -165,7 +163,8 @@ try { platformVersion: packageJson.version, safetyProtocols: { sandboxDrain: 3 }, schemaVersion: 2, - sourceRevision, + // scripts/stamp-source-revision.mjs records the revision after this cached build. + sourceRevision: null, usageProtocolVersion: PLATFORM_USAGE_PROTOCOL_VERSION, }); writeFileSync(join(runtimeRoot, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`); diff --git a/packages/platform/scripts/stamp-source-revision.mjs b/packages/platform/scripts/stamp-source-revision.mjs new file mode 100644 index 000000000..68c083711 --- /dev/null +++ b/packages/platform/scripts/stamp-source-revision.mjs @@ -0,0 +1,19 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { dirname, join } from 'pathslash'; +import { resolvePlatformSourceRevision } from './source-revision.mjs'; + +// Records the source revision in the manifest that scripts/build-runtime.mjs wrote, so a new +// revision reruns only this step and not the cached runtime build. +const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..'); +const repositoryRoot = join(packageRoot, '..', '..'); +const manifestPath = join(packageRoot, 'dist', 'runtime', 'manifest.json'); +const { parsePlatformRuntimeManifest } = await import( + pathToFileURL(join(packageRoot, 'dist', 'installer.mjs')).href +); + +const manifest = parsePlatformRuntimeManifest({ + ...JSON.parse(readFileSync(manifestPath, 'utf8')), + sourceRevision: resolvePlatformSourceRevision({ repositoryRoot }), +}); +writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); diff --git a/packages/platform/vite.config.ts b/packages/platform/vite.config.ts index b4ccce044..d6cbc5da5 100644 --- a/packages/platform/vite.config.ts +++ b/packages/platform/vite.config.ts @@ -1,6 +1,17 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ + run: { + tasks: { + // The last step of the `build` script: records the source revision in the runtime manifest. + // It isn't cached, so a new revision reruns only this step, and it gets the full + // environment, including VOID_PLATFORM_SOURCE_REVISION. + 'build:stamp': { + command: 'node scripts/stamp-source-revision.mjs', + cache: false, + }, + }, + }, pack: { entry: [ 'src/index.ts', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f44dec15..614fb9f7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -651,6 +651,9 @@ importers: '@types/node': specifier: 'catalog:' version: 26.6.6 + '@void/isr': + specifier: workspace:* + version: link:../isr typescript: specifier: 'catalog:' version: 7.0.2 diff --git a/vite.config.ts b/vite.config.ts index e9384c2a7..a43b193a5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -84,7 +84,7 @@ export default defineConfig({ }, 'test:all': { command: - 'vp fmt --check && vp lint && vp run build:all && node test/check-playground-build-artifacts.mjs && vp run lint:platform && vp run check && vp test run', + 'vp fmt --check && vp lint && vp run --cache build:all && node test/check-playground-build-artifacts.mjs && vp run lint:platform && vp run check && vp test run', // `vp run` passes ONLY the env vars a task declares to that task — not the full environment. // Without this, the gated live Cloudflare e2e's credentials never reach `vp test run`, so it // skips even when the CI job injects them. `untrackedEnv` forwards them without folding them