@defold-typescript/types 0.54.0 → 0.56.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 (31) hide show
  1. package/dist/cli-runtime.js +93 -31
  2. package/dist/library-runtime.js +7176 -0
  3. package/examples/translations.json +4 -4
  4. package/generated/gui.d.ts +1 -1
  5. package/generated/particlefx.d.ts +1 -1
  6. package/generated/versions/defold-1.12.4/gui.d.ts +1 -1
  7. package/generated/versions/defold-1.12.4/model.d.ts +1 -1
  8. package/generated/versions/defold-1.12.4/particlefx.d.ts +1 -1
  9. package/generated/versions/defold-1.12.4/resource.d.ts +4 -4
  10. package/generated/versions/defold-1.13.0/gui.d.ts +1 -1
  11. package/generated/versions/defold-1.13.0/model.d.ts +1 -1
  12. package/generated/versions/defold-1.13.0/particlefx.d.ts +1 -1
  13. package/generated/versions/defold-1.13.0/resource.d.ts +4 -4
  14. package/generated/versions/defold-1.13.1/gui.d.ts +1 -1
  15. package/generated/versions/defold-1.13.1/model.d.ts +1 -1
  16. package/generated/versions/defold-1.13.1/particlefx.d.ts +1 -1
  17. package/generated/versions/defold-1.13.1/resource.d.ts +4 -4
  18. package/generated/versions/defold-1.13.2/gui.d.ts +1 -1
  19. package/generated/versions/defold-1.13.2/particlefx.d.ts +1 -1
  20. package/package.json +8 -2
  21. package/scripts/cli-runtime.ts +6 -1
  22. package/scripts/doc-source.ts +1 -1
  23. package/scripts/extension-goldens.ts +5 -5
  24. package/scripts/generate-api-signatures.ts +1 -1
  25. package/scripts/library-runtime.ts +22 -0
  26. package/scripts/regen.ts +2 -2
  27. package/scripts/sync-api-docs.ts +47 -26
  28. package/src/doc-comment.ts +38 -7
  29. package/src/index-slot-classifications.ts +2 -2
  30. package/src/library-signature.ts +2 -2
  31. package/src/luals-type-expr.ts +1 -1
@@ -795,8 +795,8 @@
795
795
  ],
796
796
  "gui.play_particlefx": [
797
797
  {
798
- "sourceHash": "19a26b6e14ad0997",
799
- "ts": "// How to play a particle fx when a gui node is created.\n// The callback receives the gui node, the hash of the id\n// of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.\nexport default defineGuiScript({\n init(self) {\n gui.play_particlefx(gui.get_node(\"particlefx\"), (self, node, emitter, state) => {\n if (emitter === hash(\"exhaust\") && state === particlefx.EMITTER_STATE_POSTSPAWN) {\n // exhaust is done spawning particles...\n }\n });\n },\n});"
798
+ "sourceHash": "3a4e5cb8e98bf1a0",
799
+ "ts": "// How to play a particle fx when a gui node is created.\n// The callback receives the gui node, the hash of the id\n// of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.\nexport default defineGuiScript({\n init(self) {\n gui.play_particlefx(gui.get_node(\"particlefx\"), (self, node, emitter, state) => {\n if (emitter === hash(\"exhaust\") && state === particlefx.EMITTER_STATE_POSTSPAWN) {\n // exhaust is done spawning particles...\n }\n });\n },\n});"
800
800
  }
801
801
  ],
802
802
  "gui.reset_material": [
@@ -1107,8 +1107,8 @@
1107
1107
  ],
1108
1108
  "particlefx.play": [
1109
1109
  {
1110
- "sourceHash": "01a21997b47ac4fc",
1111
- "ts": "// How to play a particle fx when a game object is created.\n// The callback receives the hash of the path to the particlefx, the hash of the id\n// of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.\nexport default defineScript({\n init(self) {\n particlefx.play(\"#particlefx\", (self, id, emitter, state) => {\n if (emitter === hash(\"exhaust\") && state === particlefx.EMITTER_STATE_POSTSPAWN) {\n // exhaust is done spawning particles...\n }\n });\n },\n});"
1110
+ "sourceHash": "ad5d0aba2677b291",
1111
+ "ts": "// How to play a particle fx when a game object is created.\n// The callback receives the hash of the path to the particlefx, the hash of the id\n// of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.\nexport default defineScript({\n init(self) {\n particlefx.play(\"#particlefx\", (self, id, emitter, state) => {\n if (emitter === hash(\"exhaust\") && state === particlefx.EMITTER_STATE_POSTSPAWN) {\n // exhaust is done spawning particles...\n }\n });\n },\n});"
1112
1112
  }
1113
1113
  ],
1114
1114
  "particlefx.reset_constant": [
@@ -1355,7 +1355,7 @@ declare global {
1355
1355
  * ```ts
1356
1356
  * // How to play a particle fx when a gui node is created.
1357
1357
  * // The callback receives the gui node, the hash of the id
1358
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
1358
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
1359
1359
  * export default defineGuiScript({
1360
1360
  * init(self) {
1361
1361
  * gui.play_particlefx(gui.get_node("particlefx"), (self, node, emitter, state) => {
@@ -45,7 +45,7 @@ declare global {
45
45
  * ```ts
46
46
  * // How to play a particle fx when a game object is created.
47
47
  * // The callback receives the hash of the path to the particlefx, the hash of the id
48
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
48
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
49
49
  * export default defineScript({
50
50
  * init(self) {
51
51
  * particlefx.play("#particlefx", (self, id, emitter, state) => {
@@ -1324,7 +1324,7 @@ declare global {
1324
1324
  * ```ts
1325
1325
  * // How to play a particle fx when a gui node is created.
1326
1326
  * // The callback receives the gui node, the hash of the id
1327
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
1327
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
1328
1328
  * export default defineGuiScript({
1329
1329
  * init(self) {
1330
1330
  * gui.play_particlefx(gui.get_node("particlefx"), (self, node, emitter, state) => {
@@ -53,7 +53,7 @@ declare global {
53
53
  * AABB information return as a table with `min` and `max` fields, where `min` and `max` has type `vmath.vector3`.
54
54
  *
55
55
  * @param url - the model
56
- * @returns A table containing info about all AABB in the format
56
+ * @returns A table containing info about all AABB in the format <hash(mesh_id), aabb_info>
57
57
  * @example
58
58
  * ```ts
59
59
  * model.get_mesh_aabb("#model"); // => { hash("Sword") = { min = vmath.vector3(-0.5, -0.5, 0), max = vmath.vector3(0.5, 0.5, 0) }, hash("Shield") = { min = vmath.vector3(-0.5, -0.5, -0.5), max = vmath.vector3(0.5, 0.5, 0.5) } }
@@ -49,7 +49,7 @@ declare global {
49
49
  * ```ts
50
50
  * // How to play a particle fx when a game object is created.
51
51
  * // The callback receives the hash of the path to the particlefx, the hash of the id
52
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
52
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
53
53
  * export default defineScript({
54
54
  * init(self) {
55
55
  * particlefx.play("#particlefx", (self, id, emitter, state) => {
@@ -102,10 +102,10 @@ declare global {
102
102
  * number the height of the animation
103
103
  *
104
104
  * - `frame_start`
105
- * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. in atlas.
105
+ * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
106
106
  *
107
107
  * - `frame_end`
108
- * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. in atlas.
108
+ * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
109
109
  *
110
110
  * - `playback`
111
111
  * constant optional playback mode of the animation, the default value is go.PLAYBACK_ONCE_FORWARD
@@ -1096,10 +1096,10 @@ declare global {
1096
1096
  * number the height of the animation
1097
1097
  *
1098
1098
  * - `frame_start`
1099
- * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. in atlas.
1099
+ * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
1100
1100
  *
1101
1101
  * - `frame_end`
1102
- * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. in atlas.
1102
+ * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
1103
1103
  *
1104
1104
  * - `playback`
1105
1105
  * constant optional playback mode of the animation, the default value is go.PLAYBACK_ONCE_FORWARD
@@ -1322,7 +1322,7 @@ declare global {
1322
1322
  * ```ts
1323
1323
  * // How to play a particle fx when a gui node is created.
1324
1324
  * // The callback receives the gui node, the hash of the id
1325
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
1325
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
1326
1326
  * export default defineGuiScript({
1327
1327
  * init(self) {
1328
1328
  * gui.play_particlefx(gui.get_node("particlefx"), (self, node, emitter, state) => {
@@ -72,7 +72,7 @@ declare global {
72
72
  * AABB information return as a table with `min` and `max` fields, where `min` and `max` has type `vmath.vector3`.
73
73
  *
74
74
  * @param url - the model
75
- * @returns A table containing info about all AABB in the format
75
+ * @returns A table containing info about all AABB in the format <hash(mesh_id), aabb_info>
76
76
  * @example
77
77
  * ```ts
78
78
  * model.get_mesh_aabb("#model"); // => { hash("Sword") = { min = vmath.vector3(-0.5, -0.5, 0), max = vmath.vector3(0.5, 0.5, 0) }, hash("Shield") = { min = vmath.vector3(-0.5, -0.5, -0.5), max = vmath.vector3(0.5, 0.5, 0.5) } }
@@ -49,7 +49,7 @@ declare global {
49
49
  * ```ts
50
50
  * // How to play a particle fx when a game object is created.
51
51
  * // The callback receives the hash of the path to the particlefx, the hash of the id
52
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
52
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
53
53
  * export default defineScript({
54
54
  * init(self) {
55
55
  * particlefx.play("#particlefx", (self, id, emitter, state) => {
@@ -102,10 +102,10 @@ declare global {
102
102
  * number the height of the animation
103
103
  *
104
104
  * - `frame_start`
105
- * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. in atlas.
105
+ * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
106
106
  *
107
107
  * - `frame_end`
108
- * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. in atlas.
108
+ * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
109
109
  *
110
110
  * - `playback`
111
111
  * constant optional playback mode of the animation, the default value is go.PLAYBACK_ONCE_FORWARD
@@ -1100,10 +1100,10 @@ declare global {
1100
1100
  * number the height of the animation
1101
1101
  *
1102
1102
  * - `frame_start`
1103
- * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. in atlas.
1103
+ * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
1104
1104
  *
1105
1105
  * - `frame_end`
1106
- * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. in atlas.
1106
+ * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
1107
1107
  *
1108
1108
  * - `playback`
1109
1109
  * constant optional playback mode of the animation, the default value is go.PLAYBACK_ONCE_FORWARD
@@ -1322,7 +1322,7 @@ declare global {
1322
1322
  * ```ts
1323
1323
  * // How to play a particle fx when a gui node is created.
1324
1324
  * // The callback receives the gui node, the hash of the id
1325
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
1325
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
1326
1326
  * export default defineGuiScript({
1327
1327
  * init(self) {
1328
1328
  * gui.play_particlefx(gui.get_node("particlefx"), (self, node, emitter, state) => {
@@ -72,7 +72,7 @@ declare global {
72
72
  * AABB information return as a table with `min` and `max` fields, where `min` and `max` has type `vmath.vector3`.
73
73
  *
74
74
  * @param url - the model
75
- * @returns A table containing info about all AABB in the format
75
+ * @returns A table containing info about all AABB in the format <hash(mesh_id), aabb_info>
76
76
  * @example
77
77
  * ```ts
78
78
  * model.get_mesh_aabb("#model"); // => { hash("Sword") = { min = vmath.vector3(-0.5, -0.5, 0), max = vmath.vector3(0.5, 0.5, 0) }, hash("Shield") = { min = vmath.vector3(-0.5, -0.5, -0.5), max = vmath.vector3(0.5, 0.5, 0.5) } }
@@ -49,7 +49,7 @@ declare global {
49
49
  * ```ts
50
50
  * // How to play a particle fx when a game object is created.
51
51
  * // The callback receives the hash of the path to the particlefx, the hash of the id
52
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
52
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
53
53
  * export default defineScript({
54
54
  * init(self) {
55
55
  * particlefx.play("#particlefx", (self, id, emitter, state) => {
@@ -102,10 +102,10 @@ declare global {
102
102
  * number the height of the animation
103
103
  *
104
104
  * - `frame_start`
105
- * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. in atlas.
105
+ * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
106
106
  *
107
107
  * - `frame_end`
108
- * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. in atlas.
108
+ * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
109
109
  *
110
110
  * - `playback`
111
111
  * constant optional playback mode of the animation, the default value is go.PLAYBACK_ONCE_FORWARD
@@ -1100,10 +1100,10 @@ declare global {
1100
1100
  * number the height of the animation
1101
1101
  *
1102
1102
  * - `frame_start`
1103
- * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. in atlas.
1103
+ * number index to the first geometry of the animation. Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
1104
1104
  *
1105
1105
  * - `frame_end`
1106
- * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. in atlas.
1106
+ * number index to the last geometry of the animation (non-inclusive). Indices are lua based and must be in the range of 1 .. <number-of-geometries> in atlas.
1107
1107
  *
1108
1108
  * - `playback`
1109
1109
  * constant optional playback mode of the animation, the default value is go.PLAYBACK_ONCE_FORWARD
@@ -1355,7 +1355,7 @@ declare global {
1355
1355
  * ```ts
1356
1356
  * // How to play a particle fx when a gui node is created.
1357
1357
  * // The callback receives the gui node, the hash of the id
1358
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
1358
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
1359
1359
  * export default defineGuiScript({
1360
1360
  * init(self) {
1361
1361
  * gui.play_particlefx(gui.get_node("particlefx"), (self, node, emitter, state) => {
@@ -45,7 +45,7 @@ declare global {
45
45
  * ```ts
46
46
  * // How to play a particle fx when a game object is created.
47
47
  * // The callback receives the hash of the path to the particlefx, the hash of the id
48
- * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_.
48
+ * // of the emitter, and the new state of the emitter as particlefx.EMITTER_STATE_<STATE>.
49
49
  * export default defineScript({
50
50
  * init(self) {
51
51
  * particlefx.play("#particlefx", (self, id, emitter, state) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/types",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "TypeScript types for the Defold engine's Lua APIs.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,6 +62,11 @@
62
62
  "bun": "./scripts/cli-runtime.ts",
63
63
  "default": "./dist/cli-runtime.js"
64
64
  },
65
+ "./library-runtime": {
66
+ "types": "./scripts/library-runtime.ts",
67
+ "bun": "./scripts/library-runtime.ts",
68
+ "default": "./dist/library-runtime.js"
69
+ },
65
70
  "./api-availability.json": "./api-availability.json",
66
71
  "./api-signatures.json": "./api-signatures.json",
67
72
  "./url-parameters.json": "./url-parameters.json",
@@ -80,6 +85,7 @@
80
85
  "url-parameters.json",
81
86
  "index.d.ts",
82
87
  "dist/cli-runtime.js",
88
+ "dist/library-runtime.js",
83
89
  "fixtures/messages_doc.json",
84
90
  "fixtures/defold-*/**",
85
91
  "!fixtures/defold-*/engine-bindings/**",
@@ -118,7 +124,7 @@
118
124
  },
119
125
  "scripts": {
120
126
  "build": "tsc -p tsconfig.build.json && bun run build:cli-runtime",
121
- "build:cli-runtime": "bun build scripts/cli-runtime.ts --target=node --format=esm --packages=external --outdir=dist",
127
+ "build:cli-runtime": "bun build scripts/cli-runtime.ts scripts/library-runtime.ts --target=node --format=esm --packages=external --outdir=dist",
122
128
  "typecheck": "tsc -p tsconfig.json --noEmit",
123
129
  "regen": "bun scripts/regen.ts",
124
130
  "generate-api-availability": "bun scripts/generate-api-availability.ts",
@@ -15,4 +15,9 @@ export {
15
15
  targetKindManifest,
16
16
  } from "./materialize-version";
17
17
  export { generateGlobalTableDeclaration, generateModuleDeclaration } from "./regen";
18
- export { readZip, scriptApiToFixtureJson, type ZipAccessor } from "./sync-api-docs";
18
+ export {
19
+ readZip,
20
+ scriptApiToFixtureJson,
21
+ type ZipAccessor,
22
+ type ZipArchive,
23
+ } from "./sync-api-docs";
@@ -138,7 +138,7 @@ export async function resolveRefDoc(opts: {
138
138
  channel?: RefDocChannel;
139
139
  download?: DownloadRefDoc;
140
140
  locate?: LocateLocalRefDoc;
141
- readZip?: typeof readZip;
141
+ readZip?: (path: string) => ZipAccessor;
142
142
  fetchChannelInfo?: FetchChannelInfo;
143
143
  }): Promise<ResolvedRefDoc> {
144
144
  const open = opts.readZip ?? readZip;
@@ -13,11 +13,11 @@ export const EXTENSION_GOLDENS_DIR = "extension-goldens";
13
13
  const PACKAGE_ROOT = resolve(import.meta.dirname, "..");
14
14
  const EXTENSION_GOLDEN_NAMESPACES = ["iac", "iap", "push", "webview"] as const;
15
15
 
16
- // Each golden's doc is the conversion of the extension's `.script_api` at its
17
- // `packages/library-types/defold-extensions.json` pin, the same JSON the
18
- // Libraries pages render, so a golden carries the namespace's constants, optional
19
- // slots and callback arguments exactly as `resolve` would emit them for a project.
20
- const EXTENSION_DOCS_DIR = resolve(PACKAGE_ROOT, "../library-types/defold-extensions/api-doc");
16
+ // Each golden's doc is a copy of the conversion of the extension's `.script_api`
17
+ // at its `defold-extensions.json` pin, the same JSON the Libraries pages render,
18
+ // so a golden carries the namespace's constants, optional slots and callback
19
+ // arguments exactly as `resolve` would emit them for a project.
20
+ const EXTENSION_DOCS_DIR = resolve(PACKAGE_ROOT, "fixtures/extension-docs");
21
21
 
22
22
  export const EXTENSION_GOLDEN_MANIFEST: readonly ModuleManifestEntry[] =
23
23
  EXTENSION_GOLDEN_NAMESPACES.map((namespace) => ({
@@ -23,7 +23,7 @@ export interface SignaturesArtifact {
23
23
  readonly versions: Record<string, Record<string, string>>;
24
24
  // The same axis and keys, carrying each documented slot's rendered type. Kept
25
25
  // parallel rather than folded into `versions` because three consumers
26
- // (`api-surface-loader`, `combined-surface`, `version-window`) type that map's
26
+ // (`toolchain-surface-loader`, `combined-surface`, `version-window`) type that map's
27
27
  // value as a string; a symbol documenting no slot is absent here.
28
28
  readonly slotTypes: Record<string, Record<string, SlotTypes>>;
29
29
  }
@@ -0,0 +1,22 @@
1
+ // The emitter helpers the CLI's library printer imports statically, published as
2
+ // `@defold-typescript/types/library-runtime` and resolved the way `cli-runtime`
3
+ // is: Bun's `bun` condition to this source, Node's `default` to the bundle built
4
+ // beside it (`dist/library-runtime.js`). It stays apart from `cli-runtime`, whose
5
+ // `regen.ts` reads target registries at load: a static import lands at the top of
6
+ // the CLI bundle, so everything here must be safe to load on every command.
7
+
8
+ export { renderDocComment } from "../src/doc-comment";
9
+ export { isKnownDefoldTypeToken, TS_IDENTIFIER, TS_RESERVED_NAMES } from "../src/emit-dts";
10
+ export {
11
+ appendNotes,
12
+ libraryIndexBaseNotes,
13
+ parseLibraryIndexSlotClassifications,
14
+ } from "../src/index-slot-classifications";
15
+ export { luaMultiReturn, varargElementType } from "../src/library-signature";
16
+ export {
17
+ LUALS_VARARG_TOKEN,
18
+ mapLualsCallSignatureExpression,
19
+ mapLualsExpression,
20
+ matchBracket,
21
+ splitTopLevel,
22
+ } from "../src/luals-type-expr";
package/scripts/regen.ts CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  import { loadTranslations } from "./example-store-io";
44
44
  import { synthesizeProseConstants } from "./prose-constants";
45
45
  import { restoreSharedHandleMethods } from "./shared-handle-methods";
46
- import { groupDocsByNamespace, mergeApiDocs, type readZip } from "./sync-api-docs";
46
+ import { groupDocsByNamespace, mergeApiDocs, type ZipAccessor } from "./sync-api-docs";
47
47
  import { compareVersions, versionOf } from "./target-version";
48
48
 
49
49
  export interface ApiTargetModule {
@@ -184,7 +184,7 @@ export interface ModuleManifestEntry {
184
184
  export interface ResolveTargetOptions {
185
185
  readonly cacheDir?: string;
186
186
  readonly download?: DownloadRefDoc;
187
- readonly readZip?: typeof readZip;
187
+ readonly readZip?: (path: string) => ZipAccessor;
188
188
  readonly packageRoot?: string;
189
189
  readonly channel?: RefDocChannel;
190
190
  readonly fetchChannelInfo?: FetchChannelInfo;
@@ -273,6 +273,17 @@ export interface ZipAccessor {
273
273
  entries(): string[];
274
274
  }
275
275
 
276
+ // What `readZip` returns beyond the ref-doc seam, for the extension-archive
277
+ // resolver, which pins a dependency by what this exposes.
278
+ export interface ZipArchive extends ZipAccessor {
279
+ // An entry's bytes as stored, for a file that is not UTF-8 text or whose
280
+ // exact content is hashed.
281
+ bytes(entry: string): Uint8Array;
282
+ // The archive comment, `""` when there is none. A GitHub archive carries the
283
+ // commit it was packed from here, as `git archive` writes it.
284
+ comment(): string;
285
+ }
286
+
276
287
  const ZIP_LOCAL_HEADER_SIGNATURE = 0x04034b50;
277
288
  const ZIP_CENTRAL_HEADER_SIGNATURE = 0x02014b50;
278
289
  const ZIP_END_RECORD_SIGNATURE = 0x06054b50;
@@ -349,8 +360,12 @@ function readCentralDirectory(
349
360
  // A comment may itself contain an end record, so a candidate counts only when
350
361
  // its own comment length reaches exactly to the end of the archive and it
351
362
  // describes a central directory ending at it. When no candidate does, the
352
- // nearest one's own defect is reported.
353
- function readZipDirectory(bytes: Buffer, path: string): Map<string, ZipDirectoryEntry> {
363
+ // nearest one's own defect is reported. The comment is everything after the
364
+ // record that counted.
365
+ function readZipDirectory(
366
+ bytes: Buffer,
367
+ path: string,
368
+ ): { entries: Map<string, ZipDirectoryEntry>; comment: string } {
354
369
  const earliest = Math.max(0, bytes.length - ZIP_END_RECORD_SIZE - ZIP_MAX_COMMENT_SIZE);
355
370
  let nearestDefect: Error | undefined;
356
371
  for (let at = bytes.length - ZIP_END_RECORD_SIZE; at >= earliest; at--) {
@@ -360,7 +375,10 @@ function readZipDirectory(bytes: Buffer, path: string): Map<string, ZipDirectory
360
375
  ) {
361
376
  const directory = readCentralDirectory(bytes, at, path);
362
377
  if (!(directory instanceof Error)) {
363
- return directory;
378
+ return {
379
+ entries: directory,
380
+ comment: bytes.toString("utf8", at + ZIP_END_RECORD_SIZE, bytes.length),
381
+ };
364
382
  }
365
383
  nearestDefect ??= directory;
366
384
  }
@@ -374,33 +392,36 @@ function readZipDirectory(bytes: Buffer, path: string): Map<string, ZipDirectory
374
392
  // Parses the archive itself rather than shelling out to `unzip`, which a stock
375
393
  // Windows machine does not have: the CLI opens extension archives through this
376
394
  // under Node and Bun on every OS.
377
- export function readZip(path: string): ZipAccessor {
395
+ export function readZip(path: string): ZipArchive {
378
396
  const bytes = readFileSync(path);
379
- const entries = readZipDirectory(bytes, path);
397
+ const { entries, comment } = readZipDirectory(bytes, path);
398
+ const entryBytes = (name: string): Buffer => {
399
+ const entry = entries.get(name);
400
+ if (entry === undefined) {
401
+ throw new Error(`no entry ${name} in ${path}`);
402
+ }
403
+ const { localHeaderOffset, compressedSize, method } = entry;
404
+ if (bytes.readUInt32LE(localHeaderOffset) !== ZIP_LOCAL_HEADER_SIGNATURE) {
405
+ throw new Error(`${path}: local header of ${name} has no signature`);
406
+ }
407
+ const nameLength = bytes.readUInt16LE(localHeaderOffset + 26);
408
+ const extraLength = bytes.readUInt16LE(localHeaderOffset + 28);
409
+ const start = localHeaderOffset + 30 + nameLength + extraLength;
410
+ const data = bytes.subarray(start, start + compressedSize);
411
+ if (method === ZIP_METHOD_STORED) {
412
+ return data;
413
+ }
414
+ if (method === ZIP_METHOD_DEFLATE) {
415
+ return inflateRawSync(data);
416
+ }
417
+ throw new Error(`${path}: entry ${name} uses unsupported compression method ${method}`);
418
+ };
380
419
  return {
381
420
  has: (name) => entries.has(name),
382
421
  entries: () => [...entries.keys()],
383
- read: (name) => {
384
- const entry = entries.get(name);
385
- if (entry === undefined) {
386
- throw new Error(`no entry ${name} in ${path}`);
387
- }
388
- const { localHeaderOffset, compressedSize, method } = entry;
389
- if (bytes.readUInt32LE(localHeaderOffset) !== ZIP_LOCAL_HEADER_SIGNATURE) {
390
- throw new Error(`${path}: local header of ${name} has no signature`);
391
- }
392
- const nameLength = bytes.readUInt16LE(localHeaderOffset + 26);
393
- const extraLength = bytes.readUInt16LE(localHeaderOffset + 28);
394
- const start = localHeaderOffset + 30 + nameLength + extraLength;
395
- const data = bytes.subarray(start, start + compressedSize);
396
- if (method === ZIP_METHOD_STORED) {
397
- return data.toString("utf8");
398
- }
399
- if (method === ZIP_METHOD_DEFLATE) {
400
- return inflateRawSync(data).toString("utf8");
401
- }
402
- throw new Error(`${path}: entry ${name} uses unsupported compression method ${method}`);
403
- },
422
+ read: (name) => entryBytes(name).toString("utf8"),
423
+ bytes: (name) => new Uint8Array(entryBytes(name)),
424
+ comment: () => comment,
404
425
  };
405
426
  }
406
427
 
@@ -87,6 +87,37 @@ function weldStrandedListMarkers(text: string): string {
87
87
  return out.join("\n").trim();
88
88
  }
89
89
 
90
+ // What counts as markup in upstream prose: the tags the docs use, plus any tag
91
+ // assigning an attribute. Every other `<` is text, as in `Values <= 0` or
92
+ // `<logger_name>.log`. A listed name must close straight away, because a
93
+ // comparison such as `a <b and c> d` reads the same as a tag with bare
94
+ // attributes. `object` stays off the list: `wrap` documents `<object>` as text.
95
+ // `lia` and `super` are upstream typos. The toolchain docs site renders this
96
+ // output with `html: true`, so script-bearing elements go whatever follows their
97
+ // name, and a quoted attribute value may hold a `<` a browser reads as part of
98
+ // the tag.
99
+ const MARKUP_TAG = new RegExp(
100
+ [
101
+ "</?(?:a|b|blockquote|br|code|dd|div|dl|dt|em|h[1-6]|hr|i|iframe|img|kbd|li|lia|ol|p|pre|s|script|small|span|strong|style|sub|sup|super|table|tbody|td|tfoot|th|thead|tr|u|ul)\\s*/?>",
102
+ "</?(?:script|style|iframe)\\b[^<>]*>",
103
+ "</?[a-z][^<>=]*=(?:[^<>\"']|\"[^\"]*\"|'[^']*')*>",
104
+ "</?[a-z][^<>=]*=[^<>]*>",
105
+ ].join("|"),
106
+ "gi",
107
+ );
108
+
109
+ // Removing a tag can join the text around it into a new one (`<scr<b>ipt>`), so
110
+ // this repeats until nothing changes.
111
+ function stripMarkup(html: string): string {
112
+ let text = html;
113
+ let previous: string;
114
+ do {
115
+ previous = text;
116
+ text = text.replace(MARKUP_TAG, "");
117
+ } while (text !== previous);
118
+ return text;
119
+ }
120
+
90
121
  // Upstream Markdown indented one level too deep is highlighted as code while
91
122
  // still reading as Markdown: every line opens on a backtick-quoted name, as in
92
123
  // the field table `graphics.get_adapter_info` documents under `limits`. That
@@ -156,10 +187,9 @@ export function htmlToDocText(html: string): string {
156
187
  return close ? "" : `\n${LIST_INDENT.repeat(Math.max(0, listDepth - 1))}- `;
157
188
  })
158
189
  .replace(/<br\s*\/?>/gi, "\n")
159
- .replace(/<\/p>/gi, "\n\n")
160
- .replace(/<[^>]+>/g, "");
190
+ .replace(/<\/p>/gi, "\n\n");
161
191
 
162
- text = decodeEntities(text);
192
+ text = decodeEntities(stripMarkup(text));
163
193
 
164
194
  // Collapse horizontal whitespace runs, trim around newlines, fold runaway
165
195
  // blank runs to one blank line, then trim the whole string — preserving
@@ -195,8 +225,8 @@ export function htmlToDocText(html: string): string {
195
225
  * runs of blank lines to one. Returns `""` for empty / whitespace-only input.
196
226
  */
197
227
  export function htmlToCodeText(html: string): string {
198
- let text = html.replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "");
199
- text = decodeEntities(text);
228
+ const stripped = stripMarkup(html.replace(/<br\s*\/?>/gi, "\n"));
229
+ const text = decodeEntities(stripped);
200
230
 
201
231
  const lines = text.split("\n").map((line) => line.replace(/[ \t]+$/g, ""));
202
232
  while (lines.length > 0 && lines[0] === "") lines.shift();
@@ -479,6 +509,7 @@ export function renderDocComment(parts: DocCommentParts): string[] {
479
509
  lines.push(" * ```");
480
510
  }
481
511
 
482
- lines.push(" */");
483
- return lines;
512
+ // Doc text holding `*/` would end the comment and leak the rest into the
513
+ // declaration.
514
+ return [...lines.map((line) => line.replaceAll("*/", "*\\/")), " */"];
484
515
  }
@@ -568,8 +568,8 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
568
568
  // multi-value return as a field of that return (`cast:return::tile_x`). A
569
569
  // typedef method is keyed by its bare name, so one key covers every class of the
570
570
  // page that declares that method.
571
- // `docs-site/app/lib/library-index-slot-gate.test.ts` reds on a scanned slot
572
- // missing from `library-types/index-slot-classifications.json` and on an entry
571
+ // The library docs site's `site/app/lib/library-index-slot-gate.test.ts` reds on a scanned slot
572
+ // missing from the library data folder's `index-slot-classifications.json` and on an entry
573
573
  // no library page declares.
574
574
  export function parseLibraryIndexSlotClassifications(
575
575
  json: unknown,
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Pure string helpers for rendering variadic parameters and multi-value returns
3
3
  * the way the library `.d.ts` emitter does. Shared by the emitter
4
- * (`packages/library-types`) and the docs-site renderer (`packages/docs-site`),
4
+ * (`packages/cli/src/library`) and the docs-site renderer (`packages/docs-site`),
5
5
  * which both depend on `@defold-typescript/types`, so a library `/api` signature
6
- * cannot drift from the shipped `generated/<ns>.d.ts` for these two shapes — the
6
+ * cannot drift from the printed library declaration for these two shapes — the
7
7
  * same anti-drift move the type/name/generic primitives already use.
8
8
  */
9
9
 
@@ -3,7 +3,7 @@
3
3
  * grouping parentheses, `fun(...)`, `table<K, V>`, inline records, string, numeric
4
4
  * and boolean literals), shared by the engine lane (`emit-dts.ts`
5
5
  * `defaultMapType`) and the library lane
6
- * (`packages/library-types/scripts/map-luals-types.ts`). Pure and deterministic.
6
+ * (`packages/cli/src/library/map-luals-types.ts`). Pure and deterministic.
7
7
  *
8
8
  * Every name that is not structure — `integer`, `table`, `function`, `...`,
9
9
  * `vmath.vector3`, `druid.component` — is a leaf handed to the caller's