Files
windmill/backend/windmill-worker/loader.bun.js
T
526da60da9 feat: let raw app backend runnables import each other (#11777)
* fix: iterate workspace picker nav items without spreading a NodeList

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: let raw app backend runnables import each other

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: preview only imported sibling runnables and reserve the main id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: resolve a module's workspace imports from the entrypoint's folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: scope run-mode app scripts to their app and key bundles on module imports

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: clone app_version_lite into forks with remapped app_script ids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: update the run-mode raw_code comment for app-scoped ids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: run module-bearing app scripts only from the deployed version

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: run deployed inline code only through the deployed version's rows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: in-workspace app viewers run through the deployed rows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: let the app embed token read the app's lite value

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: pass sibling modules to the agent-worker Python preview resolver

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: list the lite route in the app embed scope comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: update ee-repo-ref to 455f98540fb042d4f624dcec681dab88b2c8df22

This commit updates the EE repository reference after PR #883 was merged in windmill-ee-private.

Previous ee-repo-ref: 927d3592a6b2127ffe16fb14eb47f5bd66afc026

New ee-repo-ref: 455f98540fb042d4f624dcec681dab88b2c8df22

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-10-15 13:36:34 +02:00

178 lines
6.7 KiB
JavaScript

// Injected by backend: maps normalized paths to temp storage hashes (or null)
const TEMP_SCRIPT_REFS = TEMP_SCRIPT_REFS_PLACEHOLDER;
const p = {
name: "windmill-relative-resolver",
async setup(build) {
const { writeFileSync, readFileSync, mkdirSync } = await import("fs");
const { dirname, resolve, join } = 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
// On Windows, normalize path to POSIX format to match args.path from Bun's resolver
const cdirPosix = cdir.replace(/\\/g, "/").replace(/^[a-zA-Z]:/, "");
const filterResolve = new RegExp(
`^(?!\\.\/main\\.ts)(?!\\.\/_wm_)(?!${cdir}\/main\\.ts)(?!${cdir}\/_wm_)(?!${cdirPosix}\/main\\.ts)(?!${cdirPosix}\/_wm_)(?!(?:/private)?${cdirNoPrivate}\/wrapper\\.mjs).*\\.ts$`
);
let cdirNodeModules = `${cdir}/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")
) {
// Whole quoted specifiers only: `./util` is also a substring of `../util_ws`.
for (const q of ['"', "'"]) {
code = code.replaceAll(q + imp.path + q, q + imp.path + ".ts" + q);
}
}
}
}
return {
contents: code,
};
}
build.onLoad({ filter: filterLoad }, async (args) => {
const code = readFileSync(args.path, "utf8");
return replaceRelativeImports(code);
});
// Module files (written by write_module_files next to main.ts) need the same `.ts`
// rewrite: an extensionless relative import misses the resolve filter below, and the
// lockfile builder then treats it as an external package named ".".
const filterLoadModule = new RegExp(
`^${cdir}\/(?!node_modules\/)(?!_wm_)(?!main\\.ts$).*\\.ts$`
);
build.onLoad({ filter: filterLoadModule }, async (args) => {
const code = readFileSync(args.path, "utf8");
return replaceRelativeImports(code);
});
// A stalled fetch would otherwise hold the whole build for bun's own 5-minute
// default, with nothing naming the script it was waiting on.
const RELATIVE_IMPORT_FETCH_TIMEOUT_MS = 120000;
function relativeImportFetchError(url, e) {
const reason =
e?.name === "TimeoutError"
? `no response within ${RELATIVE_IMPORT_FETCH_TIMEOUT_MS / 1000}s`
: String(e?.message ?? e);
return new Error(`Failed to fetch relative import at ${url}: ${reason}`);
}
async function fetchRelativeImport(url) {
let req;
try {
req = await fetch(url, {
method: "GET",
headers: {
Authorization: "Bearer " + token,
},
signal: AbortSignal.timeout(RELATIVE_IMPORT_FETCH_TIMEOUT_MS),
});
} catch (e) {
throw relativeImportFetchError(url, e);
}
if (!req.ok) {
throw new Error(
`Failed to find relative import at ${url} (status ${req.status} ${req.statusText})`
);
}
try {
return await req.text();
} catch (e) {
throw relativeImportFetchError(url, e);
}
}
build.onLoad({ filter: /.*\.url$/ }, async (args) => {
const url = readFileSync(args.path, "utf8");
const contents = await fetchRelativeImport(url);
return {
contents: replaceRelativeImports(contents).contents,
loader: "tsx",
};
});
build.onResolve({ filter: filterResolve }, (args) => {
if (args.importer?.startsWith(cdirNodeModules)) {
return undefined;
}
// Check if the import resolves to a local module file (written by write_module_files).
// Only check relative paths — absolute/bare specifiers should fall through to the
// remote resolver, matching the Windows loader pattern.
// Only for local importers (main.ts or a module, both `.ts` in the job dir): a fetched
// workspace script (`.url`) resolves within its own workspace folder, where a module
// file of the same name would shadow its real neighbour.
const importer = args.importer ? resolve(args.importer) : "";
if (
args.path.startsWith(".") &&
importer.endsWith(".ts") &&
importer.startsWith(cdir + "/")
) {
// From the importing module's own folder: a module may sit in a subfolder (`lib/a.ts`).
const localPath = resolve(dirname(importer), args.path);
try {
readFileSync(localPath);
return { path: localPath };
} catch {}
}
const importerRel = args.importer.replace(cdir + "/", "");
// A module file stands in the job dir for a script in the entrypoint's folder, so its
// own relative imports resolve from that folder's workspace path, not from the job dir.
const file_path =
args.importer == "./main.ts" || args.importer == resolve("./main.ts")
? current_path
: importerRel.endsWith(".ts")
? join(dirname(current_path), importerRel.replace(/\.ts$/, ""))
: importerRel;
const isRelative = !args.path.startsWith("/");
const endExt = args.path.endsWith(".ts") ? "" : ".ts";
const pathNoExt = args.path.replace(/\.ts$/, "");
// Lookup temp script hash
const normalized = (isRelative ? join(dirname(file_path), pathNoExt) : pathNoExt.slice(1)).replace(/\\/g, "/");
const hash = TEMP_SCRIPT_REFS?.[normalized];
// Lock generation substitutes `raw`: the dependency scan reads versions from the
// `pkg@version` specifiers in imported scripts, which `raw_unpinned` strips.
const url = (isRelative
? `${base_internal_url}/api/w/${w_id}/scripts/RAW_GET_ENDPOINT/p/${file_path}/../${args.path}${endExt}`
: `${base_internal_url}/api/w/${w_id}/scripts/RAW_GET_ENDPOINT/p/${args.path}${endExt}`
) + (hash ? `?temp_script_hash=${hash}` : "");
const file = isRelative
? resolve("./" + file_path + "/../" + args.path + ".url")
: resolve("./" + args.path + ".url");
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, url);
return {
path: file,
};
});
},
};