Skip to content

Commit 2c09a4e

Browse files
V48 (impl-only): Pipeline VCR appliance image and host image mode
Add @bitcode/pipeline-image (materialize runners, Dockerfile, VCR CI workflow) and teach buildAssetPackSandboxHostPlan to use Sandbox.create({ image }) when BITCODE_PIPELINE_SANDBOX_IMAGE is set: slim writeFiles (manifest only), image entry command, no in-box monorepo install. Deposit/read synthesize share the same path. Verified pipeline-hosts/generic-hosts tests, uapi tsc/lint, materialize.
1 parent 05e0c79 commit 2c09a4e

18 files changed

Lines changed: 669 additions & 41 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: Pipeline Image
2+
3+
# Build (and optionally push) the VCR pipeline appliance used by sandbox deposit/read.
4+
on:
5+
push:
6+
branches:
7+
- version/**
8+
- main
9+
paths:
10+
- 'packages/pipeline-image/**'
11+
- 'packages/pipeline-hosts/**'
12+
- 'packages/host-generics/**'
13+
- 'packages/asset-packs-pipelines/**'
14+
- 'packages/pipelines-generics/**'
15+
- 'packages/btd/**'
16+
- 'pnpm-lock.yaml'
17+
- '.github/workflows/pipeline-image.yml'
18+
workflow_dispatch:
19+
inputs:
20+
push_to_vcr:
21+
description: 'Push image to VCR (requires secrets)'
22+
required: false
23+
default: 'false'
24+
25+
concurrency:
26+
group: ${{ github.workflow }}-${{ github.ref }}
27+
cancel-in-progress: true
28+
29+
permissions:
30+
contents: read
31+
32+
jobs:
33+
materialize-and-build:
34+
name: Materialize runners + Docker build
35+
runs-on: ubuntu-latest
36+
timeout-minutes: 45
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v4
40+
41+
- name: Setup pnpm
42+
uses: pnpm/action-setup@v4
43+
44+
- name: Setup Node
45+
uses: actions/setup-node@v4
46+
with:
47+
node-version: '24'
48+
cache: pnpm
49+
50+
- name: Install dependencies
51+
run: pnpm install --frozen-lockfile
52+
53+
- name: Materialize pipeline-image runners
54+
run: pnpm --filter @bitcode/pipeline-image run materialize
55+
56+
- name: Pipeline-image unit test
57+
run: pnpm --filter @bitcode/pipeline-image test
58+
59+
- name: Set up Docker Buildx
60+
uses: docker/setup-buildx-action@v3
61+
62+
- name: Build pipeline image (local tag)
63+
run: |
64+
docker build \
65+
-f packages/pipeline-image/Dockerfile \
66+
-t "bitcode-pipeline:ci-${GITHUB_SHA}" \
67+
.
68+
69+
- name: Log in to VCR and push
70+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.push_to_vcr == 'true' }}
71+
env:
72+
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
73+
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}
74+
VCR_IMAGE: ${{ vars.BITCODE_PIPELINE_VCR_IMAGE }}
75+
run: |
76+
if [ -z "$VERCEL_TOKEN" ] || [ -z "$VERCEL_TEAM_ID" ] || [ -z "$VCR_IMAGE" ]; then
77+
echo "Missing VERCEL_TOKEN, VERCEL_TEAM_ID, or BITCODE_PIPELINE_VCR_IMAGE" >&2
78+
exit 1
79+
fi
80+
printf '%s' "$VERCEL_TOKEN" | docker login vcr.vercel.com \
81+
--username "$VERCEL_TEAM_ID" \
82+
--password-stdin
83+
SHORT_SHA="$(echo "$GITHUB_SHA" | cut -c1-7)"
84+
TAG="${VCR_IMAGE}:v48-${SHORT_SHA}"
85+
docker tag "bitcode-pipeline:ci-${GITHUB_SHA}" "$TAG"
86+
docker push "$TAG"
87+
echo "Pushed $TAG"
88+
echo "Set Production BITCODE_PIPELINE_SANDBOX_IMAGE=$TAG"

packages/generic-hosts/VercelSandbox/src/vercel-sandbox-host.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,17 @@ export function normalizeCreateOptions(
448448
const name =
449449
(typeof createOptions.name === 'string' && createOptions.name.trim()) ||
450450
`bitcode-host-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
451+
const image =
452+
typeof createOptions.image === 'string' && createOptions.image.trim()
453+
? createOptions.image.trim()
454+
: undefined;
455+
// Vercel SDK: runtime and image are mutually exclusive.
456+
const { runtime: _runtime, ...rest } = createOptions;
451457
return {
452-
...createOptions,
458+
...rest,
453459
persistent,
454460
name,
461+
...(image ? { image } : { runtime: createOptions.runtime }),
455462
};
456463
}
457464

packages/host-generics/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,17 @@ export type PipelineSandboxSource =
157157
| PipelineSandboxSourceSnapshot;
158158

159159
export interface SandboxCreateOptions {
160+
/**
161+
* Stock Sandbox runtime (node24 / node22 / …). Mutually exclusive with `image`
162+
* per Vercel Sandbox SDK — do not set both.
163+
*/
160164
runtime?: VercelSandboxRuntime;
165+
/**
166+
* VCR custom image reference (e.g. `bitcode-pipeline:v48-abc` or full
167+
* `vcr.vercel.com/<team>/<project>/bitcode-pipeline:<tag>`). When set, the
168+
* host plan uses the pipeline appliance image instead of a stock runtime.
169+
*/
170+
image?: string;
161171
timeout?: number;
162172
ports?: number[];
163173
resources?: {

packages/pipeline-hosts/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,10 @@ Prefer host primitives/bases:
1010
This package keeps AssetPack-specific host plan/runners (`asset-pack-host-*`)
1111
and thin re-exports of local/vercel sandbox hosts for pipeline callers.
1212
New host implementations belong under `generic-hosts/*`, not here.
13+
14+
## Pipeline appliance image (Production)
15+
16+
Set `BITCODE_PIPELINE_SANDBOX_IMAGE` (and `BITCODE_PIPELINE_HOST=sandbox`) so
17+
`buildAssetPackSandboxHostPlan` uses `Sandbox.create({ image })` with the VCR
18+
pipeline appliance from `@bitcode/pipeline-image` instead of stock `node24` +
19+
in-box monorepo install. See `packages/pipeline-image/README.md`.

packages/pipeline-hosts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"test": "pnpm exec jest --config jest.config.cjs --passWithNoTests",
2121
"typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.typecheck.json --noEmit",
2222
"qa:asset-pack:sandbox": "ts-node --transpile-only src/dev/run-asset-pack-sandbox-host.ts",
23+
"materialize-runners": "ts-node --transpile-only src/dev/materialize-runners.ts",
2324
"type-check": "pnpm run typecheck"
2425
},
2526
"dependencies": {

packages/pipeline-hosts/src/__tests__/asset-pack-host-plan.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ describe('asset-pack sandbox host plan', () => {
7777
).toThrow(/requires a sandbox source/);
7878
});
7979

80+
it('uses VCR pipeline image when sandboxImage / env is set (no stock runtime install)', () => {
81+
const plan = buildAssetPackSandboxHostPlan({
82+
...baseOptions,
83+
mode: 'asset_pack_pipeline',
84+
synthesizeMode: 'deposit',
85+
sandboxImage: 'bitcode-pipeline:v48-test',
86+
source: {
87+
type: 'git',
88+
url: 'https://github.com/engineeredsoftware/ENGI.git',
89+
revision: baseOptions.sourceRevision.commit,
90+
depth: 1,
91+
},
92+
});
93+
94+
expect(plan.createOptions.image).toBe('bitcode-pipeline:v48-test');
95+
expect(plan.createOptions.runtime).toBeUndefined();
96+
expect(plan.createOptions.persistent).toBe(false);
97+
expect(plan.files.map((file) => file.path)).toEqual([
98+
'.bitcode/pipeline-host/manifest.json',
99+
]);
100+
expect(plan.commands.map((command) => command.label)).toEqual([
101+
'runtime-readiness',
102+
'asset-pack-pipeline-run',
103+
]);
104+
const run = plan.commands.find((c) => c.label === 'asset-pack-pipeline-run');
105+
expect(run?.args?.join(' ')).toContain('/opt/bitcode/pipeline/run-pipeline.mjs');
106+
});
107+
80108
it('plans dependency install and live runner commands for real pipeline mode', () => {
81109
const plan = buildAssetPackSandboxHostPlan({
82110
...baseOptions,
@@ -95,6 +123,7 @@ describe('asset-pack sandbox host plan', () => {
95123
revision: baseOptions.sourceRevision.commit,
96124
depth: 1,
97125
});
126+
expect(plan.createOptions.image).toBeUndefined();
98127
expect(plan.commands.map((command) => command.label)).toEqual([
99128
'runtime-readiness',
100129
'package-manager-readiness',

packages/pipeline-hosts/src/asset-pack-host-constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,11 @@ export const PIPELINE_EXIT_CODE_PATH = `${HOST_RUN_DIRECTORY}/pipeline.exit-code
1717
export const SANDBOX_WORKING_DIRECTORY = '/vercel/sandbox' as const;
1818
export const DEFAULT_LONG_TIMEOUT_MS = 45 * 60 * 1000;
1919
export const SANDBOX_PNPM_VERSION = '10.33.0';
20+
21+
/** In-image dispatcher (VCR pipeline appliance). */
22+
export const PIPELINE_IMAGE_ENTRY_DEFAULT = '/opt/bitcode/pipeline/run-pipeline.mjs';
23+
export const PIPELINE_IMAGE_MONOREPO_ROOT_DEFAULT = '/opt/bitcode';
24+
25+
/** Env: when set, host plans use Sandbox.create({ image }) instead of runtime. */
26+
export const PIPELINE_SANDBOX_IMAGE_ENV = 'BITCODE_PIPELINE_SANDBOX_IMAGE';
27+
export const PIPELINE_IMAGE_ENTRY_ENV = 'BITCODE_PIPELINE_IMAGE_ENTRY';

0 commit comments

Comments
 (0)