@defold-typescript/cli 0.5.4 → 0.6.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/dist/bin.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
2
4
 
3
5
  // src/dispatch.ts
4
- import { existsSync as existsSync5, readFileSync as readFileSync8 } from "node:fs";
5
- import * as path10 from "node:path";
6
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
7
+ import * as path15 from "node:path";
6
8
 
7
9
  // src/api-registry.ts
8
10
  import { existsSync, readFileSync } from "node:fs";
9
- import { createRequire } from "node:module";
11
+ import { createRequire as createRequire2 } from "node:module";
10
12
  import * as path from "node:path";
11
13
  function findPackageRoot(start) {
12
14
  let dir = start;
@@ -23,7 +25,7 @@ function findPackageRoot(start) {
23
25
  }
24
26
  function resolveTypesPackageRoot() {
25
27
  try {
26
- const require2 = createRequire(import.meta.url);
28
+ const require2 = createRequire2(import.meta.url);
27
29
  const entry = require2.resolve("@defold-typescript/types");
28
30
  return findPackageRoot(path.dirname(entry));
29
31
  } catch {
@@ -81,17 +83,266 @@ function selectApiSurface(resolvedVersion) {
81
83
  return { surfaceId: null, available: false };
82
84
  }
83
85
 
86
+ // src/bob-command.ts
87
+ import { existsSync as existsSync2, mkdirSync } from "node:fs";
88
+ import { delimiter, dirname as dirname2, join as join3 } from "node:path";
89
+
90
+ // src/bob.ts
91
+ import { homedir } from "node:os";
92
+ import * as path2 from "node:path";
93
+
94
+ // src/debug-launcher.ts
95
+ var PLATFORM_TARGETS = {
96
+ "darwin-arm64": {
97
+ enginePlatform: "arm64-macos",
98
+ buildFolder: "arm64-macos",
99
+ executable: "dmengine"
100
+ },
101
+ "darwin-x64": {
102
+ enginePlatform: "x86_64-macos",
103
+ buildFolder: "x86_64-macos",
104
+ executable: "dmengine"
105
+ },
106
+ "linux-x64": {
107
+ enginePlatform: "x86_64-linux",
108
+ buildFolder: "x86_64-linux",
109
+ executable: "dmengine"
110
+ },
111
+ "win32-x64": {
112
+ enginePlatform: "x86_64-win32",
113
+ buildFolder: "x86_64-win32",
114
+ executable: "dmengine.exe"
115
+ }
116
+ };
117
+ var ENGINE_INFO_URL = "https://d.defold.com/stable/info.json";
118
+ var ENGINE_ARCHIVE_BASE = "https://d.defold.com/archive/stable";
119
+ var DEBUG_LAUNCHER_REL = ".vscode/defold-debug.ts";
120
+ function debugLaunchConfig() {
121
+ return {
122
+ name: "Defold: Debug (TypeScript)",
123
+ type: "lua-local",
124
+ request: "launch",
125
+ stopOnEntry: false,
126
+ verbose: false,
127
+ internalConsoleOptions: "openOnSessionStart",
128
+ program: { command: "bun" },
129
+ args: [DEBUG_LAUNCHER_REL],
130
+ scriptFiles: ["src/**/*.ts.script"],
131
+ scriptRoots: [".", "src"]
132
+ };
133
+ }
134
+ var VSCODE_LAUNCH_CONTENT = {
135
+ version: "0.2.0",
136
+ configurations: [debugLaunchConfig()]
137
+ };
138
+ function renderDebugLauncher() {
139
+ const targets = JSON.stringify(PLATFORM_TARGETS, null, 2);
140
+ return `import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
141
+ import * as path from "node:path";
142
+
143
+ // Windows only: paths to OpenAL32.dll and wrap_oal.dll from your Defold SDK
144
+ // (defoldsdk/ext/lib/x86_64-win32/). Leave empty on macOS/Linux.
145
+ const WINDOWS_OPENAL32_PATH = "";
146
+ const WINDOWS_WRAPOAL_PATH = "";
147
+
148
+ interface EngineTarget {
149
+ enginePlatform: string;
150
+ buildFolder: string;
151
+ executable: string;
152
+ }
153
+
154
+ const PLATFORM_TARGETS: Record<string, EngineTarget> = ${targets};
155
+
156
+ const ENGINE_INFO_URL = "${ENGINE_INFO_URL}";
157
+ const ENGINE_ARCHIVE_BASE = "${ENGINE_ARCHIVE_BASE}";
158
+
159
+ const target = PLATFORM_TARGETS[\`\${process.platform}-\${process.arch}\`];
160
+ if (!target) {
161
+ console.error(\`Unsupported platform: \${process.platform}-\${process.arch}\`);
162
+ process.exit(1);
163
+ }
164
+
165
+ const here = path.dirname(new URL(import.meta.url).pathname);
166
+ const stockEnginePath = path.join(here, target.executable);
167
+
168
+ if (!existsSync(stockEnginePath)) {
169
+ const info = (await (await fetch(ENGINE_INFO_URL)).json()) as { sha1: string };
170
+ const url = \`\${ENGINE_ARCHIVE_BASE}/\${info.sha1}/engine/\${target.enginePlatform}/\${target.executable}\`;
171
+ console.log(\`Fetching \${url}\`);
172
+ const res = await fetch(url);
173
+ if (!res.ok) {
174
+ console.error(\`Engine download failed: \${res.status} \${res.statusText}\`);
175
+ process.exit(1);
176
+ }
177
+ await Bun.write(stockEnginePath, res);
178
+ }
179
+
180
+ const buildFolder = path.join("build", target.buildFolder);
181
+ const buildEnginePath = path.join(buildFolder, target.executable);
182
+ let enginePath = existsSync(buildEnginePath) ? buildEnginePath : stockEnginePath;
183
+
184
+ if (process.platform === "win32" && enginePath === buildEnginePath) {
185
+ for (const [src, name] of [
186
+ [WINDOWS_OPENAL32_PATH, "OpenAL32.dll"],
187
+ [WINDOWS_WRAPOAL_PATH, "wrap_oal.dll"],
188
+ ] as const) {
189
+ const dest = path.join(buildFolder, name);
190
+ if (src && !existsSync(dest)) {
191
+ copyFileSync(src, dest);
192
+ }
193
+ }
194
+ }
195
+
196
+ // macOS: a build engine launched in place attaches to the editor process; copy
197
+ // it aside first so it runs standalone.
198
+ if (process.platform === "darwin" && enginePath === buildEnginePath) {
199
+ const tempEngine = path.join(buildFolder, "temp", target.executable);
200
+ mkdirSync(path.dirname(tempEngine), { recursive: true });
201
+ copyFileSync(buildEnginePath, tempEngine);
202
+ enginePath = tempEngine;
203
+ }
204
+
205
+ if (process.platform !== "win32") {
206
+ chmodSync(enginePath, 0o755);
207
+ }
208
+
209
+ const projectc = path.join("build", "default", "game.projectc");
210
+ console.log(\`Launching \${enginePath} \${projectc}\`);
211
+ const proc = Bun.spawn([enginePath, projectc], {
212
+ stdio: ["inherit", "inherit", "inherit"],
213
+ });
214
+ process.exit(await proc.exited);
215
+ `;
216
+ }
217
+ var DEBUG_LAUNCHER_SOURCE = renderDebugLauncher();
218
+
219
+ // src/bob.ts
220
+ function bobDownloadUrl(sha1) {
221
+ return `${ENGINE_ARCHIVE_BASE}/${sha1}/bob/bob.jar`;
222
+ }
223
+ function bobCacheDir(env = process.env, home = homedir) {
224
+ if (env.DEFOLD_TYPESCRIPT_CACHE) {
225
+ return path2.join(env.DEFOLD_TYPESCRIPT_CACHE, "bob");
226
+ }
227
+ return path2.join(env.XDG_CACHE_HOME ?? path2.join(home(), ".cache"), "defold-typescript", "bob");
228
+ }
229
+ function bobCachePath(opts) {
230
+ return path2.join(opts.cacheDir, opts.sha1, "bob.jar");
231
+ }
232
+ function resolveBobJar(opts) {
233
+ const jarPath = bobCachePath({ sha1: opts.sha1, cacheDir: opts.cacheDir });
234
+ return { jarPath, cached: opts.probe(jarPath) };
235
+ }
236
+ function resolveJava(opts) {
237
+ if (opts.override) {
238
+ return opts.override;
239
+ }
240
+ if (opts.probe("java")) {
241
+ return "java";
242
+ }
243
+ throw new Error('defold-typescript: no Java runtime found. Install a JDK and ensure "java" is on PATH, ' + "or pass --java <path> (or set DEFOLD_JAVA). bob.jar requires a JVM to run.");
244
+ }
245
+
246
+ // src/bob-command.ts
247
+ var DEFOLD_SUBCOMMANDS = ["resolve", "build", "bundle"];
248
+ function isDefoldSubcommand(value) {
249
+ return value !== undefined && DEFOLD_SUBCOMMANDS.includes(value);
250
+ }
251
+ function composeBobArgv(opts) {
252
+ const base = [opts.java, "-jar", opts.jar];
253
+ const server = opts.buildServer ? ["--build-server", opts.buildServer] : [];
254
+ switch (opts.subcommand) {
255
+ case "resolve":
256
+ return [...base, ...server, "resolve"];
257
+ case "build":
258
+ return [...base, "--variant", "debug", ...server, "build"];
259
+ case "bundle":
260
+ return [...base, ...server, "bundle"];
261
+ default:
262
+ throw new Error(`defold-typescript: unknown defold subcommand "${opts.subcommand}"; expected resolve|build|bundle.`);
263
+ }
264
+ }
265
+ async function runDefoldCommand(opts) {
266
+ const { io } = opts;
267
+ const sha = await io.fetchSha();
268
+ const { jarPath, cached } = resolveBobJar({
269
+ sha1: sha,
270
+ cacheDir: io.cacheDir,
271
+ probe: io.probe
272
+ });
273
+ if (!cached) {
274
+ await io.download(bobDownloadUrl(sha), jarPath);
275
+ }
276
+ const java = resolveJava({
277
+ ...opts.java !== undefined ? { override: opts.java } : {},
278
+ probe: io.javaProbe
279
+ });
280
+ const argv = composeBobArgv({
281
+ java,
282
+ jar: jarPath,
283
+ subcommand: opts.subcommand,
284
+ ...opts.buildServer !== undefined ? { buildServer: opts.buildServer } : {}
285
+ });
286
+ const exitCode = await io.spawn(argv, opts.cwd);
287
+ return { ok: exitCode === 0, subcommand: opts.subcommand, exitCode, argv };
288
+ }
289
+ async function fetchStableSha() {
290
+ const res = await fetch(ENGINE_INFO_URL);
291
+ if (!res.ok) {
292
+ throw new Error(`defold-typescript: could not resolve the stable Defold sha (${ENGINE_INFO_URL} -> ${res.status} ${res.statusText}).`);
293
+ }
294
+ const info = await res.json();
295
+ if (!info.sha1) {
296
+ throw new Error(`defold-typescript: ${ENGINE_INFO_URL} returned no sha1.`);
297
+ }
298
+ return info.sha1;
299
+ }
300
+ function javaOnPath(cmd, env = process.env) {
301
+ const pathVar = env.PATH ?? env.Path ?? "";
302
+ const exts = process.platform === "win32" ? [".exe", ".bat", ".cmd", ""] : [""];
303
+ for (const dir of pathVar.split(delimiter).filter(Boolean)) {
304
+ for (const ext of exts) {
305
+ if (existsSync2(join3(dir, cmd + ext))) {
306
+ return true;
307
+ }
308
+ }
309
+ }
310
+ return false;
311
+ }
312
+ async function spawnInherit(argv, cwd) {
313
+ const proc = Bun.spawn(argv, { cwd, stdio: ["inherit", "inherit", "inherit"] });
314
+ return proc.exited;
315
+ }
316
+ async function downloadTo(url, dest) {
317
+ const res = await fetch(url);
318
+ if (!res.ok) {
319
+ throw new Error(`defold-typescript: bob.jar download failed (${url} -> ${res.status} ${res.statusText}).`);
320
+ }
321
+ mkdirSync(dirname2(dest), { recursive: true });
322
+ await Bun.write(dest, res);
323
+ }
324
+ function defaultDefoldIo() {
325
+ return {
326
+ cacheDir: bobCacheDir(),
327
+ fetchSha: fetchStableSha,
328
+ probe: existsSync2,
329
+ javaProbe: (cmd) => javaOnPath(cmd),
330
+ spawn: spawnInherit,
331
+ download: downloadTo
332
+ };
333
+ }
334
+
84
335
  // src/build.ts
85
336
  import { readFileSync as readFileSync3 } from "node:fs";
86
- import * as path4 from "node:path";
337
+ import * as path5 from "node:path";
87
338
  import { transpileProject } from "@defold-typescript/transpiler";
88
339
 
89
340
  // src/build-output.ts
90
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
91
- import * as path2 from "node:path";
341
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
342
+ import * as path3 from "node:path";
92
343
  var DEFAULT_INCLUDE = ["src/**/*.ts"];
93
344
  var PROJECT_BUCKET = "<project>";
94
- function toPosix(p, sep2 = path2.sep) {
345
+ function toPosix(p, sep2 = path3.sep) {
95
346
  return p.split(sep2).join("/");
96
347
  }
97
348
  var TRANSPILER_SOURCE_RE = /\.(ts|tsx|cts|mts)$/;
@@ -99,7 +350,7 @@ function isTranspilerSource(rel) {
99
350
  return TRANSPILER_SOURCE_RE.test(toPosix(rel));
100
351
  }
101
352
  function readBuildConfig(cwd) {
102
- const tsconfigPath = path2.join(cwd, "tsconfig.json");
353
+ const tsconfigPath = path3.join(cwd, "tsconfig.json");
103
354
  let raw;
104
355
  try {
105
356
  raw = readFileSync2(tsconfigPath, "utf8");
@@ -114,24 +365,61 @@ function readBuildConfig(cwd) {
114
365
  function stripIncludeBase(pattern) {
115
366
  const firstWildcard = pattern.search(/[*?[]/);
116
367
  if (firstWildcard === -1) {
117
- return pattern.endsWith("/") ? pattern : `${path2.posix.dirname(pattern)}/`;
368
+ return pattern.endsWith("/") ? pattern : `${path3.posix.dirname(pattern)}/`;
118
369
  }
119
370
  const upToWildcard = pattern.slice(0, firstWildcard);
120
371
  const lastSlash = upToWildcard.lastIndexOf("/");
121
372
  return lastSlash === -1 ? "" : upToWildcard.slice(0, lastSlash + 1);
122
373
  }
123
- function computeScriptRel(rel, config) {
374
+ var SCRIPT_SUFFIX_BY_KIND = {
375
+ script: ".ts.script",
376
+ "gui-script": ".ts.gui_script",
377
+ "render-script": ".ts.render_script"
378
+ };
379
+ var FACTORY_KINDS = [
380
+ ["render-script", /\bdefineRenderScript\s*\(/],
381
+ ["gui-script", /\bdefineGuiScript\s*\(/],
382
+ ["script", /\bdefineScript\s*\(/]
383
+ ];
384
+ function detectSourceOutputKind(source) {
385
+ for (const [kind, re] of FACTORY_KINDS) {
386
+ if (re.test(source)) {
387
+ return kind;
388
+ }
389
+ }
390
+ return "module";
391
+ }
392
+ function relUnderOutDir(rel, config) {
124
393
  const { outDir, include } = config;
125
394
  if (outDir === undefined || outDir === "" || outDir === ".") {
126
- return rel.replace(/\.ts$/, ".ts.script");
395
+ return rel;
127
396
  }
128
397
  const includeBase = include.map(stripIncludeBase).filter((base) => rel.startsWith(base)).sort((a, b) => b.length - a.length)[0] ?? "";
129
398
  const relUnderBase = rel.slice(includeBase.length);
130
- return path2.posix.join(outDir, relUnderBase.replace(/\.ts$/, ".ts.script"));
399
+ return path3.posix.join(outDir, relUnderBase);
400
+ }
401
+ function computeOutputRel(rel, config, kind) {
402
+ const baseRel = relUnderOutDir(rel, config);
403
+ if (kind === "module") {
404
+ return baseRel.replace(/\.ts$/, ".lua");
405
+ }
406
+ return baseRel.replace(/\.ts$/, SCRIPT_SUFFIX_BY_KIND[kind]);
407
+ }
408
+ function outputRelsForSource(rel, config) {
409
+ const outputs = [
410
+ computeOutputRel(rel, config, "module"),
411
+ computeOutputRel(rel, config, "script"),
412
+ computeOutputRel(rel, config, "gui-script"),
413
+ computeOutputRel(rel, config, "render-script")
414
+ ];
415
+ return outputs.flatMap((output) => [output, `${output}.map`]);
131
416
  }
132
417
  function collectFailures(diagnostics) {
133
418
  const failures = new Map;
134
419
  for (const diag of diagnostics) {
420
+ if (diag.category === "warning") {
421
+ continue;
422
+ }
135
423
  const bucket = diag.file ?? PROJECT_BUCKET;
136
424
  const list = failures.get(bucket);
137
425
  if (list) {
@@ -152,10 +440,10 @@ function throwIfFailures(failures) {
152
440
  ${formatted}`);
153
441
  }
154
442
  function writeScriptFile(cwd, scriptRel, lua, map) {
155
- const scriptAbs = path2.join(cwd, scriptRel);
156
- mkdirSync(path2.dirname(scriptAbs), { recursive: true });
443
+ const scriptAbs = path3.join(cwd, scriptRel);
444
+ mkdirSync2(path3.dirname(scriptAbs), { recursive: true });
157
445
  if (map) {
158
- const mapBasename = `${path2.posix.basename(scriptRel)}.map`;
446
+ const mapBasename = `${path3.posix.basename(scriptRel)}.map`;
159
447
  writeFileSync(`${scriptAbs}.map`, map);
160
448
  writeFileSync(scriptAbs, `${lua}
161
449
  --# sourceMappingURL=${mapBasename}
@@ -167,9 +455,12 @@ function writeScriptFile(cwd, scriptRel, lua, map) {
167
455
 
168
456
  // src/scan.ts
169
457
  import { globSync, statSync } from "node:fs";
170
- import * as path3 from "node:path";
458
+ import * as path4 from "node:path";
459
+ function normalizeScannedPath(rel) {
460
+ return rel.split(/[/\\]/).join("/");
461
+ }
171
462
  function scanFilesSync(cwd, pattern) {
172
- return globSync(pattern, { cwd }).filter((rel) => statSync(path3.join(cwd, rel)).isFile());
463
+ return globSync(pattern, { cwd }).map(normalizeScannedPath).filter((rel) => statSync(path4.join(cwd, rel)).isFile());
173
464
  }
174
465
 
175
466
  // src/build.ts
@@ -188,7 +479,7 @@ function runBuild(opts) {
188
479
  }
189
480
  const files = {};
190
481
  for (const rel of sources) {
191
- files[rel] = readFileSync3(path4.join(cwd, rel), "utf8");
482
+ files[rel] = readFileSync3(path5.join(cwd, rel), "utf8");
192
483
  }
193
484
  const result = transpileProject({ files });
194
485
  const failures = collectFailures(result.diagnostics);
@@ -201,25 +492,25 @@ function runBuild(opts) {
201
492
  if (!lua) {
202
493
  continue;
203
494
  }
204
- const scriptRel = computeScriptRel(rel, config);
205
- writeScriptFile(cwd, scriptRel, lua, result.sourceMaps[rel]);
206
- written.push(scriptRel);
495
+ const outputRel = computeOutputRel(rel, config, detectSourceOutputKind(files[rel] ?? ""));
496
+ writeScriptFile(cwd, outputRel, lua, result.sourceMaps[rel]);
497
+ written.push(outputRel);
207
498
  }
208
499
  throwIfFailures(failures);
209
- return { written };
500
+ return { written: written.sort() };
210
501
  }
211
502
 
212
503
  // src/cli-version.ts
213
504
  import { readFileSync as readFileSync4 } from "node:fs";
214
- import * as path5 from "node:path";
505
+ import * as path6 from "node:path";
215
506
  import { fileURLToPath } from "node:url";
216
507
  var VERSION_FALLBACK = "0.0.0";
217
508
  function defaultPackageRoot() {
218
- return path5.join(path5.dirname(fileURLToPath(import.meta.url)), "..");
509
+ return path6.join(path6.dirname(fileURLToPath(import.meta.url)), "..");
219
510
  }
220
511
  function readCliVersion(packageRoot = defaultPackageRoot()) {
221
512
  try {
222
- const pkg = JSON.parse(readFileSync4(path5.join(packageRoot, "package.json"), "utf8"));
513
+ const pkg = JSON.parse(readFileSync4(path6.join(packageRoot, "package.json"), "utf8"));
223
514
  return typeof pkg.version === "string" ? pkg.version : VERSION_FALLBACK;
224
515
  } catch {
225
516
  return VERSION_FALLBACK;
@@ -227,136 +518,31 @@ function readCliVersion(packageRoot = defaultPackageRoot()) {
227
518
  }
228
519
 
229
520
  // src/init.ts
230
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
231
- import * as path6 from "node:path";
521
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
522
+ import * as path7 from "node:path";
232
523
  import { fileURLToPath as fileURLToPath2 } from "node:url";
233
524
 
234
- // src/debug-launcher.ts
235
- var PLATFORM_TARGETS = {
236
- darwin: { enginePlatform: "x86_64-darwin", buildFolder: "x86_64-osx", executable: "dmengine" },
237
- linux: { enginePlatform: "x86_64-linux", buildFolder: "x86_64-linux", executable: "dmengine" },
238
- win32: {
239
- enginePlatform: "x86_64-win32",
240
- buildFolder: "x86_64-win32",
241
- executable: "dmengine.exe"
242
- }
243
- };
244
- var ENGINE_INFO_URL = "https://d.defold.com/stable/info.json";
245
- var ENGINE_ARCHIVE_BASE = "https://d.defold.com/archive/stable";
246
- var DEBUG_LAUNCHER_REL = ".vscode/defold-debug.ts";
247
- function debugLaunchConfig() {
248
- return {
249
- name: "Defold: Debug (TypeScript)",
250
- type: "lua-local",
251
- request: "launch",
252
- stopOnEntry: false,
253
- verbose: false,
254
- internalConsoleOptions: "openOnSessionStart",
255
- program: { command: "bun" },
256
- args: [DEBUG_LAUNCHER_REL]
257
- };
258
- }
259
- var VSCODE_LAUNCH_CONTENT = {
260
- version: "0.2.0",
261
- configurations: [debugLaunchConfig()]
262
- };
263
- function renderDebugLauncher() {
264
- const targets = JSON.stringify(PLATFORM_TARGETS, null, 2);
265
- return `import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
266
- import * as path from "node:path";
267
-
268
- // Windows only: paths to OpenAL32.dll and wrap_oal.dll from your Defold SDK
269
- // (defoldsdk/ext/lib/x86_64-win32/). Leave empty on macOS/Linux.
270
- const WINDOWS_OPENAL32_PATH = "";
271
- const WINDOWS_WRAPOAL_PATH = "";
272
-
273
- interface EngineTarget {
274
- enginePlatform: string;
275
- buildFolder: string;
276
- executable: string;
277
- }
278
-
279
- const PLATFORM_TARGETS: Record<string, EngineTarget> = ${targets};
280
-
281
- const ENGINE_INFO_URL = "${ENGINE_INFO_URL}";
282
- const ENGINE_ARCHIVE_BASE = "${ENGINE_ARCHIVE_BASE}";
283
-
284
- const target = PLATFORM_TARGETS[process.platform];
285
- if (!target) {
286
- console.error(\`Unsupported platform: \${process.platform}\`);
287
- process.exit(1);
288
- }
289
-
290
- const here = path.dirname(new URL(import.meta.url).pathname);
291
- const stockEnginePath = path.join(here, target.executable);
292
-
293
- if (!existsSync(stockEnginePath)) {
294
- const info = (await (await fetch(ENGINE_INFO_URL)).json()) as { sha1: string };
295
- const url = \`\${ENGINE_ARCHIVE_BASE}/\${info.sha1}/engine/\${target.enginePlatform}/\${target.executable}\`;
296
- console.log(\`Fetching \${url}\`);
297
- const res = await fetch(url);
298
- if (!res.ok) {
299
- console.error(\`Engine download failed: \${res.status} \${res.statusText}\`);
300
- process.exit(1);
301
- }
302
- await Bun.write(stockEnginePath, res);
303
- }
304
-
305
- const buildFolder = path.join("build", target.buildFolder);
306
- const buildEnginePath = path.join(buildFolder, target.executable);
307
- let enginePath = existsSync(buildEnginePath) ? buildEnginePath : stockEnginePath;
308
-
309
- if (process.platform === "win32" && enginePath === buildEnginePath) {
310
- for (const [src, name] of [
311
- [WINDOWS_OPENAL32_PATH, "OpenAL32.dll"],
312
- [WINDOWS_WRAPOAL_PATH, "wrap_oal.dll"],
313
- ] as const) {
314
- const dest = path.join(buildFolder, name);
315
- if (src && !existsSync(dest)) {
316
- copyFileSync(src, dest);
317
- }
318
- }
319
- }
320
-
321
- // macOS: a build engine launched in place attaches to the editor process; copy
322
- // it aside first so it runs standalone.
323
- if (process.platform === "darwin" && enginePath === buildEnginePath) {
324
- const tempEngine = path.join(buildFolder, "temp", target.executable);
325
- mkdirSync(path.dirname(tempEngine), { recursive: true });
326
- copyFileSync(buildEnginePath, tempEngine);
327
- enginePath = tempEngine;
328
- }
329
-
330
- if (process.platform !== "win32") {
331
- chmodSync(enginePath, 0o755);
332
- }
333
-
334
- const projectc = path.join("build", "default", "game.projectc");
335
- console.log(\`Launching \${enginePath} \${projectc}\`);
336
- const proc = Bun.spawn([enginePath, projectc], {
337
- stdio: ["inherit", "inherit", "inherit"],
338
- });
339
- process.exit(await proc.exited);
340
- `;
341
- }
342
- var DEBUG_LAUNCHER_SOURCE = renderDebugLauncher();
343
-
344
525
  // src/mise-scaffold.ts
345
526
  var MANAGED_MARKER = "# managed by @defold-typescript";
346
527
  var MISE_TASKS_TOML = `${MANAGED_MARKER}
347
528
  [tasks."defold-typescript:build"]
348
- description = "Build the TypeScript sources with the installed defold-typescript CLI"
349
- run = "bunx --no-install defold-typescript build"
529
+ description = "Build the TypeScript sources with the defold-typescript CLI"
530
+ run = "bunx @defold-typescript/cli build"
350
531
 
351
532
  ${MANAGED_MARKER}
352
533
  [tasks."defold-typescript:watch"]
353
- description = "Watch and rebuild the TypeScript sources with the installed defold-typescript CLI"
354
- run = "bunx --no-install defold-typescript watch"
534
+ description = "Watch and rebuild the TypeScript sources with the defold-typescript CLI"
535
+ run = "bunx @defold-typescript/cli watch"
536
+
537
+ ${MANAGED_MARKER}
538
+ [tasks."defold-typescript:setup-debug"]
539
+ description = "Wire the lldebugger game.project dependency and entry-script bootstrap with the defold-typescript CLI"
540
+ run = "bunx @defold-typescript/cli setup-debug"
355
541
 
356
542
  ${MANAGED_MARKER}
357
543
  [tasks."defold-typescript:upgrade"]
358
544
  description = "Upgrade the defold-typescript CLI to its latest release and re-pin the types dependency"
359
- run = ["bunx @defold-typescript/cli@latest init --force", "bun install"]
545
+ run = ["bunx @defold-typescript/cli@latest init --force --suppress-install-reminder", "bun install"]
360
546
  `;
361
547
  function stripManagedBlocks(text) {
362
548
  const lines = text.split(`
@@ -415,18 +601,6 @@ function isComponentPath(relPath) {
415
601
  }
416
602
  return Object.keys(KIND_BY_EXT).some((ext) => relPath.endsWith(ext));
417
603
  }
418
- function detectScriptKinds(cwd) {
419
- const kinds = new Set;
420
- for (const [ext, kind] of Object.entries(KIND_BY_EXT)) {
421
- for (const match of scanFilesSync(cwd, `**/*${ext}`)) {
422
- if (!isSkipped(match) && !isGeneratedScript(match)) {
423
- kinds.add(kind);
424
- break;
425
- }
426
- }
427
- }
428
- return kinds;
429
- }
430
604
  function selectScriptKind(kinds) {
431
605
  if (kinds.size !== 1) {
432
606
  return null;
@@ -440,19 +614,6 @@ function selectScriptKindEntrypoint(kinds) {
440
614
  const kind = selectScriptKind(kinds);
441
615
  return kind === null ? DEFAULT_TYPES_ENTRYPOINT : `${DEFAULT_TYPES_ENTRYPOINT}/${kind}`;
442
616
  }
443
- var RESTRICTED_MODULES = {
444
- script: "",
445
- "gui-script": "gui",
446
- "render-script": "render"
447
- };
448
- var ALL_RESTRICTED_MODULES = ["gui", "render"];
449
- function excludedModulesForKind(kind) {
450
- if (kind === null) {
451
- return new Set;
452
- }
453
- const allowed = RESTRICTED_MODULES[kind];
454
- return new Set(ALL_RESTRICTED_MODULES.filter((mod) => mod !== allowed));
455
- }
456
617
 
457
618
  // src/init.ts
458
619
  var CONFLICTING_TS_CONFIGS = [
@@ -469,11 +630,29 @@ var TSCONFIG_COMPILER_OPTIONS = {
469
630
  strict: true,
470
631
  skipLibCheck: true
471
632
  };
472
- var GITIGNORE_LINES = ["src/**/*.ts.script", "src/**/*.ts.script.map"];
633
+ var GITIGNORE_LINES = [
634
+ "src/**/*.ts.script",
635
+ "src/**/*.ts.script.map",
636
+ "src/**/*.ts.gui_script",
637
+ "src/**/*.ts.gui_script.map",
638
+ "src/**/*.ts.render_script",
639
+ "src/**/*.ts.render_script.map",
640
+ "src/**/*.lua",
641
+ "src/**/*.lua.map"
642
+ ];
473
643
  var BIOME_JSON_CONTENT = {
474
644
  $schema: "https://biomejs.dev/schemas/2.4.15/schema.json",
475
645
  files: {
476
- includes: ["src/**/*.ts", "!**/dist", "!**/node_modules", "!**/*.ts.script"]
646
+ includes: [
647
+ "src/**/*.ts",
648
+ "!**/dist",
649
+ "!**/node_modules",
650
+ "!**/*.ts.script",
651
+ "!**/*.ts.gui_script",
652
+ "!**/*.ts.render_script",
653
+ "!src/**/*.lua",
654
+ "!src/**/*.lua.map"
655
+ ]
477
656
  },
478
657
  formatter: {
479
658
  enabled: true,
@@ -505,9 +684,15 @@ var BIOME_JSON_CONTENT = {
505
684
  }
506
685
  };
507
686
  var VSCODE_EXTENSIONS_CONTENT = {
508
- recommendations: ["sumneko.lua", "astronachos.defold", "tomblind.local-lua-debugger-vscode"],
687
+ recommendations: ["tomblind.local-lua-debugger-vscode"],
509
688
  unwantedRecommendations: ["johnnymorganz.luau-lsp"]
510
689
  };
690
+ var MANAGED_RECOMMENDATIONS = [
691
+ "tomblind.local-lua-debugger-vscode",
692
+ "sumneko.lua",
693
+ "astronachos.defold"
694
+ ];
695
+ var MANAGED_UNWANTED = ["johnnymorganz.luau-lsp"];
511
696
  var VSCODE_SETTINGS_CONTENT = {
512
697
  "Lua.workspace.ignoreDir": ["src"]
513
698
  };
@@ -552,7 +737,7 @@ function inlineSnippetBody(factory, includeOnInput) {
552
737
  return [
553
738
  `import { ${factory} } from "@defold-typescript/types";`,
554
739
  "",
555
- `export const script = ${factory}({`,
740
+ `export default ${factory}({`,
556
741
  ` // ${HOOK_COMMENTS.init}`,
557
742
  " init() {",
558
743
  " return { $0 };",
@@ -570,7 +755,7 @@ function typedSnippetBody(factory, includeOnInput) {
570
755
  " $1",
571
756
  "};",
572
757
  "",
573
- `export const script = ${factory}<Self>({`,
758
+ `export default ${factory}<Self>({`,
574
759
  ` // ${HOOK_COMMENTS.init}`,
575
760
  " init(): Self {",
576
761
  " return { $0 };",
@@ -617,21 +802,20 @@ var VSCODE_SNIPPETS_CONTENT = {
617
802
  description: "Empty Defold render script with an explicit Self type."
618
803
  }
619
804
  };
620
- var MAIN_TS_CONTENT = `export function init(): void {
621
- const start = vmath.vector3(0, 0, 0);
622
- msg.post("main:/hero", "spawn", { start });
623
- }
624
- `;
625
- var MAIN_SCRIPT_CONTENT = `function init(self) end
626
- function update(self, dt) end
627
- function on_message(self, message_id, message, sender) end
628
- function final(self) end
805
+ var MAIN_TS_CONTENT = `import { defineScript } from "@defold-typescript/types";
806
+
807
+ export default defineScript({
808
+ init() {
809
+ const start = vmath.vector3(0, 0, 0);
810
+ return { start };
811
+ },
812
+ });
629
813
  `;
630
814
  var MAIN_COLLECTION_CONTENT = `name: "main"
631
815
  scale_along_z: 0
632
816
  embedded_instances {
633
817
  id: "main"
634
- data: "components {\\n id: \\"main\\"\\n component: \\"/main/main.script\\"\\n}\\n"
818
+ data: "components {\\n id: \\"main\\"\\n component: \\"/src/main.ts.script\\"\\n}\\n"
635
819
  position { x: 0.0 y: 0.0 z: 0.0 }
636
820
  rotation { x: 0.0 y: 0.0 z: 0.0 w: 1.0 }
637
821
  scale3 { x: 1.0 y: 1.0 z: 1.0 }
@@ -639,8 +823,8 @@ embedded_instances {
639
823
  `;
640
824
  function typesVersionSpec() {
641
825
  try {
642
- const here = path6.dirname(fileURLToPath2(import.meta.url));
643
- const pkg = JSON.parse(readFileSync5(path6.join(here, "..", "package.json"), "utf8"));
826
+ const here = path7.dirname(fileURLToPath2(import.meta.url));
827
+ const pkg = JSON.parse(readFileSync5(path7.join(here, "..", "package.json"), "utf8"));
644
828
  return pkg.version ? `^${pkg.version}` : "latest";
645
829
  } catch {
646
830
  return "latest";
@@ -664,8 +848,8 @@ function writeJson(filePath, value) {
664
848
  `);
665
849
  }
666
850
  function writeGitignore(cwd) {
667
- const gitignorePath = path6.join(cwd, ".gitignore");
668
- if (existsSync2(gitignorePath)) {
851
+ const gitignorePath = path7.join(cwd, ".gitignore");
852
+ if (existsSync3(gitignorePath)) {
669
853
  const existing = readFileSync5(gitignorePath, "utf8");
670
854
  const present = new Set(existing.split(`
671
855
  `).map((line) => line.trim()));
@@ -686,16 +870,16 @@ function writeGitignore(cwd) {
686
870
  }
687
871
  }
688
872
  function writeBiome(cwd, written) {
689
- const biomePath = path6.join(cwd, "biome.json");
690
- if (existsSync2(biomePath)) {
873
+ const biomePath = path7.join(cwd, "biome.json");
874
+ if (existsSync3(biomePath)) {
691
875
  return;
692
876
  }
693
877
  writeJson(biomePath, BIOME_JSON_CONTENT);
694
878
  written.push("biome.json");
695
879
  }
696
880
  function writeMiseTasks(cwd, written) {
697
- const misePath = path6.join(cwd, "mise.toml");
698
- const existing = existsSync2(misePath) ? readFileSync5(misePath, "utf8") : undefined;
881
+ const misePath = path7.join(cwd, "mise.toml");
882
+ const existing = existsSync3(misePath) ? readFileSync5(misePath, "utf8") : undefined;
699
883
  writeFileSync2(misePath, mergeMiseToml(existing));
700
884
  written.push("mise.toml");
701
885
  }
@@ -759,6 +943,27 @@ function unionStrings(existing, additions) {
759
943
  }
760
944
  return out;
761
945
  }
946
+ function reconcileManagedList(existing, managed, canonical) {
947
+ const managedSet = new Set(managed);
948
+ const canonicalSet = new Set(canonical);
949
+ const out = [];
950
+ const values = Array.isArray(existing) ? existing.filter((value) => typeof value === "string") : [];
951
+ for (const value of values) {
952
+ if (out.includes(value)) {
953
+ continue;
954
+ }
955
+ if (managedSet.has(value) && !canonicalSet.has(value)) {
956
+ continue;
957
+ }
958
+ out.push(value);
959
+ }
960
+ for (const value of canonical) {
961
+ if (!out.includes(value)) {
962
+ out.push(value);
963
+ }
964
+ }
965
+ return out;
966
+ }
762
967
  function readVscodeJson(filePath) {
763
968
  try {
764
969
  const parsed = parseJsonc(readFileSync5(filePath, "utf8"));
@@ -768,26 +973,29 @@ function readVscodeJson(filePath) {
768
973
  }
769
974
  }
770
975
  function writeVscodeExtensions(cwd, written) {
771
- const dir = path6.join(cwd, ".vscode");
772
- const filePath = path6.join(dir, "extensions.json");
773
- if (existsSync2(filePath)) {
976
+ const dir = path7.join(cwd, ".vscode");
977
+ const filePath = path7.join(dir, "extensions.json");
978
+ if (existsSync3(filePath)) {
774
979
  const existing = readVscodeJson(filePath);
775
980
  if (existing === null) {
776
981
  return;
777
982
  }
778
- existing.recommendations = unionStrings(existing.recommendations, VSCODE_EXTENSIONS_CONTENT.recommendations);
779
- existing.unwantedRecommendations = unionStrings(existing.unwantedRecommendations, VSCODE_EXTENSIONS_CONTENT.unwantedRecommendations);
780
- writeJson(filePath, existing);
983
+ const before = JSON.stringify(existing);
984
+ existing.recommendations = reconcileManagedList(existing.recommendations, MANAGED_RECOMMENDATIONS, VSCODE_EXTENSIONS_CONTENT.recommendations);
985
+ existing.unwantedRecommendations = reconcileManagedList(existing.unwantedRecommendations, MANAGED_UNWANTED, VSCODE_EXTENSIONS_CONTENT.unwantedRecommendations);
986
+ if (JSON.stringify(existing) !== before) {
987
+ writeJson(filePath, existing);
988
+ }
781
989
  return;
782
990
  }
783
- mkdirSync2(dir, { recursive: true });
991
+ mkdirSync3(dir, { recursive: true });
784
992
  writeJson(filePath, VSCODE_EXTENSIONS_CONTENT);
785
993
  written.push(".vscode/extensions.json");
786
994
  }
787
995
  function writeVscodeSettings(cwd, written) {
788
- const dir = path6.join(cwd, ".vscode");
789
- const filePath = path6.join(dir, "settings.json");
790
- if (existsSync2(filePath)) {
996
+ const dir = path7.join(cwd, ".vscode");
997
+ const filePath = path7.join(dir, "settings.json");
998
+ if (existsSync3(filePath)) {
791
999
  const existing = readVscodeJson(filePath);
792
1000
  if (existing === null) {
793
1001
  return;
@@ -796,14 +1004,14 @@ function writeVscodeSettings(cwd, written) {
796
1004
  writeJson(filePath, existing);
797
1005
  return;
798
1006
  }
799
- mkdirSync2(dir, { recursive: true });
1007
+ mkdirSync3(dir, { recursive: true });
800
1008
  writeJson(filePath, VSCODE_SETTINGS_CONTENT);
801
1009
  written.push(".vscode/settings.json");
802
1010
  }
803
1011
  function writeVscodeSnippets(cwd, written) {
804
- const dir = path6.join(cwd, ".vscode");
805
- const filePath = path6.join(dir, "defold-typescript.code-snippets");
806
- if (existsSync2(filePath)) {
1012
+ const dir = path7.join(cwd, ".vscode");
1013
+ const filePath = path7.join(dir, "defold-typescript.code-snippets");
1014
+ if (existsSync3(filePath)) {
807
1015
  const existing = readVscodeJson(filePath);
808
1016
  if (existing === null) {
809
1017
  return;
@@ -816,15 +1024,15 @@ function writeVscodeSnippets(cwd, written) {
816
1024
  writeJson(filePath, existing);
817
1025
  return;
818
1026
  }
819
- mkdirSync2(dir, { recursive: true });
1027
+ mkdirSync3(dir, { recursive: true });
820
1028
  writeJson(filePath, VSCODE_SNIPPETS_CONTENT);
821
1029
  written.push(".vscode/defold-typescript.code-snippets");
822
1030
  }
823
1031
  function writeVscodeLaunch(cwd, written) {
824
- const dir = path6.join(cwd, ".vscode");
825
- const filePath = path6.join(dir, "launch.json");
1032
+ const dir = path7.join(cwd, ".vscode");
1033
+ const filePath = path7.join(dir, "launch.json");
826
1034
  const ours = debugLaunchConfig();
827
- if (existsSync2(filePath)) {
1035
+ if (existsSync3(filePath)) {
828
1036
  const existing = readVscodeJson(filePath);
829
1037
  if (existing === null) {
830
1038
  return;
@@ -839,36 +1047,35 @@ function writeVscodeLaunch(cwd, written) {
839
1047
  writeJson(filePath, existing);
840
1048
  return;
841
1049
  }
842
- mkdirSync2(dir, { recursive: true });
1050
+ mkdirSync3(dir, { recursive: true });
843
1051
  writeJson(filePath, VSCODE_LAUNCH_CONTENT);
844
1052
  written.push(".vscode/launch.json");
845
1053
  }
846
1054
  function writeVscodeDebugLauncher(cwd, written) {
847
- const dir = path6.join(cwd, ".vscode");
848
- const filePath = path6.join(dir, "defold-debug.ts");
849
- if (existsSync2(filePath)) {
1055
+ const dir = path7.join(cwd, ".vscode");
1056
+ const filePath = path7.join(dir, "defold-debug.ts");
1057
+ if (existsSync3(filePath)) {
850
1058
  return;
851
1059
  }
852
- mkdirSync2(dir, { recursive: true });
1060
+ mkdirSync3(dir, { recursive: true });
853
1061
  writeFileSync2(filePath, DEBUG_LAUNCHER_SOURCE);
854
1062
  written.push(".vscode/defold-debug.ts");
855
1063
  }
856
1064
  function writeTsSurface(cwd, written, force = false) {
857
- mkdirSync2(path6.join(cwd, "src"), { recursive: true });
858
- writeFileSync2(path6.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
1065
+ mkdirSync3(path7.join(cwd, "src"), { recursive: true });
1066
+ writeFileSync2(path7.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
859
1067
  written.push("src/main.ts");
860
- const kinds = detectScriptKinds(cwd);
861
1068
  const tsconfig = {
862
1069
  compilerOptions: {
863
1070
  ...TSCONFIG_COMPILER_OPTIONS,
864
- types: [selectScriptKindEntrypoint(kinds)]
1071
+ types: [DEFAULT_TYPES_ENTRYPOINT]
865
1072
  },
866
1073
  include: ["src/**/*.ts"]
867
1074
  };
868
- writeJson(path6.join(cwd, "tsconfig.json"), tsconfig);
1075
+ writeJson(path7.join(cwd, "tsconfig.json"), tsconfig);
869
1076
  written.push("tsconfig.json");
870
- const pkgPath = path6.join(cwd, "package.json");
871
- if (existsSync2(pkgPath)) {
1077
+ const pkgPath = path7.join(cwd, "package.json");
1078
+ if (existsSync3(pkgPath)) {
872
1079
  const existing = JSON.parse(readFileSync5(pkgPath, "utf8"));
873
1080
  const devDeps = { ...existing.devDependencies ?? {} };
874
1081
  for (const [name, version] of Object.entries(SCAFFOLD_DEV_DEPS)) {
@@ -882,7 +1089,7 @@ function writeTsSurface(cwd, written, force = false) {
882
1089
  writeJson(pkgPath, existing);
883
1090
  } else {
884
1091
  const fresh = {
885
- name: path6.basename(cwd),
1092
+ name: path7.basename(cwd),
886
1093
  version: "0.0.0",
887
1094
  type: "module",
888
1095
  devDependencies: { ...SCAFFOLD_DEV_DEPS },
@@ -900,43 +1107,56 @@ function writeTsSurface(cwd, written, force = false) {
900
1107
  writeVscodeSnippets(cwd, written);
901
1108
  writeVscodeLaunch(cwd, written);
902
1109
  writeVscodeDebugLauncher(cwd, written);
903
- return selectScriptKind(kinds);
904
1110
  }
905
1111
  function runNewProjectInit(cwd, force = false) {
906
- if (!existsSync2(cwd)) {
907
- mkdirSync2(cwd, { recursive: true });
1112
+ if (!existsSync3(cwd)) {
1113
+ mkdirSync3(cwd, { recursive: true });
908
1114
  } else if (readdirSync(cwd).length > 0 && !force) {
909
1115
  throw new Error(`defold-typescript init: refusing to synthesize a new Defold project into non-empty directory ${cwd}. Pass --force to proceed.`);
910
1116
  }
911
1117
  const written = [];
912
- writeFileSync2(path6.join(cwd, "game.project"), `[project]
913
- title = ${path6.basename(cwd)}
1118
+ writeFileSync2(path7.join(cwd, "game.project"), `[project]
1119
+ title = ${path7.basename(cwd)}
914
1120
  main_collection = /main/main.collectionc
915
1121
  `);
916
1122
  written.push("game.project");
917
- mkdirSync2(path6.join(cwd, "main"), { recursive: true });
918
- writeFileSync2(path6.join(cwd, "main", "main.collection"), MAIN_COLLECTION_CONTENT);
1123
+ mkdirSync3(path7.join(cwd, "main"), { recursive: true });
1124
+ writeFileSync2(path7.join(cwd, "main", "main.collection"), MAIN_COLLECTION_CONTENT);
919
1125
  written.push("main/main.collection");
920
- writeFileSync2(path6.join(cwd, "main", "main.script"), MAIN_SCRIPT_CONTENT);
921
- written.push("main/main.script");
922
- const scriptKind = writeTsSurface(cwd, written, force);
923
- return { written, scriptKind };
1126
+ writeTsSurface(cwd, written, force);
1127
+ return { written };
924
1128
  }
925
1129
  function runInit(opts) {
926
1130
  const { cwd, force = false } = opts;
927
- if (!existsSync2(path6.join(cwd, "game.project"))) {
1131
+ if (!existsSync3(path7.join(cwd, "game.project"))) {
928
1132
  return runNewProjectInit(cwd, force);
929
1133
  }
930
1134
  if (!force) {
931
1135
  for (const rel of CONFLICTING_TS_CONFIGS) {
932
- if (existsSync2(path6.join(cwd, rel))) {
1136
+ if (existsSync3(path7.join(cwd, rel))) {
933
1137
  throw new Error(`defold-typescript init: refusing to overwrite existing TS config: ${rel}. Pass --force to overwrite.`);
934
1138
  }
935
1139
  }
936
1140
  }
937
1141
  const written = [];
938
- const scriptKind = writeTsSurface(cwd, written, force);
939
- return { written, scriptKind };
1142
+ writeTsSurface(cwd, written, force);
1143
+ return { written };
1144
+ }
1145
+
1146
+ // src/install-reminder.ts
1147
+ function installHint(env = process.env) {
1148
+ const agent = env.npm_config_user_agent ?? "";
1149
+ const manager = agent.split("/")[0];
1150
+ if (manager === "pnpm") {
1151
+ return "pnpm install";
1152
+ }
1153
+ if (manager === "yarn") {
1154
+ return "yarn install";
1155
+ }
1156
+ if (manager === "npm") {
1157
+ return "npm install";
1158
+ }
1159
+ return "bun install";
940
1160
  }
941
1161
 
942
1162
  // src/json-output.ts
@@ -945,176 +1165,765 @@ function renderResult(input) {
945
1165
  const base = ok ? { command: input.command, ok, written: input.written ?? [] } : { command: input.command, ok, error: input.error };
946
1166
  const withVersion = input.defoldVersion === undefined ? base : { ...base, defoldVersion: input.defoldVersion };
947
1167
  const withSurface = "apiSurface" in input ? { ...withVersion, apiSurface: input.apiSurface } : withVersion;
948
- const withScriptKind = "scriptKind" in input ? { ...withSurface, scriptKind: input.scriptKind } : withSurface;
949
- const payload = "materializedSurface" in input ? { ...withScriptKind, materializedSurface: input.materializedSurface } : withScriptKind;
1168
+ const withMaterialized = "materializedSurface" in input ? { ...withSurface, materializedSurface: input.materializedSurface } : withSurface;
1169
+ const withWalls = "directoryWalls" in input ? { ...withMaterialized, directoryWalls: input.directoryWalls } : withMaterialized;
1170
+ const withEligible = "eligible" in input ? { ...withWalls, eligible: input.eligible } : withWalls;
1171
+ const withInstall = "installCommand" in input ? { ...withEligible, installCommand: input.installCommand } : withEligible;
1172
+ const withManual = "manualSteps" in input ? { ...withInstall, manualSteps: input.manualSteps } : withInstall;
1173
+ const withActions = "actions" in input ? { ...withManual, actions: input.actions } : withManual;
1174
+ const withAdded = "addedTo" in input ? { ...withActions, addedTo: input.addedTo } : withActions;
1175
+ const withRemoved = "removedFrom" in input ? { ...withAdded, removedFrom: input.removedFrom } : withAdded;
1176
+ const withBoot = "bootPath" in input ? { ...withRemoved, bootPath: input.bootPath } : withRemoved;
1177
+ const withSub = "subcommand" in input ? { ...withBoot, subcommand: input.subcommand } : withBoot;
1178
+ const payload = "exitCode" in input ? { ...withSub, exitCode: input.exitCode } : withSub;
950
1179
  return `${JSON.stringify(payload)}
951
1180
  `;
952
1181
  }
1182
+ function renderWatchEvent(input) {
1183
+ const ok = input.error === undefined;
1184
+ const base = ok ? { command: "watch", event: input.event, ok, written: input.written ?? [] } : { command: "watch", event: input.event, ok, error: input.error };
1185
+ const withChanged = "changed" in input ? { ...base, changed: input.changed } : base;
1186
+ const withRemoved = "removed" in input ? { ...withChanged, removed: input.removed } : withChanged;
1187
+ return `${JSON.stringify(withRemoved)}
1188
+ `;
1189
+ }
953
1190
 
954
1191
  // src/materialize.ts
955
1192
  import {
956
1193
  copyFileSync,
957
- existsSync as existsSync3,
958
- mkdirSync as mkdirSync3,
1194
+ existsSync as existsSync4,
1195
+ mkdirSync as mkdirSync4,
959
1196
  readdirSync as readdirSync2,
960
1197
  readFileSync as readFileSync6,
961
1198
  rmSync,
962
1199
  writeFileSync as writeFileSync3
963
1200
  } from "node:fs";
964
- import * as path7 from "node:path";
1201
+ import * as path8 from "node:path";
965
1202
  var MATERIALIZED_ROOT = ".defold-types";
1203
+ var CORE_TYPES_REEXPORT = `export * from "@defold-typescript/types/core-types";
1204
+ `;
966
1205
  function writeJson2(filePath, value) {
967
1206
  writeFileSync3(filePath, `${JSON.stringify(value, null, 2)}
968
1207
  `);
969
1208
  }
970
- function listDts(dir) {
971
- return readdirSync2(dir).filter((file) => file.endsWith(".d.ts")).sort();
1209
+ function listDts(dir) {
1210
+ return readdirSync2(dir).filter((file) => file.endsWith(".d.ts")).sort();
1211
+ }
1212
+ function materializeApiSurface(opts) {
1213
+ const { cwd, surface, sourceGeneratedDir } = opts;
1214
+ if (!surface.available || surface.surfaceId === null || sourceGeneratedDir === null) {
1215
+ return { materializedDir: null, active: null };
1216
+ }
1217
+ const { surfaceId } = surface;
1218
+ const relDir = path8.posix.join(MATERIALIZED_ROOT, surfaceId);
1219
+ const absDir = path8.join(cwd, MATERIALIZED_ROOT, surfaceId);
1220
+ mkdirSync4(absDir, { recursive: true });
1221
+ const sources = listDts(sourceGeneratedDir).filter((file) => file !== "index.d.ts");
1222
+ const srcDir = path8.resolve(sourceGeneratedDir, "..", "src");
1223
+ const overloads = ["msg-overloads.d.ts", "message-guard.d.ts", "go-overloads.d.ts"].filter((file) => existsSync4(path8.join(srcDir, file)));
1224
+ const coreTypesSrc = path8.join(srcDir, "core-types.ts");
1225
+ const includeCoreTypes = overloads.length > 0 && existsSync4(coreTypesSrc);
1226
+ const engineGlobalsSrc = path8.join(srcDir, "engine-globals.d.ts");
1227
+ const includeEngineGlobals = includeCoreTypes && existsSync4(engineGlobalsSrc);
1228
+ const wanted = new Set(sources);
1229
+ for (const file of overloads) {
1230
+ wanted.add(file);
1231
+ }
1232
+ if (includeCoreTypes) {
1233
+ wanted.add("core-types.d.ts");
1234
+ }
1235
+ if (includeEngineGlobals) {
1236
+ wanted.add("engine-globals.d.ts");
1237
+ }
1238
+ for (const existing of readdirSync2(absDir)) {
1239
+ if (existing.endsWith(".d.ts") && existing !== "index.d.ts" && !wanted.has(existing)) {
1240
+ rmSync(path8.join(absDir, existing));
1241
+ }
1242
+ }
1243
+ for (const file of sources) {
1244
+ writeFileSync3(path8.join(absDir, file), readFileSync6(path8.join(sourceGeneratedDir, file), "utf8"));
1245
+ }
1246
+ if (includeCoreTypes) {
1247
+ writeFileSync3(path8.join(absDir, "core-types.d.ts"), CORE_TYPES_REEXPORT);
1248
+ }
1249
+ if (includeEngineGlobals) {
1250
+ writeFileSync3(path8.join(absDir, "engine-globals.d.ts"), readFileSync6(engineGlobalsSrc, "utf8"));
1251
+ }
1252
+ for (const file of overloads) {
1253
+ writeFileSync3(path8.join(absDir, file), readFileSync6(path8.join(srcDir, file), "utf8"));
1254
+ }
1255
+ const modules = [...sources, ...overloads].map((file) => file.replace(/\.d\.ts$/, ""));
1256
+ if (includeEngineGlobals) {
1257
+ modules.push("engine-globals");
1258
+ }
1259
+ const imports = modules.map((mod) => `import "./${mod}";`).join(`
1260
+ `);
1261
+ writeFileSync3(path8.join(absDir, "index.d.ts"), `${imports}
1262
+
1263
+ export {};
1264
+ `);
1265
+ writeJson2(path8.join(absDir, "package.json"), {
1266
+ name: `@defold-typescript/materialized-${surfaceId}`,
1267
+ types: "index.d.ts"
1268
+ });
1269
+ return { materializedDir: relDir, active: surfaceId };
1270
+ }
1271
+ function ensureGitignoreLine(cwd, line) {
1272
+ const gitignorePath = path8.join(cwd, ".gitignore");
1273
+ if (!existsSync4(gitignorePath)) {
1274
+ writeFileSync3(gitignorePath, `${line}
1275
+ `);
1276
+ return;
1277
+ }
1278
+ const existing = readFileSync6(gitignorePath, "utf8");
1279
+ const present = new Set(existing.split(`
1280
+ `).map((entry) => entry.trim()));
1281
+ if (present.has(line)) {
1282
+ return;
1283
+ }
1284
+ const prefix = existing.endsWith(`
1285
+ `) || existing === "" ? "" : `
1286
+ `;
1287
+ writeFileSync3(gitignorePath, `${existing}${prefix}${line}
1288
+ `);
1289
+ }
1290
+ function ensureMaterializedReference(cwd, materializedDir) {
1291
+ if (materializedDir === null) {
1292
+ return;
1293
+ }
1294
+ const surfaceId = path8.posix.basename(materializedDir);
1295
+ const tsconfigPath = path8.join(cwd, "tsconfig.json");
1296
+ if (existsSync4(tsconfigPath)) {
1297
+ const tsconfig = JSON.parse(readFileSync6(tsconfigPath, "utf8"));
1298
+ const current = tsconfig.compilerOptions ?? {};
1299
+ const alreadyRepointed = JSON.stringify(current.typeRoots) === JSON.stringify([MATERIALIZED_ROOT]) && JSON.stringify(current.types) === JSON.stringify([surfaceId]);
1300
+ if (!alreadyRepointed) {
1301
+ tsconfig.compilerOptions = {
1302
+ ...current,
1303
+ typeRoots: [MATERIALIZED_ROOT],
1304
+ types: [surfaceId]
1305
+ };
1306
+ writeJson2(tsconfigPath, tsconfig);
1307
+ }
1308
+ }
1309
+ ensureGitignoreLine(cwd, `${MATERIALIZED_ROOT}/`);
1310
+ }
1311
+ function resolveCurrentSurfaceGeneratedDir() {
1312
+ const root = resolveTypesPackageRoot();
1313
+ return root === null ? null : path8.join(root, "generated");
1314
+ }
1315
+ async function materializeRefDocSurface(opts) {
1316
+ const { cwd, surfaceId, resolveOpts } = opts;
1317
+ const root = resolveTypesPackageRoot();
1318
+ if (root === null) {
1319
+ return { materializedDir: null, active: null };
1320
+ }
1321
+ const registry = opts.registry ?? loadApiTargetsRegistry();
1322
+ const target = registry.find((t) => t.id === surfaceId);
1323
+ if (target?.source?.kind !== "ref-doc") {
1324
+ return { materializedDir: null, active: null };
1325
+ }
1326
+ const relDir = path8.posix.join(MATERIALIZED_ROOT, surfaceId);
1327
+ const absDir = path8.join(cwd, MATERIALIZED_ROOT, surfaceId);
1328
+ try {
1329
+ const mod = await import(path8.join(root, "scripts", "materialize-version.ts"));
1330
+ const selfContained = { ...target, coreTypesImport: "./core-types" };
1331
+ await mod.materializeVersionedSurface(selfContained, {
1332
+ destDir: absDir,
1333
+ ...resolveOpts ? { resolveOpts } : {}
1334
+ });
1335
+ writeFileSync3(path8.join(absDir, "core-types.d.ts"), CORE_TYPES_REEXPORT);
1336
+ copyFileSync(path8.join(root, "src", "engine-globals.d.ts"), path8.join(absDir, "engine-globals.d.ts"));
1337
+ const indexPath = path8.join(absDir, "index.d.ts");
1338
+ writeFileSync3(indexPath, `import "./engine-globals";
1339
+ ${readFileSync6(indexPath, "utf8")}`);
1340
+ } catch {
1341
+ rmSync(absDir, { recursive: true, force: true });
1342
+ return { materializedDir: null, active: null };
1343
+ }
1344
+ return { materializedDir: relDir, active: surfaceId };
1345
+ }
1346
+
1347
+ // src/setup-debug.ts
1348
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
1349
+ import * as path10 from "node:path";
1350
+
1351
+ // src/boot-path.ts
1352
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
1353
+ import * as path9 from "node:path";
1354
+ var MAIN_COLLECTION_RE = /^main_collection\s*=\s*(.+)$/m;
1355
+ var COMPONENT_RE = /component:\s*\\?"([^"\\]+)\\?"/;
1356
+ var COLLECTION_RE = /collection:\s*\\?"([^"\\]+\.collectionc?)\\?"/;
1357
+ var TOP_LEVEL_ID_RE = /^\s*id:\s*"([^"\\]+)"\s*$/;
1358
+ var TS_SCRIPT_SUFFIX = ".ts.script";
1359
+ function projectPathToAbs(cwd, projectPath) {
1360
+ return path9.join(cwd, projectPath.replace(/^\//, ""));
1361
+ }
1362
+ function relPosix(cwd, abs) {
1363
+ return path9.relative(cwd, abs).split(path9.sep).join("/");
1364
+ }
1365
+ function resolveBootPathScripts(cwd) {
1366
+ const gameProjectPath = path9.join(cwd, "game.project");
1367
+ if (!existsSync5(gameProjectPath)) {
1368
+ return [];
1369
+ }
1370
+ let gameProject;
1371
+ try {
1372
+ gameProject = readFileSync7(gameProjectPath, "utf8");
1373
+ } catch {
1374
+ return [];
1375
+ }
1376
+ const mainMatch = gameProject.match(MAIN_COLLECTION_RE);
1377
+ if (mainMatch?.[1] === undefined) {
1378
+ return [];
1379
+ }
1380
+ const mainCollection = mainMatch[1].trim().replace(/\.collectionc$/, ".collection");
1381
+ const candidates = [];
1382
+ const visited = new Set;
1383
+ const walk = (collectionAbs, tracePrefix) => {
1384
+ if (visited.has(collectionAbs)) {
1385
+ return;
1386
+ }
1387
+ visited.add(collectionAbs);
1388
+ if (!existsSync5(collectionAbs)) {
1389
+ return;
1390
+ }
1391
+ let content;
1392
+ try {
1393
+ content = readFileSync7(collectionAbs, "utf8");
1394
+ } catch {
1395
+ return;
1396
+ }
1397
+ const trace = [...tracePrefix, relPosix(cwd, collectionAbs)];
1398
+ const children = [];
1399
+ let currentId = null;
1400
+ for (const line of content.split(`
1401
+ `)) {
1402
+ const idMatch = line.match(TOP_LEVEL_ID_RE);
1403
+ if (idMatch?.[1] !== undefined) {
1404
+ currentId = idMatch[1];
1405
+ continue;
1406
+ }
1407
+ const componentMatch = line.match(COMPONENT_RE);
1408
+ const component = componentMatch?.[1];
1409
+ if (component?.endsWith(TS_SCRIPT_SUFFIX)) {
1410
+ const candidate = component.replace(/^\//, "").replace(/\.script$/, "");
1411
+ candidates.push({
1412
+ candidate,
1413
+ trace: [...trace, currentId ?? "?", component]
1414
+ });
1415
+ continue;
1416
+ }
1417
+ const collectionMatch = line.match(COLLECTION_RE);
1418
+ if (collectionMatch?.[1] !== undefined) {
1419
+ const ref = collectionMatch[1].replace(/\.collectionc$/, ".collection");
1420
+ children.push(projectPathToAbs(cwd, ref));
1421
+ }
1422
+ }
1423
+ for (const child of children) {
1424
+ walk(child, trace);
1425
+ }
1426
+ };
1427
+ walk(projectPathToAbs(cwd, mainCollection), ["game.project"]);
1428
+ return candidates;
1429
+ }
1430
+
1431
+ // src/setup-debug.ts
1432
+ var LLDEBUGGER_URL = "https://github.com/king8fisher/defold-typescript/releases/download/lldebugger-v1/lldebugger.zip";
1433
+ var LEGACY_BOOTSTRAP_MARKER = "// lldebugger-bootstrap: debug entry, inert in release builds";
1434
+ var AMBIENT_DTS_REL = "src/lldebugger.debug.d.ts";
1435
+ var AMBIENT_DECLARATION = `/** @noResolution */
1436
+ declare module "lldebugger.debug" {
1437
+ export function start(): void;
1438
+ }
1439
+ `;
1440
+ var BLOCK_BEGIN = "// defold-typescript:setup-debug BEGIN — managed block, do not edit";
1441
+ var BLOCK_END = "// defold-typescript:setup-debug END";
1442
+ var MANAGED_BLOCK = `${BLOCK_BEGIN}
1443
+ import * as lldebugger from "lldebugger.debug";
1444
+
1445
+ if (sys.get_engine_info().is_debug) {
1446
+ lldebugger.start();
1447
+ }
1448
+
1449
+ ${BLOCK_END}`;
1450
+ var FACTORY_NAMES = ["defineScript", "defineGuiScript", "defineRenderScript"];
1451
+ var MANUAL_STEPS = [
1452
+ "Install the Local Lua Debugger extension (tomblind.local-lua-debugger-vscode) in VS Code.",
1453
+ "Run Project -> Fetch Libraries in the Defold editor to download the lldebugger module."
1454
+ ];
1455
+ function isProjectHeader(line) {
1456
+ return line.trim() === "[project]";
1457
+ }
1458
+ function isSectionHeader(line) {
1459
+ return /^\[.+\]\s*$/.test(line.trim());
1460
+ }
1461
+ function addLldebuggerDependency(gameProjectText) {
1462
+ const lines = gameProjectText.split(`
1463
+ `);
1464
+ if (!lines.some(isProjectHeader)) {
1465
+ throw new Error("defold-typescript setup-debug: game.project has no [project] section; this is not a Defold project.");
1466
+ }
1467
+ if (gameProjectText.includes(LLDEBUGGER_URL)) {
1468
+ return gameProjectText;
1469
+ }
1470
+ let inProject = false;
1471
+ let maxIndex = -1;
1472
+ let lastDepLine = -1;
1473
+ let lastProjectLine = -1;
1474
+ for (let i = 0;i < lines.length; i++) {
1475
+ const line = lines[i] ?? "";
1476
+ if (isSectionHeader(line)) {
1477
+ inProject = isProjectHeader(line);
1478
+ if (inProject) {
1479
+ lastProjectLine = i;
1480
+ }
1481
+ continue;
1482
+ }
1483
+ if (inProject && line.trim() !== "") {
1484
+ lastProjectLine = i;
1485
+ const dep = line.match(/^dependencies#(\d+)\s*=/);
1486
+ if (dep?.[1] !== undefined) {
1487
+ const n = Number(dep[1]);
1488
+ if (n > maxIndex) {
1489
+ maxIndex = n;
1490
+ }
1491
+ lastDepLine = i;
1492
+ }
1493
+ }
1494
+ }
1495
+ const newLine = `dependencies#${maxIndex + 1} = ${LLDEBUGGER_URL}`;
1496
+ const insertAt = (lastDepLine >= 0 ? lastDepLine : lastProjectLine) + 1;
1497
+ lines.splice(insertAt, 0, newLine);
1498
+ return lines.join(`
1499
+ `);
1500
+ }
1501
+ var LEGACY_BLOCK = `${LEGACY_BOOTSTRAP_MARKER}
1502
+ /** @noResolution */
1503
+ declare module "lldebugger.debug" {
1504
+ export function start(): void;
1505
+ }
1506
+
1507
+ import * as lldebugger from "lldebugger.debug";
1508
+
1509
+ if (sys.get_engine_info().is_debug) {
1510
+ lldebugger.start();
1511
+ }
1512
+ `;
1513
+ function occurrences(haystack, needle) {
1514
+ return haystack.split(needle).length - 1;
1515
+ }
1516
+ function upsertManagedBlock(source) {
1517
+ const begins = occurrences(source, BLOCK_BEGIN);
1518
+ const ends = occurrences(source, BLOCK_END);
1519
+ if (begins !== ends || begins > 1) {
1520
+ throw new Error("defold-typescript setup-debug: malformed managed block (mismatched, duplicate, or out-of-order sentinels); refusing to edit.");
1521
+ }
1522
+ if (begins === 1) {
1523
+ const beginAt = source.indexOf(BLOCK_BEGIN);
1524
+ const endAt = source.indexOf(BLOCK_END);
1525
+ if (endAt < beginAt) {
1526
+ throw new Error("defold-typescript setup-debug: malformed managed block (END before BEGIN); refusing to edit.");
1527
+ }
1528
+ const regionEnd = endAt + BLOCK_END.length;
1529
+ const region = source.slice(beginAt, regionEnd);
1530
+ if (region === MANAGED_BLOCK) {
1531
+ return { text: source, action: "unchanged" };
1532
+ }
1533
+ const text = source.slice(0, beginAt) + MANAGED_BLOCK + source.slice(regionEnd);
1534
+ return { text, action: "refreshed" };
1535
+ }
1536
+ if (source.includes(LEGACY_BLOCK)) {
1537
+ return { text: source.replace(LEGACY_BLOCK, `${MANAGED_BLOCK}
1538
+ `), action: "refreshed" };
1539
+ }
1540
+ return { text: `${MANAGED_BLOCK}
1541
+
1542
+ ${source}`, action: "injected" };
1543
+ }
1544
+ function stripManagedBlock(source) {
1545
+ const begins = occurrences(source, BLOCK_BEGIN);
1546
+ const ends = occurrences(source, BLOCK_END);
1547
+ if (begins === 0 && ends === 0) {
1548
+ return { text: source, removed: false };
1549
+ }
1550
+ if (begins !== ends || begins > 1) {
1551
+ throw new Error("defold-typescript setup-debug: malformed managed block (mismatched, duplicate, or out-of-order sentinels); refusing to edit.");
1552
+ }
1553
+ const beginAt = source.indexOf(BLOCK_BEGIN);
1554
+ const endAt = source.indexOf(BLOCK_END);
1555
+ if (endAt < beginAt) {
1556
+ throw new Error("defold-typescript setup-debug: malformed managed block (END before BEGIN); refusing to edit.");
1557
+ }
1558
+ const regionEnd = endAt + BLOCK_END.length;
1559
+ const after = source.slice(regionEnd).replace(/^\n{1,2}/, "");
1560
+ return { text: source.slice(0, beginAt) + after, removed: true };
972
1561
  }
973
- function materializeApiSurface(opts) {
974
- const { cwd, surface, sourceGeneratedDir } = opts;
975
- if (!surface.available || surface.surfaceId === null || sourceGeneratedDir === null) {
976
- return { materializedDir: null, active: null };
1562
+ function hasFactoryCall(text) {
1563
+ return FACTORY_NAMES.some((name) => text.includes(`${name}(`));
1564
+ }
1565
+ function findEntryScriptCandidates(cwd, scanner = scanFilesSync) {
1566
+ const srcDir = path10.join(cwd, "src");
1567
+ if (!existsSync6(srcDir)) {
1568
+ return [];
977
1569
  }
978
- const { surfaceId } = surface;
979
- const relDir = path7.posix.join(MATERIALIZED_ROOT, surfaceId);
980
- const absDir = path7.join(cwd, MATERIALIZED_ROOT, surfaceId);
981
- mkdirSync3(absDir, { recursive: true });
982
- const excluded = excludedModulesForKind(opts.scriptKind ?? null);
983
- const sources = listDts(sourceGeneratedDir).filter((file) => file !== "index.d.ts").filter((file) => !excluded.has(file.replace(/\.d\.ts$/, "")));
984
- const srcDir = path7.resolve(sourceGeneratedDir, "..", "src");
985
- const overloads = ["msg-overloads.d.ts", "message-guard.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path7.join(srcDir, file)));
986
- const coreTypesSrc = path7.join(srcDir, "core-types.ts");
987
- const includeCoreTypes = overloads.length > 0 && existsSync3(coreTypesSrc);
988
- const engineGlobalsSrc = path7.join(srcDir, "engine-globals.d.ts");
989
- const includeEngineGlobals = includeCoreTypes && existsSync3(engineGlobalsSrc);
990
- const wanted = new Set(sources);
991
- for (const file of overloads) {
992
- wanted.add(file);
1570
+ return scanner(cwd, "src/**/*.ts").map(normalizeScannedPath).filter((rel) => !isSkipped(rel)).filter((rel) => hasFactoryCall(readFileSync8(path10.join(cwd, rel), "utf8"))).sort();
1571
+ }
1572
+ function failure(error) {
1573
+ return { ok: false, written: [], actions: {}, manualSteps: MANUAL_STEPS, error };
1574
+ }
1575
+ async function defaultChooseScript(candidates) {
1576
+ const { select } = await import("@inquirer/prompts");
1577
+ return select({
1578
+ message: "Select the entry script to receive the debugger bootstrap:",
1579
+ choices: candidates.map((candidate) => ({ name: candidate, value: candidate }))
1580
+ });
1581
+ }
1582
+ async function pickCandidate(candidates, opts) {
1583
+ if (candidates.length === 1) {
1584
+ return candidates[0];
993
1585
  }
994
- if (includeCoreTypes) {
995
- wanted.add("core-types.d.ts");
1586
+ const chooser = opts.chooseScript ?? (opts.json ? undefined : defaultChooseScript);
1587
+ if (chooser === undefined) {
1588
+ return failure(`defold-typescript setup-debug: multiple entry scripts found (${candidates.join(", ")}); pass --script to choose one.`);
996
1589
  }
997
- if (includeEngineGlobals) {
998
- wanted.add("engine-globals.d.ts");
1590
+ return chooser(candidates);
1591
+ }
1592
+ async function resolveTargetScript(opts) {
1593
+ const { cwd } = opts;
1594
+ const script = opts.script === undefined ? undefined : normalizeScannedPath(opts.script);
1595
+ const bootCandidates = resolveBootPathScripts(cwd).filter((entry) => existsSync6(path10.join(cwd, entry.candidate)));
1596
+ if (script !== undefined) {
1597
+ if (!existsSync6(path10.join(cwd, script))) {
1598
+ return failure(`defold-typescript setup-debug: script not found: ${script}`);
1599
+ }
1600
+ const onPath = bootCandidates.find((entry) => entry.candidate === script);
1601
+ return { target: script, bootPath: onPath?.trace ?? [] };
999
1602
  }
1000
- for (const existing of readdirSync2(absDir)) {
1001
- if (existing.endsWith(".d.ts") && existing !== "index.d.ts" && !wanted.has(existing)) {
1002
- rmSync(path7.join(absDir, existing));
1603
+ if (bootCandidates.length > 0) {
1604
+ const picked2 = await pickCandidate(bootCandidates.map((entry) => entry.candidate), opts);
1605
+ if (typeof picked2 !== "string") {
1606
+ return picked2;
1003
1607
  }
1608
+ const onPath = bootCandidates.find((entry) => entry.candidate === picked2);
1609
+ return { target: picked2, bootPath: onPath?.trace ?? [] };
1004
1610
  }
1005
- for (const file of sources) {
1006
- writeFileSync3(path7.join(absDir, file), readFileSync6(path7.join(sourceGeneratedDir, file), "utf8"));
1611
+ const scanned = findEntryScriptCandidates(cwd, opts.scanFiles);
1612
+ if (scanned.length === 0) {
1613
+ return failure("defold-typescript setup-debug: no entry script with a lifecycle factory call found under src/.");
1007
1614
  }
1008
- if (includeCoreTypes) {
1009
- writeFileSync3(path7.join(absDir, "core-types.d.ts"), readFileSync6(coreTypesSrc, "utf8"));
1615
+ const picked = await pickCandidate(scanned, opts);
1616
+ if (typeof picked !== "string") {
1617
+ return picked;
1010
1618
  }
1011
- if (includeEngineGlobals) {
1012
- writeFileSync3(path7.join(absDir, "engine-globals.d.ts"), readFileSync6(engineGlobalsSrc, "utf8"));
1619
+ return { target: picked, bootPath: [] };
1620
+ }
1621
+ async function runSetupDebug(opts) {
1622
+ const { cwd } = opts;
1623
+ const gameProjectPath = path10.join(cwd, "game.project");
1624
+ if (!existsSync6(gameProjectPath)) {
1625
+ return failure("defold-typescript setup-debug: no game.project here; this is not a Defold project.");
1013
1626
  }
1014
- for (const file of overloads) {
1015
- writeFileSync3(path7.join(absDir, file), readFileSync6(path7.join(srcDir, file), "utf8"));
1627
+ const resolved = await resolveTargetScript(opts);
1628
+ if (!("target" in resolved)) {
1629
+ return resolved;
1016
1630
  }
1017
- const modules = [...sources, ...overloads].map((file) => file.replace(/\.d\.ts$/, ""));
1018
- if (includeEngineGlobals) {
1019
- modules.push("engine-globals");
1631
+ const { target, bootPath } = resolved;
1632
+ const written = [];
1633
+ const actions = {};
1634
+ const dtsPath = path10.join(cwd, AMBIENT_DTS_REL);
1635
+ mkdirSync5(path10.dirname(dtsPath), { recursive: true });
1636
+ const existingDts = existsSync6(dtsPath) ? readFileSync8(dtsPath, "utf8") : null;
1637
+ if (existingDts === null) {
1638
+ writeFileSync4(dtsPath, AMBIENT_DECLARATION);
1639
+ actions[AMBIENT_DTS_REL] = "injected";
1640
+ } else if (existingDts !== AMBIENT_DECLARATION) {
1641
+ writeFileSync4(dtsPath, AMBIENT_DECLARATION);
1642
+ actions[AMBIENT_DTS_REL] = "refreshed";
1643
+ } else {
1644
+ actions[AMBIENT_DTS_REL] = "unchanged";
1645
+ }
1646
+ written.push(AMBIENT_DTS_REL);
1647
+ const scriptPath = path10.join(cwd, target);
1648
+ const source = readFileSync8(scriptPath, "utf8");
1649
+ const { text: injected, action: scriptAction } = upsertManagedBlock(source);
1650
+ if (injected !== source) {
1651
+ writeFileSync4(scriptPath, injected);
1652
+ }
1653
+ actions[target] = scriptAction;
1654
+ written.push(target);
1655
+ const gameProjectText = readFileSync8(gameProjectPath, "utf8");
1656
+ const updated = addLldebuggerDependency(gameProjectText);
1657
+ if (updated !== gameProjectText) {
1658
+ writeFileSync4(gameProjectPath, updated);
1659
+ actions["game.project"] = "injected";
1660
+ } else {
1661
+ actions["game.project"] = "unchanged";
1020
1662
  }
1021
- const imports = modules.map((mod) => `import "./${mod}";`).join(`
1022
- `);
1023
- writeFileSync3(path7.join(absDir, "index.d.ts"), `${imports}
1663
+ written.push("game.project");
1664
+ const removedFrom = [];
1665
+ for (const scannedRel of (opts.scanFiles ?? scanFilesSync)(cwd, "src/**/*.ts")) {
1666
+ const rel = normalizeScannedPath(scannedRel);
1667
+ if (isSkipped(rel) || rel === target) {
1668
+ continue;
1669
+ }
1670
+ const otherPath = path10.join(cwd, rel);
1671
+ const otherSource = readFileSync8(otherPath, "utf8");
1672
+ let stripped;
1673
+ try {
1674
+ stripped = stripManagedBlock(otherSource);
1675
+ } catch {
1676
+ continue;
1677
+ }
1678
+ if (stripped.removed) {
1679
+ writeFileSync4(otherPath, stripped.text);
1680
+ actions[rel] = "removed";
1681
+ removedFrom.push(rel);
1682
+ }
1683
+ }
1684
+ removedFrom.sort();
1685
+ return {
1686
+ ok: true,
1687
+ written,
1688
+ actions,
1689
+ manualSteps: MANUAL_STEPS,
1690
+ addedTo: target,
1691
+ removedFrom,
1692
+ bootPath
1693
+ };
1694
+ }
1024
1695
 
1025
- export {};
1026
- `);
1027
- writeJson2(path7.join(absDir, "package.json"), {
1028
- name: `@defold-typescript/materialized-${surfaceId}`,
1029
- types: "index.d.ts"
1030
- });
1031
- return { materializedDir: relDir, active: surfaceId };
1696
+ // src/wall.ts
1697
+ import { existsSync as existsSync8, readFileSync as readFileSync10, rmSync as rmSync2 } from "node:fs";
1698
+ import * as path12 from "node:path";
1699
+
1700
+ // src/directory-walls.ts
1701
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
1702
+ import * as path11 from "node:path";
1703
+ function describeWall(dir, kind) {
1704
+ return {
1705
+ dir,
1706
+ kind,
1707
+ typesEntrypoint: selectScriptKindEntrypoint(new Set([kind]))
1708
+ };
1032
1709
  }
1033
- function ensureGitignoreLine(cwd, line) {
1034
- const gitignorePath = path7.join(cwd, ".gitignore");
1035
- if (!existsSync3(gitignorePath)) {
1036
- writeFileSync3(gitignorePath, `${line}
1037
- `);
1038
- return;
1710
+ function groupSourceScriptKindsByDirectory(cwd) {
1711
+ const byDir = new Map;
1712
+ const seen = new Set;
1713
+ for (const pattern of readBuildConfig(cwd).include) {
1714
+ for (const match of scanFilesSync(cwd, pattern)) {
1715
+ const rel = match.split(path11.sep).join("/");
1716
+ if (seen.has(rel) || !isTranspilerSource(rel) || isSkipped(rel)) {
1717
+ continue;
1718
+ }
1719
+ seen.add(rel);
1720
+ const kind = detectSourceOutputKind(readFileSync9(path11.join(cwd, match), "utf8"));
1721
+ if (kind === "module") {
1722
+ continue;
1723
+ }
1724
+ const dir = path11.posix.dirname(rel);
1725
+ let set = byDir.get(dir);
1726
+ if (set === undefined) {
1727
+ set = new Set;
1728
+ byDir.set(dir, set);
1729
+ }
1730
+ set.add(kind);
1731
+ }
1039
1732
  }
1040
- const existing = readFileSync6(gitignorePath, "utf8");
1041
- const present = new Set(existing.split(`
1042
- `).map((entry) => entry.trim()));
1043
- if (present.has(line)) {
1044
- return;
1733
+ return byDir;
1734
+ }
1735
+ function planSourceDirectoryWalls(cwd) {
1736
+ const walls = [];
1737
+ for (const [dir, kinds] of groupSourceScriptKindsByDirectory(cwd)) {
1738
+ const kind = selectScriptKind(kinds);
1739
+ if (kind !== null) {
1740
+ walls.push(describeWall(dir, kind));
1741
+ }
1045
1742
  }
1046
- const prefix = existing.endsWith(`
1047
- `) || existing === "" ? "" : `
1048
- `;
1049
- writeFileSync3(gitignorePath, `${existing}${prefix}${line}
1743
+ return walls.sort((a, b) => a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0);
1744
+ }
1745
+ function directoryWallTsconfig(wall) {
1746
+ const depth = wall.dir.split("/").length;
1747
+ return {
1748
+ extends: `${"../".repeat(depth)}tsconfig.json`,
1749
+ compilerOptions: { composite: true, typeRoots: null, types: [wall.typesEntrypoint] },
1750
+ include: ["**/*.ts"],
1751
+ exclude: []
1752
+ };
1753
+ }
1754
+ function writeJson3(filePath, value) {
1755
+ mkdirSync6(path11.dirname(filePath), { recursive: true });
1756
+ writeFileSync5(filePath, `${JSON.stringify(value, null, 2)}
1050
1757
  `);
1051
1758
  }
1052
- function ensureMaterializedReference(cwd, materializedDir) {
1053
- if (materializedDir === null) {
1054
- return;
1759
+ function sortedWallDirs(walls) {
1760
+ return walls.map((w) => w.dir).filter((dir) => dir !== ".").sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1761
+ }
1762
+ function isInsideAnyDir(rel, dirs) {
1763
+ return dirs.some((dir) => rel === dir || rel.startsWith(`${dir}/`));
1764
+ }
1765
+ function hasRootOwnedTranspilerSources(cwd, wallDirs) {
1766
+ const seen = new Set;
1767
+ for (const pattern of readBuildConfig(cwd).include) {
1768
+ for (const match of scanFilesSync(cwd, pattern)) {
1769
+ const rel = match.split(path11.sep).join("/");
1770
+ if (seen.has(rel) || !isTranspilerSource(rel) || isSkipped(rel)) {
1771
+ continue;
1772
+ }
1773
+ seen.add(rel);
1774
+ if (!isInsideAnyDir(rel, wallDirs)) {
1775
+ return true;
1776
+ }
1777
+ }
1055
1778
  }
1056
- const surfaceId = path7.posix.basename(materializedDir);
1057
- const tsconfigPath = path7.join(cwd, "tsconfig.json");
1058
- if (existsSync3(tsconfigPath)) {
1059
- const tsconfig = JSON.parse(readFileSync6(tsconfigPath, "utf8"));
1060
- const current = tsconfig.compilerOptions ?? {};
1061
- const alreadyRepointed = JSON.stringify(current.typeRoots) === JSON.stringify([MATERIALIZED_ROOT]) && JSON.stringify(current.types) === JSON.stringify([surfaceId]);
1062
- if (!alreadyRepointed) {
1063
- tsconfig.compilerOptions = {
1064
- ...current,
1065
- typeRoots: [MATERIALIZED_ROOT],
1066
- types: [surfaceId]
1067
- };
1068
- writeJson2(tsconfigPath, tsconfig);
1779
+ return false;
1780
+ }
1781
+ function wireWallReferences(cwd, walls) {
1782
+ const rootPath = path11.join(cwd, "tsconfig.json");
1783
+ const current = JSON.parse(readFileSync9(rootPath, "utf8"));
1784
+ const wallDirs = sortedWallDirs(walls);
1785
+ const previousReferences = current.references ?? [];
1786
+ const previousManaged = new Set(previousReferences.map((ref) => ref.path));
1787
+ const nextExclude = [
1788
+ ...new Set([
1789
+ ...(current.exclude ?? []).filter((entry) => !previousManaged.has(entry)),
1790
+ ...wallDirs
1791
+ ])
1792
+ ];
1793
+ const next = { ...current };
1794
+ if (wallDirs.length > 0) {
1795
+ next.references = wallDirs.map((dir) => ({ path: dir }));
1796
+ } else {
1797
+ delete next.references;
1798
+ }
1799
+ if (nextExclude.length > 0) {
1800
+ next.exclude = nextExclude;
1801
+ } else {
1802
+ delete next.exclude;
1803
+ }
1804
+ if (wallDirs.length > 0 && !hasRootOwnedTranspilerSources(cwd, wallDirs)) {
1805
+ next.files = [];
1806
+ } else if (previousReferences.length > 0 && JSON.stringify(next.files) === JSON.stringify([])) {
1807
+ delete next.files;
1808
+ }
1809
+ if (JSON.stringify(next) !== JSON.stringify(current)) {
1810
+ writeJson3(rootPath, next);
1811
+ }
1812
+ }
1813
+ function writeDirectoryWallTsconfigs(cwd, walls) {
1814
+ const written = [];
1815
+ for (const w of walls) {
1816
+ if (w.dir === ".") {
1817
+ continue;
1818
+ }
1819
+ const rel = `${w.dir}/tsconfig.json`;
1820
+ const target = path11.join(cwd, w.dir, "tsconfig.json");
1821
+ const desired = directoryWallTsconfig(w);
1822
+ if (existsSync7(target)) {
1823
+ const current = JSON.parse(readFileSync9(target, "utf8"));
1824
+ const options = current.compilerOptions ?? {};
1825
+ const alreadyNarrowed = current.extends === desired.extends && options.composite === desired.compilerOptions.composite && options.typeRoots === desired.compilerOptions.typeRoots && JSON.stringify(options.types) === JSON.stringify(desired.compilerOptions.types) && JSON.stringify(current.include) === JSON.stringify(desired.include) && JSON.stringify(current.exclude) === JSON.stringify(desired.exclude);
1826
+ if (!alreadyNarrowed) {
1827
+ writeJson3(target, {
1828
+ ...current,
1829
+ extends: desired.extends,
1830
+ compilerOptions: {
1831
+ ...options,
1832
+ composite: desired.compilerOptions.composite,
1833
+ typeRoots: desired.compilerOptions.typeRoots,
1834
+ types: desired.compilerOptions.types
1835
+ },
1836
+ include: desired.include,
1837
+ exclude: desired.exclude
1838
+ });
1839
+ written.push(rel);
1840
+ }
1841
+ } else {
1842
+ writeJson3(target, desired);
1843
+ written.push(rel);
1069
1844
  }
1070
1845
  }
1071
- ensureGitignoreLine(cwd, `${MATERIALIZED_ROOT}/`);
1846
+ return written.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1072
1847
  }
1073
- function resolveCurrentSurfaceGeneratedDir() {
1074
- const root = resolveTypesPackageRoot();
1075
- return root === null ? null : path7.join(root, "generated");
1848
+
1849
+ // src/wall.ts
1850
+ function sortDirs(items) {
1851
+ return items.sort((a, b) => a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0);
1076
1852
  }
1077
- async function materializeRefDocSurface(opts) {
1078
- const { cwd, surfaceId, resolveOpts } = opts;
1079
- const root = resolveTypesPackageRoot();
1080
- if (root === null) {
1081
- return { materializedDir: null, active: null };
1082
- }
1083
- const registry = opts.registry ?? loadApiTargetsRegistry();
1084
- const target = registry.find((t) => t.id === surfaceId);
1085
- if (target?.source?.kind !== "ref-doc") {
1086
- return { materializedDir: null, active: null };
1853
+ function currentWalledDirs(cwd) {
1854
+ const rootPath = path12.join(cwd, "tsconfig.json");
1855
+ if (!existsSync8(rootPath)) {
1856
+ return [];
1087
1857
  }
1088
- const excludeModules = [...excludedModulesForKind(opts.scriptKind ?? null)];
1089
- const relDir = path7.posix.join(MATERIALIZED_ROOT, surfaceId);
1090
- const absDir = path7.join(cwd, MATERIALIZED_ROOT, surfaceId);
1091
1858
  try {
1092
- const mod = await import(path7.join(root, "scripts", "materialize-version.ts"));
1093
- const selfContained = { ...target, coreTypesImport: "./core-types" };
1094
- await mod.materializeVersionedSurface(selfContained, {
1095
- destDir: absDir,
1096
- ...resolveOpts ? { resolveOpts } : {},
1097
- ...excludeModules.length > 0 ? { excludeModules } : {}
1098
- });
1099
- copyFileSync(path7.join(root, "src", "core-types.ts"), path7.join(absDir, "core-types.d.ts"));
1100
- copyFileSync(path7.join(root, "src", "engine-globals.d.ts"), path7.join(absDir, "engine-globals.d.ts"));
1101
- const indexPath = path7.join(absDir, "index.d.ts");
1102
- writeFileSync3(indexPath, `import "./engine-globals";
1103
- ${readFileSync6(indexPath, "utf8")}`);
1859
+ const parsed = JSON.parse(readFileSync10(rootPath, "utf8"));
1860
+ return (parsed.references ?? []).map((ref) => ref.path).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1104
1861
  } catch {
1105
- rmSync(absDir, { recursive: true, force: true });
1106
- return { materializedDir: null, active: null };
1862
+ return [];
1107
1863
  }
1108
- return { materializedDir: relDir, active: surfaceId };
1864
+ }
1865
+ function eligibleWalls(cwd) {
1866
+ return planSourceDirectoryWalls(cwd);
1867
+ }
1868
+ function applyWallSelection(cwd, desiredDirs) {
1869
+ const byDir = new Map(eligibleWalls(cwd).map((wall) => [wall.dir, wall]));
1870
+ const desired = [];
1871
+ const desiredSet = new Set;
1872
+ for (const dir of desiredDirs) {
1873
+ if (desiredSet.has(dir)) {
1874
+ continue;
1875
+ }
1876
+ const wall = byDir.get(dir);
1877
+ if (wall === undefined) {
1878
+ throw new Error(`defold-typescript wall: ${dir} is not a single-kind source directory that can be walled`);
1879
+ }
1880
+ desired.push(wall);
1881
+ desiredSet.add(dir);
1882
+ }
1883
+ for (const dir of currentWalledDirs(cwd)) {
1884
+ if (!desiredSet.has(dir)) {
1885
+ rmSync2(path12.join(cwd, dir, "tsconfig.json"), { force: true });
1886
+ }
1887
+ }
1888
+ writeDirectoryWallTsconfigs(cwd, desired);
1889
+ wireWallReferences(cwd, desired);
1890
+ return sortDirs(desired);
1891
+ }
1892
+
1893
+ // src/wall-interactive.ts
1894
+ function buildWallChoices(cwd) {
1895
+ const current = new Set(currentWalledDirs(cwd));
1896
+ const choices = [];
1897
+ for (const [dir, kinds] of groupSourceScriptKindsByDirectory(cwd)) {
1898
+ const kind = selectScriptKind(kinds);
1899
+ if (kind === null) {
1900
+ choices.push({ value: dir, name: dir, disabled: `mixed: ${[...kinds].sort().join(", ")}` });
1901
+ } else {
1902
+ choices.push({ value: dir, name: `${dir} (${kind})`, checked: current.has(dir) });
1903
+ }
1904
+ }
1905
+ return choices.sort((a, b) => a.value < b.value ? -1 : a.value > b.value ? 1 : 0);
1906
+ }
1907
+ async function defaultCheckbox() {
1908
+ const { checkbox } = await import("@inquirer/prompts");
1909
+ return (opts) => checkbox({ message: opts.message, choices: opts.choices });
1910
+ }
1911
+ async function runWallInteractive(cwd, deps = {}) {
1912
+ const checkbox = deps.checkbox ?? await defaultCheckbox();
1913
+ const selection = await checkbox({
1914
+ message: "Select the source directories to wall (space toggles, enter confirms):",
1915
+ choices: buildWallChoices(cwd)
1916
+ });
1917
+ return applyWallSelection(cwd, selection);
1109
1918
  }
1110
1919
 
1111
1920
  // src/watch.ts
1112
- import { existsSync as existsSync4, watch as fsWatch } from "node:fs";
1113
- import * as path9 from "node:path";
1921
+ import { existsSync as existsSync9, watch as fsWatch } from "node:fs";
1922
+ import * as path14 from "node:path";
1114
1923
 
1115
1924
  // src/build-session.ts
1116
- import { readFileSync as readFileSync7, rmSync as rmSync2 } from "node:fs";
1117
- import * as path8 from "node:path";
1925
+ import { readFileSync as readFileSync11, rmSync as rmSync3 } from "node:fs";
1926
+ import * as path13 from "node:path";
1118
1927
  import {
1119
1928
  createTranspileSession
1120
1929
  } from "@defold-typescript/transpiler";
@@ -1122,7 +1931,14 @@ function createBuildSession(opts) {
1122
1931
  const { cwd } = opts;
1123
1932
  const config = readBuildConfig(cwd);
1124
1933
  const session = createTranspileSession();
1125
- function writeOutputs(result, keys) {
1934
+ function pruneOutputs(rel, keepRel) {
1935
+ for (const outputRel of outputRelsForSource(rel, config)) {
1936
+ if (outputRel !== keepRel && outputRel !== `${keepRel}.map`) {
1937
+ rmSync3(path13.join(cwd, outputRel), { force: true });
1938
+ }
1939
+ }
1940
+ }
1941
+ function writeOutputs(result, keys, sources, pruneAlternatives = false) {
1126
1942
  const failures = collectFailures(result.diagnostics);
1127
1943
  const written = [];
1128
1944
  for (const rel of keys) {
@@ -1133,12 +1949,15 @@ function createBuildSession(opts) {
1133
1949
  if (lua === undefined) {
1134
1950
  continue;
1135
1951
  }
1136
- const scriptRel = computeScriptRel(rel, config);
1137
- writeScriptFile(cwd, scriptRel, lua, result.sourceMaps[rel]);
1138
- written.push(scriptRel);
1952
+ const outputRel = computeOutputRel(rel, config, detectSourceOutputKind(sources[rel] ?? ""));
1953
+ if (pruneAlternatives) {
1954
+ pruneOutputs(rel, outputRel);
1955
+ }
1956
+ writeScriptFile(cwd, outputRel, lua, result.sourceMaps[rel]);
1957
+ written.push(outputRel);
1139
1958
  }
1140
1959
  throwIfFailures(failures);
1141
- return { written };
1960
+ return { written: written.sort() };
1142
1961
  }
1143
1962
  function buildAll() {
1144
1963
  const seen = new Set;
@@ -1153,28 +1972,30 @@ function createBuildSession(opts) {
1153
1972
  }
1154
1973
  const files = {};
1155
1974
  for (const rel of sources) {
1156
- files[rel] = readFileSync7(path8.join(cwd, rel), "utf8");
1975
+ files[rel] = readFileSync11(path13.join(cwd, rel), "utf8");
1157
1976
  }
1158
1977
  const result = session.update(files);
1159
- return writeOutputs(result, sources);
1978
+ return writeOutputs(result, sources, files);
1160
1979
  }
1161
1980
  function applyEvents(changed, removed) {
1162
1981
  const sourceChanged = changed.filter(isTranspilerSource);
1163
1982
  const sourceRemoved = removed.filter(isTranspilerSource);
1164
1983
  const changes = {};
1165
1984
  for (const rel of sourceChanged) {
1166
- changes[rel] = readFileSync7(path8.join(cwd, rel), "utf8");
1985
+ changes[rel] = readFileSync11(path13.join(cwd, rel), "utf8");
1167
1986
  }
1168
1987
  for (const rel of sourceRemoved) {
1169
1988
  changes[rel] = null;
1170
1989
  }
1171
1990
  const result = session.update(changes);
1172
1991
  for (const rel of sourceRemoved) {
1173
- const scriptAbs = path8.join(cwd, computeScriptRel(rel, config));
1174
- rmSync2(scriptAbs, { force: true });
1175
- rmSync2(`${scriptAbs}.map`, { force: true });
1992
+ pruneOutputs(rel);
1993
+ }
1994
+ const changedSources = {};
1995
+ for (const rel of sourceChanged) {
1996
+ changedSources[rel] = changes[rel] ?? "";
1176
1997
  }
1177
- return writeOutputs(result, sourceChanged);
1998
+ return writeOutputs(result, sourceChanged, changedSources, true);
1178
1999
  }
1179
2000
  return { buildAll, applyEvents };
1180
2001
  }
@@ -1213,7 +2034,7 @@ function runWatch(opts) {
1213
2034
  opts.syncSurface?.();
1214
2035
  session = createBuildSession({ cwd });
1215
2036
  const { written } = session.buildAll();
1216
- stdout.write(formatBuildLine(written));
2037
+ stdout.write(opts.json ? renderWatchEvent({ event: "build", written }) : formatBuildLine(written));
1217
2038
  } catch (err) {
1218
2039
  rejectDone(rewrapInitError(err));
1219
2040
  return {
@@ -1222,7 +2043,7 @@ function runWatch(opts) {
1222
2043
  waitForIdle: () => Promise.resolve()
1223
2044
  };
1224
2045
  }
1225
- const srcDir = path9.join(cwd, "src");
2046
+ const srcDir = path14.join(cwd, "src");
1226
2047
  let scheduled = null;
1227
2048
  let syncScheduled = null;
1228
2049
  let rebuildBusy = false;
@@ -1246,7 +2067,7 @@ function runWatch(opts) {
1246
2067
  const removed = [];
1247
2068
  for (const rel of drained) {
1248
2069
  const key = `src/${toPosix(rel)}`;
1249
- if (existsSync4(path9.join(srcDir, rel))) {
2070
+ if (existsSync9(path14.join(srcDir, rel))) {
1250
2071
  changed.push(key);
1251
2072
  } else {
1252
2073
  removed.push(key);
@@ -1254,11 +2075,15 @@ function runWatch(opts) {
1254
2075
  }
1255
2076
  try {
1256
2077
  const { written } = session.applyEvents(changed, removed);
1257
- stdout.write(formatBuildLine(written));
2078
+ stdout.write(opts.json ? renderWatchEvent({ event: "rebuild", written, changed, removed }) : formatBuildLine(written));
1258
2079
  } catch (err) {
1259
2080
  const message = err instanceof Error ? err.message : String(err);
1260
- stderr.write(`${message}
2081
+ if (opts.json) {
2082
+ stdout.write(renderWatchEvent({ event: "rebuild", error: message }));
2083
+ } else {
2084
+ stderr.write(`${message}
1261
2085
  `);
2086
+ }
1262
2087
  }
1263
2088
  rebuildBusy = false;
1264
2089
  notifyIdle();
@@ -1328,7 +2153,9 @@ function runWatch(opts) {
1328
2153
  }
1329
2154
 
1330
2155
  // src/dispatch.ts
1331
- var USAGE = `Usage: defold-typescript <init|build|watch> [path]
2156
+ var USAGE = `Usage: defold-typescript <init|build|watch|wall|setup-debug|defold> [path]
2157
+ `;
2158
+ var DEFOLD_USAGE = `Usage: defold-typescript defold <resolve|build|bundle> [path]
1332
2159
  `;
1333
2160
  function parseDefoldVersionFlag(argv) {
1334
2161
  let flag;
@@ -1346,13 +2173,47 @@ function parseDefoldVersionFlag(argv) {
1346
2173
  }
1347
2174
  return { flag, rest };
1348
2175
  }
2176
+ function parseScriptFlag(argv) {
2177
+ let script;
2178
+ const rest = [];
2179
+ for (let i = 0;i < argv.length; i++) {
2180
+ const arg = argv[i];
2181
+ if (arg === "--script") {
2182
+ script = argv[i + 1];
2183
+ i++;
2184
+ } else if (arg?.startsWith("--script=")) {
2185
+ script = arg.slice("--script=".length);
2186
+ } else if (arg !== undefined) {
2187
+ rest.push(arg);
2188
+ }
2189
+ }
2190
+ return { script, rest };
2191
+ }
2192
+ function parseValueFlag(argv, name) {
2193
+ const long = `--${name}`;
2194
+ const eq = `${long}=`;
2195
+ let value;
2196
+ const rest = [];
2197
+ for (let i = 0;i < argv.length; i++) {
2198
+ const arg = argv[i];
2199
+ if (arg === long) {
2200
+ value = argv[i + 1];
2201
+ i++;
2202
+ } else if (arg?.startsWith(eq)) {
2203
+ value = arg.slice(eq.length);
2204
+ } else if (arg !== undefined) {
2205
+ rest.push(arg);
2206
+ }
2207
+ }
2208
+ return { value, rest };
2209
+ }
1349
2210
  function readProjectPin(cwd) {
1350
- const pkgPath = path10.join(cwd, "package.json");
1351
- if (!existsSync5(pkgPath)) {
2211
+ const pkgPath = path15.join(cwd, "package.json");
2212
+ if (!existsSync10(pkgPath)) {
1352
2213
  return;
1353
2214
  }
1354
2215
  try {
1355
- return readDefoldVersionPin(JSON.parse(readFileSync8(pkgPath, "utf8")));
2216
+ return readDefoldVersionPin(JSON.parse(readFileSync12(pkgPath, "utf8")));
1356
2217
  } catch {
1357
2218
  return;
1358
2219
  }
@@ -1367,10 +2228,16 @@ function dispatch(argv, io, internals) {
1367
2228
  return 0;
1368
2229
  }
1369
2230
  const force = argv.includes("--force");
1370
- const { flag: defoldVersionFlag, rest: nonFlagArgs } = parseDefoldVersionFlag(argv);
1371
- const positional = nonFlagArgs.filter((a) => a !== "--json" && a !== "--force");
2231
+ const suppressInstallReminder = argv.includes("--suppress-install-reminder");
2232
+ const wallRemove = argv.includes("--remove");
2233
+ const wallList = argv.includes("--list");
2234
+ const { flag: defoldVersionFlag, rest: afterVersionArgs } = parseDefoldVersionFlag(argv);
2235
+ const { script: scriptFlag, rest: afterScriptArgs } = parseScriptFlag(afterVersionArgs);
2236
+ const { value: javaFlag, rest: afterJavaArgs } = parseValueFlag(afterScriptArgs, "java");
2237
+ const { value: buildServerFlag, rest: nonFlagArgs } = parseValueFlag(afterJavaArgs, "build-server");
2238
+ const positional = nonFlagArgs.filter((a) => a !== "--json" && a !== "--force" && a !== "--suppress-install-reminder" && a !== "--remove" && a !== "--list");
1372
2239
  const [command, ...rest] = positional;
1373
- const cwd = rest[0] ? path10.resolve(rest[0]) : process.cwd();
2240
+ const cwd = rest[0] ? path15.resolve(rest[0]) : process.cwd();
1374
2241
  const pin = readProjectPin(cwd);
1375
2242
  const resolvedVersion = resolveDefoldVersion({
1376
2243
  ...defoldVersionFlag !== undefined ? { flag: defoldVersionFlag } : {},
@@ -1380,18 +2247,22 @@ function dispatch(argv, io, internals) {
1380
2247
  const apiSurface = surface.surfaceId;
1381
2248
  if (command === "init") {
1382
2249
  try {
1383
- const { written, scriptKind } = runInit({ cwd, force });
2250
+ const { written } = runInit({ cwd, force });
1384
2251
  if (json) {
1385
2252
  io.stdout.write(renderResult({
1386
2253
  command: "init",
1387
2254
  written,
1388
2255
  defoldVersion: resolvedVersion,
1389
2256
  apiSurface,
1390
- scriptKind
2257
+ installCommand: installHint()
1391
2258
  }));
1392
2259
  } else {
1393
2260
  io.stdout.write(`defold-typescript init: wrote ${written.length} files: ${written.join(", ")}
1394
2261
  `);
2262
+ if (!suppressInstallReminder) {
2263
+ io.stdout.write(`Next: run \`${installHint()}\` to install dependencies.
2264
+ `);
2265
+ }
1395
2266
  }
1396
2267
  return 0;
1397
2268
  } catch (err) {
@@ -1405,8 +2276,52 @@ function dispatch(argv, io, internals) {
1405
2276
  return 1;
1406
2277
  }
1407
2278
  }
2279
+ if (command === "setup-debug") {
2280
+ return (async () => {
2281
+ const result = await runSetupDebug({
2282
+ cwd,
2283
+ json,
2284
+ ...scriptFlag !== undefined ? { script: scriptFlag } : {}
2285
+ });
2286
+ if (json) {
2287
+ io.stdout.write(renderResult(result.ok ? {
2288
+ command: "setup-debug",
2289
+ written: result.written,
2290
+ actions: result.actions,
2291
+ manualSteps: result.manualSteps,
2292
+ ...result.addedTo !== undefined ? { addedTo: result.addedTo } : {},
2293
+ removedFrom: result.removedFrom ?? [],
2294
+ bootPath: result.bootPath ?? []
2295
+ } : { command: "setup-debug", error: result.error ?? "setup-debug failed" }));
2296
+ } else if (result.ok) {
2297
+ io.stdout.write(`defold-typescript setup-debug: wrote ${result.written.length} files: ${result.written.join(", ")}
2298
+ `);
2299
+ if (result.addedTo !== undefined) {
2300
+ io.stdout.write(`Debugger bootstrap added to: ${result.addedTo}
2301
+ `);
2302
+ }
2303
+ if (result.removedFrom !== undefined && result.removedFrom.length > 0) {
2304
+ io.stdout.write(`Removed stale bootstrap from: ${result.removedFrom.join(", ")}
2305
+ `);
2306
+ }
2307
+ if (result.bootPath !== undefined && result.bootPath.length > 0) {
2308
+ io.stdout.write(`Boot path: ${result.bootPath.join(" -> ")}
2309
+ `);
2310
+ }
2311
+ io.stdout.write(`Remaining manual steps:
2312
+ `);
2313
+ for (const step of result.manualSteps) {
2314
+ io.stdout.write(` - ${step}
2315
+ `);
2316
+ }
2317
+ } else {
2318
+ io.stderr.write(`${result.error}
2319
+ `);
2320
+ }
2321
+ return result.ok ? 0 : 1;
2322
+ })();
2323
+ }
1408
2324
  if (command === "build") {
1409
- const scriptKind = selectScriptKind(detectScriptKinds(cwd));
1410
2325
  const reportBuild = (written, materializedDir) => {
1411
2326
  ensureMaterializedReference(cwd, materializedDir);
1412
2327
  if (json) {
@@ -1415,7 +2330,6 @@ function dispatch(argv, io, internals) {
1415
2330
  written,
1416
2331
  defoldVersion: resolvedVersion,
1417
2332
  apiSurface,
1418
- scriptKind,
1419
2333
  materializedSurface: materializedDir
1420
2334
  }));
1421
2335
  } else {
@@ -1443,7 +2357,6 @@ function dispatch(argv, io, internals) {
1443
2357
  const { materializedDir } = await materializeRefDocSurface({
1444
2358
  cwd,
1445
2359
  surfaceId,
1446
- scriptKind,
1447
2360
  ...internals?.resolveOpts ? { resolveOpts: internals.resolveOpts } : {},
1448
2361
  ...internals?.refDocRegistry ? { registry: internals.refDocRegistry } : {}
1449
2362
  });
@@ -1463,8 +2376,7 @@ function dispatch(argv, io, internals) {
1463
2376
  const { materializedDir } = materializeApiSurface({
1464
2377
  cwd,
1465
2378
  surface,
1466
- sourceGeneratedDir,
1467
- scriptKind
2379
+ sourceGeneratedDir
1468
2380
  });
1469
2381
  return reportBuild(written, materializedDir);
1470
2382
  } catch (err) {
@@ -1478,12 +2390,10 @@ function dispatch(argv, io, internals) {
1478
2390
  if (!isRefDocSurface) {
1479
2391
  const sourceGeneratedDir = internals?.sourceGeneratedDir ?? resolveCurrentSurfaceGeneratedDir();
1480
2392
  syncSurface = () => {
1481
- const scriptKind = selectScriptKind(detectScriptKinds(cwd));
1482
2393
  const { materializedDir } = materializeApiSurface({
1483
2394
  cwd,
1484
2395
  surface,
1485
- sourceGeneratedDir,
1486
- scriptKind
2396
+ sourceGeneratedDir
1487
2397
  });
1488
2398
  ensureMaterializedReference(cwd, materializedDir);
1489
2399
  };
@@ -1497,7 +2407,8 @@ function dispatch(argv, io, internals) {
1497
2407
  ...internals?.watcherFactory ? { watcherFactory: internals.watcherFactory } : {},
1498
2408
  ...internals?.debounceMs !== undefined ? { debounceMs: internals.debounceMs } : {},
1499
2409
  ...syncSurface ? { syncSurface } : {},
1500
- ...componentWatcherFactory ? { componentWatcherFactory } : {}
2410
+ ...componentWatcherFactory ? { componentWatcherFactory } : {},
2411
+ ...json ? { json: true } : {}
1501
2412
  };
1502
2413
  const handle = runWatch(watchOpts);
1503
2414
  if (internals) {
@@ -1514,12 +2425,10 @@ function dispatch(argv, io, internals) {
1514
2425
  };
1515
2426
  if (isRefDocSurface) {
1516
2427
  const surfaceId = surface.surfaceId;
1517
- const scriptKind = selectScriptKind(detectScriptKinds(cwd));
1518
2428
  return (async () => {
1519
2429
  const { materializedDir } = await materializeRefDocSurface({
1520
2430
  cwd,
1521
2431
  surfaceId,
1522
- scriptKind,
1523
2432
  ...internals?.resolveOpts ? { resolveOpts: internals.resolveOpts } : {},
1524
2433
  ...internals?.refDocRegistry ? { registry: internals.refDocRegistry } : {}
1525
2434
  });
@@ -1529,6 +2438,116 @@ function dispatch(argv, io, internals) {
1529
2438
  }
1530
2439
  return launchWatch();
1531
2440
  }
2441
+ if (command === "wall") {
2442
+ const wallCwd = internals?.cwd ?? process.cwd();
2443
+ const dirs = rest;
2444
+ const toJsonWall = (w) => ({
2445
+ dir: w.dir,
2446
+ kind: w.kind
2447
+ });
2448
+ const reportWalls = (walls) => {
2449
+ if (json) {
2450
+ io.stdout.write(renderResult({ command: "wall", directoryWalls: walls.map(toJsonWall) }));
2451
+ } else if (walls.length === 0) {
2452
+ io.stdout.write(`defold-typescript wall: no directories walled
2453
+ `);
2454
+ } else {
2455
+ io.stdout.write(`defold-typescript wall: walled ${walls.map((w) => w.dir).join(", ")}
2456
+ `);
2457
+ }
2458
+ };
2459
+ if (wallList) {
2460
+ const current = currentWalledDirs(wallCwd);
2461
+ const eligible = eligibleWalls(wallCwd);
2462
+ const currentWalls = eligible.filter((w) => current.includes(w.dir));
2463
+ if (json) {
2464
+ io.stdout.write(renderResult({
2465
+ command: "wall",
2466
+ directoryWalls: currentWalls.map(toJsonWall),
2467
+ eligible: eligible.map(toJsonWall)
2468
+ }));
2469
+ } else {
2470
+ io.stdout.write(`defold-typescript wall: walled [${current.join(", ")}]; eligible [${eligible.map((w) => w.dir).join(", ")}]
2471
+ `);
2472
+ }
2473
+ return 0;
2474
+ }
2475
+ if (dirs.length > 0) {
2476
+ try {
2477
+ const current = currentWalledDirs(wallCwd);
2478
+ const desired = wallRemove ? current.filter((d) => !dirs.includes(d)) : [...current, ...dirs];
2479
+ reportWalls(applyWallSelection(wallCwd, desired));
2480
+ return 0;
2481
+ } catch (err) {
2482
+ const message = err instanceof Error ? err.message : String(err);
2483
+ if (json) {
2484
+ io.stdout.write(renderResult({ command: "wall", error: message }));
2485
+ } else {
2486
+ io.stderr.write(`${message}
2487
+ `);
2488
+ }
2489
+ return 1;
2490
+ }
2491
+ }
2492
+ const interactive = !json && (internals?.isTty ?? Boolean(process.stdout.isTTY));
2493
+ if (!interactive) {
2494
+ io.stderr.write(`defold-typescript wall: no directory given; pass <dir> or run in a terminal for the interactive menu
2495
+ `);
2496
+ return 1;
2497
+ }
2498
+ return (async () => {
2499
+ try {
2500
+ reportWalls(await runWallInteractive(wallCwd, internals?.wallCheckbox ? { checkbox: internals.wallCheckbox } : {}));
2501
+ return 0;
2502
+ } catch (err) {
2503
+ io.stderr.write(`${err instanceof Error ? err.message : String(err)}
2504
+ `);
2505
+ return 1;
2506
+ }
2507
+ })();
2508
+ }
2509
+ if (command === "defold") {
2510
+ const subcommand = rest[0];
2511
+ const defoldCwd = rest[1] ? path15.resolve(rest[1]) : process.cwd();
2512
+ if (!isDefoldSubcommand(subcommand)) {
2513
+ io.stderr.write(DEFOLD_USAGE);
2514
+ return 1;
2515
+ }
2516
+ const javaOverride = javaFlag ?? process.env.DEFOLD_JAVA;
2517
+ const defoldIo = { ...defaultDefoldIo(), ...internals?.defoldIo };
2518
+ return (async () => {
2519
+ try {
2520
+ const result = await runDefoldCommand({
2521
+ cwd: defoldCwd,
2522
+ subcommand,
2523
+ ...javaOverride !== undefined ? { java: javaOverride } : {},
2524
+ ...buildServerFlag !== undefined ? { buildServer: buildServerFlag } : {},
2525
+ io: defoldIo
2526
+ });
2527
+ if (json) {
2528
+ io.stdout.write(renderResult(result.ok ? { command: "defold", subcommand: result.subcommand, exitCode: result.exitCode } : {
2529
+ command: "defold",
2530
+ subcommand: result.subcommand,
2531
+ exitCode: result.exitCode,
2532
+ error: `bob ${result.subcommand} exited with code ${result.exitCode}`
2533
+ }));
2534
+ } else if (!result.ok) {
2535
+ io.stderr.write(`defold-typescript defold ${result.subcommand}: bob exited with code ${result.exitCode}
2536
+ `);
2537
+ }
2538
+ return result.exitCode;
2539
+ } catch (err) {
2540
+ const message = err instanceof Error ? err.message : String(err);
2541
+ if (json) {
2542
+ io.stdout.write(renderResult({ command: "defold", subcommand, error: message }));
2543
+ } else {
2544
+ io.stderr.write(`${message}
2545
+ `);
2546
+ }
2547
+ return 1;
2548
+ }
2549
+ })();
2550
+ }
1532
2551
  io.stderr.write(USAGE);
1533
2552
  return 1;
1534
2553
  }