@defold-typescript/cli 0.5.5 → 0.7.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/README.md +2 -2
- package/dist/bin.js +1414 -395
- package/dist/bob-command.d.ts +31 -0
- package/dist/bob.d.ts +19 -0
- package/dist/boot-path.d.ts +5 -0
- package/dist/build-output.d.ts +7 -1
- package/dist/debug-launcher.d.ts +7 -1
- package/dist/directory-walls.d.ts +23 -0
- package/dist/dispatch.d.ts +6 -0
- package/dist/index.js +1268 -248
- package/dist/init.d.ts +1 -2
- package/dist/install-reminder.d.ts +1 -0
- package/dist/json-output.d.ts +26 -2
- package/dist/materialize.d.ts +0 -3
- package/dist/mise-scaffold.d.ts +1 -1
- package/dist/scan.d.ts +1 -0
- package/dist/script-kind.d.ts +2 -1
- package/dist/setup-debug.d.ts +40 -0
- package/dist/wall-interactive.d.ts +16 -0
- package/dist/wall.d.ts +4 -0
- package/dist/watch.d.ts +1 -0
- package/package.json +11 -3
- package/src/bob-command.ts +137 -0
- package/src/bob.ts +59 -0
- package/src/boot-path.ts +113 -0
- package/src/build-output.ts +67 -3
- package/src/build-session.ts +31 -11
- package/src/build.ts +6 -5
- package/src/debug-launcher.ts +36 -12
- package/src/directory-walls.ts +214 -0
- package/src/dispatch.ts +264 -18
- package/src/init.ts +83 -38
- package/src/install-reminder.ts +18 -0
- package/src/json-output.ts +52 -7
- package/src/materialize.ts +14 -12
- package/src/mise-scaffold.ts +16 -10
- package/src/scan.ts +7 -1
- package/src/script-kind.ts +31 -19
- package/src/setup-debug.ts +422 -0
- package/src/wall-interactive.ts +60 -0
- package/src/wall.ts +71 -0
- package/src/watch.ts +15 -3
package/dist/init.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { type ScriptKind } from "./script-kind";
|
|
2
1
|
export interface RunInitOptions {
|
|
3
2
|
readonly cwd: string;
|
|
4
3
|
readonly force?: boolean;
|
|
5
4
|
}
|
|
6
5
|
export interface RunInitResult {
|
|
7
6
|
readonly written: string[];
|
|
8
|
-
readonly scriptKind: ScriptKind | null;
|
|
9
7
|
}
|
|
10
8
|
export declare const SCAFFOLD_DEV_DEPS: Record<string, string>;
|
|
9
|
+
export declare function reconcileManagedList(existing: unknown, managed: readonly string[], canonical: readonly string[]): string[];
|
|
11
10
|
export declare function runNewProjectInit(cwd: string, force?: boolean): RunInitResult;
|
|
12
11
|
export declare function runInit(opts: RunInitOptions): RunInitResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installHint(env?: NodeJS.ProcessEnv): string;
|
package/dist/json-output.d.ts
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
|
-
export type CliCommand = "init" | "build";
|
|
1
|
+
export type CliCommand = "init" | "build" | "setup-debug" | "defold" | "wall";
|
|
2
2
|
export interface RenderResultInput {
|
|
3
3
|
readonly command: CliCommand;
|
|
4
4
|
readonly written?: readonly string[];
|
|
5
5
|
readonly error?: string;
|
|
6
6
|
readonly defoldVersion?: string;
|
|
7
7
|
readonly apiSurface?: string | null;
|
|
8
|
-
readonly scriptKind?: string | null;
|
|
9
8
|
readonly materializedSurface?: string | null;
|
|
9
|
+
readonly directoryWalls?: readonly {
|
|
10
|
+
readonly dir: string;
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
}[];
|
|
13
|
+
readonly eligible?: readonly {
|
|
14
|
+
readonly dir: string;
|
|
15
|
+
readonly kind: string;
|
|
16
|
+
}[];
|
|
17
|
+
readonly installCommand?: string;
|
|
18
|
+
readonly manualSteps?: readonly string[];
|
|
19
|
+
readonly actions?: Record<string, string>;
|
|
20
|
+
readonly addedTo?: string;
|
|
21
|
+
readonly removedFrom?: readonly string[];
|
|
22
|
+
readonly bootPath?: readonly string[];
|
|
23
|
+
readonly subcommand?: string;
|
|
24
|
+
readonly exitCode?: number;
|
|
10
25
|
}
|
|
11
26
|
export declare function renderResult(input: RenderResultInput): string;
|
|
27
|
+
export type WatchEventName = "build" | "rebuild";
|
|
28
|
+
export interface RenderWatchEventInput {
|
|
29
|
+
readonly event: WatchEventName;
|
|
30
|
+
readonly written?: readonly string[];
|
|
31
|
+
readonly changed?: readonly string[];
|
|
32
|
+
readonly removed?: readonly string[];
|
|
33
|
+
readonly error?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function renderWatchEvent(input: RenderWatchEventInput): string;
|
package/dist/materialize.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { type RegistryTarget } from "./api-registry";
|
|
2
2
|
import type { SelectedApiSurface } from "./api-surface";
|
|
3
|
-
import { type ScriptKind } from "./script-kind";
|
|
4
3
|
export interface MaterializeApiSurfaceOptions {
|
|
5
4
|
readonly cwd: string;
|
|
6
5
|
readonly surface: SelectedApiSurface;
|
|
7
6
|
readonly sourceGeneratedDir: string | null;
|
|
8
|
-
readonly scriptKind?: ScriptKind | null;
|
|
9
7
|
}
|
|
10
8
|
export interface MaterializeApiSurfaceResult {
|
|
11
9
|
readonly materializedDir: string | null;
|
|
@@ -23,7 +21,6 @@ export interface MaterializeRefDocSurfaceOptions {
|
|
|
23
21
|
readonly cwd: string;
|
|
24
22
|
readonly surfaceId: string;
|
|
25
23
|
readonly resolveOpts?: RefDocResolveOptions;
|
|
26
|
-
readonly scriptKind?: ScriptKind | null;
|
|
27
24
|
readonly registry?: readonly RegistryTarget[];
|
|
28
25
|
}
|
|
29
26
|
export declare function materializeRefDocSurface(opts: MaterializeRefDocSurfaceOptions): Promise<MaterializeApiSurfaceResult>;
|
package/dist/mise-scaffold.d.ts
CHANGED
|
@@ -1,2 +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
|
|
1
|
+
export declare const MISE_TASKS_TOML = "# managed by @defold-typescript\n[tasks.\"defold-typescript:build\"]\ndescription = \"Build the TypeScript sources with the defold-typescript CLI\"\nrun = \"bunx @defold-typescript/cli build\"\n\n# managed by @defold-typescript\n[tasks.\"defold-typescript:watch\"]\ndescription = \"Watch and rebuild the TypeScript sources with the defold-typescript CLI\"\nrun = \"bunx @defold-typescript/cli watch\"\n\n# managed by @defold-typescript\n[tasks.\"defold-typescript:setup-debug\"]\ndescription = \"Wire the lldebugger game.project dependency and entry-script bootstrap with the defold-typescript CLI\"\nrun = \"bunx @defold-typescript/cli setup-debug\"\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 --suppress-install-reminder\", \"bun install\"]\n";
|
|
2
2
|
export declare function mergeMiseToml(existing?: string): string;
|
package/dist/scan.d.ts
CHANGED
package/dist/script-kind.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const DEFAULT_TYPES_ENTRYPOINT = "@defold-typescript/types";
|
|
|
3
3
|
export declare function isSkipped(relPath: string): boolean;
|
|
4
4
|
export declare function isComponentPath(relPath: string): boolean;
|
|
5
5
|
export declare function detectScriptKinds(cwd: string): Set<ScriptKind>;
|
|
6
|
+
export declare function groupScriptKindsByDirectory(cwd: string): Map<string, Set<ScriptKind>>;
|
|
7
|
+
export declare function selectDirectoryWalls(cwd: string): Map<string, ScriptKind>;
|
|
6
8
|
export declare function selectScriptKind(kinds: Set<ScriptKind>): ScriptKind | null;
|
|
7
9
|
export declare function selectScriptKindEntrypoint(kinds: Set<ScriptKind>): string;
|
|
8
|
-
export declare function excludedModulesForKind(kind: ScriptKind | null): Set<string>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare const LLDEBUGGER_URL = "https://github.com/defold-typescript/toolchain/releases/download/lldebugger-v1/lldebugger.zip";
|
|
2
|
+
export declare const LEGACY_BOOTSTRAP_MARKER = "// lldebugger-bootstrap: debug entry, inert in release builds";
|
|
3
|
+
export declare const AMBIENT_DTS_REL = "src/lldebugger.debug.d.ts";
|
|
4
|
+
export declare const AMBIENT_DECLARATION = "/** @noResolution */\ndeclare module \"lldebugger.debug\" {\n export function start(): void;\n}\n";
|
|
5
|
+
export declare const BLOCK_BEGIN = "// defold-typescript:setup-debug BEGIN \u2014 managed block, do not edit";
|
|
6
|
+
export declare const BLOCK_END = "// defold-typescript:setup-debug END";
|
|
7
|
+
export declare function addLldebuggerDependency(gameProjectText: string): string;
|
|
8
|
+
export type FileAction = "injected" | "refreshed" | "unchanged" | "removed";
|
|
9
|
+
export interface UpsertResult {
|
|
10
|
+
readonly text: string;
|
|
11
|
+
readonly action: FileAction;
|
|
12
|
+
}
|
|
13
|
+
export declare function upsertManagedBlock(source: string): UpsertResult;
|
|
14
|
+
export declare function injectDebugBootstrap(source: string): string;
|
|
15
|
+
export interface StripResult {
|
|
16
|
+
readonly text: string;
|
|
17
|
+
readonly removed: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function stripManagedBlock(source: string): StripResult;
|
|
20
|
+
type ProjectScanner = (cwd: string, pattern: string) => string[];
|
|
21
|
+
export declare function findEntryScriptCandidates(cwd: string, scanner?: ProjectScanner): string[];
|
|
22
|
+
export interface SetupDebugOptions {
|
|
23
|
+
readonly cwd: string;
|
|
24
|
+
readonly script?: string;
|
|
25
|
+
readonly json?: boolean;
|
|
26
|
+
readonly chooseScript?: (candidates: string[]) => Promise<string>;
|
|
27
|
+
readonly scanFiles?: ProjectScanner;
|
|
28
|
+
}
|
|
29
|
+
export interface SetupDebugResult {
|
|
30
|
+
readonly ok: boolean;
|
|
31
|
+
readonly written: string[];
|
|
32
|
+
readonly actions: Record<string, FileAction>;
|
|
33
|
+
readonly manualSteps: readonly string[];
|
|
34
|
+
readonly error?: string;
|
|
35
|
+
readonly addedTo?: string;
|
|
36
|
+
readonly removedFrom?: string[];
|
|
37
|
+
readonly bootPath?: string[];
|
|
38
|
+
}
|
|
39
|
+
export declare function runSetupDebug(opts: SetupDebugOptions): Promise<SetupDebugResult>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DirectoryWall } from "./directory-walls";
|
|
2
|
+
export interface WallChoice {
|
|
3
|
+
readonly value: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly checked?: boolean;
|
|
6
|
+
readonly disabled?: string | false;
|
|
7
|
+
}
|
|
8
|
+
export type CheckboxPrompt = (opts: {
|
|
9
|
+
message: string;
|
|
10
|
+
choices: WallChoice[];
|
|
11
|
+
}) => Promise<string[]>;
|
|
12
|
+
export interface WallInteractiveDeps {
|
|
13
|
+
readonly checkbox?: CheckboxPrompt;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildWallChoices(cwd: string): WallChoice[];
|
|
16
|
+
export declare function runWallInteractive(cwd: string, deps?: WallInteractiveDeps): Promise<DirectoryWall[]>;
|
package/dist/wall.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type DirectoryWall } from "./directory-walls";
|
|
2
|
+
export declare function currentWalledDirs(cwd: string): string[];
|
|
3
|
+
export declare function eligibleWalls(cwd: string): DirectoryWall[];
|
|
4
|
+
export declare function applyWallSelection(cwd: string, desiredDirs: readonly string[]): DirectoryWall[];
|
package/dist/watch.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface RunWatchOptions {
|
|
|
14
14
|
readonly watcherFactory?: WatcherFactory;
|
|
15
15
|
readonly syncSurface?: () => void;
|
|
16
16
|
readonly componentWatcherFactory?: WatcherFactory;
|
|
17
|
+
readonly json?: boolean;
|
|
17
18
|
}
|
|
18
19
|
export interface RunWatchHandle {
|
|
19
20
|
readonly stop: () => void;
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "End-user CLI for scaffolding and building Defold projects written in TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/defold-typescript/toolchain.git",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/defold-typescript/toolchain#readme",
|
|
12
|
+
"bugs": "https://github.com/defold-typescript/toolchain/issues",
|
|
6
13
|
"type": "module",
|
|
7
14
|
"main": "./dist/index.js",
|
|
8
15
|
"types": "./dist/index.d.ts",
|
|
@@ -31,7 +38,8 @@
|
|
|
31
38
|
"test": "bun test"
|
|
32
39
|
},
|
|
33
40
|
"dependencies": {
|
|
34
|
-
"@defold-typescript/transpiler": "0.
|
|
35
|
-
"@defold-typescript/types": "0.
|
|
41
|
+
"@defold-typescript/transpiler": "0.7.0",
|
|
42
|
+
"@defold-typescript/types": "0.7.0",
|
|
43
|
+
"@inquirer/prompts": "^7.0.0"
|
|
36
44
|
}
|
|
37
45
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { delimiter, dirname, join } from "node:path";
|
|
3
|
+
import { bobCacheDir, bobDownloadUrl, resolveBobJar, resolveJava } from "./bob";
|
|
4
|
+
import { ENGINE_INFO_URL } from "./debug-launcher";
|
|
5
|
+
|
|
6
|
+
export const DEFOLD_SUBCOMMANDS = ["resolve", "build", "bundle"] as const;
|
|
7
|
+
export type DefoldSubcommand = (typeof DEFOLD_SUBCOMMANDS)[number];
|
|
8
|
+
|
|
9
|
+
export function isDefoldSubcommand(value: string | undefined): value is DefoldSubcommand {
|
|
10
|
+
return value !== undefined && (DEFOLD_SUBCOMMANDS as readonly string[]).includes(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// bob takes options before the trailing command verb. `build` uses the debug
|
|
14
|
+
// variant so output lands in `build/default`, matching the engine the debug
|
|
15
|
+
// launcher runs.
|
|
16
|
+
export function composeBobArgv(opts: {
|
|
17
|
+
java: string;
|
|
18
|
+
jar: string;
|
|
19
|
+
subcommand: string;
|
|
20
|
+
buildServer?: string;
|
|
21
|
+
}): string[] {
|
|
22
|
+
const base = [opts.java, "-jar", opts.jar];
|
|
23
|
+
const server = opts.buildServer ? ["--build-server", opts.buildServer] : [];
|
|
24
|
+
switch (opts.subcommand) {
|
|
25
|
+
case "resolve":
|
|
26
|
+
return [...base, ...server, "resolve"];
|
|
27
|
+
case "build":
|
|
28
|
+
return [...base, "--variant", "debug", ...server, "build"];
|
|
29
|
+
case "bundle":
|
|
30
|
+
return [...base, ...server, "bundle"];
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(
|
|
33
|
+
`defold-typescript: unknown defold subcommand "${opts.subcommand}"; expected resolve|build|bundle.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DefoldIo {
|
|
39
|
+
readonly cacheDir: string;
|
|
40
|
+
readonly fetchSha: () => Promise<string>;
|
|
41
|
+
readonly probe: (candidate: string) => boolean;
|
|
42
|
+
readonly javaProbe: (cmd: string) => boolean;
|
|
43
|
+
readonly spawn: (argv: string[], cwd: string) => Promise<number>;
|
|
44
|
+
readonly download: (url: string, dest: string) => Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DefoldCommandResult {
|
|
48
|
+
readonly ok: boolean;
|
|
49
|
+
readonly subcommand: string;
|
|
50
|
+
readonly exitCode: number;
|
|
51
|
+
readonly argv: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function runDefoldCommand(opts: {
|
|
55
|
+
cwd: string;
|
|
56
|
+
subcommand: string;
|
|
57
|
+
java?: string;
|
|
58
|
+
buildServer?: string;
|
|
59
|
+
io: DefoldIo;
|
|
60
|
+
}): Promise<DefoldCommandResult> {
|
|
61
|
+
const { io } = opts;
|
|
62
|
+
const sha = await io.fetchSha();
|
|
63
|
+
const { jarPath, cached } = resolveBobJar({
|
|
64
|
+
sha1: sha,
|
|
65
|
+
cacheDir: io.cacheDir,
|
|
66
|
+
probe: io.probe,
|
|
67
|
+
});
|
|
68
|
+
if (!cached) {
|
|
69
|
+
await io.download(bobDownloadUrl(sha), jarPath);
|
|
70
|
+
}
|
|
71
|
+
const java = resolveJava({
|
|
72
|
+
...(opts.java !== undefined ? { override: opts.java } : {}),
|
|
73
|
+
probe: io.javaProbe,
|
|
74
|
+
});
|
|
75
|
+
const argv = composeBobArgv({
|
|
76
|
+
java,
|
|
77
|
+
jar: jarPath,
|
|
78
|
+
subcommand: opts.subcommand,
|
|
79
|
+
...(opts.buildServer !== undefined ? { buildServer: opts.buildServer } : {}),
|
|
80
|
+
});
|
|
81
|
+
const exitCode = await io.spawn(argv, opts.cwd);
|
|
82
|
+
return { ok: exitCode === 0, subcommand: opts.subcommand, exitCode, argv };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function fetchStableSha(): Promise<string> {
|
|
86
|
+
const res = await fetch(ENGINE_INFO_URL);
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`defold-typescript: could not resolve the stable Defold sha (${ENGINE_INFO_URL} -> ${res.status} ${res.statusText}).`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const info = (await res.json()) as { sha1?: string };
|
|
93
|
+
if (!info.sha1) {
|
|
94
|
+
throw new Error(`defold-typescript: ${ENGINE_INFO_URL} returned no sha1.`);
|
|
95
|
+
}
|
|
96
|
+
return info.sha1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function javaOnPath(cmd: string, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
100
|
+
const pathVar = env.PATH ?? env.Path ?? "";
|
|
101
|
+
const exts = process.platform === "win32" ? [".exe", ".bat", ".cmd", ""] : [""];
|
|
102
|
+
for (const dir of pathVar.split(delimiter).filter(Boolean)) {
|
|
103
|
+
for (const ext of exts) {
|
|
104
|
+
if (existsSync(join(dir, cmd + ext))) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function spawnInherit(argv: string[], cwd: string): Promise<number> {
|
|
113
|
+
const proc = Bun.spawn(argv, { cwd, stdio: ["inherit", "inherit", "inherit"] });
|
|
114
|
+
return proc.exited;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function downloadTo(url: string, dest: string): Promise<void> {
|
|
118
|
+
const res = await fetch(url);
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`defold-typescript: bob.jar download failed (${url} -> ${res.status} ${res.statusText}).`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
125
|
+
await Bun.write(dest, res);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function defaultDefoldIo(): DefoldIo {
|
|
129
|
+
return {
|
|
130
|
+
cacheDir: bobCacheDir(),
|
|
131
|
+
fetchSha: fetchStableSha,
|
|
132
|
+
probe: existsSync,
|
|
133
|
+
javaProbe: (cmd) => javaOnPath(cmd),
|
|
134
|
+
spawn: spawnInherit,
|
|
135
|
+
download: downloadTo,
|
|
136
|
+
};
|
|
137
|
+
}
|
package/src/bob.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ENGINE_ARCHIVE_BASE } from "./debug-launcher";
|
|
4
|
+
|
|
5
|
+
// bob is Defold's headless build tool. It downloads version-matched from the
|
|
6
|
+
// same archive the engine launcher uses, keyed by the stable-channel sha that
|
|
7
|
+
// `debug-launcher.ts` already resolves from `ENGINE_INFO_URL`.
|
|
8
|
+
export function bobDownloadUrl(sha1: string): string {
|
|
9
|
+
return `${ENGINE_ARCHIVE_BASE}/${sha1}/bob/bob.jar`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Mirrors `refDocCacheDir`: a `DEFOLD_TYPESCRIPT_CACHE` override wins, else the
|
|
13
|
+
// XDG cache home, else `~/.cache`. The jar lives outside the repo, so it is
|
|
14
|
+
// never committed.
|
|
15
|
+
export function bobCacheDir(
|
|
16
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
17
|
+
home: () => string = homedir,
|
|
18
|
+
): string {
|
|
19
|
+
if (env.DEFOLD_TYPESCRIPT_CACHE) {
|
|
20
|
+
return path.join(env.DEFOLD_TYPESCRIPT_CACHE, "bob");
|
|
21
|
+
}
|
|
22
|
+
return path.join(env.XDG_CACHE_HOME ?? path.join(home(), ".cache"), "defold-typescript", "bob");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function bobCachePath(opts: { sha1: string; cacheDir: string }): string {
|
|
26
|
+
return path.join(opts.cacheDir, opts.sha1, "bob.jar");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ResolvedBobJar {
|
|
30
|
+
readonly jarPath: string;
|
|
31
|
+
readonly cached: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Returns the sha-keyed cache path and whether it already exists. `cached`
|
|
35
|
+
// false means the caller must download `bobDownloadUrl(sha1)` to `jarPath` —
|
|
36
|
+
// this module performs no network or filesystem writes.
|
|
37
|
+
export function resolveBobJar(opts: {
|
|
38
|
+
sha1: string;
|
|
39
|
+
cacheDir: string;
|
|
40
|
+
probe: (candidate: string) => boolean;
|
|
41
|
+
}): ResolvedBobJar {
|
|
42
|
+
const jarPath = bobCachePath({ sha1: opts.sha1, cacheDir: opts.cacheDir });
|
|
43
|
+
return { jarPath, cached: opts.probe(jarPath) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// override -> `java` on PATH -> clear error. `probe` is injectable so callers
|
|
47
|
+
// (and tests) decide what "found on PATH" means without spawning.
|
|
48
|
+
export function resolveJava(opts: { override?: string; probe: (cmd: string) => boolean }): string {
|
|
49
|
+
if (opts.override) {
|
|
50
|
+
return opts.override;
|
|
51
|
+
}
|
|
52
|
+
if (opts.probe("java")) {
|
|
53
|
+
return "java";
|
|
54
|
+
}
|
|
55
|
+
throw new Error(
|
|
56
|
+
'defold-typescript: no Java runtime found. Install a JDK and ensure "java" is on PATH, ' +
|
|
57
|
+
"or pass --java <path> (or set DEFOLD_JAVA). bob.jar requires a JVM to run.",
|
|
58
|
+
);
|
|
59
|
+
}
|
package/src/boot-path.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface BootPathCandidate {
|
|
5
|
+
readonly candidate: string;
|
|
6
|
+
readonly trace: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// `game.project` is a flat INI the engine writes itself; mirror the no-parser
|
|
10
|
+
// line idiom `addLldebuggerDependency` uses rather than adding a TOML dep.
|
|
11
|
+
const MAIN_COLLECTION_RE = /^main_collection\s*=\s*(.+)$/m;
|
|
12
|
+
|
|
13
|
+
// Both `.collection` files store component/collection references two ways: as a
|
|
14
|
+
// bare `component: "/path"` line and, inside an embedded `data:` string, as an
|
|
15
|
+
// escaped `component: \"/path\"`. The optional `\\?` before each quote matches
|
|
16
|
+
// both forms with one expression.
|
|
17
|
+
const COMPONENT_RE = /component:\s*\\?"([^"\\]+)\\?"/;
|
|
18
|
+
const COLLECTION_RE = /collection:\s*\\?"([^"\\]+\.collectionc?)\\?"/;
|
|
19
|
+
|
|
20
|
+
// A top-level (unescaped) instance id; the escaped `\"id\"` lines inside a
|
|
21
|
+
// `data:` string start with a quote and never match `^\s*id:`.
|
|
22
|
+
const TOP_LEVEL_ID_RE = /^\s*id:\s*"([^"\\]+)"\s*$/;
|
|
23
|
+
|
|
24
|
+
const TS_SCRIPT_SUFFIX = ".ts.script";
|
|
25
|
+
|
|
26
|
+
function projectPathToAbs(cwd: string, projectPath: string): string {
|
|
27
|
+
return path.join(cwd, projectPath.replace(/^\//, ""));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function relPosix(cwd: string, abs: string): string {
|
|
31
|
+
return path.relative(cwd, abs).split(path.sep).join("/");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Walk the Defold boot path from `game.project`'s `main_collection`, collecting
|
|
35
|
+
// every reachable `.ts.script` component (mapped back to its source `.ts`) with
|
|
36
|
+
// the collection/instance chain that led to it. Never throws: a missing file or
|
|
37
|
+
// absent `[bootstrap]` yields an empty result; a `collection:` ref cycle is cut
|
|
38
|
+
// by the visited set.
|
|
39
|
+
export function resolveBootPathScripts(cwd: string): BootPathCandidate[] {
|
|
40
|
+
const gameProjectPath = path.join(cwd, "game.project");
|
|
41
|
+
if (!existsSync(gameProjectPath)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let gameProject: string;
|
|
46
|
+
try {
|
|
47
|
+
gameProject = readFileSync(gameProjectPath, "utf8");
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const mainMatch = gameProject.match(MAIN_COLLECTION_RE);
|
|
53
|
+
if (mainMatch?.[1] === undefined) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const mainCollection = mainMatch[1].trim().replace(/\.collectionc$/, ".collection");
|
|
57
|
+
|
|
58
|
+
const candidates: BootPathCandidate[] = [];
|
|
59
|
+
const visited = new Set<string>();
|
|
60
|
+
|
|
61
|
+
const walk = (collectionAbs: string, tracePrefix: string[]): void => {
|
|
62
|
+
if (visited.has(collectionAbs)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
visited.add(collectionAbs);
|
|
66
|
+
if (!existsSync(collectionAbs)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let content: string;
|
|
71
|
+
try {
|
|
72
|
+
content = readFileSync(collectionAbs, "utf8");
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const trace = [...tracePrefix, relPosix(cwd, collectionAbs)];
|
|
78
|
+
const children: string[] = [];
|
|
79
|
+
let currentId: string | null = null;
|
|
80
|
+
|
|
81
|
+
for (const line of content.split("\n")) {
|
|
82
|
+
const idMatch = line.match(TOP_LEVEL_ID_RE);
|
|
83
|
+
if (idMatch?.[1] !== undefined) {
|
|
84
|
+
currentId = idMatch[1];
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const componentMatch = line.match(COMPONENT_RE);
|
|
89
|
+
const component = componentMatch?.[1];
|
|
90
|
+
if (component?.endsWith(TS_SCRIPT_SUFFIX)) {
|
|
91
|
+
const candidate = component.replace(/^\//, "").replace(/\.script$/, "");
|
|
92
|
+
candidates.push({
|
|
93
|
+
candidate,
|
|
94
|
+
trace: [...trace, currentId ?? "?", component],
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const collectionMatch = line.match(COLLECTION_RE);
|
|
100
|
+
if (collectionMatch?.[1] !== undefined) {
|
|
101
|
+
const ref = collectionMatch[1].replace(/\.collectionc$/, ".collection");
|
|
102
|
+
children.push(projectPathToAbs(cwd, ref));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const child of children) {
|
|
107
|
+
walk(child, trace);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
walk(projectPathToAbs(cwd, mainCollection), ["game.project"]);
|
|
112
|
+
return candidates;
|
|
113
|
+
}
|
package/src/build-output.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { TranspileDiagnostic } from "@defold-typescript/transpiler";
|
|
4
|
+
import type { ScriptKind } from "./script-kind";
|
|
4
5
|
|
|
5
6
|
export interface BuildConfig {
|
|
6
7
|
readonly outDir: string | undefined;
|
|
@@ -54,10 +55,42 @@ function stripIncludeBase(pattern: string): string {
|
|
|
54
55
|
return lastSlash === -1 ? "" : upToWildcard.slice(0, lastSlash + 1);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
const SCRIPT_SUFFIX_BY_KIND: Record<ScriptKind, string> = {
|
|
59
|
+
script: ".ts.script",
|
|
60
|
+
"gui-script": ".ts.gui_script",
|
|
61
|
+
"render-script": ".ts.render_script",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type SourceOutputKind = ScriptKind | "module";
|
|
65
|
+
|
|
66
|
+
// A `.ts` source carries no Defold component kind of its own; the lifecycle
|
|
67
|
+
// factory it calls is the signal. The call is matched (trailing `(`) so a bare
|
|
68
|
+
// import of all three factories does not decide the kind. Precedence
|
|
69
|
+
// render > gui > script; a source using no factory emits as a Lua module.
|
|
70
|
+
const FACTORY_KINDS: ReadonlyArray<readonly [ScriptKind, RegExp]> = [
|
|
71
|
+
["render-script", /\bdefineRenderScript\s*\(/],
|
|
72
|
+
["gui-script", /\bdefineGuiScript\s*\(/],
|
|
73
|
+
["script", /\bdefineScript\s*\(/],
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
export function detectSourceOutputKind(source: string): SourceOutputKind {
|
|
77
|
+
for (const [kind, re] of FACTORY_KINDS) {
|
|
78
|
+
if (re.test(source)) {
|
|
79
|
+
return kind;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return "module";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function detectSourceScriptKind(source: string): ScriptKind {
|
|
86
|
+
const kind = detectSourceOutputKind(source);
|
|
87
|
+
return kind === "module" ? "script" : kind;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function relUnderOutDir(rel: string, config: BuildConfig): string {
|
|
58
91
|
const { outDir, include } = config;
|
|
59
92
|
if (outDir === undefined || outDir === "" || outDir === ".") {
|
|
60
|
-
return rel
|
|
93
|
+
return rel;
|
|
61
94
|
}
|
|
62
95
|
const includeBase =
|
|
63
96
|
include
|
|
@@ -65,7 +98,33 @@ export function computeScriptRel(rel: string, config: BuildConfig): string {
|
|
|
65
98
|
.filter((base) => rel.startsWith(base))
|
|
66
99
|
.sort((a, b) => b.length - a.length)[0] ?? "";
|
|
67
100
|
const relUnderBase = rel.slice(includeBase.length);
|
|
68
|
-
return path.posix.join(outDir, relUnderBase
|
|
101
|
+
return path.posix.join(outDir, relUnderBase);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function computeOutputRel(rel: string, config: BuildConfig, kind: SourceOutputKind): string {
|
|
105
|
+
const baseRel = relUnderOutDir(rel, config);
|
|
106
|
+
if (kind === "module") {
|
|
107
|
+
return baseRel.replace(/\.ts$/, ".lua");
|
|
108
|
+
}
|
|
109
|
+
return baseRel.replace(/\.ts$/, SCRIPT_SUFFIX_BY_KIND[kind]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function computeScriptRel(
|
|
113
|
+
rel: string,
|
|
114
|
+
config: BuildConfig,
|
|
115
|
+
kind: ScriptKind = "script",
|
|
116
|
+
): string {
|
|
117
|
+
return computeOutputRel(rel, config, kind);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function outputRelsForSource(rel: string, config: BuildConfig): string[] {
|
|
121
|
+
const outputs = [
|
|
122
|
+
computeOutputRel(rel, config, "module"),
|
|
123
|
+
computeOutputRel(rel, config, "script"),
|
|
124
|
+
computeOutputRel(rel, config, "gui-script"),
|
|
125
|
+
computeOutputRel(rel, config, "render-script"),
|
|
126
|
+
];
|
|
127
|
+
return outputs.flatMap((output) => [output, `${output}.map`]);
|
|
69
128
|
}
|
|
70
129
|
|
|
71
130
|
export function collectFailures(
|
|
@@ -73,6 +132,11 @@ export function collectFailures(
|
|
|
73
132
|
): Map<string, string[]> {
|
|
74
133
|
const failures = new Map<string, string[]>();
|
|
75
134
|
for (const diag of diagnostics) {
|
|
135
|
+
// Advisory diagnostics (e.g. the deprecated direct `go.property` call) warn
|
|
136
|
+
// but never fail the build — the call still registers at runtime.
|
|
137
|
+
if (diag.category === "warning") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
76
140
|
const bucket = diag.file ?? PROJECT_BUCKET;
|
|
77
141
|
const list = failures.get(bucket);
|
|
78
142
|
if (list) {
|