@defold-typescript/cli 0.1.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/index.js ADDED
@@ -0,0 +1,1017 @@
1
+ // src/build-session.ts
2
+ import { readFileSync as readFileSync2, rmSync } from "node:fs";
3
+ import * as path3 from "node:path";
4
+ import {
5
+ createTranspileSession
6
+ } from "@defold-typescript/transpiler";
7
+
8
+ // src/build-output.ts
9
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import * as path from "node:path";
11
+ var DEFAULT_INCLUDE = ["src/**/*.ts"];
12
+ var PROJECT_BUCKET = "<project>";
13
+ function toPosix(p, sep2 = path.sep) {
14
+ return p.split(sep2).join("/");
15
+ }
16
+ function readBuildConfig(cwd) {
17
+ const tsconfigPath = path.join(cwd, "tsconfig.json");
18
+ let raw;
19
+ try {
20
+ raw = readFileSync(tsconfigPath, "utf8");
21
+ } catch {
22
+ throw new Error(`defold-typescript build: no tsconfig.json at ${cwd}; run 'defold-typescript init' first.`);
23
+ }
24
+ const tsconfig = JSON.parse(raw);
25
+ const outDir = tsconfig.compilerOptions?.outDir;
26
+ const include = tsconfig.include?.length ? tsconfig.include : DEFAULT_INCLUDE;
27
+ return { outDir, include };
28
+ }
29
+ function stripIncludeBase(pattern) {
30
+ const firstWildcard = pattern.search(/[*?[]/);
31
+ if (firstWildcard === -1) {
32
+ return pattern.endsWith("/") ? pattern : `${path.posix.dirname(pattern)}/`;
33
+ }
34
+ const upToWildcard = pattern.slice(0, firstWildcard);
35
+ const lastSlash = upToWildcard.lastIndexOf("/");
36
+ return lastSlash === -1 ? "" : upToWildcard.slice(0, lastSlash + 1);
37
+ }
38
+ function computeLuaRel(rel, config) {
39
+ const { outDir, include } = config;
40
+ if (outDir === undefined || outDir === "" || outDir === ".") {
41
+ return rel.replace(/\.ts$/, ".lua");
42
+ }
43
+ const includeBase = include.map(stripIncludeBase).filter((base) => rel.startsWith(base)).sort((a, b) => b.length - a.length)[0] ?? "";
44
+ const relUnderBase = rel.slice(includeBase.length);
45
+ return path.posix.join(outDir, relUnderBase.replace(/\.ts$/, ".lua"));
46
+ }
47
+ function collectFailures(diagnostics) {
48
+ const failures = new Map;
49
+ for (const diag of diagnostics) {
50
+ const bucket = diag.file ?? PROJECT_BUCKET;
51
+ const list = failures.get(bucket);
52
+ if (list) {
53
+ list.push(diag.message);
54
+ } else {
55
+ failures.set(bucket, [diag.message]);
56
+ }
57
+ }
58
+ return failures;
59
+ }
60
+ function throwIfFailures(failures) {
61
+ if (failures.size === 0) {
62
+ return;
63
+ }
64
+ const formatted = [...failures.entries()].map(([file, messages]) => ` ${file}: ${messages.join("; ")}`).join(`
65
+ `);
66
+ throw new Error(`defold-typescript build: ${failures.size} file(s) failed:
67
+ ${formatted}`);
68
+ }
69
+ function writeLuaFile(cwd, luaRel, lua, map) {
70
+ const luaAbs = path.join(cwd, luaRel);
71
+ mkdirSync(path.dirname(luaAbs), { recursive: true });
72
+ if (map) {
73
+ const mapBasename = `${path.posix.basename(luaRel)}.map`;
74
+ writeFileSync(`${luaAbs}.map`, map);
75
+ writeFileSync(luaAbs, `${lua}
76
+ --# sourceMappingURL=${mapBasename}
77
+ `);
78
+ } else {
79
+ writeFileSync(luaAbs, lua);
80
+ }
81
+ }
82
+
83
+ // src/scan.ts
84
+ import { globSync, statSync } from "node:fs";
85
+ import * as path2 from "node:path";
86
+ function scanFilesSync(cwd, pattern) {
87
+ return globSync(pattern, { cwd }).filter((rel) => statSync(path2.join(cwd, rel)).isFile());
88
+ }
89
+
90
+ // src/build-session.ts
91
+ function createBuildSession(opts) {
92
+ const { cwd } = opts;
93
+ const config = readBuildConfig(cwd);
94
+ const session = createTranspileSession();
95
+ function writeOutputs(result, keys) {
96
+ const failures = collectFailures(result.diagnostics);
97
+ const written = [];
98
+ for (const rel of keys) {
99
+ if (failures.has(rel)) {
100
+ continue;
101
+ }
102
+ const lua = result.lua[rel];
103
+ if (lua === undefined) {
104
+ continue;
105
+ }
106
+ const luaRel = computeLuaRel(rel, config);
107
+ writeLuaFile(cwd, luaRel, lua, result.sourceMaps[rel]);
108
+ written.push(luaRel);
109
+ }
110
+ throwIfFailures(failures);
111
+ return { written };
112
+ }
113
+ function buildAll() {
114
+ const seen = new Set;
115
+ for (const pattern of config.include) {
116
+ for (const match of scanFilesSync(cwd, pattern)) {
117
+ seen.add(toPosix(match));
118
+ }
119
+ }
120
+ const sources = [...seen].sort();
121
+ if (sources.length === 0) {
122
+ return { written: [] };
123
+ }
124
+ const files = {};
125
+ for (const rel of sources) {
126
+ files[rel] = readFileSync2(path3.join(cwd, rel), "utf8");
127
+ }
128
+ const result = session.update(files);
129
+ return writeOutputs(result, sources);
130
+ }
131
+ function applyEvents(changed, removed) {
132
+ const changes = {};
133
+ for (const rel of changed) {
134
+ changes[rel] = readFileSync2(path3.join(cwd, rel), "utf8");
135
+ }
136
+ for (const rel of removed) {
137
+ changes[rel] = null;
138
+ }
139
+ const result = session.update(changes);
140
+ for (const rel of removed) {
141
+ const luaAbs = path3.join(cwd, computeLuaRel(rel, config));
142
+ rmSync(luaAbs, { force: true });
143
+ rmSync(`${luaAbs}.map`, { force: true });
144
+ }
145
+ return writeOutputs(result, changed);
146
+ }
147
+ return { buildAll, applyEvents };
148
+ }
149
+ // src/dispatch.ts
150
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
151
+ import * as path9 from "node:path";
152
+
153
+ // src/api-registry.ts
154
+ import { existsSync, readFileSync as readFileSync3 } from "node:fs";
155
+ import { createRequire } from "node:module";
156
+ import * as path4 from "node:path";
157
+ function findPackageRoot(start) {
158
+ let dir = start;
159
+ for (;; ) {
160
+ if (existsSync(path4.join(dir, "package.json"))) {
161
+ return dir;
162
+ }
163
+ const parent = path4.dirname(dir);
164
+ if (parent === dir) {
165
+ return null;
166
+ }
167
+ dir = parent;
168
+ }
169
+ }
170
+ function resolveTypesPackageRoot() {
171
+ try {
172
+ const require2 = createRequire(import.meta.url);
173
+ const entry = require2.resolve("@defold-typescript/types");
174
+ return findPackageRoot(path4.dirname(entry));
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+ function loadApiTargetsRegistry() {
180
+ const root = resolveTypesPackageRoot();
181
+ if (root === null) {
182
+ return [];
183
+ }
184
+ const registryPath = path4.join(root, "api-targets.json");
185
+ if (!existsSync(registryPath)) {
186
+ return [];
187
+ }
188
+ try {
189
+ const { targets } = JSON.parse(readFileSync3(registryPath, "utf8"));
190
+ return targets;
191
+ } catch {
192
+ return [];
193
+ }
194
+ }
195
+
196
+ // src/defold-version.ts
197
+ var CURRENT_STABLE_DEFOLD_VERSION = "1.12.4";
198
+ function readDefoldVersionPin(pkg) {
199
+ if (typeof pkg !== "object" || pkg === null) {
200
+ return;
201
+ }
202
+ const namespace = pkg["defold-typescript"];
203
+ if (typeof namespace !== "object" || namespace === null) {
204
+ return;
205
+ }
206
+ const version = namespace["defold-version"];
207
+ return typeof version === "string" ? version : undefined;
208
+ }
209
+ function resolveDefoldVersion(opts) {
210
+ if (opts.flag !== undefined) {
211
+ return { version: opts.flag, source: "flag" };
212
+ }
213
+ if (opts.pin !== undefined) {
214
+ return { version: opts.pin, source: "pin" };
215
+ }
216
+ return { version: CURRENT_STABLE_DEFOLD_VERSION, source: "default" };
217
+ }
218
+
219
+ // src/api-surface.ts
220
+ var CURRENT_STABLE_SURFACE_ID = `defold-${CURRENT_STABLE_DEFOLD_VERSION}`;
221
+ function selectApiSurface(resolvedVersion) {
222
+ const id = `defold-${resolvedVersion}`;
223
+ const target = loadApiTargetsRegistry().find((t) => t.id === id);
224
+ if (target) {
225
+ return { surfaceId: target.id, available: true };
226
+ }
227
+ return { surfaceId: null, available: false };
228
+ }
229
+
230
+ // src/build.ts
231
+ import { readFileSync as readFileSync4 } from "node:fs";
232
+ import * as path5 from "node:path";
233
+ import { transpileProject } from "@defold-typescript/transpiler";
234
+ function runBuild(opts) {
235
+ const { cwd } = opts;
236
+ const config = readBuildConfig(cwd);
237
+ const seen = new Set;
238
+ for (const pattern of config.include) {
239
+ for (const match of scanFilesSync(cwd, pattern)) {
240
+ seen.add(toPosix(match));
241
+ }
242
+ }
243
+ const sources = [...seen].sort();
244
+ if (sources.length === 0) {
245
+ return { written: [] };
246
+ }
247
+ const files = {};
248
+ for (const rel of sources) {
249
+ files[rel] = readFileSync4(path5.join(cwd, rel), "utf8");
250
+ }
251
+ const result = transpileProject({ files });
252
+ const failures = collectFailures(result.diagnostics);
253
+ const written = [];
254
+ for (const rel of sources) {
255
+ if (failures.has(rel)) {
256
+ continue;
257
+ }
258
+ const lua = result.lua[rel];
259
+ if (!lua) {
260
+ continue;
261
+ }
262
+ const luaRel = computeLuaRel(rel, config);
263
+ writeLuaFile(cwd, luaRel, lua, result.sourceMaps[rel]);
264
+ written.push(luaRel);
265
+ }
266
+ throwIfFailures(failures);
267
+ return { written };
268
+ }
269
+
270
+ // src/init.ts
271
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
272
+ import * as path6 from "node:path";
273
+
274
+ // src/script-kind.ts
275
+ var DEFAULT_TYPES_ENTRYPOINT = "@defold-typescript/types";
276
+ var KIND_BY_EXT = {
277
+ ".script": "script",
278
+ ".gui_script": "gui-script",
279
+ ".render_script": "render-script"
280
+ };
281
+ var SKIP_SEGMENTS = new Set(["node_modules", ".defold-types", "build"]);
282
+ function isSkipped(relPath) {
283
+ return relPath.split(/[/\\]/).some((segment) => SKIP_SEGMENTS.has(segment));
284
+ }
285
+ function isComponentPath(relPath) {
286
+ return Object.keys(KIND_BY_EXT).some((ext) => relPath.endsWith(ext));
287
+ }
288
+ function detectScriptKinds(cwd) {
289
+ const kinds = new Set;
290
+ for (const [ext, kind] of Object.entries(KIND_BY_EXT)) {
291
+ for (const match of scanFilesSync(cwd, `**/*${ext}`)) {
292
+ if (!isSkipped(match)) {
293
+ kinds.add(kind);
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ return kinds;
299
+ }
300
+ function selectScriptKind(kinds) {
301
+ if (kinds.size !== 1) {
302
+ return null;
303
+ }
304
+ for (const kind of kinds) {
305
+ return kind;
306
+ }
307
+ return null;
308
+ }
309
+ function selectScriptKindEntrypoint(kinds) {
310
+ const kind = selectScriptKind(kinds);
311
+ return kind === null ? DEFAULT_TYPES_ENTRYPOINT : `${DEFAULT_TYPES_ENTRYPOINT}/${kind}`;
312
+ }
313
+ var RESTRICTED_MODULES = {
314
+ script: "",
315
+ "gui-script": "gui",
316
+ "render-script": "render"
317
+ };
318
+ var ALL_RESTRICTED_MODULES = ["gui", "render"];
319
+ function excludedModulesForKind(kind) {
320
+ if (kind === null) {
321
+ return new Set;
322
+ }
323
+ const allowed = RESTRICTED_MODULES[kind];
324
+ return new Set(ALL_RESTRICTED_MODULES.filter((mod) => mod !== allowed));
325
+ }
326
+
327
+ // src/init.ts
328
+ var CONFLICTING_TS_CONFIGS = [
329
+ "tsconfig.json",
330
+ "defold-typescript.config.ts",
331
+ "defold-typescript.config.mts",
332
+ "defold-typescript.config.js"
333
+ ];
334
+ var TSCONFIG_COMPILER_OPTIONS = {
335
+ target: "ES2022",
336
+ module: "ESNext",
337
+ moduleResolution: "Bundler",
338
+ lib: ["ES2022"],
339
+ strict: true,
340
+ skipLibCheck: true
341
+ };
342
+ var GITIGNORE_LINES = ["src/**/*.lua", "src/**/*.lua.map"];
343
+ var BIOME_JSON_CONTENT = {
344
+ $schema: "https://biomejs.dev/schemas/2.4.15/schema.json",
345
+ files: {
346
+ includes: ["src/**/*.ts", "!**/dist", "!**/node_modules", "!**/*.lua"]
347
+ },
348
+ formatter: {
349
+ enabled: true,
350
+ indentStyle: "space",
351
+ indentWidth: 2,
352
+ lineWidth: 100
353
+ },
354
+ linter: {
355
+ enabled: true,
356
+ rules: {
357
+ recommended: true,
358
+ style: {
359
+ useImportType: "error",
360
+ useNodejsImportProtocol: "error"
361
+ },
362
+ correctness: {
363
+ noUnusedImports: "error",
364
+ noUnusedVariables: "warn"
365
+ }
366
+ }
367
+ },
368
+ javascript: {
369
+ formatter: {
370
+ quoteStyle: "double",
371
+ semicolons: "always",
372
+ trailingCommas: "all",
373
+ arrowParentheses: "always"
374
+ }
375
+ }
376
+ };
377
+ var MAIN_TS_CONTENT = `export function init(): void {
378
+ const start = vmath.vector3(0, 0, 0);
379
+ msg.post("main:/hero", "spawn", { start });
380
+ }
381
+ `;
382
+ var MAIN_SCRIPT_CONTENT = `function init(self) end
383
+ function update(self, dt) end
384
+ function on_message(self, message_id, message, sender) end
385
+ function final(self) end
386
+ `;
387
+ var MAIN_COLLECTION_CONTENT = `name: "main"
388
+ scale_along_z: 0
389
+ embedded_instances {
390
+ id: "main"
391
+ data: "components {\\n id: \\"main\\"\\n component: \\"/main/main.script\\"\\n}\\n"
392
+ position { x: 0.0 y: 0.0 z: 0.0 }
393
+ rotation { x: 0.0 y: 0.0 z: 0.0 w: 1.0 }
394
+ scale3 { x: 1.0 y: 1.0 z: 1.0 }
395
+ }
396
+ `;
397
+ var SCAFFOLD_DEV_DEPS = {
398
+ "@defold-typescript/transpiler": "workspace:*",
399
+ "@defold-typescript/types": "workspace:*",
400
+ "@biomejs/biome": "^2.2.0"
401
+ };
402
+ function writeJson(filePath, value) {
403
+ writeFileSync2(filePath, `${JSON.stringify(value, null, 2)}
404
+ `);
405
+ }
406
+ function writeGitignore(cwd) {
407
+ const gitignorePath = path6.join(cwd, ".gitignore");
408
+ if (existsSync2(gitignorePath)) {
409
+ const existing = readFileSync5(gitignorePath, "utf8");
410
+ const present = new Set(existing.split(`
411
+ `).map((line) => line.trim()));
412
+ const missing = GITIGNORE_LINES.filter((line) => !present.has(line));
413
+ if (missing.length === 0) {
414
+ return;
415
+ }
416
+ const prefix = existing.endsWith(`
417
+ `) || existing === "" ? "" : `
418
+ `;
419
+ writeFileSync2(gitignorePath, `${existing}${prefix}${missing.join(`
420
+ `)}
421
+ `);
422
+ } else {
423
+ writeFileSync2(gitignorePath, `${GITIGNORE_LINES.join(`
424
+ `)}
425
+ `);
426
+ }
427
+ }
428
+ function writeBiome(cwd, written) {
429
+ const biomePath = path6.join(cwd, "biome.json");
430
+ if (existsSync2(biomePath)) {
431
+ return;
432
+ }
433
+ writeJson(biomePath, BIOME_JSON_CONTENT);
434
+ written.push("biome.json");
435
+ }
436
+ function writeTsSurface(cwd, written) {
437
+ mkdirSync2(path6.join(cwd, "src"), { recursive: true });
438
+ writeFileSync2(path6.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
439
+ written.push("src/main.ts");
440
+ const kinds = detectScriptKinds(cwd);
441
+ const tsconfig = {
442
+ compilerOptions: {
443
+ ...TSCONFIG_COMPILER_OPTIONS,
444
+ types: [selectScriptKindEntrypoint(kinds)]
445
+ },
446
+ include: ["src/**/*.ts"]
447
+ };
448
+ writeJson(path6.join(cwd, "tsconfig.json"), tsconfig);
449
+ written.push("tsconfig.json");
450
+ const pkgPath = path6.join(cwd, "package.json");
451
+ if (existsSync2(pkgPath)) {
452
+ const existing = JSON.parse(readFileSync5(pkgPath, "utf8"));
453
+ const devDeps = { ...existing.devDependencies ?? {} };
454
+ for (const [name, version] of Object.entries(SCAFFOLD_DEV_DEPS)) {
455
+ if (!(name in devDeps)) {
456
+ devDeps[name] = version;
457
+ }
458
+ }
459
+ existing.devDependencies = devDeps;
460
+ existing["defold-typescript"] ??= { "defold-version": CURRENT_STABLE_DEFOLD_VERSION };
461
+ writeJson(pkgPath, existing);
462
+ } else {
463
+ const fresh = {
464
+ name: path6.basename(cwd),
465
+ version: "0.0.0",
466
+ type: "module",
467
+ devDependencies: { ...SCAFFOLD_DEV_DEPS },
468
+ "defold-typescript": { "defold-version": CURRENT_STABLE_DEFOLD_VERSION }
469
+ };
470
+ writeJson(pkgPath, fresh);
471
+ }
472
+ written.push("package.json");
473
+ writeGitignore(cwd);
474
+ written.push(".gitignore");
475
+ writeBiome(cwd, written);
476
+ return selectScriptKind(kinds);
477
+ }
478
+ function runNewProjectInit(cwd, force = false) {
479
+ if (!existsSync2(cwd)) {
480
+ mkdirSync2(cwd, { recursive: true });
481
+ } else if (readdirSync(cwd).length > 0 && !force) {
482
+ throw new Error(`defold-typescript init: refusing to synthesize a new Defold project into non-empty directory ${cwd}. Pass --force to proceed.`);
483
+ }
484
+ const written = [];
485
+ writeFileSync2(path6.join(cwd, "game.project"), `[project]
486
+ title = ${path6.basename(cwd)}
487
+ main_collection = /main/main.collectionc
488
+ `);
489
+ written.push("game.project");
490
+ mkdirSync2(path6.join(cwd, "main"), { recursive: true });
491
+ writeFileSync2(path6.join(cwd, "main", "main.collection"), MAIN_COLLECTION_CONTENT);
492
+ written.push("main/main.collection");
493
+ writeFileSync2(path6.join(cwd, "main", "main.script"), MAIN_SCRIPT_CONTENT);
494
+ written.push("main/main.script");
495
+ const scriptKind = writeTsSurface(cwd, written);
496
+ return { written, scriptKind };
497
+ }
498
+ function runInit(opts) {
499
+ const { cwd, force = false } = opts;
500
+ if (!existsSync2(path6.join(cwd, "game.project"))) {
501
+ return runNewProjectInit(cwd, force);
502
+ }
503
+ if (!force) {
504
+ for (const rel of CONFLICTING_TS_CONFIGS) {
505
+ if (existsSync2(path6.join(cwd, rel))) {
506
+ throw new Error(`defold-typescript init: refusing to overwrite existing TS config: ${rel}. Pass --force to overwrite.`);
507
+ }
508
+ }
509
+ }
510
+ const written = [];
511
+ const scriptKind = writeTsSurface(cwd, written);
512
+ return { written, scriptKind };
513
+ }
514
+
515
+ // src/json-output.ts
516
+ function renderResult(input) {
517
+ const ok = input.error === undefined;
518
+ const base = ok ? { command: input.command, ok, written: input.written ?? [] } : { command: input.command, ok, error: input.error };
519
+ const withVersion = input.defoldVersion === undefined ? base : { ...base, defoldVersion: input.defoldVersion };
520
+ const withSurface = "apiSurface" in input ? { ...withVersion, apiSurface: input.apiSurface } : withVersion;
521
+ const withScriptKind = "scriptKind" in input ? { ...withSurface, scriptKind: input.scriptKind } : withSurface;
522
+ const payload = "materializedSurface" in input ? { ...withScriptKind, materializedSurface: input.materializedSurface } : withScriptKind;
523
+ return `${JSON.stringify(payload)}
524
+ `;
525
+ }
526
+
527
+ // src/materialize.ts
528
+ import {
529
+ copyFileSync,
530
+ existsSync as existsSync3,
531
+ mkdirSync as mkdirSync3,
532
+ readdirSync as readdirSync2,
533
+ readFileSync as readFileSync6,
534
+ rmSync as rmSync2,
535
+ writeFileSync as writeFileSync3
536
+ } from "node:fs";
537
+ import * as path7 from "node:path";
538
+ var MATERIALIZED_ROOT = ".defold-types";
539
+ function writeJson2(filePath, value) {
540
+ writeFileSync3(filePath, `${JSON.stringify(value, null, 2)}
541
+ `);
542
+ }
543
+ function listDts(dir) {
544
+ return readdirSync2(dir).filter((file) => file.endsWith(".d.ts")).sort();
545
+ }
546
+ function materializeApiSurface(opts) {
547
+ const { cwd, surface, sourceGeneratedDir } = opts;
548
+ if (!surface.available || surface.surfaceId === null || sourceGeneratedDir === null) {
549
+ return { materializedDir: null, active: null };
550
+ }
551
+ const { surfaceId } = surface;
552
+ const relDir = path7.posix.join(MATERIALIZED_ROOT, surfaceId);
553
+ const absDir = path7.join(cwd, MATERIALIZED_ROOT, surfaceId);
554
+ mkdirSync3(absDir, { recursive: true });
555
+ const excluded = excludedModulesForKind(opts.scriptKind ?? null);
556
+ const sources = listDts(sourceGeneratedDir).filter((file) => file !== "index.d.ts").filter((file) => !excluded.has(file.replace(/\.d\.ts$/, "")));
557
+ const srcDir = path7.resolve(sourceGeneratedDir, "..", "src");
558
+ const overloads = ["msg-overloads.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path7.join(srcDir, file)));
559
+ const coreTypesSrc = path7.join(srcDir, "core-types.ts");
560
+ const includeCoreTypes = overloads.length > 0 && existsSync3(coreTypesSrc);
561
+ const wanted = new Set(sources);
562
+ for (const file of overloads) {
563
+ wanted.add(file);
564
+ }
565
+ if (includeCoreTypes) {
566
+ wanted.add("core-types.d.ts");
567
+ }
568
+ for (const existing of readdirSync2(absDir)) {
569
+ if (existing.endsWith(".d.ts") && existing !== "index.d.ts" && !wanted.has(existing)) {
570
+ rmSync2(path7.join(absDir, existing));
571
+ }
572
+ }
573
+ for (const file of sources) {
574
+ writeFileSync3(path7.join(absDir, file), readFileSync6(path7.join(sourceGeneratedDir, file), "utf8"));
575
+ }
576
+ if (includeCoreTypes) {
577
+ writeFileSync3(path7.join(absDir, "core-types.d.ts"), readFileSync6(coreTypesSrc, "utf8"));
578
+ }
579
+ for (const file of overloads) {
580
+ writeFileSync3(path7.join(absDir, file), readFileSync6(path7.join(srcDir, file), "utf8"));
581
+ }
582
+ const modules = [...sources, ...overloads].map((file) => file.replace(/\.d\.ts$/, ""));
583
+ const imports = modules.map((mod) => `import "./${mod}";`).join(`
584
+ `);
585
+ writeFileSync3(path7.join(absDir, "index.d.ts"), `${imports}
586
+
587
+ export {};
588
+ `);
589
+ writeJson2(path7.join(absDir, "package.json"), {
590
+ name: `@defold-typescript/materialized-${surfaceId}`,
591
+ types: "index.d.ts"
592
+ });
593
+ return { materializedDir: relDir, active: surfaceId };
594
+ }
595
+ function ensureGitignoreLine(cwd, line) {
596
+ const gitignorePath = path7.join(cwd, ".gitignore");
597
+ if (!existsSync3(gitignorePath)) {
598
+ writeFileSync3(gitignorePath, `${line}
599
+ `);
600
+ return;
601
+ }
602
+ const existing = readFileSync6(gitignorePath, "utf8");
603
+ const present = new Set(existing.split(`
604
+ `).map((entry) => entry.trim()));
605
+ if (present.has(line)) {
606
+ return;
607
+ }
608
+ const prefix = existing.endsWith(`
609
+ `) || existing === "" ? "" : `
610
+ `;
611
+ writeFileSync3(gitignorePath, `${existing}${prefix}${line}
612
+ `);
613
+ }
614
+ function ensureMaterializedReference(cwd, materializedDir) {
615
+ if (materializedDir === null) {
616
+ return;
617
+ }
618
+ const surfaceId = path7.posix.basename(materializedDir);
619
+ const tsconfigPath = path7.join(cwd, "tsconfig.json");
620
+ if (existsSync3(tsconfigPath)) {
621
+ const tsconfig = JSON.parse(readFileSync6(tsconfigPath, "utf8"));
622
+ tsconfig.compilerOptions = {
623
+ ...tsconfig.compilerOptions ?? {},
624
+ typeRoots: [MATERIALIZED_ROOT],
625
+ types: [surfaceId]
626
+ };
627
+ writeJson2(tsconfigPath, tsconfig);
628
+ }
629
+ ensureGitignoreLine(cwd, `${MATERIALIZED_ROOT}/`);
630
+ }
631
+ function resolveCurrentSurfaceGeneratedDir() {
632
+ const root = resolveTypesPackageRoot();
633
+ return root === null ? null : path7.join(root, "generated");
634
+ }
635
+ async function materializeRefDocSurface(opts) {
636
+ const { cwd, surfaceId, resolveOpts } = opts;
637
+ const root = resolveTypesPackageRoot();
638
+ if (root === null) {
639
+ return { materializedDir: null, active: null };
640
+ }
641
+ const registry = opts.registry ?? loadApiTargetsRegistry();
642
+ const target = registry.find((t) => t.id === surfaceId);
643
+ if (!target || target.source?.kind !== "ref-doc") {
644
+ return { materializedDir: null, active: null };
645
+ }
646
+ const excludeModules = [...excludedModulesForKind(opts.scriptKind ?? null)];
647
+ const relDir = path7.posix.join(MATERIALIZED_ROOT, surfaceId);
648
+ const absDir = path7.join(cwd, MATERIALIZED_ROOT, surfaceId);
649
+ try {
650
+ const mod = await import(path7.join(root, "scripts", "materialize-version.ts"));
651
+ const selfContained = { ...target, coreTypesImport: "./core-types" };
652
+ await mod.materializeVersionedSurface(selfContained, {
653
+ destDir: absDir,
654
+ ...resolveOpts ? { resolveOpts } : {},
655
+ ...excludeModules.length > 0 ? { excludeModules } : {}
656
+ });
657
+ copyFileSync(path7.join(root, "src", "core-types.ts"), path7.join(absDir, "core-types.d.ts"));
658
+ } catch {
659
+ rmSync2(absDir, { recursive: true, force: true });
660
+ return { materializedDir: null, active: null };
661
+ }
662
+ return { materializedDir: relDir, active: surfaceId };
663
+ }
664
+
665
+ // src/watch.ts
666
+ import { existsSync as existsSync4, watch as fsWatch } from "node:fs";
667
+ import * as path8 from "node:path";
668
+ var DEFAULT_DEBOUNCE_MS = 50;
669
+ var recursiveWatcherFactory = (srcDir, onEvent) => {
670
+ const w = fsWatch(srcDir, { recursive: true }, (eventType, filename) => {
671
+ onEvent({
672
+ kind: eventType === "rename" ? "rename" : "change",
673
+ path: filename ?? ""
674
+ });
675
+ });
676
+ return { close: () => w.close() };
677
+ };
678
+ function formatBuildLine(written) {
679
+ return `defold-typescript build: wrote ${written.length} files: ${written.join(", ")}
680
+ `;
681
+ }
682
+ function rewrapInitError(err) {
683
+ const message = err instanceof Error ? err.message : String(err);
684
+ return new Error(message.replace(/^defold-typescript build:/, "defold-typescript watch:"));
685
+ }
686
+ function runWatch(opts) {
687
+ const { cwd, stdout, stderr } = opts;
688
+ const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
689
+ const factory = opts.watcherFactory ?? recursiveWatcherFactory;
690
+ let resolveDone;
691
+ let rejectDone;
692
+ const done = new Promise((res, rej) => {
693
+ resolveDone = res;
694
+ rejectDone = rej;
695
+ });
696
+ let session;
697
+ try {
698
+ opts.syncSurface?.();
699
+ session = createBuildSession({ cwd });
700
+ const { written } = session.buildAll();
701
+ stdout.write(formatBuildLine(written));
702
+ } catch (err) {
703
+ rejectDone(rewrapInitError(err));
704
+ return {
705
+ stop: () => {},
706
+ done,
707
+ waitForIdle: () => Promise.resolve()
708
+ };
709
+ }
710
+ const srcDir = path8.join(cwd, "src");
711
+ let scheduled = null;
712
+ let syncScheduled = null;
713
+ let rebuildBusy = false;
714
+ let syncBusy = false;
715
+ let stopped = false;
716
+ let idleResolvers = [];
717
+ const pending = new Set;
718
+ function notifyIdle() {
719
+ if (rebuildBusy || syncBusy)
720
+ return;
721
+ const resolvers = idleResolvers;
722
+ idleResolvers = [];
723
+ for (const resolve2 of resolvers)
724
+ resolve2();
725
+ }
726
+ function rebuild() {
727
+ scheduled = null;
728
+ const drained = [...pending];
729
+ pending.clear();
730
+ const changed = [];
731
+ const removed = [];
732
+ for (const rel of drained) {
733
+ const key = `src/${toPosix(rel)}`;
734
+ if (existsSync4(path8.join(srcDir, rel))) {
735
+ changed.push(key);
736
+ } else {
737
+ removed.push(key);
738
+ }
739
+ }
740
+ try {
741
+ const { written } = session.applyEvents(changed, removed);
742
+ stdout.write(formatBuildLine(written));
743
+ } catch (err) {
744
+ const message = err instanceof Error ? err.message : String(err);
745
+ stderr.write(`${message}
746
+ `);
747
+ }
748
+ rebuildBusy = false;
749
+ notifyIdle();
750
+ }
751
+ function onEvent(e) {
752
+ if (stopped)
753
+ return;
754
+ rebuildBusy = true;
755
+ if (e.path)
756
+ pending.add(e.path);
757
+ if (scheduled)
758
+ clearTimeout(scheduled);
759
+ scheduled = setTimeout(rebuild, debounceMs);
760
+ }
761
+ const watcher = factory(srcDir, onEvent);
762
+ function runSync() {
763
+ syncScheduled = null;
764
+ try {
765
+ opts.syncSurface?.();
766
+ } catch (err) {
767
+ const message = err instanceof Error ? err.message : String(err);
768
+ stderr.write(`${message}
769
+ `);
770
+ }
771
+ syncBusy = false;
772
+ notifyIdle();
773
+ }
774
+ function onComponentEvent(e) {
775
+ if (stopped)
776
+ return;
777
+ if (!e.path || isSkipped(e.path) || !isComponentPath(e.path))
778
+ return;
779
+ syncBusy = true;
780
+ if (syncScheduled)
781
+ clearTimeout(syncScheduled);
782
+ syncScheduled = setTimeout(runSync, debounceMs);
783
+ }
784
+ const componentWatcher = opts.componentWatcherFactory ? opts.componentWatcherFactory(cwd, onComponentEvent) : null;
785
+ function stop() {
786
+ if (stopped)
787
+ return;
788
+ stopped = true;
789
+ if (scheduled) {
790
+ clearTimeout(scheduled);
791
+ scheduled = null;
792
+ }
793
+ if (syncScheduled) {
794
+ clearTimeout(syncScheduled);
795
+ syncScheduled = null;
796
+ }
797
+ watcher.close();
798
+ componentWatcher?.close();
799
+ rebuildBusy = false;
800
+ syncBusy = false;
801
+ notifyIdle();
802
+ resolveDone(0);
803
+ }
804
+ function waitForIdle() {
805
+ if (!rebuildBusy && !syncBusy)
806
+ return Promise.resolve();
807
+ return new Promise((resolve2) => {
808
+ idleResolvers.push(resolve2);
809
+ });
810
+ }
811
+ return { stop, done, waitForIdle };
812
+ }
813
+
814
+ // src/dispatch.ts
815
+ var USAGE = `Usage: defold-typescript <init|build|watch> [path]
816
+ `;
817
+ function parseDefoldVersionFlag(argv) {
818
+ let flag;
819
+ const rest = [];
820
+ for (let i = 0;i < argv.length; i++) {
821
+ const arg = argv[i];
822
+ if (arg === "--defold-version") {
823
+ flag = argv[i + 1];
824
+ i++;
825
+ } else if (arg?.startsWith("--defold-version=")) {
826
+ flag = arg.slice("--defold-version=".length);
827
+ } else if (arg !== undefined) {
828
+ rest.push(arg);
829
+ }
830
+ }
831
+ return { flag, rest };
832
+ }
833
+ function readProjectPin(cwd) {
834
+ const pkgPath = path9.join(cwd, "package.json");
835
+ if (!existsSync5(pkgPath)) {
836
+ return;
837
+ }
838
+ try {
839
+ return readDefoldVersionPin(JSON.parse(readFileSync7(pkgPath, "utf8")));
840
+ } catch {
841
+ return;
842
+ }
843
+ }
844
+ function dispatch(argv, io, internals) {
845
+ const json = argv.includes("--json");
846
+ const force = argv.includes("--force");
847
+ const { flag: defoldVersionFlag, rest: nonFlagArgs } = parseDefoldVersionFlag(argv);
848
+ const positional = nonFlagArgs.filter((a) => a !== "--json" && a !== "--force");
849
+ const [command, ...rest] = positional;
850
+ const cwd = rest[0] ? path9.resolve(rest[0]) : process.cwd();
851
+ const pin = readProjectPin(cwd);
852
+ const resolvedVersion = resolveDefoldVersion({
853
+ ...defoldVersionFlag !== undefined ? { flag: defoldVersionFlag } : {},
854
+ ...pin !== undefined ? { pin } : {}
855
+ }).version;
856
+ const surface = selectApiSurface(resolvedVersion);
857
+ const apiSurface = surface.surfaceId;
858
+ if (command === "init") {
859
+ try {
860
+ const { written, scriptKind } = runInit({ cwd, force });
861
+ if (json) {
862
+ io.stdout.write(renderResult({
863
+ command: "init",
864
+ written,
865
+ defoldVersion: resolvedVersion,
866
+ apiSurface,
867
+ scriptKind
868
+ }));
869
+ } else {
870
+ io.stdout.write(`defold-typescript init: wrote ${written.length} files: ${written.join(", ")}
871
+ `);
872
+ }
873
+ return 0;
874
+ } catch (err) {
875
+ const message = err instanceof Error ? err.message : String(err);
876
+ if (json) {
877
+ io.stdout.write(renderResult({ command: "init", error: message }));
878
+ } else {
879
+ io.stderr.write(`${message}
880
+ `);
881
+ }
882
+ return 1;
883
+ }
884
+ }
885
+ if (command === "build") {
886
+ const scriptKind = selectScriptKind(detectScriptKinds(cwd));
887
+ const reportBuild = (written, materializedDir) => {
888
+ ensureMaterializedReference(cwd, materializedDir);
889
+ if (json) {
890
+ io.stdout.write(renderResult({
891
+ command: "build",
892
+ written,
893
+ defoldVersion: resolvedVersion,
894
+ apiSurface,
895
+ scriptKind,
896
+ materializedSurface: materializedDir
897
+ }));
898
+ } else {
899
+ io.stdout.write(`defold-typescript build: wrote ${written.length} files: ${written.join(", ")}
900
+ `);
901
+ }
902
+ return 0;
903
+ };
904
+ const reportError = (err) => {
905
+ const message = err instanceof Error ? err.message : String(err);
906
+ if (json) {
907
+ io.stdout.write(renderResult({ command: "build", error: message }));
908
+ } else {
909
+ io.stderr.write(`${message}
910
+ `);
911
+ }
912
+ return 1;
913
+ };
914
+ const isRefDocSurface = surface.available && surface.surfaceId !== null && surface.surfaceId !== CURRENT_STABLE_SURFACE_ID;
915
+ if (isRefDocSurface) {
916
+ const surfaceId = surface.surfaceId;
917
+ return (async () => {
918
+ try {
919
+ const { written } = runBuild({ cwd });
920
+ const { materializedDir } = await materializeRefDocSurface({
921
+ cwd,
922
+ surfaceId,
923
+ scriptKind,
924
+ ...internals?.resolveOpts ? { resolveOpts: internals.resolveOpts } : {},
925
+ ...internals?.refDocRegistry ? { registry: internals.refDocRegistry } : {}
926
+ });
927
+ if (!json && materializedDir === null) {
928
+ io.stderr.write(`defold-typescript build: could not materialize ${surfaceId}; the default surface stays active
929
+ `);
930
+ }
931
+ return reportBuild(written, materializedDir);
932
+ } catch (err) {
933
+ return reportError(err);
934
+ }
935
+ })();
936
+ }
937
+ try {
938
+ const { written } = runBuild({ cwd });
939
+ const sourceGeneratedDir = internals?.sourceGeneratedDir ?? resolveCurrentSurfaceGeneratedDir();
940
+ const { materializedDir } = materializeApiSurface({
941
+ cwd,
942
+ surface,
943
+ sourceGeneratedDir,
944
+ scriptKind
945
+ });
946
+ return reportBuild(written, materializedDir);
947
+ } catch (err) {
948
+ return reportError(err);
949
+ }
950
+ }
951
+ if (command === "watch") {
952
+ const isRefDocSurface = surface.available && surface.surfaceId !== null && surface.surfaceId !== CURRENT_STABLE_SURFACE_ID;
953
+ let syncSurface;
954
+ let componentWatcherFactory;
955
+ if (!isRefDocSurface) {
956
+ const sourceGeneratedDir = internals?.sourceGeneratedDir ?? resolveCurrentSurfaceGeneratedDir();
957
+ syncSurface = () => {
958
+ const scriptKind = selectScriptKind(detectScriptKinds(cwd));
959
+ const { materializedDir } = materializeApiSurface({
960
+ cwd,
961
+ surface,
962
+ sourceGeneratedDir,
963
+ scriptKind
964
+ });
965
+ ensureMaterializedReference(cwd, materializedDir);
966
+ };
967
+ componentWatcherFactory = internals ? internals.componentWatcherFactory : recursiveWatcherFactory;
968
+ }
969
+ const launchWatch = () => {
970
+ const watchOpts = {
971
+ cwd,
972
+ stdout: io.stdout,
973
+ stderr: io.stderr,
974
+ ...internals?.watcherFactory ? { watcherFactory: internals.watcherFactory } : {},
975
+ ...internals?.debounceMs !== undefined ? { debounceMs: internals.debounceMs } : {},
976
+ ...syncSurface ? { syncSurface } : {},
977
+ ...componentWatcherFactory ? { componentWatcherFactory } : {}
978
+ };
979
+ const handle = runWatch(watchOpts);
980
+ if (internals) {
981
+ internals.onWatchStart?.(handle);
982
+ } else {
983
+ process.once("SIGINT", () => handle.stop());
984
+ }
985
+ return handle.done.catch((err) => {
986
+ const message = err instanceof Error ? err.message : String(err);
987
+ io.stderr.write(`${message}
988
+ `);
989
+ return 1;
990
+ });
991
+ };
992
+ if (isRefDocSurface) {
993
+ const surfaceId = surface.surfaceId;
994
+ const scriptKind = selectScriptKind(detectScriptKinds(cwd));
995
+ return (async () => {
996
+ const { materializedDir } = await materializeRefDocSurface({
997
+ cwd,
998
+ surfaceId,
999
+ scriptKind,
1000
+ ...internals?.resolveOpts ? { resolveOpts: internals.resolveOpts } : {},
1001
+ ...internals?.refDocRegistry ? { registry: internals.refDocRegistry } : {}
1002
+ });
1003
+ ensureMaterializedReference(cwd, materializedDir);
1004
+ return launchWatch();
1005
+ })();
1006
+ }
1007
+ return launchWatch();
1008
+ }
1009
+ io.stderr.write(USAGE);
1010
+ return 1;
1011
+ }
1012
+ export {
1013
+ runWatch,
1014
+ runInit,
1015
+ dispatch,
1016
+ createBuildSession
1017
+ };