Skip to content

Commit 127f0c0

Browse files
V48 (impl-only): Fix deposit sandbox package imports
Ship live runner into sandbox, resolve monorepo packages via BITCODE_MONOREPO_ROOT + .ts URLs, and run with tsx so Pipeliner image mode can load TypeScript package sources.
1 parent 7731a19 commit 127f0c0

7 files changed

Lines changed: 370 additions & 539 deletions

File tree

containers/images/pipeliner/scripts/materialize.mjs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
3434
const mode = (process.env.BITCODE_PIPELINE_HOST_MODE || 'host_smoke').trim();
3535
const monorepoRoot = (process.env.BITCODE_MONOREPO_ROOT || '/opt/bitcode').trim();
3636
37-
// Prefer image layout (runners next to monorepo for relative imports).
37+
// Prefer sandbox-uploaded runners (hot-fixed by host plan), then image-baked.
38+
const sandboxRunnerDir = '/vercel/sandbox/.bitcode/pipeline-host';
3839
const imageRunnerDir = path.join(monorepoRoot, '.bitcode', 'pipeline-host');
3940
const localRunnerDir = __dirname;
4041
4142
async function resolveRunner(name) {
4243
const candidates = [
44+
path.join(sandboxRunnerDir, name),
4345
path.join(imageRunnerDir, name),
4446
path.join(localRunnerDir, name),
4547
];
@@ -72,28 +74,21 @@ async function main() {
7274
monorepoRoot,
7375
'packages/pipeline-hosts/tsconfig.json',
7476
);
77+
const hostsPkg = path.join(monorepoRoot, 'packages/pipeline-hosts');
78+
const outAbs = path.resolve(distDir);
79+
// shell:true so PATH resolves pnpm when invoked under pnpm recursive run.
7580
const result = spawnSync(
76-
'pnpm',
77-
[
78-
'--filter',
79-
'@bitcode/pipeline-hosts',
80-
'exec',
81-
'ts-node',
82-
'--transpile-only',
83-
'--project',
84-
hostsTsconfig,
85-
'src/dev/materialize-runners.ts',
86-
distDir,
87-
],
81+
`pnpm exec ts-node --transpile-only --project ${JSON.stringify(hostsTsconfig)} src/dev/materialize-runners.ts ${JSON.stringify(outAbs)}`,
8882
{
89-
cwd: path.join(monorepoRoot, 'packages/pipeline-hosts'),
83+
cwd: hostsPkg,
9084
encoding: 'utf8',
9185
env: process.env,
86+
shell: true,
9287
},
9388
);
94-
if (result.status !== 0) {
89+
if (result.error || result.status !== 0) {
9590
throw new Error(
96-
`materialize-runners failed (status ${result.status}): ${result.stderr || result.stdout}`,
91+
`materialize-runners failed (status ${result.status}, error=${result.error?.message || 'none'}): ${result.stderr || result.stdout}`,
9792
);
9893
}
9994

packages/pipeline-hosts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"@vercel/sandbox": "2.6.0",
2828
"@bitcode/host-generics": "workspace:*",
2929
"@bitcode/generic-hosts-local": "workspace:*",
30-
"@bitcode/generic-hosts-vercel-sandbox": "workspace:*"
30+
"@bitcode/generic-hosts-vercel-sandbox": "workspace:*",
31+
"tsx": "^4.19.3"
3132
},
3233
"devDependencies": {
3334
"@types/jest": "^29.5.12",

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('asset-pack sandbox host plan', () => {
2929
]);
3030
expect(plan.files.map((file) => file.path)).toEqual([
3131
'.bitcode/pipeline-host/manifest.json',
32+
'.bitcode/pipeline-host/run-live-asset-pack-pipeline.mjs',
3233
'.bitcode/pipeline-host/run-host-smoke.mjs',
3334
'.bitcode/pipeline-host/run-live-asset-pack-pipeline.ts',
3435
]);
@@ -98,13 +99,18 @@ describe('asset-pack sandbox host plan', () => {
9899
expect(plan.createOptions.persistent).toBe(false);
99100
expect(plan.files.map((file) => file.path)).toEqual([
100101
'.bitcode/pipeline-host/manifest.json',
102+
'.bitcode/pipeline-host/run-live-asset-pack-pipeline.mjs',
101103
]);
102104
expect(plan.commands.map((command) => command.label)).toEqual([
103105
'runtime-readiness',
104106
'asset-pack-pipeline-run',
105107
]);
106108
const run = plan.commands.find((c) => c.label === 'asset-pack-pipeline-run');
107-
expect(run?.args?.join(' ')).toContain('/opt/bitcode/pipeline/run-pipeline.mjs');
109+
const runScript = run?.args?.join(' ') ?? '';
110+
// Image path runs sandbox-uploaded runner with tsx (loads monorepo .ts packages).
111+
expect(runScript).toContain('run-live-asset-pack-pipeline.mjs');
112+
expect(runScript).toContain('tsx');
113+
expect(runScript).toContain('/opt/bitcode');
108114
});
109115

110116
it('plans dependency install and live runner commands for real pipeline mode', () => {

packages/pipeline-hosts/src/__tests__/vercel-sandbox-host.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ describe('VercelSandboxPipelineHost', () => {
154154
const result = await host.runHostPlan(plan);
155155

156156
expect(createOptions[0].runtime).toBe('node24');
157-
expect(fakeSandbox.writtenFiles).toHaveLength(3);
157+
// manifest + live runner mjs (+ smoke/ts runners when not image mode)
158+
expect(fakeSandbox.writtenFiles).toHaveLength(4);
158159
expect(fakeSandbox.commands.map((command) => command.cmd)).toEqual(['node', 'node']);
159160
expect(result.outcome).toBe('completed');
160161
expect(result.artifacts.evidence).toEqual({ resultState: 'blocked_readiness' });

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,21 @@ export function buildAssetPackSandboxHostPlan(
207207
: { runtime: options.runtime ?? VERCEL_SANDBOX_HOST_CAPABILITIES.defaultRuntime }),
208208
};
209209

210-
// Image mode: only ship the run manifest (+ optional overlay). Runners live
211-
// in the VCR image under /opt/bitcode.
210+
// Always ship the live runner into the sandbox workspace so host plan fixes
211+
// apply without waiting for a Pipeliner image rebuild. Image mode still uses
212+
// /opt/bitcode packages; the runner is invoked with tsx from monorepo root.
213+
const liveRunnerMjsPath = `${HOST_RUN_DIRECTORY}/run-live-asset-pack-pipeline.mjs`;
212214
const files: PipelineHostPlan['files'] = [
213215
{
214216
path: MANIFEST_PATH,
215217
content: Buffer.from(JSON.stringify(manifest, null, 2)),
216218
mode: 0o644,
217219
},
220+
{
221+
path: liveRunnerMjsPath,
222+
content: Buffer.from(createLiveAssetPackPipelineRunner()),
223+
mode: 0o755,
224+
},
218225
...(usePipelineImage
219226
? []
220227
: [
@@ -284,13 +291,27 @@ function buildPipelineImageCommands(
284291
120000;
285292

286293
if (mode === 'asset_pack_pipeline') {
294+
// Prefer sandbox-uploaded runner (hot-fixed) over image-baked copy.
295+
// tsx loads monorepo .ts package sources under BITCODE_MONOREPO_ROOT.
296+
const sandboxLiveRunner = `${SANDBOX_WORKING_DIRECTORY}/${HOST_RUN_DIRECTORY}/run-live-asset-pack-pipeline.mjs`;
297+
const monorepoRoot = PIPELINE_IMAGE_MONOREPO_ROOT_DEFAULT;
298+
const pipelineRunScript = [
299+
`cd ${shellQuote(monorepoRoot)}`,
300+
// tsx loads monorepo .ts package sources (plain node cannot).
301+
`if ! node --import tsx -e "process.exit(0)" >/dev/null 2>&1; then`,
302+
` npm install -g tsx@4.19.3 || npm install --no-save --prefix ${shellQuote(monorepoRoot)} tsx@4.19.3`,
303+
`fi`,
304+
`RUNNER=${shellQuote(sandboxLiveRunner)}`,
305+
`if [ ! -f "$RUNNER" ]; then RUNNER=${shellQuote(`${monorepoRoot}/.bitcode/pipeline-host/run-live-asset-pack-pipeline.mjs`)}; fi`,
306+
`if [ ! -f "$RUNNER" ]; then echo "live runner missing; falling back to image dispatcher" >&2; node ${shellQuote(pipelineImageEntry)}; else node --import tsx "$RUNNER"; fi`,
307+
].join(' && ');
287308
commands.push({
288309
label: 'asset-pack-pipeline-run',
289310
cmd: 'sh',
290311
args: [
291312
'-lc',
292313
[
293-
`node ${shellQuote(pipelineImageEntry)} > ${shellQuote(PIPELINE_STDOUT_PATH)} 2> ${shellQuote(PIPELINE_STDERR_PATH)}`,
314+
`( ${pipelineRunScript} ) > ${shellQuote(PIPELINE_STDOUT_PATH)} 2> ${shellQuote(PIPELINE_STDERR_PATH)}`,
294315
'code=$?',
295316
`printf "%s" "$code" > ${shellQuote(PIPELINE_EXIT_CODE_PATH)}`,
296317
'exit "$code"',

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ await writeFile(\`\${artifactDir}/evidence.json\`, JSON.stringify(evidence, null
8484
export function createLiveAssetPackPipelineRunner(): string {
8585
return `import { mkdir, readFile, writeFile } from 'node:fs/promises';
8686
import { createHash, randomUUID } from 'node:crypto';
87+
import path from 'node:path';
88+
import { pathToFileURL } from 'node:url';
89+
90+
const monorepoRoot = (process.env.BITCODE_MONOREPO_ROOT || '/opt/bitcode').trim();
91+
/** Absolute file URL into monorepo TypeScript sources (loaded via tsx/ts-node). */
92+
function pkgImport(relPath) {
93+
return pathToFileURL(path.join(monorepoRoot, relPath)).href;
94+
}
8795
8896
const manifestPath = process.env.BITCODE_PIPELINE_HOST_MANIFEST || '${MANIFEST_PATH}';
8997
const artifactDir = process.env.BITCODE_PIPELINE_HOST_ARTIFACT_DIR || '${HOST_RUN_DIRECTORY}';
@@ -1413,12 +1421,14 @@ try {
14131421
{ evaluateBtdOrganizationInterfaceAuthority },
14141422
btdReceiptBuilders,
14151423
] = await Promise.all([
1416-
import('../../packages/asset-packs-pipelines/domain/src/index'),
1417-
import('../../packages/pipelines-generics/src/index'),
1418-
import('../../packages/btd/src/settlement'),
1419-
import('../../packages/btd/src/reconciliation'),
1420-
import('../../packages/btd/src/authority'),
1421-
import('../../packages/btd/src/receipts'),
1424+
// Resolve monorepo packages by absolute file URL + .ts (tsx/ts-node loaders).
1425+
// Relative extensionless imports fail under plain node in the Pipeliner image.
1426+
import(pkgImport('packages/asset-packs-pipelines/domain/src/index.ts')),
1427+
import(pkgImport('packages/pipelines-generics/src/index.ts')),
1428+
import(pkgImport('packages/btd/src/settlement.ts')),
1429+
import(pkgImport('packages/btd/src/reconciliation.ts')),
1430+
import(pkgImport('packages/btd/src/authority.ts')),
1431+
import(pkgImport('packages/btd/src/receipts.ts')),
14221432
]);
14231433
buildBtdAssetPackMintReceiptFn = btdReceiptBuilders.buildBtdAssetPackMintReceipt;
14241434
buildBtdReadReceiptFn = btdReceiptBuilders.buildBtdReadReceipt;
@@ -1443,7 +1453,7 @@ try {
14431453
14441454
const databaseStreamingRequested = process.env.BITCODE_PIPELINE_STREAM_TO_DATABASE === '1';
14451455
if (databaseStreamingRequested) {
1446-
const { supabaseAdmin } = await import('../../packages/supabase/src/index');
1456+
const { supabaseAdmin } = await import(pkgImport('packages/supabase/src/index.ts'));
14471457
supabase = supabaseAdmin;
14481458
userId = await resolvePipelineUserId();
14491459
execution.store('host', 'userId', userId);

0 commit comments

Comments
 (0)