gdharness 0.13.11 → 0.13.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +154 -30
- package/build/godot/operations/godot_operations.gd +0 -2
- package/build/godot/operations/resource_files.gd +0 -91
- package/build/index.js +126 -12
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -7281,6 +7281,32 @@ var init_game_log = __esm(() => {
|
|
|
7281
7281
|
STREAMS = ["stdout", "stderr", "transcript"];
|
|
7282
7282
|
});
|
|
7283
7283
|
|
|
7284
|
+
// src/scratch.ts
|
|
7285
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
7286
|
+
function discardWith(remove, paths) {
|
|
7287
|
+
const left = [];
|
|
7288
|
+
for (const path of paths) {
|
|
7289
|
+
if (!path) {
|
|
7290
|
+
continue;
|
|
7291
|
+
}
|
|
7292
|
+
try {
|
|
7293
|
+
remove(path);
|
|
7294
|
+
} catch (error) {
|
|
7295
|
+
left.push({ path, reason: error instanceof Error ? error.message : String(error) });
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
return left;
|
|
7299
|
+
}
|
|
7300
|
+
function discard(...paths) {
|
|
7301
|
+
return discardWith((path) => {
|
|
7302
|
+
rmSync2(path, SWEPT);
|
|
7303
|
+
}, paths);
|
|
7304
|
+
}
|
|
7305
|
+
var SWEPT;
|
|
7306
|
+
var init_scratch = __esm(() => {
|
|
7307
|
+
SWEPT = { recursive: true, force: true, maxRetries: 20, retryDelay: 250 };
|
|
7308
|
+
});
|
|
7309
|
+
|
|
7284
7310
|
// src/update-check.ts
|
|
7285
7311
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7286
7312
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
@@ -7554,7 +7580,7 @@ var init_class_cache = () => {};
|
|
|
7554
7580
|
|
|
7555
7581
|
// src/headless.ts
|
|
7556
7582
|
import { execFile as execFile3 } from "node:child_process";
|
|
7557
|
-
import { mkdtempSync as mkdtempSync2,
|
|
7583
|
+
import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7558
7584
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
7559
7585
|
import { join as join7 } from "node:path";
|
|
7560
7586
|
import { promisify as promisify3 } from "node:util";
|
|
@@ -7595,6 +7621,36 @@ function reason2(stdout, stderr) {
|
|
|
7595
7621
|
}
|
|
7596
7622
|
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
7597
7623
|
}
|
|
7624
|
+
async function runImport(godotPath, projectPath) {
|
|
7625
|
+
const logDir = mkdtempSync2(join7(tmpdir3(), "gdharness-import-"));
|
|
7626
|
+
try {
|
|
7627
|
+
const { stderr } = await run3(godotPath, [
|
|
7628
|
+
"--headless",
|
|
7629
|
+
"--log-file",
|
|
7630
|
+
join7(logDir, "engine.log"),
|
|
7631
|
+
"--path",
|
|
7632
|
+
projectPath,
|
|
7633
|
+
"--import"
|
|
7634
|
+
]);
|
|
7635
|
+
return { ok: true, messages: problems2(stderr) };
|
|
7636
|
+
} catch (error) {
|
|
7637
|
+
if (error instanceof Error && "stdout" in error && "stderr" in error) {
|
|
7638
|
+
const failed = error;
|
|
7639
|
+
return {
|
|
7640
|
+
ok: false,
|
|
7641
|
+
message: `the import pass failed (exit ${failed.code ?? "unknown"}): ${reason2(failed.stdout, failed.stderr)}`,
|
|
7642
|
+
messages: problems2(failed.stderr)
|
|
7643
|
+
};
|
|
7644
|
+
}
|
|
7645
|
+
return {
|
|
7646
|
+
ok: false,
|
|
7647
|
+
message: `the import pass could not be run: ${error instanceof Error ? error.message : String(error)}`,
|
|
7648
|
+
messages: []
|
|
7649
|
+
};
|
|
7650
|
+
} finally {
|
|
7651
|
+
discard(logDir);
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7598
7654
|
async function runOperation2(engine, operation, params, projectPath) {
|
|
7599
7655
|
const paramsDir = mkdtempSync2(join7(tmpdir3(), "gdharness-params-"));
|
|
7600
7656
|
const paramsFile = join7(paramsDir, `${operation}.json`);
|
|
@@ -7630,7 +7686,7 @@ async function runOperation2(engine, operation, params, projectPath) {
|
|
|
7630
7686
|
messages: []
|
|
7631
7687
|
};
|
|
7632
7688
|
} finally {
|
|
7633
|
-
|
|
7689
|
+
discard(paramsDir);
|
|
7634
7690
|
}
|
|
7635
7691
|
const payload = lastJsonObject2(stdout);
|
|
7636
7692
|
if (payload === null) {
|
|
@@ -7645,6 +7701,7 @@ async function runOperation2(engine, operation, params, projectPath) {
|
|
|
7645
7701
|
var run3;
|
|
7646
7702
|
var init_headless = __esm(() => {
|
|
7647
7703
|
init_game_log();
|
|
7704
|
+
init_scratch();
|
|
7648
7705
|
run3 = promisify3(execFile3);
|
|
7649
7706
|
});
|
|
7650
7707
|
|
|
@@ -15448,7 +15505,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
15448
15505
|
requires: []
|
|
15449
15506
|
},
|
|
15450
15507
|
uid: { summary: "the UID of one file", requires: ["resourcePath"] },
|
|
15451
|
-
refresh_uids: {
|
|
15508
|
+
refresh_uids: {
|
|
15509
|
+
summary: "import the project so every script and shader has its .uid sidecar, and name under uidsCreated the ones this made and under stillWithoutUid the ones the engine would not import: it writes no scene and no script, so a project whose sidecars are all present is left untouched",
|
|
15510
|
+
requires: []
|
|
15511
|
+
},
|
|
15452
15512
|
refresh_classes: {
|
|
15453
15513
|
summary: 'rewrite .godot/global_script_class_cache.cfg from the class_name declarations on disk, and name under unseenByEditor any class the editor open on this project still cannot resolve: rewriting the file does not reach the list a running editor loaded, so "added: []" means the file was already right rather than that nothing is wrong',
|
|
15454
15514
|
requires: []
|
|
@@ -16196,7 +16256,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16196
16256
|
});
|
|
16197
16257
|
|
|
16198
16258
|
// src/headless-operations.ts
|
|
16199
|
-
var HEADLESS_OPERATIONS, EDITOR_READS;
|
|
16259
|
+
var HEADLESS_OPERATIONS, ENGINE_PASSES, EDITOR_READS;
|
|
16200
16260
|
var init_headless_operations = __esm(() => {
|
|
16201
16261
|
HEADLESS_OPERATIONS = dictionary({
|
|
16202
16262
|
project_settings: dictionary({
|
|
@@ -16218,7 +16278,6 @@ var init_headless_operations = __esm(() => {
|
|
|
16218
16278
|
set_options: "set_import_options",
|
|
16219
16279
|
reimport: "reimport_resource",
|
|
16220
16280
|
uid: "get_uid",
|
|
16221
|
-
refresh_uids: "resave_resources",
|
|
16222
16281
|
refresh_classes: "refresh_class_cache"
|
|
16223
16282
|
}),
|
|
16224
16283
|
project_export: dictionary({ list: "list_export_presets" }),
|
|
@@ -16230,6 +16289,9 @@ var init_headless_operations = __esm(() => {
|
|
|
16230
16289
|
inheritance: "inspect_inheritance"
|
|
16231
16290
|
})
|
|
16232
16291
|
});
|
|
16292
|
+
ENGINE_PASSES = dictionary({
|
|
16293
|
+
project_import: dictionary({ refresh_uids: "import" })
|
|
16294
|
+
});
|
|
16233
16295
|
EDITOR_READS = dictionary({
|
|
16234
16296
|
project_settings: dictionary({ get: "get_project_setting" })
|
|
16235
16297
|
});
|
|
@@ -30946,7 +31008,7 @@ var init_stdio2 = __esm(() => {
|
|
|
30946
31008
|
});
|
|
30947
31009
|
|
|
30948
31010
|
// src/bridge-announce.ts
|
|
30949
|
-
import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as
|
|
31011
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
30950
31012
|
import { dirname as dirname6, join as join11 } from "node:path";
|
|
30951
31013
|
function announcementPath(projectPath) {
|
|
30952
31014
|
return join11(projectPath, ".godot", "gdharness-bridge.json");
|
|
@@ -30994,7 +31056,7 @@ function withdrawBridge(path) {
|
|
|
30994
31056
|
return;
|
|
30995
31057
|
}
|
|
30996
31058
|
try {
|
|
30997
|
-
|
|
31059
|
+
rmSync5(path, { force: true });
|
|
30998
31060
|
} catch (error) {
|
|
30999
31061
|
console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
|
|
31000
31062
|
}
|
|
@@ -36022,6 +36084,33 @@ function projectStructure(projectPath) {
|
|
|
36022
36084
|
visit(projectPath);
|
|
36023
36085
|
return structure;
|
|
36024
36086
|
}
|
|
36087
|
+
function scriptsWithoutUid(projectPath) {
|
|
36088
|
+
const found = [];
|
|
36089
|
+
const visit = (directory, prefix) => {
|
|
36090
|
+
if (steppedOver(directory)) {
|
|
36091
|
+
return;
|
|
36092
|
+
}
|
|
36093
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
36094
|
+
if (skipped(entry.name)) {
|
|
36095
|
+
continue;
|
|
36096
|
+
}
|
|
36097
|
+
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
36098
|
+
if (entry.isDirectory()) {
|
|
36099
|
+
visit(join13(directory, entry.name), spelled);
|
|
36100
|
+
continue;
|
|
36101
|
+
}
|
|
36102
|
+
if (!entry.isFile()) {
|
|
36103
|
+
continue;
|
|
36104
|
+
}
|
|
36105
|
+
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
36106
|
+
if (CARRIES_A_UID.has(extension) && !existsSync11(join13(directory, `${entry.name}.uid`))) {
|
|
36107
|
+
found.push(spelled);
|
|
36108
|
+
}
|
|
36109
|
+
}
|
|
36110
|
+
};
|
|
36111
|
+
visit(projectPath, "");
|
|
36112
|
+
return found.sort();
|
|
36113
|
+
}
|
|
36025
36114
|
function searchProject(projectPath, options) {
|
|
36026
36115
|
const extensions = new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
|
|
36027
36116
|
const result = {
|
|
@@ -36081,10 +36170,11 @@ function searchProject(projectPath, options) {
|
|
|
36081
36170
|
visit(projectPath);
|
|
36082
36171
|
return result;
|
|
36083
36172
|
}
|
|
36084
|
-
var SKIPPED, ASSET_EXTENSIONS;
|
|
36173
|
+
var SKIPPED, ASSET_EXTENSIONS, CARRIES_A_UID;
|
|
36085
36174
|
var init_project_scan = __esm(() => {
|
|
36086
36175
|
SKIPPED = new Set(["node_modules"]);
|
|
36087
36176
|
ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
|
|
36177
|
+
CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
36088
36178
|
});
|
|
36089
36179
|
|
|
36090
36180
|
// src/runtime-client.ts
|
|
@@ -36330,7 +36420,7 @@ var init_runtime_client = __esm(() => {
|
|
|
36330
36420
|
|
|
36331
36421
|
// src/run-record.ts
|
|
36332
36422
|
import { execFileSync } from "node:child_process";
|
|
36333
|
-
import { mkdirSync as mkdirSync6, openSync, readdirSync as readdirSync6, readFileSync as readFileSync13, rmSync as
|
|
36423
|
+
import { mkdirSync as mkdirSync6, openSync, readdirSync as readdirSync6, readFileSync as readFileSync13, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
36334
36424
|
import { basename as basename2, join as join15 } from "node:path";
|
|
36335
36425
|
function runsDirectory() {
|
|
36336
36426
|
return join15(runtimeDirectory(), "runs");
|
|
@@ -36540,7 +36630,7 @@ function clearRunRecord(ours = () => true) {
|
|
|
36540
36630
|
continue;
|
|
36541
36631
|
}
|
|
36542
36632
|
try {
|
|
36543
|
-
|
|
36633
|
+
rmSync6(path);
|
|
36544
36634
|
} catch {}
|
|
36545
36635
|
}
|
|
36546
36636
|
}
|
|
@@ -36569,7 +36659,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
36569
36659
|
continue;
|
|
36570
36660
|
}
|
|
36571
36661
|
try {
|
|
36572
|
-
|
|
36662
|
+
rmSync6(path);
|
|
36573
36663
|
} catch {}
|
|
36574
36664
|
}
|
|
36575
36665
|
}
|
|
@@ -36578,7 +36668,7 @@ var init_run_record = __esm(() => {
|
|
|
36578
36668
|
});
|
|
36579
36669
|
|
|
36580
36670
|
// src/setup.ts
|
|
36581
|
-
import { cpSync as cpSync2, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync14, rmSync as
|
|
36671
|
+
import { cpSync as cpSync2, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync14, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
36582
36672
|
import { dirname as dirname8, join as join16 } from "node:path";
|
|
36583
36673
|
function installedAddonVersion(projectPath) {
|
|
36584
36674
|
const marker = join16(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
|
|
@@ -36614,8 +36704,7 @@ import {
|
|
|
36614
36704
|
openSync as openSync2,
|
|
36615
36705
|
readdirSync as readdirSync7,
|
|
36616
36706
|
readFileSync as readFileSync15,
|
|
36617
|
-
readSync
|
|
36618
|
-
rmSync as rmSync9
|
|
36707
|
+
readSync
|
|
36619
36708
|
} from "node:fs";
|
|
36620
36709
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
36621
36710
|
import { basename as basename3, dirname as dirname9, join as join17, normalize as normalize3 } from "node:path";
|
|
@@ -37192,6 +37281,9 @@ class GodotServer {
|
|
|
37192
37281
|
return { ok: true, op };
|
|
37193
37282
|
}
|
|
37194
37283
|
async dispatch(tool, op, args) {
|
|
37284
|
+
if (ENGINE_PASSES[tool]?.[op] !== undefined) {
|
|
37285
|
+
return await this.handleRefreshUids(args);
|
|
37286
|
+
}
|
|
37195
37287
|
const headless = HEADLESS_OPERATIONS[tool]?.[op];
|
|
37196
37288
|
if (headless !== undefined) {
|
|
37197
37289
|
const { from: asked, ...answerable } = args;
|
|
@@ -37562,6 +37654,33 @@ class GodotServer {
|
|
|
37562
37654
|
const { op: _op, projectPath: _projectPath, ...params } = contained.value;
|
|
37563
37655
|
return this.answer(await this.operation(operation, params, project.value.path));
|
|
37564
37656
|
}
|
|
37657
|
+
async handleRefreshUids(args) {
|
|
37658
|
+
const project = this.project(args);
|
|
37659
|
+
if (!project.ok) {
|
|
37660
|
+
return project.response;
|
|
37661
|
+
}
|
|
37662
|
+
const engine = await this.engine();
|
|
37663
|
+
if (!engine.ok) {
|
|
37664
|
+
return this.createErrorResponse("No Godot executable found.");
|
|
37665
|
+
}
|
|
37666
|
+
const projectPath = project.value.path;
|
|
37667
|
+
const before = scriptsWithoutUid(projectPath);
|
|
37668
|
+
const imported = await runImport(engine.value, projectPath);
|
|
37669
|
+
if (!imported.ok) {
|
|
37670
|
+
return this.answer(imported);
|
|
37671
|
+
}
|
|
37672
|
+
const after = scriptsWithoutUid(projectPath);
|
|
37673
|
+
const given = before.filter((script) => !after.includes(script));
|
|
37674
|
+
return this.answer({
|
|
37675
|
+
ok: true,
|
|
37676
|
+
messages: imported.messages,
|
|
37677
|
+
payload: {
|
|
37678
|
+
uidsCreated: given,
|
|
37679
|
+
stillWithoutUid: after,
|
|
37680
|
+
note: after.length > 0 ? `${after.length} still have no .uid, which is the engine declining to import them rather than this op skipping them. No scene was written.` : "No scene was written: this reads the project and imports it, and does not resave anything."
|
|
37681
|
+
}
|
|
37682
|
+
});
|
|
37683
|
+
}
|
|
37565
37684
|
async operation(operation, params, projectPath) {
|
|
37566
37685
|
const engine = await this.engine();
|
|
37567
37686
|
if (!engine.ok) {
|
|
@@ -37692,7 +37811,7 @@ class GodotServer {
|
|
|
37692
37811
|
log.append("stderr", failed.stderr);
|
|
37693
37812
|
exitCode = typeof failed.code === "number" ? failed.code : -1;
|
|
37694
37813
|
} finally {
|
|
37695
|
-
|
|
37814
|
+
discard(exportLogs);
|
|
37696
37815
|
}
|
|
37697
37816
|
log.finish();
|
|
37698
37817
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
@@ -37790,8 +37909,7 @@ class GodotServer {
|
|
|
37790
37909
|
} catch (error) {
|
|
37791
37910
|
reportProblem = errorMessage(error);
|
|
37792
37911
|
} finally {
|
|
37793
|
-
|
|
37794
|
-
rmSync9(userData, { recursive: true, force: true });
|
|
37912
|
+
discard(reportsDir, userData);
|
|
37795
37913
|
}
|
|
37796
37914
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
37797
37915
|
const verdicts = {
|
|
@@ -38870,9 +38988,7 @@ class GodotServer {
|
|
|
38870
38988
|
]
|
|
38871
38989
|
};
|
|
38872
38990
|
} finally {
|
|
38873
|
-
|
|
38874
|
-
rmSync9(screenshotDir, { recursive: true, force: true });
|
|
38875
|
-
}
|
|
38991
|
+
discard(screenshotDir);
|
|
38876
38992
|
}
|
|
38877
38993
|
}
|
|
38878
38994
|
async handleFindRuntimeNodes(args) {
|
|
@@ -38959,6 +39075,7 @@ var init_server3 = __esm(() => {
|
|
|
38959
39075
|
init_resources();
|
|
38960
39076
|
init_run_record();
|
|
38961
39077
|
init_runtime_client();
|
|
39078
|
+
init_scratch();
|
|
38962
39079
|
init_server_version();
|
|
38963
39080
|
init_setup();
|
|
38964
39081
|
init_tool_definitions();
|
|
@@ -39911,8 +40028,9 @@ function taken(harness, path, held, stripped) {
|
|
|
39911
40028
|
|
|
39912
40029
|
// src/headless.ts
|
|
39913
40030
|
init_game_log();
|
|
40031
|
+
init_scratch();
|
|
39914
40032
|
import { execFile as execFile2 } from "node:child_process";
|
|
39915
|
-
import { mkdtempSync,
|
|
40033
|
+
import { mkdtempSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
39916
40034
|
import { tmpdir } from "node:os";
|
|
39917
40035
|
import { join as join4 } from "node:path";
|
|
39918
40036
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -39989,7 +40107,7 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
39989
40107
|
messages: []
|
|
39990
40108
|
};
|
|
39991
40109
|
} finally {
|
|
39992
|
-
|
|
40110
|
+
discard(paramsDir);
|
|
39993
40111
|
}
|
|
39994
40112
|
const payload = lastJsonObject(stdout);
|
|
39995
40113
|
if (payload === null) {
|
|
@@ -40138,7 +40256,7 @@ init_headless();
|
|
|
40138
40256
|
init_resources();
|
|
40139
40257
|
init_server_version();
|
|
40140
40258
|
import { spawnSync } from "node:child_process";
|
|
40141
|
-
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as
|
|
40259
|
+
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
40142
40260
|
import { dirname as dirname3, join as join8 } from "node:path";
|
|
40143
40261
|
import { fileURLToPath } from "node:url";
|
|
40144
40262
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
@@ -40163,7 +40281,7 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
|
40163
40281
|
}
|
|
40164
40282
|
const target = join8(projectPath, "addons", name);
|
|
40165
40283
|
const replaced = existsSync7(target);
|
|
40166
|
-
|
|
40284
|
+
rmSync3(target, { recursive: true, force: true });
|
|
40167
40285
|
mkdirSync3(dirname3(target), { recursive: true });
|
|
40168
40286
|
cpSync(source, target, { recursive: true });
|
|
40169
40287
|
writeFileSync5(join8(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
@@ -40177,7 +40295,7 @@ function removeAddons(projectPath) {
|
|
|
40177
40295
|
for (const name of ADDONS) {
|
|
40178
40296
|
const target = join8(projectPath, "addons", name);
|
|
40179
40297
|
if (existsSync7(target)) {
|
|
40180
|
-
|
|
40298
|
+
rmSync3(target, { recursive: true, force: true });
|
|
40181
40299
|
removed.push(target);
|
|
40182
40300
|
}
|
|
40183
40301
|
}
|
|
@@ -40306,7 +40424,7 @@ function inspectProject(projectPath) {
|
|
|
40306
40424
|
|
|
40307
40425
|
// src/skill.ts
|
|
40308
40426
|
init_tool_definitions();
|
|
40309
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync3, rmSync as
|
|
40427
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
40310
40428
|
import { basename, dirname as dirname4, join as join9 } from "node:path";
|
|
40311
40429
|
|
|
40312
40430
|
// src/tool-reference.ts
|
|
@@ -40322,7 +40440,13 @@ function namedType(declared) {
|
|
|
40322
40440
|
return "any";
|
|
40323
40441
|
}
|
|
40324
40442
|
function headlessSection() {
|
|
40325
|
-
const
|
|
40443
|
+
const everyOp = new Map;
|
|
40444
|
+
for (const table of [HEADLESS_OPERATIONS, ENGINE_PASSES]) {
|
|
40445
|
+
for (const [tool, ops] of Object.entries(table)) {
|
|
40446
|
+
everyOp.set(tool, [...everyOp.get(tool) ?? [], ...Object.keys(ops)]);
|
|
40447
|
+
}
|
|
40448
|
+
}
|
|
40449
|
+
const byTool = [...everyOp].map(([tool, ops]) => `\`${tool}\` (${ops.join(", ")})`).sort();
|
|
40326
40450
|
return [
|
|
40327
40451
|
"## What a gate can call",
|
|
40328
40452
|
"",
|
|
@@ -40551,7 +40675,7 @@ function removeSkill(directories, projectPath) {
|
|
|
40551
40675
|
const removed = [];
|
|
40552
40676
|
for (const directory of directories) {
|
|
40553
40677
|
if (existsSync8(directory)) {
|
|
40554
|
-
|
|
40678
|
+
rmSync4(directory, { recursive: true, force: true });
|
|
40555
40679
|
pruneEmpty(dirname4(directory), projectPath);
|
|
40556
40680
|
removed.push(directory);
|
|
40557
40681
|
}
|
|
@@ -40565,7 +40689,7 @@ function pruneEmpty(directory, projectPath) {
|
|
|
40565
40689
|
if (readdirSync3(path).length > 0) {
|
|
40566
40690
|
return;
|
|
40567
40691
|
}
|
|
40568
|
-
|
|
40692
|
+
rmSync4(path, { recursive: true, force: true });
|
|
40569
40693
|
path = dirname4(path);
|
|
40570
40694
|
}
|
|
40571
40695
|
}
|
|
@@ -40573,7 +40697,7 @@ function writeSkill(directories, version) {
|
|
|
40573
40697
|
const files = skillFiles(version);
|
|
40574
40698
|
return directories.map((directory) => {
|
|
40575
40699
|
const replaced = existsSync8(directory);
|
|
40576
|
-
|
|
40700
|
+
rmSync4(directory, { recursive: true, force: true });
|
|
40577
40701
|
for (const [name, contents] of files) {
|
|
40578
40702
|
const path = join9(directory, name);
|
|
40579
40703
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
@@ -108,8 +108,6 @@ func _run(operation: String, params: Dictionary) -> Dictionary:
|
|
|
108
108
|
# Resource files
|
|
109
109
|
"get_uid":
|
|
110
110
|
payload = ResourceFiles.new(_log).get_uid(params)
|
|
111
|
-
"resave_resources":
|
|
112
|
-
payload = ResourceFiles.new(_log).resave_resources(params)
|
|
113
111
|
"refresh_class_cache":
|
|
114
112
|
payload = ClassCache.new(_log).refresh_class_cache(params)
|
|
115
113
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const FileWalk = preload("file_walk.gd")
|
|
4
3
|
const Log = preload("logger.gd")
|
|
5
4
|
|
|
6
5
|
var _log: Log
|
|
7
|
-
var _files: FileWalk = FileWalk.new()
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
func _init(p_log: Log) -> void:
|
|
@@ -41,92 +39,3 @@ func get_uid(params: Dictionary) -> Dictionary:
|
|
|
41
39
|
return {
|
|
42
40
|
"file": file_path, "absolute_path": absolute_path, "uid": uid_content.strip_edges(), "exists": true
|
|
43
41
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Resave every scene, script and shader so that UID references are current.
|
|
47
|
-
func resave_resources(_params: Dictionary) -> Dictionary:
|
|
48
|
-
_log.info("Resaving all resources to update UID references...")
|
|
49
|
-
var project_path: String = "res://"
|
|
50
|
-
|
|
51
|
-
var scenes: Array[String] = _files.find_files(project_path, ".tscn")
|
|
52
|
-
_log.debug("Found " + str(scenes.size()) + " scenes")
|
|
53
|
-
|
|
54
|
-
# Resave each scene
|
|
55
|
-
var success_count: int = 0
|
|
56
|
-
var error_count: int = 0
|
|
57
|
-
|
|
58
|
-
for scene_path: String in scenes:
|
|
59
|
-
_log.debug("Processing scene: " + scene_path)
|
|
60
|
-
|
|
61
|
-
if not FileAccess.file_exists(scene_path):
|
|
62
|
-
_log.error("Scene file does not exist at: " + scene_path)
|
|
63
|
-
error_count += 1
|
|
64
|
-
continue
|
|
65
|
-
|
|
66
|
-
var scene: Resource = load(scene_path)
|
|
67
|
-
if not scene:
|
|
68
|
-
error_count += 1
|
|
69
|
-
_log.error("Failed to load: " + scene_path)
|
|
70
|
-
continue
|
|
71
|
-
|
|
72
|
-
var error: Error = ResourceSaver.save(scene, scene_path)
|
|
73
|
-
if error != OK:
|
|
74
|
-
error_count += 1
|
|
75
|
-
_log.error("Failed to save: " + scene_path + ", error: " + str(error))
|
|
76
|
-
continue
|
|
77
|
-
|
|
78
|
-
success_count += 1
|
|
79
|
-
if _log.debug_mode:
|
|
80
|
-
_log.debug("Scene saved successfully: " + scene_path)
|
|
81
|
-
var file_check_after: bool = FileAccess.file_exists(scene_path)
|
|
82
|
-
_log.debug("File exists check after save: " + str(file_check_after))
|
|
83
|
-
if not file_check_after:
|
|
84
|
-
_log.error("File reported as saved but does not exist at: " + scene_path)
|
|
85
|
-
|
|
86
|
-
# A UID sits beside every script and shader as a .uid file. Outside the editor the save
|
|
87
|
-
# below answers OK and writes no sidecar, so what comes back is what was resaved and how
|
|
88
|
-
# many are still without one, rather than a count of UIDs this made.
|
|
89
|
-
var scripts: Array[String] = (
|
|
90
|
-
_files.find_files(project_path, ".gd")
|
|
91
|
-
+ _files.find_files(project_path, ".shader")
|
|
92
|
-
+ _files.find_files(project_path, ".gdshader")
|
|
93
|
-
)
|
|
94
|
-
_log.debug("Found " + str(scripts.size()) + " scripts/shaders")
|
|
95
|
-
|
|
96
|
-
var missing_uids: int = 0
|
|
97
|
-
var resaved_scripts: int = 0
|
|
98
|
-
|
|
99
|
-
for script_path: String in scripts:
|
|
100
|
-
_log.debug("Checking UID for: " + script_path)
|
|
101
|
-
var uid_path: String = script_path + ".uid"
|
|
102
|
-
|
|
103
|
-
var f: FileAccess = FileAccess.open(uid_path, FileAccess.READ)
|
|
104
|
-
if f:
|
|
105
|
-
_log.debug("UID file already exists for: " + script_path)
|
|
106
|
-
continue
|
|
107
|
-
|
|
108
|
-
missing_uids += 1
|
|
109
|
-
_log.debug("Missing UID file for: " + script_path + ", resaving...")
|
|
110
|
-
|
|
111
|
-
var res: Resource = load(script_path)
|
|
112
|
-
if not res:
|
|
113
|
-
_log.error("Failed to load resource: " + script_path)
|
|
114
|
-
continue
|
|
115
|
-
|
|
116
|
-
var error: Error = ResourceSaver.save(res, script_path)
|
|
117
|
-
if error != OK:
|
|
118
|
-
_log.error("Failed to resave: " + script_path + ", error: " + str(error))
|
|
119
|
-
continue
|
|
120
|
-
|
|
121
|
-
resaved_scripts += 1
|
|
122
|
-
_log.debug("Resaved: " + script_path)
|
|
123
|
-
|
|
124
|
-
_log.info("Resave operation complete")
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
"scenes_processed": scenes.size(),
|
|
128
|
-
"scenes_saved": success_count,
|
|
129
|
-
"scenes_with_errors": error_count,
|
|
130
|
-
"scripts_missing_uids": missing_uids,
|
|
131
|
-
"scripts_resaved": resaved_scripts
|
|
132
|
-
}
|
package/build/index.js
CHANGED
|
@@ -10151,8 +10151,7 @@ import {
|
|
|
10151
10151
|
openSync as openSync2,
|
|
10152
10152
|
readdirSync as readdirSync6,
|
|
10153
10153
|
readFileSync as readFileSync11,
|
|
10154
|
-
readSync
|
|
10155
|
-
rmSync as rmSync5
|
|
10154
|
+
readSync
|
|
10156
10155
|
} from "node:fs";
|
|
10157
10156
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
10158
10157
|
import { basename as basename2, dirname as dirname4, join as join11, normalize as normalize2 } from "node:path";
|
|
@@ -26324,10 +26323,35 @@ class GodotLocator {
|
|
|
26324
26323
|
|
|
26325
26324
|
// src/headless.ts
|
|
26326
26325
|
import { execFile as execFile2 } from "node:child_process";
|
|
26327
|
-
import { mkdtempSync,
|
|
26326
|
+
import { mkdtempSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
26328
26327
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
26329
26328
|
import { join as join6 } from "node:path";
|
|
26330
26329
|
import { promisify as promisify2 } from "node:util";
|
|
26330
|
+
|
|
26331
|
+
// src/scratch.ts
|
|
26332
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
26333
|
+
var SWEPT = { recursive: true, force: true, maxRetries: 20, retryDelay: 250 };
|
|
26334
|
+
function discardWith(remove, paths) {
|
|
26335
|
+
const left = [];
|
|
26336
|
+
for (const path of paths) {
|
|
26337
|
+
if (!path) {
|
|
26338
|
+
continue;
|
|
26339
|
+
}
|
|
26340
|
+
try {
|
|
26341
|
+
remove(path);
|
|
26342
|
+
} catch (error) {
|
|
26343
|
+
left.push({ path, reason: error instanceof Error ? error.message : String(error) });
|
|
26344
|
+
}
|
|
26345
|
+
}
|
|
26346
|
+
return left;
|
|
26347
|
+
}
|
|
26348
|
+
function discard(...paths) {
|
|
26349
|
+
return discardWith((path) => {
|
|
26350
|
+
rmSync2(path, SWEPT);
|
|
26351
|
+
}, paths);
|
|
26352
|
+
}
|
|
26353
|
+
|
|
26354
|
+
// src/headless.ts
|
|
26331
26355
|
var run2 = promisify2(execFile2);
|
|
26332
26356
|
function snakeCased(params) {
|
|
26333
26357
|
const result = emptyRecord();
|
|
@@ -26366,6 +26390,36 @@ function reason(stdout, stderr) {
|
|
|
26366
26390
|
}
|
|
26367
26391
|
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
26368
26392
|
}
|
|
26393
|
+
async function runImport(godotPath, projectPath) {
|
|
26394
|
+
const logDir = mkdtempSync(join6(tmpdir2(), "gdharness-import-"));
|
|
26395
|
+
try {
|
|
26396
|
+
const { stderr } = await run2(godotPath, [
|
|
26397
|
+
"--headless",
|
|
26398
|
+
"--log-file",
|
|
26399
|
+
join6(logDir, "engine.log"),
|
|
26400
|
+
"--path",
|
|
26401
|
+
projectPath,
|
|
26402
|
+
"--import"
|
|
26403
|
+
]);
|
|
26404
|
+
return { ok: true, messages: problems(stderr) };
|
|
26405
|
+
} catch (error) {
|
|
26406
|
+
if (error instanceof Error && "stdout" in error && "stderr" in error) {
|
|
26407
|
+
const failed = error;
|
|
26408
|
+
return {
|
|
26409
|
+
ok: false,
|
|
26410
|
+
message: `the import pass failed (exit ${failed.code ?? "unknown"}): ${reason(failed.stdout, failed.stderr)}`,
|
|
26411
|
+
messages: problems(failed.stderr)
|
|
26412
|
+
};
|
|
26413
|
+
}
|
|
26414
|
+
return {
|
|
26415
|
+
ok: false,
|
|
26416
|
+
message: `the import pass could not be run: ${error instanceof Error ? error.message : String(error)}`,
|
|
26417
|
+
messages: []
|
|
26418
|
+
};
|
|
26419
|
+
} finally {
|
|
26420
|
+
discard(logDir);
|
|
26421
|
+
}
|
|
26422
|
+
}
|
|
26369
26423
|
async function runOperation(engine, operation, params, projectPath) {
|
|
26370
26424
|
const paramsDir = mkdtempSync(join6(tmpdir2(), "gdharness-params-"));
|
|
26371
26425
|
const paramsFile = join6(paramsDir, `${operation}.json`);
|
|
@@ -26401,7 +26455,7 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
26401
26455
|
messages: []
|
|
26402
26456
|
};
|
|
26403
26457
|
} finally {
|
|
26404
|
-
|
|
26458
|
+
discard(paramsDir);
|
|
26405
26459
|
}
|
|
26406
26460
|
const payload = lastJsonObject(stdout);
|
|
26407
26461
|
if (payload === null) {
|
|
@@ -26435,7 +26489,6 @@ var HEADLESS_OPERATIONS = dictionary({
|
|
|
26435
26489
|
set_options: "set_import_options",
|
|
26436
26490
|
reimport: "reimport_resource",
|
|
26437
26491
|
uid: "get_uid",
|
|
26438
|
-
refresh_uids: "resave_resources",
|
|
26439
26492
|
refresh_classes: "refresh_class_cache"
|
|
26440
26493
|
}),
|
|
26441
26494
|
project_export: dictionary({ list: "list_export_presets" }),
|
|
@@ -26447,6 +26500,9 @@ var HEADLESS_OPERATIONS = dictionary({
|
|
|
26447
26500
|
inheritance: "inspect_inheritance"
|
|
26448
26501
|
})
|
|
26449
26502
|
});
|
|
26503
|
+
var ENGINE_PASSES = dictionary({
|
|
26504
|
+
project_import: dictionary({ refresh_uids: "import" })
|
|
26505
|
+
});
|
|
26450
26506
|
var EDITOR_READS = dictionary({
|
|
26451
26507
|
project_settings: dictionary({ get: "get_project_setting" })
|
|
26452
26508
|
});
|
|
@@ -27384,6 +27440,34 @@ function projectStructure(projectPath) {
|
|
|
27384
27440
|
visit(projectPath);
|
|
27385
27441
|
return structure;
|
|
27386
27442
|
}
|
|
27443
|
+
var CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
27444
|
+
function scriptsWithoutUid(projectPath) {
|
|
27445
|
+
const found = [];
|
|
27446
|
+
const visit = (directory, prefix) => {
|
|
27447
|
+
if (steppedOver(directory)) {
|
|
27448
|
+
return;
|
|
27449
|
+
}
|
|
27450
|
+
for (const entry of readdirSync3(directory, { withFileTypes: true })) {
|
|
27451
|
+
if (skipped(entry.name)) {
|
|
27452
|
+
continue;
|
|
27453
|
+
}
|
|
27454
|
+
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
27455
|
+
if (entry.isDirectory()) {
|
|
27456
|
+
visit(join7(directory, entry.name), spelled);
|
|
27457
|
+
continue;
|
|
27458
|
+
}
|
|
27459
|
+
if (!entry.isFile()) {
|
|
27460
|
+
continue;
|
|
27461
|
+
}
|
|
27462
|
+
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
27463
|
+
if (CARRIES_A_UID.has(extension) && !existsSync6(join7(directory, `${entry.name}.uid`))) {
|
|
27464
|
+
found.push(spelled);
|
|
27465
|
+
}
|
|
27466
|
+
}
|
|
27467
|
+
};
|
|
27468
|
+
visit(projectPath, "");
|
|
27469
|
+
return found.sort();
|
|
27470
|
+
}
|
|
27387
27471
|
function searchProject(projectPath, options) {
|
|
27388
27472
|
const extensions = new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
|
|
27389
27473
|
const result = {
|
|
@@ -28538,7 +28622,10 @@ var TOOL_SPECS = [
|
|
|
28538
28622
|
requires: []
|
|
28539
28623
|
},
|
|
28540
28624
|
uid: { summary: "the UID of one file", requires: ["resourcePath"] },
|
|
28541
|
-
refresh_uids: {
|
|
28625
|
+
refresh_uids: {
|
|
28626
|
+
summary: "import the project so every script and shader has its .uid sidecar, and name under uidsCreated the ones this made and under stillWithoutUid the ones the engine would not import: it writes no scene and no script, so a project whose sidecars are all present is left untouched",
|
|
28627
|
+
requires: []
|
|
28628
|
+
},
|
|
28542
28629
|
refresh_classes: {
|
|
28543
28630
|
summary: 'rewrite .godot/global_script_class_cache.cfg from the class_name declarations on disk, and name under unseenByEditor any class the editor open on this project still cannot resolve: rewriting the file does not reach the list a running editor loaded, so "added: []" means the file was already right rather than that nothing is wrong',
|
|
28544
28631
|
requires: []
|
|
@@ -29946,6 +30033,9 @@ class GodotServer {
|
|
|
29946
30033
|
return { ok: true, op };
|
|
29947
30034
|
}
|
|
29948
30035
|
async dispatch(tool, op, args) {
|
|
30036
|
+
if (ENGINE_PASSES[tool]?.[op] !== undefined) {
|
|
30037
|
+
return await this.handleRefreshUids(args);
|
|
30038
|
+
}
|
|
29949
30039
|
const headless = HEADLESS_OPERATIONS[tool]?.[op];
|
|
29950
30040
|
if (headless !== undefined) {
|
|
29951
30041
|
const { from: asked, ...answerable } = args;
|
|
@@ -30316,6 +30406,33 @@ class GodotServer {
|
|
|
30316
30406
|
const { op: _op, projectPath: _projectPath, ...params } = contained.value;
|
|
30317
30407
|
return this.answer(await this.operation(operation, params, project.value.path));
|
|
30318
30408
|
}
|
|
30409
|
+
async handleRefreshUids(args) {
|
|
30410
|
+
const project = this.project(args);
|
|
30411
|
+
if (!project.ok) {
|
|
30412
|
+
return project.response;
|
|
30413
|
+
}
|
|
30414
|
+
const engine = await this.engine();
|
|
30415
|
+
if (!engine.ok) {
|
|
30416
|
+
return this.createErrorResponse("No Godot executable found.");
|
|
30417
|
+
}
|
|
30418
|
+
const projectPath = project.value.path;
|
|
30419
|
+
const before = scriptsWithoutUid(projectPath);
|
|
30420
|
+
const imported = await runImport(engine.value, projectPath);
|
|
30421
|
+
if (!imported.ok) {
|
|
30422
|
+
return this.answer(imported);
|
|
30423
|
+
}
|
|
30424
|
+
const after = scriptsWithoutUid(projectPath);
|
|
30425
|
+
const given = before.filter((script) => !after.includes(script));
|
|
30426
|
+
return this.answer({
|
|
30427
|
+
ok: true,
|
|
30428
|
+
messages: imported.messages,
|
|
30429
|
+
payload: {
|
|
30430
|
+
uidsCreated: given,
|
|
30431
|
+
stillWithoutUid: after,
|
|
30432
|
+
note: after.length > 0 ? `${after.length} still have no .uid, which is the engine declining to import them rather than this op skipping them. No scene was written.` : "No scene was written: this reads the project and imports it, and does not resave anything."
|
|
30433
|
+
}
|
|
30434
|
+
});
|
|
30435
|
+
}
|
|
30319
30436
|
async operation(operation, params, projectPath) {
|
|
30320
30437
|
const engine = await this.engine();
|
|
30321
30438
|
if (!engine.ok) {
|
|
@@ -30446,7 +30563,7 @@ class GodotServer {
|
|
|
30446
30563
|
log.append("stderr", failed.stderr);
|
|
30447
30564
|
exitCode = typeof failed.code === "number" ? failed.code : -1;
|
|
30448
30565
|
} finally {
|
|
30449
|
-
|
|
30566
|
+
discard(exportLogs);
|
|
30450
30567
|
}
|
|
30451
30568
|
log.finish();
|
|
30452
30569
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
@@ -30544,8 +30661,7 @@ class GodotServer {
|
|
|
30544
30661
|
} catch (error) {
|
|
30545
30662
|
reportProblem = errorMessage(error);
|
|
30546
30663
|
} finally {
|
|
30547
|
-
|
|
30548
|
-
rmSync5(userData, { recursive: true, force: true });
|
|
30664
|
+
discard(reportsDir, userData);
|
|
30549
30665
|
}
|
|
30550
30666
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
30551
30667
|
const verdicts = {
|
|
@@ -31624,9 +31740,7 @@ class GodotServer {
|
|
|
31624
31740
|
]
|
|
31625
31741
|
};
|
|
31626
31742
|
} finally {
|
|
31627
|
-
|
|
31628
|
-
rmSync5(screenshotDir, { recursive: true, force: true });
|
|
31629
|
-
}
|
|
31743
|
+
discard(screenshotDir);
|
|
31630
31744
|
}
|
|
31631
31745
|
}
|
|
31632
31746
|
async handleFindRuntimeNodes(args) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.13",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|