diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 9a0c319cf8..a6f526804d 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -104,6 +104,7 @@ import { getModuleFolderSuffix, isModuleEntryPoint, getScriptBasePathFromModulePath, + hasWrongFormatSuffix, } from "../../utils/resource_folders.ts"; // Merge CLI options with effective settings, preserving CLI flags as overrides @@ -1263,11 +1264,22 @@ export async function elementsToMap( ): Promise<{ [key: string]: string }> { const map: { [key: string]: string } = {}; const processedBasePaths = new Set(); + const wrongFormatPaths: string[] = []; // Cache git branch at the start to avoid repeated execSync calls per file const cachedBranch = branchOverride ?? getCurrentGitBranch() ?? undefined; for await (const entry of readDirRecursiveWithIgnore(ignore, els)) { // console.log("FOO", entry.path, entry.ignored, entry.isDirectory) - if (entry.isDirectory || entry.ignored) { + if (entry.isDirectory) { + // Check for folder suffix format mismatch (only for local paths) + if (!isRemote) { + const dirName = entry.path.split(SEP).pop() ?? ""; + if (hasWrongFormatSuffix(dirName)) { + wrongFormatPaths.push(entry.path); + } + } + continue; + } + if (entry.ignored) { continue; } const path = entry.path; @@ -1442,6 +1454,20 @@ export async function elementsToMap( } // Note: branch-specific files for other branches are already filtered out earlier } + + if (wrongFormatPaths.length > 0) { + const isNonDotted = getNonDottedPaths(); + const foundFormat = isNonDotted ? ".flow/.app/.raw_app" : "__flow/__app/__raw_app"; + const expectedFormat = isNonDotted ? "__flow/__app/__raw_app" : ".flow/.app/.raw_app"; + const configHint = isNonDotted + ? "Either remove 'nonDottedPaths: true' from wmill.yaml, or rename these directories to use __flow/__app/__raw_app format." + : "Either add 'nonDottedPaths: true' to wmill.yaml, or rename these directories to use .flow/.app/.raw_app format."; + const pathList = wrongFormatPaths.map((p) => ` ${p}`).join("\n"); + throw new Error( + `Found ${wrongFormatPaths.length} directory(ies) using ${foundFormat} format, but wmill.yaml expects ${expectedFormat}:\n${pathList}\n${configHint}` + ); + } + return map; } diff --git a/cli/src/utils/resource_folders.ts b/cli/src/utils/resource_folders.ts index f720fdc680..8a72a8b9ff 100644 --- a/cli/src/utils/resource_folders.ts +++ b/cli/src/utils/resource_folders.ts @@ -156,6 +156,24 @@ export function getMetadataPathSuffix( // Path Detection Functions // ============================================================================ +/** + * Check if a directory name uses the *wrong* folder suffix format for the + * current nonDottedPaths setting. Returns the resource type if mismatched, + * null if the name is fine (or not a resource folder at all). + * + * - nonDottedPaths=false (dotted mode): flags __flow, __app, __raw_app + * - nonDottedPaths=true (non-dotted): flags .flow, .app, .raw_app + */ +export function hasWrongFormatSuffix(dirName: string): FolderResourceType | null { + const wrongSuffixes = _nonDottedPaths ? DOTTED_SUFFIXES : NON_DOTTED_SUFFIXES; + for (const [type, suffix] of Object.entries(wrongSuffixes)) { + if (dirName.endsWith(suffix)) { + return type as FolderResourceType; + } + } + return null; +} + /** Normalize path separators to forward slash for cross-platform matching */ function normalizeSep(p: string): string { return p.replaceAll("\\", "/"); diff --git a/cli/test/sync_pull_push.test.ts b/cli/test/sync_pull_push.test.ts index 93f42bee54..07f3e9226b 100644 --- a/cli/test/sync_pull_push.test.ts +++ b/cli/test/sync_pull_push.test.ts @@ -41,6 +41,7 @@ import { isFlowInlineScriptPath, isRawAppBackendPath, getModuleFolderSuffix, + hasWrongFormatSuffix, } from "../src/utils/resource_folders.ts"; import { newPathAssigner } from "../windmill-utils-internal/src/path-utils/path-assigner.ts"; @@ -1240,6 +1241,32 @@ test("setNonDottedPaths and getNonDottedPaths work correctly", () => { expect(getNonDottedPaths()).toEqual(false); }); +test("hasWrongFormatSuffix detects mismatched folder suffixes", () => { + // In dotted mode (default), non-dotted dirs are wrong + setNonDottedPaths(false); + expect(hasWrongFormatSuffix("my_flow__flow")).toEqual("flow"); + expect(hasWrongFormatSuffix("my_app__app")).toEqual("app"); + expect(hasWrongFormatSuffix("my_raw_app__raw_app")).toEqual("raw_app"); + // Correct format should return null + expect(hasWrongFormatSuffix("my_flow.flow")).toBeNull(); + expect(hasWrongFormatSuffix("my_app.app")).toBeNull(); + expect(hasWrongFormatSuffix("my_raw_app.raw_app")).toBeNull(); + // Non-resource dirs return null + expect(hasWrongFormatSuffix("some_folder")).toBeNull(); + expect(hasWrongFormatSuffix("node_modules")).toBeNull(); + + // In non-dotted mode, dotted dirs are wrong + setNonDottedPaths(true); + expect(hasWrongFormatSuffix("my_flow.flow")).toEqual("flow"); + expect(hasWrongFormatSuffix("my_app.app")).toEqual("app"); + expect(hasWrongFormatSuffix("my_raw_app.raw_app")).toEqual("raw_app"); + // Correct format should return null + expect(hasWrongFormatSuffix("my_flow__flow")).toBeNull(); + expect(hasWrongFormatSuffix("my_app__app")).toBeNull(); + expect(hasWrongFormatSuffix("my_raw_app__raw_app")).toBeNull(); + setNonDottedPaths(false); // Reset +}); + // ============================================================================= // nonDottedPaths Fixture Functions // =============================================================================