mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
nonDottedPaths fixes
This commit is contained in:
@@ -91,6 +91,7 @@ import {
|
||||
isRawAppFolderMetadataFile,
|
||||
getDeleteSuffix,
|
||||
transformJsonPathToDir,
|
||||
getFolderSuffix,
|
||||
getFolderSuffixWithSep,
|
||||
} from "../../utils/resource_folders.ts";
|
||||
|
||||
@@ -1289,7 +1290,7 @@ export async function elementsToMap(
|
||||
}
|
||||
|
||||
if (isRawAppFile(path)) {
|
||||
const suffix = path.split(".raw_app" + SEP).pop();
|
||||
const suffix = path.split(getFolderSuffix("raw_app") + SEP).pop();
|
||||
if (
|
||||
suffix?.startsWith("dist/") ||
|
||||
suffix == "wmill.d.ts" ||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* (.flow, .app, .raw_app) or dunder-prefixed names (__flow, __app, __raw_app).
|
||||
*/
|
||||
|
||||
import { SEP } from "../../deps.ts";
|
||||
import { log, SEP } from "../../deps.ts";
|
||||
|
||||
// Resource types that use folder-based storage
|
||||
export type FolderResourceType = "flow" | "app" | "raw_app";
|
||||
@@ -28,7 +28,9 @@ const NON_DOTTED_SUFFIXES = {
|
||||
raw_app: "__raw_app",
|
||||
} as const;
|
||||
|
||||
export type FolderSuffixes = typeof DOTTED_SUFFIXES | typeof NON_DOTTED_SUFFIXES;
|
||||
export type FolderSuffixes =
|
||||
| typeof DOTTED_SUFFIXES
|
||||
| typeof NON_DOTTED_SUFFIXES;
|
||||
|
||||
// Global state for nonDottedPaths configuration
|
||||
let _nonDottedPaths = false;
|
||||
@@ -39,6 +41,9 @@ let _nonDottedPaths = false;
|
||||
* This should be called once at startup based on wmill.yaml configuration.
|
||||
*/
|
||||
export function setNonDottedPaths(value: boolean): void {
|
||||
if (value) {
|
||||
log.info("Using non-dotted paths (__flow, __app, __raw_app)");
|
||||
}
|
||||
_nonDottedPaths = value;
|
||||
}
|
||||
|
||||
@@ -148,7 +153,7 @@ export function isRawAppBackendPath(filePath: string): boolean {
|
||||
// Normalize path separators for consistent matching
|
||||
const normalizedPath = filePath.replaceAll(SEP, "/");
|
||||
// Check if path contains pattern: *.[suffix]/backend/
|
||||
const escapedSuffix = suffixes.raw_app.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const escapedSuffix = suffixes.raw_app.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const pattern = new RegExp(`${escapedSuffix}/backend/`);
|
||||
return pattern.test(normalizedPath);
|
||||
}
|
||||
@@ -205,7 +210,12 @@ export function buildMetadataPath(
|
||||
type: FolderResourceType,
|
||||
format: "yaml" | "json"
|
||||
): string {
|
||||
return resourceName + getFolderSuffixes()[type] + SEP + METADATA_FILES[type][format];
|
||||
return (
|
||||
resourceName +
|
||||
getFolderSuffixes()[type] +
|
||||
SEP +
|
||||
METADATA_FILES[type][format]
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -258,27 +268,71 @@ export function extractNameFromFolder(
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Check if a path ends with a flow metadata file suffix
|
||||
* Check if a path ends with a flow metadata file suffix.
|
||||
* Detects BOTH API format (always dotted: .flow.json) and local format (user-configured).
|
||||
* This is necessary because the API always returns dotted format, but local files
|
||||
* may use non-dotted format if nonDottedPaths is configured.
|
||||
*/
|
||||
export function isFlowMetadataFile(p: string): boolean {
|
||||
const suffixes = getFolderSuffixes();
|
||||
return p.endsWith(suffixes.flow + ".json") || p.endsWith(suffixes.flow + ".yaml");
|
||||
// Always check API format (dotted)
|
||||
if (
|
||||
p.endsWith(DOTTED_SUFFIXES.flow + ".json") ||
|
||||
p.endsWith(DOTTED_SUFFIXES.flow + ".yaml")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// Also check non-dotted format for local files
|
||||
if (_nonDottedPaths) {
|
||||
return (
|
||||
p.endsWith(NON_DOTTED_SUFFIXES.flow + ".json") ||
|
||||
p.endsWith(NON_DOTTED_SUFFIXES.flow + ".yaml")
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path ends with an app metadata file suffix
|
||||
* Check if a path ends with an app metadata file suffix.
|
||||
* Detects BOTH API format (always dotted: .app.json) and local format (user-configured).
|
||||
*/
|
||||
export function isAppMetadataFile(p: string): boolean {
|
||||
const suffixes = getFolderSuffixes();
|
||||
return p.endsWith(suffixes.app + ".json") || p.endsWith(suffixes.app + ".yaml");
|
||||
// Always check API format (dotted)
|
||||
if (
|
||||
p.endsWith(DOTTED_SUFFIXES.app + ".json") ||
|
||||
p.endsWith(DOTTED_SUFFIXES.app + ".yaml")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// Also check non-dotted format for local files
|
||||
if (_nonDottedPaths) {
|
||||
return (
|
||||
p.endsWith(NON_DOTTED_SUFFIXES.app + ".json") ||
|
||||
p.endsWith(NON_DOTTED_SUFFIXES.app + ".yaml")
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path ends with a raw_app metadata file suffix
|
||||
* Check if a path ends with a raw_app metadata file suffix.
|
||||
* Detects BOTH API format (always dotted: .raw_app.json) and local format (user-configured).
|
||||
*/
|
||||
export function isRawAppMetadataFile(p: string): boolean {
|
||||
const suffixes = getFolderSuffixes();
|
||||
return p.endsWith(suffixes.raw_app + ".json") || p.endsWith(suffixes.raw_app + ".yaml");
|
||||
// Always check API format (dotted)
|
||||
if (
|
||||
p.endsWith(DOTTED_SUFFIXES.raw_app + ".json") ||
|
||||
p.endsWith(DOTTED_SUFFIXES.raw_app + ".yaml")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// Also check non-dotted format for local files
|
||||
if (_nonDottedPaths) {
|
||||
return (
|
||||
p.endsWith(NON_DOTTED_SUFFIXES.raw_app + ".json") ||
|
||||
p.endsWith(NON_DOTTED_SUFFIXES.raw_app + ".yaml")
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,16 +362,26 @@ export function getDeleteSuffix(
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a JSON path to the appropriate directory/file path for sync
|
||||
* e.g., "f/my_flow.flow.json" -> "f/my_flow.flow" or "f/my_flow__flow.json" -> "f/my_flow__flow"
|
||||
* Transform a JSON path from API format to local directory path for sync.
|
||||
* The API always returns dotted format (.flow.json, .app.json, .raw_app.json).
|
||||
* This function transforms to the user's configured format (dotted or non-dotted).
|
||||
* e.g., with nonDottedPaths=true: "f/my_flow.flow.json" -> "f/my_flow__flow"
|
||||
* e.g., with nonDottedPaths=false: "f/my_flow.flow.json" -> "f/my_flow.flow"
|
||||
*/
|
||||
export function transformJsonPathToDir(
|
||||
p: string,
|
||||
type: FolderResourceType
|
||||
): string {
|
||||
const suffixes = getFolderSuffixes();
|
||||
const fullSuffix = suffixes[type] + ".json";
|
||||
if (p.endsWith(fullSuffix)) {
|
||||
// API always returns dotted format
|
||||
const apiSuffix = DOTTED_SUFFIXES[type] + ".json";
|
||||
if (p.endsWith(apiSuffix)) {
|
||||
// Remove the API suffix and add user's configured suffix
|
||||
const basePath = p.substring(0, p.length - apiSuffix.length);
|
||||
return basePath + getFolderSuffixes()[type];
|
||||
}
|
||||
// Also handle the case where path already has user's configured format
|
||||
const userSuffix = getFolderSuffixes()[type] + ".json";
|
||||
if (p.endsWith(userSuffix)) {
|
||||
return p.substring(0, p.length - 5); // Remove ".json"
|
||||
}
|
||||
// Path doesn't match expected suffix pattern, return unchanged
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck This file is copied from a JS project, so it's not type-safe.
|
||||
|
||||
import { colors, encodeHex, log } from "../../deps.ts";
|
||||
import { colors, encodeHex, log, SEP } from "../../deps.ts";
|
||||
import crypto from "node:crypto";
|
||||
import { fetchVersion } from "../core/context.ts";
|
||||
import { updateGlobalVersions } from "../commands/sync/global.ts";
|
||||
|
||||
@@ -22,6 +22,10 @@ import {
|
||||
hasFolderSuffix,
|
||||
setNonDottedPaths,
|
||||
getNonDottedPaths,
|
||||
isFlowMetadataFile,
|
||||
isAppMetadataFile,
|
||||
isRawAppMetadataFile,
|
||||
transformJsonPathToDir,
|
||||
} from "../src/utils/resource_folders.ts";
|
||||
|
||||
// =============================================================================
|
||||
@@ -481,6 +485,123 @@ Deno.test("buildFolderPath creates correct paths", () => {
|
||||
assertEquals(buildFolderPath("u/admin/raw_app", "raw_app"), "u/admin/raw_app.raw_app");
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// nonDottedPaths Tests - API format detection and transformation
|
||||
// =============================================================================
|
||||
|
||||
Deno.test("Metadata file detection works with dotted format (default)", () => {
|
||||
// Ensure we're in default mode
|
||||
setNonDottedPaths(false);
|
||||
|
||||
// API always returns dotted format
|
||||
assert(isFlowMetadataFile("f/my_flow.flow.json"), "Should detect .flow.json");
|
||||
assert(isFlowMetadataFile("f/my_flow.flow.yaml"), "Should detect .flow.yaml");
|
||||
assert(isAppMetadataFile("f/my_app.app.json"), "Should detect .app.json");
|
||||
assert(isAppMetadataFile("f/my_app.app.yaml"), "Should detect .app.yaml");
|
||||
assert(isRawAppMetadataFile("f/my_raw.raw_app.json"), "Should detect .raw_app.json");
|
||||
assert(isRawAppMetadataFile("f/my_raw.raw_app.yaml"), "Should detect .raw_app.yaml");
|
||||
|
||||
// Non-matching should return false
|
||||
assert(!isFlowMetadataFile("f/my_script.ts"), "Should not detect script file");
|
||||
assert(!isAppMetadataFile("f/my_script.ts"), "Should not detect script file");
|
||||
});
|
||||
|
||||
Deno.test("Metadata file detection works with nonDottedPaths=true", () => {
|
||||
// Store original value
|
||||
const wasNonDotted = getNonDottedPaths();
|
||||
|
||||
try {
|
||||
setNonDottedPaths(true);
|
||||
|
||||
// API format (dotted) should still be detected
|
||||
assert(isFlowMetadataFile("f/my_flow.flow.json"), "Should detect API format .flow.json");
|
||||
assert(isAppMetadataFile("f/my_app.app.json"), "Should detect API format .app.json");
|
||||
assert(isRawAppMetadataFile("f/my_raw.raw_app.json"), "Should detect API format .raw_app.json");
|
||||
|
||||
// Local format (non-dotted) should also be detected
|
||||
assert(isFlowMetadataFile("f/my_flow__flow.json"), "Should detect local format __flow.json");
|
||||
assert(isFlowMetadataFile("f/my_flow__flow.yaml"), "Should detect local format __flow.yaml");
|
||||
assert(isAppMetadataFile("f/my_app__app.json"), "Should detect local format __app.json");
|
||||
assert(isRawAppMetadataFile("f/my_raw__raw_app.json"), "Should detect local format __raw_app.json");
|
||||
} finally {
|
||||
// Restore original value
|
||||
setNonDottedPaths(wasNonDotted);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("transformJsonPathToDir transforms API format to local format", () => {
|
||||
// Store original value
|
||||
const wasNonDotted = getNonDottedPaths();
|
||||
|
||||
try {
|
||||
// Test with dotted paths (default)
|
||||
setNonDottedPaths(false);
|
||||
assertEquals(
|
||||
transformJsonPathToDir("f/my_flow.flow.json", "flow"),
|
||||
"f/my_flow.flow",
|
||||
"Should transform dotted API format to dotted local format"
|
||||
);
|
||||
assertEquals(
|
||||
transformJsonPathToDir("f/my_app.app.json", "app"),
|
||||
"f/my_app.app",
|
||||
"Should transform app correctly"
|
||||
);
|
||||
assertEquals(
|
||||
transformJsonPathToDir("f/my_raw.raw_app.json", "raw_app"),
|
||||
"f/my_raw.raw_app",
|
||||
"Should transform raw_app correctly"
|
||||
);
|
||||
|
||||
// Test with non-dotted paths
|
||||
setNonDottedPaths(true);
|
||||
assertEquals(
|
||||
transformJsonPathToDir("f/my_flow.flow.json", "flow"),
|
||||
"f/my_flow__flow",
|
||||
"Should transform dotted API format to non-dotted local format"
|
||||
);
|
||||
assertEquals(
|
||||
transformJsonPathToDir("f/my_app.app.json", "app"),
|
||||
"f/my_app__app",
|
||||
"Should transform app to non-dotted format"
|
||||
);
|
||||
assertEquals(
|
||||
transformJsonPathToDir("f/my_raw.raw_app.json", "raw_app"),
|
||||
"f/my_raw__raw_app",
|
||||
"Should transform raw_app to non-dotted format"
|
||||
);
|
||||
|
||||
// Non-matching paths should be returned unchanged
|
||||
assertEquals(
|
||||
transformJsonPathToDir("f/my_script.ts", "flow"),
|
||||
"f/my_script.ts",
|
||||
"Should return non-matching path unchanged"
|
||||
);
|
||||
} finally {
|
||||
// Restore original value
|
||||
setNonDottedPaths(wasNonDotted);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("getFolderSuffix returns correct suffix based on nonDottedPaths setting", () => {
|
||||
// Store original value
|
||||
const wasNonDotted = getNonDottedPaths();
|
||||
|
||||
try {
|
||||
setNonDottedPaths(false);
|
||||
assertEquals(getFolderSuffix("flow"), ".flow");
|
||||
assertEquals(getFolderSuffix("app"), ".app");
|
||||
assertEquals(getFolderSuffix("raw_app"), ".raw_app");
|
||||
|
||||
setNonDottedPaths(true);
|
||||
assertEquals(getFolderSuffix("flow"), "__flow");
|
||||
assertEquals(getFolderSuffix("app"), "__app");
|
||||
assertEquals(getFolderSuffix("raw_app"), "__raw_app");
|
||||
} finally {
|
||||
// Restore original value
|
||||
setNonDottedPaths(wasNonDotted);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("Script fixture creates valid structure", () => {
|
||||
const pythonScript = createScriptFixture("test_script", "python3");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user