@godot-scene-web/project 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { t as GodotProjectResolvedResource } from "./shared-Dgi2oc_H.js";
2
+ import { GodotExtResource, GodotNode, GodotResource, GodotResourceRefValue, GodotSceneState, GodotVariant } from "@godot-scene-web/core";
3
+
4
+ //#region src/theme-query.d.ts
5
+ /**
6
+ * Resolve a single theme item for a node from a parsed Theme resource document,
7
+ * following Godot's lookup cascade: the node's `theme_type_variation` (then that
8
+ * variation's `base_type`), then the node's own type, then the theme-wide default.
9
+ *
10
+ * A parsed Theme `.tres` flattens its entries to keys like `default_font_size`,
11
+ * `Label/colors/font_color`, `RichTextLabel/font_sizes/normal_font_size`, and
12
+ * `<Variation>/base_type` — so the lookup is plain property access by composed key.
13
+ *
14
+ * v1 covers FONT SIZES only — the gap that left themed labels (e.g. a RichTextLabel whose
15
+ * size lives in its theme's `default_font_size`, not an inline `theme_override`) rendering
16
+ * at the renderer's 1em fallback. Every other item kind returns `undefined`, so colors,
17
+ * fonts and constants keep their current behavior (inline overrides + renderer defaults).
18
+ */
19
+ declare function queryTheme(themeDoc: GodotResource, nodeType: string | undefined, themeTypeVariation: string | undefined, name: string): GodotVariant | undefined;
20
+ //#endregion
21
+ //#region src/node.d.ts
22
+ interface GodotProjectResolverOptions {
23
+ projectRoot: string;
24
+ assetBaseUrl?: string | ((resourcePath: string) => string);
25
+ }
26
+ interface GodotProjectSceneOptions {
27
+ resolveResource: (ref: GodotResourceRefValue, node: GodotNode) => GodotProjectResolvedResource | undefined;
28
+ resolveResourcePath: (path: string, node: GodotNode) => GodotProjectResolvedResource | undefined;
29
+ mountExternalScene: (ref: GodotResourceRefValue, node: GodotNode) => GodotSceneState | undefined;
30
+ /**
31
+ * Resolve a theme item (e.g. a font size) for a node from its assigned `theme`
32
+ * resource (type-variation -> type -> default cascade). Optional: absent ⇒ the
33
+ * renderer keeps its built-in fallbacks.
34
+ */
35
+ resolveTheme?: (node: GodotNode, name: string) => GodotVariant | undefined;
36
+ }
37
+ interface GodotProjectResolver {
38
+ projectRoot: string;
39
+ resourcePathToFilePath: (resourcePath: string) => string;
40
+ resourcePathToUrl: (resourcePath: string) => string;
41
+ loadScene: (resourcePath: string) => GodotSceneState;
42
+ loadResource: (resourcePath: string) => GodotResource;
43
+ extResource: (scene: GodotSceneState, ref: GodotResourceRefValue) => GodotExtResource | undefined;
44
+ resolveResource: (scene: GodotSceneState, ref: GodotResourceRefValue, node?: GodotNode) => GodotProjectResolvedResource | undefined;
45
+ mountExternalScene: (scene: GodotSceneState, ref: GodotResourceRefValue, node?: GodotNode) => GodotSceneState | undefined;
46
+ sceneOptions: (scene: GodotSceneState) => GodotProjectSceneOptions;
47
+ }
48
+ declare function createGodotProjectResolver(options: GodotProjectResolverOptions): GodotProjectResolver;
49
+ declare function extResourcePath(scene: GodotSceneState, ref: GodotResourceRefValue): string | undefined;
50
+ declare function extResourceRef(value: unknown): GodotResourceRefValue | undefined;
51
+ //#endregion
52
+ export { type GodotProjectResolvedResource, GodotProjectResolver, GodotProjectResolverOptions, GodotProjectSceneOptions, createGodotProjectResolver, extResourcePath, extResourceRef, queryTheme };
53
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","names":[],"sources":["../src/theme-query.ts","../src/node.ts"],"mappings":";;;;;;;AAgCA;;;;;;;;;;;iBAAgB,UAAA,CACd,QAAA,EAAU,aAAA,EACV,QAAA,sBACA,kBAAA,sBACA,IAAA,WACC,YAAY;;;UCRE,2BAAA;EACf,WAAA;EACA,YAAA,cAA0B,YAAA;AAAA;AAAA,UAGX,wBAAA;EACf,eAAA,GACE,GAAA,EAAK,qBAAA,EACL,IAAA,EAAM,SAAA,KACH,4BAAA;EACL,mBAAA,GACE,IAAA,UACA,IAAA,EAAM,SAAA,KACH,4BAAA;EACL,kBAAA,GACE,GAAA,EAAK,qBAAA,EACL,IAAA,EAAM,SAAA,KACH,eAAA;EDVL;;;AACa;;ECeb,YAAA,IAAgB,IAAA,EAAM,SAAA,EAAW,IAAA,aAAiB,YAAA;AAAA;AAAA,UAGnC,oBAAA;EACf,WAAA;EACA,sBAAA,GAAyB,YAAA;EACzB,iBAAA,GAAoB,YAAA;EACpB,SAAA,GAAY,YAAA,aAAyB,eAAA;EACrC,YAAA,GAAe,YAAA,aAAyB,aAAA;EACxC,WAAA,GACE,KAAA,EAAO,eAAA,EACP,GAAA,EAAK,qBAAA,KACF,gBAAA;EACL,eAAA,GACE,KAAA,EAAO,eAAA,EACP,GAAA,EAAK,qBAAA,EACL,IAAA,GAAO,SAAA,KACJ,4BAAA;EACL,kBAAA,GACE,KAAA,EAAO,eAAA,EACP,GAAA,EAAK,qBAAA,EACL,IAAA,GAAO,SAAA,KACJ,eAAA;EACL,YAAA,GAAe,KAAA,EAAO,eAAA,KAAoB,wBAAA;AAAA;AAAA,iBAG5B,0BAAA,CACd,OAAA,EAAS,2BAAA,GACR,oBAAoB;AAAA,iBAsfP,eAAA,CACd,KAAA,EAAO,eAAA,EACP,GAAA,EAAK,qBAAqB;AAAA,iBAWZ,cAAA,CACd,KAAA,YACC,qBAAqB"}
package/dist/node.js ADDED
@@ -0,0 +1,316 @@
1
+ import { t as queryTheme } from "./theme-query-ClAPxgZ7.js";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { asBoolean, asNumber, asRect2, asResourceRef, asString } from "@godot-scene-web/core";
5
+ import { SOURCE_SCENE_PATH_ATTRIBUTE, tagSceneNodes } from "@godot-scene-web/scene-graph";
6
+ import { parseGodotResource, parseGodotTextScene } from "@godot-scene-web/tscn-parser";
7
+ //#region src/node.ts
8
+ function createGodotProjectResolver(options) {
9
+ const projectRoot = resolve(options.projectRoot);
10
+ const sceneCache = /* @__PURE__ */ new Map();
11
+ const resourceCache = /* @__PURE__ */ new Map();
12
+ const imageSizeCache = /* @__PURE__ */ new Map();
13
+ const resourcePathToFilePath = (resourcePath) => {
14
+ assertResourcePath(resourcePath);
15
+ return resolve(projectRoot, resourcePath.replace(/^res:\/\//, ""));
16
+ };
17
+ const resourcePathToUrl = (resourcePath) => {
18
+ assertResourcePath(resourcePath);
19
+ if (typeof options.assetBaseUrl === "function") return options.assetBaseUrl(resourcePath);
20
+ const relativePath = resourcePath.replace(/^res:\/\//, "").split("/").join("/");
21
+ return `${(options.assetBaseUrl ?? "/").replace(/\/?$/, "/")}${relativePath}`;
22
+ };
23
+ const loadScene = (resourcePath) => {
24
+ const cached = sceneCache.get(resourcePath);
25
+ if (cached) return cached;
26
+ const scene = parseGodotTextScene(readFileSync(resourcePathToFilePath(resourcePath), "utf8"), { path: resourcePath });
27
+ tagSceneNodes(scene, resourcePath);
28
+ sceneCache.set(resourcePath, scene);
29
+ return scene;
30
+ };
31
+ const loadResource = (resourcePath) => {
32
+ const cached = resourceCache.get(resourcePath);
33
+ if (cached) return cached;
34
+ const document = parseGodotResource(readFileSync(resourcePathToFilePath(resourcePath), "utf8"), { path: resourcePath });
35
+ resourceCache.set(resourcePath, document);
36
+ return document;
37
+ };
38
+ const extResource = (scene, ref) => ref.type === "ExtResource" && ref.id !== void 0 ? scene.extResources.find((resource) => resource.id === ref.id) : void 0;
39
+ const extTarget = (extResources, ref) => {
40
+ if (ref.type !== "ExtResource") return;
41
+ if (ref.path) return { path: ref.path };
42
+ const resource = extResources.find((candidate) => candidate.id === ref.id);
43
+ return resource?.path ? {
44
+ path: resource.path,
45
+ type: resource.type
46
+ } : void 0;
47
+ };
48
+ const resolveResource = (scene, ref, _node) => {
49
+ if (ref.type === "SubResource") {
50
+ const resource = scene.subResources.find((candidate) => candidate.id === ref.id);
51
+ return resource ? {
52
+ type: resource.type,
53
+ document: subResourceDocument(resource)
54
+ } : void 0;
55
+ }
56
+ const target = extTarget(scene.extResources, ref);
57
+ if (!target) return;
58
+ return resolvePathResource(target.path, target.type);
59
+ };
60
+ const mountExternalScene = (scene, ref, _node) => {
61
+ const target = extTarget(scene.extResources, ref);
62
+ if (!target?.path.endsWith(".tscn") || !existsSync(resourcePathToFilePath(target.path))) return;
63
+ return loadScene(target.path);
64
+ };
65
+ const sceneOptions = (scene) => ({
66
+ resolveResource: (ref, node) => resolveResource(sceneForNode(scene, node) ?? scene, ref, node),
67
+ resolveResourcePath: (path) => resolvePathResource(path),
68
+ mountExternalScene: (ref, node) => mountExternalScene(sceneForNode(scene, node) ?? scene, ref, node),
69
+ resolveTheme: (node, name) => {
70
+ const themeRef = asResourceRef(node.properties?.theme);
71
+ if (!themeRef) return void 0;
72
+ const doc = resolveResource(sceneForNode(scene, node) ?? scene, themeRef, node)?.document;
73
+ return doc ? queryTheme(doc, node.type, asString(node.properties?.theme_type_variation), name) : void 0;
74
+ }
75
+ });
76
+ const resolveDocumentResource = (document, ref, seen) => {
77
+ if (ref.type === "SubResource") {
78
+ const resource = document.subResources.find((candidate) => candidate.id === ref.id);
79
+ return resource ? {
80
+ type: resource.type,
81
+ document: subResourceDocument(resource)
82
+ } : void 0;
83
+ }
84
+ const target = extTarget(document.extResources, ref);
85
+ return target ? resolvePathResource(target.path, target.type, seen) : void 0;
86
+ };
87
+ const resolvePathResource = (resourcePath, type, seen = /* @__PURE__ */ new Set()) => {
88
+ if (seen.has(resourcePath)) return {
89
+ type,
90
+ path: resourcePath,
91
+ url: resourcePathToUrl(resourcePath)
92
+ };
93
+ const nextSeen = new Set(seen).add(resourcePath);
94
+ if (resourcePath.endsWith(".tscn")) {
95
+ loadScene(resourcePath);
96
+ return {
97
+ type,
98
+ path: resourcePath,
99
+ url: resourcePathToUrl(resourcePath)
100
+ };
101
+ }
102
+ if (resourcePath.endsWith(".tres") || resourcePath.endsWith(".res")) {
103
+ const document = loadResource(resourcePath);
104
+ const resourceType = asString(document.header?.attributes.type) ?? type;
105
+ const baseResource = {
106
+ type: resourceType,
107
+ path: resourcePath,
108
+ url: resourcePathToUrl(resourcePath),
109
+ document
110
+ };
111
+ if (resourceType === "AtlasTexture") {
112
+ const region = asRect2(document.properties.region);
113
+ const atlasRef = asResourceRef(document.properties.atlas);
114
+ const atlas = atlasRef ? resolveDocumentResource(document, atlasRef, nextSeen) : void 0;
115
+ return {
116
+ ...baseResource,
117
+ url: void 0,
118
+ atlas,
119
+ region,
120
+ size: region ? {
121
+ width: region.width,
122
+ height: region.height
123
+ } : atlas?.size
124
+ };
125
+ }
126
+ if (resourceType === "FontVariation") {
127
+ const baseRef = asResourceRef(document.properties.base_font);
128
+ const base = baseRef ? resolveDocumentResource(document, baseRef, nextSeen) : void 0;
129
+ const variationWeight = fontWeightFromVariation(document);
130
+ const glyphSpacing = asNumber(document.properties.spacing_glyph);
131
+ const fontMsdf = asBoolean(document.properties.multichannel_signed_distance_field);
132
+ return {
133
+ ...base,
134
+ ...baseResource,
135
+ fontFamily: base?.fontFamily ?? fontMetadataFromPath(resourcePath).fontFamily,
136
+ fontUrl: base?.fontUrl,
137
+ fontStyle: base?.fontStyle,
138
+ fontWeight: variationWeight ?? base?.fontWeight,
139
+ glyphSpacing: glyphSpacing ?? base?.glyphSpacing,
140
+ fontMsdf: fontMsdf ?? base?.fontMsdf
141
+ };
142
+ }
143
+ if (resourceType === "FontFile") {
144
+ const fontPath = asString(document.properties.font_path);
145
+ const metadata = fontPath ? fontMetadataFromPath(fontPath) : fontMetadataFromPath(resourcePath);
146
+ return {
147
+ ...baseResource,
148
+ fontUrl: fontPath && isFontPath(fontPath) ? resourcePathToUrl(fontPath) : void 0,
149
+ fontMsdf: asBoolean(document.properties.multichannel_signed_distance_field),
150
+ ...metadata
151
+ };
152
+ }
153
+ if (resourceType === "ShaderMaterial") {
154
+ const shaderRef = asResourceRef(document.properties.shader);
155
+ return {
156
+ ...baseResource,
157
+ shader: shaderRef ? resolveDocumentResource(document, shaderRef, nextSeen) : void 0
158
+ };
159
+ }
160
+ const fontPath = asString(document.properties.font_path);
161
+ return {
162
+ type: resourceType,
163
+ path: resourcePath,
164
+ url: resourcePathToUrl(resourcePath),
165
+ fontUrl: fontPath && isFontPath(fontPath) ? resourcePathToUrl(fontPath) : void 0,
166
+ document,
167
+ ...fontMetadataFromPath(resourcePath)
168
+ };
169
+ }
170
+ return {
171
+ type,
172
+ path: resourcePath,
173
+ url: resourcePathToUrl(resourcePath),
174
+ size: imageSize(resourcePath),
175
+ fontUrl: isFontPath(resourcePath) ? resourcePathToUrl(resourcePath) : void 0,
176
+ ...fontMetadataFromPath(resourcePath)
177
+ };
178
+ };
179
+ const imageSize = (resourcePath) => {
180
+ if (!isImagePath(resourcePath)) return;
181
+ if (imageSizeCache.has(resourcePath)) return imageSizeCache.get(resourcePath);
182
+ let size;
183
+ try {
184
+ size = readImageSize(readFileSync(resourcePathToFilePath(resourcePath)), resourcePath);
185
+ } catch {
186
+ size = void 0;
187
+ }
188
+ imageSizeCache.set(resourcePath, size);
189
+ return size;
190
+ };
191
+ const sceneForNode = (_scene, node) => {
192
+ const sourcePath = asString(node.properties[SOURCE_SCENE_PATH_ATTRIBUTE]);
193
+ return sourcePath ? loadScene(sourcePath) : void 0;
194
+ };
195
+ return {
196
+ projectRoot,
197
+ resourcePathToFilePath,
198
+ resourcePathToUrl,
199
+ loadScene,
200
+ loadResource,
201
+ extResource,
202
+ resolveResource,
203
+ mountExternalScene,
204
+ sceneOptions
205
+ };
206
+ }
207
+ function assertResourcePath(resourcePath) {
208
+ if (!resourcePath.startsWith("res://")) throw new Error(`Expected a Godot resource path starting with res://, got ${resourcePath}`);
209
+ }
210
+ function subResourceDocument(resource) {
211
+ return {
212
+ type: resource.type,
213
+ header: resource.type ? {
214
+ section: "gd_resource",
215
+ attributes: { type: resource.type }
216
+ } : null,
217
+ extResources: [],
218
+ subResources: [],
219
+ properties: resource.properties,
220
+ diagnostics: []
221
+ };
222
+ }
223
+ function isFontPath(path) {
224
+ return /\.(?:ttf|otf|woff2?|ttc)$/i.test(path);
225
+ }
226
+ function isImagePath(path) {
227
+ return /\.(?:png|jpe?g|webp|svg)$/i.test(path);
228
+ }
229
+ function fontMetadataFromPath(path) {
230
+ if (!isFontPath(path)) return {};
231
+ return {
232
+ fontFamily: path.split("/").at(-1)?.replace(/\.[^.]+$/, ""),
233
+ fontStyle: /italic/i.test(path) ? "italic" : "normal",
234
+ fontWeight: /bold/i.test(path) ? "700" : "400"
235
+ };
236
+ }
237
+ function fontWeightFromVariation(document) {
238
+ const variation = document.properties.variation_opentype;
239
+ if (!variation || typeof variation !== "object" || Array.isArray(variation) || "type" in variation) return;
240
+ return asNumber(variation["2003265652"]);
241
+ }
242
+ function readImageSize(buffer, path) {
243
+ if (isPng(buffer)) return {
244
+ width: buffer.readUInt32BE(16),
245
+ height: buffer.readUInt32BE(20)
246
+ };
247
+ if (/\.jpe?g$/i.test(path)) return jpegSize(buffer);
248
+ if (/\.webp$/i.test(path)) return webpSize(buffer);
249
+ if (/\.svg$/i.test(path)) return svgSize(buffer.toString("utf8"));
250
+ }
251
+ function isPng(buffer) {
252
+ return buffer.length >= 24 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10;
253
+ }
254
+ function jpegSize(buffer) {
255
+ let offset = 2;
256
+ while (offset + 9 < buffer.length) {
257
+ if (buffer[offset] !== 255) return;
258
+ const marker = buffer[offset + 1];
259
+ const length = buffer.readUInt16BE(offset + 2);
260
+ if (marker !== void 0 && marker >= 192 && marker <= 195) return {
261
+ height: buffer.readUInt16BE(offset + 5),
262
+ width: buffer.readUInt16BE(offset + 7)
263
+ };
264
+ offset += 2 + length;
265
+ }
266
+ }
267
+ function webpSize(buffer) {
268
+ if (buffer.length < 30 || buffer.toString("ascii", 0, 4) !== "RIFF" || buffer.toString("ascii", 8, 12) !== "WEBP") return;
269
+ const chunk = buffer.toString("ascii", 12, 16);
270
+ if (chunk === "VP8X" && buffer.length >= 30) return {
271
+ width: 1 + buffer.readUIntLE(24, 3),
272
+ height: 1 + buffer.readUIntLE(27, 3)
273
+ };
274
+ if (chunk === "VP8 " && buffer.length >= 30) return {
275
+ width: buffer.readUInt16LE(26) & 16383,
276
+ height: buffer.readUInt16LE(28) & 16383
277
+ };
278
+ if (chunk === "VP8L" && buffer.length >= 25) {
279
+ const bits = buffer.readUInt32LE(21);
280
+ return {
281
+ width: (bits & 16383) + 1,
282
+ height: (bits >> 14 & 16383) + 1
283
+ };
284
+ }
285
+ }
286
+ function svgSize(content) {
287
+ const tag = content.match(/<svg\b[^>]*>/i)?.[0];
288
+ if (!tag) return;
289
+ const width = svgLength(tag.match(/\bwidth=["']?([0-9.]+)/i)?.[1]);
290
+ const height = svgLength(tag.match(/\bheight=["']?([0-9.]+)/i)?.[1]);
291
+ if (width !== void 0 && height !== void 0) return {
292
+ width,
293
+ height
294
+ };
295
+ const viewBox = tag.match(/\bviewBox=["']?([0-9.\s-]+)/i)?.[1]?.trim().split(/\s+/).map(Number);
296
+ if (viewBox?.length === 4 && viewBox.every(Number.isFinite)) return {
297
+ width: viewBox[2] ?? 0,
298
+ height: viewBox[3] ?? 0
299
+ };
300
+ }
301
+ function svgLength(value) {
302
+ if (value === void 0) return;
303
+ const parsed = Number.parseFloat(value);
304
+ return Number.isFinite(parsed) ? parsed : void 0;
305
+ }
306
+ function extResourcePath(scene, ref) {
307
+ if (ref.type !== "ExtResource") return;
308
+ return ref.path ?? scene.extResources.find((resource) => resource.id === ref.id)?.path;
309
+ }
310
+ function extResourceRef(value) {
311
+ return asResourceRef(value);
312
+ }
313
+ //#endregion
314
+ export { createGodotProjectResolver, extResourcePath, extResourceRef, queryTheme };
315
+
316
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","names":[],"sources":["../src/node.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n asBoolean,\n asNumber,\n asRect2,\n asResourceRef,\n asString,\n type GodotExtResource,\n type GodotNode,\n type GodotResource,\n type GodotResourceRefValue,\n type GodotSceneState,\n type GodotVariant,\n} from \"@godot-scene-web/core\";\nimport {\n SOURCE_SCENE_PATH_ATTRIBUTE,\n tagSceneNodes,\n} from \"@godot-scene-web/scene-graph\";\nimport {\n parseGodotResource,\n parseGodotTextScene,\n} from \"@godot-scene-web/tscn-parser\";\nimport type { GodotProjectResolvedResource } from \"./shared\";\nimport { queryTheme } from \"./theme-query\";\n\nexport type { GodotProjectResolvedResource } from \"./shared\";\nexport { queryTheme } from \"./theme-query\";\n\nexport interface GodotProjectResolverOptions {\n projectRoot: string;\n assetBaseUrl?: string | ((resourcePath: string) => string);\n}\n\nexport interface GodotProjectSceneOptions {\n resolveResource: (\n ref: GodotResourceRefValue,\n node: GodotNode,\n ) => GodotProjectResolvedResource | undefined;\n resolveResourcePath: (\n path: string,\n node: GodotNode,\n ) => GodotProjectResolvedResource | undefined;\n mountExternalScene: (\n ref: GodotResourceRefValue,\n node: GodotNode,\n ) => GodotSceneState | undefined;\n /**\n * Resolve a theme item (e.g. a font size) for a node from its assigned `theme`\n * resource (type-variation -> type -> default cascade). Optional: absent ⇒ the\n * renderer keeps its built-in fallbacks.\n */\n resolveTheme?: (node: GodotNode, name: string) => GodotVariant | undefined;\n}\n\nexport interface GodotProjectResolver {\n projectRoot: string;\n resourcePathToFilePath: (resourcePath: string) => string;\n resourcePathToUrl: (resourcePath: string) => string;\n loadScene: (resourcePath: string) => GodotSceneState;\n loadResource: (resourcePath: string) => GodotResource;\n extResource: (\n scene: GodotSceneState,\n ref: GodotResourceRefValue,\n ) => GodotExtResource | undefined;\n resolveResource: (\n scene: GodotSceneState,\n ref: GodotResourceRefValue,\n node?: GodotNode,\n ) => GodotProjectResolvedResource | undefined;\n mountExternalScene: (\n scene: GodotSceneState,\n ref: GodotResourceRefValue,\n node?: GodotNode,\n ) => GodotSceneState | undefined;\n sceneOptions: (scene: GodotSceneState) => GodotProjectSceneOptions;\n}\n\nexport function createGodotProjectResolver(\n options: GodotProjectResolverOptions,\n): GodotProjectResolver {\n const projectRoot = resolve(options.projectRoot);\n const sceneCache = new Map<string, GodotSceneState>();\n const resourceCache = new Map<string, GodotResource>();\n const imageSizeCache = new Map<\n string,\n { width: number; height: number } | undefined\n >();\n\n const resourcePathToFilePath = (resourcePath: string): string => {\n assertResourcePath(resourcePath);\n return resolve(projectRoot, resourcePath.replace(/^res:\\/\\//, \"\"));\n };\n\n const resourcePathToUrl = (resourcePath: string): string => {\n assertResourcePath(resourcePath);\n if (typeof options.assetBaseUrl === \"function\") {\n return options.assetBaseUrl(resourcePath);\n }\n const relativePath = resourcePath\n .replace(/^res:\\/\\//, \"\")\n .split(\"/\")\n .join(\"/\");\n const base = options.assetBaseUrl ?? \"/\";\n return `${base.replace(/\\/?$/, \"/\")}${relativePath}`;\n };\n\n const loadScene = (resourcePath: string): GodotSceneState => {\n const cached = sceneCache.get(resourcePath);\n if (cached) {\n return cached;\n }\n const scene = parseGodotTextScene(\n readFileSync(resourcePathToFilePath(resourcePath), \"utf8\"),\n {\n path: resourcePath,\n },\n );\n tagSceneNodes(scene, resourcePath);\n sceneCache.set(resourcePath, scene);\n return scene;\n };\n\n const loadResource = (resourcePath: string): GodotResource => {\n const cached = resourceCache.get(resourcePath);\n if (cached) {\n return cached;\n }\n const document = parseGodotResource(\n readFileSync(resourcePathToFilePath(resourcePath), \"utf8\"),\n {\n path: resourcePath,\n },\n );\n resourceCache.set(resourcePath, document);\n return document;\n };\n\n const extResource = (\n scene: GodotSceneState,\n ref: GodotResourceRefValue,\n ): GodotExtResource | undefined =>\n ref.type === \"ExtResource\" && ref.id !== undefined\n ? scene.extResources.find((resource) => resource.id === ref.id)\n : undefined;\n\n // Resolve an ExtResource ref to a res:// path (+ table type when known).\n // Path-first (runtime producers supply `ref.path`), id-fallback (text\n // producers supply a scene-local `id` we look up in the table).\n const extTarget = (\n extResources: GodotExtResource[],\n ref: GodotResourceRefValue,\n ): { path: string; type?: string } | undefined => {\n if (ref.type !== \"ExtResource\") {\n return undefined;\n }\n if (ref.path) {\n return { path: ref.path };\n }\n const resource = extResources.find((candidate) => candidate.id === ref.id);\n return resource?.path\n ? { path: resource.path, type: resource.type }\n : undefined;\n };\n\n const resolveResource = (\n scene: GodotSceneState,\n ref: GodotResourceRefValue,\n _node?: GodotNode,\n ): GodotProjectResolvedResource | undefined => {\n if (ref.type === \"SubResource\") {\n const resource = scene.subResources.find(\n (candidate) => candidate.id === ref.id,\n );\n return resource\n ? { type: resource.type, document: subResourceDocument(resource) }\n : undefined;\n }\n const target = extTarget(scene.extResources, ref);\n if (!target) {\n return undefined;\n }\n return resolvePathResource(target.path, target.type);\n };\n\n const mountExternalScene = (\n scene: GodotSceneState,\n ref: GodotResourceRefValue,\n _node?: GodotNode,\n ): GodotSceneState | undefined => {\n const target = extTarget(scene.extResources, ref);\n if (\n !target?.path.endsWith(\".tscn\") ||\n !existsSync(resourcePathToFilePath(target.path))\n ) {\n return undefined;\n }\n return loadScene(target.path);\n };\n\n const sceneOptions = (scene: GodotSceneState): GodotProjectSceneOptions => ({\n resolveResource: (ref, node) =>\n resolveResource(sceneForNode(scene, node) ?? scene, ref, node),\n resolveResourcePath: (path) => resolvePathResource(path),\n mountExternalScene: (ref, node) =>\n mountExternalScene(sceneForNode(scene, node) ?? scene, ref, node),\n resolveTheme: (node, name) => {\n const themeRef = asResourceRef(node.properties?.theme);\n if (!themeRef) return undefined;\n const doc = resolveResource(\n sceneForNode(scene, node) ?? scene,\n themeRef,\n node,\n )?.document;\n return doc\n ? queryTheme(\n doc,\n node.type,\n asString(node.properties?.theme_type_variation),\n name,\n )\n : undefined;\n },\n });\n\n const resolveDocumentResource = (\n document: GodotResource,\n ref: GodotResourceRefValue,\n seen: Set<string>,\n ): GodotProjectResolvedResource | undefined => {\n if (ref.type === \"SubResource\") {\n const resource = document.subResources.find(\n (candidate) => candidate.id === ref.id,\n );\n return resource\n ? { type: resource.type, document: subResourceDocument(resource) }\n : undefined;\n }\n const target = extTarget(document.extResources, ref);\n return target\n ? resolvePathResource(target.path, target.type, seen)\n : undefined;\n };\n\n const resolvePathResource = (\n resourcePath: string,\n type?: string,\n seen: Set<string> = new Set(),\n ): GodotProjectResolvedResource | undefined => {\n if (seen.has(resourcePath)) {\n return { type, path: resourcePath, url: resourcePathToUrl(resourcePath) };\n }\n const nextSeen = new Set(seen).add(resourcePath);\n if (resourcePath.endsWith(\".tscn\")) {\n loadScene(resourcePath);\n return { type, path: resourcePath, url: resourcePathToUrl(resourcePath) };\n }\n if (resourcePath.endsWith(\".tres\") || resourcePath.endsWith(\".res\")) {\n const document = loadResource(resourcePath);\n const resourceType = asString(document.header?.attributes.type) ?? type;\n const baseResource = {\n type: resourceType,\n path: resourcePath,\n url: resourcePathToUrl(resourcePath),\n document,\n };\n if (resourceType === \"AtlasTexture\") {\n const region = asRect2(document.properties.region);\n const atlasRef = asResourceRef(document.properties.atlas);\n const atlas = atlasRef\n ? resolveDocumentResource(document, atlasRef, nextSeen)\n : undefined;\n return {\n ...baseResource,\n // An AtlasTexture has no directly renderable image of its own; its\n // pixels come from the atlas, so don't expose the .tres path as `url`.\n url: undefined,\n atlas,\n region,\n size: region\n ? { width: region.width, height: region.height }\n : atlas?.size,\n };\n }\n if (resourceType === \"FontVariation\") {\n const baseRef = asResourceRef(document.properties.base_font);\n const base = baseRef\n ? resolveDocumentResource(document, baseRef, nextSeen)\n : undefined;\n const variationWeight = fontWeightFromVariation(document);\n const glyphSpacing = asNumber(document.properties.spacing_glyph);\n const fontMsdf = asBoolean(\n document.properties.multichannel_signed_distance_field,\n );\n return {\n ...base,\n ...baseResource,\n fontFamily:\n base?.fontFamily ?? fontMetadataFromPath(resourcePath).fontFamily,\n fontUrl: base?.fontUrl,\n fontStyle: base?.fontStyle,\n fontWeight: variationWeight ?? base?.fontWeight,\n glyphSpacing: glyphSpacing ?? base?.glyphSpacing,\n fontMsdf: fontMsdf ?? base?.fontMsdf,\n };\n }\n if (resourceType === \"FontFile\") {\n const fontPath = asString(document.properties.font_path);\n const metadata = fontPath\n ? fontMetadataFromPath(fontPath)\n : fontMetadataFromPath(resourcePath);\n return {\n ...baseResource,\n fontUrl:\n fontPath && isFontPath(fontPath)\n ? resourcePathToUrl(fontPath)\n : undefined,\n fontMsdf: asBoolean(\n document.properties.multichannel_signed_distance_field,\n ),\n ...metadata,\n };\n }\n if (resourceType === \"ShaderMaterial\") {\n const shaderRef = asResourceRef(document.properties.shader);\n return {\n ...baseResource,\n shader: shaderRef\n ? resolveDocumentResource(document, shaderRef, nextSeen)\n : undefined,\n };\n }\n const fontPath = asString(document.properties.font_path);\n return {\n type: resourceType,\n path: resourcePath,\n url: resourcePathToUrl(resourcePath),\n fontUrl:\n fontPath && isFontPath(fontPath)\n ? resourcePathToUrl(fontPath)\n : undefined,\n document,\n ...fontMetadataFromPath(resourcePath),\n };\n }\n return {\n type,\n path: resourcePath,\n url: resourcePathToUrl(resourcePath),\n size: imageSize(resourcePath),\n fontUrl: isFontPath(resourcePath)\n ? resourcePathToUrl(resourcePath)\n : undefined,\n ...fontMetadataFromPath(resourcePath),\n };\n };\n\n const imageSize = (\n resourcePath: string,\n ): { width: number; height: number } | undefined => {\n if (!isImagePath(resourcePath)) {\n return undefined;\n }\n if (imageSizeCache.has(resourcePath)) {\n return imageSizeCache.get(resourcePath);\n }\n let size: { width: number; height: number } | undefined;\n try {\n size = readImageSize(\n readFileSync(resourcePathToFilePath(resourcePath)),\n resourcePath,\n );\n } catch {\n size = undefined;\n }\n imageSizeCache.set(resourcePath, size);\n return size;\n };\n\n const sceneForNode = (\n _scene: GodotSceneState,\n node: GodotNode,\n ): GodotSceneState | undefined => {\n const sourcePath = asString(node.properties[SOURCE_SCENE_PATH_ATTRIBUTE]);\n return sourcePath ? loadScene(sourcePath) : undefined;\n };\n\n return {\n projectRoot,\n resourcePathToFilePath,\n resourcePathToUrl,\n loadScene,\n loadResource,\n extResource,\n resolveResource,\n mountExternalScene,\n sceneOptions,\n };\n}\n\nfunction assertResourcePath(resourcePath: string): void {\n if (!resourcePath.startsWith(\"res://\")) {\n throw new Error(\n `Expected a Godot resource path starting with res://, got ${resourcePath}`,\n );\n }\n}\n\nfunction subResourceDocument(resource: {\n type?: string;\n properties: Record<string, unknown>;\n}): GodotResource {\n return {\n type: resource.type,\n header: resource.type\n ? { section: \"gd_resource\", attributes: { type: resource.type } }\n : null,\n extResources: [],\n subResources: [],\n properties: resource.properties as GodotResource[\"properties\"],\n diagnostics: [],\n };\n}\n\nfunction isFontPath(path: string): boolean {\n return /\\.(?:ttf|otf|woff2?|ttc)$/i.test(path);\n}\n\nfunction isImagePath(path: string): boolean {\n return /\\.(?:png|jpe?g|webp|svg)$/i.test(path);\n}\n\nfunction fontMetadataFromPath(\n path: string,\n): Partial<GodotProjectResolvedResource> {\n if (!isFontPath(path)) {\n return {};\n }\n return {\n fontFamily: path\n .split(\"/\")\n .at(-1)\n ?.replace(/\\.[^.]+$/, \"\"),\n fontStyle: /italic/i.test(path) ? \"italic\" : \"normal\",\n fontWeight: /bold/i.test(path) ? \"700\" : \"400\",\n };\n}\n\nfunction fontWeightFromVariation(document: GodotResource): number | undefined {\n const variation = document.properties.variation_opentype;\n if (\n !variation ||\n typeof variation !== \"object\" ||\n Array.isArray(variation) ||\n \"type\" in variation\n ) {\n return undefined;\n }\n return asNumber((variation as Record<string, GodotVariant>)[\"2003265652\"]);\n}\n\nfunction readImageSize(\n buffer: Buffer,\n path: string,\n): { width: number; height: number } | undefined {\n if (isPng(buffer)) {\n return {\n width: buffer.readUInt32BE(16),\n height: buffer.readUInt32BE(20),\n };\n }\n if (/\\.jpe?g$/i.test(path)) {\n return jpegSize(buffer);\n }\n if (/\\.webp$/i.test(path)) {\n return webpSize(buffer);\n }\n if (/\\.svg$/i.test(path)) {\n return svgSize(buffer.toString(\"utf8\"));\n }\n return undefined;\n}\n\nfunction isPng(buffer: Buffer): boolean {\n return (\n buffer.length >= 24 &&\n buffer[0] === 0x89 &&\n buffer[1] === 0x50 &&\n buffer[2] === 0x4e &&\n buffer[3] === 0x47 &&\n buffer[4] === 0x0d &&\n buffer[5] === 0x0a &&\n buffer[6] === 0x1a &&\n buffer[7] === 0x0a\n );\n}\n\nfunction jpegSize(\n buffer: Buffer,\n): { width: number; height: number } | undefined {\n let offset = 2;\n while (offset + 9 < buffer.length) {\n if (buffer[offset] !== 0xff) {\n return undefined;\n }\n const marker = buffer[offset + 1];\n const length = buffer.readUInt16BE(offset + 2);\n if (marker !== undefined && marker >= 0xc0 && marker <= 0xc3) {\n return {\n height: buffer.readUInt16BE(offset + 5),\n width: buffer.readUInt16BE(offset + 7),\n };\n }\n offset += 2 + length;\n }\n return undefined;\n}\n\nfunction webpSize(\n buffer: Buffer,\n): { width: number; height: number } | undefined {\n if (\n buffer.length < 30 ||\n buffer.toString(\"ascii\", 0, 4) !== \"RIFF\" ||\n buffer.toString(\"ascii\", 8, 12) !== \"WEBP\"\n ) {\n return undefined;\n }\n const chunk = buffer.toString(\"ascii\", 12, 16);\n if (chunk === \"VP8X\" && buffer.length >= 30) {\n return {\n width: 1 + buffer.readUIntLE(24, 3),\n height: 1 + buffer.readUIntLE(27, 3),\n };\n }\n if (chunk === \"VP8 \" && buffer.length >= 30) {\n return {\n width: buffer.readUInt16LE(26) & 0x3fff,\n height: buffer.readUInt16LE(28) & 0x3fff,\n };\n }\n if (chunk === \"VP8L\" && buffer.length >= 25) {\n const bits = buffer.readUInt32LE(21);\n return {\n width: (bits & 0x3fff) + 1,\n height: ((bits >> 14) & 0x3fff) + 1,\n };\n }\n return undefined;\n}\n\nfunction svgSize(\n content: string,\n): { width: number; height: number } | undefined {\n const tag = content.match(/<svg\\b[^>]*>/i)?.[0];\n if (!tag) {\n return undefined;\n }\n const width = svgLength(tag.match(/\\bwidth=[\"']?([0-9.]+)/i)?.[1]);\n const height = svgLength(tag.match(/\\bheight=[\"']?([0-9.]+)/i)?.[1]);\n if (width !== undefined && height !== undefined) {\n return { width, height };\n }\n const viewBox = tag\n .match(/\\bviewBox=[\"']?([0-9.\\s-]+)/i)?.[1]\n ?.trim()\n .split(/\\s+/)\n .map(Number);\n if (viewBox?.length === 4 && viewBox.every(Number.isFinite)) {\n return { width: viewBox[2] ?? 0, height: viewBox[3] ?? 0 };\n }\n return undefined;\n}\n\nfunction svgLength(value: string | undefined): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const parsed = Number.parseFloat(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nexport function extResourcePath(\n scene: GodotSceneState,\n ref: GodotResourceRefValue,\n): string | undefined {\n if (ref.type !== \"ExtResource\") {\n return undefined;\n }\n return (\n ref.path ??\n scene.extResources.find((resource) => resource.id === ref.id)?.path\n );\n}\n\nexport function extResourceRef(\n value: unknown,\n): GodotResourceRefValue | undefined {\n return asResourceRef(value as GodotVariant | undefined);\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { GodotResource } from "@godot-scene-web/core";
2
+ import { GodotResourceLoadStatus } from "@godot-scene-web/scene-graph";
3
+
4
+ //#region src/shared.d.ts
5
+ /** A platform-neutral description of an asset resolved from a Godot resource reference. */
6
+ interface GodotProjectResolvedResource {
7
+ status?: GodotResourceLoadStatus;
8
+ type?: string;
9
+ path?: string;
10
+ url?: string;
11
+ document?: GodotResource;
12
+ atlas?: GodotProjectResolvedResource;
13
+ shader?: GodotProjectResolvedResource;
14
+ region?: {
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ };
20
+ size?: {
21
+ width: number;
22
+ height: number;
23
+ };
24
+ fontUrl?: string;
25
+ fontFamily?: string;
26
+ fontStyle?: "normal" | "italic";
27
+ fontWeight?: number | string;
28
+ glyphSpacing?: number;
29
+ fontMsdf?: boolean;
30
+ message?: string;
31
+ }
32
+ //#endregion
33
+ export { GodotProjectResolvedResource as t };
34
+ //# sourceMappingURL=shared-Dgi2oc_H.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-Dgi2oc_H.d.ts","names":[],"sources":["../src/shared.ts"],"mappings":";;;;;UAIiB,4BAAA;EACf,MAAA,GAAS,uBAAA;EACT,IAAA;EACA,IAAA;EACA,GAAA;EACA,QAAA,GAAW,aAAA;EACX,KAAA,GAAQ,4BAAA;EACR,MAAA,GAAS,4BAAA;EACT,MAAA;IAAW,CAAA;IAAW,CAAA;IAAW,KAAA;IAAe,MAAA;EAAA;EAChD,IAAA;IAAS,KAAA;IAAe,MAAA;EAAA;EACxB,OAAA;EACA,UAAA;EACA,SAAA;EACA,UAAA;EACA,YAAA;EACA,QAAA;EACA,OAAA;AAAA"}
@@ -0,0 +1,43 @@
1
+ import { asString } from "@godot-scene-web/core";
2
+ //#region src/theme-query.ts
3
+ const FONT_SIZE_ITEMS = new Set([
4
+ "font_size",
5
+ "normal_font_size",
6
+ "bold_font_size",
7
+ "italics_font_size",
8
+ "bold_italics_font_size",
9
+ "mono_font_size",
10
+ "title_font_size"
11
+ ]);
12
+ /**
13
+ * Resolve a single theme item for a node from a parsed Theme resource document,
14
+ * following Godot's lookup cascade: the node's `theme_type_variation` (then that
15
+ * variation's `base_type`), then the node's own type, then the theme-wide default.
16
+ *
17
+ * A parsed Theme `.tres` flattens its entries to keys like `default_font_size`,
18
+ * `Label/colors/font_color`, `RichTextLabel/font_sizes/normal_font_size`, and
19
+ * `<Variation>/base_type` — so the lookup is plain property access by composed key.
20
+ *
21
+ * v1 covers FONT SIZES only — the gap that left themed labels (e.g. a RichTextLabel whose
22
+ * size lives in its theme's `default_font_size`, not an inline `theme_override`) rendering
23
+ * at the renderer's 1em fallback. Every other item kind returns `undefined`, so colors,
24
+ * fonts and constants keep their current behavior (inline overrides + renderer defaults).
25
+ */
26
+ function queryTheme(themeDoc, nodeType, themeTypeVariation, name) {
27
+ if (!FONT_SIZE_ITEMS.has(name)) return void 0;
28
+ const props = themeDoc.properties;
29
+ const inType = (type) => type ? props[`${type}/font_sizes/${name}`] : void 0;
30
+ const variationValue = inType(themeTypeVariation);
31
+ if (variationValue !== void 0) return variationValue;
32
+ if (themeTypeVariation) {
33
+ const baseValue = inType(asString(props[`${themeTypeVariation}/base_type`]));
34
+ if (baseValue !== void 0) return baseValue;
35
+ }
36
+ const typeValue = inType(nodeType);
37
+ if (typeValue !== void 0) return typeValue;
38
+ return props.default_font_size;
39
+ }
40
+ //#endregion
41
+ export { queryTheme as t };
42
+
43
+ //# sourceMappingURL=theme-query-ClAPxgZ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-query-ClAPxgZ7.js","names":[],"sources":["../src/theme-query.ts"],"sourcesContent":["import {\n asString,\n type GodotResource,\n type GodotVariant,\n} from \"@godot-scene-web/core\";\n\n// Theme item names that resolve to Godot's \"font_sizes\" data category and, absent a\n// type/variation-specific entry, fall back to the theme-wide `default_font_size`.\nconst FONT_SIZE_ITEMS = new Set([\n \"font_size\",\n \"normal_font_size\",\n \"bold_font_size\",\n \"italics_font_size\",\n \"bold_italics_font_size\",\n \"mono_font_size\",\n \"title_font_size\",\n]);\n\n/**\n * Resolve a single theme item for a node from a parsed Theme resource document,\n * following Godot's lookup cascade: the node's `theme_type_variation` (then that\n * variation's `base_type`), then the node's own type, then the theme-wide default.\n *\n * A parsed Theme `.tres` flattens its entries to keys like `default_font_size`,\n * `Label/colors/font_color`, `RichTextLabel/font_sizes/normal_font_size`, and\n * `<Variation>/base_type` — so the lookup is plain property access by composed key.\n *\n * v1 covers FONT SIZES only — the gap that left themed labels (e.g. a RichTextLabel whose\n * size lives in its theme's `default_font_size`, not an inline `theme_override`) rendering\n * at the renderer's 1em fallback. Every other item kind returns `undefined`, so colors,\n * fonts and constants keep their current behavior (inline overrides + renderer defaults).\n */\nexport function queryTheme(\n themeDoc: GodotResource,\n nodeType: string | undefined,\n themeTypeVariation: string | undefined,\n name: string,\n): GodotVariant | undefined {\n if (!FONT_SIZE_ITEMS.has(name)) return undefined;\n const props = themeDoc.properties;\n const inType = (type: string | undefined): GodotVariant | undefined =>\n type ? props[`${type}/font_sizes/${name}`] : undefined;\n\n const variationValue = inType(themeTypeVariation);\n if (variationValue !== undefined) return variationValue;\n if (themeTypeVariation) {\n const baseValue = inType(\n asString(props[`${themeTypeVariation}/base_type`]),\n );\n if (baseValue !== undefined) return baseValue;\n }\n const typeValue = inType(nodeType);\n if (typeValue !== undefined) return typeValue;\n // Theme-wide default size (Godot `Theme.default_font_size`).\n return props.default_font_size;\n}\n"],"mappings":";;AAQA,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;;;;;AAgBD,SAAgB,WACd,UACA,UACA,oBACA,MAC0B;CAC1B,IAAI,CAAC,gBAAgB,IAAI,IAAI,GAAG,OAAO,KAAA;CACvC,MAAM,QAAQ,SAAS;CACvB,MAAM,UAAU,SACd,OAAO,MAAM,GAAG,KAAK,cAAc,UAAU,KAAA;CAE/C,MAAM,iBAAiB,OAAO,kBAAkB;CAChD,IAAI,mBAAmB,KAAA,GAAW,OAAO;CACzC,IAAI,oBAAoB;EACtB,MAAM,YAAY,OAChB,SAAS,MAAM,GAAG,mBAAmB,YAAY,CACnD;EACA,IAAI,cAAc,KAAA,GAAW,OAAO;CACtC;CACA,MAAM,YAAY,OAAO,QAAQ;CACjC,IAAI,cAAc,KAAA,GAAW,OAAO;CAEpC,OAAO,MAAM;AACf"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@godot-scene-web/project",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "description": "Godot project resource resolver for browser and Node environments.",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/tfoxy/godot-scene-web.git"
14
+ },
15
+ "homepage": "https://github.com/tfoxy/godot-scene-web#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/tfoxy/godot-scene-web/issues"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "browser": {
22
+ "development": "./src/fetch.ts",
23
+ "types": "./dist/fetch.d.ts",
24
+ "import": "./dist/fetch.js"
25
+ },
26
+ "node": {
27
+ "development": "./src/node.ts",
28
+ "types": "./dist/node.d.ts",
29
+ "import": "./dist/node.js"
30
+ },
31
+ "development": "./src/node.ts",
32
+ "types": "./dist/node.d.ts",
33
+ "import": "./dist/node.js"
34
+ },
35
+ "./node": {
36
+ "development": "./src/node.ts",
37
+ "types": "./dist/node.d.ts",
38
+ "import": "./dist/node.js"
39
+ },
40
+ "./fetch": {
41
+ "development": "./src/fetch.ts",
42
+ "types": "./dist/fetch.d.ts",
43
+ "import": "./dist/fetch.js"
44
+ }
45
+ },
46
+ "main": "./dist/node.js",
47
+ "types": "./dist/node.d.ts",
48
+ "files": [
49
+ "dist",
50
+ "LICENSE"
51
+ ],
52
+ "dependencies": {
53
+ "@godot-scene-web/core": "0.1.0",
54
+ "@godot-scene-web/tscn-parser": "0.1.0",
55
+ "@godot-scene-web/scene-graph": "0.1.0"
56
+ },
57
+ "scripts": {
58
+ "build": "tsdown",
59
+ "typecheck": "tsc -p tsconfig.json --noEmit",
60
+ "test": "vitest run --root ../.. --config ../../vitest.config.ts packages/project/test"
61
+ }
62
+ }