@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,14 +1,9 @@
1
1
  import { ChildProcess, exec } from 'child_process';
2
2
  import { NEEDLE_CLOUD_CLI_NAME } from '../common/cloud.js';
3
3
  import { getOutputDirectory, loadConfig } from './config.js';
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'fs';
5
- import { relative } from 'path';
4
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
6
5
  import { copyFilesSync } from '../common/files.js';
7
6
  import { delay } from '../common/timers.js';
8
- import { needleBlue, needleDim, needleLog, needleSupportsColor, setTransientLogLineCleaner } from './logging.js';
9
-
10
- const PIPELINE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
11
- const PIPELINE_STRUCTURED_LOG_PREFIX = "__needle_pipeline_log__:";
12
7
 
13
8
  /**
14
9
  * @param {import('../types').userSettings} config
@@ -40,12 +35,10 @@ env:
40
35
  // see https://linear.app/needle/issue/NE-3798
41
36
 
42
37
 
43
- /** @type {Promise<void>|null} */
38
+ /** @type {Promise<any>|null} */
44
39
  let buildPipelineTask;
45
40
  /** @type {null | {tempDirectory:string, outputDirectory:string}} */
46
41
  let buildPipelineTaskResults = null;
47
- /** @type {null | string} */
48
- let buildPipelineStepSummary = null;
49
42
 
50
43
  export function waitForBuildPipelineToFinish() {
51
44
  return buildPipelineTask;
@@ -107,9 +100,7 @@ export const needleBuildPipeline = async (command, config, userSettings) => {
107
100
  }
108
101
 
109
102
  if (!shouldRun) {
110
- if (command === "build") {
111
- log("Skipping build pipeline because this is a development build.\n- Invoke with `--production` to run the build pipeline.\n- For example \"vite build -- --production\".");
112
- }
103
+ log("Skipping build pipeline because this is a development build.\n- Invoke with `--production` to run the build pipeline.\n- For example \"vite build -- --production\".");
113
104
  await new Promise((resolve, _) => setTimeout(resolve, 1000));
114
105
  return null;
115
106
  }
@@ -122,7 +113,6 @@ export const needleBuildPipeline = async (command, config, userSettings) => {
122
113
 
123
114
  const verboseOutput = userSettings?.buildPipeline?.verbose || false;
124
115
  let taskHasCompleted = false;
125
- let taskSucceeded = false;
126
116
 
127
117
  return {
128
118
  name: 'needle:buildpipeline',
@@ -158,22 +148,26 @@ export const needleBuildPipeline = async (command, config, userSettings) => {
158
148
  log("Build pipeline already running...");
159
149
  return;
160
150
  }
151
+ let taskSucceeded = false;
161
152
  // start the compression process once vite is done copying the files
162
153
  buildPipelineTask = invokeBuildPipeline(userSettings, { verbose: verboseOutput })
163
154
  .then((res) => {
164
155
  if (verboseOutput) log("Build pipeline task result:", res);
165
156
  taskSucceeded = res;
166
157
  })
167
- .catch((/** @type {{ message?: string }} */ err) => {
168
- needleLog("needle-buildpipeline", "- Error during build pipeline: " + err.message, "error");
158
+ .catch(err => {
159
+ console.error("[needle-buildpipeline] - Error during build pipeline: " + err.message);
169
160
  if (verboseOutput) log("Error details:", err);
170
161
  })
171
162
  .finally(() => {
172
163
  taskHasCompleted = true;
173
164
  if (!taskSucceeded) {
174
- needleLog("needle-buildpipeline", "- Build pipeline task did not succeed.", "error");
165
+ console.error("[needle-buildpipeline] - Build pipeline task did not succeed.");
175
166
  throw new Error("[needle-buildpipeline] - Build pipeline failed. Please check the logs above for more information.");
176
167
  }
168
+ else {
169
+ log("Finished successfully");
170
+ }
177
171
  });
178
172
  }
179
173
  },
@@ -181,34 +175,21 @@ export const needleBuildPipeline = async (command, config, userSettings) => {
181
175
  if (!buildPipelineTask) {
182
176
  return;
183
177
  }
178
+ if (!taskHasCompleted) {
179
+ log("Waiting for build pipeline to finish...");
180
+ }
184
181
  // // this is the last hook that is called, so we can wait for the task to finish here
185
182
  return buildPipelineTask = buildPipelineTask?.then(() => {
186
- const lines = /** @type {string[]} */ ([]);
187
- if (buildPipelineStepSummary) {
188
- lines.push(buildPipelineStepSummary);
189
- }
190
- if (taskSucceeded) {
191
- lines.push(needleDim("✓ Finished successfully"));
192
- }
193
183
  // Copy the results to their final output directory.
194
184
  if (buildPipelineTaskResults != null) {
195
- const supportsColor = needleSupportsColor();
196
- const key = (/** @type {string} */ text) => supportsColor ? needleBlue(text) : text;
197
- const outputPath = relative(process.cwd(), buildPipelineTaskResults.outputDirectory).replaceAll("\\", "/") || ".";
198
- const moved = getDirectoryStats(buildPipelineTaskResults.tempDirectory);
199
- lines.push(`${key("Copying files")}: \"${outputPath}\"`);
200
- const ctx = { count: 0, bytes: 0 }
185
+ log(`Copying files from temporary output directory to final output directory at \"${buildPipelineTaskResults.outputDirectory}\"`);
186
+ const ctx = { count: 0 }
201
187
  copyFilesSync(buildPipelineTaskResults.tempDirectory, buildPipelineTaskResults.outputDirectory, true, ctx);
202
- lines.push(`${key("Copied files")}: ${ctx.count}`);
203
- lines.push(`${key("Data moved")}: ${formatBytes(moved.totalBytes)} (${moved.fileCount} files)`);
188
+ log(`Copied ${ctx.count} file(s)`);
204
189
  }
205
190
  else {
206
- lines.push("No files to copy - build pipeline did not run or did not finish successfully");
191
+ log("No files to copy - build pipeline did not run or did not finish successfully");
207
192
  }
208
- if (lines.length > 0) {
209
- needleLog("needle-buildpipeline", lines.join("\n"), "log", { dimBody: false });
210
- }
211
- buildPipelineStepSummary = null;
212
193
  });
213
194
  },
214
195
  }
@@ -235,31 +216,21 @@ async function fixPackageJson(packageJsonPath) {
235
216
  writeFileSync(packageJsonPath, fixed);
236
217
  }
237
218
 
238
- /** @param {...unknown} args */
219
+ /** @param {any} args */
239
220
  function log(...args) {
240
- needleLog("needle-buildpipeline", args.join(" "));
221
+ console.log("[needle-buildpipeline]", ...args);
241
222
  }
242
- /** @param {...unknown} args */
223
+ /** @param {any} args */
243
224
  function warn(...args) {
244
- needleLog("needle-buildpipeline", args.join(" "), "warn");
225
+ console.warn("WARN: [needle-buildpipeline]", ...args);
245
226
  }
246
227
 
247
- /**
248
- * @typedef {{ event?: string, phase?: string, target?: string, message?: string, level?: string }} BuildPipelinePayload
249
- */
250
228
  /**
251
229
  * @param {import('../types').userSettings} opts
252
230
  * @param {{verbose?:boolean}} [options]
253
231
  * @returns {Promise<boolean>}
254
232
  */
255
233
  async function invokeBuildPipeline(opts, options = {}) {
256
- const rel = (/** @type {string} */ pathValue) => {
257
- const value = relative(process.cwd(), pathValue).replaceAll("\\", "/");
258
- return value?.length ? value : ".";
259
- };
260
- const supportsColor = needleSupportsColor();
261
- const key = (/** @type {string} */ text) => supportsColor ? needleBlue(text) : text;
262
-
263
234
  const installPath = "node_modules/@needle-tools/gltf-build-pipeline";
264
235
  const fullInstallPath = process.cwd() + "/" + installPath;
265
236
  const existsLocally = existsSync(fullInstallPath);
@@ -286,7 +257,7 @@ async function invokeBuildPipeline(opts, options = {}) {
286
257
  log("Max wait time exceeded - aborting...");
287
258
  return Promise.resolve(false);
288
259
  }
289
- if (iteration <= 0) needleLog("needle-buildpipeline", `Waiting for output directory to be created... (${outputDirectory})`, "log", { leadingNewline: true });
260
+ if (iteration <= 0) log(`Waiting for output directory to be created... (${outputDirectory})`);
290
261
  return delay(1000).then(() => waitForOutputDirectory(iteration + 1));
291
262
  }
292
263
  if (options?.verbose) log(`Output directory found after ${iteration} iteration(s) at "${outputDirectory}" - continuing...`);
@@ -297,19 +268,7 @@ async function invokeBuildPipeline(opts, options = {}) {
297
268
  return false;
298
269
  }
299
270
  const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf") || f.endsWith(".vrm") || f.endsWith(".fbx"));
300
- const filesBytes = files.reduce((total, file) => {
301
- try {
302
- return total + statSync(outputDirectory + "/" + file).size;
303
- }
304
- catch {
305
- return total;
306
- }
307
- }, 0);
308
- needleLog("needle-buildpipeline", [
309
- `${key("Files to process")}: ${files.length} in ${rel(outputDirectory)}`,
310
- `${key("Input size")}: ${formatBytes(filesBytes)}`,
311
- existsSync(process.cwd() + "/node_modules/.needle/build-pipeline/output") ? needleDim("Removing temporary output directory") : undefined,
312
- ].filter(Boolean), "log", { dimBody: false });
271
+ log(`${files.length} file(s) to process in ${outputDirectory}`);
313
272
 
314
273
  /** @type {null | ChildProcess} */
315
274
  let proc = null;
@@ -331,6 +290,7 @@ async function invokeBuildPipeline(opts, options = {}) {
331
290
  // this is so that processes like sveltekit-static-adapter can run first and does not override already compressed files
332
291
  const tempOutputPath = process.cwd() + "/node_modules/.needle/build-pipeline/output";
333
292
  if (existsSync(tempOutputPath)) {
293
+ log("Removing temporary output directory at " + tempOutputPath);
334
294
  rmSync(tempOutputPath, { recursive: true, force: true });
335
295
  }
336
296
  mkdirSync(tempOutputPath, { recursive: true });
@@ -347,7 +307,6 @@ async function invokeBuildPipeline(opts, options = {}) {
347
307
 
348
308
  // allow running the build pipeline in the cloud. It requires and access token to be set in the vite.config.js
349
309
  // this can be set via e.g. process.env.NEEDLE_CLOUD_TOKEN
350
- const commandEnv = { ...process.env, NEEDLE_PIPELINE_STRUCTURED_LOGS: "1" };
351
310
  if (runInCloud) {
352
311
  if (!cloudAccessToken || !(typeof cloudAccessToken === "string") || cloudAccessToken.length <= 0) {
353
312
  throw new Error("No cloud access token configured. Please set it via process.env.NEEDLE_CLOUD_TOKEN or in the vite.config.js");
@@ -369,12 +328,12 @@ async function invokeBuildPipeline(opts, options = {}) {
369
328
  console.log("\n");
370
329
  const obfuscatedToken = `${cloudAccessToken.slice(0, 2)}*****${cloudAccessToken.slice(-2)}`;
371
330
  log(`Running compression in cloud ⛅ using access token: ${obfuscatedToken}`);
372
- proc = exec(cmd, { env: commandEnv });
331
+ proc = exec(cmd);
373
332
  }
374
333
  else if (existsLocally) {
375
334
  const cmd = `needle-gltf transform "${outputDirectory}" \"${tempOutputPath}\"`;
376
335
  log("Running command \"" + cmd + "\" at " + process.cwd() + "...");
377
- proc = exec(cmd, { cwd: installPath, env: commandEnv });
336
+ proc = exec(cmd, { cwd: installPath });
378
337
  }
379
338
  else {
380
339
  // First check if the user passed in a specific version to use via the vite config
@@ -394,140 +353,33 @@ async function invokeBuildPipeline(opts, options = {}) {
394
353
 
395
354
  const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}" \"${tempOutputPath}\"`;
396
355
  log(`Running compression locally using version '${version}'`);
397
- proc = exec(cmd, { env: commandEnv });
398
- }
399
- let pipelineSpinnerIndex = 0;
400
- let pipelineSpinnerActive = false;
401
- let transformStepCount = 0;
402
- let compressStepCount = 0;
403
-
404
- function clearPipelineProgress() {
405
- if (!process.stdout.isTTY || !pipelineSpinnerActive) return;
406
- process.stdout.write("\r\x1b[2K");
407
- pipelineSpinnerActive = false;
408
- }
409
-
410
- /** @param {string} text */
411
- function updatePipelineProgress(text) {
412
- if (!process.stdout.isTTY) return;
413
- const frame = PIPELINE_SPINNER_FRAMES[pipelineSpinnerIndex++ % PIPELINE_SPINNER_FRAMES.length];
414
- const maxLength = Math.max(24, (process.stdout.columns || 120) - 4);
415
- const value = text.length > maxLength ? `${text.slice(0, Math.max(0, maxLength - 1))}…` : text;
416
- process.stdout.write(`\r\x1b[2K${frame} ${value}\x1b[0K`);
417
- pipelineSpinnerActive = true;
356
+ proc = exec(cmd);
418
357
  }
419
-
420
- setTransientLogLineCleaner(() => clearPipelineProgress());
421
-
422
- /** @param {Buffer|string} data */
358
+ /** @param {any} data */
423
359
  function onLog(data) {
424
360
  if (data.length <= 0) return;
425
- const str = String(data).replace(/\r/g, "");
426
- const lines = str.split("\n");
427
- for (let line of lines) {
428
- if (!line?.trim().length) continue;
429
-
430
- if (line.startsWith(PIPELINE_STRUCTURED_LOG_PREFIX)) {
431
- let payload = /** @type {BuildPipelinePayload | null} */ (null);
432
- try {
433
- payload = /** @type {BuildPipelinePayload} */ (JSON.parse(line.slice(PIPELINE_STRUCTURED_LOG_PREFIX.length)));
434
- }
435
- catch {
436
- payload = null;
437
- }
438
- if (payload) {
439
- if (payload.event === "progress") {
440
- if (payload.phase === "transform") transformStepCount++;
441
- if (payload.phase === "compress") compressStepCount++;
442
- updatePipelineProgress(`Build pipeline ${payload.phase === "compress" ? "Compressing" : "Transform"} ${payload.target || payload.message || ""}`.trim());
443
- continue;
444
- }
445
-
446
- clearPipelineProgress();
447
-
448
- if (payload.event === "summary") {
449
- needleLog("needle-buildpipeline", payload.message || "Build pipeline summary", "log", { showHeader: false, leadingNewline: true });
450
- continue;
451
- }
452
-
453
- const level = String(payload.level || "info").toLowerCase();
454
- const message = payload.message || line;
455
- if (level === "error") {
456
- needleLog("needle-buildpipeline", message, "error", { dimBody: false, showHeader: false, leadingNewline: true });
457
- }
458
- else if (level === "warn") {
459
- needleLog("needle-buildpipeline", message, "warn", { dimBody: false, showHeader: false, leadingNewline: true });
460
- }
461
- else {
462
- needleLog("needle-buildpipeline", message, "log", { showHeader: false, leadingNewline: true });
463
- }
464
- continue;
465
- }
466
- }
467
-
468
- if (line.startsWith("info: [Needle Build Pipeline]") || line.startsWith("info: No \"gltf\" config found") || line.startsWith("info: No config found") || line.startsWith("Limit cache size to ") || line.startsWith("Current cache size is ")) {
469
- continue;
470
- }
471
-
472
- if (line.startsWith("[NEEDLE_progressive] Skipping")) {
473
- continue;
474
- }
475
-
476
- if (line.startsWith("objc[") || line.includes("Class GNotificationCenterDelegate is implemented in both") || line.includes("This may cause spurious casting failures and mysterious crashes")) {
477
- continue;
478
- }
479
-
480
- if (line.startsWith("INFO: Environment variable 'NEEDLE_TOKTX' not set")) {
481
- continue;
482
- }
483
-
484
- if (line.startsWith("metalRough: KHR_materials_pbrSpecularGlossiness not found")) {
485
- continue;
486
- }
487
-
488
- if (line.startsWith("WARN: Could not validate image type")) {
489
- continue;
490
- }
491
-
492
- const progressMatch = line.match(/^info:\s*→\s*(Transform|Compressing)\s+(.+)$/i);
493
- if (progressMatch) {
494
- if (progressMatch[1].toLowerCase() === "transform") transformStepCount++;
495
- if (progressMatch[1].toLowerCase() === "compressing") compressStepCount++;
496
- updatePipelineProgress(`Build pipeline ${progressMatch[1]} ${progressMatch[2]}`);
497
- continue;
498
- }
499
-
500
- if (line.startsWith("info: ← Writing to ") || line.startsWith("info: ← Compressing done in ")) {
501
- continue;
361
+ // ensure that it doesnt end with a newline
362
+ while (data.endsWith("\n")) data = data.slice(0, -1);
363
+ if (typeof data === "string") {
364
+ if (data.startsWith("ERR:")) {
365
+ console.error(data);
366
+ return;
502
367
  }
503
-
504
- clearPipelineProgress();
505
-
506
- if (line.startsWith("ERR:")) {
507
- needleLog("needle-buildpipeline", line, "error", { dimBody: false, showHeader: false, leadingNewline: true });
508
- continue;
368
+ else if (data.startsWith("WARN:")) {
369
+ console.warn(data);
370
+ return;
509
371
  }
510
- else if (line.startsWith("WARN:")) {
511
- needleLog("needle-buildpipeline", line, "warn", { dimBody: false, showHeader: false, leadingNewline: true });
512
- continue;
372
+ // Ignore empty lines
373
+ else if (data.trim().length <= 0) {
374
+ return;
513
375
  }
514
-
515
- const shouldDim = line.includes("Loaded compressed file from cache");
516
- needleLog("needle-buildpipeline", line, "log", { showHeader: false, leadingNewline: true, dimBody: shouldDim });
517
376
  }
377
+ log(data);
518
378
  }
519
379
  proc.stdout?.on('data', onLog);
520
380
  proc.stderr?.on('data', onLog);
521
381
  return new Promise((resolve, reject) => {
522
382
  proc.on('exit', (code) => {
523
- clearPipelineProgress();
524
- setTransientLogLineCleaner(null);
525
-
526
- if (transformStepCount > 0 || compressStepCount > 0) {
527
- buildPipelineStepSummary = `✓ Pipeline steps: transformed ${transformStepCount} file(s), compressed ${compressStepCount} file(s)`;
528
- }
529
- else buildPipelineStepSummary = null;
530
-
531
383
  if (code === null || code === undefined) {
532
384
  if (options?.verbose) log("Process exited with no code - assuming success");
533
385
  code = 0;
@@ -539,38 +391,3 @@ async function invokeBuildPipeline(opts, options = {}) {
539
391
  });
540
392
  });
541
393
  }
542
-
543
- /** @param {number | undefined} bytes */
544
- function formatBytes(bytes) {
545
- const value = Number(bytes || 0);
546
- if (value < 1024) return `${value} B`;
547
- if (value < 1024 * 1024) return `${(value / 1024).toFixed(2)} kB`;
548
- if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(2)} MB`;
549
- return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GB`;
550
- }
551
-
552
- /** @param {string | null | undefined} directory */
553
- function getDirectoryStats(directory) {
554
- if (!directory || !existsSync(directory)) return { fileCount: 0, totalBytes: 0 };
555
- let fileCount = 0;
556
- let totalBytes = 0;
557
- const entries = readdirSync(directory, { withFileTypes: true });
558
- for (const entry of entries) {
559
- const path = directory + "/" + entry.name;
560
- if (entry.isDirectory()) {
561
- const stats = getDirectoryStats(path);
562
- fileCount += stats.fileCount;
563
- totalBytes += stats.totalBytes;
564
- }
565
- else {
566
- try {
567
- const stat = statSync(path);
568
- fileCount += 1;
569
- totalBytes += stat.size;
570
- }
571
- catch {
572
- }
573
- }
574
- }
575
- return { fileCount, totalBytes };
576
- }
@@ -1,8 +1,6 @@
1
- import { collectBuildDirectoryStats, createBuildInfoFile } from '../common/buildinfo.js';
2
- import { closeLogStreams } from '../common/logger.js';
1
+ import { createBuildInfoFile } from '../common/buildinfo.js';
3
2
  import { waitForBuildPipelineToFinish } from './build-pipeline.js';
4
3
  import { getOutputDirectory } from './config.js';
5
- import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js';
6
4
 
7
5
  let level = 0;
8
6
 
@@ -23,80 +21,22 @@ export const needleBuildInfo = (command, config, userSettings) => {
23
21
  },
24
22
  closeBundle: async () => {
25
23
  if (--level > 0) {
26
- needleLog("needle-buildinfo", "Skipped because of nested build");
24
+ console.log("[needle-buildinfo] - Skipped because of nested build");
27
25
  return;
28
26
  }
29
- const buildDirectory = getOutputDirectory();
30
- const beforeStats = collectBuildDirectoryStats(buildDirectory);
31
27
  const task = waitForBuildPipelineToFinish();
32
28
  if (task instanceof Promise) {
33
- needleLog("needle-buildinfo", "Waiting for build pipeline to finish");
34
- await task.catch(() => { });
29
+ console.log("[needle-buildinfo] - Waiting for build pipeline to finish");
30
+ await task.catch(() => { }).finally(() => console.log("[needle-buildinfo] - Build pipeline finished!"));
35
31
  }
36
32
  // wait for gzip
37
33
  await delay(500);
38
- const result = createBuildInfoFile(buildDirectory, { log: false });
39
- const closedHandles = closeDanglingNetworkHandles();
40
- if (!result?.ok) {
41
- needleLog("needle-buildinfo", result?.error || "Failed to create build info file", "warn", { dimBody: false });
42
- return;
43
- }
44
- const supportsColor = needleSupportsColor();
45
- const key = (text) => supportsColor ? needleBlue(text) : text;
46
- const sizeDelta = (result.totalSizeBytes || 0) - (beforeStats.totalSizeBytes || 0);
47
- const fileDelta = (result.fileCount || 0) - (beforeStats.fileCount || 0);
48
- const lines = [
49
- "Build pipeline finished!",
50
- `${key("Before pipeline")}: ${beforeStats.fileCount} files (${formatBytes(beforeStats.totalSizeBytes)})`,
51
- `${key("After pipeline")}: ${result.fileCount} files (${formatBytes(result.totalSizeBytes)})`,
52
- `${key("Delta")}: ${(sizeDelta >= 0 ? "+" : "-") + formatBytes(Math.abs(sizeDelta))}, ${(fileDelta >= 0 ? "+" : "") + fileDelta} files`,
53
- needleDim(`Begin collecting files in \"${result.buildDirectory}\"`),
54
- needleDim(`Collected ${result.fileCount} files (${result.totalSizeInMB.toFixed(2)} MB). Writing build info to \"${result.buildInfoPath}\"`),
55
- needleDim(`Build info file successfully written to \"${result.buildInfoPath}\"`),
56
- ];
57
- if (closedHandles > 0) lines.push(`Closed ${closedHandles} dangling network handle(s)`);
58
- needleLog("needle-buildinfo", lines.join("\n"), "log", { dimBody: false });
59
- closeLogStreams();
34
+ const buildDirectory = getOutputDirectory();
35
+ createBuildInfoFile(buildDirectory);
60
36
  }
61
37
  }
62
38
  }
63
39
 
64
- function formatBytes(bytes) {
65
- const value = Number(bytes || 0);
66
- if (value < 1024) return `${value} B`;
67
- if (value < 1024 * 1024) return `${(value / 1024).toFixed(2)} kB`;
68
- if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(2)} MB`;
69
- return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GB`;
70
- }
71
-
72
40
  function delay(ms) {
73
41
  return new Promise(res => setTimeout(res, ms));
74
- }
75
-
76
- function closeDanglingNetworkHandles() {
77
- /** @type {() => any[] | undefined} */
78
- // @ts-ignore private node api
79
- const getActiveHandles = process._getActiveHandles;
80
- if (typeof getActiveHandles !== "function") {
81
- return 0;
82
- }
83
-
84
- let closed = 0;
85
- const handles = getActiveHandles() || [];
86
- for (const handle of handles) {
87
- if (!handle || handle === process.stdin || handle === process.stdout || handle === process.stderr) continue;
88
- const hasNetworkShape = typeof handle.destroy === "function" && (typeof handle.remotePort === "number" || typeof handle.remoteAddress === "string");
89
- if (!hasNetworkShape) continue;
90
-
91
- if (typeof handle.remotePort === "number" && handle.remotePort > 0) {
92
- try {
93
- handle.destroy();
94
- closed++;
95
- }
96
- catch {
97
- // ignore cleanup failures
98
- }
99
- }
100
- }
101
- return closed;
102
42
  }
@@ -1,9 +1,8 @@
1
1
 
2
- import { resolve, join, isAbsolute, relative } from 'path'
2
+ import { resolve, join, isAbsolute } from 'path'
3
3
  import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir, rmSync } from 'fs';
4
4
  import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
5
5
  import { copyFilesSync } from '../common/files.js';
6
- import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js';
7
6
 
8
7
  const pluginName = "needle-copy-files";
9
8
 
@@ -36,19 +35,11 @@ export const needleCopyFiles = (command, config, userSettings) => {
36
35
  * @param {import('../types').userSettings} config
37
36
  */
38
37
  async function run(buildstep, config) {
39
- const logLines = [needleDim(`Copy files at ${buildstep}`)];
38
+ console.log(`[${pluginName}] - Copy files at ${buildstep}`);
40
39
  const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true;
41
- const copied = { count: 0, bytes: 0 };
42
40
 
43
41
  const baseDir = process.cwd();
44
42
  const override = buildstep === "start";
45
- const supportsColor = needleSupportsColor();
46
- const key = (text) => supportsColor ? needleBlue(text) : text;
47
- const rel = (pathValue) => {
48
- const result = relative(baseDir, pathValue).replaceAll("\\", "/");
49
- if (!result || result.length === 0) return ".";
50
- return result;
51
- };
52
43
 
53
44
  let assetsDirName = "assets";
54
45
  let outdirName = "dist";
@@ -66,9 +57,9 @@ async function run(buildstep, config) {
66
57
  // copy include from engine
67
58
  const engineIncludeDir = resolve(baseDir, 'node_modules', '@needle-tools', 'engine', 'src', 'include');
68
59
  if (existsSync(engineIncludeDir)) {
69
- logLines.push(`Engine include: ${rel(engineIncludeDir)} -> ${rel(resolve(baseDir, 'include'))}`);
60
+ console.log(`[${pluginName}] - Copy engine include to ${baseDir}/include`)
70
61
  const projectIncludeDir = resolve(baseDir, 'include');
71
- copyFilesSync(engineIncludeDir, projectIncludeDir, true, copied);
62
+ copyFilesSync(engineIncludeDir, projectIncludeDir);
72
63
  }
73
64
  }
74
65
 
@@ -94,8 +85,8 @@ async function run(buildstep, config) {
94
85
  const src = resolve(baseDir, entry);
95
86
  const dest = resolvePath(outDir, entry);
96
87
  if (existsSync(src) && dest) {
97
- logLines.push(`Configured copy: ${rel(src)} -> ${rel(dest)}`);
98
- copyFilesSync(src, dest, override, copied);
88
+ console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
89
+ copyFilesSync(src, dest, override);
99
90
  }
100
91
  }
101
92
  }
@@ -107,41 +98,21 @@ async function run(buildstep, config) {
107
98
  // ensure that the target directory exists and is cleared if it already exists
108
99
  // otherwise we might run into issues where the build pipeline is running for already compressed files
109
100
  if (override && existsSync(targetDir)) {
110
- logLines.push(needleDim(`Clearing target directory "${rel(targetDir)}"`));
101
+ console.log(`[${pluginName}] - Clearing target directory \"${targetDir}\"`);
111
102
  rmSync(targetDir, { recursive: true, force: true });
112
103
  }
113
- logLines.push(needleDim(`Assets: ${rel(assetsDir)} -> ${rel(targetDir)}`));
114
- copyFilesSync(assetsDir, targetDir, override, copied);
104
+ console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
105
+ copyFilesSync(assetsDir, targetDir, override);
115
106
  }
116
- else logLines.push(`No assets directory found. Skipping copy of ${assetsDirName} resolved to ${rel(assetsDir)}`);
107
+ else console.log(`WARN: No assets directory found. Skipping copy of ${assetsDirName} resolved to ${assetsDir}`)
117
108
 
118
109
  // copy include dir
119
110
  const includeDir = resolve(baseDir, 'include');
120
111
  if (existsSync(includeDir)) {
112
+ console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
121
113
  const targetDir = resolve(outDir, 'include');
122
- logLines.push(needleDim(`Include: ${rel(includeDir)} -> ${rel(targetDir)}`));
123
- copyFilesSync(includeDir, targetDir, override, copied);
114
+ copyFilesSync(includeDir, targetDir, override);
124
115
  }
125
-
126
- logLines.push(`${key("Copied files")}: ${copied.count}`);
127
- logLines.push(`${key("Copied size")}: ${formatBytes(copied.bytes || 0)}`);
128
- if (buildstep === "end") {
129
- logLines.push("");
130
- logLines.push("✨ Happy creating! 🌵");
131
- }
132
-
133
- needleLog(pluginName, logLines.join("\n"), "log", {
134
- leadingNewline: buildstep === "end",
135
- dimBody: false,
136
- });
137
- }
138
-
139
- function formatBytes(bytes) {
140
- const value = Number(bytes || 0);
141
- if (value < 1024) return `${value} B`;
142
- if (value < 1024 * 1024) return `${(value / 1024).toFixed(2)} kB`;
143
- if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(2)} MB`;
144
- return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GB`;
145
116
  }
146
117
 
147
118
  /** resolves relative or absolute paths to a path inside the out directory