Skip to content

Commit eb03ebb

Browse files
travisprubenfiszelclaude
authored
fix(cli): Fix nonDottedPaths handling in cli flow lock generation (#8375)
* fix(cli): preserve non-dotted flow lock filenames * test(cli): add non-dotted path tests for generate-metadata and sync pull Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Ruben Fiszel <ruben@windmill.dev> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5296ade commit eb03ebb

File tree

3 files changed

+233
-5
lines changed

3 files changed

+233
-5
lines changed

cli/src/commands/flow/flow_metadata.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import { FlowFile } from "./flow.ts";
2929
import { FlowValue } from "../../../gen/types.gen.ts";
3030
import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts";
3131
import { workspaceDependenciesLanguages } from "../../utils/script_common.ts";
32-
import { extractNameFromFolder, getFolderSuffix } from "../../utils/resource_folders.ts";
32+
import {
33+
extractNameFromFolder,
34+
getNonDottedPaths,
35+
} from "../../utils/resource_folders.ts";
3336

3437
const TOP_HASH = "__flow_hash";
3538
async function generateFlowHash(
@@ -157,7 +160,9 @@ export async function generateFlowLockInternal(
157160
filteredDeps
158161
);
159162

160-
const lockAssigner = newPathAssigner(opts.defaultTs ?? "bun");
163+
const lockAssigner = newPathAssigner(opts.defaultTs ?? "bun", {
164+
skipInlineScriptSuffix: getNonDottedPaths(),
165+
});
161166
const inlineScripts = extractInlineScriptsForFlows(
162167
flowValue.value.modules,
163168
{},

cli/test/sync_pull_push.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,66 @@ excludes: []
17681768
});
17691769
});
17701770

1771+
test("Integration: Sync pull with nonDottedPaths uses non-dotted inline script filenames", async () => {
1772+
await withTestBackend(async (backend, tempDir) => {
1773+
// Push a flow using default dotted paths
1774+
await writeFile(
1775+
`${tempDir}/wmill.yaml`,
1776+
`defaultTs: bun
1777+
includes:
1778+
- "**"
1779+
excludes: []
1780+
`,
1781+
"utf-8",
1782+
);
1783+
1784+
const uniqueId = Date.now();
1785+
const flowName = `f/test/nondot_pull_inline_${uniqueId}`;
1786+
const flowFixture = createFlowFixture(flowName);
1787+
await mkdir(`${tempDir}/f/test/nondot_pull_inline_${uniqueId}${getFolderSuffix("flow")}`, { recursive: true });
1788+
for (const file of Object.values(flowFixture)) {
1789+
await writeFile(`${tempDir}/${file.path}`, file.content, "utf-8");
1790+
}
1791+
1792+
const pushResult = await backend.runCLICommand(
1793+
["sync", "push", "--yes", "--includes", `f/test/nondot_pull_inline_${uniqueId}*/**`],
1794+
tempDir,
1795+
);
1796+
expect(pushResult.code).toEqual(0);
1797+
1798+
// Pull into a fresh directory with nonDottedPaths enabled
1799+
const tempDir2 = await mkdtemp(join(tmpdir(), "wmill_nondot_inline_"));
1800+
try {
1801+
await writeFile(
1802+
`${tempDir2}/wmill.yaml`,
1803+
`defaultTs: bun
1804+
nonDottedPaths: true
1805+
includes:
1806+
- "**"
1807+
excludes: []
1808+
`,
1809+
"utf-8",
1810+
);
1811+
1812+
const pullResult = await backend.runCLICommand(["sync", "pull", "--yes"], tempDir2);
1813+
expect(pullResult.code).toEqual(0);
1814+
1815+
// Verify pulled files use non-dotted inline script naming
1816+
const files = await listFilesRecursive(tempDir2);
1817+
const flowFiles = files.filter((f) => f.includes(`nondot_pull_inline_${uniqueId}`));
1818+
1819+
expect(flowFiles.length > 0).toBeTruthy();
1820+
// Should use __flow folder, not .flow
1821+
expect(flowFiles.some((f) => f.includes("__flow/"))).toBeTruthy();
1822+
// No files should have .inline_script. in their name
1823+
const dottedInlineFiles = flowFiles.filter((f) => f.includes(".inline_script."));
1824+
expect(dottedInlineFiles.length).toEqual(0);
1825+
} finally {
1826+
await cleanupTempDir(tempDir2);
1827+
}
1828+
});
1829+
});
1830+
17711831
// =============================================================================
17721832
// ws_error_handler_muted Persistence Tests
17731833
// =============================================================================

cli/test/unified_generate_metadata.test.ts

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { expect, test, describe } from "bun:test";
99
import { withTestBackend } from "./test_backend.ts";
1010
import { addWorkspace } from "../workspace.ts";
11-
import { writeFile } from "node:fs/promises";
11+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
1212
import {
1313
createLocalScript,
1414
createLocalFlow,
@@ -19,7 +19,12 @@ import {
1919
/**
2020
* Helper to set up a workspace with wmill.yaml
2121
*/
22-
async function setupWorkspace(backend: any, tempDir: string, workspaceName: string) {
22+
async function setupWorkspace(
23+
backend: any,
24+
tempDir: string,
25+
workspaceName: string,
26+
nonDottedPaths = false
27+
) {
2328
const testWorkspace = {
2429
remote: backend.baseUrl,
2530
workspaceId: backend.workspace,
@@ -29,11 +34,88 @@ async function setupWorkspace(backend: any, tempDir: string, workspaceName: stri
2934
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
3035

3136
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
32-
includes:
37+
${nonDottedPaths ? "nonDottedPaths: true\n" : ""}includes:
3338
- "**"
3439
excludes: []`, "utf-8");
3540
}
3641

42+
async function createLocalNonDottedFlow(tempDir: string, name: string) {
43+
const flowDir = `${tempDir}/f/test/${name}__flow`;
44+
await mkdir(flowDir, { recursive: true });
45+
46+
await writeFile(
47+
`${flowDir}/a.ts`,
48+
`export async function main() {\n return "Hello from flow ${name}";\n}`,
49+
"utf-8"
50+
);
51+
52+
await writeFile(
53+
`${flowDir}/flow.yaml`,
54+
`summary: "${name} flow"
55+
description: "A flow for testing"
56+
value:
57+
modules:
58+
- id: a
59+
value:
60+
type: rawscript
61+
content: "!inline a.ts"
62+
language: bun
63+
input_transforms: {}
64+
schema:
65+
$schema: "https://json-schema.org/draft/2020-12/schema"
66+
type: object
67+
properties: {}
68+
required: []
69+
`,
70+
"utf-8"
71+
);
72+
}
73+
74+
async function createLocalNonDottedApp(tempDir: string, name: string) {
75+
const appDir = `${tempDir}/f/test/${name}__app`;
76+
await mkdir(appDir, { recursive: true });
77+
78+
await writeFile(
79+
`${appDir}/app.yaml`,
80+
`summary: "${name} app"
81+
value:
82+
type: app
83+
grid:
84+
- id: button1
85+
data:
86+
type: buttoncomponent
87+
componentInput:
88+
type: runnable
89+
runnable:
90+
type: runnableByName
91+
inlineScript:
92+
content: |
93+
export async function main() {
94+
return "hello from app";
95+
}
96+
language: bun
97+
hiddenInlineScripts: []
98+
css: {}
99+
norefreshbar: false
100+
policy:
101+
on_behalf_of: null
102+
on_behalf_of_email: null
103+
triggerables: {}
104+
execution_mode: viewer
105+
`,
106+
"utf-8"
107+
);
108+
}
109+
110+
async function fileExists(filePath: string): Promise<boolean> {
111+
try {
112+
await stat(filePath);
113+
return true;
114+
} catch {
115+
return false;
116+
}
117+
}
118+
37119
// =============================================================================
38120
// Main test: processes scripts, flows, and apps together
39121
// =============================================================================
@@ -156,6 +238,87 @@ describe("generate-metadata flags", () => {
156238
});
157239
});
158240

241+
test("--lock-only preserves non-dotted flow filenames", async () => {
242+
await withTestBackend(async (backend, tempDir) => {
243+
await setupWorkspace(backend, tempDir, "lock_only_non_dotted_test", true);
244+
245+
await createLocalNonDottedFlow(tempDir, "my_flow");
246+
247+
const result = await backend.runCLICommand(
248+
["generate-metadata", "--yes", "--lock-only"],
249+
tempDir,
250+
"lock_only_non_dotted_test"
251+
);
252+
253+
expect(result.code).toEqual(0);
254+
255+
const flowDir = `${tempDir}/f/test/my_flow__flow`;
256+
const flowYaml = await readFile(`${flowDir}/flow.yaml`, "utf-8");
257+
258+
expect(flowYaml).toContain("!inline a.ts");
259+
expect(flowYaml).toContain("!inline a.lock");
260+
expect(flowYaml).not.toContain(".inline_script.");
261+
expect(await fileExists(`${flowDir}/a.lock`)).toEqual(true);
262+
expect(await fileExists(`${flowDir}/a.inline_script.ts`)).toEqual(false);
263+
expect(await fileExists(`${flowDir}/a.inline_script.lock`)).toEqual(false);
264+
});
265+
});
266+
267+
test("generate-metadata preserves non-dotted flow inline script filenames", async () => {
268+
await withTestBackend(async (backend, tempDir) => {
269+
await setupWorkspace(backend, tempDir, "full_gen_non_dotted_flow_test", true);
270+
271+
await createLocalNonDottedFlow(tempDir, "my_flow");
272+
273+
const result = await backend.runCLICommand(
274+
["generate-metadata", "--yes"],
275+
tempDir,
276+
"full_gen_non_dotted_flow_test"
277+
);
278+
279+
expect(result.code).toEqual(0);
280+
281+
const flowDir = `${tempDir}/f/test/my_flow__flow`;
282+
const flowYaml = await readFile(`${flowDir}/flow.yaml`, "utf-8");
283+
284+
// Inline script references should use non-dotted naming
285+
expect(flowYaml).toContain("!inline a.ts");
286+
expect(flowYaml).toContain("!inline a.lock");
287+
expect(flowYaml).not.toContain(".inline_script.");
288+
expect(await fileExists(`${flowDir}/a.ts`)).toEqual(true);
289+
expect(await fileExists(`${flowDir}/a.lock`)).toEqual(true);
290+
expect(await fileExists(`${flowDir}/a.inline_script.ts`)).toEqual(false);
291+
expect(await fileExists(`${flowDir}/a.inline_script.lock`)).toEqual(false);
292+
});
293+
});
294+
295+
test("generate-metadata uses non-dotted app inline script filenames", async () => {
296+
await withTestBackend(async (backend, tempDir) => {
297+
await setupWorkspace(backend, tempDir, "non_dotted_app_gen_test", true);
298+
299+
await createLocalNonDottedApp(tempDir, "my_app");
300+
301+
const result = await backend.runCLICommand(
302+
["generate-metadata", "--yes"],
303+
tempDir,
304+
"non_dotted_app_gen_test"
305+
);
306+
307+
expect(result.code).toEqual(0);
308+
309+
const appDir = `${tempDir}/f/test/my_app__app`;
310+
const appYaml = await readFile(`${appDir}/app.yaml`, "utf-8");
311+
312+
// Inline script references should use non-dotted naming
313+
expect(appYaml).not.toContain(".inline_script.");
314+
// Verify no dotted inline script files were created
315+
const { readdir: readdirAsync } = await import("node:fs/promises");
316+
const files = await readdirAsync(appDir);
317+
const dottedFiles = files.filter((f: string) => f.includes(".inline_script."));
318+
expect(dottedFiles.length).toEqual(0);
319+
});
320+
});
321+
159322
test("--schema-only only processes scripts (skips flows and apps)", async () => {
160323
await withTestBackend(async (backend, tempDir) => {
161324
await setupWorkspace(backend, tempDir, "schema_only_test");

0 commit comments

Comments
 (0)