@defold-typescript/cli 0.5.1 → 0.5.3

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/dist/index.js CHANGED
@@ -295,6 +295,169 @@ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFi
295
295
  import * as path7 from "node:path";
296
296
  import { fileURLToPath as fileURLToPath2 } from "node:url";
297
297
 
298
+ // src/debug-launcher.ts
299
+ var PLATFORM_TARGETS = {
300
+ darwin: { enginePlatform: "x86_64-darwin", buildFolder: "x86_64-osx", executable: "dmengine" },
301
+ linux: { enginePlatform: "x86_64-linux", buildFolder: "x86_64-linux", executable: "dmengine" },
302
+ win32: {
303
+ enginePlatform: "x86_64-win32",
304
+ buildFolder: "x86_64-win32",
305
+ executable: "dmengine.exe"
306
+ }
307
+ };
308
+ var ENGINE_INFO_URL = "https://d.defold.com/stable/info.json";
309
+ var ENGINE_ARCHIVE_BASE = "https://d.defold.com/archive/stable";
310
+ var DEBUG_LAUNCHER_REL = ".vscode/defold-debug.ts";
311
+ function debugLaunchConfig() {
312
+ return {
313
+ name: "Defold: Debug (TypeScript)",
314
+ type: "lua-local",
315
+ request: "launch",
316
+ stopOnEntry: false,
317
+ verbose: false,
318
+ internalConsoleOptions: "openOnSessionStart",
319
+ program: { command: "bun" },
320
+ args: [DEBUG_LAUNCHER_REL]
321
+ };
322
+ }
323
+ var VSCODE_LAUNCH_CONTENT = {
324
+ version: "0.2.0",
325
+ configurations: [debugLaunchConfig()]
326
+ };
327
+ function renderDebugLauncher() {
328
+ const targets = JSON.stringify(PLATFORM_TARGETS, null, 2);
329
+ return `import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
330
+ import * as path from "node:path";
331
+
332
+ // Windows only: paths to OpenAL32.dll and wrap_oal.dll from your Defold SDK
333
+ // (defoldsdk/ext/lib/x86_64-win32/). Leave empty on macOS/Linux.
334
+ const WINDOWS_OPENAL32_PATH = "";
335
+ const WINDOWS_WRAPOAL_PATH = "";
336
+
337
+ interface EngineTarget {
338
+ enginePlatform: string;
339
+ buildFolder: string;
340
+ executable: string;
341
+ }
342
+
343
+ const PLATFORM_TARGETS: Record<string, EngineTarget> = ${targets};
344
+
345
+ const ENGINE_INFO_URL = "${ENGINE_INFO_URL}";
346
+ const ENGINE_ARCHIVE_BASE = "${ENGINE_ARCHIVE_BASE}";
347
+
348
+ const target = PLATFORM_TARGETS[process.platform];
349
+ if (!target) {
350
+ console.error(\`Unsupported platform: \${process.platform}\`);
351
+ process.exit(1);
352
+ }
353
+
354
+ const here = path.dirname(new URL(import.meta.url).pathname);
355
+ const stockEnginePath = path.join(here, target.executable);
356
+
357
+ if (!existsSync(stockEnginePath)) {
358
+ const info = (await (await fetch(ENGINE_INFO_URL)).json()) as { sha1: string };
359
+ const url = \`\${ENGINE_ARCHIVE_BASE}/\${info.sha1}/engine/\${target.enginePlatform}/\${target.executable}\`;
360
+ console.log(\`Fetching \${url}\`);
361
+ const res = await fetch(url);
362
+ if (!res.ok) {
363
+ console.error(\`Engine download failed: \${res.status} \${res.statusText}\`);
364
+ process.exit(1);
365
+ }
366
+ await Bun.write(stockEnginePath, res);
367
+ }
368
+
369
+ const buildFolder = path.join("build", target.buildFolder);
370
+ const buildEnginePath = path.join(buildFolder, target.executable);
371
+ let enginePath = existsSync(buildEnginePath) ? buildEnginePath : stockEnginePath;
372
+
373
+ if (process.platform === "win32" && enginePath === buildEnginePath) {
374
+ for (const [src, name] of [
375
+ [WINDOWS_OPENAL32_PATH, "OpenAL32.dll"],
376
+ [WINDOWS_WRAPOAL_PATH, "wrap_oal.dll"],
377
+ ] as const) {
378
+ const dest = path.join(buildFolder, name);
379
+ if (src && !existsSync(dest)) {
380
+ copyFileSync(src, dest);
381
+ }
382
+ }
383
+ }
384
+
385
+ // macOS: a build engine launched in place attaches to the editor process; copy
386
+ // it aside first so it runs standalone.
387
+ if (process.platform === "darwin" && enginePath === buildEnginePath) {
388
+ const tempEngine = path.join(buildFolder, "temp", target.executable);
389
+ mkdirSync(path.dirname(tempEngine), { recursive: true });
390
+ copyFileSync(buildEnginePath, tempEngine);
391
+ enginePath = tempEngine;
392
+ }
393
+
394
+ if (process.platform !== "win32") {
395
+ chmodSync(enginePath, 0o755);
396
+ }
397
+
398
+ const projectc = path.join("build", "default", "game.projectc");
399
+ console.log(\`Launching \${enginePath} \${projectc}\`);
400
+ const proc = Bun.spawn([enginePath, projectc], {
401
+ stdio: ["inherit", "inherit", "inherit"],
402
+ });
403
+ process.exit(await proc.exited);
404
+ `;
405
+ }
406
+ var DEBUG_LAUNCHER_SOURCE = renderDebugLauncher();
407
+
408
+ // src/mise-scaffold.ts
409
+ var MANAGED_MARKER = "# managed by @defold-typescript";
410
+ var MISE_TASKS_TOML = `${MANAGED_MARKER}
411
+ [tasks."defold-typescript:build"]
412
+ description = "Build the TypeScript sources with the installed defold-typescript CLI"
413
+ run = "bunx --no-install defold-typescript build"
414
+
415
+ ${MANAGED_MARKER}
416
+ [tasks."defold-typescript:watch"]
417
+ description = "Watch and rebuild the TypeScript sources with the installed defold-typescript CLI"
418
+ run = "bunx --no-install defold-typescript watch"
419
+
420
+ ${MANAGED_MARKER}
421
+ [tasks."defold-typescript:upgrade"]
422
+ description = "Upgrade the defold-typescript CLI to its latest release and re-pin the types dependency"
423
+ run = ["bunx @defold-typescript/cli@latest init --force", "bun install"]
424
+ `;
425
+ function stripManagedBlocks(text) {
426
+ const lines = text.split(`
427
+ `);
428
+ const out = [];
429
+ let i = 0;
430
+ while (i < lines.length) {
431
+ const line = lines[i] ?? "";
432
+ if (line.trim() === MANAGED_MARKER) {
433
+ i += 1;
434
+ while (i < lines.length && (lines[i] ?? "").trim() !== "") {
435
+ i += 1;
436
+ }
437
+ if (i < lines.length) {
438
+ i += 1;
439
+ }
440
+ continue;
441
+ }
442
+ out.push(line);
443
+ i += 1;
444
+ }
445
+ return out.join(`
446
+ `);
447
+ }
448
+ function mergeMiseToml(existing) {
449
+ if (existing === undefined) {
450
+ return MISE_TASKS_TOML;
451
+ }
452
+ const userContent = stripManagedBlocks(existing).replace(/\s*$/, "");
453
+ if (userContent === "") {
454
+ return MISE_TASKS_TOML;
455
+ }
456
+ return `${userContent}
457
+
458
+ ${MISE_TASKS_TOML}`;
459
+ }
460
+
298
461
  // src/script-kind.ts
299
462
  var DEFAULT_TYPES_ENTRYPOINT = "@defold-typescript/types";
300
463
  var KIND_BY_EXT = {
@@ -406,12 +569,118 @@ var BIOME_JSON_CONTENT = {
406
569
  }
407
570
  };
408
571
  var VSCODE_EXTENSIONS_CONTENT = {
409
- recommendations: ["sumneko.lua", "astronachos.defold"],
572
+ recommendations: ["sumneko.lua", "astronachos.defold", "tomblind.local-lua-debugger-vscode"],
410
573
  unwantedRecommendations: ["johnnymorganz.luau-lsp"]
411
574
  };
412
575
  var VSCODE_SETTINGS_CONTENT = {
413
576
  "Lua.workspace.ignoreDir": ["src"]
414
577
  };
578
+ var HOOK_COMMENTS = {
579
+ init: "Initialize the component and return its state.",
580
+ update: "Update the component every frame; `dt` is the time step.",
581
+ fixed_update: "Update at the fixed physics time step.",
582
+ late_update: "Update every frame after `update`.",
583
+ on_message: "Handle an incoming message.",
584
+ on_input: "Handle input once input focus is acquired.",
585
+ final: "Clean up when the component is deleted.",
586
+ on_reload: "React to a hot reload of this script."
587
+ };
588
+ var HOOK_SIGNATURES = {
589
+ init: "",
590
+ update: "self, dt",
591
+ fixed_update: "self, dt",
592
+ late_update: "self, dt",
593
+ on_message: "self, message_id, message, sender",
594
+ on_input: "self, action_id, action",
595
+ final: "self",
596
+ on_reload: "self"
597
+ };
598
+ var SNIPPET_HOOK_ORDER = Object.keys(HOOK_SIGNATURES);
599
+ function hookLines(includeOnInput, startTabStop) {
600
+ const lines = [];
601
+ let tabStop = startTabStop;
602
+ for (const hook of SNIPPET_HOOK_ORDER) {
603
+ if (hook === "init") {
604
+ continue;
605
+ }
606
+ if (hook === "on_input" && !includeOnInput) {
607
+ continue;
608
+ }
609
+ lines.push(` // ${HOOK_COMMENTS[hook]}`);
610
+ lines.push(` ${hook}(${HOOK_SIGNATURES[hook]}) {$${tabStop}},`);
611
+ tabStop += 1;
612
+ }
613
+ return lines;
614
+ }
615
+ function inlineSnippetBody(factory, includeOnInput) {
616
+ return [
617
+ `import { ${factory} } from "@defold-typescript/types";`,
618
+ "",
619
+ `export const script = ${factory}({`,
620
+ ` // ${HOOK_COMMENTS.init}`,
621
+ " init() {",
622
+ " return { $0 };",
623
+ " },",
624
+ ...hookLines(includeOnInput, 1),
625
+ "});"
626
+ ];
627
+ }
628
+ function typedSnippetBody(factory, includeOnInput) {
629
+ return [
630
+ `import { ${factory} } from "@defold-typescript/types";`,
631
+ "",
632
+ "type Self = {",
633
+ " // Your script's state goes here.",
634
+ " $1",
635
+ "};",
636
+ "",
637
+ `export const script = ${factory}<Self>({`,
638
+ ` // ${HOOK_COMMENTS.init}`,
639
+ " init(): Self {",
640
+ " return { $0 };",
641
+ " },",
642
+ ...hookLines(includeOnInput, 2),
643
+ "});"
644
+ ];
645
+ }
646
+ var VSCODE_SNIPPETS_CONTENT = {
647
+ "Defold script (inferred self)": {
648
+ scope: "typescript",
649
+ prefix: "defold-script",
650
+ body: inlineSnippetBody("defineScript", true),
651
+ description: "Empty Defold script; state inferred from init's return."
652
+ },
653
+ "Defold script (typed self)": {
654
+ scope: "typescript",
655
+ prefix: "defold-script-typed",
656
+ body: typedSnippetBody("defineScript", true),
657
+ description: "Empty Defold script with an explicit Self type."
658
+ },
659
+ "Defold GUI script (inferred self)": {
660
+ scope: "typescript",
661
+ prefix: "defold-gui",
662
+ body: inlineSnippetBody("defineGuiScript", true),
663
+ description: "Empty Defold GUI script; state inferred from init's return."
664
+ },
665
+ "Defold GUI script (typed self)": {
666
+ scope: "typescript",
667
+ prefix: "defold-gui-typed",
668
+ body: typedSnippetBody("defineGuiScript", true),
669
+ description: "Empty Defold GUI script with an explicit Self type."
670
+ },
671
+ "Defold render script (inferred self)": {
672
+ scope: "typescript",
673
+ prefix: "defold-render",
674
+ body: inlineSnippetBody("defineRenderScript", false),
675
+ description: "Empty Defold render script; state inferred from init's return."
676
+ },
677
+ "Defold render script (typed self)": {
678
+ scope: "typescript",
679
+ prefix: "defold-render-typed",
680
+ body: typedSnippetBody("defineRenderScript", false),
681
+ description: "Empty Defold render script with an explicit Self type."
682
+ }
683
+ };
415
684
  var MAIN_TS_CONTENT = `export function init(): void {
416
685
  const start = vmath.vector3(0, 0, 0);
417
686
  msg.post("main:/hero", "spawn", { start });
@@ -485,6 +754,12 @@ function writeBiome(cwd, written) {
485
754
  writeJson(biomePath, BIOME_JSON_CONTENT);
486
755
  written.push("biome.json");
487
756
  }
757
+ function writeMiseTasks(cwd, written) {
758
+ const misePath = path7.join(cwd, "mise.toml");
759
+ const existing = existsSync2(misePath) ? readFileSync6(misePath, "utf8") : undefined;
760
+ writeFileSync2(misePath, mergeMiseToml(existing));
761
+ written.push("mise.toml");
762
+ }
488
763
  function parseJsonc(text) {
489
764
  let out = "";
490
765
  let inString = false;
@@ -586,6 +861,59 @@ function writeVscodeSettings(cwd, written) {
586
861
  writeJson(filePath, VSCODE_SETTINGS_CONTENT);
587
862
  written.push(".vscode/settings.json");
588
863
  }
864
+ function writeVscodeSnippets(cwd, written) {
865
+ const dir = path7.join(cwd, ".vscode");
866
+ const filePath = path7.join(dir, "defold-typescript.code-snippets");
867
+ if (existsSync2(filePath)) {
868
+ const existing = readVscodeJson(filePath);
869
+ if (existing === null) {
870
+ return;
871
+ }
872
+ for (const [key, snippet] of Object.entries(VSCODE_SNIPPETS_CONTENT)) {
873
+ if (!(key in existing)) {
874
+ existing[key] = snippet;
875
+ }
876
+ }
877
+ writeJson(filePath, existing);
878
+ return;
879
+ }
880
+ mkdirSync2(dir, { recursive: true });
881
+ writeJson(filePath, VSCODE_SNIPPETS_CONTENT);
882
+ written.push(".vscode/defold-typescript.code-snippets");
883
+ }
884
+ function writeVscodeLaunch(cwd, written) {
885
+ const dir = path7.join(cwd, ".vscode");
886
+ const filePath = path7.join(dir, "launch.json");
887
+ const ours = debugLaunchConfig();
888
+ if (existsSync2(filePath)) {
889
+ const existing = readVscodeJson(filePath);
890
+ if (existing === null) {
891
+ return;
892
+ }
893
+ const configs = Array.isArray(existing.configurations) ? [...existing.configurations] : [];
894
+ const names = new Set(configs.map((c) => isJsonObject(c) ? c.name : undefined));
895
+ if (!names.has(ours.name)) {
896
+ configs.push(ours);
897
+ }
898
+ existing.configurations = configs;
899
+ existing.version ??= VSCODE_LAUNCH_CONTENT.version;
900
+ writeJson(filePath, existing);
901
+ return;
902
+ }
903
+ mkdirSync2(dir, { recursive: true });
904
+ writeJson(filePath, VSCODE_LAUNCH_CONTENT);
905
+ written.push(".vscode/launch.json");
906
+ }
907
+ function writeVscodeDebugLauncher(cwd, written) {
908
+ const dir = path7.join(cwd, ".vscode");
909
+ const filePath = path7.join(dir, "defold-debug.ts");
910
+ if (existsSync2(filePath)) {
911
+ return;
912
+ }
913
+ mkdirSync2(dir, { recursive: true });
914
+ writeFileSync2(filePath, DEBUG_LAUNCHER_SOURCE);
915
+ written.push(".vscode/defold-debug.ts");
916
+ }
589
917
  function writeTsSurface(cwd, written, force = false) {
590
918
  mkdirSync2(path7.join(cwd, "src"), { recursive: true });
591
919
  writeFileSync2(path7.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
@@ -627,8 +955,12 @@ function writeTsSurface(cwd, written, force = false) {
627
955
  writeGitignore(cwd);
628
956
  written.push(".gitignore");
629
957
  writeBiome(cwd, written);
958
+ writeMiseTasks(cwd, written);
630
959
  writeVscodeExtensions(cwd, written);
631
960
  writeVscodeSettings(cwd, written);
961
+ writeVscodeSnippets(cwd, written);
962
+ writeVscodeLaunch(cwd, written);
963
+ writeVscodeDebugLauncher(cwd, written);
632
964
  return selectScriptKind(kinds);
633
965
  }
634
966
  function runNewProjectInit(cwd, force = false) {
@@ -711,7 +1043,7 @@ function materializeApiSurface(opts) {
711
1043
  const excluded = excludedModulesForKind(opts.scriptKind ?? null);
712
1044
  const sources = listDts(sourceGeneratedDir).filter((file) => file !== "index.d.ts").filter((file) => !excluded.has(file.replace(/\.d\.ts$/, "")));
713
1045
  const srcDir = path8.resolve(sourceGeneratedDir, "..", "src");
714
- const overloads = ["msg-overloads.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path8.join(srcDir, file)));
1046
+ const overloads = ["msg-overloads.d.ts", "message-guard.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path8.join(srcDir, file)));
715
1047
  const coreTypesSrc = path8.join(srcDir, "core-types.ts");
716
1048
  const includeCoreTypes = overloads.length > 0 && existsSync3(coreTypesSrc);
717
1049
  const engineGlobalsSrc = path8.join(srcDir, "engine-globals.d.ts");
@@ -786,12 +1118,16 @@ function ensureMaterializedReference(cwd, materializedDir) {
786
1118
  const tsconfigPath = path8.join(cwd, "tsconfig.json");
787
1119
  if (existsSync3(tsconfigPath)) {
788
1120
  const tsconfig = JSON.parse(readFileSync7(tsconfigPath, "utf8"));
789
- tsconfig.compilerOptions = {
790
- ...tsconfig.compilerOptions ?? {},
791
- typeRoots: [MATERIALIZED_ROOT],
792
- types: [surfaceId]
793
- };
794
- writeJson2(tsconfigPath, tsconfig);
1121
+ const current = tsconfig.compilerOptions ?? {};
1122
+ const alreadyRepointed = JSON.stringify(current.typeRoots) === JSON.stringify([MATERIALIZED_ROOT]) && JSON.stringify(current.types) === JSON.stringify([surfaceId]);
1123
+ if (!alreadyRepointed) {
1124
+ tsconfig.compilerOptions = {
1125
+ ...current,
1126
+ typeRoots: [MATERIALIZED_ROOT],
1127
+ types: [surfaceId]
1128
+ };
1129
+ writeJson2(tsconfigPath, tsconfig);
1130
+ }
795
1131
  }
796
1132
  ensureGitignoreLine(cwd, `${MATERIALIZED_ROOT}/`);
797
1133
  }
@@ -0,0 +1,2 @@
1
+ export declare const MISE_TASKS_TOML = "# managed by @defold-typescript\n[tasks.\"defold-typescript:build\"]\ndescription = \"Build the TypeScript sources with the installed defold-typescript CLI\"\nrun = \"bunx --no-install defold-typescript build\"\n\n# managed by @defold-typescript\n[tasks.\"defold-typescript:watch\"]\ndescription = \"Watch and rebuild the TypeScript sources with the installed defold-typescript CLI\"\nrun = \"bunx --no-install defold-typescript watch\"\n\n# managed by @defold-typescript\n[tasks.\"defold-typescript:upgrade\"]\ndescription = \"Upgrade the defold-typescript CLI to its latest release and re-pin the types dependency\"\nrun = [\"bunx @defold-typescript/cli@latest init --force\", \"bun install\"]\n";
2
+ export declare function mergeMiseToml(existing?: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "End-user CLI for scaffolding and building Defold projects written in TypeScript.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@defold-typescript/transpiler": "0.5.1",
35
- "@defold-typescript/types": "0.5.1"
34
+ "@defold-typescript/transpiler": "0.5.3",
35
+ "@defold-typescript/types": "0.5.3"
36
36
  }
37
37
  }
@@ -0,0 +1,165 @@
1
+ import * as path from "node:path";
2
+
3
+ export interface EngineTarget {
4
+ readonly enginePlatform: string;
5
+ readonly buildFolder: string;
6
+ readonly executable: string;
7
+ }
8
+
9
+ // `enginePlatform` keys the d.defold.com download path; `buildFolder` keys the
10
+ // native-extension build output. They diverge on macOS (`x86_64-darwin` vs
11
+ // `x86_64-osx`), which is why they are tracked separately.
12
+ const PLATFORM_TARGETS: Record<string, EngineTarget> = {
13
+ darwin: { enginePlatform: "x86_64-darwin", buildFolder: "x86_64-osx", executable: "dmengine" },
14
+ linux: { enginePlatform: "x86_64-linux", buildFolder: "x86_64-linux", executable: "dmengine" },
15
+ win32: {
16
+ enginePlatform: "x86_64-win32",
17
+ buildFolder: "x86_64-win32",
18
+ executable: "dmengine.exe",
19
+ },
20
+ };
21
+
22
+ const ENGINE_INFO_URL = "https://d.defold.com/stable/info.json";
23
+ const ENGINE_ARCHIVE_BASE = "https://d.defold.com/archive/stable";
24
+
25
+ export const DEBUG_LAUNCHER_REL = ".vscode/defold-debug.ts";
26
+
27
+ export function targetPlatform(platform: NodeJS.Platform): EngineTarget {
28
+ const target = PLATFORM_TARGETS[platform];
29
+ if (!target) {
30
+ throw new Error(
31
+ `defold-typescript debug: unsupported platform "${platform}"; expected one of ${Object.keys(
32
+ PLATFORM_TARGETS,
33
+ ).join(", ")}.`,
34
+ );
35
+ }
36
+ return target;
37
+ }
38
+
39
+ export function engineDownloadUrl(
40
+ sha1: string,
41
+ enginePlatform: string,
42
+ executable: string,
43
+ ): string {
44
+ return `${ENGINE_ARCHIVE_BASE}/${sha1}/engine/${enginePlatform}/${executable}`;
45
+ }
46
+
47
+ export interface ResolveEngineOptions {
48
+ readonly cwd: string;
49
+ readonly target: EngineTarget;
50
+ readonly stockPath: string;
51
+ readonly probe: (candidate: string) => boolean;
52
+ }
53
+
54
+ // Prefer the native-extension build engine when it exists; the stock engine is
55
+ // the fallback for projects without native extensions.
56
+ export function resolveEnginePath(opts: ResolveEngineOptions): string {
57
+ const { cwd, target, stockPath, probe } = opts;
58
+ const buildEnginePath = path.join(cwd, "build", target.buildFolder, target.executable);
59
+ return probe(buildEnginePath) ? buildEnginePath : stockPath;
60
+ }
61
+
62
+ export function debugLaunchConfig() {
63
+ return {
64
+ name: "Defold: Debug (TypeScript)",
65
+ type: "lua-local",
66
+ request: "launch",
67
+ stopOnEntry: false,
68
+ verbose: false,
69
+ internalConsoleOptions: "openOnSessionStart",
70
+ program: { command: "bun" },
71
+ args: [DEBUG_LAUNCHER_REL],
72
+ };
73
+ }
74
+
75
+ export const VSCODE_LAUNCH_CONTENT = {
76
+ version: "0.2.0",
77
+ configurations: [debugLaunchConfig()],
78
+ };
79
+
80
+ // The scaffolded launcher embeds the same platform table and archive endpoints
81
+ // the helpers above use, so the self-contained `.vscode/defold-debug.ts` and the
82
+ // unit-tested logic stay in lockstep. It is a Bun script: `process.platform` for
83
+ // the OS, `fetch` for the engine download, `Bun.spawn` with inherited stdio for
84
+ // the run (the pipe Local Lua Debugger attaches over). No shell, no Git Bash.
85
+ function renderDebugLauncher(): string {
86
+ const targets = JSON.stringify(PLATFORM_TARGETS, null, 2);
87
+ return `import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
88
+ import * as path from "node:path";
89
+
90
+ // Windows only: paths to OpenAL32.dll and wrap_oal.dll from your Defold SDK
91
+ // (defoldsdk/ext/lib/x86_64-win32/). Leave empty on macOS/Linux.
92
+ const WINDOWS_OPENAL32_PATH = "";
93
+ const WINDOWS_WRAPOAL_PATH = "";
94
+
95
+ interface EngineTarget {
96
+ enginePlatform: string;
97
+ buildFolder: string;
98
+ executable: string;
99
+ }
100
+
101
+ const PLATFORM_TARGETS: Record<string, EngineTarget> = ${targets};
102
+
103
+ const ENGINE_INFO_URL = "${ENGINE_INFO_URL}";
104
+ const ENGINE_ARCHIVE_BASE = "${ENGINE_ARCHIVE_BASE}";
105
+
106
+ const target = PLATFORM_TARGETS[process.platform];
107
+ if (!target) {
108
+ console.error(\`Unsupported platform: \${process.platform}\`);
109
+ process.exit(1);
110
+ }
111
+
112
+ const here = path.dirname(new URL(import.meta.url).pathname);
113
+ const stockEnginePath = path.join(here, target.executable);
114
+
115
+ if (!existsSync(stockEnginePath)) {
116
+ const info = (await (await fetch(ENGINE_INFO_URL)).json()) as { sha1: string };
117
+ const url = \`\${ENGINE_ARCHIVE_BASE}/\${info.sha1}/engine/\${target.enginePlatform}/\${target.executable}\`;
118
+ console.log(\`Fetching \${url}\`);
119
+ const res = await fetch(url);
120
+ if (!res.ok) {
121
+ console.error(\`Engine download failed: \${res.status} \${res.statusText}\`);
122
+ process.exit(1);
123
+ }
124
+ await Bun.write(stockEnginePath, res);
125
+ }
126
+
127
+ const buildFolder = path.join("build", target.buildFolder);
128
+ const buildEnginePath = path.join(buildFolder, target.executable);
129
+ let enginePath = existsSync(buildEnginePath) ? buildEnginePath : stockEnginePath;
130
+
131
+ if (process.platform === "win32" && enginePath === buildEnginePath) {
132
+ for (const [src, name] of [
133
+ [WINDOWS_OPENAL32_PATH, "OpenAL32.dll"],
134
+ [WINDOWS_WRAPOAL_PATH, "wrap_oal.dll"],
135
+ ] as const) {
136
+ const dest = path.join(buildFolder, name);
137
+ if (src && !existsSync(dest)) {
138
+ copyFileSync(src, dest);
139
+ }
140
+ }
141
+ }
142
+
143
+ // macOS: a build engine launched in place attaches to the editor process; copy
144
+ // it aside first so it runs standalone.
145
+ if (process.platform === "darwin" && enginePath === buildEnginePath) {
146
+ const tempEngine = path.join(buildFolder, "temp", target.executable);
147
+ mkdirSync(path.dirname(tempEngine), { recursive: true });
148
+ copyFileSync(buildEnginePath, tempEngine);
149
+ enginePath = tempEngine;
150
+ }
151
+
152
+ if (process.platform !== "win32") {
153
+ chmodSync(enginePath, 0o755);
154
+ }
155
+
156
+ const projectc = path.join("build", "default", "game.projectc");
157
+ console.log(\`Launching \${enginePath} \${projectc}\`);
158
+ const proc = Bun.spawn([enginePath, projectc], {
159
+ stdio: ["inherit", "inherit", "inherit"],
160
+ });
161
+ process.exit(await proc.exited);
162
+ `;
163
+ }
164
+
165
+ export const DEBUG_LAUNCHER_SOURCE = renderDebugLauncher();