mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-07-16 00:44:59 +00:00
add ignoreScripts option to createRuntime (#12)
This commit is contained in:
parent
5f48c1c2d2
commit
0839c99a9f
4 changed files with 205 additions and 0 deletions
|
|
@ -69,6 +69,17 @@ function useExecutedMission(
|
||||||
fileSystem,
|
fileSystem,
|
||||||
cache: scriptCache,
|
cache: scriptCache,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
|
ignoreScripts: [
|
||||||
|
"scripts/admin.cs",
|
||||||
|
"scripts/ai.cs",
|
||||||
|
"scripts/aiCTF.cs",
|
||||||
|
"scripts/aiHunters.cs",
|
||||||
|
"scripts/deathMessages.cs",
|
||||||
|
"scripts/graphBuild.cs",
|
||||||
|
"scripts/navGraph.cs",
|
||||||
|
"scripts/serverTasks.cs",
|
||||||
|
"scripts/spdialog.cs",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
onMissionLoadDone: () => {
|
onMissionLoadDone: () => {
|
||||||
const missionGroup = runtime.getObjectByName("MissionGroup");
|
const missionGroup = runtime.getObjectByName("MissionGroup");
|
||||||
|
|
|
||||||
|
|
@ -2538,6 +2538,182 @@ describe("TorqueScript Runtime", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("ignoreScripts option", () => {
|
||||||
|
function createLoader(
|
||||||
|
files: Record<string, string>,
|
||||||
|
): (path: string) => Promise<string | null> {
|
||||||
|
return async (path: string) =>
|
||||||
|
files[path.replace(/\\/g, "/").toLowerCase()] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("skips scripts matching glob patterns", async () => {
|
||||||
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: createLoader({
|
||||||
|
"scripts/main.cs": 'exec("scripts/ai/brain.cs"); $Main = 1;',
|
||||||
|
"scripts/ai/brain.cs": "$AI = 1;",
|
||||||
|
}),
|
||||||
|
ignoreScripts: ["**/ai/**"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
|
||||||
|
expect(runtime.$g.get("Main")).toBe(1);
|
||||||
|
expect(runtime.$g.get("AI")).toBe(""); // Not loaded
|
||||||
|
expect(warnSpy).toHaveBeenCalledWith(
|
||||||
|
"Ignoring script: scripts/ai/brain.cs",
|
||||||
|
);
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is case insensitive", async () => {
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: createLoader({
|
||||||
|
"scripts/main.cs": 'exec("Scripts/AI/Brain.cs"); $Main = 1;',
|
||||||
|
"scripts/ai/brain.cs": "$AI = 1;",
|
||||||
|
}),
|
||||||
|
ignoreScripts: ["**/ai/**"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
|
||||||
|
expect(runtime.$g.get("Main")).toBe(1);
|
||||||
|
expect(runtime.$g.get("AI")).toBe(""); // Not loaded due to case-insensitive match
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports multiple patterns", async () => {
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: createLoader({
|
||||||
|
"scripts/main.cs":
|
||||||
|
'exec("scripts/ai/brain.cs"); exec("scripts/vehicles/tank.cs"); exec("scripts/utils.cs"); $Main = 1;',
|
||||||
|
"scripts/ai/brain.cs": "$AI = 1;",
|
||||||
|
"scripts/vehicles/tank.cs": "$Vehicle = 1;",
|
||||||
|
"scripts/utils.cs": "$Utils = 1;",
|
||||||
|
}),
|
||||||
|
ignoreScripts: ["**/ai/**", "**/vehicles/**"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
|
||||||
|
expect(runtime.$g.get("Main")).toBe(1);
|
||||||
|
expect(runtime.$g.get("AI")).toBe(""); // Ignored
|
||||||
|
expect(runtime.$g.get("Vehicle")).toBe(""); // Ignored
|
||||||
|
expect(runtime.$g.get("Utils")).toBe(1); // Loaded (not in ignore list)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("marks ignored scripts as failed (not retried)", async () => {
|
||||||
|
let loadCount = 0;
|
||||||
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: async (path) => {
|
||||||
|
loadCount++;
|
||||||
|
if (path.toLowerCase() === "scripts/ignored.cs") {
|
||||||
|
return "$Ignored = 1;";
|
||||||
|
}
|
||||||
|
if (path.toLowerCase() === "scripts/main.cs") {
|
||||||
|
return 'exec("scripts/ignored.cs"); exec("scripts/ignored.cs"); $Main = 1;';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
ignoreScripts: ["**/ignored.cs"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
|
||||||
|
// Should only warn once (second exec sees it's already in failedScripts)
|
||||||
|
expect(
|
||||||
|
warnSpy.mock.calls.filter(
|
||||||
|
(c) => c[0] === "Ignoring script: scripts/ignored.cs",
|
||||||
|
).length,
|
||||||
|
).toBe(1);
|
||||||
|
// Loader should only be called for main.cs, not for ignored.cs
|
||||||
|
expect(loadCount).toBe(1);
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches exact filenames with glob", async () => {
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: createLoader({
|
||||||
|
"scripts/main.cs":
|
||||||
|
'exec("scripts/skip-me.cs"); exec("scripts/keep-me.cs"); $Main = 1;',
|
||||||
|
"scripts/skip-me.cs": "$Skip = 1;",
|
||||||
|
"scripts/keep-me.cs": "$Keep = 1;",
|
||||||
|
}),
|
||||||
|
ignoreScripts: ["**/skip-me.cs"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
|
||||||
|
expect(runtime.$g.get("Main")).toBe(1);
|
||||||
|
expect(runtime.$g.get("Skip")).toBe(""); // Ignored
|
||||||
|
expect(runtime.$g.get("Keep")).toBe(1); // Loaded
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing when ignoreScripts is empty array", async () => {
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: createLoader({
|
||||||
|
"scripts/main.cs": 'exec("scripts/dep.cs"); $Main = 1;',
|
||||||
|
"scripts/dep.cs": "$Dep = 1;",
|
||||||
|
}),
|
||||||
|
ignoreScripts: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
|
||||||
|
expect(runtime.$g.get("Main")).toBe(1);
|
||||||
|
expect(runtime.$g.get("Dep")).toBe(1); // Loaded normally
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing when ignoreScripts is not provided", async () => {
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: createLoader({
|
||||||
|
"scripts/main.cs": 'exec("scripts/dep.cs"); $Main = 1;',
|
||||||
|
"scripts/dep.cs": "$Dep = 1;",
|
||||||
|
}),
|
||||||
|
// No ignoreScripts option
|
||||||
|
});
|
||||||
|
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
|
||||||
|
expect(runtime.$g.get("Main")).toBe(1);
|
||||||
|
expect(runtime.$g.get("Dep")).toBe(1); // Loaded normally
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exec() returns false for ignored scripts", async () => {
|
||||||
|
const runtime = createRuntime({
|
||||||
|
loadScript: createLoader({
|
||||||
|
"scripts/main.cs": '$Result = exec("scripts/ignored.cs"); $Main = 1;',
|
||||||
|
"scripts/ignored.cs": "$Ignored = 1;",
|
||||||
|
}),
|
||||||
|
ignoreScripts: ["**/ignored.cs"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
|
const script = await runtime.loadFromPath("scripts/main.cs");
|
||||||
|
script.execute();
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
|
||||||
|
expect(runtime.$g.get("Main")).toBe(1);
|
||||||
|
// exec() should return false when the script is ignored (same as failed)
|
||||||
|
expect(runtime.$g.get("Result")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("nsRef with execution context", () => {
|
describe("nsRef with execution context", () => {
|
||||||
it("tracks execution context for Parent:: calls via nsRef", () => {
|
it("tracks execution context for Parent:: calls via nsRef", () => {
|
||||||
const runtime = createRuntime();
|
const runtime = createRuntime();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import picomatch from "picomatch";
|
||||||
import { generate } from "./codegen";
|
import { generate } from "./codegen";
|
||||||
import { parse, type Program } from "./index";
|
import { parse, type Program } from "./index";
|
||||||
import { createBuiltins as defaultCreateBuiltins } from "./builtins";
|
import { createBuiltins as defaultCreateBuiltins } from "./builtins";
|
||||||
|
|
@ -94,6 +95,11 @@ export function createRuntime(
|
||||||
|
|
||||||
const executedScripts = new Set<string>();
|
const executedScripts = new Set<string>();
|
||||||
const failedScripts = new Set<string>();
|
const failedScripts = new Set<string>();
|
||||||
|
// Create matcher for ignored scripts (case insensitive)
|
||||||
|
const isIgnoredScript =
|
||||||
|
options.ignoreScripts && options.ignoreScripts.length > 0
|
||||||
|
? picomatch(options.ignoreScripts, { nocase: true })
|
||||||
|
: null;
|
||||||
// Use cache if provided, otherwise create new maps
|
// Use cache if provided, otherwise create new maps
|
||||||
const cache = options.cache ?? createScriptCache();
|
const cache = options.cache ?? createScriptCache();
|
||||||
const scripts = cache.scripts;
|
const scripts = cache.scripts;
|
||||||
|
|
@ -1093,6 +1099,13 @@ export function createRuntime(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip if script matches ignore patterns
|
||||||
|
if (isIgnoredScript && isIgnoredScript(normalized)) {
|
||||||
|
console.warn(`Ignoring script: ${ref}`);
|
||||||
|
state.failedScripts.add(normalized);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If this script is an ancestor in our load chain, it's a cycle - skip
|
// If this script is an ancestor in our load chain, it's a cycle - skip
|
||||||
// (awaiting would cause deadlock)
|
// (awaiting would cause deadlock)
|
||||||
if (ancestors.has(normalized)) {
|
if (ancestors.has(normalized)) {
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,11 @@ export interface TorqueRuntimeOptions {
|
||||||
* are exec()'d dynamically and can't be statically analyzed.
|
* are exec()'d dynamically and can't be statically analyzed.
|
||||||
*/
|
*/
|
||||||
preloadScripts?: string[];
|
preloadScripts?: string[];
|
||||||
|
/**
|
||||||
|
* Glob patterns for scripts to ignore during dependency resolution.
|
||||||
|
* Matched scripts will be skipped and logged as warnings.
|
||||||
|
*/
|
||||||
|
ignoreScripts?: string[];
|
||||||
/**
|
/**
|
||||||
* Cache for parsed scripts and generated code. If provided, the runtime
|
* Cache for parsed scripts and generated code. If provided, the runtime
|
||||||
* will use this cache to store and retrieve parsed ASTs, avoiding redundant
|
* will use this cache to store and retrieve parsed ASTs, avoiding redundant
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue