@defold-typescript/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-registry.d.ts +14 -0
- package/dist/api-surface.d.ts +6 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +1023 -0
- package/dist/build-output.d.ts +11 -0
- package/dist/build-session.d.ts +11 -0
- package/dist/build.d.ts +7 -0
- package/dist/defold-version.d.ts +11 -0
- package/dist/dispatch.d.ts +17 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1017 -0
- package/dist/init.d.ts +11 -0
- package/dist/json-output.d.ts +11 -0
- package/dist/materialize.d.ts +29 -0
- package/dist/ref-doc-test-fixture.d.ts +18 -0
- package/dist/scan.d.ts +1 -0
- package/dist/script-kind.d.ts +8 -0
- package/dist/watch.d.ts +24 -0
- package/package.json +36 -0
- package/src/api-registry.ts +65 -0
- package/src/api-surface.ts +18 -0
- package/src/bin.ts +8 -0
- package/src/build-output.ts +106 -0
- package/src/build-session.ts +98 -0
- package/src/build.ts +62 -0
- package/src/defold-version.ts +30 -0
- package/src/dispatch.ts +273 -0
- package/src/index.ts +17 -0
- package/src/init.ts +237 -0
- package/src/json-output.ts +29 -0
- package/src/materialize.ts +224 -0
- package/src/ref-doc-test-fixture.ts +92 -0
- package/src/scan.ts +10 -0
- package/src/script-kind.ts +68 -0
- package/src/watch.ts +185 -0
package/src/dispatch.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { RegistryTarget } from "./api-registry";
|
|
4
|
+
import { CURRENT_STABLE_SURFACE_ID, selectApiSurface } from "./api-surface";
|
|
5
|
+
import { runBuild } from "./build";
|
|
6
|
+
import { readDefoldVersionPin, resolveDefoldVersion } from "./defold-version";
|
|
7
|
+
import { runInit } from "./init";
|
|
8
|
+
import { renderResult } from "./json-output";
|
|
9
|
+
import {
|
|
10
|
+
ensureMaterializedReference,
|
|
11
|
+
materializeApiSurface,
|
|
12
|
+
materializeRefDocSurface,
|
|
13
|
+
type RefDocResolveOptions,
|
|
14
|
+
resolveCurrentSurfaceGeneratedDir,
|
|
15
|
+
} from "./materialize";
|
|
16
|
+
import { detectScriptKinds, selectScriptKind } from "./script-kind";
|
|
17
|
+
import {
|
|
18
|
+
type RunWatchHandle,
|
|
19
|
+
type RunWatchOptions,
|
|
20
|
+
recursiveWatcherFactory,
|
|
21
|
+
runWatch,
|
|
22
|
+
type WatcherFactory,
|
|
23
|
+
} from "./watch";
|
|
24
|
+
|
|
25
|
+
export interface DispatchIo {
|
|
26
|
+
readonly stdout: NodeJS.WritableStream;
|
|
27
|
+
readonly stderr: NodeJS.WritableStream;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DispatchInternals {
|
|
31
|
+
readonly watcherFactory?: WatcherFactory;
|
|
32
|
+
readonly componentWatcherFactory?: WatcherFactory;
|
|
33
|
+
readonly debounceMs?: number;
|
|
34
|
+
readonly onWatchStart?: (handle: RunWatchHandle) => void;
|
|
35
|
+
readonly sourceGeneratedDir?: string;
|
|
36
|
+
readonly resolveOpts?: RefDocResolveOptions;
|
|
37
|
+
readonly refDocRegistry?: readonly RegistryTarget[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const USAGE = "Usage: defold-typescript <init|build|watch> [path]\n";
|
|
41
|
+
|
|
42
|
+
function parseDefoldVersionFlag(argv: string[]): { flag: string | undefined; rest: string[] } {
|
|
43
|
+
let flag: string | undefined;
|
|
44
|
+
const rest: string[] = [];
|
|
45
|
+
for (let i = 0; i < argv.length; i++) {
|
|
46
|
+
const arg = argv[i];
|
|
47
|
+
if (arg === "--defold-version") {
|
|
48
|
+
flag = argv[i + 1];
|
|
49
|
+
i++;
|
|
50
|
+
} else if (arg?.startsWith("--defold-version=")) {
|
|
51
|
+
flag = arg.slice("--defold-version=".length);
|
|
52
|
+
} else if (arg !== undefined) {
|
|
53
|
+
rest.push(arg);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { flag, rest };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readProjectPin(cwd: string): string | undefined {
|
|
60
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
61
|
+
if (!existsSync(pkgPath)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return readDefoldVersionPin(JSON.parse(readFileSync(pkgPath, "utf8")));
|
|
66
|
+
} catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function dispatch(
|
|
72
|
+
argv: string[],
|
|
73
|
+
io: DispatchIo,
|
|
74
|
+
internals?: DispatchInternals,
|
|
75
|
+
): number | Promise<number> {
|
|
76
|
+
const json = argv.includes("--json");
|
|
77
|
+
const force = argv.includes("--force");
|
|
78
|
+
const { flag: defoldVersionFlag, rest: nonFlagArgs } = parseDefoldVersionFlag(argv);
|
|
79
|
+
const positional = nonFlagArgs.filter((a) => a !== "--json" && a !== "--force");
|
|
80
|
+
const [command, ...rest] = positional;
|
|
81
|
+
const cwd = rest[0] ? path.resolve(rest[0]) : process.cwd();
|
|
82
|
+
|
|
83
|
+
const pin = readProjectPin(cwd);
|
|
84
|
+
const resolvedVersion = resolveDefoldVersion({
|
|
85
|
+
...(defoldVersionFlag !== undefined ? { flag: defoldVersionFlag } : {}),
|
|
86
|
+
...(pin !== undefined ? { pin } : {}),
|
|
87
|
+
}).version;
|
|
88
|
+
const surface = selectApiSurface(resolvedVersion);
|
|
89
|
+
const apiSurface = surface.surfaceId;
|
|
90
|
+
|
|
91
|
+
if (command === "init") {
|
|
92
|
+
try {
|
|
93
|
+
const { written, scriptKind } = runInit({ cwd, force });
|
|
94
|
+
if (json) {
|
|
95
|
+
io.stdout.write(
|
|
96
|
+
renderResult({
|
|
97
|
+
command: "init",
|
|
98
|
+
written,
|
|
99
|
+
defoldVersion: resolvedVersion,
|
|
100
|
+
apiSurface,
|
|
101
|
+
scriptKind,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
io.stdout.write(
|
|
106
|
+
`defold-typescript init: wrote ${written.length} files: ${written.join(", ")}\n`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return 0;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
112
|
+
if (json) {
|
|
113
|
+
io.stdout.write(renderResult({ command: "init", error: message }));
|
|
114
|
+
} else {
|
|
115
|
+
io.stderr.write(`${message}\n`);
|
|
116
|
+
}
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (command === "build") {
|
|
122
|
+
const scriptKind = selectScriptKind(detectScriptKinds(cwd));
|
|
123
|
+
const reportBuild = (written: readonly string[], materializedDir: string | null): number => {
|
|
124
|
+
ensureMaterializedReference(cwd, materializedDir);
|
|
125
|
+
if (json) {
|
|
126
|
+
io.stdout.write(
|
|
127
|
+
renderResult({
|
|
128
|
+
command: "build",
|
|
129
|
+
written,
|
|
130
|
+
defoldVersion: resolvedVersion,
|
|
131
|
+
apiSurface,
|
|
132
|
+
scriptKind,
|
|
133
|
+
materializedSurface: materializedDir,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
io.stdout.write(
|
|
138
|
+
`defold-typescript build: wrote ${written.length} files: ${written.join(", ")}\n`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return 0;
|
|
142
|
+
};
|
|
143
|
+
const reportError = (err: unknown): number => {
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
if (json) {
|
|
146
|
+
io.stdout.write(renderResult({ command: "build", error: message }));
|
|
147
|
+
} else {
|
|
148
|
+
io.stderr.write(`${message}\n`);
|
|
149
|
+
}
|
|
150
|
+
return 1;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const isRefDocSurface =
|
|
154
|
+
surface.available &&
|
|
155
|
+
surface.surfaceId !== null &&
|
|
156
|
+
surface.surfaceId !== CURRENT_STABLE_SURFACE_ID;
|
|
157
|
+
|
|
158
|
+
if (isRefDocSurface) {
|
|
159
|
+
const surfaceId = surface.surfaceId as string;
|
|
160
|
+
return (async (): Promise<number> => {
|
|
161
|
+
try {
|
|
162
|
+
const { written } = runBuild({ cwd });
|
|
163
|
+
const { materializedDir } = await materializeRefDocSurface({
|
|
164
|
+
cwd,
|
|
165
|
+
surfaceId,
|
|
166
|
+
scriptKind,
|
|
167
|
+
...(internals?.resolveOpts ? { resolveOpts: internals.resolveOpts } : {}),
|
|
168
|
+
...(internals?.refDocRegistry ? { registry: internals.refDocRegistry } : {}),
|
|
169
|
+
});
|
|
170
|
+
if (!json && materializedDir === null) {
|
|
171
|
+
io.stderr.write(
|
|
172
|
+
`defold-typescript build: could not materialize ${surfaceId}; the default surface stays active\n`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return reportBuild(written, materializedDir);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return reportError(err);
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const { written } = runBuild({ cwd });
|
|
184
|
+
const sourceGeneratedDir =
|
|
185
|
+
internals?.sourceGeneratedDir ?? resolveCurrentSurfaceGeneratedDir();
|
|
186
|
+
const { materializedDir } = materializeApiSurface({
|
|
187
|
+
cwd,
|
|
188
|
+
surface,
|
|
189
|
+
sourceGeneratedDir,
|
|
190
|
+
scriptKind,
|
|
191
|
+
});
|
|
192
|
+
return reportBuild(written, materializedDir);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
return reportError(err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (command === "watch") {
|
|
199
|
+
const isRefDocSurface =
|
|
200
|
+
surface.available &&
|
|
201
|
+
surface.surfaceId !== null &&
|
|
202
|
+
surface.surfaceId !== CURRENT_STABLE_SURFACE_ID;
|
|
203
|
+
|
|
204
|
+
let syncSurface: (() => void) | undefined;
|
|
205
|
+
let componentWatcherFactory: WatcherFactory | undefined;
|
|
206
|
+
if (!isRefDocSurface) {
|
|
207
|
+
const sourceGeneratedDir =
|
|
208
|
+
internals?.sourceGeneratedDir ?? resolveCurrentSurfaceGeneratedDir();
|
|
209
|
+
syncSurface = (): void => {
|
|
210
|
+
const scriptKind = selectScriptKind(detectScriptKinds(cwd));
|
|
211
|
+
const { materializedDir } = materializeApiSurface({
|
|
212
|
+
cwd,
|
|
213
|
+
surface,
|
|
214
|
+
sourceGeneratedDir,
|
|
215
|
+
scriptKind,
|
|
216
|
+
});
|
|
217
|
+
ensureMaterializedReference(cwd, materializedDir);
|
|
218
|
+
};
|
|
219
|
+
componentWatcherFactory = internals
|
|
220
|
+
? internals.componentWatcherFactory
|
|
221
|
+
: recursiveWatcherFactory;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const launchWatch = (): Promise<number> => {
|
|
225
|
+
const watchOpts: RunWatchOptions = {
|
|
226
|
+
cwd,
|
|
227
|
+
stdout: io.stdout,
|
|
228
|
+
stderr: io.stderr,
|
|
229
|
+
...(internals?.watcherFactory ? { watcherFactory: internals.watcherFactory } : {}),
|
|
230
|
+
...(internals?.debounceMs !== undefined ? { debounceMs: internals.debounceMs } : {}),
|
|
231
|
+
...(syncSurface ? { syncSurface } : {}),
|
|
232
|
+
...(componentWatcherFactory ? { componentWatcherFactory } : {}),
|
|
233
|
+
};
|
|
234
|
+
const handle = runWatch(watchOpts);
|
|
235
|
+
if (internals) {
|
|
236
|
+
internals.onWatchStart?.(handle);
|
|
237
|
+
} else {
|
|
238
|
+
process.once("SIGINT", () => handle.stop());
|
|
239
|
+
}
|
|
240
|
+
return handle.done.catch((err: unknown) => {
|
|
241
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
242
|
+
io.stderr.write(`${message}\n`);
|
|
243
|
+
return 1;
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// A pinned ref-doc surface is generated on the fly, so it has no
|
|
248
|
+
// `syncSurface`; narrow it once at startup the same way `build` does, then
|
|
249
|
+
// start the watcher. A single detected kind drops the forbidden restricted
|
|
250
|
+
// namespaces; mixed/none keeps the full surface. Live re-narrowing of
|
|
251
|
+
// ref-doc surfaces stays deferred.
|
|
252
|
+
if (isRefDocSurface) {
|
|
253
|
+
const surfaceId = surface.surfaceId as string;
|
|
254
|
+
const scriptKind = selectScriptKind(detectScriptKinds(cwd));
|
|
255
|
+
return (async (): Promise<number> => {
|
|
256
|
+
const { materializedDir } = await materializeRefDocSurface({
|
|
257
|
+
cwd,
|
|
258
|
+
surfaceId,
|
|
259
|
+
scriptKind,
|
|
260
|
+
...(internals?.resolveOpts ? { resolveOpts: internals.resolveOpts } : {}),
|
|
261
|
+
...(internals?.refDocRegistry ? { registry: internals.refDocRegistry } : {}),
|
|
262
|
+
});
|
|
263
|
+
ensureMaterializedReference(cwd, materializedDir);
|
|
264
|
+
return launchWatch();
|
|
265
|
+
})();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return launchWatch();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
io.stderr.write(USAGE);
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
BuildResult,
|
|
3
|
+
BuildSession,
|
|
4
|
+
CreateBuildSessionOptions,
|
|
5
|
+
} from "./build-session";
|
|
6
|
+
export { createBuildSession } from "./build-session";
|
|
7
|
+
export type { DispatchInternals, DispatchIo } from "./dispatch";
|
|
8
|
+
export { dispatch } from "./dispatch";
|
|
9
|
+
export type { RunInitOptions, RunInitResult } from "./init";
|
|
10
|
+
export { runInit } from "./init";
|
|
11
|
+
export type {
|
|
12
|
+
RunWatchHandle,
|
|
13
|
+
RunWatchOptions,
|
|
14
|
+
WatchEvent,
|
|
15
|
+
WatcherFactory,
|
|
16
|
+
} from "./watch";
|
|
17
|
+
export { runWatch } from "./watch";
|
package/src/init.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { CURRENT_STABLE_DEFOLD_VERSION } from "./defold-version";
|
|
4
|
+
import {
|
|
5
|
+
detectScriptKinds,
|
|
6
|
+
type ScriptKind,
|
|
7
|
+
selectScriptKind,
|
|
8
|
+
selectScriptKindEntrypoint,
|
|
9
|
+
} from "./script-kind";
|
|
10
|
+
|
|
11
|
+
export interface RunInitOptions {
|
|
12
|
+
readonly cwd: string;
|
|
13
|
+
readonly force?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RunInitResult {
|
|
17
|
+
readonly written: string[];
|
|
18
|
+
readonly scriptKind: ScriptKind | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CONFLICTING_TS_CONFIGS = [
|
|
22
|
+
"tsconfig.json",
|
|
23
|
+
"defold-typescript.config.ts",
|
|
24
|
+
"defold-typescript.config.mts",
|
|
25
|
+
"defold-typescript.config.js",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const TSCONFIG_COMPILER_OPTIONS = {
|
|
29
|
+
target: "ES2022",
|
|
30
|
+
module: "ESNext",
|
|
31
|
+
moduleResolution: "Bundler",
|
|
32
|
+
lib: ["ES2022"],
|
|
33
|
+
strict: true,
|
|
34
|
+
skipLibCheck: true,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const GITIGNORE_LINES = ["src/**/*.lua", "src/**/*.lua.map"];
|
|
38
|
+
|
|
39
|
+
const BIOME_JSON_CONTENT = {
|
|
40
|
+
$schema: "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
41
|
+
files: {
|
|
42
|
+
includes: ["src/**/*.ts", "!**/dist", "!**/node_modules", "!**/*.lua"],
|
|
43
|
+
},
|
|
44
|
+
formatter: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
indentStyle: "space",
|
|
47
|
+
indentWidth: 2,
|
|
48
|
+
lineWidth: 100,
|
|
49
|
+
},
|
|
50
|
+
linter: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
rules: {
|
|
53
|
+
recommended: true,
|
|
54
|
+
style: {
|
|
55
|
+
useImportType: "error",
|
|
56
|
+
useNodejsImportProtocol: "error",
|
|
57
|
+
},
|
|
58
|
+
correctness: {
|
|
59
|
+
noUnusedImports: "error",
|
|
60
|
+
noUnusedVariables: "warn",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
javascript: {
|
|
65
|
+
formatter: {
|
|
66
|
+
quoteStyle: "double",
|
|
67
|
+
semicolons: "always",
|
|
68
|
+
trailingCommas: "all",
|
|
69
|
+
arrowParentheses: "always",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const MAIN_TS_CONTENT = `export function init(): void {
|
|
75
|
+
const start = vmath.vector3(0, 0, 0);
|
|
76
|
+
msg.post("main:/hero", "spawn", { start });
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const MAIN_SCRIPT_CONTENT = `function init(self) end
|
|
81
|
+
function update(self, dt) end
|
|
82
|
+
function on_message(self, message_id, message, sender) end
|
|
83
|
+
function final(self) end
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const MAIN_COLLECTION_CONTENT = `name: "main"
|
|
87
|
+
scale_along_z: 0
|
|
88
|
+
embedded_instances {
|
|
89
|
+
id: "main"
|
|
90
|
+
data: "components {\\n id: \\"main\\"\\n component: \\"/main/main.script\\"\\n}\\n"
|
|
91
|
+
position { x: 0.0 y: 0.0 z: 0.0 }
|
|
92
|
+
rotation { x: 0.0 y: 0.0 z: 0.0 w: 1.0 }
|
|
93
|
+
scale3 { x: 1.0 y: 1.0 z: 1.0 }
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
interface PackageJson {
|
|
98
|
+
name?: string;
|
|
99
|
+
version?: string;
|
|
100
|
+
type?: string;
|
|
101
|
+
devDependencies?: Record<string, string>;
|
|
102
|
+
"defold-typescript"?: { "defold-version"?: string };
|
|
103
|
+
[key: string]: unknown;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const SCAFFOLD_DEV_DEPS: Record<string, string> = {
|
|
107
|
+
"@defold-typescript/transpiler": "workspace:*",
|
|
108
|
+
"@defold-typescript/types": "workspace:*",
|
|
109
|
+
"@biomejs/biome": "^2.2.0",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function writeJson(filePath: string, value: unknown): void {
|
|
113
|
+
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function writeGitignore(cwd: string): void {
|
|
117
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
118
|
+
if (existsSync(gitignorePath)) {
|
|
119
|
+
const existing = readFileSync(gitignorePath, "utf8");
|
|
120
|
+
const present = new Set(existing.split("\n").map((line) => line.trim()));
|
|
121
|
+
const missing = GITIGNORE_LINES.filter((line) => !present.has(line));
|
|
122
|
+
if (missing.length === 0) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const prefix = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
126
|
+
writeFileSync(gitignorePath, `${existing}${prefix}${missing.join("\n")}\n`);
|
|
127
|
+
} else {
|
|
128
|
+
writeFileSync(gitignorePath, `${GITIGNORE_LINES.join("\n")}\n`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeBiome(cwd: string, written: string[]): void {
|
|
133
|
+
const biomePath = path.join(cwd, "biome.json");
|
|
134
|
+
if (existsSync(biomePath)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
writeJson(biomePath, BIOME_JSON_CONTENT);
|
|
138
|
+
written.push("biome.json");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function writeTsSurface(cwd: string, written: string[]): ScriptKind | null {
|
|
142
|
+
mkdirSync(path.join(cwd, "src"), { recursive: true });
|
|
143
|
+
writeFileSync(path.join(cwd, "src", "main.ts"), MAIN_TS_CONTENT);
|
|
144
|
+
written.push("src/main.ts");
|
|
145
|
+
|
|
146
|
+
const kinds = detectScriptKinds(cwd);
|
|
147
|
+
const tsconfig = {
|
|
148
|
+
compilerOptions: {
|
|
149
|
+
...TSCONFIG_COMPILER_OPTIONS,
|
|
150
|
+
types: [selectScriptKindEntrypoint(kinds)],
|
|
151
|
+
},
|
|
152
|
+
include: ["src/**/*.ts"],
|
|
153
|
+
};
|
|
154
|
+
writeJson(path.join(cwd, "tsconfig.json"), tsconfig);
|
|
155
|
+
written.push("tsconfig.json");
|
|
156
|
+
|
|
157
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
158
|
+
if (existsSync(pkgPath)) {
|
|
159
|
+
const existing = JSON.parse(readFileSync(pkgPath, "utf8")) as PackageJson;
|
|
160
|
+
const devDeps = { ...(existing.devDependencies ?? {}) };
|
|
161
|
+
for (const [name, version] of Object.entries(SCAFFOLD_DEV_DEPS)) {
|
|
162
|
+
if (!(name in devDeps)) {
|
|
163
|
+
devDeps[name] = version;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
existing.devDependencies = devDeps;
|
|
167
|
+
existing["defold-typescript"] ??= { "defold-version": CURRENT_STABLE_DEFOLD_VERSION };
|
|
168
|
+
writeJson(pkgPath, existing);
|
|
169
|
+
} else {
|
|
170
|
+
const fresh: PackageJson = {
|
|
171
|
+
name: path.basename(cwd),
|
|
172
|
+
version: "0.0.0",
|
|
173
|
+
type: "module",
|
|
174
|
+
devDependencies: { ...SCAFFOLD_DEV_DEPS },
|
|
175
|
+
"defold-typescript": { "defold-version": CURRENT_STABLE_DEFOLD_VERSION },
|
|
176
|
+
};
|
|
177
|
+
writeJson(pkgPath, fresh);
|
|
178
|
+
}
|
|
179
|
+
written.push("package.json");
|
|
180
|
+
|
|
181
|
+
writeGitignore(cwd);
|
|
182
|
+
written.push(".gitignore");
|
|
183
|
+
|
|
184
|
+
writeBiome(cwd, written);
|
|
185
|
+
|
|
186
|
+
return selectScriptKind(kinds);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function runNewProjectInit(cwd: string, force = false): RunInitResult {
|
|
190
|
+
if (!existsSync(cwd)) {
|
|
191
|
+
mkdirSync(cwd, { recursive: true });
|
|
192
|
+
} else if (readdirSync(cwd).length > 0 && !force) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`defold-typescript init: refusing to synthesize a new Defold project into non-empty directory ${cwd}. Pass --force to proceed.`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const written: string[] = [];
|
|
199
|
+
|
|
200
|
+
writeFileSync(
|
|
201
|
+
path.join(cwd, "game.project"),
|
|
202
|
+
`[project]\ntitle = ${path.basename(cwd)}\nmain_collection = /main/main.collectionc\n`,
|
|
203
|
+
);
|
|
204
|
+
written.push("game.project");
|
|
205
|
+
|
|
206
|
+
mkdirSync(path.join(cwd, "main"), { recursive: true });
|
|
207
|
+
writeFileSync(path.join(cwd, "main", "main.collection"), MAIN_COLLECTION_CONTENT);
|
|
208
|
+
written.push("main/main.collection");
|
|
209
|
+
writeFileSync(path.join(cwd, "main", "main.script"), MAIN_SCRIPT_CONTENT);
|
|
210
|
+
written.push("main/main.script");
|
|
211
|
+
|
|
212
|
+
const scriptKind = writeTsSurface(cwd, written);
|
|
213
|
+
|
|
214
|
+
return { written, scriptKind };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function runInit(opts: RunInitOptions): RunInitResult {
|
|
218
|
+
const { cwd, force = false } = opts;
|
|
219
|
+
|
|
220
|
+
if (!existsSync(path.join(cwd, "game.project"))) {
|
|
221
|
+
return runNewProjectInit(cwd, force);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!force) {
|
|
225
|
+
for (const rel of CONFLICTING_TS_CONFIGS) {
|
|
226
|
+
if (existsSync(path.join(cwd, rel))) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`defold-typescript init: refusing to overwrite existing TS config: ${rel}. Pass --force to overwrite.`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const written: string[] = [];
|
|
235
|
+
const scriptKind = writeTsSurface(cwd, written);
|
|
236
|
+
return { written, scriptKind };
|
|
237
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type CliCommand = "init" | "build";
|
|
2
|
+
|
|
3
|
+
export interface RenderResultInput {
|
|
4
|
+
readonly command: CliCommand;
|
|
5
|
+
readonly written?: readonly string[];
|
|
6
|
+
readonly error?: string;
|
|
7
|
+
readonly defoldVersion?: string;
|
|
8
|
+
readonly apiSurface?: string | null;
|
|
9
|
+
readonly scriptKind?: string | null;
|
|
10
|
+
readonly materializedSurface?: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function renderResult(input: RenderResultInput): string {
|
|
14
|
+
const ok = input.error === undefined;
|
|
15
|
+
const base = ok
|
|
16
|
+
? { command: input.command, ok, written: input.written ?? [] }
|
|
17
|
+
: { command: input.command, ok, error: input.error };
|
|
18
|
+
const withVersion =
|
|
19
|
+
input.defoldVersion === undefined ? base : { ...base, defoldVersion: input.defoldVersion };
|
|
20
|
+
const withSurface =
|
|
21
|
+
"apiSurface" in input ? { ...withVersion, apiSurface: input.apiSurface } : withVersion;
|
|
22
|
+
const withScriptKind =
|
|
23
|
+
"scriptKind" in input ? { ...withSurface, scriptKind: input.scriptKind } : withSurface;
|
|
24
|
+
const payload =
|
|
25
|
+
"materializedSurface" in input
|
|
26
|
+
? { ...withScriptKind, materializedSurface: input.materializedSurface }
|
|
27
|
+
: withScriptKind;
|
|
28
|
+
return `${JSON.stringify(payload)}\n`;
|
|
29
|
+
}
|