@skillcap/gdh 3.0.2 → 3.2.0
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/INSTALL-BUNDLE.json +1 -1
- package/README.md +1 -0
- package/RELEASE-SPAN-UPDATE-CONTRACTS.json +166 -0
- package/node_modules/@gdh/adapters/package.json +8 -8
- package/node_modules/@gdh/authoring/package.json +2 -2
- package/node_modules/@gdh/cli/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/cli/dist/index.js +195 -4
- package/node_modules/@gdh/cli/dist/index.js.map +1 -1
- package/node_modules/@gdh/cli/package.json +11 -10
- package/node_modules/@gdh/core/dist/bridge-substrate.d.ts +20 -0
- package/node_modules/@gdh/core/dist/bridge-substrate.d.ts.map +1 -0
- package/node_modules/@gdh/core/dist/bridge-substrate.js +40 -0
- package/node_modules/@gdh/core/dist/bridge-substrate.js.map +1 -0
- package/node_modules/@gdh/core/dist/index.d.ts +27 -32
- package/node_modules/@gdh/core/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/core/dist/index.js +15 -14
- package/node_modules/@gdh/core/dist/index.js.map +1 -1
- package/node_modules/@gdh/core/package.json +1 -1
- package/node_modules/@gdh/docs/dist/guidance.d.ts.map +1 -1
- package/node_modules/@gdh/docs/dist/guidance.js +29 -0
- package/node_modules/@gdh/docs/dist/guidance.js.map +1 -1
- package/node_modules/@gdh/docs/dist/templates/guidance/editor-bridge.md.tpl +28 -0
- package/node_modules/@gdh/docs/package.json +2 -2
- package/node_modules/@gdh/editor/dist/index.d.ts +226 -0
- package/node_modules/@gdh/editor/dist/index.d.ts.map +1 -0
- package/node_modules/@gdh/editor/dist/index.js +1380 -0
- package/node_modules/@gdh/editor/dist/index.js.map +1 -0
- package/node_modules/@gdh/editor/package.json +17 -0
- package/node_modules/@gdh/mcp/dist/index.d.ts +1 -1
- package/node_modules/@gdh/mcp/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/mcp/dist/index.js +392 -7
- package/node_modules/@gdh/mcp/dist/index.js.map +1 -1
- package/node_modules/@gdh/mcp/package.json +10 -8
- package/node_modules/@gdh/observability/package.json +2 -2
- package/node_modules/@gdh/runtime/dist/bridge-broker-contract.d.ts +2 -2
- package/node_modules/@gdh/runtime/dist/bridge-broker-contract.d.ts.map +1 -1
- package/node_modules/@gdh/runtime/dist/bridge-broker-contract.js +2 -37
- package/node_modules/@gdh/runtime/dist/bridge-broker-contract.js.map +1 -1
- package/node_modules/@gdh/runtime/dist/bridge-surface.d.ts +1 -1
- package/node_modules/@gdh/runtime/dist/bridge-surface.d.ts.map +1 -1
- package/node_modules/@gdh/runtime/dist/bridge-surface.js +396 -73
- package/node_modules/@gdh/runtime/dist/bridge-surface.js.map +1 -1
- package/node_modules/@gdh/runtime/dist/index.d.ts +2 -3
- package/node_modules/@gdh/runtime/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/runtime/dist/index.js +2 -3
- package/node_modules/@gdh/runtime/dist/index.js.map +1 -1
- package/node_modules/@gdh/runtime/package.json +3 -2
- package/node_modules/@gdh/scan/package.json +3 -3
- package/node_modules/@gdh/verify/package.json +7 -7
- package/package.json +13 -11
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {} from "@gdh/
|
|
4
|
-
const RUNTIME_BRIDGE_SURFACE_VERSION =
|
|
3
|
+
import { renderEditorOperationCatalogGdscript } from "@gdh/editor";
|
|
4
|
+
const RUNTIME_BRIDGE_SURFACE_VERSION = 15;
|
|
5
5
|
const AUTOLOAD_ENABLE_SENTINEL = "GDH_RUNTIME_BRIDGE_AUTOLOAD_ENABLED";
|
|
6
6
|
const DEFAULT_RUNTIME_BRIDGE_CONFIG = {
|
|
7
7
|
enabled: true,
|
|
@@ -50,6 +50,7 @@ export async function inspectRuntimeBridgeSurface(input) {
|
|
|
50
50
|
projectGodotPath: null,
|
|
51
51
|
addonRootPath: null,
|
|
52
52
|
autoloadRegistered: false,
|
|
53
|
+
editorPluginRegistered: false,
|
|
53
54
|
customEntryPaths: [],
|
|
54
55
|
managedArtifacts: [],
|
|
55
56
|
};
|
|
@@ -67,6 +68,7 @@ export async function inspectRuntimeBridgeSurface(input) {
|
|
|
67
68
|
projectGodotPath: null,
|
|
68
69
|
addonRootPath: null,
|
|
69
70
|
autoloadRegistered: false,
|
|
71
|
+
editorPluginRegistered: false,
|
|
70
72
|
customEntryPaths: [],
|
|
71
73
|
managedArtifacts: [],
|
|
72
74
|
};
|
|
@@ -89,12 +91,14 @@ export async function inspectRuntimeBridgeSurface(input) {
|
|
|
89
91
|
projectGodotPath,
|
|
90
92
|
addonRootPath,
|
|
91
93
|
autoloadRegistered: false,
|
|
94
|
+
editorPluginRegistered: false,
|
|
92
95
|
customEntryPaths: [],
|
|
93
96
|
managedArtifacts: [],
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
99
|
const projectGodotContent = await fs.readFile(projectGodotPath, "utf8");
|
|
97
100
|
const autoloadRegistered = hasAutoloadRegistration(projectGodotContent, runtimeBridge);
|
|
101
|
+
const editorPluginRegistered = hasEditorPluginRegistration(projectGodotContent, runtimeBridge);
|
|
98
102
|
const managedArtifacts = await Promise.all(managedFiles.map((file) => inspectManagedBridgeFile({
|
|
99
103
|
targetPath: resolvedTargetPath,
|
|
100
104
|
targetProjectPath,
|
|
@@ -110,6 +114,7 @@ export async function inspectRuntimeBridgeSurface(input) {
|
|
|
110
114
|
const hasPresentManagedFiles = managedArtifacts.some((artifact) => artifact.state === "present" || artifact.state === "drifted");
|
|
111
115
|
const state = resolveRuntimeBridgeState({
|
|
112
116
|
autoloadRegistered,
|
|
117
|
+
editorPluginRegistered,
|
|
113
118
|
hasDrift,
|
|
114
119
|
hasMissing,
|
|
115
120
|
hasPresentManagedFiles,
|
|
@@ -122,6 +127,7 @@ export async function inspectRuntimeBridgeSurface(input) {
|
|
|
122
127
|
? ["runtime_bridge_plugin_enable_required"]
|
|
123
128
|
: []),
|
|
124
129
|
...(!autoloadRegistered ? ["runtime_bridge_autoload_missing_or_drifted"] : []),
|
|
130
|
+
...(!editorPluginRegistered ? ["editor_bridge_plugin_missing_or_drifted"] : []),
|
|
125
131
|
...managedArtifacts
|
|
126
132
|
.filter((artifact) => artifact.state === "missing")
|
|
127
133
|
.map((artifact) => `runtime_bridge_missing:${artifact.id}`),
|
|
@@ -136,6 +142,7 @@ export async function inspectRuntimeBridgeSurface(input) {
|
|
|
136
142
|
state,
|
|
137
143
|
compatibility: resolvedConfig.compatibility,
|
|
138
144
|
autoloadRegistered,
|
|
145
|
+
editorPluginRegistered,
|
|
139
146
|
runtimeBridge,
|
|
140
147
|
customEntryCount: customEntryPaths.length,
|
|
141
148
|
}),
|
|
@@ -147,6 +154,7 @@ export async function inspectRuntimeBridgeSurface(input) {
|
|
|
147
154
|
projectGodotPath,
|
|
148
155
|
addonRootPath,
|
|
149
156
|
autoloadRegistered,
|
|
157
|
+
editorPluginRegistered,
|
|
150
158
|
customEntryPaths,
|
|
151
159
|
managedArtifacts,
|
|
152
160
|
};
|
|
@@ -197,30 +205,31 @@ async function applyRuntimeBridgeOperation(input) {
|
|
|
197
205
|
actions.push({
|
|
198
206
|
id: `remove-${artifact.id}`,
|
|
199
207
|
kind: "remove_file",
|
|
200
|
-
state: artifact.state === "missing"
|
|
201
|
-
? "unchanged"
|
|
202
|
-
: input.dryRun
|
|
203
|
-
? "planned"
|
|
204
|
-
: "applied",
|
|
208
|
+
state: artifact.state === "missing" ? "unchanged" : input.dryRun ? "planned" : "applied",
|
|
205
209
|
relativePath: artifact.relativePath,
|
|
206
210
|
summary: artifact.state === "missing"
|
|
207
|
-
? "Managed
|
|
208
|
-
: "Remove the GDH-managed
|
|
211
|
+
? "Managed bridge addon file is already absent."
|
|
212
|
+
: "Remove the GDH-managed bridge addon file.",
|
|
209
213
|
});
|
|
210
214
|
}
|
|
211
215
|
actions.push({
|
|
212
216
|
id: "remove-runtime-bridge-autoload",
|
|
213
217
|
kind: "remove_project_godot_entry",
|
|
214
|
-
state: !status.autoloadRegistered
|
|
215
|
-
? "unchanged"
|
|
216
|
-
: input.dryRun
|
|
217
|
-
? "planned"
|
|
218
|
-
: "applied",
|
|
218
|
+
state: !status.autoloadRegistered ? "unchanged" : input.dryRun ? "planned" : "applied",
|
|
219
219
|
relativePath: path.relative(status.targetPath, projectGodotPath),
|
|
220
220
|
summary: !status.autoloadRegistered
|
|
221
221
|
? "Runtime bridge autoload entry is already absent."
|
|
222
222
|
: "Remove the GDH runtime bridge autoload entry left by older bridge installs.",
|
|
223
223
|
});
|
|
224
|
+
actions.push({
|
|
225
|
+
id: "remove-editor-bridge-plugin-registration",
|
|
226
|
+
kind: "remove_project_godot_entry",
|
|
227
|
+
state: !status.editorPluginRegistered ? "unchanged" : input.dryRun ? "planned" : "applied",
|
|
228
|
+
relativePath: path.relative(status.targetPath, projectGodotPath),
|
|
229
|
+
summary: !status.editorPluginRegistered
|
|
230
|
+
? "GDH editor plugin registration is already absent."
|
|
231
|
+
: "Remove the GDH editor plugin registration from project.godot.",
|
|
232
|
+
});
|
|
224
233
|
if (!input.dryRun) {
|
|
225
234
|
for (const artifact of status.managedArtifacts) {
|
|
226
235
|
if (artifact.state === "missing") {
|
|
@@ -232,17 +241,17 @@ async function applyRuntimeBridgeOperation(input) {
|
|
|
232
241
|
const content = await fs.readFile(projectGodotPath, "utf8");
|
|
233
242
|
await fs.writeFile(projectGodotPath, removeAutoloadRegistration(content, status.runtimeBridge), "utf8");
|
|
234
243
|
}
|
|
244
|
+
if (status.editorPluginRegistered) {
|
|
245
|
+
const content = await fs.readFile(projectGodotPath, "utf8");
|
|
246
|
+
await fs.writeFile(projectGodotPath, removeEditorPluginRegistration(content, status.runtimeBridge), "utf8");
|
|
247
|
+
}
|
|
235
248
|
}
|
|
236
249
|
}
|
|
237
250
|
else {
|
|
238
251
|
actions.push({
|
|
239
252
|
id: "write-project-bridge-entries-gitkeep",
|
|
240
253
|
kind: "write_file",
|
|
241
|
-
state: projectEntryGitkeepExists
|
|
242
|
-
? "unchanged"
|
|
243
|
-
: input.dryRun
|
|
244
|
-
? "planned"
|
|
245
|
-
: "applied",
|
|
254
|
+
state: projectEntryGitkeepExists ? "unchanged" : input.dryRun ? "planned" : "applied",
|
|
246
255
|
relativePath: normalizePath(path.relative(status.targetPath, projectEntryGitkeepPath)),
|
|
247
256
|
summary: projectEntryGitkeepExists
|
|
248
257
|
? "Project-owned runtime bridge entry folder scaffold already exists."
|
|
@@ -253,30 +262,31 @@ async function applyRuntimeBridgeOperation(input) {
|
|
|
253
262
|
actions.push({
|
|
254
263
|
id: `write-${file.id}`,
|
|
255
264
|
kind: "write_file",
|
|
256
|
-
state: artifact.state === "present"
|
|
257
|
-
? "unchanged"
|
|
258
|
-
: input.dryRun
|
|
259
|
-
? "planned"
|
|
260
|
-
: "applied",
|
|
265
|
+
state: artifact.state === "present" ? "unchanged" : input.dryRun ? "planned" : "applied",
|
|
261
266
|
relativePath: artifact.relativePath,
|
|
262
267
|
summary: artifact.state === "present"
|
|
263
|
-
? "Managed
|
|
264
|
-
: "Write the GDH-managed
|
|
268
|
+
? "Managed bridge addon file already matches the current GDH baseline."
|
|
269
|
+
: "Write the GDH-managed bridge addon file.",
|
|
265
270
|
});
|
|
266
271
|
}
|
|
267
272
|
actions.push({
|
|
268
273
|
id: "write-runtime-bridge-autoload",
|
|
269
274
|
kind: "update_project_godot",
|
|
270
|
-
state: status.autoloadRegistered
|
|
271
|
-
? "unchanged"
|
|
272
|
-
: input.dryRun
|
|
273
|
-
? "planned"
|
|
274
|
-
: "applied",
|
|
275
|
+
state: status.autoloadRegistered ? "unchanged" : input.dryRun ? "planned" : "applied",
|
|
275
276
|
relativePath: path.relative(status.targetPath, projectGodotPath),
|
|
276
277
|
summary: status.autoloadRegistered
|
|
277
278
|
? "Runtime bridge autoload entry is already present."
|
|
278
279
|
: "Register the GDH runtime bridge autoload in project.godot (class-3 register_autoload op, GDH_MANAGED_SURFACE_CLASSES allowlist).",
|
|
279
280
|
});
|
|
281
|
+
actions.push({
|
|
282
|
+
id: "write-editor-bridge-plugin-registration",
|
|
283
|
+
kind: "update_project_godot",
|
|
284
|
+
state: status.editorPluginRegistered ? "unchanged" : input.dryRun ? "planned" : "applied",
|
|
285
|
+
relativePath: path.relative(status.targetPath, projectGodotPath),
|
|
286
|
+
summary: status.editorPluginRegistered
|
|
287
|
+
? "GDH editor plugin registration is already present."
|
|
288
|
+
: "Register the GDH editor plugin in project.godot (class-3 edit_editor_plugins_array op, GDH_MANAGED_SURFACE_CLASSES allowlist).",
|
|
289
|
+
});
|
|
280
290
|
if (!input.dryRun) {
|
|
281
291
|
if (!projectEntryGitkeepExists) {
|
|
282
292
|
await fs.mkdir(path.dirname(projectEntryGitkeepPath), { recursive: true });
|
|
@@ -295,6 +305,10 @@ async function applyRuntimeBridgeOperation(input) {
|
|
|
295
305
|
const content = await fs.readFile(projectGodotPath, "utf8");
|
|
296
306
|
await fs.writeFile(projectGodotPath, addAutoloadRegistration(content, status.runtimeBridge), "utf8");
|
|
297
307
|
}
|
|
308
|
+
if (!status.editorPluginRegistered) {
|
|
309
|
+
const content = await fs.readFile(projectGodotPath, "utf8");
|
|
310
|
+
await fs.writeFile(projectGodotPath, addEditorPluginRegistration(content, status.runtimeBridge), "utf8");
|
|
311
|
+
}
|
|
298
312
|
}
|
|
299
313
|
}
|
|
300
314
|
const nextStatus = input.dryRun
|
|
@@ -328,7 +342,7 @@ async function inspectManagedBridgeFile(input) {
|
|
|
328
342
|
absolutePath,
|
|
329
343
|
managed: true,
|
|
330
344
|
state: "missing",
|
|
331
|
-
summary: "Managed
|
|
345
|
+
summary: "Managed bridge addon file has not been installed yet.",
|
|
332
346
|
expectedVersion: input.file.expectedVersion,
|
|
333
347
|
detectedVersion: null,
|
|
334
348
|
};
|
|
@@ -340,8 +354,8 @@ async function inspectManagedBridgeFile(input) {
|
|
|
340
354
|
managed: true,
|
|
341
355
|
state: currentContent === input.file.content ? "present" : "drifted",
|
|
342
356
|
summary: currentContent === input.file.content
|
|
343
|
-
? "Managed
|
|
344
|
-
: "Managed
|
|
357
|
+
? "Managed bridge addon file matches the current GDH baseline."
|
|
358
|
+
: "Managed bridge addon file exists but has drifted from the current GDH baseline.",
|
|
345
359
|
expectedVersion: input.file.expectedVersion,
|
|
346
360
|
detectedVersion: input.file.relativePath.endsWith(".json")
|
|
347
361
|
? parseBridgeManifestVersion(currentContent)
|
|
@@ -378,6 +392,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
378
392
|
managedFiles: [
|
|
379
393
|
"plugin.cfg",
|
|
380
394
|
"plugin.gd",
|
|
395
|
+
"editor/gdh_editor_bridge_plugin.gd",
|
|
381
396
|
"registry/bridge_registry.gd",
|
|
382
397
|
"entries/core_entries.gd",
|
|
383
398
|
"runtime/gdh_runtime_bridge.gd",
|
|
@@ -390,11 +405,11 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
390
405
|
relativePath: normalizePath(path.join(runtimeBridge.addonPath, "plugin.cfg")),
|
|
391
406
|
expectedVersion: RUNTIME_BRIDGE_SURFACE_VERSION,
|
|
392
407
|
content: [
|
|
393
|
-
`; GDH managed
|
|
408
|
+
`; GDH managed bridge addon v${RUNTIME_BRIDGE_SURFACE_VERSION}`,
|
|
394
409
|
"",
|
|
395
410
|
"[plugin]",
|
|
396
|
-
'name="GDH
|
|
397
|
-
'description="GDH-managed
|
|
411
|
+
'name="GDH Bridge"',
|
|
412
|
+
'description="GDH-managed editor and runtime bridge surfaces."',
|
|
398
413
|
'author="GDH"',
|
|
399
414
|
`version="${RUNTIME_BRIDGE_SURFACE_VERSION}"`,
|
|
400
415
|
'script="plugin.gd"',
|
|
@@ -409,12 +424,31 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
409
424
|
"@tool",
|
|
410
425
|
"extends EditorPlugin",
|
|
411
426
|
"",
|
|
412
|
-
`const
|
|
427
|
+
`const GDH_BRIDGE_ADDON_VERSION := ${RUNTIME_BRIDGE_SURFACE_VERSION}`,
|
|
428
|
+
`const EditorBridgePlugin = preload("res://${normalizePath(path.join(runtimeBridge.addonPath, "editor", "gdh_editor_bridge_plugin.gd"))}")`,
|
|
413
429
|
`const AUTOLOAD_NAME := "${runtimeBridge.autoloadName}"`,
|
|
414
430
|
`const AUTOLOAD_PATH := "res://${normalizePath(path.join(runtimeBridge.addonPath, "runtime", "gdh_runtime_bridge.gd"))}"`,
|
|
415
431
|
"",
|
|
432
|
+
"var _editor_bridge: RefCounted",
|
|
433
|
+
"",
|
|
434
|
+
"func _enter_tree() -> void:",
|
|
435
|
+
"\tset_process(true)",
|
|
436
|
+
"\t_editor_bridge = EditorBridgePlugin.new()",
|
|
437
|
+
'\tif _editor_bridge != null and _editor_bridge.has_method("enter_tree"):',
|
|
438
|
+
"\t\t_editor_bridge.enter_tree(self)",
|
|
439
|
+
"",
|
|
440
|
+
"func _process(delta: float) -> void:",
|
|
441
|
+
'\tif _editor_bridge != null and _editor_bridge.has_method("process"):',
|
|
442
|
+
"\t\t_editor_bridge.process(delta)",
|
|
443
|
+
"",
|
|
444
|
+
"func _exit_tree() -> void:",
|
|
445
|
+
"\tset_process(false)",
|
|
446
|
+
'\tif _editor_bridge != null and _editor_bridge.has_method("exit_tree"):',
|
|
447
|
+
"\t\t_editor_bridge.exit_tree(self)",
|
|
448
|
+
"\t_editor_bridge = null",
|
|
449
|
+
"",
|
|
416
450
|
"func _enable_plugin() -> void:",
|
|
417
|
-
|
|
451
|
+
'\tif not ProjectSettings.has_setting("autoload/%s" % AUTOLOAD_NAME):',
|
|
418
452
|
"\t\tadd_autoload_singleton(AUTOLOAD_NAME, AUTOLOAD_PATH)",
|
|
419
453
|
`\t\tProjectSettings.set_setting("${AUTOLOAD_ENABLE_SENTINEL}", true)`,
|
|
420
454
|
"",
|
|
@@ -425,6 +459,231 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
425
459
|
"",
|
|
426
460
|
].join("\n"),
|
|
427
461
|
},
|
|
462
|
+
{
|
|
463
|
+
id: "editor-bridge-plugin-gd",
|
|
464
|
+
relativePath: normalizePath(path.join(runtimeBridge.addonPath, "editor", "gdh_editor_bridge_plugin.gd")),
|
|
465
|
+
expectedVersion: RUNTIME_BRIDGE_SURFACE_VERSION,
|
|
466
|
+
content: [
|
|
467
|
+
"@tool",
|
|
468
|
+
"extends RefCounted",
|
|
469
|
+
"",
|
|
470
|
+
`const GDH_EDITOR_BRIDGE_VERSION := ${RUNTIME_BRIDGE_SURFACE_VERSION}`,
|
|
471
|
+
"const GDH_EDITOR_ADOPTION_PROTOCOL_VERSION := 1",
|
|
472
|
+
'const GDH_EDITOR_ADOPTION_HOST := "127.0.0.1"',
|
|
473
|
+
"const GDH_EDITOR_ADOPTION_PORT_START := 42240",
|
|
474
|
+
"const GDH_EDITOR_ADOPTION_PORT_END := 42320",
|
|
475
|
+
"const GDH_EDITOR_HEARTBEAT_INTERVAL_SECONDS := 2.0",
|
|
476
|
+
"",
|
|
477
|
+
"var _plugin: EditorPlugin",
|
|
478
|
+
"var _server := TCPServer.new()",
|
|
479
|
+
"var _peers: Array = []",
|
|
480
|
+
"var _heartbeat_elapsed := 0.0",
|
|
481
|
+
'var _target_root_path := ""',
|
|
482
|
+
'var _godot_project_root_path := ""',
|
|
483
|
+
'var _state_root_path := ""',
|
|
484
|
+
'var _editor_bridge_dir := ""',
|
|
485
|
+
'var _metadata_path := ""',
|
|
486
|
+
'var _token_file_path := ""',
|
|
487
|
+
'var _heartbeat_path := ""',
|
|
488
|
+
'var _token := ""',
|
|
489
|
+
"var _port := 0",
|
|
490
|
+
'var _started_at := ""',
|
|
491
|
+
"",
|
|
492
|
+
"func enter_tree(plugin: EditorPlugin) -> void:",
|
|
493
|
+
"\t_plugin = plugin",
|
|
494
|
+
"\t_started_at = _now_iso()",
|
|
495
|
+
'\t_godot_project_root_path = _normalize_absolute_path(ProjectSettings.globalize_path("res://"))',
|
|
496
|
+
"\t_target_root_path = _find_target_root(_godot_project_root_path)",
|
|
497
|
+
'\t_state_root_path = _normalize_absolute_path(_target_root_path.path_join(".gdh-state"))',
|
|
498
|
+
'\t_editor_bridge_dir = _state_root_path.path_join("editor-bridge")',
|
|
499
|
+
'\t_metadata_path = _editor_bridge_dir.path_join("adopted-editor.json")',
|
|
500
|
+
'\t_token_file_path = _editor_bridge_dir.path_join("adopted-editor.token")',
|
|
501
|
+
'\t_heartbeat_path = _editor_bridge_dir.path_join("adopted-editor.heartbeat")',
|
|
502
|
+
"\tDirAccess.make_dir_recursive_absolute(_editor_bridge_dir)",
|
|
503
|
+
"\t_token = _read_or_create_token(_token_file_path)",
|
|
504
|
+
"\t_start_server()",
|
|
505
|
+
"\t_write_heartbeat()",
|
|
506
|
+
"",
|
|
507
|
+
"func process(delta: float) -> void:",
|
|
508
|
+
"\t_poll_server()",
|
|
509
|
+
"\t_heartbeat_elapsed += delta",
|
|
510
|
+
"\tif _heartbeat_elapsed >= GDH_EDITOR_HEARTBEAT_INTERVAL_SECONDS:",
|
|
511
|
+
"\t\t_heartbeat_elapsed = 0.0",
|
|
512
|
+
"\t\t_write_heartbeat()",
|
|
513
|
+
"",
|
|
514
|
+
"func exit_tree(_plugin_instance: EditorPlugin) -> void:",
|
|
515
|
+
"\t_server.stop()",
|
|
516
|
+
"\t_peers.clear()",
|
|
517
|
+
"\tDirAccess.remove_absolute(_metadata_path)",
|
|
518
|
+
"\tDirAccess.remove_absolute(_heartbeat_path)",
|
|
519
|
+
"\t_plugin = null",
|
|
520
|
+
"",
|
|
521
|
+
"func get_status() -> Dictionary:",
|
|
522
|
+
"\treturn {",
|
|
523
|
+
'\t\t"version": GDH_EDITOR_BRIDGE_VERSION,',
|
|
524
|
+
'\t\t"state": "ready",',
|
|
525
|
+
'\t\t"capabilities": [',
|
|
526
|
+
'\t\t\t"editor_operation_catalog",',
|
|
527
|
+
'\t\t\t"editor_session_adoption_anchor",',
|
|
528
|
+
"\t\t],",
|
|
529
|
+
"\t}",
|
|
530
|
+
"",
|
|
531
|
+
"func _start_server() -> void:",
|
|
532
|
+
"\tfor candidate_port in range(GDH_EDITOR_ADOPTION_PORT_START, GDH_EDITOR_ADOPTION_PORT_END + 1):",
|
|
533
|
+
"\t\tif _server.listen(candidate_port, GDH_EDITOR_ADOPTION_HOST) == OK:",
|
|
534
|
+
"\t\t\t_port = candidate_port",
|
|
535
|
+
"\t\t\treturn",
|
|
536
|
+
"\t_server.stop()",
|
|
537
|
+
"\t_port = 0",
|
|
538
|
+
"",
|
|
539
|
+
"func _poll_server() -> void:",
|
|
540
|
+
"\tif _port <= 0:",
|
|
541
|
+
"\t\treturn",
|
|
542
|
+
"\twhile _server.is_connection_available():",
|
|
543
|
+
"\t\tvar peer := _server.take_connection()",
|
|
544
|
+
'\t\t_peers.append({"peer": peer, "buffer": PackedByteArray()})',
|
|
545
|
+
"\tvar remaining: Array = []",
|
|
546
|
+
"\tfor entry in _peers:",
|
|
547
|
+
'\t\tvar peer: StreamPeerTCP = entry["peer"]',
|
|
548
|
+
'\t\tvar buffer: PackedByteArray = entry["buffer"]',
|
|
549
|
+
"\t\tif peer.get_status() == StreamPeerTCP.STATUS_CONNECTED:",
|
|
550
|
+
"\t\t\tvar available := peer.get_available_bytes()",
|
|
551
|
+
"\t\t\tif available > 0:",
|
|
552
|
+
"\t\t\t\tvar chunk := peer.get_data(available)",
|
|
553
|
+
"\t\t\t\tif int(chunk[0]) == OK:",
|
|
554
|
+
"\t\t\t\t\tbuffer.append_array(chunk[1])",
|
|
555
|
+
"\t\t\tif _try_handle_peer(peer, buffer):",
|
|
556
|
+
"\t\t\t\tpeer.disconnect_from_host()",
|
|
557
|
+
"\t\t\telse:",
|
|
558
|
+
"\t\t\t\tremaining.append(entry)",
|
|
559
|
+
"\t_peers = remaining",
|
|
560
|
+
"",
|
|
561
|
+
"func _try_handle_peer(peer: StreamPeerTCP, buffer: PackedByteArray) -> bool:",
|
|
562
|
+
"\tvar text := buffer.get_string_from_utf8()",
|
|
563
|
+
'\tvar header_end := text.find("\\r\\n\\r\\n")',
|
|
564
|
+
"\tif header_end < 0:",
|
|
565
|
+
"\t\treturn false",
|
|
566
|
+
"\tvar header_text := text.substr(0, header_end)",
|
|
567
|
+
"\tvar body_start := header_end + 4",
|
|
568
|
+
"\tvar content_length := 0",
|
|
569
|
+
"\tvar authorized := false",
|
|
570
|
+
'\tfor raw_header in header_text.split("\\r\\n"):',
|
|
571
|
+
"\t\tvar header := str(raw_header)",
|
|
572
|
+
"\t\tvar lower := header.to_lower()",
|
|
573
|
+
'\t\tif lower.begins_with("content-length:"):',
|
|
574
|
+
'\t\t\tcontent_length = int(header.substr(header.find(":") + 1).strip_edges())',
|
|
575
|
+
'\t\telif lower.begins_with("authorization:"):',
|
|
576
|
+
'\t\t\tauthorized = header.substr(header.find(":") + 1).strip_edges() == "Bearer " + _token',
|
|
577
|
+
"\tif text.length() < body_start + content_length:",
|
|
578
|
+
"\t\treturn false",
|
|
579
|
+
"\tif not authorized:",
|
|
580
|
+
'\t\t_write_http_response(peer, 401, _failure("Adopted editor token is invalid.", ["adopted_editor_token_invalid"]))',
|
|
581
|
+
"\t\treturn true",
|
|
582
|
+
"\tvar request_text := text.substr(body_start, content_length)",
|
|
583
|
+
"\tvar request = JSON.parse_string(request_text)",
|
|
584
|
+
"\tif typeof(request) != TYPE_DICTIONARY:",
|
|
585
|
+
'\t\t_write_http_response(peer, 400, _failure("Invalid adopted editor request JSON.", ["request_json_invalid"]))',
|
|
586
|
+
"\t\treturn true",
|
|
587
|
+
"\t_write_http_response(peer, 200, _handle_adopted_request(request))",
|
|
588
|
+
"\treturn true",
|
|
589
|
+
"",
|
|
590
|
+
"func _handle_adopted_request(request: Dictionary) -> Dictionary:",
|
|
591
|
+
'\tif int(request.get("protocolVersion", 0)) != GDH_EDITOR_ADOPTION_PROTOCOL_VERSION:',
|
|
592
|
+
'\t\treturn _failure("Adopted editor protocol version mismatch.", ["adopted_editor_protocol_mismatch"])',
|
|
593
|
+
'\tvar target_identity = request.get("targetIdentity", {})',
|
|
594
|
+
'\tif typeof(target_identity) != TYPE_DICTIONARY or str(target_identity.get("identityKey", "")) != str(_target_identity().get("identityKey", "")):',
|
|
595
|
+
'\t\treturn _failure("Adopted editor target identity mismatch.", ["adopted_editor_identity_mismatch"])',
|
|
596
|
+
'\tvar operation = request.get("operation", {})',
|
|
597
|
+
"\tif typeof(operation) != TYPE_DICTIONARY:",
|
|
598
|
+
'\t\treturn _failure("Missing adopted editor operation payload.", ["operation_missing"])',
|
|
599
|
+
"\treturn run_editor_operation(operation)",
|
|
600
|
+
"",
|
|
601
|
+
"func _write_http_response(peer: StreamPeerTCP, status_code: int, body: Dictionary) -> void:",
|
|
602
|
+
"\tvar body_text := JSON.stringify(body)",
|
|
603
|
+
'\tvar status_text := "OK" if status_code == 200 else "Error"',
|
|
604
|
+
'\tvar response := "HTTP/1.1 %d %s\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\nConnection: close\\r\\n\\r\\n%s" % [status_code, status_text, body_text.to_utf8_buffer().size(), body_text]',
|
|
605
|
+
"\tpeer.put_data(response.to_utf8_buffer())",
|
|
606
|
+
"",
|
|
607
|
+
"func _write_heartbeat() -> void:",
|
|
608
|
+
"\tif _port <= 0:",
|
|
609
|
+
"\t\treturn",
|
|
610
|
+
"\tvar now := _now_iso()",
|
|
611
|
+
'\t_write_text_file(_heartbeat_path, now + "\\n")',
|
|
612
|
+
"\t_write_text_file(_metadata_path, JSON.stringify({",
|
|
613
|
+
'\t\t"protocolVersion": GDH_EDITOR_ADOPTION_PROTOCOL_VERSION,',
|
|
614
|
+
'\t\t"bridgeSurfaceVersion": GDH_EDITOR_BRIDGE_VERSION,',
|
|
615
|
+
'\t\t"targetIdentity": _target_identity(),',
|
|
616
|
+
'\t\t"pid": OS.get_process_id(),',
|
|
617
|
+
'\t\t"host": GDH_EDITOR_ADOPTION_HOST,',
|
|
618
|
+
'\t\t"port": _port,',
|
|
619
|
+
'\t\t"metadataPath": _metadata_path,',
|
|
620
|
+
'\t\t"tokenFilePath": _token_file_path,',
|
|
621
|
+
'\t\t"heartbeatPath": _heartbeat_path,',
|
|
622
|
+
'\t\t"startedAt": _started_at,',
|
|
623
|
+
'\t\t"lastHeartbeatAt": now,',
|
|
624
|
+
'\t}, "\\t") + "\\n")',
|
|
625
|
+
"",
|
|
626
|
+
"func _target_identity() -> Dictionary:",
|
|
627
|
+
'\tvar path_style := "win32" if OS.get_name() == "Windows" else "posix"',
|
|
628
|
+
"\tvar normalized_target := _normalize_identity_path(_target_root_path, path_style)",
|
|
629
|
+
"\tvar normalized_project := _normalize_identity_path(_godot_project_root_path, path_style)",
|
|
630
|
+
"\tvar normalized_state := _normalize_identity_path(_state_root_path, path_style)",
|
|
631
|
+
"\treturn {",
|
|
632
|
+
'\t\t"targetRootPath": _target_root_path,',
|
|
633
|
+
'\t\t"godotProjectRootPath": _godot_project_root_path,',
|
|
634
|
+
'\t\t"stateRootPath": _state_root_path,',
|
|
635
|
+
'\t\t"normalizedTargetRootPath": normalized_target,',
|
|
636
|
+
'\t\t"normalizedGodotProjectRootPath": normalized_project,',
|
|
637
|
+
'\t\t"normalizedStateRootPath": normalized_state,',
|
|
638
|
+
'\t\t"pathStyle": path_style,',
|
|
639
|
+
'\t\t"identityKey": "target=%s|godot=%s|state=%s|style=%s" % [normalized_target, normalized_project, normalized_state, path_style],',
|
|
640
|
+
"\t}",
|
|
641
|
+
"",
|
|
642
|
+
"func _read_or_create_token(token_path: String) -> String:",
|
|
643
|
+
"\tif FileAccess.file_exists(token_path):",
|
|
644
|
+
"\t\treturn FileAccess.get_file_as_string(token_path).strip_edges()",
|
|
645
|
+
"\tvar token := _generate_token()",
|
|
646
|
+
'\t_write_text_file(token_path, token + "\\n")',
|
|
647
|
+
"\treturn token",
|
|
648
|
+
"",
|
|
649
|
+
"func _generate_token() -> String:",
|
|
650
|
+
"\tvar crypto := Crypto.new()",
|
|
651
|
+
"\treturn crypto.generate_random_bytes(32).hex_encode()",
|
|
652
|
+
"",
|
|
653
|
+
"func _write_text_file(file_path: String, content: String) -> void:",
|
|
654
|
+
"\tDirAccess.make_dir_recursive_absolute(file_path.get_base_dir())",
|
|
655
|
+
"\tvar file := FileAccess.open(file_path, FileAccess.WRITE)",
|
|
656
|
+
"\tif file:",
|
|
657
|
+
"\t\tfile.store_string(content)",
|
|
658
|
+
"\t\tfile.close()",
|
|
659
|
+
"",
|
|
660
|
+
"func _find_target_root(project_root: String) -> String:",
|
|
661
|
+
"\tvar current := _normalize_absolute_path(project_root)",
|
|
662
|
+
"\tfor _index in range(0, 64):",
|
|
663
|
+
'\t\tif FileAccess.file_exists(current.path_join(".gdh/project.yaml")):',
|
|
664
|
+
"\t\t\treturn current",
|
|
665
|
+
"\t\tvar parent := current.get_base_dir()",
|
|
666
|
+
'\t\tif parent == current or parent == "":',
|
|
667
|
+
"\t\t\treturn _normalize_absolute_path(project_root)",
|
|
668
|
+
"\t\tcurrent = parent",
|
|
669
|
+
"\treturn _normalize_absolute_path(project_root)",
|
|
670
|
+
"",
|
|
671
|
+
"func _normalize_absolute_path(value: String) -> String:",
|
|
672
|
+
'\tvar normalized := value.simplify_path().trim_suffix("/").trim_suffix("\\\\")',
|
|
673
|
+
"\treturn normalized",
|
|
674
|
+
"",
|
|
675
|
+
"func _normalize_identity_path(value: String, path_style: String) -> String:",
|
|
676
|
+
"\tvar normalized := _normalize_absolute_path(value)",
|
|
677
|
+
'\tif path_style == "win32":',
|
|
678
|
+
'\t\treturn normalized.replace("/", "\\\\").to_lower()',
|
|
679
|
+
"\treturn normalized",
|
|
680
|
+
"",
|
|
681
|
+
"func _now_iso() -> String:",
|
|
682
|
+
'\treturn Time.get_datetime_string_from_system(true) + "Z"',
|
|
683
|
+
"",
|
|
684
|
+
renderEditorOperationCatalogGdscript().trimEnd(),
|
|
685
|
+
].join("\n"),
|
|
686
|
+
},
|
|
428
687
|
{
|
|
429
688
|
id: "bridge-registry-gd",
|
|
430
689
|
relativePath: normalizePath(path.join(runtimeBridge.addonPath, "registry", "bridge_registry.gd")),
|
|
@@ -439,18 +698,18 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
439
698
|
"static func list_entries() -> Array[Dictionary]:",
|
|
440
699
|
"\tvar entries: Array[Dictionary] = CoreEntries.list_entries()",
|
|
441
700
|
"\tfor project_entries in _load_project_entries():",
|
|
442
|
-
|
|
701
|
+
'\t\tif project_entries != null and project_entries.has_method("list_entries"):',
|
|
443
702
|
"\t\t\tentries.append_array(project_entries.list_entries())",
|
|
444
703
|
"\treturn entries",
|
|
445
704
|
"",
|
|
446
705
|
"static func invoke(runtime: Node, entry_id: String, input: Dictionary) -> Dictionary:",
|
|
447
706
|
"\tvar core_result := CoreEntries.invoke(runtime, entry_id, input)",
|
|
448
|
-
|
|
707
|
+
'\tif str(core_result.get("state", "")) != "unavailable":',
|
|
449
708
|
"\t\treturn core_result",
|
|
450
709
|
"\tfor project_entries in _load_project_entries():",
|
|
451
|
-
|
|
710
|
+
'\t\tif project_entries != null and project_entries.has_method("invoke"):',
|
|
452
711
|
"\t\t\tvar project_result: Dictionary = project_entries.invoke(runtime, entry_id, input)",
|
|
453
|
-
|
|
712
|
+
'\t\t\tif str(project_result.get("state", "")) != "unavailable":',
|
|
454
713
|
"\t\t\t\treturn project_result",
|
|
455
714
|
"\treturn core_result",
|
|
456
715
|
"",
|
|
@@ -477,10 +736,10 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
477
736
|
"\tdirectory.list_dir_begin()",
|
|
478
737
|
"\tvar file_name := directory.get_next()",
|
|
479
738
|
"\twhile not file_name.is_empty():",
|
|
480
|
-
|
|
739
|
+
'\t\tvar child_path := "%s/%s" % [directory_path, file_name]',
|
|
481
740
|
"\t\tif directory.current_is_dir():",
|
|
482
741
|
"\t\t\t_collect_project_entry_paths(child_path, paths)",
|
|
483
|
-
|
|
742
|
+
'\t\telif file_name.ends_with(".gd"):',
|
|
484
743
|
"\t\t\tpaths.append(child_path)",
|
|
485
744
|
"\t\tfile_name = directory.get_next()",
|
|
486
745
|
"\tdirectory.list_dir_end()",
|
|
@@ -713,7 +972,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
713
972
|
'\t\t"input.action.release":',
|
|
714
973
|
"\t\t\treturn _set_input_action(input, false)",
|
|
715
974
|
'\t\t"input.action.hold":',
|
|
716
|
-
|
|
975
|
+
'\t\t\treturn _result("unavailable", null, "input.action.hold is handled by the GDH runtime dispatch path.")',
|
|
717
976
|
'\t\t"input.event.send":',
|
|
718
977
|
"\t\t\treturn _send_input_event(input)",
|
|
719
978
|
'\t\t"ui.button.press":',
|
|
@@ -774,7 +1033,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
774
1033
|
'\tvar node_path: String = str(input.get("nodePath", ""))',
|
|
775
1034
|
'\tvar signal_name: String = str(input.get("signalName", ""))',
|
|
776
1035
|
"",
|
|
777
|
-
|
|
1036
|
+
"\tif node_path.is_empty() or signal_name.is_empty():",
|
|
778
1037
|
'\t\treturn _result("failed", null, "state.signal_observation.start requires nodePath and signalName.")',
|
|
779
1038
|
"",
|
|
780
1039
|
"\treturn runtime.start_signal_observation(node_path, signal_name)",
|
|
@@ -783,7 +1042,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
783
1042
|
'\tvar node_path: String = str(input.get("nodePath", ""))',
|
|
784
1043
|
'\tvar signal_name: String = str(input.get("signalName", ""))',
|
|
785
1044
|
"",
|
|
786
|
-
|
|
1045
|
+
"\tif node_path.is_empty() or signal_name.is_empty():",
|
|
787
1046
|
'\t\treturn _result("failed", null, "state.signal_observation.get requires nodePath and signalName.")',
|
|
788
1047
|
"",
|
|
789
1048
|
"\treturn runtime.get_signal_observation_snapshot(node_path, signal_name)",
|
|
@@ -792,7 +1051,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
792
1051
|
'\tvar node_path: String = str(input.get("nodePath", ""))',
|
|
793
1052
|
'\tvar property_name: String = str(input.get("property", ""))',
|
|
794
1053
|
"",
|
|
795
|
-
|
|
1054
|
+
"\tif node_path.is_empty() or property_name.is_empty():",
|
|
796
1055
|
'\t\treturn _result("failed", null, "state.node_property.get requires nodePath and property.")',
|
|
797
1056
|
"",
|
|
798
1057
|
"\tvar node: Node = runtime.resolve_node_by_path(node_path)",
|
|
@@ -1103,7 +1362,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
1103
1362
|
'\tvar requested_class_name: String = str(filters.get("className", ""))',
|
|
1104
1363
|
"",
|
|
1105
1364
|
"\t_append_node_if_matches(search_root, name_contains, requested_class_name, limit, results)",
|
|
1106
|
-
|
|
1365
|
+
'\tfor node in search_root.find_children("*", requested_class_name, true, false):',
|
|
1107
1366
|
"\t\tif results.size() >= limit:",
|
|
1108
1367
|
"\t\t\tbreak",
|
|
1109
1368
|
"\t\t_append_node_if_matches(node, name_contains, requested_class_name, limit, results)",
|
|
@@ -1118,7 +1377,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
1118
1377
|
"",
|
|
1119
1378
|
"func start_signal_observation(node_path: String, signal_name: String) -> Dictionary:",
|
|
1120
1379
|
'\tvar entry_id := "state.signal_observation.start"',
|
|
1121
|
-
|
|
1380
|
+
"\tvar observation_key := _build_signal_observation_key(node_path, signal_name)",
|
|
1122
1381
|
"",
|
|
1123
1382
|
"\tif _signal_observations.has(observation_key):",
|
|
1124
1383
|
"\t\treturn _bridge_ok(_duplicate_signal_observation(_signal_observations[observation_key]))",
|
|
@@ -1150,7 +1409,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
1150
1409
|
"",
|
|
1151
1410
|
"func get_signal_observation_snapshot(node_path: String, signal_name: String) -> Dictionary:",
|
|
1152
1411
|
'\tvar entry_id := "state.signal_observation.get"',
|
|
1153
|
-
|
|
1412
|
+
"\tvar observation_key := _build_signal_observation_key(node_path, signal_name)",
|
|
1154
1413
|
"",
|
|
1155
1414
|
"\tif not _signal_observations.has(observation_key):",
|
|
1156
1415
|
'\t\treturn _bridge_result("unavailable", null, "%s has not started observing %s on node %s." % [entry_id, signal_name, node_path])',
|
|
@@ -1195,11 +1454,11 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
1195
1454
|
"\targ2: Variant = null,",
|
|
1196
1455
|
"\targ3: Variant = null,",
|
|
1197
1456
|
") -> void:",
|
|
1198
|
-
|
|
1457
|
+
"\tvar observation_key := _build_signal_observation_key(node_path, signal_name)",
|
|
1199
1458
|
"\tif not _signal_observations.has(observation_key):",
|
|
1200
1459
|
"\t\treturn",
|
|
1201
1460
|
"",
|
|
1202
|
-
|
|
1461
|
+
"\tvar snapshot: Dictionary = _signal_observations[observation_key]",
|
|
1203
1462
|
'\tsnapshot["count"] = int(snapshot.get("count", 0)) + 1',
|
|
1204
1463
|
'\tsnapshot["lastObservedAt"] = Time.get_datetime_string_from_system(true, true)',
|
|
1205
1464
|
'\tsnapshot["lastPayload"] = _normalize_signal_payload(arg0, arg1, arg2, arg3)',
|
|
@@ -1247,9 +1506,9 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
1247
1506
|
"\tif has_duration == has_physics_frames:",
|
|
1248
1507
|
'\t\treturn _bridge_result("failed", null, "input.action.hold requires exactly one of durationMs or physicsFrames.")',
|
|
1249
1508
|
"",
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1509
|
+
"\tvar started_at_ms := Time.get_ticks_msec()",
|
|
1510
|
+
"\tvar duration_ms := 0",
|
|
1511
|
+
"\tvar physics_frames := 0",
|
|
1253
1512
|
'\tvar mode := "durationMs" if has_duration else "physicsFrames"',
|
|
1254
1513
|
"",
|
|
1255
1514
|
"\tInput.action_press(action)",
|
|
@@ -1262,7 +1521,7 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
1262
1521
|
"\t\t\tawait get_tree().physics_frame",
|
|
1263
1522
|
"\tInput.action_release(action)",
|
|
1264
1523
|
"",
|
|
1265
|
-
|
|
1524
|
+
"\tvar finished_at_ms := Time.get_ticks_msec()",
|
|
1266
1525
|
"\treturn _bridge_ok(",
|
|
1267
1526
|
"\t\t{",
|
|
1268
1527
|
'\t\t\t"action": action,',
|
|
@@ -1377,9 +1636,9 @@ function renderManagedBridgeFiles(runtimeBridge) {
|
|
|
1377
1636
|
"\tfile.store_string(JSON.stringify(payload))",
|
|
1378
1637
|
"\tfile.close()",
|
|
1379
1638
|
"",
|
|
1380
|
-
|
|
1639
|
+
'\tif state == "captured":',
|
|
1381
1640
|
"\t\treturn _bridge_ok(payload)",
|
|
1382
|
-
|
|
1641
|
+
"\treturn _bridge_result(state, payload, summary)",
|
|
1383
1642
|
"",
|
|
1384
1643
|
"func _accept_pending_connection() -> void:",
|
|
1385
1644
|
"\twhile _server.is_connection_available():",
|
|
@@ -1505,7 +1764,7 @@ function resolveRuntimeBridgeState(input) {
|
|
|
1505
1764
|
if (input.hasDrift) {
|
|
1506
1765
|
return "drifted";
|
|
1507
1766
|
}
|
|
1508
|
-
if (!input.autoloadRegistered) {
|
|
1767
|
+
if (!input.autoloadRegistered || !input.editorPluginRegistered) {
|
|
1509
1768
|
return input.hasPresentManagedFiles ? "drifted" : "needs_install";
|
|
1510
1769
|
}
|
|
1511
1770
|
if (input.hasMissing) {
|
|
@@ -1517,18 +1776,21 @@ function summarizeRuntimeBridgeStatus(input) {
|
|
|
1517
1776
|
if (input.state === "ready") {
|
|
1518
1777
|
return input.compatibility === "migration_available"
|
|
1519
1778
|
? "Runtime bridge files are installed and healthy, but the project config still needs the current runtime_bridge contract migrated."
|
|
1520
|
-
: "Runtime bridge files,
|
|
1779
|
+
: "Runtime and editor bridge files, project.godot registrations, and managed manifest all match the current GDH baseline.";
|
|
1521
1780
|
}
|
|
1522
1781
|
if (input.state === "needs_install") {
|
|
1523
|
-
return `
|
|
1782
|
+
return `GDH bridge is configured for "${input.runtimeBridge.projectPath}" but the managed addon files or project.godot registrations have not been installed yet.`;
|
|
1524
1783
|
}
|
|
1525
1784
|
if (input.state === "drifted") {
|
|
1526
1785
|
if (!input.autoloadRegistered) {
|
|
1527
|
-
return "
|
|
1786
|
+
return "GDH bridge addon files are installed, but GDHBridge is not registered as an autoload yet.";
|
|
1787
|
+
}
|
|
1788
|
+
if (!input.editorPluginRegistered) {
|
|
1789
|
+
return "GDH bridge addon files are installed, but the GDH editor plugin is not enabled in project.godot yet.";
|
|
1528
1790
|
}
|
|
1529
1791
|
return input.customEntryCount > 0
|
|
1530
|
-
? "
|
|
1531
|
-
: "
|
|
1792
|
+
? "GDH bridge lifecycle detected drift in GDH-managed files or project.godot registrations while preserving project-owned bridge entries."
|
|
1793
|
+
: "GDH bridge lifecycle detected drift in GDH-managed files or project.godot registrations.";
|
|
1532
1794
|
}
|
|
1533
1795
|
return "Runtime bridge lifecycle is blocked and cannot inspect the configured target project safely.";
|
|
1534
1796
|
}
|
|
@@ -1548,13 +1810,79 @@ function hasAutoloadRegistration(content, runtimeBridge) {
|
|
|
1548
1810
|
const normalizedValue = value.replace(/^"\*/, "").replace(/^"/, "").replace(/"$/, "");
|
|
1549
1811
|
return normalizedValue === runtimeBridge.autoloadScriptPath;
|
|
1550
1812
|
}
|
|
1813
|
+
function hasEditorPluginRegistration(content, runtimeBridge) {
|
|
1814
|
+
return readEditorPluginRegistrations(content).includes(editorPluginResourcePath(runtimeBridge));
|
|
1815
|
+
}
|
|
1816
|
+
function addEditorPluginRegistration(content, runtimeBridge) {
|
|
1817
|
+
const pluginPath = editorPluginResourcePath(runtimeBridge);
|
|
1818
|
+
const lines = content.split(/\r?\n/);
|
|
1819
|
+
const section = findSection(lines, "editor_plugins");
|
|
1820
|
+
const registrations = readEditorPluginRegistrations(content);
|
|
1821
|
+
const nextRegistrations = registrations.includes(pluginPath)
|
|
1822
|
+
? registrations
|
|
1823
|
+
: [...registrations, pluginPath].sort();
|
|
1824
|
+
const entryLine = renderEditorPluginsEnabledLine(nextRegistrations);
|
|
1825
|
+
if (section === null) {
|
|
1826
|
+
const trailingBlank = lines.length > 0 && lines[lines.length - 1] === "" ? 1 : 0;
|
|
1827
|
+
const baseLines = trailingBlank ? lines.slice(0, -1) : lines;
|
|
1828
|
+
const needsSeparator = baseLines.length > 0 && baseLines[baseLines.length - 1]?.trim() !== "";
|
|
1829
|
+
return `${[
|
|
1830
|
+
...baseLines,
|
|
1831
|
+
...(needsSeparator ? [""] : []),
|
|
1832
|
+
"[editor_plugins]",
|
|
1833
|
+
"",
|
|
1834
|
+
entryLine,
|
|
1835
|
+
].join("\n")}\n`;
|
|
1836
|
+
}
|
|
1837
|
+
const replacementIndex = lines.findIndex((line, index) => index > section.start && index < section.end && line.trim().startsWith("enabled="));
|
|
1838
|
+
const nextLines = replacementIndex >= 0
|
|
1839
|
+
? lines.map((line, index) => (index === replacementIndex ? entryLine : line))
|
|
1840
|
+
: [...lines.slice(0, section.end), entryLine, ...lines.slice(section.end)];
|
|
1841
|
+
return `${nextLines.join("\n").replace(/\n+$/, "\n")}`;
|
|
1842
|
+
}
|
|
1843
|
+
function removeEditorPluginRegistration(content, runtimeBridge) {
|
|
1844
|
+
const pluginPath = editorPluginResourcePath(runtimeBridge);
|
|
1845
|
+
const lines = content.split(/\r?\n/);
|
|
1846
|
+
const section = findSection(lines, "editor_plugins");
|
|
1847
|
+
if (section === null)
|
|
1848
|
+
return `${lines.join("\n").replace(/\n+$/, "\n")}`;
|
|
1849
|
+
const registrations = readEditorPluginRegistrations(content).filter((entry) => entry !== pluginPath);
|
|
1850
|
+
const entryLine = renderEditorPluginsEnabledLine(registrations);
|
|
1851
|
+
const replacementIndex = lines.findIndex((line, index) => index > section.start && index < section.end && line.trim().startsWith("enabled="));
|
|
1852
|
+
if (replacementIndex < 0)
|
|
1853
|
+
return `${lines.join("\n").replace(/\n+$/, "\n")}`;
|
|
1854
|
+
const nextLines = lines.map((line, index) => (index === replacementIndex ? entryLine : line));
|
|
1855
|
+
return `${nextLines.join("\n").replace(/\n+$/, "\n")}`;
|
|
1856
|
+
}
|
|
1857
|
+
function readEditorPluginRegistrations(content) {
|
|
1858
|
+
const lines = content.split(/\r?\n/);
|
|
1859
|
+
const section = findSection(lines, "editor_plugins");
|
|
1860
|
+
if (section === null)
|
|
1861
|
+
return [];
|
|
1862
|
+
for (let index = section.start + 1; index < section.end; index += 1) {
|
|
1863
|
+
const line = lines[index]?.trim() ?? "";
|
|
1864
|
+
if (line.startsWith("enabled=")) {
|
|
1865
|
+
const entries = [...line.matchAll(/"([^"]+)"/gu)].map((match) => match[1] ?? "");
|
|
1866
|
+
return entries.filter((entry) => entry.length > 0);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return [];
|
|
1870
|
+
}
|
|
1871
|
+
function renderEditorPluginsEnabledLine(entries) {
|
|
1872
|
+
return `enabled=PackedStringArray(${entries.map((entry) => JSON.stringify(entry)).join(", ")})`;
|
|
1873
|
+
}
|
|
1874
|
+
function editorPluginResourcePath(runtimeBridge) {
|
|
1875
|
+
return `res://${normalizePath(path.join(runtimeBridge.addonPath, "plugin.cfg"))}`;
|
|
1876
|
+
}
|
|
1551
1877
|
function removeAutoloadRegistration(content, runtimeBridge) {
|
|
1552
1878
|
const lines = content.split(/\r?\n/);
|
|
1553
1879
|
const section = findSection(lines, "autoload");
|
|
1554
1880
|
if (section === null) {
|
|
1555
1881
|
return `${lines.join("\n").replace(/\n+$/, "\n")}`;
|
|
1556
1882
|
}
|
|
1557
|
-
const nextLines = lines.filter((_, index) => !(index > section.start &&
|
|
1883
|
+
const nextLines = lines.filter((_, index) => !(index > section.start &&
|
|
1884
|
+
index < section.end &&
|
|
1885
|
+
lines[index]?.startsWith(`${runtimeBridge.autoloadName}=`)));
|
|
1558
1886
|
return `${nextLines.join("\n").replace(/\n+$/, "\n")}`;
|
|
1559
1887
|
}
|
|
1560
1888
|
/**
|
|
@@ -1585,12 +1913,7 @@ function addAutoloadRegistration(content, runtimeBridge) {
|
|
|
1585
1913
|
// section if the previous content does not already end with one.
|
|
1586
1914
|
const baseLines = trailingBlank ? lines.slice(0, -1) : lines;
|
|
1587
1915
|
const needsSeparator = baseLines.length > 0 && baseLines[baseLines.length - 1]?.trim() !== "";
|
|
1588
|
-
const sectionLines = [
|
|
1589
|
-
...(needsSeparator ? [""] : []),
|
|
1590
|
-
"[autoload]",
|
|
1591
|
-
"",
|
|
1592
|
-
entryLine,
|
|
1593
|
-
];
|
|
1916
|
+
const sectionLines = [...(needsSeparator ? [""] : []), "[autoload]", "", entryLine];
|
|
1594
1917
|
return `${[...baseLines, ...sectionLines].join("\n")}\n`;
|
|
1595
1918
|
}
|
|
1596
1919
|
// Section exists. Replace any existing entry for this autoloadName, or
|