nonDottedPaths fixes

This commit is contained in:
Ruben Fiszel
2025-12-26 23:44:14 +00:00
parent e9a960dca7
commit 642d44bbc1
4 changed files with 206 additions and 20 deletions
+2 -1
View File
@@ -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" ||
+82 -18
View File
@@ -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
+1 -1
View File
@@ -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";
+121
View File
@@ -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");