@defold-typescript/transpiler 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -7,8 +7,11 @@ export type { BuildConfig, ScriptKind, SourceOutputKind } from "./output-paths";
7
7
  export { computeOutputRel, DEFAULT_INCLUDE, parseBuildConfig, SCRIPT_SUFFIX_BY_KIND, stripIncludeBase, } from "./output-paths";
8
8
  export { getProgramDiagnostics } from "./program-diagnostics";
9
9
  export { isDefignoredPath, SCAFFOLDED_DEFIGNORE_LINES } from "./project-resources";
10
+ export { buildSceneAddressDeclaration } from "./scene-address-declaration";
10
11
  export type { SceneComponentIndex } from "./scene-component-index";
11
12
  export { buildSceneComponentIndex } from "./scene-component-index";
13
+ export type { SceneReadHost } from "./scene-documents";
14
+ export { ANIMATION_ASSET_EXTENSIONS, displayPathOf, GAME_PROJECT_DOCUMENT, GUI_EXTENSIONS, INPUT_BINDING_EXTENSIONS, isExcludedProjectPath, listProjectResourcePaths, PROJECT_EXTENSIONS, readSceneDocuments, SCENE_EXTENSIONS, } from "./scene-documents";
12
15
  export type { SceneObjectPathIndex } from "./scene-object-path-index";
13
16
  export { buildSceneObjectPathIndex } from "./scene-object-path-index";
14
17
  export type { SceneMessage } from "./scene-text-format";
package/dist/index.js CHANGED
@@ -876,6 +876,7 @@ function buildSceneComponentIndex(documents) {
876
876
  }
877
877
  return { ids, incomplete };
878
878
  }
879
+
879
880
  // src/scene-object-path-index.ts
880
881
  var LEAF_BLOCKS = ["instances", "embedded_instances"];
881
882
  function resourceKey(resource) {
@@ -971,6 +972,93 @@ function buildSceneObjectPathIndex(documents) {
971
972
  declarers.sort();
972
973
  return { paths, declaredIn, incomplete };
973
974
  }
975
+
976
+ // src/scene-address-declaration.ts
977
+ var BANNER = `// Generated from this project's scenes by \`defold-typescript scene-types\`.
978
+ // Do not edit: every run rewrites it from the .go/.collection sources.
979
+ //
980
+ // Each key is an address the project declares. The aliases in
981
+ // \`@defold-typescript/types\` stay widened with \`(string & {})\`, so these keys
982
+ // only add completions — an address composed at runtime is never rejected.
983
+ `;
984
+ function componentAddressesOf(documents) {
985
+ return [...buildSceneComponentIndex(documents).ids].map((id) => `#${id}`);
986
+ }
987
+ function bodyOf(keys) {
988
+ if (keys.length === 0)
989
+ return "{}";
990
+ const members = keys.map((key) => ` ${JSON.stringify(key)}: true;
991
+ `).sort().join("");
992
+ return `{
993
+ ${members} }`;
994
+ }
995
+ function buildSceneAddressDeclaration(documents) {
996
+ const paths = [...buildSceneObjectPathIndex(documents).paths];
997
+ const components = componentAddressesOf(documents);
998
+ return `${BANNER}
999
+ declare global {
1000
+ interface SceneGameObjectAddresses ${bodyOf(paths)}
1001
+
1002
+ interface SceneComponentAddresses ${bodyOf(components)}
1003
+ }
1004
+
1005
+ export {};
1006
+ `;
1007
+ }
1008
+ // src/scene-documents.ts
1009
+ var SCENE_EXTENSIONS = [".go", ".collection"];
1010
+ var GUI_EXTENSIONS = [".gui"];
1011
+ var INPUT_BINDING_EXTENSIONS = [".input_binding"];
1012
+ var ANIMATION_ASSET_EXTENSIONS = [".atlas", ".tilesource", ".sprite"];
1013
+ var PROJECT_EXTENSIONS = [".project"];
1014
+ var GAME_PROJECT_DOCUMENT = "game.project";
1015
+ function displayPathOf(projectRoot, filePath) {
1016
+ const path2 = filePath.replace(/\\/g, "/");
1017
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
1018
+ return path2.startsWith(`${root}/`) ? path2.slice(root.length + 1) : path2;
1019
+ }
1020
+ function isBuildOutput(displayPath) {
1021
+ return displayPath.split("/").includes("build");
1022
+ }
1023
+ function isExcludedProjectPath(displayPath) {
1024
+ return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
1025
+ }
1026
+ function readSceneDocuments(host, projectRoot, extensions = SCENE_EXTENSIONS) {
1027
+ const documents = new Map;
1028
+ const unreadable = [];
1029
+ const paths = [];
1030
+ if (!host.readDirectory) {
1031
+ unreadable.push("the editor host cannot enumerate project files, so no scene source was read");
1032
+ return { documents, unreadable, paths };
1033
+ }
1034
+ for (const filePath of host.readDirectory(projectRoot, extensions)) {
1035
+ const displayPath = displayPathOf(projectRoot, filePath);
1036
+ if (isExcludedProjectPath(displayPath))
1037
+ continue;
1038
+ const text = host.readFile(filePath);
1039
+ if (text === undefined) {
1040
+ unreadable.push(`${displayPath}: could not be read`);
1041
+ continue;
1042
+ }
1043
+ documents.set(displayPath, text);
1044
+ paths.push(filePath);
1045
+ }
1046
+ return { documents, unreadable, paths };
1047
+ }
1048
+ function listProjectResourcePaths(host, projectRoot, extensions) {
1049
+ if (!host.readDirectory)
1050
+ return new Set;
1051
+ const paths = [];
1052
+ for (const filePath of host.readDirectory(projectRoot, extensions)) {
1053
+ const displayPath = displayPathOf(projectRoot, filePath);
1054
+ if (isExcludedProjectPath(displayPath))
1055
+ continue;
1056
+ if (!extensions.some((extension) => displayPath.endsWith(extension)))
1057
+ continue;
1058
+ paths.push(`/${displayPath}`);
1059
+ }
1060
+ return new Set(paths.sort());
1061
+ }
974
1062
  // src/session.ts
975
1063
  import { readFileSync as readFileSync2 } from "node:fs";
976
1064
  import { createRequire as createRequire2 } from "node:module";
@@ -1807,13 +1895,17 @@ export {
1807
1895
  transpile,
1808
1896
  stripIncludeBase,
1809
1897
  resolveClassifiedSlotAtPosition,
1898
+ readSceneDocuments,
1810
1899
  parseSceneTextFormat,
1811
1900
  parseBuildConfig,
1901
+ listProjectResourcePaths,
1812
1902
  isFragmentCaret,
1903
+ isExcludedProjectPath,
1813
1904
  isDefignoredPath,
1814
1905
  isAddressClass,
1815
1906
  getProgramDiagnostics,
1816
1907
  findMainEntryFactoryImports,
1908
+ displayPathOf,
1817
1909
  createTranspileSession,
1818
1910
  computeOutputRel,
1819
1911
  componentIdOfSameObjectAddress,
@@ -1821,12 +1913,19 @@ export {
1821
1913
  buildSpriteAnimationIndex,
1822
1914
  buildSceneObjectPathIndex,
1823
1915
  buildSceneComponentIndex,
1916
+ buildSceneAddressDeclaration,
1824
1917
  buildInputActionIndex,
1825
1918
  buildGuiNodeIndex,
1826
1919
  buildConfigKeyIndex,
1827
1920
  addressClassOfArgument,
1828
1921
  SceneTextFormatError,
1829
1922
  SCRIPT_SUFFIX_BY_KIND,
1923
+ SCENE_EXTENSIONS,
1830
1924
  SCAFFOLDED_DEFIGNORE_LINES,
1831
- DEFAULT_INCLUDE
1925
+ PROJECT_EXTENSIONS,
1926
+ INPUT_BINDING_EXTENSIONS,
1927
+ GUI_EXTENSIONS,
1928
+ GAME_PROJECT_DOCUMENT,
1929
+ DEFAULT_INCLUDE,
1930
+ ANIMATION_ASSET_EXTENSIONS
1832
1931
  };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The global augmentation that fills `SceneGameObjectAddresses` and
3
+ * `SceneComponentAddresses` from already-read scene sources — keys are
4
+ * project-relative display paths, values are file text.
5
+ *
6
+ * Pure, like the two indexes it composes: the filesystem walk belongs to the
7
+ * caller. Keys are sorted so a project whose scenes did not change re-emits a
8
+ * byte-identical file, and a document map built in a different order emits the
9
+ * same bytes as well; a `Set` iteration order leaking into the output would
10
+ * make every regeneration rewrite the file and re-check the whole program.
11
+ */
12
+ export declare function buildSceneAddressDeclaration(documents: ReadonlyMap<string, string>): string;
@@ -0,0 +1,18 @@
1
+ export interface SceneReadHost {
2
+ readDirectory?(path: string, extensions?: readonly string[]): string[];
3
+ readFile(path: string): string | undefined;
4
+ }
5
+ export declare const SCENE_EXTENSIONS: string[];
6
+ export declare const GUI_EXTENSIONS: string[];
7
+ export declare const INPUT_BINDING_EXTENSIONS: string[];
8
+ export declare const ANIMATION_ASSET_EXTENSIONS: string[];
9
+ export declare const PROJECT_EXTENSIONS: string[];
10
+ export declare const GAME_PROJECT_DOCUMENT = "game.project";
11
+ export declare function displayPathOf(projectRoot: string, filePath: string): string;
12
+ export declare function isExcludedProjectPath(displayPath: string): boolean;
13
+ export declare function readSceneDocuments(host: SceneReadHost, projectRoot: string, extensions?: readonly string[]): {
14
+ documents: Map<string, string>;
15
+ unreadable: string[];
16
+ paths: string[];
17
+ };
18
+ export declare function listProjectResourcePaths(host: SceneReadHost, projectRoot: string, extensions: readonly string[]): Set<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/transpiler",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "test": "bun test"
36
36
  },
37
37
  "dependencies": {
38
- "@defold-typescript/types": "0.29.0",
38
+ "@defold-typescript/types": "0.30.0",
39
39
  "@typescript-to-lua/language-extensions": "1.19.0",
40
40
  "typescript-to-lua": "^1.36.0"
41
41
  }
package/src/index.ts CHANGED
@@ -13,8 +13,22 @@ export {
13
13
  } from "./output-paths";
14
14
  export { getProgramDiagnostics } from "./program-diagnostics";
15
15
  export { isDefignoredPath, SCAFFOLDED_DEFIGNORE_LINES } from "./project-resources";
16
+ export { buildSceneAddressDeclaration } from "./scene-address-declaration";
16
17
  export type { SceneComponentIndex } from "./scene-component-index";
17
18
  export { buildSceneComponentIndex } from "./scene-component-index";
19
+ export type { SceneReadHost } from "./scene-documents";
20
+ export {
21
+ ANIMATION_ASSET_EXTENSIONS,
22
+ displayPathOf,
23
+ GAME_PROJECT_DOCUMENT,
24
+ GUI_EXTENSIONS,
25
+ INPUT_BINDING_EXTENSIONS,
26
+ isExcludedProjectPath,
27
+ listProjectResourcePaths,
28
+ PROJECT_EXTENSIONS,
29
+ readSceneDocuments,
30
+ SCENE_EXTENSIONS,
31
+ } from "./scene-documents";
18
32
  export type { SceneObjectPathIndex } from "./scene-object-path-index";
19
33
  export { buildSceneObjectPathIndex } from "./scene-object-path-index";
20
34
  export type { SceneMessage } from "./scene-text-format";
@@ -0,0 +1,57 @@
1
+ import { buildSceneComponentIndex } from "./scene-component-index";
2
+ import { buildSceneObjectPathIndex } from "./scene-object-path-index";
3
+
4
+ const BANNER = `// Generated from this project's scenes by \`defold-typescript scene-types\`.
5
+ // Do not edit: every run rewrites it from the .go/.collection sources.
6
+ //
7
+ // Each key is an address the project declares. The aliases in
8
+ // \`@defold-typescript/types\` stay widened with \`(string & {})\`, so these keys
9
+ // only add completions — an address composed at runtime is never rejected.
10
+ `;
11
+
12
+ // `buildSceneComponentIndex` reports bare component ids with no owning object,
13
+ // so the same-object form `#id` is the whole address the index can prove. A
14
+ // `/path#id` key would be a cross product of two universes that were never
15
+ // joined: it would claim every object owns every component.
16
+ function componentAddressesOf(documents: ReadonlyMap<string, string>): string[] {
17
+ return [...buildSceneComponentIndex(documents).ids].map((id) => `#${id}`);
18
+ }
19
+
20
+ // An interface with no members closes on the same line: an empty body written
21
+ // across two lines is what a formatter would collapse, and the generated file
22
+ // has to survive a consumer's formatter run byte-identical or every regeneration
23
+ // looks like a change.
24
+ function bodyOf(keys: readonly string[]): string {
25
+ if (keys.length === 0) return "{}";
26
+ const members = keys
27
+ .map((key) => ` ${JSON.stringify(key)}: true;\n`)
28
+ .sort()
29
+ .join("");
30
+ return `{\n${members} }`;
31
+ }
32
+
33
+ /**
34
+ * The global augmentation that fills `SceneGameObjectAddresses` and
35
+ * `SceneComponentAddresses` from already-read scene sources — keys are
36
+ * project-relative display paths, values are file text.
37
+ *
38
+ * Pure, like the two indexes it composes: the filesystem walk belongs to the
39
+ * caller. Keys are sorted so a project whose scenes did not change re-emits a
40
+ * byte-identical file, and a document map built in a different order emits the
41
+ * same bytes as well; a `Set` iteration order leaking into the output would
42
+ * make every regeneration rewrite the file and re-check the whole program.
43
+ */
44
+ export function buildSceneAddressDeclaration(documents: ReadonlyMap<string, string>): string {
45
+ const paths = [...buildSceneObjectPathIndex(documents).paths];
46
+ const components = componentAddressesOf(documents);
47
+
48
+ return `${BANNER}
49
+ declare global {
50
+ interface SceneGameObjectAddresses ${bodyOf(paths)}
51
+
52
+ interface SceneComponentAddresses ${bodyOf(components)}
53
+ }
54
+
55
+ export {};
56
+ `;
57
+ }
@@ -0,0 +1,126 @@
1
+ import { isDefignoredPath } from "./project-resources";
2
+
3
+ // The narrow slice of `ts.server.ServerHost` this needs: the real host
4
+ // satisfies it structurally, and a test fake is two methods rather than a whole
5
+ // server. `readDirectory` is optional because a host may not provide it, and a
6
+ // plugin that throws takes the editor's completions down with it.
7
+ export interface SceneReadHost {
8
+ readDirectory?(path: string, extensions?: readonly string[]): string[];
9
+ readFile(path: string): string | undefined;
10
+ }
11
+
12
+ export const SCENE_EXTENSIONS = [".go", ".collection"];
13
+
14
+ // The extension sets more than one reader asks the host for. They live here
15
+ // rather than beside either caller because the index cache serves exactly the
16
+ // sets it is handed: a second definition that drifted would walk — and watch —
17
+ // a universe the other reader never invalidates.
18
+ export const GUI_EXTENSIONS = [".gui"];
19
+ export const INPUT_BINDING_EXTENSIONS = [".input_binding"];
20
+
21
+ // The documents an animation id is declared by: an atlas or tile source names
22
+ // the ids, and a `.sprite` is the hop a referenced component takes to reach one.
23
+ export const ANIMATION_ASSET_EXTENSIONS = [".atlas", ".tilesource", ".sprite"];
24
+
25
+ // The walk returns every `.project` the project holds, so the lookup is by
26
+ // display path — a vendored `*.project` declares keys this project's readers
27
+ // cannot resolve.
28
+ export const PROJECT_EXTENSIONS = [".project"];
29
+ export const GAME_PROJECT_DOCUMENT = "game.project";
30
+
31
+ // The project-relative name a file is keyed by — the same name the editor
32
+ // reports for a program file, so a scene's claim on a script can be looked up
33
+ // against the file being edited.
34
+ export function displayPathOf(projectRoot: string, filePath: string): string {
35
+ const path = filePath.replace(/\\/g, "/");
36
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
37
+ return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
38
+ }
39
+
40
+ // What both walks exclude is what Defold does not load as a project resource:
41
+ // `build/` output, plus the directories the scaffolded `.defignore` names
42
+ // (`isDefignoredPath`, root-anchored because a `.defignore` line is a
43
+ // root-relative path).
44
+ //
45
+ // Bob writes `_generated_*.go` copies of the project's scenes under `build/`;
46
+ // reading them would report every id twice, against files no author edits. The
47
+ // segment test runs on the project-relative path, so a project that itself
48
+ // lives under a directory named `build` is not excluded wholesale — a different
49
+ // rule from `.defignore`'s, because bob writes those copies below whatever
50
+ // depth it is pointed at.
51
+ function isBuildOutput(displayPath: string): boolean {
52
+ return displayPath.split("/").includes("build");
53
+ }
54
+
55
+ // The single rule, exported because the index cache filters watch events with
56
+ // it: a copy in the watcher could drift from what the walks exclude, and an
57
+ // event the walk would have ignored is exactly the event that must not throw the
58
+ // cache away — bob writes `_generated_*.go` throughout a build.
59
+ export function isExcludedProjectPath(displayPath: string): boolean {
60
+ return isBuildOutput(displayPath) || isDefignoredPath(displayPath);
61
+ }
62
+
63
+ // Read the project's own scene sources through the editor host — by default the
64
+ // `.go`/`.collection` set the component-id universe is built from, or whatever
65
+ // `extensions` names instead. The two universes stay disjoint: a `.gui` declares
66
+ // node ids, not component ids, so folding it into the default would feed gui
67
+ // text to `buildSceneComponentIndex`. Keys are project-relative display paths,
68
+ // matching what `buildSceneComponentIndex` reports in `incomplete`, and every
69
+ // file that could not be read is named in `unreadable` — a universe with a
70
+ // silent hole cannot be reasoned about. `paths` names the host paths actually
71
+ // read — what a watcher must be registered on, which cannot be reconstructed
72
+ // from a display path for a file outside the project root.
73
+ export function readSceneDocuments(
74
+ host: SceneReadHost,
75
+ projectRoot: string,
76
+ extensions: readonly string[] = SCENE_EXTENSIONS,
77
+ ): { documents: Map<string, string>; unreadable: string[]; paths: string[] } {
78
+ const documents = new Map<string, string>();
79
+ const unreadable: string[] = [];
80
+ const paths: string[] = [];
81
+
82
+ if (!host.readDirectory) {
83
+ unreadable.push("the editor host cannot enumerate project files, so no scene source was read");
84
+ return { documents, unreadable, paths };
85
+ }
86
+
87
+ for (const filePath of host.readDirectory(projectRoot, extensions)) {
88
+ const displayPath = displayPathOf(projectRoot, filePath);
89
+ if (isExcludedProjectPath(displayPath)) continue;
90
+ const text = host.readFile(filePath);
91
+ if (text === undefined) {
92
+ unreadable.push(`${displayPath}: could not be read`);
93
+ continue;
94
+ }
95
+ documents.set(displayPath, text);
96
+ paths.push(filePath);
97
+ }
98
+
99
+ return { documents, unreadable, paths };
100
+ }
101
+
102
+ // The project's own files of the given kinds, as the `/`-prefixed
103
+ // project-relative paths a Defold resource property names. Nothing is read: a
104
+ // resource path is the whole suggestion, so the walk is the entire universe.
105
+ //
106
+ // The extension filter is re-applied rather than trusted. `readSceneDocuments`
107
+ // can take the host at its word because a wrong document merely contributes no
108
+ // ids, but here a wrong file *is* the suggestion — a host that ignored its
109
+ // `extensions` argument would turn a `.font` slot into a project-wide file dump.
110
+ export function listProjectResourcePaths(
111
+ host: SceneReadHost,
112
+ projectRoot: string,
113
+ extensions: readonly string[],
114
+ ): Set<string> {
115
+ if (!host.readDirectory) return new Set();
116
+
117
+ const paths: string[] = [];
118
+ for (const filePath of host.readDirectory(projectRoot, extensions)) {
119
+ const displayPath = displayPathOf(projectRoot, filePath);
120
+ if (isExcludedProjectPath(displayPath)) continue;
121
+ if (!extensions.some((extension) => displayPath.endsWith(extension))) continue;
122
+ paths.push(`/${displayPath}`);
123
+ }
124
+
125
+ return new Set(paths.sort());
126
+ }