@needle-tools/engine 4.14.0 → 4.15.0-next.f391a30

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 (198) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{gltf-progressive-BttGBXw6.umd.cjs → gltf-progressive-CMwJPwEt.umd.cjs} +1 -1
  4. package/dist/{gltf-progressive-Bm_6aEi4.js → gltf-progressive-CTlvpS3A.js} +1 -1
  5. package/dist/{gltf-progressive-T5WKTux5.min.js → gltf-progressive-DYL3SLVb.min.js} +1 -1
  6. package/dist/materialx-4jJLLe9Q.js +4174 -0
  7. package/dist/materialx-Bt9FHwco.min.js +158 -0
  8. package/dist/materialx-NDD0y4JY.umd.cjs +158 -0
  9. package/dist/{needle-engine.bundle-COL2Bar3.umd.cjs → needle-engine.bundle-C1BFRZDF.umd.cjs} +150 -140
  10. package/dist/{needle-engine.bundle-Z_gAD7Kg.js → needle-engine.bundle-DB4kLWO_.js} +6651 -6400
  11. package/dist/{needle-engine.bundle-NolzHLqO.min.js → needle-engine.bundle-DsTdfmeb.min.js} +151 -141
  12. package/dist/needle-engine.d.ts +345 -88
  13. package/dist/needle-engine.js +322 -322
  14. package/dist/needle-engine.min.js +1 -1
  15. package/dist/needle-engine.umd.cjs +1 -1
  16. package/dist/{postprocessing-06AXuvdv.min.js → postprocessing-BN-f4viE.min.js} +1 -1
  17. package/dist/{postprocessing-CPDcA21P.umd.cjs → postprocessing-DYmYOVm4.umd.cjs} +1 -1
  18. package/dist/{postprocessing-CI2x8Cln.js → postprocessing-De9ZpJrk.js} +1 -1
  19. package/dist/{three-examples-BMmNgNCN.umd.cjs → three-examples-BHqRVpO_.umd.cjs} +12 -12
  20. package/dist/{three-examples-CMYCd5nH.js → three-examples-C0ZCCA_K.js} +182 -192
  21. package/dist/{three-examples-CQl1fFZp.min.js → three-examples-DmTY8tGr.min.js} +14 -14
  22. package/lib/engine/api.d.ts +0 -2
  23. package/lib/engine/api.js +0 -2
  24. package/lib/engine/api.js.map +1 -1
  25. package/lib/engine/debug/debug.js +1 -1
  26. package/lib/engine/debug/debug.js.map +1 -1
  27. package/lib/engine/debug/debug_spatial_console.js +1 -1
  28. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  29. package/lib/engine/engine_accessibility.d.ts +77 -0
  30. package/lib/engine/engine_accessibility.js +162 -0
  31. package/lib/engine/engine_accessibility.js.map +1 -0
  32. package/lib/engine/engine_context.d.ts +2 -0
  33. package/lib/engine/engine_context.js +8 -1
  34. package/lib/engine/engine_context.js.map +1 -1
  35. package/lib/engine/engine_create_objects.js +1 -1
  36. package/lib/engine/engine_create_objects.js.map +1 -1
  37. package/lib/engine/engine_gizmos.js +1 -1
  38. package/lib/engine/engine_gizmos.js.map +1 -1
  39. package/lib/engine/engine_license.js +7 -2
  40. package/lib/engine/engine_license.js.map +1 -1
  41. package/lib/engine/engine_materialpropertyblock.d.ts +90 -4
  42. package/lib/engine/engine_materialpropertyblock.js +97 -7
  43. package/lib/engine/engine_materialpropertyblock.js.map +1 -1
  44. package/lib/engine/engine_math.d.ts +34 -1
  45. package/lib/engine/engine_math.js +34 -1
  46. package/lib/engine/engine_math.js.map +1 -1
  47. package/lib/engine/engine_networking.js +1 -1
  48. package/lib/engine/engine_networking.js.map +1 -1
  49. package/lib/engine/engine_types.d.ts +2 -0
  50. package/lib/engine/engine_types.js +2 -0
  51. package/lib/engine/engine_types.js.map +1 -1
  52. package/lib/engine/engine_utils.js +2 -2
  53. package/lib/engine/engine_utils.js.map +1 -1
  54. package/lib/engine/export/gltf/EXT_mesh_gpu_instancing_exporter.js.map +1 -0
  55. package/lib/engine/export/gltf/index.js +1 -1
  56. package/lib/engine/export/gltf/index.js.map +1 -1
  57. package/lib/engine/webcomponents/icons.js +3 -0
  58. package/lib/engine/webcomponents/icons.js.map +1 -1
  59. package/lib/engine/webcomponents/logo-element.d.ts +7 -3
  60. package/lib/engine/webcomponents/logo-element.js +21 -1
  61. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  62. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  63. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  64. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +10 -7
  65. package/lib/engine/webcomponents/needle menu/needle-menu.js +14 -4
  66. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  67. package/lib/engine/webcomponents/needle-button.d.ts +37 -11
  68. package/lib/engine/webcomponents/needle-button.js +42 -11
  69. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  70. package/lib/engine/webcomponents/needle-engine.ar-overlay.js +10 -1
  71. package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
  72. package/lib/engine/webcomponents/needle-engine.d.ts +13 -2
  73. package/lib/engine/webcomponents/needle-engine.js +23 -3
  74. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  75. package/lib/engine-components/Component.d.ts +1 -2
  76. package/lib/engine-components/Component.js +1 -3
  77. package/lib/engine-components/Component.js.map +1 -1
  78. package/lib/engine-components/DragControls.d.ts +1 -0
  79. package/lib/engine-components/DragControls.js +21 -0
  80. package/lib/engine-components/DragControls.js.map +1 -1
  81. package/lib/engine-components/NeedleMenu.d.ts +2 -0
  82. package/lib/engine-components/NeedleMenu.js +2 -0
  83. package/lib/engine-components/NeedleMenu.js.map +1 -1
  84. package/lib/engine-components/Networking.d.ts +28 -3
  85. package/lib/engine-components/Networking.js +28 -3
  86. package/lib/engine-components/Networking.js.map +1 -1
  87. package/lib/engine-components/ReflectionProbe.d.ts +25 -2
  88. package/lib/engine-components/ReflectionProbe.js +46 -2
  89. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  90. package/lib/engine-components/Skybox.js +4 -2
  91. package/lib/engine-components/Skybox.js.map +1 -1
  92. package/lib/engine-components/export/gltf/GltfExport.js +1 -1
  93. package/lib/engine-components/export/gltf/GltfExport.js.map +1 -1
  94. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +2 -2
  95. package/lib/engine-components/export/usdz/USDZExporter.js +1 -1
  96. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  97. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +15 -0
  98. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +77 -0
  99. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  100. package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js +2 -2
  101. package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js.map +1 -1
  102. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  103. package/lib/engine-components/ui/Button.d.ts +1 -0
  104. package/lib/engine-components/ui/Button.js +11 -0
  105. package/lib/engine-components/ui/Button.js.map +1 -1
  106. package/lib/engine-components/ui/Text.d.ts +1 -0
  107. package/lib/engine-components/ui/Text.js +11 -0
  108. package/lib/engine-components/ui/Text.js.map +1 -1
  109. package/package.json +18 -14
  110. package/plugins/common/buildinfo.js +46 -10
  111. package/plugins/common/files.js +2 -1
  112. package/plugins/common/license.js +144 -69
  113. package/plugins/common/logger.js +172 -11
  114. package/plugins/common/needle-engine-skill.md +175 -0
  115. package/plugins/common/worker.js +5 -4
  116. package/plugins/types/userconfig.d.ts +40 -2
  117. package/plugins/vite/ai.js +71 -0
  118. package/plugins/vite/alias.js +6 -5
  119. package/plugins/vite/asap.js +6 -5
  120. package/plugins/vite/build-pipeline.js +224 -41
  121. package/plugins/vite/buildinfo.js +66 -6
  122. package/plugins/vite/copyfiles.js +41 -12
  123. package/plugins/vite/custom-element-data.js +26 -16
  124. package/plugins/vite/defines.js +8 -5
  125. package/plugins/vite/dependencies.js +16 -10
  126. package/plugins/vite/dependency-watcher.js +35 -7
  127. package/plugins/vite/drop-client.js +7 -5
  128. package/plugins/vite/drop.js +16 -14
  129. package/plugins/vite/editor-connection.js +18 -16
  130. package/plugins/vite/imports-logger.js +12 -2
  131. package/plugins/vite/index.js +8 -3
  132. package/plugins/vite/local-files-analysis.js +789 -0
  133. package/plugins/vite/local-files-core.js +992 -0
  134. package/plugins/vite/local-files-internals.js +28 -0
  135. package/plugins/vite/local-files-types.d.ts +111 -0
  136. package/plugins/vite/local-files-utils.js +359 -0
  137. package/plugins/vite/local-files.js +2 -441
  138. package/plugins/vite/logger.client.js +45 -35
  139. package/plugins/vite/logger.js +6 -3
  140. package/plugins/vite/logging.js +129 -0
  141. package/plugins/vite/meta.js +18 -4
  142. package/plugins/vite/needle-app.js +4 -3
  143. package/plugins/vite/peer.js +2 -1
  144. package/plugins/vite/pwa.js +33 -17
  145. package/plugins/vite/reload.js +24 -2
  146. package/src/engine/api.ts +0 -3
  147. package/src/engine/debug/debug.ts +1 -1
  148. package/src/engine/debug/debug_spatial_console.ts +5 -1
  149. package/src/engine/engine_accessibility.ts +198 -0
  150. package/src/engine/engine_context.ts +10 -1
  151. package/src/engine/engine_create_objects.ts +1 -1
  152. package/src/engine/engine_gizmos.ts +9 -5
  153. package/src/engine/engine_license.ts +7 -2
  154. package/src/engine/engine_materialpropertyblock.ts +102 -11
  155. package/src/engine/engine_math.ts +34 -1
  156. package/src/engine/engine_networking.ts +1 -1
  157. package/src/engine/engine_types.ts +5 -0
  158. package/src/engine/engine_utils.ts +2 -2
  159. package/src/engine/export/gltf/index.ts +1 -1
  160. package/src/engine/webcomponents/icons.ts +3 -0
  161. package/src/engine/webcomponents/logo-element.ts +24 -4
  162. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +6 -2
  163. package/src/engine/webcomponents/needle menu/needle-menu.ts +23 -11
  164. package/src/engine/webcomponents/needle-button.ts +44 -13
  165. package/src/engine/webcomponents/needle-engine.ar-overlay.ts +13 -2
  166. package/src/engine/webcomponents/needle-engine.ts +31 -8
  167. package/src/engine-components/Component.ts +2 -5
  168. package/src/engine-components/DragControls.ts +29 -4
  169. package/src/engine-components/NeedleMenu.ts +5 -3
  170. package/src/engine-components/Networking.ts +29 -4
  171. package/src/engine-components/ReflectionProbe.ts +52 -9
  172. package/src/engine-components/Skybox.ts +4 -2
  173. package/src/engine-components/export/gltf/GltfExport.ts +1 -1
  174. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
  175. package/src/engine-components/export/usdz/USDZExporter.ts +1 -1
  176. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +108 -32
  177. package/src/engine-components/export/usdz/extensions/behavior/PhysicsExtension.ts +2 -2
  178. package/src/engine-components/ui/Button.ts +12 -0
  179. package/src/engine-components/ui/Text.ts +13 -0
  180. package/dist/materialx-CJyQZtjt.min.js +0 -90
  181. package/dist/materialx-DMs1E08Z.js +0 -4636
  182. package/dist/materialx-DaKKOoVk.umd.cjs +0 -90
  183. package/lib/engine/engine_test_utils.d.ts +0 -39
  184. package/lib/engine/engine_test_utils.js +0 -84
  185. package/lib/engine/engine_test_utils.js.map +0 -1
  186. package/lib/include/three/EXT_mesh_gpu_instancing_exporter.js.map +0 -1
  187. package/src/engine/engine_test_utils.ts +0 -109
  188. package/src/include/draco/draco_decoder.js +0 -34
  189. package/src/include/draco/draco_decoder.wasm +0 -0
  190. package/src/include/draco/draco_wasm_wrapper.js +0 -117
  191. package/src/include/ktx2/basis_transcoder.js +0 -19
  192. package/src/include/ktx2/basis_transcoder.wasm +0 -0
  193. package/src/include/needle/arial-msdf.json +0 -1472
  194. package/src/include/needle/arial.png +0 -0
  195. package/src/include/needle/poweredbyneedle.webp +0 -0
  196. /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.d.ts +0 -0
  197. /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
  198. /package/src/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
@@ -0,0 +1,28 @@
1
+ export {
2
+ getActiveHandlers,
3
+ getFeatureBasePath,
4
+ handleWebXRProfiles,
5
+ makeLocal,
6
+ makeLocalHtml,
7
+ } from './local-files-core.js';
8
+
9
+ export {
10
+ analyzeProjectGlbs,
11
+ buildAutoPolicy,
12
+ collectAssetGlbs,
13
+ collectIndexHtmlSkyboxUrls,
14
+ collectNeedleComponentExtensionBlobs,
15
+ detectAutoPolicy,
16
+ getWebXRProfilesForMode,
17
+ hasAutoFeatureSelection,
18
+ readGlbJsonChunk,
19
+ resolveSkyboxSelectionUrls,
20
+ resolveSkyboxValueToUrl,
21
+ shouldHandleUrlInAutoMode,
22
+ } from './local-files-analysis.js';
23
+
24
+ export {
25
+ fixDracoRangeQueryProbe,
26
+ fixRelativeNewURL,
27
+ getRelativeToBasePath,
28
+ } from './local-files-utils.js';
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Shared type declarations for the local-files plugin family.
3
+ * Referenced from local-files-utils.js, local-files-analysis.js, local-files-core.js, etc.
4
+ */
5
+
6
+ export interface UrlHandler {
7
+ name: string;
8
+ /** RegExp with the /g flag — match group 1 must capture the URL. */
9
+ pattern: RegExp;
10
+ type: "binary" | "css" | "decoder-dir" | "webxr-profiles";
11
+ feature: string;
12
+ decoderFiles?: string[];
13
+ localDirName?: string;
14
+ }
15
+
16
+ export interface LocalizationStats {
17
+ fileCount: number;
18
+ totalBytes: number;
19
+ mimeCounts: Map<string, number>;
20
+ }
21
+
22
+ export interface LocalizationOptions {
23
+ features?: string | string[];
24
+ excludeFeatures?: string[];
25
+ /** URL substrings or RegExps to skip. */
26
+ exclude?: (string | RegExp)[];
27
+ /** @deprecated use `exclude` */
28
+ excludeUrls?: string[];
29
+ /** e.g. "facebook-instant", "discord" */
30
+ platform?: string;
31
+ /** Only process files inside these npm packages. */
32
+ packages?: string[];
33
+ /** Skybox selection: URL, magic keyword, array of those, or "all". */
34
+ skybox?: string | string[];
35
+ /** WebXR controller profile mode: "minimal" | "quest" | "pico" | "all" */
36
+ webxr?: string;
37
+ templateExpansions?: Array<{
38
+ cdnPrefix: string;
39
+ variables: Record<string, string[]>;
40
+ localPrefix?: string;
41
+ }>;
42
+ /** Used when makeFilesLocal is an object — set to false to disable. */
43
+ enabled?: boolean;
44
+ }
45
+
46
+ export interface AutoPolicy {
47
+ features: Set<string>;
48
+ hasWebXR: boolean;
49
+ hasVideoPlayer: boolean;
50
+ allowedSkyboxUrls: Set<string> | null;
51
+ selectedWebXRProfiles: string[];
52
+ }
53
+
54
+ export interface VitePluginContext {
55
+ emitFile(asset: {
56
+ type: "asset" | "chunk";
57
+ fileName?: string;
58
+ name?: string;
59
+ source?: string | Uint8Array;
60
+ }): string;
61
+ getFileName(referenceId: string): string;
62
+ }
63
+
64
+ export interface LocalizationContext {
65
+ pluginContext: VitePluginContext | null;
66
+ cache: {
67
+ addToCache(key: string, value: Buffer | string): void;
68
+ getFromCache(key: string): Buffer | string | null;
69
+ readonly map: Map<string, Buffer | string>;
70
+ };
71
+ command: "build" | "serve";
72
+ viteConfig: { build?: { assetsDir?: string } } | null;
73
+ options: LocalizationOptions;
74
+ autoPolicy: AutoPolicy | null;
75
+ failedDownloads: Map<string, string>;
76
+ localizationStats: LocalizationStats;
77
+ }
78
+
79
+ export interface SceneAnalysisReport {
80
+ hasWebXRComponent: boolean;
81
+ hasVideoPlayerComponent: boolean;
82
+ extensions: Set<string>;
83
+ componentTypes: Set<string>;
84
+ componentCounts: Map<string, number>;
85
+ glbFileCount: number;
86
+ gltfFileCount: number;
87
+ totalSceneFileCount: number;
88
+ totalNodeCount: number;
89
+ totalVertexCount: number;
90
+ totalTextureCount: number;
91
+ totalMeshCount: number;
92
+ totalPrimitiveCount: number;
93
+ dracoPrimitiveCount: number;
94
+ meshoptBufferViewCount: number;
95
+ }
96
+
97
+ export interface SceneFile {
98
+ path: string;
99
+ type: "glb" | "gltf";
100
+ }
101
+
102
+ export interface NeedleComponentEntry {
103
+ name?: string;
104
+ type?: string;
105
+ guid?: string;
106
+ }
107
+
108
+ export interface NeedleComponentExtension {
109
+ components?: NeedleComponentEntry[];
110
+ builtin_components?: NeedleComponentEntry[];
111
+ }
@@ -0,0 +1,359 @@
1
+ // @ts-check
2
+ import https from 'https';
3
+ import http from 'http';
4
+ import { createHash } from 'crypto';
5
+ import { needleLog, setTransientLogLineCleaner } from './logging.js';
6
+
7
+ /** @typedef {import('./local-files-types.js').LocalizationContext} LocalizationContext */
8
+
9
+ const debug = false;
10
+ const DOWNLOAD_TIMEOUT_MS = 30_000;
11
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
12
+ let spinnerIndex = 0;
13
+ let spinnerActive = false;
14
+
15
+ setTransientLogLineCleaner(() => clearSpinnerLine());
16
+
17
+ function clearSpinnerLine() {
18
+ if (!process.stdout.isTTY || !spinnerActive) return;
19
+ process.stdout.write("\r\x1b[2K");
20
+ spinnerActive = false;
21
+ }
22
+
23
+ /** @param {string} url */
24
+ function updateMakeLocalProgress(url) {
25
+ if (!process.stdout.isTTY) {
26
+ needleLog("needle:local-files", "Make local: " + url, "log", { dimBody: true, showHeader: false });
27
+ return;
28
+ }
29
+ const frame = SPINNER_FRAMES[spinnerIndex++ % SPINNER_FRAMES.length];
30
+ const maxLength = Math.max(24, (process.stdout.columns || 120) - 4);
31
+ const message = `Make local: ${url}`;
32
+ const text = message.length > maxLength ? `${message.slice(0, Math.max(0, maxLength - 1))}…` : message;
33
+ process.stdout.write(`\r\x1b[2K${frame} ${text}\x1b[0K`);
34
+ spinnerActive = true;
35
+ }
36
+
37
+ export function finishMakeLocalProgress() {
38
+ clearSpinnerLine();
39
+ }
40
+
41
+ /**
42
+ * @param {string} src
43
+ * @param {string} search
44
+ * @param {string} replacement
45
+ * @returns {string}
46
+ */
47
+ export function replaceAll(src, search, replacement) {
48
+ return src.split(search).join(replacement);
49
+ }
50
+
51
+ export class Cache {
52
+ /** @type {Map<string, Buffer|string>} */
53
+ __cache = new Map();
54
+ /**
55
+ * @param {string} key
56
+ * @param {Buffer|string} value
57
+ */
58
+ addToCache(key, value) {
59
+ if (debug && this.__cache.has(key)) {
60
+ console.warn("Key " + key + " already exists in cache, overwriting.");
61
+ }
62
+ this.__cache.set(key, value);
63
+ }
64
+ /**
65
+ * @param {string} key
66
+ * @returns {Buffer|string|null}
67
+ */
68
+ getFromCache(key) {
69
+ return this.__cache.get(key) ?? null;
70
+ }
71
+ get map() {
72
+ return this.__cache;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * @param {string} path
78
+ * @param {string} basePath
79
+ * @returns {string}
80
+ */
81
+ export function getRelativeToBasePath(path, basePath) {
82
+ const normalizedPath = normalizeWebPath(path);
83
+ const normalizedBasePath = normalizeWebPath(basePath);
84
+
85
+ if (!normalizedBasePath) return normalizedPath;
86
+
87
+ if (normalizedPath.startsWith(normalizedBasePath)) {
88
+ return "./" + normalizedPath.substring(normalizedBasePath.length);
89
+ }
90
+
91
+ const baseSegments = normalizedBasePath.replace(/\/+$/g, "").split("/").filter(Boolean);
92
+ const backtrack = baseSegments.map(() => "..").join("/");
93
+ return (backtrack ? backtrack + "/" : "") + normalizedPath;
94
+ }
95
+
96
+ /**
97
+ * @param {string} path
98
+ * @returns {string}
99
+ */
100
+ export function normalizeWebPath(path) {
101
+ if (!path) return "";
102
+ return path.replace(/\\/g, "/");
103
+ }
104
+
105
+ /**
106
+ * @param {string} path
107
+ * @returns {string}
108
+ */
109
+ export function ensureTrailingSlash(path) {
110
+ if (!path) return "";
111
+ return path.endsWith("/") ? path : path + "/";
112
+ }
113
+
114
+ /**
115
+ * @param {string} src
116
+ * @returns {string}
117
+ */
118
+ export function fixRelativeNewURL(src) {
119
+ src = src.replace(
120
+ /(?<==\s*)(["'])((?:(?:\.{1,2}\/)|\/)?ext\/[^"']*\/)\1/g,
121
+ (/** @type {string} */ match, /** @type {string} */ quote, /** @type {string} */ path) => {
122
+ const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
123
+ return `new URL(${quote}${runtimePath}${quote}, self.location?.href || ${quote}${quote}).href`;
124
+ }
125
+ );
126
+
127
+ src = src.replace(
128
+ /new\s+URL\s*\(\s*(["'`])((?:(?:\.{1,2}\/)|\/)?ext\/[^"'`]+)\1\s*\)/g,
129
+ (/** @type {string} */ match, /** @type {string} */ quote, /** @type {string} */ path) => {
130
+ const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
131
+ return `new URL(${quote}${runtimePath}${quote}, self.location?.href)`;
132
+ }
133
+ );
134
+
135
+ return src;
136
+ }
137
+
138
+ /**
139
+ * @param {string} src
140
+ * @returns {string}
141
+ */
142
+ export function fixDracoRangeQueryProbe(src) {
143
+ return src;
144
+ }
145
+
146
+ /**
147
+ * @param {string} path
148
+ * @param {string|Buffer} content
149
+ * @returns {string}
150
+ */
151
+ export function getValidFilename(path, content) {
152
+ if (path.startsWith("http:") || path.startsWith("https:")) {
153
+ const url = new URL(path);
154
+ const pathParts = url.pathname.split('/');
155
+ const filename = pathParts.pop() || 'file';
156
+ const nameParts = filename.split('.');
157
+ const nameWithoutExt = nameParts.slice(0, -1).join('.');
158
+ path = nameWithoutExt + "-" + createContentMd5(url.host + pathParts.join('/')) + "." + (nameParts.pop() || 'unknown');
159
+ }
160
+
161
+ let name = path.replace(/[^a-z0-9_\-\.\+]/gi, '-');
162
+
163
+ const maxLength = 200;
164
+ if (path.length > maxLength) {
165
+ const hash = createContentMd5(content);
166
+ let ext = "";
167
+ const extIndex = name.lastIndexOf('.');
168
+ if (extIndex !== -1) {
169
+ ext = name.substring(extIndex + 1);
170
+ name = name.substring(0, extIndex);
171
+ }
172
+ name = name.substring(0, maxLength) + "-" + hash + (ext ? "." + ext : '');
173
+ }
174
+ return name;
175
+ }
176
+
177
+ /**
178
+ * @param {string} str
179
+ * @returns {string}
180
+ */
181
+ function createContentMd5(str) {
182
+ return createHash('md5')
183
+ .update(str)
184
+ .digest('hex');
185
+ }
186
+
187
+ /**
188
+ * @param {string} url
189
+ * @returns {Promise<string>}
190
+ */
191
+ export function downloadText(url) {
192
+ return new Promise((res, rej) => {
193
+ const timer = setTimeout(() => {
194
+ rej(new Error("Download timed out after " + DOWNLOAD_TIMEOUT_MS + "ms: " + url));
195
+ }, DOWNLOAD_TIMEOUT_MS);
196
+
197
+ const req = (url.startsWith("http://") ? http.get(url, (response) => {
198
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
199
+ clearTimeout(timer);
200
+ return downloadText(response.headers.location).then(res).catch(rej);
201
+ }
202
+
203
+ if (response.statusCode !== 200) {
204
+ clearTimeout(timer);
205
+ clearSpinnerLine();
206
+ rej(new Error("Failed to download (" + response.statusCode + "): " + url));
207
+ return;
208
+ }
209
+
210
+ updateMakeLocalProgress(url);
211
+
212
+ let data = '';
213
+ response.on('data', (/** @type {Buffer|string} */ chunk) => {
214
+ data += chunk;
215
+ });
216
+ response.on('end', () => {
217
+ clearTimeout(timer);
218
+ res(data);
219
+ });
220
+ response.on('error', (err) => {
221
+ clearTimeout(timer);
222
+ rej(err);
223
+ });
224
+ }) : https.get(url, (response) => {
225
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
226
+ clearTimeout(timer);
227
+ return downloadText(response.headers.location).then(res).catch(rej);
228
+ }
229
+
230
+ if (response.statusCode !== 200) {
231
+ clearTimeout(timer);
232
+ clearSpinnerLine();
233
+ rej(new Error("Failed to download (" + response.statusCode + "): " + url));
234
+ return;
235
+ }
236
+
237
+ updateMakeLocalProgress(url);
238
+
239
+ let data = '';
240
+ response.on('data', (/** @type {Buffer|string} */ chunk) => {
241
+ data += chunk;
242
+ });
243
+ response.on('end', () => {
244
+ clearTimeout(timer);
245
+ res(data);
246
+ });
247
+ response.on('error', (err) => {
248
+ clearTimeout(timer);
249
+ rej(err);
250
+ });
251
+ }));
252
+ req.on('error', (err) => {
253
+ clearTimeout(timer);
254
+ clearSpinnerLine();
255
+ rej(err);
256
+ });
257
+ });
258
+ }
259
+
260
+ /**
261
+ * @param {string} url
262
+ * @returns {Promise<Buffer>}
263
+ */
264
+ export function downloadBinary(url) {
265
+ return new Promise((res, rej) => {
266
+ const timer = setTimeout(() => {
267
+ rej(new Error("Download timed out after " + DOWNLOAD_TIMEOUT_MS + "ms: " + url));
268
+ }, DOWNLOAD_TIMEOUT_MS);
269
+
270
+ const req = (url.startsWith("http://") ? http.get(url, (response) => {
271
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
272
+ clearTimeout(timer);
273
+ return downloadBinary(response.headers.location).then(res).catch(rej);
274
+ }
275
+
276
+ if (response.statusCode !== 200) {
277
+ clearTimeout(timer);
278
+ clearSpinnerLine();
279
+ rej(new Error("Failed to download (" + response.statusCode + "): " + url));
280
+ return;
281
+ }
282
+
283
+ updateMakeLocalProgress(url);
284
+
285
+ const chunks = /** @type {Buffer[]} */ ([]);
286
+ response.on('data', (/** @type {Buffer|string} */ chunk) => {
287
+ chunks.push(chunk);
288
+ });
289
+ response.on('end', () => {
290
+ clearTimeout(timer);
291
+ res(Buffer.concat(chunks));
292
+ });
293
+ response.on('error', (err) => {
294
+ clearTimeout(timer);
295
+ rej(err);
296
+ });
297
+ }) : https.get(url, (response) => {
298
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
299
+ clearTimeout(timer);
300
+ return downloadBinary(response.headers.location).then(res).catch(rej);
301
+ }
302
+
303
+ if (response.statusCode !== 200) {
304
+ clearTimeout(timer);
305
+ clearSpinnerLine();
306
+ rej(new Error("Failed to download (" + response.statusCode + "): " + url));
307
+ return;
308
+ }
309
+
310
+ updateMakeLocalProgress(url);
311
+
312
+ const chunks = /** @type {Buffer[]} */ ([]);
313
+ response.on('data', (/** @type {Buffer|string} */ chunk) => {
314
+ chunks.push(chunk);
315
+ });
316
+ response.on('end', () => {
317
+ clearTimeout(timer);
318
+ res(Buffer.concat(chunks));
319
+ });
320
+ response.on('error', (err) => {
321
+ clearTimeout(timer);
322
+ rej(err);
323
+ });
324
+ }));
325
+ req.on('error', (err) => {
326
+ clearTimeout(timer);
327
+ clearSpinnerLine();
328
+ rej(err);
329
+ });
330
+ });
331
+ }
332
+
333
+ /**
334
+ * @param {LocalizationContext} context
335
+ * @param {string} url
336
+ * @param {unknown} err
337
+ */
338
+ export function recordFailedDownload(context, url, err) {
339
+ if (!url || !context?.failedDownloads) return;
340
+ const message = err?.message ? String(err.message) : "";
341
+ if (!context.failedDownloads.has(url)) {
342
+ context.failedDownloads.set(url, message);
343
+ }
344
+ }
345
+
346
+ /**
347
+ * @param {string} url
348
+ * @returns {string}
349
+ */
350
+ export function getShortUrlName(url) {
351
+ try {
352
+ const parsed = new URL(url);
353
+ const parts = parsed.pathname.split("/").filter(Boolean);
354
+ return parts[parts.length - 1] || parsed.host;
355
+ }
356
+ catch {
357
+ return url;
358
+ }
359
+ }