// Injected by backend: maps normalized paths to temp storage hashes (or null) const TEMP_SCRIPT_REFS = TEMP_SCRIPT_REFS_PLACEHOLDER; // Windows-specific bun loader that uses a virtual "windmill-url" namespace instead // of writing .url files to disk. This avoids Windows path issues (backslashes in // resolve(), 8.3 short filenames, drive letter prefixes). The virtual namespace // approach is likely better on all fronts but we keep the original .url-file loader // on Linux to avoid breaking back-compat. const p = { name: "windmill-relative-resolver", async setup(build) { const { readFileSync } = await import("fs"); const { resolve } = await import("node:path"); const base_internal_url = "BASE_INTERNAL_URL".replace( "localhost", "127.0.0.1" ); const w_id = "W_ID"; const current_path = "CURRENT_PATH"; const token = "TOKEN"; const cdir = resolve("./"); const cdirNoPrivate = cdir.replace(/^\/private/, ""); // for macos // Normalize path to forward slashes to match Bun's resolver output on Windows const cdirFwd = cdir.replace(/\\/g, "/"); const cdirPosix = cdirFwd.replace(/^[a-zA-Z]:/, ""); const filterResolve = new RegExp( `^(?!\\.\/main\\.ts)(?!${cdirFwd}\/main\\.ts)(?!${cdirPosix}\/main\\.ts)(?!(?:/private)?${cdirNoPrivate}\/wrapper\\.mjs).*\\.ts$` ); let cdirNodeModules = `${cdirFwd}/node_modules/`; const filterLoad = new RegExp(`^${cdir}\/main\\.ts$`); const transpiler = new Bun.Transpiler({ loader: "ts", }); function replaceRelativeImports(code) { const imports = transpiler.scanImports(code); for (const imp of imports) { if (imp.kind == "import-statement") { if ( (imp.path.startsWith(".") || imp.path.startsWith("/u/") || imp.path.startsWith("/f/")) && !imp.path.endsWith(".ts") ) { code = code.replaceAll(imp.path, imp.path + ".ts"); } } } return { contents: code, }; } function normalizePath(rawPath) { return rawPath.split("/").reduce((acc, seg) => { if (seg === "..") acc.pop(); else if (seg !== "." && seg !== "") acc.push(seg); return acc; }, []).join("/"); } // Resolve a windmill script import path relative to an importer path. // Bun on Windows may prefix args with "windmill-url:" or strip leading "/". function resolveWindmillImport(importerPath, importPath) { const path = importPath.replace(/^windmill-url:/, "").replace(/^\//, ""); const isAbsolute = path.startsWith("f/") || path.startsWith("u/"); const endExt = path.endsWith(".ts") ? "" : ".ts"; const rawScriptPath = isAbsolute ? `${path}${endExt}` : `${importerPath}/../${path}${endExt}`; const normalized = normalizePath(rawScriptPath); // Look up temp script hash (keys are extensionless paths) const lookupPath = normalized.replace(/\.ts$/, ""); const hash = TEMP_SCRIPT_REFS?.[lookupPath]; // Encode hash in the path so onLoad can extract it and append to fetch URL const resolvedPath = hash ? `${normalized}?temp_script_hash=${hash}` : normalized; return { path: resolvedPath, namespace: "windmill-url" }; } build.onLoad({ filter: filterLoad }, async (args) => { const code = readFileSync(args.path, "utf8"); return replaceRelativeImports(code); }); // Load windmill scripts by fetching from the API build.onLoad({ filter: /.*/, namespace: "windmill-url" }, async (args) => { // Extract temp_script_hash if embedded in the path by resolveWindmillImport const [scriptPath, queryString] = args.path.replace(/^windmill-url:/, "").split("?"); const hashParam = queryString?.startsWith("temp_script_hash=") ? queryString.replace("temp_script_hash=", "") : undefined; const url = `${base_internal_url}/api/w/${w_id}/scripts/RAW_GET_ENDPOINT/p/${scriptPath}` + (hashParam ? `?temp_script_hash=${hashParam}` : ""); const req = await fetch(url, { method: "GET", headers: { Authorization: "Bearer " + token, }, }); if (!req.ok) { throw new Error( `Failed to find relative import at ${url} (status ${req.status})` ); } const contents = await req.text(); return { contents: replaceRelativeImports(contents).contents, loader: "tsx", }; }); // Resolve windmill script imports from the file namespace (e.g. from main.ts) build.onResolve({ filter: filterResolve }, (args) => { const importerFwd = args.importer?.replace(/\\/g, "/") ?? ""; if (importerFwd.startsWith(cdirNodeModules)) { return undefined; } // Check if the import resolves to a local module file (written by write_module_files) if (args.path.startsWith(".")) { const cwdPath = resolve(cdir, args.path); try { readFileSync(cwdPath); return { path: cwdPath }; } catch {} } const isMainTs = args.importer == "./main.ts" || importerFwd.endsWith("/main.ts"); const file_path = isMainTs ? current_path : importerFwd.replace(cdirFwd + "/", ""); return resolveWindmillImport(file_path, args.path); }); // Resolve nested imports from within windmill-url modules build.onResolve({ filter: /\.ts$/, namespace: "windmill-url" }, (args) => { // Strip any query string from the importer path before resolving const importer = args.importer.replace(/^windmill-url:/, "").split("?")[0]; return resolveWindmillImport(importer, args.path); }); }, };