Skip to content

Commit 302d73a

Browse files
committed
fix(cli): make local flow path scripts opt-in
1 parent 7ec9bec commit 302d73a

File tree

2 files changed

+148
-75
lines changed

2 files changed

+148
-75
lines changed

cli/src/commands/flow/flow.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,15 @@ async function preview(
236236
opts: GlobalOptions & {
237237
data?: string;
238238
silent: boolean;
239+
local?: boolean;
239240
} & SyncOptions,
240241
flowPath: string
241242
) {
242243
opts = await mergeConfigWithConfigFile(opts);
243244
const workspace = await resolveWorkspace(opts);
244245
await requireLogin(opts);
245-
const codebases = await listSyncCodebases(opts);
246+
const useLocalPathScripts = !!opts.local;
247+
const codebases = useLocalPathScripts ? await listSyncCodebases(opts) : [];
246248

247249
// Normalize path - ensure it's a directory path to a .flow folder
248250
if (!flowPath.endsWith(".flow") && !flowPath.endsWith(".flow" + SEP)) {
@@ -279,13 +281,16 @@ async function preview(
279281
await replaceInlineScripts([localFlow.value.preprocessor_module], fileReader, log, flowPath, SEP);
280282
}
281283

282-
// Replace PathScript modules with local file content so preview uses local versions
283-
const localScriptReader = createPreviewLocalScriptReader({
284-
exts,
285-
defaultTs: opts.defaultTs,
286-
codebases,
287-
});
288-
await replaceAllPathScriptsWithLocal(localFlow.value, localScriptReader, log);
284+
if (useLocalPathScripts) {
285+
// Opt-in local rewrite for PathScript modules. By default preview keeps the
286+
// historical behavior and lets the backend resolve workspace scripts remotely.
287+
const localScriptReader = createPreviewLocalScriptReader({
288+
exts,
289+
defaultTs: opts.defaultTs,
290+
codebases,
291+
});
292+
await replaceAllPathScriptsWithLocal(localFlow.value, localScriptReader, log);
293+
}
289294

290295
const input = opts.data ? await resolve(opts.data) : {};
291296

@@ -468,6 +473,10 @@ const command = new Command()
468473
"-s --silent",
469474
"Do not output anything other then the final output. Useful for scripting."
470475
)
476+
.option(
477+
"--local",
478+
"Resolve PathScript steps from local files instead of remote workspace scripts."
479+
)
471480
.action(preview as any)
472481
.command(
473482
"generate-locks",

cli/test/preview.test.ts

Lines changed: 131 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,39 @@ schema:
122122
await writeFile(`${dir}/flow.yaml`, flowYaml, "utf-8");
123123
}
124124

125+
async function createPathScriptFlow(
126+
tempDir: string,
127+
flowPath: string,
128+
options: {
129+
summary: string;
130+
scriptPath: string;
131+
inputTransforms?: string;
132+
}
133+
): Promise<void> {
134+
const dir = `${tempDir}/${flowPath}`;
135+
await mkdir(dir, { recursive: true });
136+
const inputTransforms = options.inputTransforms
137+
? ` input_transforms:\n${options.inputTransforms}`
138+
: " input_transforms: {}\n";
139+
140+
const flowYaml = `summary: "${options.summary}"
141+
description: "Test flow"
142+
value:
143+
modules:
144+
- id: "a"
145+
value:
146+
type: "script"
147+
path: "${options.scriptPath}"
148+
${inputTransforms}
149+
schema:
150+
$schema: "https://json-schema.org/draft/2020-12/schema"
151+
type: object
152+
properties: {}
153+
required: []
154+
`;
155+
await writeFile(`${dir}/flow.yaml`, flowYaml, "utf-8");
156+
}
157+
125158
// =============================================================================
126159
// SCRIPT PREVIEW TESTS
127160
// =============================================================================
@@ -381,51 +414,63 @@ test("flow preview: simple flow", async () => {
381414
});
382415
});
383416

384-
test("flow preview: uses local script for PathScript steps", async () => {
417+
test("flow preview: uses remote PathScript by default and local PathScript with --local", async () => {
385418
await withTestBackend(async (backend, tempDir) => {
386419
await createWmillConfig(tempDir, { defaultTs: "bun" });
387420

388-
// Create a local script file that the flow will reference by path
389421
await createScript(
390422
tempDir,
391423
"f/test/helper_script.ts",
392-
`export function main(name: string = "World") { return \`Local script says: \${name}!\`; }`
424+
`export function main(name: string = "World") { return \`Remote script says: \${name}!\`; }`
393425
);
394426

395-
// Create a flow that references the script by path (PathScript)
396-
const flowDir = `${tempDir}/f/test/path_flow.flow`;
397-
await mkdir(flowDir, { recursive: true });
398-
const flowYaml = `summary: "Flow with PathScript"
399-
description: "Test flow that references a script by path"
400-
value:
401-
modules:
402-
- id: "a"
403-
value:
404-
type: "script"
405-
path: "f/test/helper_script"
406-
input_transforms:
407-
name:
427+
const pushResult = await backend.runCLICommand(
428+
["script", "push", "f/test/helper_script.ts"],
429+
tempDir
430+
);
431+
expect(pushResult.code).toEqual(0);
432+
433+
await writeFile(
434+
`${tempDir}/f/test/helper_script.ts`,
435+
`export function main(name: string = "World") { return \`Local script says: \${name}!\`; }`,
436+
"utf-8"
437+
);
438+
439+
await createPathScriptFlow(tempDir, "f/test/path_flow.flow", {
440+
summary: "Flow with PathScript",
441+
scriptPath: "f/test/helper_script",
442+
inputTransforms: ` name:
408443
type: "static"
409444
value: "PathTest"
410-
schema:
411-
$schema: "https://json-schema.org/draft/2020-12/schema"
412-
type: object
413-
properties: {}
414-
required: []
415-
`;
416-
await writeFile(`${flowDir}/flow.yaml`, flowYaml, "utf-8");
445+
`,
446+
});
417447

418-
const result = await backend.runCLICommand(
448+
const remoteResult = await backend.runCLICommand(
419449
["flow", "preview", "f/test/path_flow.flow"],
420450
tempDir
421451
);
422452

423-
expect(result.code).toEqual(0);
424-
expect(result.stdout + result.stderr).toContain("Local script says: PathTest!");
453+
expect(remoteResult.code).toEqual(0);
454+
expect(remoteResult.stdout + remoteResult.stderr).toContain(
455+
"Remote script says: PathTest!"
456+
);
457+
expect(remoteResult.stdout + remoteResult.stderr).not.toContain(
458+
"Local script says: PathTest!"
459+
);
460+
461+
const localResult = await backend.runCLICommand(
462+
["flow", "preview", "--local", "f/test/path_flow.flow"],
463+
tempDir
464+
);
465+
466+
expect(localResult.code).toEqual(0);
467+
expect(localResult.stdout + localResult.stderr).toContain(
468+
"Local script says: PathTest!"
469+
);
425470
});
426471
});
427472

428-
test("flow preview: respects defaultTs when resolving local PathScripts", async () => {
473+
test("flow preview --local: respects defaultTs when resolving local PathScripts", async () => {
429474
await withTestBackend(async (backend, tempDir) => {
430475
await createWmillConfig(tempDir, { defaultTs: "deno" });
431476

@@ -435,27 +480,13 @@ test("flow preview: respects defaultTs when resolving local PathScripts", async
435480
`export function main() { return Deno.version.deno ? "deno-runtime" : "missing"; }`
436481
);
437482

438-
const flowDir = `${tempDir}/f/test/deno_path_flow.flow`;
439-
await mkdir(flowDir, { recursive: true });
440-
const flowYaml = `summary: "Flow with Deno PathScript"
441-
description: "Test flow that references a plain .ts Deno script by path"
442-
value:
443-
modules:
444-
- id: "a"
445-
value:
446-
type: "script"
447-
path: "f/test/deno_helper"
448-
input_transforms: {}
449-
schema:
450-
$schema: "https://json-schema.org/draft/2020-12/schema"
451-
type: object
452-
properties: {}
453-
required: []
454-
`;
455-
await writeFile(`${flowDir}/flow.yaml`, flowYaml, "utf-8");
483+
await createPathScriptFlow(tempDir, "f/test/deno_path_flow.flow", {
484+
summary: "Flow with Deno PathScript",
485+
scriptPath: "f/test/deno_helper",
486+
});
456487

457488
const result = await backend.runCLICommand(
458-
["flow", "preview", "f/test/deno_path_flow.flow"],
489+
["flow", "preview", "--local", "f/test/deno_path_flow.flow"],
459490
tempDir
460491
);
461492

@@ -464,7 +495,7 @@ schema:
464495
});
465496
});
466497

467-
test("flow preview: bundles local PathScripts with local imports", async () => {
498+
test("flow preview --local: bundles local PathScripts with local imports", async () => {
468499
await withTestBackend(async (backend, tempDir) => {
469500
await createWmillConfig(tempDir, {
470501
defaultTs: "bun",
@@ -490,30 +521,17 @@ export function main(name: string = "World") {
490521
}`
491522
);
492523

493-
const flowDir = `${tempDir}/f/test/importing_path_flow.flow`;
494-
await mkdir(flowDir, { recursive: true });
495-
const flowYaml = `summary: "Flow with imported PathScript"
496-
description: "Test flow that references a local codebase script by path"
497-
value:
498-
modules:
499-
- id: "a"
500-
value:
501-
type: "script"
502-
path: "f/flow_codebase/main_script"
503-
input_transforms:
504-
name:
524+
await createPathScriptFlow(tempDir, "f/test/importing_path_flow.flow", {
525+
summary: "Flow with imported PathScript",
526+
scriptPath: "f/flow_codebase/main_script",
527+
inputTransforms: ` name:
505528
type: "static"
506529
value: "FlowTest"
507-
schema:
508-
$schema: "https://json-schema.org/draft/2020-12/schema"
509-
type: object
510-
properties: {}
511-
required: []
512-
`;
513-
await writeFile(`${flowDir}/flow.yaml`, flowYaml, "utf-8");
530+
`,
531+
});
514532

515533
const result = await backend.runCLICommand(
516-
["flow", "preview", "f/test/importing_path_flow.flow"],
534+
["flow", "preview", "--local", "f/test/importing_path_flow.flow"],
517535
tempDir
518536
);
519537

@@ -523,3 +541,49 @@ schema:
523541
);
524542
});
525543
});
544+
545+
test("flow preview --local: fails loudly for asset-backed codebase scripts", async () => {
546+
await withTestBackend(async (backend, tempDir) => {
547+
await createWmillConfig(tempDir, {
548+
defaultTs: "bun",
549+
codebases: [{
550+
relative_path: "f/codebase_tar",
551+
includes: ["**"],
552+
assets: [{ from: "f/codebase_tar/data.json", to: "data.json" }],
553+
}],
554+
});
555+
556+
await mkdir(`${tempDir}/f/codebase_tar`, { recursive: true });
557+
await writeFile(
558+
`${tempDir}/f/codebase_tar/data.json`,
559+
JSON.stringify({ message: "Hello from asset!" }),
560+
"utf-8"
561+
);
562+
563+
await createScript(
564+
tempDir,
565+
"f/codebase_tar/main_script.ts",
566+
`import * as fs from "fs";
567+
568+
export function main() {
569+
const data = JSON.parse(fs.readFileSync("data.json", "utf-8"));
570+
return data.message;
571+
}`
572+
);
573+
574+
await createPathScriptFlow(tempDir, "f/test/assets_path_flow.flow", {
575+
summary: "Flow with asset-backed PathScript",
576+
scriptPath: "f/codebase_tar/main_script",
577+
});
578+
579+
const localResult = await backend.runCLICommand(
580+
["flow", "preview", "--local", "f/test/assets_path_flow.flow"],
581+
tempDir
582+
);
583+
584+
expect(localResult.code).not.toEqual(0);
585+
expect(localResult.stdout + localResult.stderr).toContain(
586+
"requires codebase assets"
587+
);
588+
});
589+
});

0 commit comments

Comments
 (0)