@defold-typescript/types 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.
Files changed (58) hide show
  1. package/api-targets.json +84 -0
  2. package/generated/b2d.d.ts +13 -0
  3. package/generated/buffer.d.ts +25 -0
  4. package/generated/builtin-messages.d.ts +42 -0
  5. package/generated/camera.d.ts +7 -0
  6. package/generated/collectionfactory.d.ts +17 -0
  7. package/generated/collectionproxy.d.ts +14 -0
  8. package/generated/crash.d.ts +31 -0
  9. package/generated/factory.d.ts +17 -0
  10. package/generated/go.d.ts +96 -0
  11. package/generated/graphics.d.ts +115 -0
  12. package/generated/gui.d.ts +245 -0
  13. package/generated/http.d.ts +8 -0
  14. package/generated/iac.d.ts +8 -0
  15. package/generated/iap.d.ts +16 -0
  16. package/generated/image.d.ts +16 -0
  17. package/generated/json.d.ts +11 -0
  18. package/generated/kinds/gui-script.d.ts +39 -0
  19. package/generated/kinds/render-script.d.ts +39 -0
  20. package/generated/kinds/script.d.ts +38 -0
  21. package/generated/label.d.ts +23 -0
  22. package/generated/liveupdate.d.ts +23 -0
  23. package/generated/model.d.ts +23 -0
  24. package/generated/msg.d.ts +12 -0
  25. package/generated/particlefx.d.ts +22 -0
  26. package/generated/physics.d.ts +47 -0
  27. package/generated/profiler.d.ts +28 -0
  28. package/generated/push.d.ts +15 -0
  29. package/generated/render.d.ts +55 -0
  30. package/generated/resource.d.ts +33 -0
  31. package/generated/socket.d.ts +25 -0
  32. package/generated/sound.d.ts +28 -0
  33. package/generated/sprite.d.ts +23 -0
  34. package/generated/sys.d.ts +37 -0
  35. package/generated/tilemap.d.ts +24 -0
  36. package/generated/timer.d.ts +12 -0
  37. package/generated/vmath.d.ts +63 -0
  38. package/generated/webview.d.ts +15 -0
  39. package/generated/window.d.ts +28 -0
  40. package/generated/zlib.d.ts +9 -0
  41. package/index.d.ts +63 -0
  42. package/package.json +46 -0
  43. package/scripts/doc-source.ts +100 -0
  44. package/scripts/fidelity-audit.ts +311 -0
  45. package/scripts/fidelity-baseline.json +282 -0
  46. package/scripts/materialize-version.ts +51 -0
  47. package/scripts/regen.ts +294 -0
  48. package/scripts/sync-api-docs.ts +375 -0
  49. package/src/api-doc.ts +168 -0
  50. package/src/core-types.ts +121 -0
  51. package/src/emit-dts.ts +754 -0
  52. package/src/emit-messages.ts +148 -0
  53. package/src/go-overloads.d.ts +35 -0
  54. package/src/index.ts +24 -0
  55. package/src/lifecycle.ts +81 -0
  56. package/src/msg-overloads.d.ts +21 -0
  57. package/src/publish-dts.ts +33 -0
  58. package/src/script-api.ts +95 -0
@@ -0,0 +1,311 @@
1
+ import { resolve } from "node:path";
2
+ import { DEFOLD_TYPE_MAP } from "../src/core-types";
3
+ import {
4
+ ARBITRARY_TABLE_SLOTS,
5
+ buildTableDocResolver,
6
+ HOMOGENEOUS_ARRAY_SLOTS,
7
+ MAPPING_TABLE_SLOTS,
8
+ parseTableFields,
9
+ recoverCallbackSignature,
10
+ TS_IDENTIFIER,
11
+ } from "../src/emit-dts";
12
+ import { parseMessagesDoc } from "../src/emit-messages";
13
+ import {
14
+ collectConstantFqns,
15
+ generateModuleDeclaration,
16
+ MESSAGES_MANIFEST,
17
+ MODULE_MANIFEST,
18
+ type ModuleManifestEntry,
19
+ } from "./regen";
20
+
21
+ const NO_KNOWN_CONSTANTS: ReadonlySet<string> = new Set();
22
+
23
+ // These message names are typed by builtin-messages-typing's separate surface
24
+ // (BuiltinMessages), so a fixture `MESSAGE` element of the same name is not a
25
+ // namespace-API loss — it is reclassified out of droppedElements below.
26
+ const BUILTIN_MESSAGE_NAMES: ReadonlySet<string> = new Set(
27
+ parseMessagesDoc(MESSAGES_MANIFEST.doc).entries.map((e) => e.name),
28
+ );
29
+
30
+ export interface FidelityEntry {
31
+ droppedElements: number;
32
+ unknownTokens: string[];
33
+ recordTables: number;
34
+ multiReturn: number;
35
+ droppedMembers: number;
36
+ optionalAsRequired: number;
37
+ }
38
+
39
+ function isRecord(value: unknown): value is Record<string, unknown> {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ }
42
+
43
+ function elementsOf(doc: unknown): Record<string, unknown>[] {
44
+ if (!isRecord(doc) || !Array.isArray(doc.elements)) return [];
45
+ return doc.elements.filter(isRecord);
46
+ }
47
+
48
+ function stringArray(raw: unknown): string[] {
49
+ return Array.isArray(raw) ? raw.filter((item): item is string => typeof item === "string") : [];
50
+ }
51
+
52
+ function paramList(raw: unknown): Record<string, unknown>[] {
53
+ return Array.isArray(raw) ? raw.filter(isRecord) : [];
54
+ }
55
+
56
+ function docString(raw: unknown): string | undefined {
57
+ return typeof raw === "string" ? raw : undefined;
58
+ }
59
+
60
+ function isDocOptional(param: Record<string, unknown>): boolean {
61
+ return param.is_optional === "True" || stringArray(param.types).includes("nil");
62
+ }
63
+
64
+ function trailingOptionalCutoff(params: readonly Record<string, unknown>[]): number {
65
+ let cutoff = params.length;
66
+ for (let i = params.length - 1; i >= 0; i -= 1) {
67
+ const p = params[i];
68
+ if (p && isDocOptional(p)) cutoff = i;
69
+ else break;
70
+ }
71
+ return cutoff;
72
+ }
73
+
74
+ function auditEntry(
75
+ entry: ModuleManifestEntry,
76
+ knownConstantFqns: ReadonlySet<string>,
77
+ ): FidelityEntry {
78
+ const skipFunctions = new Set(entry.skipFunctions ?? []);
79
+ const elements = elementsOf(entry.doc);
80
+
81
+ let droppedElements = 0;
82
+ let recordTables = 0;
83
+ // multiReturn is fully recovered: emitReturn emits LuaMultiReturn<[...]> for
84
+ // every >1-return function, so no documented multi-return is a loss anymore.
85
+ const multiReturn = 0;
86
+ let optionalAsRequired = 0;
87
+ const unknown = new Set<string>();
88
+
89
+ const constantFqns = new Set<string>();
90
+ for (const element of elements) {
91
+ if (element.type === "CONSTANT" && typeof element.name === "string") {
92
+ constantFqns.add(element.name);
93
+ }
94
+ }
95
+
96
+ // Same module-scoped resolver the emitter builds, so a cross-reference table
97
+ // slot is recovered (not counted under recordTables) in lockstep with the
98
+ // emitted surface.
99
+ const resolver = buildTableDocResolver(
100
+ elements
101
+ .filter((element) => element.type === "FUNCTION" && typeof element.name === "string")
102
+ .map((element) => ({
103
+ name: element.name as string,
104
+ slots: [...paramList(element.parameters), ...paramList(element.returnvalues)].map(
105
+ (slot) => ({ types: stringArray(slot.types), doc: docString(slot.doc) ?? "" }),
106
+ ),
107
+ })),
108
+ );
109
+
110
+ const considerTypes = (
111
+ types: readonly string[],
112
+ doc?: string,
113
+ arbitraryTable = false,
114
+ mappingSlot?: { key: string; value: string },
115
+ homogeneousElement?: string | readonly string[],
116
+ ) => {
117
+ for (const token of types) {
118
+ // A `table` slot whose doc carries a parseable `<dl>` field list is
119
+ // recovered by emit-dts into an inline object type, so it no longer
120
+ // collapses to `Record`: don't count it under recordTables. Instead feed
121
+ // each recovered field's types back through considerTypes so an unmapped
122
+ // field token still surfaces under unknownTokens and a nested `table`
123
+ // field (no doc → not recovered) still counts under recordTables. This
124
+ // keeps the invariant that every loss in the emitted surface is measured.
125
+ if (token === "table") {
126
+ // A mapping-table slot is recovered by emit-dts into `LuaMap<K, V>` from
127
+ // the curated key/value tokens — not a `Record`, so don't count it.
128
+ // Feed the curated tokens back through considerTypes so an unmapped one
129
+ // still surfaces under unknownTokens (none today: hash/node/vector3 map).
130
+ if (mappingSlot !== undefined) {
131
+ considerTypes([mappingSlot.key, mappingSlot.value]);
132
+ continue;
133
+ }
134
+ // A homogeneous-array slot is recovered by emit-dts into `T[]` (or a
135
+ // `(A | B)[]` union element) from the curated element token(s) — not a
136
+ // `Record`, so don't count it. Feed the curated token(s) back through
137
+ // considerTypes so an unmapped one still surfaces under unknownTokens
138
+ // (none today: number/hash/string/url map).
139
+ if (homogeneousElement !== undefined) {
140
+ considerTypes(
141
+ typeof homogeneousElement === "string" ? [homogeneousElement] : homogeneousElement,
142
+ );
143
+ continue;
144
+ }
145
+ const fields = doc !== undefined ? parseTableFields(doc, resolver) : null;
146
+ if (fields !== null) {
147
+ // A recovered field carrying nested fields (the mixed `<dl>`+`<ul>`
148
+ // shape) emits a nested object, not a `Record` — recurse into the
149
+ // nested field types instead of counting the nested `table`.
150
+ for (const field of fields) {
151
+ if (field.fields !== undefined) {
152
+ for (const nested of field.fields) considerTypes(nested.types);
153
+ } else if (field.numberList === true) {
154
+ // Recovered as `number[]` by inlineTableType — no longer a
155
+ // `Record`, so skip it. Re-counting its `table` token here would
156
+ // double-count a slot the emitted surface no longer loses.
157
+ } else {
158
+ considerTypes(field.types);
159
+ }
160
+ }
161
+ continue;
162
+ }
163
+ // A slot on a serialization/JSON passthrough function is a genuinely
164
+ // arbitrary lua table — its emitted `Record<string | number, unknown>`
165
+ // is faithful, not a loss — so it is not counted under recordTables.
166
+ if (arbitraryTable) continue;
167
+ recordTables += 1;
168
+ continue;
169
+ }
170
+ // `nil` is the optional-parameter sentinel (emitParameter strips it), not a
171
+ // real Defold type the mapper fails to resolve — the optionalAsRequired
172
+ // category covers that loss instead. Constant FQNs defined in this module
173
+ // (constantFqns) or in any other manifest module (knownConstantFqns) now
174
+ // resolve to their brand type, and `function(...)` callback signatures
175
+ // recover to typed functions, so none is an unknown token.
176
+ if (
177
+ token !== "nil" &&
178
+ !Object.hasOwn(DEFOLD_TYPE_MAP, token) &&
179
+ !constantFqns.has(token) &&
180
+ !knownConstantFqns.has(token) &&
181
+ recoverCallbackSignature(token) === null
182
+ ) {
183
+ unknown.add(token);
184
+ }
185
+ }
186
+ };
187
+
188
+ for (const element of elements) {
189
+ const type = element.type;
190
+ // An identifier-named TYPEDEF is recovered into a per-namespace
191
+ // `type <name> = Opaque<"<name>">` alias, under the same TS_IDENTIFIER guard
192
+ // the emitter uses, so the gate and the emitted surface agree. A
193
+ // non-identifier TYPEDEF cannot become an alias and stays dropped.
194
+ if (
195
+ type === "TYPEDEF" &&
196
+ typeof element.name === "string" &&
197
+ TS_IDENTIFIER.test(element.name)
198
+ ) {
199
+ continue;
200
+ }
201
+ // A MESSAGE element whose name is in the builtin-messages catalog is a
202
+ // built-in message owned by builtin-messages-typing, not a namespace-API
203
+ // member — counting it here double-counts a surface that is fully typed
204
+ // elsewhere. A MESSAGE name absent from the catalog falls through to the
205
+ // catch-all and still counts, so the gate stays honest.
206
+ if (
207
+ type === "MESSAGE" &&
208
+ typeof element.name === "string" &&
209
+ BUILTIN_MESSAGE_NAMES.has(element.name)
210
+ ) {
211
+ continue;
212
+ }
213
+ if (type !== "FUNCTION" && type !== "VARIABLE" && type !== "CONSTANT" && type !== "PROPERTY") {
214
+ droppedElements += 1;
215
+ continue;
216
+ }
217
+ // PROPERTY is recovered into the per-namespace `interface properties` block;
218
+ // its name+type survive, so it is no longer a dropped element.
219
+ if (type === "CONSTANT" || type === "PROPERTY") continue;
220
+ if (type === "VARIABLE") {
221
+ considerTypes(stringArray(element.types));
222
+ continue;
223
+ }
224
+ // Skipped functions are measured as droppedMembers only; counting their
225
+ // params/returns here would double-count losses covered by hand-written d.ts.
226
+ if (typeof element.name === "string" && skipFunctions.has(stripNamespace(element.name))) {
227
+ continue;
228
+ }
229
+ const params = paramList(element.parameters);
230
+ const returns = paramList(element.returnvalues);
231
+ const cutoff = trailingOptionalCutoff(params);
232
+ const arbitraryTable =
233
+ typeof element.name === "string" && ARBITRARY_TABLE_SLOTS.has(element.name);
234
+ const mappingSlot =
235
+ typeof element.name === "string" ? MAPPING_TABLE_SLOTS.get(element.name) : undefined;
236
+ const homogeneousElement =
237
+ typeof element.name === "string" ? HOMOGENEOUS_ARRAY_SLOTS.get(element.name) : undefined;
238
+ params.forEach((param, index) => {
239
+ considerTypes(
240
+ stringArray(param.types),
241
+ docString(param.doc),
242
+ arbitraryTable,
243
+ mappingSlot,
244
+ homogeneousElement,
245
+ );
246
+ // Residual: a doc-optional param the emitter cannot mark `?` because a
247
+ // required param follows it. The trailing-run cutoff must match
248
+ // emit-dts so the gate and the emitted surface agree.
249
+ if (isDocOptional(param) && index < cutoff) optionalAsRequired += 1;
250
+ });
251
+ for (const ret of returns)
252
+ considerTypes(
253
+ stringArray(ret.types),
254
+ docString(ret.doc),
255
+ arbitraryTable,
256
+ mappingSlot,
257
+ homogeneousElement,
258
+ );
259
+ }
260
+
261
+ return {
262
+ droppedElements,
263
+ unknownTokens: [...unknown].sort(),
264
+ recordTables,
265
+ multiReturn,
266
+ droppedMembers: generateModuleDeclaration(entry, { knownConstantFqns: NO_KNOWN_CONSTANTS })
267
+ .dropped.length,
268
+ optionalAsRequired,
269
+ };
270
+ }
271
+
272
+ function stripNamespace(name: string): string {
273
+ const index = name.lastIndexOf(".");
274
+ return index === -1 ? name : name.slice(index + 1);
275
+ }
276
+
277
+ export function buildFidelityReport(
278
+ manifest: readonly ModuleManifestEntry[] = MODULE_MANIFEST,
279
+ ): Record<string, FidelityEntry> {
280
+ const report: Record<string, FidelityEntry> = {};
281
+ const knownConstantFqns = collectConstantFqns(manifest);
282
+ for (const namespace of [...manifest.map((e) => e.namespace)].sort()) {
283
+ const entry = manifest.find((e) => e.namespace === namespace);
284
+ if (entry) report[namespace] = auditEntry(entry, knownConstantFqns);
285
+ }
286
+ return report;
287
+ }
288
+
289
+ function biomeFormatJson(raw: string): string {
290
+ const out = Bun.spawnSync(
291
+ ["bunx", "biome", "format", "--stdin-file-path=fidelity-baseline.json"],
292
+ {
293
+ stdin: Buffer.from(raw),
294
+ },
295
+ );
296
+ if (out.exitCode !== 0) {
297
+ throw new Error(`biome format failed: ${out.stderr.toString()}`);
298
+ }
299
+ return out.stdout.toString();
300
+ }
301
+
302
+ if (import.meta.main) {
303
+ const report = buildFidelityReport();
304
+ if (process.argv.includes("--write")) {
305
+ const path = resolve(import.meta.dir, "fidelity-baseline.json");
306
+ Bun.write(path, biomeFormatJson(JSON.stringify(report)));
307
+ console.log(`wrote ${path}`);
308
+ } else {
309
+ console.log(JSON.stringify(report, null, 2));
310
+ }
311
+ }
@@ -0,0 +1,282 @@
1
+ {
2
+ "b2d": {
3
+ "droppedElements": 0,
4
+ "unknownTokens": [],
5
+ "recordTables": 0,
6
+ "multiReturn": 0,
7
+ "droppedMembers": 0,
8
+ "optionalAsRequired": 0
9
+ },
10
+ "buffer": {
11
+ "droppedElements": 0,
12
+ "unknownTokens": [],
13
+ "recordTables": 0,
14
+ "multiReturn": 0,
15
+ "droppedMembers": 0,
16
+ "optionalAsRequired": 0
17
+ },
18
+ "camera": {
19
+ "droppedElements": 0,
20
+ "unknownTokens": [],
21
+ "recordTables": 0,
22
+ "multiReturn": 0,
23
+ "droppedMembers": 0,
24
+ "optionalAsRequired": 0
25
+ },
26
+ "collectionfactory": {
27
+ "droppedElements": 0,
28
+ "unknownTokens": [],
29
+ "recordTables": 2,
30
+ "multiReturn": 0,
31
+ "droppedMembers": 0,
32
+ "optionalAsRequired": 0
33
+ },
34
+ "collectionproxy": {
35
+ "droppedElements": 0,
36
+ "unknownTokens": [],
37
+ "recordTables": 1,
38
+ "multiReturn": 0,
39
+ "droppedMembers": 0,
40
+ "optionalAsRequired": 0
41
+ },
42
+ "crash": {
43
+ "droppedElements": 0,
44
+ "unknownTokens": [],
45
+ "recordTables": 2,
46
+ "multiReturn": 0,
47
+ "droppedMembers": 0,
48
+ "optionalAsRequired": 0
49
+ },
50
+ "factory": {
51
+ "droppedElements": 0,
52
+ "unknownTokens": [],
53
+ "recordTables": 1,
54
+ "multiReturn": 0,
55
+ "droppedMembers": 0,
56
+ "optionalAsRequired": 0
57
+ },
58
+ "go": {
59
+ "droppedElements": 0,
60
+ "unknownTokens": [],
61
+ "recordTables": 2,
62
+ "multiReturn": 0,
63
+ "droppedMembers": 2,
64
+ "optionalAsRequired": 0
65
+ },
66
+ "graphics": {
67
+ "droppedElements": 0,
68
+ "unknownTokens": [],
69
+ "recordTables": 0,
70
+ "multiReturn": 0,
71
+ "droppedMembers": 0,
72
+ "optionalAsRequired": 0
73
+ },
74
+ "gui": {
75
+ "droppedElements": 0,
76
+ "unknownTokens": [],
77
+ "recordTables": 2,
78
+ "multiReturn": 0,
79
+ "droppedMembers": 0,
80
+ "optionalAsRequired": 0
81
+ },
82
+ "http": {
83
+ "droppedElements": 0,
84
+ "unknownTokens": [],
85
+ "recordTables": 1,
86
+ "multiReturn": 0,
87
+ "droppedMembers": 0,
88
+ "optionalAsRequired": 0
89
+ },
90
+ "iac": {
91
+ "droppedElements": 0,
92
+ "unknownTokens": [],
93
+ "recordTables": 1,
94
+ "multiReturn": 0,
95
+ "droppedMembers": 0,
96
+ "optionalAsRequired": 0
97
+ },
98
+ "iap": {
99
+ "droppedElements": 0,
100
+ "unknownTokens": [],
101
+ "recordTables": 3,
102
+ "multiReturn": 0,
103
+ "droppedMembers": 0,
104
+ "optionalAsRequired": 0
105
+ },
106
+ "image": {
107
+ "droppedElements": 0,
108
+ "unknownTokens": [],
109
+ "recordTables": 0,
110
+ "multiReturn": 0,
111
+ "droppedMembers": 0,
112
+ "optionalAsRequired": 0
113
+ },
114
+ "json": {
115
+ "droppedElements": 0,
116
+ "unknownTokens": [],
117
+ "recordTables": 0,
118
+ "multiReturn": 0,
119
+ "droppedMembers": 0,
120
+ "optionalAsRequired": 0
121
+ },
122
+ "label": {
123
+ "droppedElements": 0,
124
+ "unknownTokens": [],
125
+ "recordTables": 0,
126
+ "multiReturn": 0,
127
+ "droppedMembers": 0,
128
+ "optionalAsRequired": 0
129
+ },
130
+ "liveupdate": {
131
+ "droppedElements": 0,
132
+ "unknownTokens": [],
133
+ "recordTables": 1,
134
+ "multiReturn": 0,
135
+ "droppedMembers": 0,
136
+ "optionalAsRequired": 0
137
+ },
138
+ "model": {
139
+ "droppedElements": 0,
140
+ "unknownTokens": [],
141
+ "recordTables": 2,
142
+ "multiReturn": 0,
143
+ "droppedMembers": 0,
144
+ "optionalAsRequired": 0
145
+ },
146
+ "msg": {
147
+ "droppedElements": 0,
148
+ "unknownTokens": [],
149
+ "recordTables": 0,
150
+ "multiReturn": 0,
151
+ "droppedMembers": 1,
152
+ "optionalAsRequired": 0
153
+ },
154
+ "particlefx": {
155
+ "droppedElements": 0,
156
+ "unknownTokens": [],
157
+ "recordTables": 0,
158
+ "multiReturn": 0,
159
+ "droppedMembers": 0,
160
+ "optionalAsRequired": 0
161
+ },
162
+ "physics": {
163
+ "droppedElements": 0,
164
+ "unknownTokens": [],
165
+ "recordTables": 5,
166
+ "multiReturn": 0,
167
+ "droppedMembers": 0,
168
+ "optionalAsRequired": 0
169
+ },
170
+ "profiler": {
171
+ "droppedElements": 0,
172
+ "unknownTokens": [],
173
+ "recordTables": 1,
174
+ "multiReturn": 0,
175
+ "droppedMembers": 0,
176
+ "optionalAsRequired": 0
177
+ },
178
+ "push": {
179
+ "droppedElements": 0,
180
+ "unknownTokens": [],
181
+ "recordTables": 4,
182
+ "multiReturn": 0,
183
+ "droppedMembers": 0,
184
+ "optionalAsRequired": 0
185
+ },
186
+ "render": {
187
+ "droppedElements": 0,
188
+ "unknownTokens": [],
189
+ "recordTables": 4,
190
+ "multiReturn": 0,
191
+ "droppedMembers": 0,
192
+ "optionalAsRequired": 0
193
+ },
194
+ "resource": {
195
+ "droppedElements": 0,
196
+ "unknownTokens": [],
197
+ "recordTables": 3,
198
+ "multiReturn": 0,
199
+ "droppedMembers": 0,
200
+ "optionalAsRequired": 0
201
+ },
202
+ "socket": {
203
+ "droppedElements": 0,
204
+ "unknownTokens": [],
205
+ "recordTables": 8,
206
+ "multiReturn": 0,
207
+ "droppedMembers": 0,
208
+ "optionalAsRequired": 0
209
+ },
210
+ "sound": {
211
+ "droppedElements": 0,
212
+ "unknownTokens": [],
213
+ "recordTables": 0,
214
+ "multiReturn": 0,
215
+ "droppedMembers": 0,
216
+ "optionalAsRequired": 0
217
+ },
218
+ "sprite": {
219
+ "droppedElements": 0,
220
+ "unknownTokens": [],
221
+ "recordTables": 0,
222
+ "multiReturn": 0,
223
+ "droppedMembers": 0,
224
+ "optionalAsRequired": 0
225
+ },
226
+ "sys": {
227
+ "droppedElements": 0,
228
+ "unknownTokens": [],
229
+ "recordTables": 0,
230
+ "multiReturn": 0,
231
+ "droppedMembers": 0,
232
+ "optionalAsRequired": 0
233
+ },
234
+ "tilemap": {
235
+ "droppedElements": 0,
236
+ "unknownTokens": [],
237
+ "recordTables": 2,
238
+ "multiReturn": 0,
239
+ "droppedMembers": 0,
240
+ "optionalAsRequired": 0
241
+ },
242
+ "timer": {
243
+ "droppedElements": 0,
244
+ "unknownTokens": [],
245
+ "recordTables": 0,
246
+ "multiReturn": 0,
247
+ "droppedMembers": 0,
248
+ "optionalAsRequired": 0
249
+ },
250
+ "vmath": {
251
+ "droppedElements": 0,
252
+ "unknownTokens": [],
253
+ "recordTables": 0,
254
+ "multiReturn": 0,
255
+ "droppedMembers": 0,
256
+ "optionalAsRequired": 0
257
+ },
258
+ "webview": {
259
+ "droppedElements": 0,
260
+ "unknownTokens": [],
261
+ "recordTables": 1,
262
+ "multiReturn": 0,
263
+ "droppedMembers": 0,
264
+ "optionalAsRequired": 0
265
+ },
266
+ "window": {
267
+ "droppedElements": 0,
268
+ "unknownTokens": [],
269
+ "recordTables": 0,
270
+ "multiReturn": 0,
271
+ "droppedMembers": 0,
272
+ "optionalAsRequired": 0
273
+ },
274
+ "zlib": {
275
+ "droppedElements": 0,
276
+ "unknownTokens": [],
277
+ "recordTables": 0,
278
+ "multiReturn": 0,
279
+ "droppedMembers": 0,
280
+ "optionalAsRequired": 0
281
+ }
282
+ }
@@ -0,0 +1,51 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ type ApiTarget,
5
+ generateModuleDeclaration,
6
+ generateVersionIndex,
7
+ type ResolveTargetOptions,
8
+ resolveTargetModules,
9
+ } from "./regen";
10
+
11
+ export interface MaterializeVersionedSurfaceOptions {
12
+ readonly destDir: string;
13
+ readonly resolveOpts?: ResolveTargetOptions;
14
+ // Bare module names (no `.d.ts`) to omit from the surface — both the emitted
15
+ // file and its aggregate-index import. Lets a caller narrow the surface to a
16
+ // script kind without the generator knowing what a script kind is.
17
+ readonly excludeModules?: readonly string[];
18
+ }
19
+
20
+ // Generate a versioned surface on the fly into a project-local faux `@types`
21
+ // package: resolve the target's module docs (ref-doc or committed fixture),
22
+ // emit each module declaration, then write the aggregate side-effect entrypoint
23
+ // and a minimal package.json. ref-doc targets are never pre-baked, so this is
24
+ // the only path that turns a resolved version into a consumable type surface.
25
+ export async function materializeVersionedSurface(
26
+ target: ApiTarget,
27
+ opts: MaterializeVersionedSurfaceOptions,
28
+ ): Promise<void> {
29
+ const exclude = new Set(opts.excludeModules ?? []);
30
+ const modules = (await resolveTargetModules(target, opts.resolveOpts ?? {})).filter(
31
+ (entry) => !exclude.has(entry.outFile.replace(/\.d\.ts$/, "")),
32
+ );
33
+ mkdirSync(opts.destDir, { recursive: true });
34
+
35
+ for (const entry of modules) {
36
+ const { contents } = generateModuleDeclaration(entry);
37
+ writeFileSync(resolve(opts.destDir, entry.outFile), contents);
38
+ }
39
+
40
+ const versioned = modules.map((entry) => ({ ...entry, versionId: target.id }));
41
+ writeFileSync(resolve(opts.destDir, "index.d.ts"), generateVersionIndex(target.id, versioned));
42
+
43
+ writeFileSync(
44
+ resolve(opts.destDir, "package.json"),
45
+ `${JSON.stringify(
46
+ { name: `@defold-typescript/materialized-${target.id}`, types: "index.d.ts" },
47
+ null,
48
+ 2,
49
+ )}\n`,
50
+ );
51
+ }