@defold-typescript/library-types 0.23.0 → 0.25.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/NOTICE +2 -3
- package/api-doc/boom.json +3872 -0
- package/api-doc/bridge.json +10 -5
- package/api-doc/bzAnim.json +338 -0
- package/api-doc/checkpoint.json +127 -0
- package/api-doc/decore.json +1 -1
- package/api-doc/defcon.json +150 -0
- package/api-doc/{defmath.defmath.json → defmath.json} +6 -46
- package/api-doc/defsave.json +518 -0
- package/api-doc/deftest.json +745 -0
- package/api-doc/{dicebag.dicebag.json → dicebag.json} +19 -1
- package/api-doc/druid.json +90 -92
- package/api-doc/event.json +16 -16
- package/api-doc/gooey.json +2050 -0
- package/api-doc/in.accelerometer.json +75 -1
- package/api-doc/in.button.json +17 -10
- package/api-doc/in.cursor.json +26 -0
- package/api-doc/in.gesture.json +401 -6
- package/api-doc/in.keyboard.json +2 -0
- package/api-doc/in.mapper.json +12 -8
- package/api-doc/in.onscreen.json +61 -5
- package/api-doc/in.state.json +61 -13
- package/api-doc/in.textbox.json +23 -12
- package/api-doc/in.triggers.json +336 -0
- package/api-doc/lang.json +5 -5
- package/api-doc/log.json +1 -1
- package/api-doc/metrics.fps.json +61 -2
- package/api-doc/metrics.mem.json +61 -2
- package/api-doc/monarch.monarch.json +249 -7
- package/api-doc/monarch.transitions.easings.json +19 -26
- package/api-doc/monarch.transitions.gui.json +782 -79
- package/api-doc/nakama.engine.defold.json +31 -18
- package/api-doc/{nakama.nakama.json → nakama.json} +4752 -2554
- package/api-doc/nakama.session.json +315 -0
- package/api-doc/nakama.socket.json +3356 -0
- package/api-doc/nakama.util.log.json +32 -4
- package/api-doc/narrator.json +1 -1
- package/api-doc/openapi/nakama.nakama.json +7680 -0
- package/api-doc/orthographic.camera.json +96 -98
- package/api-doc/orthographic.json +602 -0
- package/api-doc/panthera.json +1183 -0
- package/api-doc/{persist.persist.json → persist.json} +26 -1
- package/api-doc/{platypus.platypus.json → platypus.json} +68 -14
- package/api-doc/{rendy.rendy.json → rendy.json} +46 -99
- package/api-doc/richtext.color.json +77 -0
- package/api-doc/richtext.richtext.json +375 -5
- package/api-doc/saver.saver.json +2 -1
- package/api-doc/saver.storage.json +2 -1
- package/api-doc/shutter.json +551 -0
- package/api-doc/{yagames.yagames.json → yagames.json} +784 -189
- package/api-doc/{zzfx.api.json → zzfx.json} +9 -9
- package/authored-targets.json +433 -0
- package/generated/{boom.boom.d.ts → boom.d.ts} +14 -0
- package/generated/bridge.d.ts +5 -5
- package/generated/{bzAnim.bzLibrary.d.ts → bzAnim.d.ts} +6 -0
- package/generated/checkpoint.d.ts +62 -0
- package/generated/decore.d.ts +1 -1
- package/generated/defcon.d.ts +43 -0
- package/generated/{defmath.defmath.d.ts → defmath.d.ts} +3 -2
- package/generated/defsave.d.ts +144 -0
- package/generated/{dicebag.dicebag.d.ts → dicebag.d.ts} +25 -0
- package/generated/druid.d.ts +81 -79
- package/generated/event.d.ts +15 -15
- package/generated/{gooey.gooey.d.ts → gooey.d.ts} +61 -6
- package/generated/in.onscreen.d.ts +5 -0
- package/generated/in.textbox.d.ts +4 -0
- package/generated/lang.d.ts +5 -5
- package/generated/log.d.ts +1 -1
- package/generated/metrics.fps.d.ts +19 -1
- package/generated/metrics.mem.d.ts +19 -1
- package/generated/monarch.monarch.d.ts +44 -1
- package/generated/monarch.transitions.easings.d.ts +0 -1
- package/generated/monarch.transitions.gui.d.ts +96 -12
- package/generated/nakama.d.ts +1158 -0
- package/generated/nakama.engine.defold.d.ts +3 -1
- package/generated/nakama.session.d.ts +95 -0
- package/generated/nakama.socket.d.ts +706 -0
- package/generated/nakama.util.log.d.ts +6 -0
- package/generated/narrator.d.ts +1 -1
- package/generated/openapi/nakama.nakama.d.ts +884 -0
- package/generated/orthographic.camera.d.ts +24 -38
- package/generated/orthographic.d.ts +155 -0
- package/generated/panthera.d.ts +170 -0
- package/generated/{persist.persist.d.ts → persist.d.ts} +3 -0
- package/generated/{platypus.platypus.d.ts → platypus.d.ts} +9 -0
- package/generated/proto.d.ts +4 -4
- package/generated/{rendy.rendy.d.ts → rendy.d.ts} +33 -80
- package/generated/richtext.color.d.ts +15 -0
- package/generated/saver.saver.d.ts +2 -0
- package/generated/saver.storage.d.ts +2 -0
- package/generated/shutter.d.ts +183 -0
- package/generated/{yagames.yagames.d.ts → yagames.d.ts} +151 -29
- package/generated/{zzfx.api.d.ts → zzfx.d.ts} +7 -0
- package/library-classification.json +0 -156
- package/library-targets.json +1 -218
- package/luals-targets.json +56 -3
- package/openapi-targets.json +17 -0
- package/package.json +7 -57
- package/scripts/__snapshots__/parse-luals.test.ts.snap +164 -10
- package/scripts/apply-luals-overrides.ts +39 -7
- package/scripts/authored-parity.ts +718 -0
- package/scripts/emit-library-dts.ts +94 -17
- package/scripts/extract-api-doc.ts +214 -42
- package/scripts/fidelity-floor.ts +178 -0
- package/scripts/import-upstream-docs.ts +125 -0
- package/scripts/lower-api-doc.ts +21 -3
- package/scripts/luals-fidelity.ts +16 -11
- package/scripts/map-luals-types.ts +52 -21
- package/scripts/parse-lua-surface.ts +187 -0
- package/scripts/parse-luals.ts +50 -4
- package/scripts/parse-markdown-api.ts +247 -0
- package/scripts/parse-openapi-api.ts +313 -0
- package/scripts/sync-authored-types.ts +249 -0
- package/scripts/sync-library-types.ts +99 -3
- package/scripts/sync-luals-types.ts +14 -1
- package/scripts/sync-markdown-types.ts +761 -0
- package/scripts/sync-openapi-types.ts +308 -0
- package/scripts/sync-script-api-types.ts +16 -17
- package/api-doc/boom.boom.json +0 -26
- package/api-doc/bzAnim.bzLibrary.json +0 -156
- package/api-doc/defcon.console.json +0 -109
- package/api-doc/defsave.defsave.json +0 -175
- package/api-doc/deftest.deftest.json +0 -71
- package/api-doc/gooey.gooey.json +0 -963
- package/api-doc/starly.starly.json +0 -488
- package/generated/defcon.console.d.ts +0 -24
- package/generated/defsave.defsave.d.ts +0 -31
- package/generated/nakama.nakama.d.ts +0 -594
- package/generated/starly.starly.d.ts +0 -148
- /package/generated/{deftest.deftest.d.ts → deftest.d.ts} +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenAPI/proto ingestion front-end's parser: a fifth `library-types` corpus
|
|
3
|
+
* source beside the LuaLS annotations (`parse-luals.ts`), the typed `.script_api`
|
|
4
|
+
* (`scriptApiToFixtureJson`), the README markdown (`parse-markdown-api.ts`), and
|
|
5
|
+
* the authored `.d.ts` lane. It reads a structured REST swagger (JSON) plus the
|
|
6
|
+
* realtime `.proto` message list — the machine-readable source a codegen'd Lua
|
|
7
|
+
* client (nakama-defold) is generated from — and produces the same ref-doc `doc`
|
|
8
|
+
* shape those front-ends feed the shared emitter (`generateModuleDeclaration`), so
|
|
9
|
+
* the OpenAPI path reuses the exact emit + fidelity machinery.
|
|
10
|
+
*
|
|
11
|
+
* The surface mirrors the client codegen's naming, not the raw wire schema:
|
|
12
|
+
*
|
|
13
|
+
* - a swagger `paths` operation (`operationId` `Nakama_AuthenticateCustom`) becomes
|
|
14
|
+
* a client RPC function `authenticate_custom` (service prefix stripped, snake);
|
|
15
|
+
* - a swagger `definitions` object (`apiAccountCustom`) becomes a constructor
|
|
16
|
+
* `create_api_account_custom`;
|
|
17
|
+
* - a realtime proto `message ChannelMessageSend` becomes a realtime message
|
|
18
|
+
* constructor `create_channel_message_send_message`.
|
|
19
|
+
*
|
|
20
|
+
* Scope is a **flat signature surface**, not deep schema modelling (a PRD
|
|
21
|
+
* non-goal): swagger scalar types and `$ref`/object/array types collapse to the
|
|
22
|
+
* emitter's token vocabulary (`string`/`number`/`boolean`/`table`), and proto
|
|
23
|
+
* scalars/messages likewise. Every parameter and return slot carries at least one
|
|
24
|
+
* resolvable token — a slot is never left type-empty (the "no silent swallow"
|
|
25
|
+
* contract the fidelity gate depends on).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** A single ref-doc parameter or return slot. `is_optional` mirrors the consumer
|
|
29
|
+
* contract's string flag (`"True"`); it is present only when set. */
|
|
30
|
+
export interface OpenApiParam {
|
|
31
|
+
name: string;
|
|
32
|
+
doc: string;
|
|
33
|
+
types: string[];
|
|
34
|
+
is_optional?: "True";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OpenApiElement {
|
|
38
|
+
type: "FUNCTION";
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
parameters: OpenApiParam[];
|
|
42
|
+
returnvalues: OpenApiParam[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The ref-doc `doc` shape `generateModuleDeclaration` consumes. `info.namespace`
|
|
46
|
+
* is empty here — the swagger/proto source names no publish alias — and the
|
|
47
|
+
* front-end retargets the bare element names onto the pinned namespace. */
|
|
48
|
+
export interface OpenApiDoc {
|
|
49
|
+
info: { namespace: string; brief: string; description: string };
|
|
50
|
+
elements: OpenApiElement[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** camelCase / PascalCase -> snake_case, splitting acronym runs on the trailing
|
|
54
|
+
* capitalised word (`HTTPStatus` -> `http_status`). */
|
|
55
|
+
function snake(name: string): string {
|
|
56
|
+
return name
|
|
57
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
58
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
59
|
+
.toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Map a swagger schema type keyword to a resolvable emitter token. Object,
|
|
63
|
+
* array, and `$ref` shapes all collapse to `table` (a Lua table), the faithful
|
|
64
|
+
* shape a Lua client marshals them as. */
|
|
65
|
+
function swaggerType(type: string | undefined, hasRef: boolean): string {
|
|
66
|
+
if (hasRef) return "table";
|
|
67
|
+
switch (type) {
|
|
68
|
+
case "integer":
|
|
69
|
+
case "number":
|
|
70
|
+
return "number";
|
|
71
|
+
case "boolean":
|
|
72
|
+
return "boolean";
|
|
73
|
+
case "string":
|
|
74
|
+
return "string";
|
|
75
|
+
default:
|
|
76
|
+
// `object`, `array`, and anything unrecognised marshal as a table.
|
|
77
|
+
return "table";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface SwaggerSchema {
|
|
82
|
+
type?: string;
|
|
83
|
+
$ref?: string;
|
|
84
|
+
items?: SwaggerSchema;
|
|
85
|
+
properties?: Record<string, SwaggerSchema>;
|
|
86
|
+
required?: string[];
|
|
87
|
+
additionalProperties?: unknown;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface SwaggerParameter {
|
|
91
|
+
name: string;
|
|
92
|
+
in: string;
|
|
93
|
+
required?: boolean;
|
|
94
|
+
type?: string;
|
|
95
|
+
schema?: SwaggerSchema;
|
|
96
|
+
description?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface SwaggerOperation {
|
|
100
|
+
operationId?: string;
|
|
101
|
+
summary?: string;
|
|
102
|
+
description?: string;
|
|
103
|
+
parameters?: SwaggerParameter[];
|
|
104
|
+
responses?: Record<string, { schema?: SwaggerSchema; description?: string }>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface SwaggerRoot {
|
|
108
|
+
paths?: Record<string, Record<string, SwaggerOperation>>;
|
|
109
|
+
definitions?: Record<string, SwaggerSchema>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Strip a leading `Service_` prefix from an operationId, then snake the rest. */
|
|
113
|
+
function operationName(operationId: string): string {
|
|
114
|
+
const underscore = operationId.indexOf("_");
|
|
115
|
+
const bare = underscore >= 0 ? operationId.slice(underscore + 1) : operationId;
|
|
116
|
+
return snake(bare);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function schemaToken(schema: SwaggerSchema): string {
|
|
120
|
+
return swaggerType(schema.type, typeof schema.$ref === "string");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function swaggerParamSlot(param: SwaggerParameter): OpenApiParam {
|
|
124
|
+
const token =
|
|
125
|
+
param.in === "body" && param.schema !== undefined
|
|
126
|
+
? schemaToken(param.schema)
|
|
127
|
+
: swaggerType(param.type, false);
|
|
128
|
+
const slot: OpenApiParam = {
|
|
129
|
+
name: param.name,
|
|
130
|
+
doc: (param.description ?? "").trim(),
|
|
131
|
+
types: [token],
|
|
132
|
+
};
|
|
133
|
+
if (param.required !== true) slot.is_optional = "True";
|
|
134
|
+
return slot;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function swaggerReturnSlots(op: SwaggerOperation): OpenApiParam[] {
|
|
138
|
+
const schema = op.responses?.["200"]?.schema;
|
|
139
|
+
if (schema === undefined) return [];
|
|
140
|
+
return [{ name: "result", doc: "", types: [schemaToken(schema)] }];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseSwaggerOperations(root: SwaggerRoot): OpenApiElement[] {
|
|
144
|
+
const elements: OpenApiElement[] = [];
|
|
145
|
+
for (const methods of Object.values(root.paths ?? {})) {
|
|
146
|
+
for (const op of Object.values(methods)) {
|
|
147
|
+
if (typeof op.operationId !== "string") continue;
|
|
148
|
+
elements.push({
|
|
149
|
+
type: "FUNCTION",
|
|
150
|
+
name: operationName(op.operationId),
|
|
151
|
+
description: (op.summary ?? op.description ?? "").trim(),
|
|
152
|
+
parameters: (op.parameters ?? []).map(swaggerParamSlot),
|
|
153
|
+
returnvalues: swaggerReturnSlots(op),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return elements;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseSwaggerDefinitions(root: SwaggerRoot): OpenApiElement[] {
|
|
161
|
+
const elements: OpenApiElement[] = [];
|
|
162
|
+
for (const [name, schema] of Object.entries(root.definitions ?? {})) {
|
|
163
|
+
// Only object definitions become constructors; enum/scalar definitions carry
|
|
164
|
+
// no properties and would emit a meaningless zero-argument builder.
|
|
165
|
+
if (schema.type !== "object" || schema.properties === undefined) continue;
|
|
166
|
+
const required = new Set(schema.required ?? []);
|
|
167
|
+
const parameters = Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
168
|
+
const slot: OpenApiParam = { name: propName, doc: "", types: [schemaToken(propSchema)] };
|
|
169
|
+
if (!required.has(propName)) slot.is_optional = "True";
|
|
170
|
+
return slot;
|
|
171
|
+
});
|
|
172
|
+
elements.push({
|
|
173
|
+
type: "FUNCTION",
|
|
174
|
+
name: `create_${snake(name)}`,
|
|
175
|
+
description: (root.definitions?.[name] as { description?: string }).description?.trim() ?? "",
|
|
176
|
+
parameters,
|
|
177
|
+
returnvalues: [{ name: "result", doc: "", types: ["table"] }],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return elements;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Map a proto field type to a resolvable emitter token. Scalar numerics and
|
|
184
|
+
* `bytes` collapse per Lua marshalling; message/enum/`map`/`repeated` types are
|
|
185
|
+
* tables. */
|
|
186
|
+
function protoType(rawType: string): string {
|
|
187
|
+
const type = rawType.trim();
|
|
188
|
+
if (type.startsWith("map<") || type.startsWith("repeated ")) return "table";
|
|
189
|
+
switch (type) {
|
|
190
|
+
case "double":
|
|
191
|
+
case "float":
|
|
192
|
+
case "int32":
|
|
193
|
+
case "int64":
|
|
194
|
+
case "uint32":
|
|
195
|
+
case "uint64":
|
|
196
|
+
case "sint32":
|
|
197
|
+
case "sint64":
|
|
198
|
+
case "fixed32":
|
|
199
|
+
case "fixed64":
|
|
200
|
+
case "sfixed32":
|
|
201
|
+
case "sfixed64":
|
|
202
|
+
return "number";
|
|
203
|
+
case "bool":
|
|
204
|
+
return "boolean";
|
|
205
|
+
case "string":
|
|
206
|
+
case "bytes":
|
|
207
|
+
return "string";
|
|
208
|
+
default:
|
|
209
|
+
// A message- or enum-typed field marshals as a table.
|
|
210
|
+
return "table";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// A proto field line: an optional `repeated`, a type (`map<...>` allowed), a field
|
|
215
|
+
// name, `=`, a tag, `;`. Reserved lines, `oneof`, options, and nested blocks are
|
|
216
|
+
// ignored by requiring the `= <tag>;` tail.
|
|
217
|
+
const PROTO_FIELD = /^\s*(map<[^>]+>|repeated\s+[\w.]+|[\w.]+)\s+([a-z_]\w*)\s*=\s*\d+\s*;/;
|
|
218
|
+
const PROTO_MESSAGE_OPEN = /^message\s+([A-Za-z_]\w*)\s*\{/;
|
|
219
|
+
// A `oneof` groups alternatives that are the enclosing message's own fields; any
|
|
220
|
+
// other block (`message`/`enum`) owns its fields and must not leak them upward, so
|
|
221
|
+
// non-`oneof` openers default to an isolating `other` frame.
|
|
222
|
+
const PROTO_ONEOF_OPEN = /^\s*oneof\s+[A-Za-z_]\w*\s*\{/;
|
|
223
|
+
|
|
224
|
+
function parseProtoMessages(protoText: string): OpenApiElement[] {
|
|
225
|
+
const elements: OpenApiElement[] = [];
|
|
226
|
+
const lines = protoText.split("\n");
|
|
227
|
+
let current: { name: string; parameters: OpenApiParam[] } | null = null;
|
|
228
|
+
// Block-kind frames nested inside the current message, excluding the message
|
|
229
|
+
// root (which `current !== null` represents). Integer depth cannot tell a
|
|
230
|
+
// `oneof` alternative (a parent field) from a nested-`message` field (not one);
|
|
231
|
+
// the frame kinds carry that distinction.
|
|
232
|
+
let frames: Array<"oneof" | "other"> = [];
|
|
233
|
+
|
|
234
|
+
const emit = () => {
|
|
235
|
+
if (current === null) return;
|
|
236
|
+
elements.push({
|
|
237
|
+
type: "FUNCTION",
|
|
238
|
+
name: `create_${snake(current.name)}_message`,
|
|
239
|
+
description: "",
|
|
240
|
+
parameters: current.parameters,
|
|
241
|
+
returnvalues: [],
|
|
242
|
+
});
|
|
243
|
+
current = null;
|
|
244
|
+
frames = [];
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Account one message-body segment (a whole line, or the tail of the open line
|
|
248
|
+
// after its header brace): count a field only when the frames above the root
|
|
249
|
+
// are empty or all `oneof`, then apply the segment's braces — a `}` that finds
|
|
250
|
+
// no nested frame closes the message root.
|
|
251
|
+
const processBody = (body: string): void => {
|
|
252
|
+
if (current === null) return;
|
|
253
|
+
const field = PROTO_FIELD.exec(body);
|
|
254
|
+
if (field !== null && frames.every((f) => f === "oneof")) {
|
|
255
|
+
const slot: OpenApiParam = {
|
|
256
|
+
name: field[2] as string,
|
|
257
|
+
doc: "",
|
|
258
|
+
types: [protoType(field[1] as string)],
|
|
259
|
+
};
|
|
260
|
+
if (frames.includes("oneof")) slot.is_optional = "True";
|
|
261
|
+
current.parameters.push(slot);
|
|
262
|
+
}
|
|
263
|
+
const openKind: "oneof" | "other" = PROTO_ONEOF_OPEN.test(body) ? "oneof" : "other";
|
|
264
|
+
for (let i = 0; i < (body.match(/\{/g) ?? []).length; i++) frames.push(openKind);
|
|
265
|
+
for (let i = 0; i < (body.match(/\}/g) ?? []).length; i++) {
|
|
266
|
+
if (frames.length > 0) {
|
|
267
|
+
frames.pop();
|
|
268
|
+
} else {
|
|
269
|
+
emit();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
for (const line of lines) {
|
|
276
|
+
if (current === null) {
|
|
277
|
+
const open = PROTO_MESSAGE_OPEN.exec(line);
|
|
278
|
+
if (open !== null) {
|
|
279
|
+
current = { name: open[1] as string, parameters: [] };
|
|
280
|
+
frames = [];
|
|
281
|
+
// Process the same line's remainder so `message Ping {}` closes here
|
|
282
|
+
// instead of swallowing every following top-level message.
|
|
283
|
+
processBody(line.slice(open[0].length));
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
processBody(line);
|
|
288
|
+
}
|
|
289
|
+
return elements;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Parse the pinned swagger JSON and realtime `.proto` into the shared ref-doc
|
|
294
|
+
* `doc` shape. Swagger operations, swagger object definitions, and proto messages
|
|
295
|
+
* each contribute one element; the first occurrence of a codegen'd name wins so a
|
|
296
|
+
* definition and a like-named operation never emit a duplicate member.
|
|
297
|
+
*/
|
|
298
|
+
export function parseOpenApi(swaggerText: string, protoText: string): OpenApiDoc {
|
|
299
|
+
const root = JSON.parse(swaggerText) as SwaggerRoot;
|
|
300
|
+
const all = [
|
|
301
|
+
...parseSwaggerOperations(root),
|
|
302
|
+
...parseSwaggerDefinitions(root),
|
|
303
|
+
...parseProtoMessages(protoText),
|
|
304
|
+
];
|
|
305
|
+
const seen = new Set<string>();
|
|
306
|
+
const elements: OpenApiElement[] = [];
|
|
307
|
+
for (const element of all) {
|
|
308
|
+
if (seen.has(element.name)) continue;
|
|
309
|
+
seen.add(element.name);
|
|
310
|
+
elements.push(element);
|
|
311
|
+
}
|
|
312
|
+
return { info: { namespace: "", brief: "", description: "" }, elements };
|
|
313
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { extractApiDoc } from "./extract-api-doc";
|
|
4
|
+
import { type ApiDocElement, importUpstreamDocs } from "./import-upstream-docs";
|
|
5
|
+
import { type LuaMember, parseLuaSurface } from "./parse-lua-surface";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The authored/forked front-end: a fourth `library-types` corpus mode beside the
|
|
9
|
+
* LuaLS (`sync-luals-types.ts`), `.script_api` (`sync-script-api-types.ts`), and
|
|
10
|
+
* markdown (`sync-markdown-types.ts`) lanes. The other three ingest a *non-*`.d.ts`
|
|
11
|
+
* primary source and generate a declaration; this one is for a library whose
|
|
12
|
+
* upstream has no usable structured source, so its `.d.ts` is hand-authored or
|
|
13
|
+
* forked from the retired ts-defold `generated/` golden and vendored under
|
|
14
|
+
* `fixtures/authored/<moduleId>.d.ts`.
|
|
15
|
+
*
|
|
16
|
+
* A vendored authored `.d.ts` is already target-form — a `declare module
|
|
17
|
+
* '<moduleId>'` ambient — so the lane is light: it emits the vendored source
|
|
18
|
+
* verbatim as the `generated/<namespace>.d.ts` golden and runs
|
|
19
|
+
* `extractApiDoc` for the `api-doc/<namespace>.json` the docs-site consumes. The
|
|
20
|
+
* api-doc alone carries one thing the fork does not: where a target vendors its
|
|
21
|
+
* upstream `.lua` and the fork documents a member nowhere, upstream's own LuaDoc
|
|
22
|
+
* summary is lowered in, marked `docSource: "upstream"` (see
|
|
23
|
+
* `import-upstream-docs.ts`). The *emitted* surface is still the vendored authored
|
|
24
|
+
* source untouched, so the go/no-go gate is a
|
|
25
|
+
* forked-vs-generated identity diff (see the golden loop) — emission fidelity.
|
|
26
|
+
* There is no *type* coverage comparison because upstream declares no types; the
|
|
27
|
+
* *surface* is another matter, and a target that vendors its upstream `.lua` under
|
|
28
|
+
* `upstreamLua` is measured on member names and arity by `authored-parity.ts`.
|
|
29
|
+
* Beyond that, a corrected fork's accuracy rests on its manual audit against
|
|
30
|
+
* upstream plus the per-library shape assertions in the sibling test.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Why a target's surface cannot be measured against upstream. A closed set: the
|
|
34
|
+
* alternative to `upstreamLua` has to be a *chosen* category, because free prose
|
|
35
|
+
* would let "nobody looked yet" and "looked, and it is genuinely unmeasurable"
|
|
36
|
+
* read the same in the config.
|
|
37
|
+
*
|
|
38
|
+
* - `unresolved-path` — the `moduleId`-derived path does not resolve at the
|
|
39
|
+
* pinned `ref` (including a repository that no longer exists there).
|
|
40
|
+
* - `no-module-file` — the upstream resolves but declares no single module file
|
|
41
|
+
* this target could be measured against.
|
|
42
|
+
* - `unparseable-shape` — the module file exists but closes in a way
|
|
43
|
+
* `parseLuaSurface` refuses, such as a metatable that could delegate members
|
|
44
|
+
* no column-0 scan would find.
|
|
45
|
+
*/
|
|
46
|
+
export const PARITY_VERDICT_REASONS = [
|
|
47
|
+
"no-module-file",
|
|
48
|
+
"unresolved-path",
|
|
49
|
+
"unparseable-shape",
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
export type ParityVerdictReason = (typeof PARITY_VERDICT_REASONS)[number];
|
|
53
|
+
|
|
54
|
+
export interface ParityVerdict {
|
|
55
|
+
reason: ParityVerdictReason;
|
|
56
|
+
note: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AuthoredTarget {
|
|
60
|
+
repo: string;
|
|
61
|
+
ref: string;
|
|
62
|
+
// Package-relative path of the vendored authored/forked `.d.ts`
|
|
63
|
+
// (`fixtures/authored/<moduleId>.d.ts`).
|
|
64
|
+
authored: string;
|
|
65
|
+
moduleId: string;
|
|
66
|
+
namespace: string;
|
|
67
|
+
generated: string;
|
|
68
|
+
apiDoc: string;
|
|
69
|
+
// Defaults to `fidelity/<namespace>.json` when omitted. A fork carries no
|
|
70
|
+
// fidelity artifact — there is no primary source to measure against, and the
|
|
71
|
+
// emit is lossless by construction — but the field mirrors the sibling lanes
|
|
72
|
+
// so a future hand-authored target can record one.
|
|
73
|
+
fidelity: string;
|
|
74
|
+
// SPDX-style license id, surfaced by the docs-site provenance block. Optional
|
|
75
|
+
// in the config; defaults to "".
|
|
76
|
+
license: string;
|
|
77
|
+
// Package-relative paths of the vendored upstream `.lua` sources this target's
|
|
78
|
+
// surface is measured against (`authored-parity.ts`). Optional and defaulted to
|
|
79
|
+
// `[]`: a target with no vendored upstream is simply unmeasured, which is why it
|
|
80
|
+
// must not join `REQUIRED_FIELDS`.
|
|
81
|
+
upstreamLua: string[];
|
|
82
|
+
// Why this target vendors no upstream `.lua`. Mutually exclusive with a
|
|
83
|
+
// non-empty `upstreamLua`, and one of the two is mandatory: neither field is
|
|
84
|
+
// required on its own, but the *disjunction* is, so a target cannot land
|
|
85
|
+
// unexamined and read as measured-and-clean. Absent on a measured target.
|
|
86
|
+
parityVerdict?: ParityVerdict;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface AuthoredTargets {
|
|
90
|
+
targets: AuthoredTarget[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const REQUIRED_FIELDS = [
|
|
94
|
+
"repo",
|
|
95
|
+
"ref",
|
|
96
|
+
"authored",
|
|
97
|
+
"moduleId",
|
|
98
|
+
"namespace",
|
|
99
|
+
"generated",
|
|
100
|
+
"apiDoc",
|
|
101
|
+
] as const;
|
|
102
|
+
|
|
103
|
+
/** Validate the parity declaration: exactly one of a non-empty `upstreamLua` and
|
|
104
|
+
* a well-formed `parityVerdict`. Enforced here rather than through
|
|
105
|
+
* `REQUIRED_FIELDS` because neither field is required on its own — the bijection
|
|
106
|
+
* between them is the gate. */
|
|
107
|
+
function validateParityDeclaration(
|
|
108
|
+
label: string,
|
|
109
|
+
upstreamLua: string[],
|
|
110
|
+
verdict: ParityVerdict | undefined,
|
|
111
|
+
): void {
|
|
112
|
+
if (upstreamLua.length > 0 && verdict !== undefined) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`authored-targets.json: entry ${label} declares both upstreamLua and parityVerdict — a measured target carries no verdict.`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (upstreamLua.length === 0 && verdict === undefined) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`authored-targets.json: entry ${label} declares neither upstreamLua nor parityVerdict — vendor its upstream .lua, or record a parityVerdict saying why it cannot be measured.`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (verdict === undefined) return;
|
|
123
|
+
if (!(PARITY_VERDICT_REASONS as readonly string[]).includes(verdict.reason)) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`authored-targets.json: entry ${label} has parityVerdict reason "${verdict.reason}", which is not one of ${PARITY_VERDICT_REASONS.join(", ")}.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (typeof verdict.note !== "string" || verdict.note.trim() === "") {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`authored-targets.json: entry ${label} has a parityVerdict with an empty note — say what was looked at and why it could not be measured.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Read `authored-targets.json`, validate every required field per entry, and fill
|
|
137
|
+
* optional defaults (`fidelity` → `fidelity/<namespace>.json`, `license` → "",
|
|
138
|
+
* `upstreamLua` → []).
|
|
139
|
+
* Throws on the first missing field naming both the field and the offending entry
|
|
140
|
+
* (its `moduleId`, or its index when `moduleId` itself is absent) — the loud-fail
|
|
141
|
+
* discipline `readMarkdownTargets`/`readScriptApiTargets`/`readLualsTargets` use.
|
|
142
|
+
* Also throws when an entry's parity declaration is missing, doubled, or
|
|
143
|
+
* malformed (see `validateParityDeclaration`).
|
|
144
|
+
*/
|
|
145
|
+
export function readAuthoredTargets(packageRoot: string): AuthoredTarget[] {
|
|
146
|
+
const parsed = JSON.parse(readFileSync(join(packageRoot, "authored-targets.json"), "utf8")) as {
|
|
147
|
+
targets: Partial<AuthoredTarget>[];
|
|
148
|
+
};
|
|
149
|
+
return parsed.targets.map((entry, index) => {
|
|
150
|
+
const label = typeof entry.moduleId === "string" ? entry.moduleId : `index ${index}`;
|
|
151
|
+
for (const field of REQUIRED_FIELDS) {
|
|
152
|
+
if (entry[field] === undefined) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`authored-targets.json: entry ${label} is missing required field "${field}".`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const upstreamLua = entry.upstreamLua ?? [];
|
|
159
|
+
validateParityDeclaration(label, upstreamLua, entry.parityVerdict);
|
|
160
|
+
return {
|
|
161
|
+
repo: entry.repo as string,
|
|
162
|
+
ref: entry.ref as string,
|
|
163
|
+
authored: entry.authored as string,
|
|
164
|
+
moduleId: entry.moduleId as string,
|
|
165
|
+
namespace: entry.namespace as string,
|
|
166
|
+
generated: entry.generated as string,
|
|
167
|
+
apiDoc: entry.apiDoc as string,
|
|
168
|
+
fidelity: entry.fidelity ?? `fidelity/${entry.namespace as string}.json`,
|
|
169
|
+
license: entry.license ?? "",
|
|
170
|
+
upstreamLua,
|
|
171
|
+
...(entry.parityVerdict === undefined ? {} : { parityVerdict: entry.parityVerdict }),
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function readAuthoredSource(packageRoot: string, target: AuthoredTarget): string {
|
|
177
|
+
return readFileSync(join(packageRoot, target.authored), "utf8");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The `generated/<namespace>.d.ts` golden, under whatever stem the entry pins —
|
|
182
|
+
* a bare segment or the dotted `moduleId`. The vendored authored
|
|
183
|
+
* `.d.ts` is already a `declare module '<moduleId>'` ambient, so the emit is the
|
|
184
|
+
* source verbatim — the vendored surface passes through unchanged, which is what
|
|
185
|
+
* makes the emission lossless. It says nothing about how that vendored surface
|
|
186
|
+
* compares to upstream.
|
|
187
|
+
*/
|
|
188
|
+
export function emitAuthoredDeclaration(packageRoot: string, target: AuthoredTarget): string {
|
|
189
|
+
return readAuthoredSource(packageRoot, target);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The pinned upstream members by name, the way `authored-parity.ts` builds them:
|
|
193
|
+
* a later definition of a name wins, matching Lua. Empty for a target that vendors
|
|
194
|
+
* no upstream `.lua`, which is then never parsed. */
|
|
195
|
+
function upstreamMembers(packageRoot: string, target: AuthoredTarget): Map<string, LuaMember> {
|
|
196
|
+
const members = new Map<string, LuaMember>();
|
|
197
|
+
for (const relative of target.upstreamLua) {
|
|
198
|
+
for (const member of parseLuaSurface(readFileSync(join(packageRoot, relative), "utf8")).members)
|
|
199
|
+
members.set(member.name, member);
|
|
200
|
+
}
|
|
201
|
+
return members;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* `extractApiDoc` the vendored `.d.ts` under the pinned publish namespace, plus
|
|
206
|
+
* upstream's own LuaDoc for the members the fork documents nowhere, pretty-printed
|
|
207
|
+
* as the `api-doc/<namespace>.json` golden.
|
|
208
|
+
*
|
|
209
|
+
* The api-doc is therefore the fork *plus* imported upstream prose, and it is the
|
|
210
|
+
* only artifact that is: the emitted `generated/<namespace>.d.ts` stays the vendored
|
|
211
|
+
* source verbatim, so the identity diff still proves the emit is lossless. An
|
|
212
|
+
* imported element carries `docSource: "upstream"` through to the docs-site, which
|
|
213
|
+
* marks it rather than presenting it as first-party. See `import-upstream-docs.ts`
|
|
214
|
+
* for the merge rules.
|
|
215
|
+
*/
|
|
216
|
+
export function lowerAuthoredApiDoc(packageRoot: string, target: AuthoredTarget): string {
|
|
217
|
+
// `extractApiDoc` returns `unknown`; the merge needs the element list, and
|
|
218
|
+
// overwriting the existing `elements` key by spread keeps it in place, so the
|
|
219
|
+
// golden's key order is unchanged.
|
|
220
|
+
const doc = extractApiDoc(readAuthoredSource(packageRoot, target), target.namespace) as Record<
|
|
221
|
+
string,
|
|
222
|
+
unknown
|
|
223
|
+
> & { elements: ApiDocElement[] };
|
|
224
|
+
const elements = importUpstreamDocs(doc.elements, upstreamMembers(packageRoot, target));
|
|
225
|
+
return `${JSON.stringify({ ...doc, elements }, null, 2)}\n`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (import.meta.main) {
|
|
229
|
+
const root = join(import.meta.dir, "..");
|
|
230
|
+
const argv = process.argv.slice(2);
|
|
231
|
+
if (argv.includes("--emit")) {
|
|
232
|
+
for (const target of readAuthoredTargets(root)) {
|
|
233
|
+
const contents = emitAuthoredDeclaration(root, target);
|
|
234
|
+
const dest = join(root, target.generated);
|
|
235
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
236
|
+
writeFileSync(dest, contents);
|
|
237
|
+
console.log(`emitted ${target.moduleId} -> ${target.generated}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (argv.includes("--api-doc")) {
|
|
241
|
+
for (const target of readAuthoredTargets(root)) {
|
|
242
|
+
const json = lowerAuthoredApiDoc(root, target);
|
|
243
|
+
const dest = join(root, target.apiDoc);
|
|
244
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
245
|
+
writeFileSync(dest, json);
|
|
246
|
+
console.log(`lowered ${target.moduleId} -> ${target.apiDoc}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -2,6 +2,11 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
import { extractApiDoc } from "./extract-api-doc";
|
|
5
|
+
import { readAuthoredTargets } from "./sync-authored-types";
|
|
6
|
+
import { readLualsTargets } from "./sync-luals-types";
|
|
7
|
+
import { readMarkdownTargets } from "./sync-markdown-types";
|
|
8
|
+
import { readOpenApiTargets } from "./sync-openapi-types";
|
|
9
|
+
import { readScriptApiTargets } from "./sync-script-api-types";
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* ts-defold/library core-type references -> the @defold-typescript/types surface.
|
|
@@ -330,6 +335,65 @@ export function classifyLibraryDirs(
|
|
|
330
335
|
.sort((a, b) => (a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0));
|
|
331
336
|
}
|
|
332
337
|
|
|
338
|
+
/**
|
|
339
|
+
* The union every maintained-here lane contributes: authored `moduleId`s, and
|
|
340
|
+
* the `namespace`s of the LuaLS, script_api, OpenAPI and markdown front-ends.
|
|
341
|
+
* Namespaces are matched in both forms because the lanes use both — druid-style
|
|
342
|
+
* libraries register a bare `tweener`, while `defold-saver` registers the dotted
|
|
343
|
+
* `saver.saver` / `saver.storage`.
|
|
344
|
+
*/
|
|
345
|
+
export interface MaintainedHereRegistry {
|
|
346
|
+
moduleIds: ReadonlySet<string>;
|
|
347
|
+
namespaces: ReadonlySet<string>;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The subset of `modules` this repo maintains itself rather than sourcing from
|
|
352
|
+
* ts-defold/library. A live `library-targets.json` row always wins, so a module
|
|
353
|
+
* registered on a maintained-here lane *and* still vendored stays
|
|
354
|
+
* ts-defold-sourced and keeps its dir classified `already-vendored`. The
|
|
355
|
+
* registry is empty today, so no committed module takes that branch; it is what
|
|
356
|
+
* a returning row would take.
|
|
357
|
+
*/
|
|
358
|
+
export function maintainedHereModules(
|
|
359
|
+
modules: readonly string[],
|
|
360
|
+
registry: MaintainedHereRegistry,
|
|
361
|
+
liveModules: ReadonlySet<string>,
|
|
362
|
+
): Set<string> {
|
|
363
|
+
return new Set(
|
|
364
|
+
modules.filter(
|
|
365
|
+
(m) =>
|
|
366
|
+
!liveModules.has(m) &&
|
|
367
|
+
(registry.moduleIds.has(m) ||
|
|
368
|
+
registry.namespaces.has(m) ||
|
|
369
|
+
registry.namespaces.has(m.split(".")[0] as string)),
|
|
370
|
+
),
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The upstream dirs this repo has severed onto a maintained-here lane. Severance
|
|
376
|
+
* deletes a library's `library-targets.json` row, but its dir still exists
|
|
377
|
+
* upstream — so a regen would re-add it under whatever its module shape implies
|
|
378
|
+
* unless it is filtered out first. The registries give an exact derivation with
|
|
379
|
+
* no hand-maintained list: a dir is severed iff it has at least one module and
|
|
380
|
+
* every module is maintained here. A dir mixing maintained-here and live modules
|
|
381
|
+
* is still upstream-sourced and stays classified — `nakama-defold` was the last
|
|
382
|
+
* such dir, and severed whole once its live row went.
|
|
383
|
+
*/
|
|
384
|
+
export function severedDirsFromModules(
|
|
385
|
+
modulesByDir: Map<string, string[]>,
|
|
386
|
+
registry: MaintainedHereRegistry,
|
|
387
|
+
liveModules: ReadonlySet<string>,
|
|
388
|
+
): Set<string> {
|
|
389
|
+
const severed = new Set<string>();
|
|
390
|
+
for (const [dir, modules] of modulesByDir) {
|
|
391
|
+
const maintained = maintainedHereModules(modules, registry, liveModules);
|
|
392
|
+
if (modules.length > 0 && maintained.size === modules.length) severed.add(dir);
|
|
393
|
+
}
|
|
394
|
+
return severed;
|
|
395
|
+
}
|
|
396
|
+
|
|
333
397
|
interface GithubTreeResponse {
|
|
334
398
|
tree?: { path: string }[];
|
|
335
399
|
}
|
|
@@ -376,8 +440,28 @@ export type ListTree = (source: LibrarySource) => Promise<string[]>;
|
|
|
376
440
|
const defaultListTree: ListTree = (source) => githubTreePaths(repoSlug(source.repo), source.commit);
|
|
377
441
|
|
|
378
442
|
/**
|
|
379
|
-
*
|
|
380
|
-
*
|
|
443
|
+
* Read every maintained-here registry off the package root and union them.
|
|
444
|
+
* Each reader `readFileSync`s unconditionally and throws on a missing required
|
|
445
|
+
* field — deliberately no absent-file fallback, so a lane added without its
|
|
446
|
+
* fixture fails loudly rather than silently un-severing that lane's dirs.
|
|
447
|
+
*/
|
|
448
|
+
export function readMaintainedHereRegistry(packageRoot: string): MaintainedHereRegistry {
|
|
449
|
+
return {
|
|
450
|
+
moduleIds: new Set(readAuthoredTargets(packageRoot).map((t) => t.moduleId)),
|
|
451
|
+
namespaces: new Set([
|
|
452
|
+
...readLualsTargets(packageRoot).map((t) => t.namespace),
|
|
453
|
+
...readScriptApiTargets(packageRoot).map((t) => t.namespace),
|
|
454
|
+
...readOpenApiTargets(packageRoot).map((t) => t.namespace),
|
|
455
|
+
...readMarkdownTargets(packageRoot).map((t) => t.namespace),
|
|
456
|
+
]),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Enumerate every ts-defold/library dir at the pin, drop the dirs severed onto
|
|
462
|
+
* a maintained-here lane, classify each survivor by its module-name shape, and write
|
|
463
|
+
* `library-classification.json`. Filtering happens here, not in
|
|
464
|
+
* `classifyLibraryDirs`, so that pass stays a pure map-and-sort. The `listTree`
|
|
381
465
|
* seam keeps the pass offline-testable; only the CLI wires the real call, and it
|
|
382
466
|
* stays out of CI (mirrors `--check`). The manifest pins the same `source` as
|
|
383
467
|
* `library-targets.json`.
|
|
@@ -392,9 +476,21 @@ export async function writeClassification(
|
|
|
392
476
|
targets.map((t) => t.path.split("/")[1]).filter((d): d is string => d !== undefined),
|
|
393
477
|
);
|
|
394
478
|
const coveredByGoalDirs = new Set(["defold-lldebugger", "defold-xmath"]);
|
|
479
|
+
const liveModules = new Set(targets.map((t) => t.module));
|
|
480
|
+
|
|
481
|
+
const registry = readMaintainedHereRegistry(packageRoot);
|
|
395
482
|
|
|
396
483
|
const modulesByDir = libraryModulesFromTree(await listTree(source));
|
|
397
|
-
const
|
|
484
|
+
const severedDirs = severedDirsFromModules(modulesByDir, registry, liveModules);
|
|
485
|
+
const dirs = [...modulesByDir]
|
|
486
|
+
// Vendored wins: a stale maintained-here entry can never erase a live ts-defold row.
|
|
487
|
+
.filter(([dir]) => !severedDirs.has(dir) || vendoredDirs.has(dir))
|
|
488
|
+
// A survivor records only its ts-defold-sourced remainder as evidence, so its
|
|
489
|
+
// written shape agrees with its own `modules` array.
|
|
490
|
+
.map(([dir, modules]) => {
|
|
491
|
+
const maintained = maintainedHereModules(modules, registry, liveModules);
|
|
492
|
+
return { dir, modules: modules.filter((m) => !maintained.has(m)) };
|
|
493
|
+
});
|
|
398
494
|
const entries = classifyLibraryDirs(dirs, { vendoredDirs, coveredByGoalDirs });
|
|
399
495
|
writeFileSync(
|
|
400
496
|
join(packageRoot, "library-classification.json"),
|