@needle-tools/engine 4.15.0-next.f391a30 → 4.15.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 (150) hide show
  1. package/components.needle.json +1 -1
  2. package/dist/{gltf-progressive-CTlvpS3A.js → gltf-progressive-Bm_6aEi4.js} +1 -1
  3. package/dist/{gltf-progressive-CMwJPwEt.umd.cjs → gltf-progressive-BttGBXw6.umd.cjs} +1 -1
  4. package/dist/{gltf-progressive-DYL3SLVb.min.js → gltf-progressive-T5WKTux5.min.js} +1 -1
  5. package/dist/materialx-CJyQZtjt.min.js +90 -0
  6. package/dist/materialx-DMs1E08Z.js +4636 -0
  7. package/dist/materialx-DaKKOoVk.umd.cjs +90 -0
  8. package/dist/{needle-engine.bundle-DsTdfmeb.min.js → needle-engine.bundle-CBq_OMnI.min.js} +122 -124
  9. package/dist/{needle-engine.bundle-DB4kLWO_.js → needle-engine.bundle-DGyiwNWR.js} +3226 -3232
  10. package/dist/{needle-engine.bundle-C1BFRZDF.umd.cjs → needle-engine.bundle-JN3eiiYc.umd.cjs} +113 -115
  11. package/dist/needle-engine.d.ts +52 -33
  12. package/dist/needle-engine.js +288 -287
  13. package/dist/needle-engine.min.js +1 -1
  14. package/dist/needle-engine.umd.cjs +1 -1
  15. package/dist/{postprocessing-BN-f4viE.min.js → postprocessing-06AXuvdv.min.js} +1 -1
  16. package/dist/{postprocessing-De9ZpJrk.js → postprocessing-CI2x8Cln.js} +1 -1
  17. package/dist/{postprocessing-DYmYOVm4.umd.cjs → postprocessing-CPDcA21P.umd.cjs} +1 -1
  18. package/dist/{three-examples-BHqRVpO_.umd.cjs → three-examples-BMmNgNCN.umd.cjs} +12 -12
  19. package/dist/{three-examples-C0ZCCA_K.js → three-examples-CMYCd5nH.js} +192 -182
  20. package/dist/{three-examples-DmTY8tGr.min.js → three-examples-CQl1fFZp.min.js} +14 -14
  21. package/lib/engine/api.d.ts +2 -0
  22. package/lib/engine/api.js +2 -0
  23. package/lib/engine/api.js.map +1 -1
  24. package/lib/engine/debug/debug.js +1 -1
  25. package/lib/engine/debug/debug.js.map +1 -1
  26. package/lib/engine/debug/debug_spatial_console.js +1 -1
  27. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  28. package/lib/engine/engine_accessibility.d.ts +1 -1
  29. package/lib/engine/engine_accessibility.js +1 -1
  30. package/lib/engine/engine_accessibility.js.map +1 -1
  31. package/lib/engine/engine_context.d.ts +1 -1
  32. package/lib/engine/engine_context.js +2 -2
  33. package/lib/engine/engine_context.js.map +1 -1
  34. package/lib/engine/engine_create_objects.js +1 -1
  35. package/lib/engine/engine_create_objects.js.map +1 -1
  36. package/lib/engine/engine_gizmos.js +1 -1
  37. package/lib/engine/engine_gizmos.js.map +1 -1
  38. package/lib/engine/engine_license.js +2 -7
  39. package/lib/engine/engine_license.js.map +1 -1
  40. package/lib/engine/engine_test_utils.d.ts +39 -0
  41. package/lib/engine/engine_test_utils.js +84 -0
  42. package/lib/engine/engine_test_utils.js.map +1 -0
  43. package/lib/engine/engine_utils.js +2 -2
  44. package/lib/engine/engine_utils.js.map +1 -1
  45. package/lib/engine/export/gltf/index.js +1 -1
  46. package/lib/engine/export/gltf/index.js.map +1 -1
  47. package/lib/engine/webcomponents/logo-element.d.ts +3 -6
  48. package/lib/engine/webcomponents/logo-element.js +0 -18
  49. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  50. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  51. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  52. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +7 -10
  53. package/lib/engine/webcomponents/needle menu/needle-menu.js +4 -14
  54. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  55. package/lib/engine/webcomponents/needle-engine.ar-overlay.js +1 -10
  56. package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
  57. package/lib/engine/webcomponents/needle-engine.d.ts +0 -3
  58. package/lib/engine/webcomponents/needle-engine.js +0 -10
  59. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  60. package/lib/engine-components/Component.js +1 -0
  61. package/lib/engine-components/Component.js.map +1 -1
  62. package/lib/engine-components/ReflectionProbe.d.ts +2 -24
  63. package/lib/engine-components/ReflectionProbe.js +2 -28
  64. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  65. package/lib/engine-components/Skybox.js +2 -4
  66. package/lib/engine-components/Skybox.js.map +1 -1
  67. package/lib/engine-components/export/gltf/GltfExport.js +1 -1
  68. package/lib/engine-components/export/gltf/GltfExport.js.map +1 -1
  69. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +2 -2
  70. package/lib/engine-components/export/usdz/USDZExporter.js +1 -1
  71. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  72. package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js +2 -2
  73. package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js.map +1 -1
  74. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  75. package/lib/include/three/EXT_mesh_gpu_instancing_exporter.js.map +1 -0
  76. package/package.json +14 -18
  77. package/plugins/common/buildinfo.js +10 -46
  78. package/plugins/common/files.js +1 -2
  79. package/plugins/common/license.js +69 -144
  80. package/plugins/common/logger.js +11 -172
  81. package/plugins/common/worker.js +4 -5
  82. package/plugins/types/userconfig.d.ts +2 -40
  83. package/plugins/vite/alias.js +5 -6
  84. package/plugins/vite/asap.js +5 -6
  85. package/plugins/vite/build-pipeline.js +41 -224
  86. package/plugins/vite/buildinfo.js +6 -66
  87. package/plugins/vite/copyfiles.js +12 -41
  88. package/plugins/vite/custom-element-data.js +16 -26
  89. package/plugins/vite/defines.js +5 -8
  90. package/plugins/vite/dependencies.js +10 -16
  91. package/plugins/vite/dependency-watcher.js +7 -35
  92. package/plugins/vite/drop-client.js +5 -7
  93. package/plugins/vite/drop.js +14 -16
  94. package/plugins/vite/editor-connection.js +16 -18
  95. package/plugins/vite/imports-logger.js +2 -12
  96. package/plugins/vite/index.js +3 -8
  97. package/plugins/vite/local-files.js +441 -2
  98. package/plugins/vite/logger.client.js +35 -45
  99. package/plugins/vite/logger.js +3 -6
  100. package/plugins/vite/meta.js +4 -18
  101. package/plugins/vite/needle-app.js +3 -4
  102. package/plugins/vite/peer.js +1 -2
  103. package/plugins/vite/pwa.js +17 -33
  104. package/plugins/vite/reload.js +2 -24
  105. package/src/engine/api.ts +3 -0
  106. package/src/engine/debug/debug.ts +1 -1
  107. package/src/engine/debug/debug_spatial_console.ts +1 -5
  108. package/src/engine/engine_accessibility.ts +1 -2
  109. package/src/engine/engine_context.ts +2 -2
  110. package/src/engine/engine_create_objects.ts +1 -1
  111. package/src/engine/engine_gizmos.ts +5 -9
  112. package/src/engine/engine_license.ts +2 -7
  113. package/src/engine/engine_test_utils.ts +109 -0
  114. package/src/engine/engine_utils.ts +2 -2
  115. package/src/engine/export/gltf/index.ts +1 -1
  116. package/src/engine/webcomponents/logo-element.ts +3 -20
  117. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -6
  118. package/src/engine/webcomponents/needle menu/needle-menu.ts +11 -23
  119. package/src/engine/webcomponents/needle-engine.ar-overlay.ts +2 -13
  120. package/src/engine/webcomponents/needle-engine.ts +1 -13
  121. package/src/engine-components/Component.ts +2 -1
  122. package/src/engine-components/ReflectionProbe.ts +9 -33
  123. package/src/engine-components/Skybox.ts +2 -4
  124. package/src/engine-components/export/gltf/GltfExport.ts +1 -1
  125. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
  126. package/src/engine-components/export/usdz/USDZExporter.ts +1 -1
  127. package/src/engine-components/export/usdz/extensions/behavior/PhysicsExtension.ts +2 -2
  128. package/src/include/draco/draco_decoder.js +34 -0
  129. package/src/include/draco/draco_decoder.wasm +0 -0
  130. package/src/include/draco/draco_wasm_wrapper.js +117 -0
  131. package/src/include/ktx2/basis_transcoder.js +19 -0
  132. package/src/include/ktx2/basis_transcoder.wasm +0 -0
  133. package/src/include/needle/arial-msdf.json +1472 -0
  134. package/src/include/needle/arial.png +0 -0
  135. package/src/include/needle/poweredbyneedle.webp +0 -0
  136. package/dist/materialx-4jJLLe9Q.js +0 -4174
  137. package/dist/materialx-Bt9FHwco.min.js +0 -158
  138. package/dist/materialx-NDD0y4JY.umd.cjs +0 -158
  139. package/lib/engine/export/gltf/EXT_mesh_gpu_instancing_exporter.js.map +0 -1
  140. package/plugins/common/needle-engine-skill.md +0 -175
  141. package/plugins/vite/ai.js +0 -71
  142. package/plugins/vite/local-files-analysis.js +0 -789
  143. package/plugins/vite/local-files-core.js +0 -992
  144. package/plugins/vite/local-files-internals.js +0 -28
  145. package/plugins/vite/local-files-types.d.ts +0 -111
  146. package/plugins/vite/local-files-utils.js +0 -359
  147. package/plugins/vite/logging.js +0 -129
  148. /package/lib/{engine/export/gltf → include/three}/EXT_mesh_gpu_instancing_exporter.d.ts +0 -0
  149. /package/lib/{engine/export/gltf → include/three}/EXT_mesh_gpu_instancing_exporter.js +0 -0
  150. /package/src/{engine/export/gltf → include/three}/EXT_mesh_gpu_instancing_exporter.js +0 -0
@@ -1,6 +1,3 @@
1
- import { needleAI } from "./ai.js";
2
- export { needleAI } from "./ai.js";
3
-
4
1
  import { needleAsap } from "./asap.js";
5
2
  export { needleAsap } from "./asap.js";
6
3
 
@@ -46,8 +43,8 @@ export { needleTransformCodegen } from "./transform-codegen.js";
46
43
  import { needleLicense } from "./license.js";
47
44
  export { needleLicense } from "./license.js";
48
45
 
49
- import { needleMakeFilesLocal, needleLocalFilesSceneAnalysis } from "./local-files.js";
50
- export { needleMakeFilesLocal, needleLocalFilesSceneAnalysis } from "./local-files.js";
46
+ import { needleMakeFilesLocal } from "./local-files.js";
47
+ export { needleMakeFilesLocal } from "./local-files.js";
51
48
 
52
49
  import { needlePeerjs } from "./peer.js";
53
50
  export { needlePeerjs } from "./peer.js";
@@ -122,7 +119,6 @@ export const needlePlugins = async (command, config = undefined, userSettings =
122
119
  userSettings = { ...defaultUserSettings, ...userSettings };
123
120
 
124
121
  const array = [
125
- needleAI(command, config, userSettings),
126
122
  needleLogger(command, config, userSettings),
127
123
  needleDefines(command, config, userSettings),
128
124
  needleLicense(command, config, userSettings),
@@ -131,7 +127,6 @@ export const needlePlugins = async (command, config = undefined, userSettings =
131
127
  needleMeta(command, config, userSettings),
132
128
  needlePoster(command, config, userSettings),
133
129
  needleReload(command, config, userSettings),
134
- needleLocalFilesSceneAnalysis(command, config, userSettings),
135
130
  needleMakeFilesLocal(command, config, userSettings),
136
131
  needleBuild(command, config, userSettings),
137
132
  needleBuildInfo(command, config, userSettings),
@@ -140,6 +135,7 @@ export const needlePlugins = async (command, config = undefined, userSettings =
140
135
  needleTransformCode(command, config, userSettings),
141
136
  needleDrop(command, config, userSettings),
142
137
  needlePeerjs(command, config, userSettings),
138
+ needleDependencyWatcher(command, config, userSettings),
143
139
  needleDependencies(command, config, userSettings),
144
140
  vite_4_4_hack(command, config, userSettings),
145
141
  needleFacebookInstantGames(command, config, userSettings),
@@ -156,6 +152,5 @@ export const needlePlugins = async (command, config = undefined, userSettings =
156
152
  if(asap) array.push(asap);
157
153
 
158
154
  array.push(await editorConnection(command, config, userSettings, array));
159
- array.push(needleDependencyWatcher(command, config, userSettings));
160
155
  return array;
161
156
  }
@@ -1,2 +1,441 @@
1
- export { needleMakeFilesLocal, makeFilesLocalIsEnabled } from './local-files-core.js';
2
- export { needleLocalFilesSceneAnalysis } from './local-files-analysis.js';
1
+ import https from 'https';
2
+ import { createHash } from 'crypto';
3
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
4
+ import { resolve } from 'path';
5
+ import { start } from 'repl';
6
+
7
+ /**
8
+ * @typedef {{pluginContext:import('rollup').TransformPluginContext, cache:Cache, command:string, viteConfig:import("vite").ResolvedConfig | null}} Context
9
+ */
10
+
11
+ const debug = false;
12
+
13
+ /**
14
+ * Checks if the local files plugin is enabled in user settings.
15
+ * @param {import('../types/userconfig.js').userSettings} userSettings - The user settings object
16
+ */
17
+ export const makeFilesLocalIsEnabled = (userSettings) => {
18
+ if (typeof userSettings?.makeFilesLocal === "object") return userSettings?.makeFilesLocal?.enabled === true;
19
+ return userSettings?.makeFilesLocal === true;
20
+ }
21
+
22
+ /**
23
+ * Download files and rewrite code
24
+ * @param {string} command - The command that is being run
25
+ * @param {object} config - The config object
26
+ * @param {import('../types/userconfig.js').userSettings} userSettings
27
+ * @returns {import('vite').Plugin | null}
28
+ */
29
+ export const needleMakeFilesLocal = (command, config, userSettings) => {
30
+
31
+ if (!makeFilesLocalIsEnabled(userSettings)) {
32
+ return null;
33
+ }
34
+
35
+ console.log(`[needle:local-files] Local files plugin is enabled`);
36
+
37
+ const cache = new Cache();
38
+
39
+ /** @type {import("vite").ResolvedConfig | null} */
40
+ let viteConfig = null;
41
+
42
+ /** @type {import("vite").Plugin} */
43
+ const plugin = {
44
+ name: "needle:local-files",
45
+ // enforce: 'pre', // explictly DON'T define enforce:pre because of svelte postcss compat
46
+ apply: "build",
47
+ configResolved(config) {
48
+ viteConfig = config;
49
+ },
50
+ // transform bundle
51
+ async transform(src, _id) {
52
+ src = await makeLocal(src, "ext/", "", {
53
+ pluginContext: this,
54
+ cache: cache,
55
+ command: command,
56
+ viteConfig: viteConfig,
57
+ });
58
+ return {
59
+ code: src,
60
+ map: null,
61
+ };
62
+ },
63
+ buildEnd() {
64
+ const map = cache.map;
65
+ console.log(""); // Make a new line for better readability
66
+ console.log(`[needle:local-files] Made ${map.size} files local:`);
67
+ for (const [key, value] of map.entries()) {
68
+ console.log(`- ${key} → ${value}`);
69
+ }
70
+ }
71
+ }
72
+ return plugin;
73
+ }
74
+
75
+ /**
76
+ * Rewrites the source code to make local files
77
+ * @param {string} src - The source code to rewrite
78
+ * @param {string} basePath - The base path where the files will be saved
79
+ * @param {string} currentDir - The current directory of the file being processed
80
+ * @param {Context} context - The Vite plugin context and command
81
+ */
82
+ async function makeLocal(src, basePath, currentDir, context) {
83
+
84
+ const command = context.command;
85
+
86
+ if (debug) {
87
+ // Find all urls in the source code.
88
+ // Exclude URLs inside comments, like:
89
+ // - // https://example.com
90
+ // - /* ... https://example.com ... */
91
+ // - @link https://example.com
92
+ // - * ... https://example.com
93
+ const urlRegexExcludingComments = /(?<!\/\/.*)(?<!\/\*.*)(?<!@link\s+)(["'])(https?:\/\/[^\s'"]+?)\1/g;
94
+ let match0;
95
+ while ((match0 = urlRegexExcludingComments.exec(src)) !== null) {
96
+ const url = match0[2];
97
+ if (debug) console.log(`\nFound URL: ${url} in ${currentDir}`);
98
+ }
99
+ }
100
+
101
+ // Google Fonts URLs
102
+ while (true) {
103
+ const match = /["'](https:\/\/fonts\.googleapis\.com\/.+?)["']/g.exec(src);
104
+ if (match === null) {
105
+ break;
106
+ }
107
+ const url = match[1];
108
+ if (debug) console.log(`\nFound google font URL: ${url}`);
109
+ // Check if the font URL is already in the cache
110
+ const cachedPath = context.cache.getFromCache(url);
111
+ if (cachedPath) {
112
+ if (debug) console.log(`Using cached font URL: ${cachedPath}`);
113
+ src = src.replace(url, cachedPath);
114
+ continue; // Skip downloading if already cached
115
+ }
116
+ let font = await downloadText(url);
117
+ const familyNameMatch = /family=([^&]+)/.exec(url);
118
+ const familyName = familyNameMatch ? getValidFilename(familyNameMatch[1], font) : (new URL(url).pathname.split('/').pop());
119
+ font = await makeLocal(font, basePath, basePath, context);
120
+ const fontFileName = `font-${familyName}.css`;
121
+ const outputPath = basePath + fontFileName;
122
+ let newPath;
123
+ if (command === 'build') {
124
+ const referenceId = context.pluginContext.emitFile({
125
+ type: 'asset',
126
+ fileName: outputPath,
127
+ source: font,
128
+ });
129
+ const localPath = `${context.pluginContext.getFileName(referenceId)}`;
130
+ newPath = getRelativeToBasePath(localPath, currentDir);
131
+ // ensureFileExists(localPath, font);
132
+ }
133
+ else {
134
+ // Create base64 URL for dev mode
135
+ const base64Font = Buffer.from(font).toString('base64');
136
+ newPath = `data:text/css;base64,${base64Font}`;
137
+ }
138
+ if (newPath) {
139
+ context.cache.addToCache(url, newPath);
140
+ src = src.replace(url, newPath);
141
+ }
142
+ }
143
+
144
+ // Google Fonts gstatic URLs
145
+ while (true) {
146
+ const match = /["'(](https:\/\/fonts\.gstatic\.com\/)(.+?)["')]/g.exec(src);
147
+ if (match === null) {
148
+ break;
149
+ }
150
+ const fontPath = match[2];
151
+ const url = match[1] + fontPath;
152
+ if (debug) console.log(`\nFound gstatic URL: ${url}`);
153
+ // Check if the font URL is already in the cache
154
+ const cachedPath = context.cache.getFromCache(url);
155
+ if (cachedPath) {
156
+ if (debug) console.log(`Using cached gstatic font URL: ${cachedPath}`);
157
+ src = src.replace(url, cachedPath);
158
+ continue; // Skip downloading if already cached
159
+ }
160
+ const font = await downloadBinary(url);
161
+ const filename = getValidFilename(fontPath, font);
162
+ if (debug) console.log(`Saving font to: ${basePath + filename}`);
163
+ let newPath;
164
+ if (command === 'build') {
165
+ const referenceId = context.pluginContext.emitFile({
166
+ type: 'asset',
167
+ fileName: basePath + filename,
168
+ source: font,
169
+ });
170
+ const localPath = `${context.pluginContext.getFileName(referenceId)}`;
171
+ newPath = getRelativeToBasePath(localPath, currentDir);
172
+ // ensureFileExists(localPath, font);
173
+ } else {
174
+ // Create base64 URL for dev mode
175
+ const base64Font = Buffer.from(font).toString('base64');
176
+ newPath = `data:text/css;base64,${base64Font}`;
177
+ }
178
+ context.cache.addToCache(url, newPath);
179
+ src = src.replace(url, newPath);
180
+ }
181
+
182
+ // Load QRCode.js
183
+ while (true) {
184
+ // https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs@gh-pages/qrcode.min.js
185
+ const match = /["'](https:\/\/cdn\.jsdelivr\.net\/gh\/davidshimjs\/qrcodejs@[^'"]+?\/qrcode\.min\.js)["']/g.exec(src);
186
+ if (match === null) {
187
+ break;
188
+ }
189
+ const url = match[1];
190
+ if (debug) console.log(`\nFound QR code URL: ${url}`);
191
+ // Check if the QR code URL is already in the cache
192
+ const cachedPath = context.cache.getFromCache(url);
193
+ if (cachedPath) {
194
+ if (debug) console.log(`Using cached QR code URL: ${cachedPath}`);
195
+ src = src.replace(url, cachedPath);
196
+ continue; // Skip downloading if already cached
197
+ }
198
+ const qrCode = await downloadBinary(url);
199
+ const filename = getValidFilename(url, qrCode);
200
+ if (debug) console.log(`Saving QR code to: ${basePath + filename}`);
201
+ let newPath;
202
+ if (command === 'build') {
203
+ const referenceId = context.pluginContext.emitFile({
204
+ type: 'asset',
205
+ fileName: basePath + filename,
206
+ source: qrCode,
207
+ });
208
+ const localPath = `${context.pluginContext.getFileName(referenceId)}`;
209
+ newPath = getRelativeToBasePath(localPath, currentDir);
210
+ // ensureFileExists(localPath, qrCode);
211
+ } else {
212
+ // create base64 URL for dev mode
213
+ const base64QrCode = Buffer.from(qrCode).toString('base64');
214
+ newPath = `data:application/javascript;base64,${base64QrCode}`;
215
+ }
216
+ context.cache.addToCache(url, newPath);
217
+ src = src.replace(url, newPath);
218
+ }
219
+
220
+ // Polyhaven.org URLs
221
+ let startIndex = 0;
222
+ while (true) {
223
+ const match = /["'](https:\/\/dl\.polyhaven\.org\/file\/.+?)["']/g.exec(src.slice(startIndex));
224
+ if (match === null) {
225
+ break;
226
+ }
227
+ startIndex += match.index + match[0].length; // Update startIndex to continue searching
228
+ const url = match[1];
229
+ if (url.endsWith("/")) {
230
+ if (debug) console.warn(`Skipping Polyhaven URL that ends with a slash: ${url}`);
231
+ continue; // Skip URLs that end with a slash
232
+ }
233
+ if (url.includes("\"") || url.includes("'")) {
234
+ if (debug) console.warn(`Skipping Polyhaven URL with quotes: ${url}`);
235
+ continue; // Skip URLs with quotes
236
+ }
237
+ if (debug) console.log(`\nFound Polyhaven URL: ${url}`);
238
+ // Check if the Polyhaven URL is already in the cache
239
+ const cachedPath = context.cache.getFromCache(url);
240
+ if (cachedPath) {
241
+ if (debug) console.log(`Using cached Polyhaven URL: ${cachedPath}`);
242
+ src = src.replace(url, cachedPath);
243
+ continue; // Skip downloading if already cached
244
+ }
245
+ const polyhavenFile = await downloadBinary(url);
246
+ const filename = getValidFilename(url, polyhavenFile);
247
+ if (debug) console.log(`Saving Polyhaven file to: ${basePath + filename}`);
248
+ let newPath;
249
+ if (command === 'build') {
250
+ const referenceId = context.pluginContext.emitFile({
251
+ type: 'asset',
252
+ fileName: basePath + filename,
253
+ source: polyhavenFile,
254
+ });
255
+ const localPath = `${context.pluginContext.getFileName(referenceId)}`;
256
+ newPath = getRelativeToBasePath(localPath, currentDir);
257
+ // ensureFileExists(localPath, polyhavenFile);
258
+ } else {
259
+ // Create base64 URL for dev mode
260
+ const base64PolyhavenFile = Buffer.from(polyhavenFile).toString('base64');
261
+ newPath = `data:application/octet-stream;base64,${base64PolyhavenFile}`;
262
+ }
263
+ if (newPath) {
264
+ context.cache.addToCache(url, newPath);
265
+ src = src.replace(url, newPath);
266
+ }
267
+ }
268
+
269
+ return src;
270
+
271
+ // /**
272
+ // * Ensures that a file exists at the specified relative path with the given content.
273
+ // * If the file does not exist, it will be created with the provided content.
274
+ // * @param {string} relPath - The relative path to the file
275
+ // * @param {string|Uint8Array} content - The content to write to the file
276
+ // * @returns {void}
277
+ // */
278
+ // function ensureFileExists(relPath, content) {
279
+ // const outputPath = context.viteConfig?.build?.outDir || "dist";
280
+ // const fullPath = resolve(outputPath, relPath);
281
+ // if (!existsSync(fullPath)) {
282
+ // if (debug) console.log(`Creating file: ${fullPath}`);
283
+ // const dir = resolve(fullPath, '..');
284
+ // mkdirSync(dir, { recursive: true });
285
+ // writeFileSync(fullPath, content);
286
+ // }
287
+ // }
288
+ }
289
+
290
+
291
+ class Cache {
292
+ __cache = new Map();
293
+ /**
294
+ * Adds a key-value pair to the cache.
295
+ * @param {string} key - The key to store the value under
296
+ * @param {string|Uint8Array} value - The value to store in the cache
297
+ * @returns {void}
298
+ */
299
+ addToCache(key, value) {
300
+ if (this.__cache.has(key)) {
301
+ if (debug) console.warn(`Key ${key} already exists in cache, overwriting.`);
302
+ }
303
+ this.__cache.set(key, value);
304
+ }
305
+ /**
306
+ * Retrieves a value from the cache by its key.
307
+ * @param {string} key - The key to look up in the cache
308
+ */
309
+ getFromCache(key) {
310
+ if (this.__cache.has(key)) {
311
+ return this.__cache.get(key);
312
+ } else {
313
+ return null;
314
+ }
315
+ }
316
+ get map() {
317
+ return this.__cache;
318
+ }
319
+ }
320
+
321
+
322
+ /**
323
+ * Returns a relative path based on the base path.
324
+ * @param {string} path - The path to check
325
+ * @param {string | undefined | null} basePath - The base path to compare against
326
+ * @return {string} - The relative path if it starts with the base path, otherwise the original path
327
+ */
328
+ function getRelativeToBasePath(path, basePath) {
329
+ if (basePath?.length && path.startsWith(basePath)) {
330
+ return "./" + path.substring(basePath.length);
331
+ }
332
+ return path;
333
+ }
334
+
335
+
336
+ /**
337
+ * Generates a valid filename from a given path.
338
+ * @param {string} path - The path to generate a filename from
339
+ * @param {string|Uint8Array} content - The content to hash for uniqueness (not used in this example)
340
+ */
341
+ function getValidFilename(path, content) {
342
+ if (path.startsWith("http:") || path.startsWith("https:")) {
343
+ const url = new URL(path);
344
+ const pathParts = url.pathname.split('/');
345
+ const filename = pathParts.pop() || 'file';
346
+ const nameParts = filename.split('.');
347
+ const nameWithoutExt = nameParts.slice(0, -1).join('.');
348
+ path = `${nameWithoutExt}-${createContentMd5(url.host + pathParts.join('/'))}.${nameParts.pop() || 'unknown'}`;
349
+ }
350
+
351
+ // Remove any characters that are not valid in filenames
352
+ let name = path.replace(/[^a-z0-9_\-\.\+]/gi, '-');
353
+
354
+ const maxLength = 200;
355
+ if (path.length > maxLength) {
356
+ // If the name is too long, hash it to create a unique identifier
357
+ const hash = createContentMd5(content);
358
+ let ext = "";
359
+ const extIndex = name.lastIndexOf('.');
360
+ if (extIndex !== -1) {
361
+ ext = name.substring(extIndex + 1);
362
+ name = name.substring(0, extIndex);
363
+ }
364
+ name = `${name.substring(0, maxLength)}-${hash}${ext ? `.${ext}` : ''}`;
365
+ }
366
+ return name;
367
+
368
+ }
369
+
370
+ /**
371
+ * Creates a hash of the content using MD5.
372
+ * @param {string|Uint8Array} str - The content to hash
373
+ */
374
+ function createContentMd5(str) {
375
+ return createHash('md5')
376
+ .update(str)
377
+ .digest('hex');
378
+ }
379
+
380
+
381
+ /**
382
+ * @param {string} url - The URL of the font to download
383
+ */
384
+ function downloadText(url) {
385
+ return new Promise(((res, rej) => {
386
+ https.get(url, (response) => {
387
+ if (response.statusCode !== 200) {
388
+ console.log();
389
+ console.error(`Failed to download (${response.statusCode}): ${url}`);
390
+ rej(new Error(`Failed to download (${response.statusCode}): ${url}`));
391
+ return;
392
+ }
393
+
394
+ console.log(""); // Make a new line for better readability
395
+ console.log(`[needle:local-files] Make local: ${url}`);
396
+
397
+ let data = '';
398
+ response.on('data', (chunk) => {
399
+ data += chunk;
400
+ });
401
+ response.on('end', () => {
402
+ // Here you can save the data to a file or process it as needed
403
+ if (debug) console.log(`Downloaded from ${url}`);
404
+ res(data);
405
+ });
406
+ });
407
+ }))
408
+ }
409
+
410
+ function downloadBinary(url) {
411
+ return new Promise((res, rej) => {
412
+ https.get(url, (response) => {
413
+
414
+ // Handle redirects
415
+ if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
416
+ if (debug) console.log(`Redirecting to ${response.headers.location}`);
417
+ return downloadBinary(response.headers.location).then(res).catch(rej);
418
+ }
419
+
420
+ if (response.statusCode !== 200) {
421
+ console.log();
422
+ console.error(`Failed to download (${response.statusCode}): ${url}`);
423
+ rej(new Error(`Failed to download (${response.statusCode}): ${url}`));
424
+ return;
425
+ }
426
+
427
+ console.log(""); // Make a new line for better readability
428
+ console.log(`[needle:local-files] Make local: ${url}`);
429
+
430
+ const chunks = [];
431
+ response.on('data', (chunk) => {
432
+ chunks.push(chunk);
433
+ });
434
+ response.on('end', () => {
435
+ // Here you can save the data to a file or process it as needed
436
+ if (debug) console.log(`Downloaded from ${url}`);
437
+ res(Buffer.concat(chunks));
438
+ });
439
+ });
440
+ });
441
+ }
@@ -1,4 +1,3 @@
1
- // @ts-check
2
1
  import path from "path";
3
2
 
4
3
  let isStringifying = false;
@@ -7,7 +6,7 @@ let isStringifying = false;
7
6
  * Patches console methods to capture log messages and send them to the server.
8
7
  * This is useful for debugging and logging in the client.
9
8
  * @param {"log" | "warn" | "info" | "debug" | "error" | "internal"} level
10
- * @param {...unknown} message - The log message to capture.
9
+ * @param {any} message - The log message to capture.
11
10
  */
12
11
  function sendLogToServer(level, ...message) {
13
12
  if (isStringifying) return;
@@ -15,20 +14,20 @@ function sendLogToServer(level, ...message) {
15
14
  try {
16
15
  isStringifying = true;
17
16
  // console.time("sendLogToServer");
18
- let msg = /** @type {string} */ (stringifyLog(message));
17
+ message = stringifyLog(message);
19
18
  // console.timeEnd("sendLogToServer");
20
19
  // keep messages below payload limit
21
- if (msg.length > 100_000) {
22
- msg = msg.slice(0, 100_000) + "... <truncated>";
20
+ if (message.length > 100_000) {
21
+ message = message.slice(0, 100_000) + "... <truncated>";
23
22
  }
24
23
  // @ts-ignore
25
- import.meta.hot.send("needle:client-log", { level, message: msg });
26
- } catch (/** @type {{ message?: string }} */ e) {
24
+ import.meta.hot.send("needle:client-log", { level, message: message });
25
+ } catch (e) {
27
26
  // silently fail but send a message
28
27
  try {
29
28
  import.meta.hot.send("needle:client-log", { level: "error", message: `Error during logging: ${e.message}` });
30
29
  }
31
- catch (/** @type {unknown} */ e2) {
30
+ catch (e2) {
32
31
  // fallback failed as well
33
32
  }
34
33
  }
@@ -38,7 +37,6 @@ function sendLogToServer(level, ...message) {
38
37
  }
39
38
  }
40
39
 
41
- /** @param {(...args: unknown[]) => void} fn @param {unknown[]} args */
42
40
  function logHelper(fn, args) {
43
41
  const error = new Error();
44
42
  const stack = error.stack;
@@ -54,32 +52,32 @@ function logHelper(fn, args) {
54
52
  if (import.meta && "hot" in import.meta) {
55
53
 
56
54
  function patchLogs() {
57
- const originalLog = /** @type {(...args: unknown[]) => void} */ (console.log.bind(console));
58
- const originalWarn = /** @type {(...args: unknown[]) => void} */ (console.warn.bind(console));
59
- const originalInfo = /** @type {(...args: unknown[]) => void} */ (console.info.bind(console));
60
- const originalDebug = /** @type {(...args: unknown[]) => void} */ (console.debug.bind(console));
61
- const originalError = /** @type {(...args: unknown[]) => void} */ (console.error.bind(console));
55
+ const originalLog = console.log.bind(console);
56
+ const originalWarn = console.warn.bind(console);
57
+ const originalInfo = console.info.bind(console);
58
+ const originalDebug = console.debug.bind(console);
59
+ const originalError = console.error.bind(console);
62
60
 
63
- console.log = /** @type {(...args: unknown[]) => void} */ (function (...args) {
61
+ console.log = function (...args) {
64
62
  logHelper(originalLog, args);
65
63
  sendLogToServer("log", ...args);
66
- })
67
- console.warn = /** @type {(...args: unknown[]) => void} */ ((...args) => {
64
+ }
65
+ console.warn = (...args) => {
68
66
  logHelper(originalWarn, args);
69
67
  sendLogToServer("warn", ...args);
70
- })
71
- console.info = /** @type {(...args: unknown[]) => void} */ ((...args) => {
68
+ }
69
+ console.info = (...args) => {
72
70
  logHelper(originalInfo, args);
73
71
  sendLogToServer("info", ...args);
74
- })
75
- console.debug = /** @type {(...args: unknown[]) => void} */ ((...args) => {
72
+ }
73
+ console.debug = (...args) => {
76
74
  logHelper(originalDebug, args);
77
75
  sendLogToServer("debug", ...args);
78
- })
79
- console.error = /** @type {(...args: unknown[]) => void} */ ((...args) => {
76
+ }
77
+ console.error = (...args) => {
80
78
  logHelper(originalError, args);
81
79
  sendLogToServer("error", ...args);
82
- })
80
+ }
83
81
  return () => {
84
82
  console.log = originalLog;
85
83
  console.warn = originalWarn;
@@ -127,12 +125,11 @@ Connection: ${"connection" in navigator ? JSON.stringify(navigator.connection) :
127
125
  User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.userActivation) : "Not available"}
128
126
  `);
129
127
 
130
- /** @typedef {{ vendor?: string, architecture?: string, device?: string, description?: string, features?: unknown, limits?: unknown }} GPUAdapterInfoLike */
131
128
  if ("gpu" in navigator) {
132
129
  // @ts-ignore
133
130
  navigator.gpu.requestAdapter()
134
- .then(/** @type {(adapter: {requestDevice(): Promise<{adapterInfo: GPUAdapterInfoLike}>}|null) => Promise<{adapterInfo: GPUAdapterInfoLike}>|null} */ (adapter) => adapter ? adapter.requestDevice() : null)
135
- .then(/** @type {(device: {adapterInfo: GPUAdapterInfoLike}|null) => void} */ (device) => {
131
+ .then(adapter => adapter ? adapter.requestDevice() : null)
132
+ .then(device => {
136
133
  if (device) {
137
134
  const adapterInfo = device.adapterInfo;
138
135
  if (adapterInfo) {
@@ -149,21 +146,17 @@ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.user
149
146
  });
150
147
  }
151
148
  }
152
- catch (/** @type {{ message?: string }} */ e) {
149
+ catch (e) {
153
150
  // silently fail
154
151
  sendLogToServer("error", `Error during initial log: ${e.message}`);
155
152
  }
156
153
 
157
154
  window.addEventListener('error', (event) => {
158
- const typedErrorEvent = /** @type {{ error: unknown, message: string }} */ (/** @type {unknown} */ (event));
159
- const error = /** @type {{ stack?: string, message?: string } | null | undefined} */ (typedErrorEvent.error);
160
- const errorMessage = error ? error.stack || error.message : typedErrorEvent.message;
155
+ const errorMessage = event.error ? event.error.stack || event.error.message : event.message;
161
156
  sendLogToServer("error", errorMessage);
162
157
  });
163
158
  window.addEventListener('unhandledrejection', (event) => {
164
- const typedRejEvent = /** @type {{ reason: unknown }} */ (/** @type {unknown} */ (event));
165
- const rejectionReason = /** @type {{ stack?: string, message?: string } | null | undefined} */ (typedRejEvent.reason);
166
- const reason = rejectionReason ? rejectionReason.stack || rejectionReason.message : "Unhandled rejection without reason";
159
+ const reason = event.reason ? event.reason.stack || event.reason.message : "Unhandled rejection without reason";
167
160
  sendLogToServer("error", `Unhandled promise rejection: ${reason}`);
168
161
  });
169
162
  window.addEventListener('beforeunload', () => {
@@ -218,7 +211,7 @@ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.user
218
211
  sendLogToServer("internal", `URL hash changed to ${location.hash}`);
219
212
  });
220
213
  window.addEventListener('popstate', () => {
221
- sendLogToServer("internal", `History state changed: ${JSON.stringify(/** @type {{ state: unknown }} */ (history).state)}`);
214
+ sendLogToServer("internal", `History state changed: ${JSON.stringify(history.state)}`);
222
215
  });
223
216
 
224
217
 
@@ -234,10 +227,10 @@ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.user
234
227
 
235
228
  /**
236
229
  * Stringifies a log message, handling circular references and formatting.
237
- * @param {unknown} log
238
- * @param {Set<unknown>} [seen]
230
+ * @param {any} log
231
+ * @param {Set<any>} [seen]
239
232
  */
240
- function stringifyLog(log, seen = /** @type {Set<unknown>} */ (new Set()), depth = 0) {
233
+ function stringifyLog(log, seen = new Set(), depth = 0) {
241
234
  const isServer = typeof window === "undefined";
242
235
  const stringify_limits = {
243
236
  string: isServer ? 100_000 : 1_000,
@@ -279,9 +272,8 @@ function stringifyLog(log, seen = /** @type {Set<unknown>} */ (new Set()), depth
279
272
  || log instanceof BigUint64Array
280
273
  || log instanceof Float64Array
281
274
  ) {
282
- const logArr = /** @type {ArrayLike<unknown>} */ (/** @type {unknown} */ (log));
283
- seen.add(logArr);
284
- return stringifyArray(logArr);
275
+ seen.add(log);
276
+ return stringifyArray(log);
285
277
  }
286
278
  if (typeof log === "object") {
287
279
 
@@ -295,12 +287,11 @@ function stringifyLog(log, seen = /** @type {Set<unknown>} */ (new Set()), depth
295
287
  return `<Error: ${log.message}\nStack: ${log.stack}>`;
296
288
  }
297
289
 
298
- const logObj = /** @type {Record<string, unknown>} */ (log);
299
- const keys = Object.keys(logObj);
290
+ const keys = Object.keys(log);
300
291
  let res = "{";
301
292
  for (let i = 0; i < keys.length; i++) {
302
293
  const key = keys[i];
303
- let value = logObj[key];
294
+ let value = log[key];
304
295
  if (i >= stringify_limits.object_keys) {
305
296
  res += `, ... <truncated ${keys.length - i} keys>`;
306
297
  break;
@@ -335,7 +326,6 @@ function stringifyLog(log, seen = /** @type {Set<unknown>} */ (new Set()), depth
335
326
 
336
327
  return String(log);
337
328
 
338
- /** @param {ArrayLike<unknown>} arr @returns {string} */
339
329
  function stringifyArray(arr) {
340
330
  let res = "";
341
331
  for (let i = 0; i < arr.length; i++) {