Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions cli/src/commands/flow/flow_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import { FlowFile } from "./flow.ts";
import { FlowValue } from "../../../gen/types.gen.ts";
import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts";
import { workspaceDependenciesLanguages } from "../../utils/script_common.ts";
import { extractNameFromFolder, getFolderSuffix } from "../../utils/resource_folders.ts";
import {
extractNameFromFolder,
getNonDottedPaths,
} from "../../utils/resource_folders.ts";

const TOP_HASH = "__flow_hash";
async function generateFlowHash(
Expand Down Expand Up @@ -157,7 +160,9 @@ export async function generateFlowLockInternal(
filteredDeps
);

const lockAssigner = newPathAssigner(opts.defaultTs ?? "bun");
const lockAssigner = newPathAssigner(opts.defaultTs ?? "bun", {
skipInlineScriptSuffix: getNonDottedPaths(),
});
const inlineScripts = extractInlineScriptsForFlows(
flowValue.value.modules,
{},
Expand Down
60 changes: 60 additions & 0 deletions cli/test/sync_pull_push.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,66 @@ excludes: []
});
});

test("Integration: Sync pull with nonDottedPaths uses non-dotted inline script filenames", async () => {
await withTestBackend(async (backend, tempDir) => {
// Push a flow using default dotted paths
await writeFile(
`${tempDir}/wmill.yaml`,
`defaultTs: bun
includes:
- "**"
excludes: []
`,
"utf-8",
);

const uniqueId = Date.now();
const flowName = `f/test/nondot_pull_inline_${uniqueId}`;
const flowFixture = createFlowFixture(flowName);
await mkdir(`${tempDir}/f/test/nondot_pull_inline_${uniqueId}${getFolderSuffix("flow")}`, { recursive: true });
for (const file of Object.values(flowFixture)) {
await writeFile(`${tempDir}/${file.path}`, file.content, "utf-8");
}

const pushResult = await backend.runCLICommand(
["sync", "push", "--yes", "--includes", `f/test/nondot_pull_inline_${uniqueId}*/**`],
tempDir,
);
expect(pushResult.code).toEqual(0);

// Pull into a fresh directory with nonDottedPaths enabled
const tempDir2 = await mkdtemp(join(tmpdir(), "wmill_nondot_inline_"));
try {
await writeFile(
`${tempDir2}/wmill.yaml`,
`defaultTs: bun
nonDottedPaths: true
includes:
- "**"
excludes: []
`,
"utf-8",
);

const pullResult = await backend.runCLICommand(["sync", "pull", "--yes"], tempDir2);
expect(pullResult.code).toEqual(0);

// Verify pulled files use non-dotted inline script naming
const files = await listFilesRecursive(tempDir2);
const flowFiles = files.filter((f) => f.includes(`nondot_pull_inline_${uniqueId}`));

expect(flowFiles.length > 0).toBeTruthy();
// Should use __flow folder, not .flow
expect(flowFiles.some((f) => f.includes("__flow/"))).toBeTruthy();
// No files should have .inline_script. in their name
const dottedInlineFiles = flowFiles.filter((f) => f.includes(".inline_script."));
expect(dottedInlineFiles.length).toEqual(0);
} finally {
await cleanupTempDir(tempDir2);
}
});
});

// =============================================================================
// ws_error_handler_muted Persistence Tests
// =============================================================================
Expand Down
169 changes: 166 additions & 3 deletions cli/test/unified_generate_metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { expect, test, describe } from "bun:test";
import { withTestBackend } from "./test_backend.ts";
import { addWorkspace } from "../workspace.ts";
import { writeFile } from "node:fs/promises";
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
import {
createLocalScript,
createLocalFlow,
Expand All @@ -19,7 +19,12 @@ import {
/**
* Helper to set up a workspace with wmill.yaml
*/
async function setupWorkspace(backend: any, tempDir: string, workspaceName: string) {
async function setupWorkspace(
backend: any,
tempDir: string,
workspaceName: string,
nonDottedPaths = false
) {
const testWorkspace = {
remote: backend.baseUrl,
workspaceId: backend.workspace,
Expand All @@ -29,11 +34,88 @@ async function setupWorkspace(backend: any, tempDir: string, workspaceName: stri
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });

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

async function createLocalNonDottedFlow(tempDir: string, name: string) {
const flowDir = `${tempDir}/f/test/${name}__flow`;
await mkdir(flowDir, { recursive: true });

await writeFile(
`${flowDir}/a.ts`,
`export async function main() {\n return "Hello from flow ${name}";\n}`,
"utf-8"
);

await writeFile(
`${flowDir}/flow.yaml`,
`summary: "${name} flow"
description: "A flow for testing"
value:
modules:
- id: a
value:
type: rawscript
content: "!inline a.ts"
language: bun
input_transforms: {}
schema:
$schema: "https://json-schema.org/draft/2020-12/schema"
type: object
properties: {}
required: []
`,
"utf-8"
);
}

async function createLocalNonDottedApp(tempDir: string, name: string) {
const appDir = `${tempDir}/f/test/${name}__app`;
await mkdir(appDir, { recursive: true });

await writeFile(
`${appDir}/app.yaml`,
`summary: "${name} app"
value:
type: app
grid:
- id: button1
data:
type: buttoncomponent
componentInput:
type: runnable
runnable:
type: runnableByName
inlineScript:
content: |
export async function main() {
return "hello from app";
}
language: bun
hiddenInlineScripts: []
css: {}
norefreshbar: false
policy:
on_behalf_of: null
on_behalf_of_email: null
triggerables: {}
execution_mode: viewer
`,
"utf-8"
);
}

async function fileExists(filePath: string): Promise<boolean> {
try {
await stat(filePath);
return true;
} catch {
return false;
}
}

// =============================================================================
// Main test: processes scripts, flows, and apps together
// =============================================================================
Expand Down Expand Up @@ -156,6 +238,87 @@ describe("generate-metadata flags", () => {
});
});

test("--lock-only preserves non-dotted flow filenames", async () => {
await withTestBackend(async (backend, tempDir) => {
await setupWorkspace(backend, tempDir, "lock_only_non_dotted_test", true);

await createLocalNonDottedFlow(tempDir, "my_flow");

const result = await backend.runCLICommand(
["generate-metadata", "--yes", "--lock-only"],
tempDir,
"lock_only_non_dotted_test"
);

expect(result.code).toEqual(0);

const flowDir = `${tempDir}/f/test/my_flow__flow`;
const flowYaml = await readFile(`${flowDir}/flow.yaml`, "utf-8");

expect(flowYaml).toContain("!inline a.ts");
expect(flowYaml).toContain("!inline a.lock");
expect(flowYaml).not.toContain(".inline_script.");
expect(await fileExists(`${flowDir}/a.lock`)).toEqual(true);
expect(await fileExists(`${flowDir}/a.inline_script.ts`)).toEqual(false);
expect(await fileExists(`${flowDir}/a.inline_script.lock`)).toEqual(false);
});
});

test("generate-metadata preserves non-dotted flow inline script filenames", async () => {
await withTestBackend(async (backend, tempDir) => {
await setupWorkspace(backend, tempDir, "full_gen_non_dotted_flow_test", true);

await createLocalNonDottedFlow(tempDir, "my_flow");

const result = await backend.runCLICommand(
["generate-metadata", "--yes"],
tempDir,
"full_gen_non_dotted_flow_test"
);

expect(result.code).toEqual(0);

const flowDir = `${tempDir}/f/test/my_flow__flow`;
const flowYaml = await readFile(`${flowDir}/flow.yaml`, "utf-8");

// Inline script references should use non-dotted naming
expect(flowYaml).toContain("!inline a.ts");
expect(flowYaml).toContain("!inline a.lock");
expect(flowYaml).not.toContain(".inline_script.");
expect(await fileExists(`${flowDir}/a.ts`)).toEqual(true);
expect(await fileExists(`${flowDir}/a.lock`)).toEqual(true);
expect(await fileExists(`${flowDir}/a.inline_script.ts`)).toEqual(false);
expect(await fileExists(`${flowDir}/a.inline_script.lock`)).toEqual(false);
});
});

test("generate-metadata uses non-dotted app inline script filenames", async () => {
await withTestBackend(async (backend, tempDir) => {
await setupWorkspace(backend, tempDir, "non_dotted_app_gen_test", true);

await createLocalNonDottedApp(tempDir, "my_app");

const result = await backend.runCLICommand(
["generate-metadata", "--yes"],
tempDir,
"non_dotted_app_gen_test"
);

expect(result.code).toEqual(0);

const appDir = `${tempDir}/f/test/my_app__app`;
const appYaml = await readFile(`${appDir}/app.yaml`, "utf-8");

// Inline script references should use non-dotted naming
expect(appYaml).not.toContain(".inline_script.");
// Verify no dotted inline script files were created
const { readdir: readdirAsync } = await import("node:fs/promises");
const files = await readdirAsync(appDir);
const dottedFiles = files.filter((f: string) => f.includes(".inline_script."));
expect(dottedFiles.length).toEqual(0);
});
});

test("--schema-only only processes scripts (skips flows and apps)", async () => {
await withTestBackend(async (backend, tempDir) => {
await setupWorkspace(backend, tempDir, "schema_only_test");
Expand Down
Loading