@defold-typescript/cli 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/bin.js +262 -48
- package/dist/debug-launcher.d.ts +43 -0
- package/dist/index.js +262 -48
- package/dist/mise-scaffold.d.ts +2 -0
- package/package.json +3 -3
- package/src/debug-launcher.ts +165 -0
- package/src/init.ts +111 -41
- package/src/materialize.ts +17 -8
- package/src/mise-scaffold.ts +62 -0
package/README.md
CHANGED
|
@@ -4,11 +4,16 @@ The end-user CLI for scaffolding and building [Defold](https://defold.com/)
|
|
|
4
4
|
projects written in TypeScript, transpiled to Lua.
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
|
-
bunx @defold-typescript/cli init
|
|
8
|
-
|
|
9
|
-
bunx @defold-typescript/cli
|
|
7
|
+
bunx @defold-typescript/cli@latest init # add TypeScript to a Defold project, or scaffold a new one
|
|
8
|
+
bun install # install the scaffolded devDependencies (types, biome)
|
|
9
|
+
bunx @defold-typescript/cli build # transpile src/ to Lua alongside your Defold sources
|
|
10
|
+
bunx @defold-typescript/cli watch # rebuild on change
|
|
10
11
|
```
|
|
11
12
|
|
|
13
|
+
Scaffold with the `@latest` tag — `init` writes your `@defold-typescript/types`
|
|
14
|
+
version pin, so a stale `bunx` cache would pin an older release. Run `bun install`
|
|
15
|
+
once after `init` to put the scaffolded dev dependencies on disk.
|
|
16
|
+
|
|
12
17
|
The package name is scoped (`@defold-typescript/cli`); `bunx defold-typescript`
|
|
13
18
|
without the scope resolves a different, nonexistent package. Install it to get
|
|
14
19
|
the shorter `defold-typescript` binary on your `PATH` and pin the version:
|
package/dist/bin.js
CHANGED
|
@@ -231,6 +231,169 @@ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFi
|
|
|
231
231
|
import * as path6 from "node:path";
|
|
232
232
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
233
233
|
|
|
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
|
+
// src/mise-scaffold.ts
|
|
345
|
+
var MANAGED_MARKER = "# managed by @defold-typescript";
|
|
346
|
+
var MISE_TASKS_TOML = `${MANAGED_MARKER}
|
|
347
|
+
[tasks."defold-typescript:build"]
|
|
348
|
+
description = "Build the TypeScript sources with the installed defold-typescript CLI"
|
|
349
|
+
run = "bunx --no-install defold-typescript build"
|
|
350
|
+
|
|
351
|
+
${MANAGED_MARKER}
|
|
352
|
+
[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"
|
|
355
|
+
|
|
356
|
+
${MANAGED_MARKER}
|
|
357
|
+
[tasks."defold-typescript:upgrade"]
|
|
358
|
+
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"]
|
|
360
|
+
`;
|
|
361
|
+
function stripManagedBlocks(text) {
|
|
362
|
+
const lines = text.split(`
|
|
363
|
+
`);
|
|
364
|
+
const out = [];
|
|
365
|
+
let i = 0;
|
|
366
|
+
while (i < lines.length) {
|
|
367
|
+
const line = lines[i] ?? "";
|
|
368
|
+
if (line.trim() === MANAGED_MARKER) {
|
|
369
|
+
i += 1;
|
|
370
|
+
while (i < lines.length && (lines[i] ?? "").trim() !== "") {
|
|
371
|
+
i += 1;
|
|
372
|
+
}
|
|
373
|
+
if (i < lines.length) {
|
|
374
|
+
i += 1;
|
|
375
|
+
}
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
out.push(line);
|
|
379
|
+
i += 1;
|
|
380
|
+
}
|
|
381
|
+
return out.join(`
|
|
382
|
+
`);
|
|
383
|
+
}
|
|
384
|
+
function mergeMiseToml(existing) {
|
|
385
|
+
if (existing === undefined) {
|
|
386
|
+
return MISE_TASKS_TOML;
|
|
387
|
+
}
|
|
388
|
+
const userContent = stripManagedBlocks(existing).replace(/\s*$/, "");
|
|
389
|
+
if (userContent === "") {
|
|
390
|
+
return MISE_TASKS_TOML;
|
|
391
|
+
}
|
|
392
|
+
return `${userContent}
|
|
393
|
+
|
|
394
|
+
${MISE_TASKS_TOML}`;
|
|
395
|
+
}
|
|
396
|
+
|
|
234
397
|
// src/script-kind.ts
|
|
235
398
|
var DEFAULT_TYPES_ENTRYPOINT = "@defold-typescript/types";
|
|
236
399
|
var KIND_BY_EXT = {
|
|
@@ -342,43 +505,64 @@ var BIOME_JSON_CONTENT = {
|
|
|
342
505
|
}
|
|
343
506
|
};
|
|
344
507
|
var VSCODE_EXTENSIONS_CONTENT = {
|
|
345
|
-
recommendations: ["sumneko.lua", "astronachos.defold"],
|
|
508
|
+
recommendations: ["sumneko.lua", "astronachos.defold", "tomblind.local-lua-debugger-vscode"],
|
|
346
509
|
unwantedRecommendations: ["johnnymorganz.luau-lsp"]
|
|
347
510
|
};
|
|
348
511
|
var VSCODE_SETTINGS_CONTENT = {
|
|
349
512
|
"Lua.workspace.ignoreDir": ["src"]
|
|
350
513
|
};
|
|
514
|
+
var HOOK_COMMENTS = {
|
|
515
|
+
init: "Initialize the component and return its state.",
|
|
516
|
+
update: "Update the component every frame; `dt` is the time step.",
|
|
517
|
+
fixed_update: "Update at the fixed physics time step.",
|
|
518
|
+
late_update: "Update every frame after `update`.",
|
|
519
|
+
on_message: "Handle an incoming message.",
|
|
520
|
+
on_input: "Handle input once input focus is acquired.",
|
|
521
|
+
final: "Clean up when the component is deleted.",
|
|
522
|
+
on_reload: "React to a hot reload of this script."
|
|
523
|
+
};
|
|
524
|
+
var HOOK_SIGNATURES = {
|
|
525
|
+
init: "",
|
|
526
|
+
update: "self, dt",
|
|
527
|
+
fixed_update: "self, dt",
|
|
528
|
+
late_update: "self, dt",
|
|
529
|
+
on_message: "self, message_id, message, sender",
|
|
530
|
+
on_input: "self, action_id, action",
|
|
531
|
+
final: "self",
|
|
532
|
+
on_reload: "self"
|
|
533
|
+
};
|
|
534
|
+
var SNIPPET_HOOK_ORDER = Object.keys(HOOK_SIGNATURES);
|
|
535
|
+
function hookLines(includeOnInput, startTabStop) {
|
|
536
|
+
const lines = [];
|
|
537
|
+
let tabStop = startTabStop;
|
|
538
|
+
for (const hook of SNIPPET_HOOK_ORDER) {
|
|
539
|
+
if (hook === "init") {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (hook === "on_input" && !includeOnInput) {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
lines.push(` // ${HOOK_COMMENTS[hook]}`);
|
|
546
|
+
lines.push(` ${hook}(${HOOK_SIGNATURES[hook]}) {$${tabStop}},`);
|
|
547
|
+
tabStop += 1;
|
|
548
|
+
}
|
|
549
|
+
return lines;
|
|
550
|
+
}
|
|
351
551
|
function inlineSnippetBody(factory, includeOnInput) {
|
|
352
|
-
|
|
552
|
+
return [
|
|
353
553
|
`import { ${factory} } from "@defold-typescript/types";`,
|
|
354
554
|
"",
|
|
355
555
|
`export const script = ${factory}({`,
|
|
356
|
-
|
|
556
|
+
` // ${HOOK_COMMENTS.init}`,
|
|
357
557
|
" init() {",
|
|
358
558
|
" return { $0 };",
|
|
359
559
|
" },",
|
|
360
|
-
|
|
361
|
-
"
|
|
362
|
-
" // Update at the fixed physics time step.",
|
|
363
|
-
" fixed_update(self, dt) {$2},",
|
|
364
|
-
" // Update every frame after `update`.",
|
|
365
|
-
" late_update(self, dt) {$3},",
|
|
366
|
-
" // Handle an incoming message.",
|
|
367
|
-
" on_message(self, message_id, message, sender) {$4},"
|
|
560
|
+
...hookLines(includeOnInput, 1),
|
|
561
|
+
"});"
|
|
368
562
|
];
|
|
369
|
-
if (includeOnInput) {
|
|
370
|
-
lines.push(" // Handle input once input focus is acquired.");
|
|
371
|
-
lines.push(" on_input(self, action_id, action) {$5},");
|
|
372
|
-
}
|
|
373
|
-
lines.push(" // Clean up when the component is deleted.");
|
|
374
|
-
lines.push(" final(self) {$6},");
|
|
375
|
-
lines.push(" // React to a hot reload of this script.");
|
|
376
|
-
lines.push(" on_reload(self) {$7},");
|
|
377
|
-
lines.push("});");
|
|
378
|
-
return lines;
|
|
379
563
|
}
|
|
380
564
|
function typedSnippetBody(factory, includeOnInput) {
|
|
381
|
-
|
|
565
|
+
return [
|
|
382
566
|
`import { ${factory} } from "@defold-typescript/types";`,
|
|
383
567
|
"",
|
|
384
568
|
"type Self = {",
|
|
@@ -387,29 +571,13 @@ function typedSnippetBody(factory, includeOnInput) {
|
|
|
387
571
|
"};",
|
|
388
572
|
"",
|
|
389
573
|
`export const script = ${factory}<Self>({`,
|
|
390
|
-
|
|
574
|
+
` // ${HOOK_COMMENTS.init}`,
|
|
391
575
|
" init(): Self {",
|
|
392
576
|
" return { $0 };",
|
|
393
577
|
" },",
|
|
394
|
-
|
|
395
|
-
"
|
|
396
|
-
" // Update at the fixed physics time step.",
|
|
397
|
-
" fixed_update(self, dt) {$3},",
|
|
398
|
-
" // Update every frame after `update`.",
|
|
399
|
-
" late_update(self, dt) {$4},",
|
|
400
|
-
" // Handle an incoming message.",
|
|
401
|
-
" on_message(self, message_id, message, sender) {$5},"
|
|
578
|
+
...hookLines(includeOnInput, 2),
|
|
579
|
+
"});"
|
|
402
580
|
];
|
|
403
|
-
if (includeOnInput) {
|
|
404
|
-
lines.push(" // Handle input once input focus is acquired.");
|
|
405
|
-
lines.push(" on_input(self, action_id, action) {$6},");
|
|
406
|
-
}
|
|
407
|
-
lines.push(" // Clean up when the component is deleted.");
|
|
408
|
-
lines.push(" final(self) {$7},");
|
|
409
|
-
lines.push(" // React to a hot reload of this script.");
|
|
410
|
-
lines.push(" on_reload(self) {$8},");
|
|
411
|
-
lines.push("});");
|
|
412
|
-
return lines;
|
|
413
581
|
}
|
|
414
582
|
var VSCODE_SNIPPETS_CONTENT = {
|
|
415
583
|
"Defold script (inferred self)": {
|
|
@@ -522,6 +690,12 @@ function writeBiome(cwd, written) {
|
|
|
522
690
|
writeJson(biomePath, BIOME_JSON_CONTENT);
|
|
523
691
|
written.push("biome.json");
|
|
524
692
|
}
|
|
693
|
+
function writeMiseTasks(cwd, written) {
|
|
694
|
+
const misePath = path6.join(cwd, "mise.toml");
|
|
695
|
+
const existing = existsSync2(misePath) ? readFileSync5(misePath, "utf8") : undefined;
|
|
696
|
+
writeFileSync2(misePath, mergeMiseToml(existing));
|
|
697
|
+
written.push("mise.toml");
|
|
698
|
+
}
|
|
525
699
|
function parseJsonc(text) {
|
|
526
700
|
let out = "";
|
|
527
701
|
let inString = false;
|
|
@@ -643,6 +817,39 @@ function writeVscodeSnippets(cwd, written) {
|
|
|
643
817
|
writeJson(filePath, VSCODE_SNIPPETS_CONTENT);
|
|
644
818
|
written.push(".vscode/defold-typescript.code-snippets");
|
|
645
819
|
}
|
|
820
|
+
function writeVscodeLaunch(cwd, written) {
|
|
821
|
+
const dir = path6.join(cwd, ".vscode");
|
|
822
|
+
const filePath = path6.join(dir, "launch.json");
|
|
823
|
+
const ours = debugLaunchConfig();
|
|
824
|
+
if (existsSync2(filePath)) {
|
|
825
|
+
const existing = readVscodeJson(filePath);
|
|
826
|
+
if (existing === null) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const configs = Array.isArray(existing.configurations) ? [...existing.configurations] : [];
|
|
830
|
+
const names = new Set(configs.map((c) => isJsonObject(c) ? c.name : undefined));
|
|
831
|
+
if (!names.has(ours.name)) {
|
|
832
|
+
configs.push(ours);
|
|
833
|
+
}
|
|
834
|
+
existing.configurations = configs;
|
|
835
|
+
existing.version ??= VSCODE_LAUNCH_CONTENT.version;
|
|
836
|
+
writeJson(filePath, existing);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
mkdirSync2(dir, { recursive: true });
|
|
840
|
+
writeJson(filePath, VSCODE_LAUNCH_CONTENT);
|
|
841
|
+
written.push(".vscode/launch.json");
|
|
842
|
+
}
|
|
843
|
+
function writeVscodeDebugLauncher(cwd, written) {
|
|
844
|
+
const dir = path6.join(cwd, ".vscode");
|
|
845
|
+
const filePath = path6.join(dir, "defold-debug.ts");
|
|
846
|
+
if (existsSync2(filePath)) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
mkdirSync2(dir, { recursive: true });
|
|
850
|
+
writeFileSync2(filePath, DEBUG_LAUNCHER_SOURCE);
|
|
851
|
+
written.push(".vscode/defold-debug.ts");
|
|
852
|
+
}
|
|
646
853
|
function writeTsSurface(cwd, written, force = false) {
|
|
647
854
|
mkdirSync2(path6.join(cwd, "src"), { recursive: true });
|
|
648
855
|
writeFileSync2(path6.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
|
|
@@ -684,9 +891,12 @@ function writeTsSurface(cwd, written, force = false) {
|
|
|
684
891
|
writeGitignore(cwd);
|
|
685
892
|
written.push(".gitignore");
|
|
686
893
|
writeBiome(cwd, written);
|
|
894
|
+
writeMiseTasks(cwd, written);
|
|
687
895
|
writeVscodeExtensions(cwd, written);
|
|
688
896
|
writeVscodeSettings(cwd, written);
|
|
689
897
|
writeVscodeSnippets(cwd, written);
|
|
898
|
+
writeVscodeLaunch(cwd, written);
|
|
899
|
+
writeVscodeDebugLauncher(cwd, written);
|
|
690
900
|
return selectScriptKind(kinds);
|
|
691
901
|
}
|
|
692
902
|
function runNewProjectInit(cwd, force = false) {
|
|
@@ -769,7 +979,7 @@ function materializeApiSurface(opts) {
|
|
|
769
979
|
const excluded = excludedModulesForKind(opts.scriptKind ?? null);
|
|
770
980
|
const sources = listDts(sourceGeneratedDir).filter((file) => file !== "index.d.ts").filter((file) => !excluded.has(file.replace(/\.d\.ts$/, "")));
|
|
771
981
|
const srcDir = path7.resolve(sourceGeneratedDir, "..", "src");
|
|
772
|
-
const overloads = ["msg-overloads.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path7.join(srcDir, file)));
|
|
982
|
+
const overloads = ["msg-overloads.d.ts", "message-guard.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path7.join(srcDir, file)));
|
|
773
983
|
const coreTypesSrc = path7.join(srcDir, "core-types.ts");
|
|
774
984
|
const includeCoreTypes = overloads.length > 0 && existsSync3(coreTypesSrc);
|
|
775
985
|
const engineGlobalsSrc = path7.join(srcDir, "engine-globals.d.ts");
|
|
@@ -844,12 +1054,16 @@ function ensureMaterializedReference(cwd, materializedDir) {
|
|
|
844
1054
|
const tsconfigPath = path7.join(cwd, "tsconfig.json");
|
|
845
1055
|
if (existsSync3(tsconfigPath)) {
|
|
846
1056
|
const tsconfig = JSON.parse(readFileSync6(tsconfigPath, "utf8"));
|
|
847
|
-
tsconfig.compilerOptions
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1057
|
+
const current = tsconfig.compilerOptions ?? {};
|
|
1058
|
+
const alreadyRepointed = JSON.stringify(current.typeRoots) === JSON.stringify([MATERIALIZED_ROOT]) && JSON.stringify(current.types) === JSON.stringify([surfaceId]);
|
|
1059
|
+
if (!alreadyRepointed) {
|
|
1060
|
+
tsconfig.compilerOptions = {
|
|
1061
|
+
...current,
|
|
1062
|
+
typeRoots: [MATERIALIZED_ROOT],
|
|
1063
|
+
types: [surfaceId]
|
|
1064
|
+
};
|
|
1065
|
+
writeJson2(tsconfigPath, tsconfig);
|
|
1066
|
+
}
|
|
853
1067
|
}
|
|
854
1068
|
ensureGitignoreLine(cwd, `${MATERIALIZED_ROOT}/`);
|
|
855
1069
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface EngineTarget {
|
|
2
|
+
readonly enginePlatform: string;
|
|
3
|
+
readonly buildFolder: string;
|
|
4
|
+
readonly executable: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEBUG_LAUNCHER_REL = ".vscode/defold-debug.ts";
|
|
7
|
+
export declare function targetPlatform(platform: NodeJS.Platform): EngineTarget;
|
|
8
|
+
export declare function engineDownloadUrl(sha1: string, enginePlatform: string, executable: string): string;
|
|
9
|
+
export interface ResolveEngineOptions {
|
|
10
|
+
readonly cwd: string;
|
|
11
|
+
readonly target: EngineTarget;
|
|
12
|
+
readonly stockPath: string;
|
|
13
|
+
readonly probe: (candidate: string) => boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function resolveEnginePath(opts: ResolveEngineOptions): string;
|
|
16
|
+
export declare function debugLaunchConfig(): {
|
|
17
|
+
name: string;
|
|
18
|
+
type: string;
|
|
19
|
+
request: string;
|
|
20
|
+
stopOnEntry: boolean;
|
|
21
|
+
verbose: boolean;
|
|
22
|
+
internalConsoleOptions: string;
|
|
23
|
+
program: {
|
|
24
|
+
command: string;
|
|
25
|
+
};
|
|
26
|
+
args: string[];
|
|
27
|
+
};
|
|
28
|
+
export declare const VSCODE_LAUNCH_CONTENT: {
|
|
29
|
+
version: string;
|
|
30
|
+
configurations: {
|
|
31
|
+
name: string;
|
|
32
|
+
type: string;
|
|
33
|
+
request: string;
|
|
34
|
+
stopOnEntry: boolean;
|
|
35
|
+
verbose: boolean;
|
|
36
|
+
internalConsoleOptions: string;
|
|
37
|
+
program: {
|
|
38
|
+
command: string;
|
|
39
|
+
};
|
|
40
|
+
args: string[];
|
|
41
|
+
}[];
|
|
42
|
+
};
|
|
43
|
+
export declare const DEBUG_LAUNCHER_SOURCE: string;
|
package/dist/index.js
CHANGED
|
@@ -295,6 +295,169 @@ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFi
|
|
|
295
295
|
import * as path7 from "node:path";
|
|
296
296
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
297
297
|
|
|
298
|
+
// src/debug-launcher.ts
|
|
299
|
+
var PLATFORM_TARGETS = {
|
|
300
|
+
darwin: { enginePlatform: "x86_64-darwin", buildFolder: "x86_64-osx", executable: "dmengine" },
|
|
301
|
+
linux: { enginePlatform: "x86_64-linux", buildFolder: "x86_64-linux", executable: "dmengine" },
|
|
302
|
+
win32: {
|
|
303
|
+
enginePlatform: "x86_64-win32",
|
|
304
|
+
buildFolder: "x86_64-win32",
|
|
305
|
+
executable: "dmengine.exe"
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var ENGINE_INFO_URL = "https://d.defold.com/stable/info.json";
|
|
309
|
+
var ENGINE_ARCHIVE_BASE = "https://d.defold.com/archive/stable";
|
|
310
|
+
var DEBUG_LAUNCHER_REL = ".vscode/defold-debug.ts";
|
|
311
|
+
function debugLaunchConfig() {
|
|
312
|
+
return {
|
|
313
|
+
name: "Defold: Debug (TypeScript)",
|
|
314
|
+
type: "lua-local",
|
|
315
|
+
request: "launch",
|
|
316
|
+
stopOnEntry: false,
|
|
317
|
+
verbose: false,
|
|
318
|
+
internalConsoleOptions: "openOnSessionStart",
|
|
319
|
+
program: { command: "bun" },
|
|
320
|
+
args: [DEBUG_LAUNCHER_REL]
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
var VSCODE_LAUNCH_CONTENT = {
|
|
324
|
+
version: "0.2.0",
|
|
325
|
+
configurations: [debugLaunchConfig()]
|
|
326
|
+
};
|
|
327
|
+
function renderDebugLauncher() {
|
|
328
|
+
const targets = JSON.stringify(PLATFORM_TARGETS, null, 2);
|
|
329
|
+
return `import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
330
|
+
import * as path from "node:path";
|
|
331
|
+
|
|
332
|
+
// Windows only: paths to OpenAL32.dll and wrap_oal.dll from your Defold SDK
|
|
333
|
+
// (defoldsdk/ext/lib/x86_64-win32/). Leave empty on macOS/Linux.
|
|
334
|
+
const WINDOWS_OPENAL32_PATH = "";
|
|
335
|
+
const WINDOWS_WRAPOAL_PATH = "";
|
|
336
|
+
|
|
337
|
+
interface EngineTarget {
|
|
338
|
+
enginePlatform: string;
|
|
339
|
+
buildFolder: string;
|
|
340
|
+
executable: string;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const PLATFORM_TARGETS: Record<string, EngineTarget> = ${targets};
|
|
344
|
+
|
|
345
|
+
const ENGINE_INFO_URL = "${ENGINE_INFO_URL}";
|
|
346
|
+
const ENGINE_ARCHIVE_BASE = "${ENGINE_ARCHIVE_BASE}";
|
|
347
|
+
|
|
348
|
+
const target = PLATFORM_TARGETS[process.platform];
|
|
349
|
+
if (!target) {
|
|
350
|
+
console.error(\`Unsupported platform: \${process.platform}\`);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const here = path.dirname(new URL(import.meta.url).pathname);
|
|
355
|
+
const stockEnginePath = path.join(here, target.executable);
|
|
356
|
+
|
|
357
|
+
if (!existsSync(stockEnginePath)) {
|
|
358
|
+
const info = (await (await fetch(ENGINE_INFO_URL)).json()) as { sha1: string };
|
|
359
|
+
const url = \`\${ENGINE_ARCHIVE_BASE}/\${info.sha1}/engine/\${target.enginePlatform}/\${target.executable}\`;
|
|
360
|
+
console.log(\`Fetching \${url}\`);
|
|
361
|
+
const res = await fetch(url);
|
|
362
|
+
if (!res.ok) {
|
|
363
|
+
console.error(\`Engine download failed: \${res.status} \${res.statusText}\`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
await Bun.write(stockEnginePath, res);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const buildFolder = path.join("build", target.buildFolder);
|
|
370
|
+
const buildEnginePath = path.join(buildFolder, target.executable);
|
|
371
|
+
let enginePath = existsSync(buildEnginePath) ? buildEnginePath : stockEnginePath;
|
|
372
|
+
|
|
373
|
+
if (process.platform === "win32" && enginePath === buildEnginePath) {
|
|
374
|
+
for (const [src, name] of [
|
|
375
|
+
[WINDOWS_OPENAL32_PATH, "OpenAL32.dll"],
|
|
376
|
+
[WINDOWS_WRAPOAL_PATH, "wrap_oal.dll"],
|
|
377
|
+
] as const) {
|
|
378
|
+
const dest = path.join(buildFolder, name);
|
|
379
|
+
if (src && !existsSync(dest)) {
|
|
380
|
+
copyFileSync(src, dest);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// macOS: a build engine launched in place attaches to the editor process; copy
|
|
386
|
+
// it aside first so it runs standalone.
|
|
387
|
+
if (process.platform === "darwin" && enginePath === buildEnginePath) {
|
|
388
|
+
const tempEngine = path.join(buildFolder, "temp", target.executable);
|
|
389
|
+
mkdirSync(path.dirname(tempEngine), { recursive: true });
|
|
390
|
+
copyFileSync(buildEnginePath, tempEngine);
|
|
391
|
+
enginePath = tempEngine;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (process.platform !== "win32") {
|
|
395
|
+
chmodSync(enginePath, 0o755);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const projectc = path.join("build", "default", "game.projectc");
|
|
399
|
+
console.log(\`Launching \${enginePath} \${projectc}\`);
|
|
400
|
+
const proc = Bun.spawn([enginePath, projectc], {
|
|
401
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
402
|
+
});
|
|
403
|
+
process.exit(await proc.exited);
|
|
404
|
+
`;
|
|
405
|
+
}
|
|
406
|
+
var DEBUG_LAUNCHER_SOURCE = renderDebugLauncher();
|
|
407
|
+
|
|
408
|
+
// src/mise-scaffold.ts
|
|
409
|
+
var MANAGED_MARKER = "# managed by @defold-typescript";
|
|
410
|
+
var MISE_TASKS_TOML = `${MANAGED_MARKER}
|
|
411
|
+
[tasks."defold-typescript:build"]
|
|
412
|
+
description = "Build the TypeScript sources with the installed defold-typescript CLI"
|
|
413
|
+
run = "bunx --no-install defold-typescript build"
|
|
414
|
+
|
|
415
|
+
${MANAGED_MARKER}
|
|
416
|
+
[tasks."defold-typescript:watch"]
|
|
417
|
+
description = "Watch and rebuild the TypeScript sources with the installed defold-typescript CLI"
|
|
418
|
+
run = "bunx --no-install defold-typescript watch"
|
|
419
|
+
|
|
420
|
+
${MANAGED_MARKER}
|
|
421
|
+
[tasks."defold-typescript:upgrade"]
|
|
422
|
+
description = "Upgrade the defold-typescript CLI to its latest release and re-pin the types dependency"
|
|
423
|
+
run = ["bunx @defold-typescript/cli@latest init --force", "bun install"]
|
|
424
|
+
`;
|
|
425
|
+
function stripManagedBlocks(text) {
|
|
426
|
+
const lines = text.split(`
|
|
427
|
+
`);
|
|
428
|
+
const out = [];
|
|
429
|
+
let i = 0;
|
|
430
|
+
while (i < lines.length) {
|
|
431
|
+
const line = lines[i] ?? "";
|
|
432
|
+
if (line.trim() === MANAGED_MARKER) {
|
|
433
|
+
i += 1;
|
|
434
|
+
while (i < lines.length && (lines[i] ?? "").trim() !== "") {
|
|
435
|
+
i += 1;
|
|
436
|
+
}
|
|
437
|
+
if (i < lines.length) {
|
|
438
|
+
i += 1;
|
|
439
|
+
}
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
out.push(line);
|
|
443
|
+
i += 1;
|
|
444
|
+
}
|
|
445
|
+
return out.join(`
|
|
446
|
+
`);
|
|
447
|
+
}
|
|
448
|
+
function mergeMiseToml(existing) {
|
|
449
|
+
if (existing === undefined) {
|
|
450
|
+
return MISE_TASKS_TOML;
|
|
451
|
+
}
|
|
452
|
+
const userContent = stripManagedBlocks(existing).replace(/\s*$/, "");
|
|
453
|
+
if (userContent === "") {
|
|
454
|
+
return MISE_TASKS_TOML;
|
|
455
|
+
}
|
|
456
|
+
return `${userContent}
|
|
457
|
+
|
|
458
|
+
${MISE_TASKS_TOML}`;
|
|
459
|
+
}
|
|
460
|
+
|
|
298
461
|
// src/script-kind.ts
|
|
299
462
|
var DEFAULT_TYPES_ENTRYPOINT = "@defold-typescript/types";
|
|
300
463
|
var KIND_BY_EXT = {
|
|
@@ -406,43 +569,64 @@ var BIOME_JSON_CONTENT = {
|
|
|
406
569
|
}
|
|
407
570
|
};
|
|
408
571
|
var VSCODE_EXTENSIONS_CONTENT = {
|
|
409
|
-
recommendations: ["sumneko.lua", "astronachos.defold"],
|
|
572
|
+
recommendations: ["sumneko.lua", "astronachos.defold", "tomblind.local-lua-debugger-vscode"],
|
|
410
573
|
unwantedRecommendations: ["johnnymorganz.luau-lsp"]
|
|
411
574
|
};
|
|
412
575
|
var VSCODE_SETTINGS_CONTENT = {
|
|
413
576
|
"Lua.workspace.ignoreDir": ["src"]
|
|
414
577
|
};
|
|
578
|
+
var HOOK_COMMENTS = {
|
|
579
|
+
init: "Initialize the component and return its state.",
|
|
580
|
+
update: "Update the component every frame; `dt` is the time step.",
|
|
581
|
+
fixed_update: "Update at the fixed physics time step.",
|
|
582
|
+
late_update: "Update every frame after `update`.",
|
|
583
|
+
on_message: "Handle an incoming message.",
|
|
584
|
+
on_input: "Handle input once input focus is acquired.",
|
|
585
|
+
final: "Clean up when the component is deleted.",
|
|
586
|
+
on_reload: "React to a hot reload of this script."
|
|
587
|
+
};
|
|
588
|
+
var HOOK_SIGNATURES = {
|
|
589
|
+
init: "",
|
|
590
|
+
update: "self, dt",
|
|
591
|
+
fixed_update: "self, dt",
|
|
592
|
+
late_update: "self, dt",
|
|
593
|
+
on_message: "self, message_id, message, sender",
|
|
594
|
+
on_input: "self, action_id, action",
|
|
595
|
+
final: "self",
|
|
596
|
+
on_reload: "self"
|
|
597
|
+
};
|
|
598
|
+
var SNIPPET_HOOK_ORDER = Object.keys(HOOK_SIGNATURES);
|
|
599
|
+
function hookLines(includeOnInput, startTabStop) {
|
|
600
|
+
const lines = [];
|
|
601
|
+
let tabStop = startTabStop;
|
|
602
|
+
for (const hook of SNIPPET_HOOK_ORDER) {
|
|
603
|
+
if (hook === "init") {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (hook === "on_input" && !includeOnInput) {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
lines.push(` // ${HOOK_COMMENTS[hook]}`);
|
|
610
|
+
lines.push(` ${hook}(${HOOK_SIGNATURES[hook]}) {$${tabStop}},`);
|
|
611
|
+
tabStop += 1;
|
|
612
|
+
}
|
|
613
|
+
return lines;
|
|
614
|
+
}
|
|
415
615
|
function inlineSnippetBody(factory, includeOnInput) {
|
|
416
|
-
|
|
616
|
+
return [
|
|
417
617
|
`import { ${factory} } from "@defold-typescript/types";`,
|
|
418
618
|
"",
|
|
419
619
|
`export const script = ${factory}({`,
|
|
420
|
-
|
|
620
|
+
` // ${HOOK_COMMENTS.init}`,
|
|
421
621
|
" init() {",
|
|
422
622
|
" return { $0 };",
|
|
423
623
|
" },",
|
|
424
|
-
|
|
425
|
-
"
|
|
426
|
-
" // Update at the fixed physics time step.",
|
|
427
|
-
" fixed_update(self, dt) {$2},",
|
|
428
|
-
" // Update every frame after `update`.",
|
|
429
|
-
" late_update(self, dt) {$3},",
|
|
430
|
-
" // Handle an incoming message.",
|
|
431
|
-
" on_message(self, message_id, message, sender) {$4},"
|
|
624
|
+
...hookLines(includeOnInput, 1),
|
|
625
|
+
"});"
|
|
432
626
|
];
|
|
433
|
-
if (includeOnInput) {
|
|
434
|
-
lines.push(" // Handle input once input focus is acquired.");
|
|
435
|
-
lines.push(" on_input(self, action_id, action) {$5},");
|
|
436
|
-
}
|
|
437
|
-
lines.push(" // Clean up when the component is deleted.");
|
|
438
|
-
lines.push(" final(self) {$6},");
|
|
439
|
-
lines.push(" // React to a hot reload of this script.");
|
|
440
|
-
lines.push(" on_reload(self) {$7},");
|
|
441
|
-
lines.push("});");
|
|
442
|
-
return lines;
|
|
443
627
|
}
|
|
444
628
|
function typedSnippetBody(factory, includeOnInput) {
|
|
445
|
-
|
|
629
|
+
return [
|
|
446
630
|
`import { ${factory} } from "@defold-typescript/types";`,
|
|
447
631
|
"",
|
|
448
632
|
"type Self = {",
|
|
@@ -451,29 +635,13 @@ function typedSnippetBody(factory, includeOnInput) {
|
|
|
451
635
|
"};",
|
|
452
636
|
"",
|
|
453
637
|
`export const script = ${factory}<Self>({`,
|
|
454
|
-
|
|
638
|
+
` // ${HOOK_COMMENTS.init}`,
|
|
455
639
|
" init(): Self {",
|
|
456
640
|
" return { $0 };",
|
|
457
641
|
" },",
|
|
458
|
-
|
|
459
|
-
"
|
|
460
|
-
" // Update at the fixed physics time step.",
|
|
461
|
-
" fixed_update(self, dt) {$3},",
|
|
462
|
-
" // Update every frame after `update`.",
|
|
463
|
-
" late_update(self, dt) {$4},",
|
|
464
|
-
" // Handle an incoming message.",
|
|
465
|
-
" on_message(self, message_id, message, sender) {$5},"
|
|
642
|
+
...hookLines(includeOnInput, 2),
|
|
643
|
+
"});"
|
|
466
644
|
];
|
|
467
|
-
if (includeOnInput) {
|
|
468
|
-
lines.push(" // Handle input once input focus is acquired.");
|
|
469
|
-
lines.push(" on_input(self, action_id, action) {$6},");
|
|
470
|
-
}
|
|
471
|
-
lines.push(" // Clean up when the component is deleted.");
|
|
472
|
-
lines.push(" final(self) {$7},");
|
|
473
|
-
lines.push(" // React to a hot reload of this script.");
|
|
474
|
-
lines.push(" on_reload(self) {$8},");
|
|
475
|
-
lines.push("});");
|
|
476
|
-
return lines;
|
|
477
645
|
}
|
|
478
646
|
var VSCODE_SNIPPETS_CONTENT = {
|
|
479
647
|
"Defold script (inferred self)": {
|
|
@@ -586,6 +754,12 @@ function writeBiome(cwd, written) {
|
|
|
586
754
|
writeJson(biomePath, BIOME_JSON_CONTENT);
|
|
587
755
|
written.push("biome.json");
|
|
588
756
|
}
|
|
757
|
+
function writeMiseTasks(cwd, written) {
|
|
758
|
+
const misePath = path7.join(cwd, "mise.toml");
|
|
759
|
+
const existing = existsSync2(misePath) ? readFileSync6(misePath, "utf8") : undefined;
|
|
760
|
+
writeFileSync2(misePath, mergeMiseToml(existing));
|
|
761
|
+
written.push("mise.toml");
|
|
762
|
+
}
|
|
589
763
|
function parseJsonc(text) {
|
|
590
764
|
let out = "";
|
|
591
765
|
let inString = false;
|
|
@@ -707,6 +881,39 @@ function writeVscodeSnippets(cwd, written) {
|
|
|
707
881
|
writeJson(filePath, VSCODE_SNIPPETS_CONTENT);
|
|
708
882
|
written.push(".vscode/defold-typescript.code-snippets");
|
|
709
883
|
}
|
|
884
|
+
function writeVscodeLaunch(cwd, written) {
|
|
885
|
+
const dir = path7.join(cwd, ".vscode");
|
|
886
|
+
const filePath = path7.join(dir, "launch.json");
|
|
887
|
+
const ours = debugLaunchConfig();
|
|
888
|
+
if (existsSync2(filePath)) {
|
|
889
|
+
const existing = readVscodeJson(filePath);
|
|
890
|
+
if (existing === null) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const configs = Array.isArray(existing.configurations) ? [...existing.configurations] : [];
|
|
894
|
+
const names = new Set(configs.map((c) => isJsonObject(c) ? c.name : undefined));
|
|
895
|
+
if (!names.has(ours.name)) {
|
|
896
|
+
configs.push(ours);
|
|
897
|
+
}
|
|
898
|
+
existing.configurations = configs;
|
|
899
|
+
existing.version ??= VSCODE_LAUNCH_CONTENT.version;
|
|
900
|
+
writeJson(filePath, existing);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
mkdirSync2(dir, { recursive: true });
|
|
904
|
+
writeJson(filePath, VSCODE_LAUNCH_CONTENT);
|
|
905
|
+
written.push(".vscode/launch.json");
|
|
906
|
+
}
|
|
907
|
+
function writeVscodeDebugLauncher(cwd, written) {
|
|
908
|
+
const dir = path7.join(cwd, ".vscode");
|
|
909
|
+
const filePath = path7.join(dir, "defold-debug.ts");
|
|
910
|
+
if (existsSync2(filePath)) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
mkdirSync2(dir, { recursive: true });
|
|
914
|
+
writeFileSync2(filePath, DEBUG_LAUNCHER_SOURCE);
|
|
915
|
+
written.push(".vscode/defold-debug.ts");
|
|
916
|
+
}
|
|
710
917
|
function writeTsSurface(cwd, written, force = false) {
|
|
711
918
|
mkdirSync2(path7.join(cwd, "src"), { recursive: true });
|
|
712
919
|
writeFileSync2(path7.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
|
|
@@ -748,9 +955,12 @@ function writeTsSurface(cwd, written, force = false) {
|
|
|
748
955
|
writeGitignore(cwd);
|
|
749
956
|
written.push(".gitignore");
|
|
750
957
|
writeBiome(cwd, written);
|
|
958
|
+
writeMiseTasks(cwd, written);
|
|
751
959
|
writeVscodeExtensions(cwd, written);
|
|
752
960
|
writeVscodeSettings(cwd, written);
|
|
753
961
|
writeVscodeSnippets(cwd, written);
|
|
962
|
+
writeVscodeLaunch(cwd, written);
|
|
963
|
+
writeVscodeDebugLauncher(cwd, written);
|
|
754
964
|
return selectScriptKind(kinds);
|
|
755
965
|
}
|
|
756
966
|
function runNewProjectInit(cwd, force = false) {
|
|
@@ -833,7 +1043,7 @@ function materializeApiSurface(opts) {
|
|
|
833
1043
|
const excluded = excludedModulesForKind(opts.scriptKind ?? null);
|
|
834
1044
|
const sources = listDts(sourceGeneratedDir).filter((file) => file !== "index.d.ts").filter((file) => !excluded.has(file.replace(/\.d\.ts$/, "")));
|
|
835
1045
|
const srcDir = path8.resolve(sourceGeneratedDir, "..", "src");
|
|
836
|
-
const overloads = ["msg-overloads.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path8.join(srcDir, file)));
|
|
1046
|
+
const overloads = ["msg-overloads.d.ts", "message-guard.d.ts", "go-overloads.d.ts"].filter((file) => existsSync3(path8.join(srcDir, file)));
|
|
837
1047
|
const coreTypesSrc = path8.join(srcDir, "core-types.ts");
|
|
838
1048
|
const includeCoreTypes = overloads.length > 0 && existsSync3(coreTypesSrc);
|
|
839
1049
|
const engineGlobalsSrc = path8.join(srcDir, "engine-globals.d.ts");
|
|
@@ -908,12 +1118,16 @@ function ensureMaterializedReference(cwd, materializedDir) {
|
|
|
908
1118
|
const tsconfigPath = path8.join(cwd, "tsconfig.json");
|
|
909
1119
|
if (existsSync3(tsconfigPath)) {
|
|
910
1120
|
const tsconfig = JSON.parse(readFileSync7(tsconfigPath, "utf8"));
|
|
911
|
-
tsconfig.compilerOptions
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1121
|
+
const current = tsconfig.compilerOptions ?? {};
|
|
1122
|
+
const alreadyRepointed = JSON.stringify(current.typeRoots) === JSON.stringify([MATERIALIZED_ROOT]) && JSON.stringify(current.types) === JSON.stringify([surfaceId]);
|
|
1123
|
+
if (!alreadyRepointed) {
|
|
1124
|
+
tsconfig.compilerOptions = {
|
|
1125
|
+
...current,
|
|
1126
|
+
typeRoots: [MATERIALIZED_ROOT],
|
|
1127
|
+
types: [surfaceId]
|
|
1128
|
+
};
|
|
1129
|
+
writeJson2(tsconfigPath, tsconfig);
|
|
1130
|
+
}
|
|
917
1131
|
}
|
|
918
1132
|
ensureGitignoreLine(cwd, `${MATERIALIZED_ROOT}/`);
|
|
919
1133
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const MISE_TASKS_TOML = "# managed by @defold-typescript\n[tasks.\"defold-typescript:build\"]\ndescription = \"Build the TypeScript sources with the installed defold-typescript CLI\"\nrun = \"bunx --no-install defold-typescript build\"\n\n# managed by @defold-typescript\n[tasks.\"defold-typescript:watch\"]\ndescription = \"Watch and rebuild the TypeScript sources with the installed defold-typescript CLI\"\nrun = \"bunx --no-install defold-typescript watch\"\n\n# managed by @defold-typescript\n[tasks.\"defold-typescript:upgrade\"]\ndescription = \"Upgrade the defold-typescript CLI to its latest release and re-pin the types dependency\"\nrun = [\"bunx @defold-typescript/cli@latest init --force\", \"bun install\"]\n";
|
|
2
|
+
export declare function mergeMiseToml(existing?: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "End-user CLI for scaffolding and building Defold projects written in TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"test": "bun test"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@defold-typescript/transpiler": "0.5.
|
|
35
|
-
"@defold-typescript/types": "0.5.
|
|
34
|
+
"@defold-typescript/transpiler": "0.5.3",
|
|
35
|
+
"@defold-typescript/types": "0.5.3"
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
|
|
3
|
+
export interface EngineTarget {
|
|
4
|
+
readonly enginePlatform: string;
|
|
5
|
+
readonly buildFolder: string;
|
|
6
|
+
readonly executable: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// `enginePlatform` keys the d.defold.com download path; `buildFolder` keys the
|
|
10
|
+
// native-extension build output. They diverge on macOS (`x86_64-darwin` vs
|
|
11
|
+
// `x86_64-osx`), which is why they are tracked separately.
|
|
12
|
+
const PLATFORM_TARGETS: Record<string, EngineTarget> = {
|
|
13
|
+
darwin: { enginePlatform: "x86_64-darwin", buildFolder: "x86_64-osx", executable: "dmengine" },
|
|
14
|
+
linux: { enginePlatform: "x86_64-linux", buildFolder: "x86_64-linux", executable: "dmengine" },
|
|
15
|
+
win32: {
|
|
16
|
+
enginePlatform: "x86_64-win32",
|
|
17
|
+
buildFolder: "x86_64-win32",
|
|
18
|
+
executable: "dmengine.exe",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ENGINE_INFO_URL = "https://d.defold.com/stable/info.json";
|
|
23
|
+
const ENGINE_ARCHIVE_BASE = "https://d.defold.com/archive/stable";
|
|
24
|
+
|
|
25
|
+
export const DEBUG_LAUNCHER_REL = ".vscode/defold-debug.ts";
|
|
26
|
+
|
|
27
|
+
export function targetPlatform(platform: NodeJS.Platform): EngineTarget {
|
|
28
|
+
const target = PLATFORM_TARGETS[platform];
|
|
29
|
+
if (!target) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`defold-typescript debug: unsupported platform "${platform}"; expected one of ${Object.keys(
|
|
32
|
+
PLATFORM_TARGETS,
|
|
33
|
+
).join(", ")}.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function engineDownloadUrl(
|
|
40
|
+
sha1: string,
|
|
41
|
+
enginePlatform: string,
|
|
42
|
+
executable: string,
|
|
43
|
+
): string {
|
|
44
|
+
return `${ENGINE_ARCHIVE_BASE}/${sha1}/engine/${enginePlatform}/${executable}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ResolveEngineOptions {
|
|
48
|
+
readonly cwd: string;
|
|
49
|
+
readonly target: EngineTarget;
|
|
50
|
+
readonly stockPath: string;
|
|
51
|
+
readonly probe: (candidate: string) => boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Prefer the native-extension build engine when it exists; the stock engine is
|
|
55
|
+
// the fallback for projects without native extensions.
|
|
56
|
+
export function resolveEnginePath(opts: ResolveEngineOptions): string {
|
|
57
|
+
const { cwd, target, stockPath, probe } = opts;
|
|
58
|
+
const buildEnginePath = path.join(cwd, "build", target.buildFolder, target.executable);
|
|
59
|
+
return probe(buildEnginePath) ? buildEnginePath : stockPath;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function debugLaunchConfig() {
|
|
63
|
+
return {
|
|
64
|
+
name: "Defold: Debug (TypeScript)",
|
|
65
|
+
type: "lua-local",
|
|
66
|
+
request: "launch",
|
|
67
|
+
stopOnEntry: false,
|
|
68
|
+
verbose: false,
|
|
69
|
+
internalConsoleOptions: "openOnSessionStart",
|
|
70
|
+
program: { command: "bun" },
|
|
71
|
+
args: [DEBUG_LAUNCHER_REL],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const VSCODE_LAUNCH_CONTENT = {
|
|
76
|
+
version: "0.2.0",
|
|
77
|
+
configurations: [debugLaunchConfig()],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// The scaffolded launcher embeds the same platform table and archive endpoints
|
|
81
|
+
// the helpers above use, so the self-contained `.vscode/defold-debug.ts` and the
|
|
82
|
+
// unit-tested logic stay in lockstep. It is a Bun script: `process.platform` for
|
|
83
|
+
// the OS, `fetch` for the engine download, `Bun.spawn` with inherited stdio for
|
|
84
|
+
// the run (the pipe Local Lua Debugger attaches over). No shell, no Git Bash.
|
|
85
|
+
function renderDebugLauncher(): string {
|
|
86
|
+
const targets = JSON.stringify(PLATFORM_TARGETS, null, 2);
|
|
87
|
+
return `import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
88
|
+
import * as path from "node:path";
|
|
89
|
+
|
|
90
|
+
// Windows only: paths to OpenAL32.dll and wrap_oal.dll from your Defold SDK
|
|
91
|
+
// (defoldsdk/ext/lib/x86_64-win32/). Leave empty on macOS/Linux.
|
|
92
|
+
const WINDOWS_OPENAL32_PATH = "";
|
|
93
|
+
const WINDOWS_WRAPOAL_PATH = "";
|
|
94
|
+
|
|
95
|
+
interface EngineTarget {
|
|
96
|
+
enginePlatform: string;
|
|
97
|
+
buildFolder: string;
|
|
98
|
+
executable: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const PLATFORM_TARGETS: Record<string, EngineTarget> = ${targets};
|
|
102
|
+
|
|
103
|
+
const ENGINE_INFO_URL = "${ENGINE_INFO_URL}";
|
|
104
|
+
const ENGINE_ARCHIVE_BASE = "${ENGINE_ARCHIVE_BASE}";
|
|
105
|
+
|
|
106
|
+
const target = PLATFORM_TARGETS[process.platform];
|
|
107
|
+
if (!target) {
|
|
108
|
+
console.error(\`Unsupported platform: \${process.platform}\`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const here = path.dirname(new URL(import.meta.url).pathname);
|
|
113
|
+
const stockEnginePath = path.join(here, target.executable);
|
|
114
|
+
|
|
115
|
+
if (!existsSync(stockEnginePath)) {
|
|
116
|
+
const info = (await (await fetch(ENGINE_INFO_URL)).json()) as { sha1: string };
|
|
117
|
+
const url = \`\${ENGINE_ARCHIVE_BASE}/\${info.sha1}/engine/\${target.enginePlatform}/\${target.executable}\`;
|
|
118
|
+
console.log(\`Fetching \${url}\`);
|
|
119
|
+
const res = await fetch(url);
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
console.error(\`Engine download failed: \${res.status} \${res.statusText}\`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
await Bun.write(stockEnginePath, res);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const buildFolder = path.join("build", target.buildFolder);
|
|
128
|
+
const buildEnginePath = path.join(buildFolder, target.executable);
|
|
129
|
+
let enginePath = existsSync(buildEnginePath) ? buildEnginePath : stockEnginePath;
|
|
130
|
+
|
|
131
|
+
if (process.platform === "win32" && enginePath === buildEnginePath) {
|
|
132
|
+
for (const [src, name] of [
|
|
133
|
+
[WINDOWS_OPENAL32_PATH, "OpenAL32.dll"],
|
|
134
|
+
[WINDOWS_WRAPOAL_PATH, "wrap_oal.dll"],
|
|
135
|
+
] as const) {
|
|
136
|
+
const dest = path.join(buildFolder, name);
|
|
137
|
+
if (src && !existsSync(dest)) {
|
|
138
|
+
copyFileSync(src, dest);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// macOS: a build engine launched in place attaches to the editor process; copy
|
|
144
|
+
// it aside first so it runs standalone.
|
|
145
|
+
if (process.platform === "darwin" && enginePath === buildEnginePath) {
|
|
146
|
+
const tempEngine = path.join(buildFolder, "temp", target.executable);
|
|
147
|
+
mkdirSync(path.dirname(tempEngine), { recursive: true });
|
|
148
|
+
copyFileSync(buildEnginePath, tempEngine);
|
|
149
|
+
enginePath = tempEngine;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (process.platform !== "win32") {
|
|
153
|
+
chmodSync(enginePath, 0o755);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const projectc = path.join("build", "default", "game.projectc");
|
|
157
|
+
console.log(\`Launching \${enginePath} \${projectc}\`);
|
|
158
|
+
const proc = Bun.spawn([enginePath, projectc], {
|
|
159
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
160
|
+
});
|
|
161
|
+
process.exit(await proc.exited);
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const DEBUG_LAUNCHER_SOURCE = renderDebugLauncher();
|
package/src/init.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { ScriptHookName } from "@defold-typescript/types";
|
|
5
|
+
import { DEBUG_LAUNCHER_SOURCE, debugLaunchConfig, VSCODE_LAUNCH_CONTENT } from "./debug-launcher";
|
|
4
6
|
import { CURRENT_STABLE_DEFOLD_VERSION } from "./defold-version";
|
|
7
|
+
import { mergeMiseToml } from "./mise-scaffold";
|
|
5
8
|
import {
|
|
6
9
|
detectScriptKinds,
|
|
7
10
|
type ScriptKind,
|
|
@@ -73,7 +76,7 @@ const BIOME_JSON_CONTENT = {
|
|
|
73
76
|
};
|
|
74
77
|
|
|
75
78
|
const VSCODE_EXTENSIONS_CONTENT = {
|
|
76
|
-
recommendations: ["sumneko.lua", "astronachos.defold"],
|
|
79
|
+
recommendations: ["sumneko.lua", "astronachos.defold", "tomblind.local-lua-debugger-vscode"],
|
|
77
80
|
unwantedRecommendations: ["johnnymorganz.luau-lsp"],
|
|
78
81
|
};
|
|
79
82
|
|
|
@@ -88,43 +91,81 @@ interface VscodeSnippet {
|
|
|
88
91
|
description: string;
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
// One learn-more comment and one parameter list per lifecycle hook, keyed by
|
|
95
|
+
// `ScriptHookName` so a hook added to the types fails to compile here until both
|
|
96
|
+
// maps gain an entry (`satisfies` exhaustiveness — the type is derived from the
|
|
97
|
+
// canonical `SCRIPT_HOOK_NAMES`). The hook list is read off these keys rather
|
|
98
|
+
// than imported as a runtime value: the types package is type-only and not
|
|
99
|
+
// node-ESM-runnable, so the CLI bundle must not resolve it at runtime. `init` is
|
|
100
|
+
// special-cased by the body builders (it carries the return placeholder, not a
|
|
101
|
+
// `self` param), so its signature entry is unused but still required for
|
|
102
|
+
// exhaustiveness.
|
|
103
|
+
const HOOK_COMMENTS = {
|
|
104
|
+
init: "Initialize the component and return its state.",
|
|
105
|
+
update: "Update the component every frame; `dt` is the time step.",
|
|
106
|
+
fixed_update: "Update at the fixed physics time step.",
|
|
107
|
+
late_update: "Update every frame after `update`.",
|
|
108
|
+
on_message: "Handle an incoming message.",
|
|
109
|
+
on_input: "Handle input once input focus is acquired.",
|
|
110
|
+
final: "Clean up when the component is deleted.",
|
|
111
|
+
on_reload: "React to a hot reload of this script.",
|
|
112
|
+
} satisfies Record<ScriptHookName, string>;
|
|
113
|
+
|
|
114
|
+
const HOOK_SIGNATURES = {
|
|
115
|
+
init: "",
|
|
116
|
+
update: "self, dt",
|
|
117
|
+
fixed_update: "self, dt",
|
|
118
|
+
late_update: "self, dt",
|
|
119
|
+
on_message: "self, message_id, message, sender",
|
|
120
|
+
on_input: "self, action_id, action",
|
|
121
|
+
final: "self",
|
|
122
|
+
on_reload: "self",
|
|
123
|
+
} satisfies Record<ScriptHookName, string>;
|
|
124
|
+
|
|
125
|
+
const SNIPPET_HOOK_ORDER = Object.keys(HOOK_SIGNATURES) as ScriptHookName[];
|
|
126
|
+
|
|
127
|
+
// Emit every hook except `init` (the caller writes it with its return
|
|
128
|
+
// placeholder) as a commented `name(sig) {$N},` line. Render scripts pass
|
|
129
|
+
// includeOnInput=false because `RenderScriptHooks` omits `on_input`. Tab stops
|
|
130
|
+
// run sequentially from `startTabStop` across the hooks actually emitted.
|
|
131
|
+
function hookLines(includeOnInput: boolean, startTabStop: number): string[] {
|
|
132
|
+
const lines: string[] = [];
|
|
133
|
+
let tabStop = startTabStop;
|
|
134
|
+
for (const hook of SNIPPET_HOOK_ORDER) {
|
|
135
|
+
if (hook === "init") {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (hook === "on_input" && !includeOnInput) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
lines.push(` // ${HOOK_COMMENTS[hook]}`);
|
|
142
|
+
lines.push(` ${hook}(${HOOK_SIGNATURES[hook]}) {$${tabStop}},`);
|
|
143
|
+
tabStop += 1;
|
|
144
|
+
}
|
|
145
|
+
return lines;
|
|
146
|
+
}
|
|
147
|
+
|
|
91
148
|
// Whole-file TS scaffolds mirroring the Defold editor's empty script/gui/render
|
|
92
149
|
// templates over the lifecycle factories. Two self-typing variants per kind:
|
|
93
150
|
// inline-self (TSelf inferred from `init`'s return) and typed-self (an explicit
|
|
94
151
|
// dummy `Self` placeholder). Hook order mirrors the Lua templates; render omits
|
|
95
152
|
// `on_input` because `RenderScriptHooks` does. The final `$0` lands inside `init`.
|
|
96
153
|
function inlineSnippetBody(factory: string, includeOnInput: boolean): string[] {
|
|
97
|
-
|
|
154
|
+
return [
|
|
98
155
|
`import { ${factory} } from "@defold-typescript/types";`,
|
|
99
156
|
"",
|
|
100
157
|
`export const script = ${factory}({`,
|
|
101
|
-
|
|
158
|
+
` // ${HOOK_COMMENTS.init}`,
|
|
102
159
|
" init() {",
|
|
103
160
|
" return { $0 };",
|
|
104
161
|
" },",
|
|
105
|
-
|
|
106
|
-
"
|
|
107
|
-
" // Update at the fixed physics time step.",
|
|
108
|
-
" fixed_update(self, dt) {$2},",
|
|
109
|
-
" // Update every frame after `update`.",
|
|
110
|
-
" late_update(self, dt) {$3},",
|
|
111
|
-
" // Handle an incoming message.",
|
|
112
|
-
" on_message(self, message_id, message, sender) {$4},",
|
|
162
|
+
...hookLines(includeOnInput, 1),
|
|
163
|
+
"});",
|
|
113
164
|
];
|
|
114
|
-
if (includeOnInput) {
|
|
115
|
-
lines.push(" // Handle input once input focus is acquired.");
|
|
116
|
-
lines.push(" on_input(self, action_id, action) {$5},");
|
|
117
|
-
}
|
|
118
|
-
lines.push(" // Clean up when the component is deleted.");
|
|
119
|
-
lines.push(" final(self) {$6},");
|
|
120
|
-
lines.push(" // React to a hot reload of this script.");
|
|
121
|
-
lines.push(" on_reload(self) {$7},");
|
|
122
|
-
lines.push("});");
|
|
123
|
-
return lines;
|
|
124
165
|
}
|
|
125
166
|
|
|
126
167
|
function typedSnippetBody(factory: string, includeOnInput: boolean): string[] {
|
|
127
|
-
|
|
168
|
+
return [
|
|
128
169
|
`import { ${factory} } from "@defold-typescript/types";`,
|
|
129
170
|
"",
|
|
130
171
|
"type Self = {",
|
|
@@ -133,29 +174,13 @@ function typedSnippetBody(factory: string, includeOnInput: boolean): string[] {
|
|
|
133
174
|
"};",
|
|
134
175
|
"",
|
|
135
176
|
`export const script = ${factory}<Self>({`,
|
|
136
|
-
|
|
177
|
+
` // ${HOOK_COMMENTS.init}`,
|
|
137
178
|
" init(): Self {",
|
|
138
179
|
" return { $0 };",
|
|
139
180
|
" },",
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
" // Update at the fixed physics time step.",
|
|
143
|
-
" fixed_update(self, dt) {$3},",
|
|
144
|
-
" // Update every frame after `update`.",
|
|
145
|
-
" late_update(self, dt) {$4},",
|
|
146
|
-
" // Handle an incoming message.",
|
|
147
|
-
" on_message(self, message_id, message, sender) {$5},",
|
|
181
|
+
...hookLines(includeOnInput, 2),
|
|
182
|
+
"});",
|
|
148
183
|
];
|
|
149
|
-
if (includeOnInput) {
|
|
150
|
-
lines.push(" // Handle input once input focus is acquired.");
|
|
151
|
-
lines.push(" on_input(self, action_id, action) {$6},");
|
|
152
|
-
}
|
|
153
|
-
lines.push(" // Clean up when the component is deleted.");
|
|
154
|
-
lines.push(" final(self) {$7},");
|
|
155
|
-
lines.push(" // React to a hot reload of this script.");
|
|
156
|
-
lines.push(" on_reload(self) {$8},");
|
|
157
|
-
lines.push("});");
|
|
158
|
-
return lines;
|
|
159
184
|
}
|
|
160
185
|
|
|
161
186
|
const VSCODE_SNIPPETS_CONTENT: Record<string, VscodeSnippet> = {
|
|
@@ -297,6 +322,13 @@ function writeBiome(cwd: string, written: string[]): void {
|
|
|
297
322
|
written.push("biome.json");
|
|
298
323
|
}
|
|
299
324
|
|
|
325
|
+
function writeMiseTasks(cwd: string, written: string[]): void {
|
|
326
|
+
const misePath = path.join(cwd, "mise.toml");
|
|
327
|
+
const existing = existsSync(misePath) ? readFileSync(misePath, "utf8") : undefined;
|
|
328
|
+
writeFileSync(misePath, mergeMiseToml(existing));
|
|
329
|
+
written.push("mise.toml");
|
|
330
|
+
}
|
|
331
|
+
|
|
300
332
|
// Strip `//` line comments, `/* */` block comments, and trailing commas so a
|
|
301
333
|
// hand-edited JSONC `.vscode` file parses with `JSON.parse`. The walk tracks
|
|
302
334
|
// string state so a `//` or comma inside a value (e.g. a URL) is preserved.
|
|
@@ -438,6 +470,41 @@ function writeVscodeSnippets(cwd: string, written: string[]): void {
|
|
|
438
470
|
written.push(".vscode/defold-typescript.code-snippets");
|
|
439
471
|
}
|
|
440
472
|
|
|
473
|
+
function writeVscodeLaunch(cwd: string, written: string[]): void {
|
|
474
|
+
const dir = path.join(cwd, ".vscode");
|
|
475
|
+
const filePath = path.join(dir, "launch.json");
|
|
476
|
+
const ours = debugLaunchConfig();
|
|
477
|
+
if (existsSync(filePath)) {
|
|
478
|
+
const existing = readVscodeJson(filePath);
|
|
479
|
+
if (existing === null) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const configs = Array.isArray(existing.configurations) ? [...existing.configurations] : [];
|
|
483
|
+
const names = new Set(configs.map((c) => (isJsonObject(c) ? c.name : undefined)));
|
|
484
|
+
if (!names.has(ours.name)) {
|
|
485
|
+
configs.push(ours);
|
|
486
|
+
}
|
|
487
|
+
existing.configurations = configs;
|
|
488
|
+
existing.version ??= VSCODE_LAUNCH_CONTENT.version;
|
|
489
|
+
writeJson(filePath, existing);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
mkdirSync(dir, { recursive: true });
|
|
493
|
+
writeJson(filePath, VSCODE_LAUNCH_CONTENT);
|
|
494
|
+
written.push(".vscode/launch.json");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function writeVscodeDebugLauncher(cwd: string, written: string[]): void {
|
|
498
|
+
const dir = path.join(cwd, ".vscode");
|
|
499
|
+
const filePath = path.join(dir, "defold-debug.ts");
|
|
500
|
+
if (existsSync(filePath)) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
mkdirSync(dir, { recursive: true });
|
|
504
|
+
writeFileSync(filePath, DEBUG_LAUNCHER_SOURCE);
|
|
505
|
+
written.push(".vscode/defold-debug.ts");
|
|
506
|
+
}
|
|
507
|
+
|
|
441
508
|
function writeTsSurface(cwd: string, written: string[], force = false): ScriptKind | null {
|
|
442
509
|
mkdirSync(path.join(cwd, "src"), { recursive: true });
|
|
443
510
|
writeFileSync(path.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
|
|
@@ -483,10 +550,13 @@ function writeTsSurface(cwd: string, written: string[], force = false): ScriptKi
|
|
|
483
550
|
written.push(".gitignore");
|
|
484
551
|
|
|
485
552
|
writeBiome(cwd, written);
|
|
553
|
+
writeMiseTasks(cwd, written);
|
|
486
554
|
|
|
487
555
|
writeVscodeExtensions(cwd, written);
|
|
488
556
|
writeVscodeSettings(cwd, written);
|
|
489
557
|
writeVscodeSnippets(cwd, written);
|
|
558
|
+
writeVscodeLaunch(cwd, written);
|
|
559
|
+
writeVscodeDebugLauncher(cwd, written);
|
|
490
560
|
|
|
491
561
|
return selectScriptKind(kinds);
|
|
492
562
|
}
|
package/src/materialize.ts
CHANGED
|
@@ -65,8 +65,8 @@ export function materializeApiSurface(
|
|
|
65
65
|
// surface silently drops those globals. Skipped when the source has no
|
|
66
66
|
// sibling `src/` (e.g. synthetic test fixtures).
|
|
67
67
|
const srcDir = path.resolve(sourceGeneratedDir, "..", "src");
|
|
68
|
-
const overloads = ["msg-overloads.d.ts", "go-overloads.d.ts"].filter(
|
|
69
|
-
existsSync(path.join(srcDir, file)),
|
|
68
|
+
const overloads = ["msg-overloads.d.ts", "message-guard.d.ts", "go-overloads.d.ts"].filter(
|
|
69
|
+
(file) => existsSync(path.join(srcDir, file)),
|
|
70
70
|
);
|
|
71
71
|
const coreTypesSrc = path.join(srcDir, "core-types.ts");
|
|
72
72
|
const includeCoreTypes = overloads.length > 0 && existsSync(coreTypesSrc);
|
|
@@ -148,12 +148,21 @@ export function ensureMaterializedReference(cwd: string, materializedDir: string
|
|
|
148
148
|
compilerOptions?: Record<string, unknown>;
|
|
149
149
|
[key: string]: unknown;
|
|
150
150
|
};
|
|
151
|
-
tsconfig.compilerOptions
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
const current = tsconfig.compilerOptions ?? {};
|
|
152
|
+
// Skip the write when already repointed so the file keeps its existing
|
|
153
|
+
// formatting (a consumer's Biome/Prettier shape) instead of churning to
|
|
154
|
+
// JSON.stringify's layout on every build.
|
|
155
|
+
const alreadyRepointed =
|
|
156
|
+
JSON.stringify(current.typeRoots) === JSON.stringify([MATERIALIZED_ROOT]) &&
|
|
157
|
+
JSON.stringify(current.types) === JSON.stringify([surfaceId]);
|
|
158
|
+
if (!alreadyRepointed) {
|
|
159
|
+
tsconfig.compilerOptions = {
|
|
160
|
+
...current,
|
|
161
|
+
typeRoots: [MATERIALIZED_ROOT],
|
|
162
|
+
types: [surfaceId],
|
|
163
|
+
};
|
|
164
|
+
writeJson(tsconfigPath, tsconfig);
|
|
165
|
+
}
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
ensureGitignoreLine(cwd, `${MATERIALIZED_ROOT}/`);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// The repo has no TOML parser and must not add one for three tasks, so the
|
|
2
|
+
// managed block is emitted as a literal string and merged line-aware. Every
|
|
3
|
+
// managed task is fronted by this marker so a re-merge can locate and refresh
|
|
4
|
+
// the block without disturbing user-authored `[tools]`/`[tasks.*]` content.
|
|
5
|
+
const MANAGED_MARKER = "# managed by @defold-typescript";
|
|
6
|
+
|
|
7
|
+
// `bunx --no-install` resolves only the locally installed binary and never
|
|
8
|
+
// fetches from the registry — that is the installed-version contract the types
|
|
9
|
+
// pin upholds, while staying cross-platform (the bare `node_modules/.bin` path
|
|
10
|
+
// is `.cmd`-shimmed on Windows). `:upgrade` is the deliberate `@latest` pull
|
|
11
|
+
// that re-pins `@defold-typescript/types` via `init --force` + reinstall.
|
|
12
|
+
export const MISE_TASKS_TOML = `${MANAGED_MARKER}
|
|
13
|
+
[tasks."defold-typescript:build"]
|
|
14
|
+
description = "Build the TypeScript sources with the installed defold-typescript CLI"
|
|
15
|
+
run = "bunx --no-install defold-typescript build"
|
|
16
|
+
|
|
17
|
+
${MANAGED_MARKER}
|
|
18
|
+
[tasks."defold-typescript:watch"]
|
|
19
|
+
description = "Watch and rebuild the TypeScript sources with the installed defold-typescript CLI"
|
|
20
|
+
run = "bunx --no-install defold-typescript watch"
|
|
21
|
+
|
|
22
|
+
${MANAGED_MARKER}
|
|
23
|
+
[tasks."defold-typescript:upgrade"]
|
|
24
|
+
description = "Upgrade the defold-typescript CLI to its latest release and re-pin the types dependency"
|
|
25
|
+
run = ["bunx @defold-typescript/cli@latest init --force", "bun install"]
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
// Drop every managed block (marker line through the next blank line or EOF),
|
|
29
|
+
// leaving all other lines byte-identical, so a refresh strips the stale block
|
|
30
|
+
// before the fresh one is re-appended.
|
|
31
|
+
function stripManagedBlocks(text: string): string {
|
|
32
|
+
const lines = text.split("\n");
|
|
33
|
+
const out: string[] = [];
|
|
34
|
+
let i = 0;
|
|
35
|
+
while (i < lines.length) {
|
|
36
|
+
const line = lines[i] ?? "";
|
|
37
|
+
if (line.trim() === MANAGED_MARKER) {
|
|
38
|
+
i += 1;
|
|
39
|
+
while (i < lines.length && (lines[i] ?? "").trim() !== "") {
|
|
40
|
+
i += 1;
|
|
41
|
+
}
|
|
42
|
+
if (i < lines.length) {
|
|
43
|
+
i += 1;
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
out.push(line);
|
|
48
|
+
i += 1;
|
|
49
|
+
}
|
|
50
|
+
return out.join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function mergeMiseToml(existing?: string): string {
|
|
54
|
+
if (existing === undefined) {
|
|
55
|
+
return MISE_TASKS_TOML;
|
|
56
|
+
}
|
|
57
|
+
const userContent = stripManagedBlocks(existing).replace(/\s*$/, "");
|
|
58
|
+
if (userContent === "") {
|
|
59
|
+
return MISE_TASKS_TOML;
|
|
60
|
+
}
|
|
61
|
+
return `${userContent}\n\n${MISE_TASKS_TOML}`;
|
|
62
|
+
}
|