@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.
- package/CHANGELOG.md +8 -0
- package/components.needle.json +1 -1
- package/dist/{gltf-progressive-BttGBXw6.umd.cjs → gltf-progressive-CMwJPwEt.umd.cjs} +1 -1
- package/dist/{gltf-progressive-Bm_6aEi4.js → gltf-progressive-CTlvpS3A.js} +1 -1
- package/dist/{gltf-progressive-T5WKTux5.min.js → gltf-progressive-DYL3SLVb.min.js} +1 -1
- package/dist/materialx-4jJLLe9Q.js +4174 -0
- package/dist/materialx-Bt9FHwco.min.js +158 -0
- package/dist/materialx-NDD0y4JY.umd.cjs +158 -0
- package/dist/{needle-engine.bundle-COL2Bar3.umd.cjs → needle-engine.bundle-C1BFRZDF.umd.cjs} +150 -140
- package/dist/{needle-engine.bundle-Z_gAD7Kg.js → needle-engine.bundle-DB4kLWO_.js} +6651 -6400
- package/dist/{needle-engine.bundle-NolzHLqO.min.js → needle-engine.bundle-DsTdfmeb.min.js} +151 -141
- package/dist/needle-engine.d.ts +345 -88
- package/dist/needle-engine.js +322 -322
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-06AXuvdv.min.js → postprocessing-BN-f4viE.min.js} +1 -1
- package/dist/{postprocessing-CPDcA21P.umd.cjs → postprocessing-DYmYOVm4.umd.cjs} +1 -1
- package/dist/{postprocessing-CI2x8Cln.js → postprocessing-De9ZpJrk.js} +1 -1
- package/dist/{three-examples-BMmNgNCN.umd.cjs → three-examples-BHqRVpO_.umd.cjs} +12 -12
- package/dist/{three-examples-CMYCd5nH.js → three-examples-C0ZCCA_K.js} +182 -192
- package/dist/{three-examples-CQl1fFZp.min.js → three-examples-DmTY8tGr.min.js} +14 -14
- package/lib/engine/api.d.ts +0 -2
- package/lib/engine/api.js +0 -2
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/debug/debug.js +1 -1
- package/lib/engine/debug/debug.js.map +1 -1
- package/lib/engine/debug/debug_spatial_console.js +1 -1
- package/lib/engine/debug/debug_spatial_console.js.map +1 -1
- package/lib/engine/engine_accessibility.d.ts +77 -0
- package/lib/engine/engine_accessibility.js +162 -0
- package/lib/engine/engine_accessibility.js.map +1 -0
- package/lib/engine/engine_context.d.ts +2 -0
- package/lib/engine/engine_context.js +8 -1
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_create_objects.js +1 -1
- package/lib/engine/engine_create_objects.js.map +1 -1
- package/lib/engine/engine_gizmos.js +1 -1
- package/lib/engine/engine_gizmos.js.map +1 -1
- package/lib/engine/engine_license.js +7 -2
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_materialpropertyblock.d.ts +90 -4
- package/lib/engine/engine_materialpropertyblock.js +97 -7
- package/lib/engine/engine_materialpropertyblock.js.map +1 -1
- package/lib/engine/engine_math.d.ts +34 -1
- package/lib/engine/engine_math.js +34 -1
- package/lib/engine/engine_math.js.map +1 -1
- package/lib/engine/engine_networking.js +1 -1
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_types.d.ts +2 -0
- package/lib/engine/engine_types.js +2 -0
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_utils.js +2 -2
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/export/gltf/EXT_mesh_gpu_instancing_exporter.js.map +1 -0
- package/lib/engine/export/gltf/index.js +1 -1
- package/lib/engine/export/gltf/index.js.map +1 -1
- package/lib/engine/webcomponents/icons.js +3 -0
- package/lib/engine/webcomponents/icons.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.d.ts +7 -3
- package/lib/engine/webcomponents/logo-element.js +21 -1
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +10 -7
- package/lib/engine/webcomponents/needle menu/needle-menu.js +14 -4
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-button.d.ts +37 -11
- package/lib/engine/webcomponents/needle-button.js +42 -11
- package/lib/engine/webcomponents/needle-button.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js +10 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +13 -2
- package/lib/engine/webcomponents/needle-engine.js +23 -3
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/Component.d.ts +1 -2
- package/lib/engine-components/Component.js +1 -3
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +1 -0
- package/lib/engine-components/DragControls.js +21 -0
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/NeedleMenu.d.ts +2 -0
- package/lib/engine-components/NeedleMenu.js +2 -0
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +28 -3
- package/lib/engine-components/Networking.js +28 -3
- package/lib/engine-components/Networking.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +25 -2
- package/lib/engine-components/ReflectionProbe.js +46 -2
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Skybox.js +4 -2
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js +1 -1
- package/lib/engine-components/export/gltf/GltfExport.js.map +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +2 -2
- package/lib/engine-components/export/usdz/USDZExporter.js +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +15 -0
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +77 -0
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js +2 -2
- package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/ui/Button.d.ts +1 -0
- package/lib/engine-components/ui/Button.js +11 -0
- package/lib/engine-components/ui/Button.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +1 -0
- package/lib/engine-components/ui/Text.js +11 -0
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/package.json +18 -14
- package/plugins/common/buildinfo.js +46 -10
- package/plugins/common/files.js +2 -1
- package/plugins/common/license.js +144 -69
- package/plugins/common/logger.js +172 -11
- package/plugins/common/needle-engine-skill.md +175 -0
- package/plugins/common/worker.js +5 -4
- package/plugins/types/userconfig.d.ts +40 -2
- package/plugins/vite/ai.js +71 -0
- package/plugins/vite/alias.js +6 -5
- package/plugins/vite/asap.js +6 -5
- package/plugins/vite/build-pipeline.js +224 -41
- package/plugins/vite/buildinfo.js +66 -6
- package/plugins/vite/copyfiles.js +41 -12
- package/plugins/vite/custom-element-data.js +26 -16
- package/plugins/vite/defines.js +8 -5
- package/plugins/vite/dependencies.js +16 -10
- package/plugins/vite/dependency-watcher.js +35 -7
- package/plugins/vite/drop-client.js +7 -5
- package/plugins/vite/drop.js +16 -14
- package/plugins/vite/editor-connection.js +18 -16
- package/plugins/vite/imports-logger.js +12 -2
- package/plugins/vite/index.js +8 -3
- package/plugins/vite/local-files-analysis.js +789 -0
- package/plugins/vite/local-files-core.js +992 -0
- package/plugins/vite/local-files-internals.js +28 -0
- package/plugins/vite/local-files-types.d.ts +111 -0
- package/plugins/vite/local-files-utils.js +359 -0
- package/plugins/vite/local-files.js +2 -441
- package/plugins/vite/logger.client.js +45 -35
- package/plugins/vite/logger.js +6 -3
- package/plugins/vite/logging.js +129 -0
- package/plugins/vite/meta.js +18 -4
- package/plugins/vite/needle-app.js +4 -3
- package/plugins/vite/peer.js +2 -1
- package/plugins/vite/pwa.js +33 -17
- package/plugins/vite/reload.js +24 -2
- package/src/engine/api.ts +0 -3
- package/src/engine/debug/debug.ts +1 -1
- package/src/engine/debug/debug_spatial_console.ts +5 -1
- package/src/engine/engine_accessibility.ts +198 -0
- package/src/engine/engine_context.ts +10 -1
- package/src/engine/engine_create_objects.ts +1 -1
- package/src/engine/engine_gizmos.ts +9 -5
- package/src/engine/engine_license.ts +7 -2
- package/src/engine/engine_materialpropertyblock.ts +102 -11
- package/src/engine/engine_math.ts +34 -1
- package/src/engine/engine_networking.ts +1 -1
- package/src/engine/engine_types.ts +5 -0
- package/src/engine/engine_utils.ts +2 -2
- package/src/engine/export/gltf/index.ts +1 -1
- package/src/engine/webcomponents/icons.ts +3 -0
- package/src/engine/webcomponents/logo-element.ts +24 -4
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +6 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +23 -11
- package/src/engine/webcomponents/needle-button.ts +44 -13
- package/src/engine/webcomponents/needle-engine.ar-overlay.ts +13 -2
- package/src/engine/webcomponents/needle-engine.ts +31 -8
- package/src/engine-components/Component.ts +2 -5
- package/src/engine-components/DragControls.ts +29 -4
- package/src/engine-components/NeedleMenu.ts +5 -3
- package/src/engine-components/Networking.ts +29 -4
- package/src/engine-components/ReflectionProbe.ts +52 -9
- package/src/engine-components/Skybox.ts +4 -2
- package/src/engine-components/export/gltf/GltfExport.ts +1 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
- package/src/engine-components/export/usdz/USDZExporter.ts +1 -1
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +108 -32
- package/src/engine-components/export/usdz/extensions/behavior/PhysicsExtension.ts +2 -2
- package/src/engine-components/ui/Button.ts +12 -0
- package/src/engine-components/ui/Text.ts +13 -0
- package/dist/materialx-CJyQZtjt.min.js +0 -90
- package/dist/materialx-DMs1E08Z.js +0 -4636
- package/dist/materialx-DaKKOoVk.umd.cjs +0 -90
- package/lib/engine/engine_test_utils.d.ts +0 -39
- package/lib/engine/engine_test_utils.js +0 -84
- package/lib/engine/engine_test_utils.js.map +0 -1
- package/lib/include/three/EXT_mesh_gpu_instancing_exporter.js.map +0 -1
- package/src/engine/engine_test_utils.ts +0 -109
- package/src/include/draco/draco_decoder.js +0 -34
- package/src/include/draco/draco_decoder.wasm +0 -0
- package/src/include/draco/draco_wasm_wrapper.js +0 -117
- package/src/include/ktx2/basis_transcoder.js +0 -19
- package/src/include/ktx2/basis_transcoder.wasm +0 -0
- package/src/include/needle/arial-msdf.json +0 -1472
- package/src/include/needle/arial.png +0 -0
- package/src/include/needle/poweredbyneedle.webp +0 -0
- /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.d.ts +0 -0
- /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
- /package/src/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
package/plugins/common/logger.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import { createWriteStream, existsSync, mkdirSync, readdirSync, rmSync, statSync, write } from "fs";
|
|
2
3
|
|
|
3
4
|
const filename_timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
@@ -15,57 +16,209 @@ let originalConsoleWarn = console.warn;
|
|
|
15
16
|
let originalConsoleInfo = console.info;
|
|
16
17
|
let originalConsoleDebug = console.debug;
|
|
17
18
|
let didPatch = false;
|
|
19
|
+
/** @type {(() => void) | null} */
|
|
18
20
|
let unpatchFunction = null;
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
const buildOutputState = {
|
|
23
|
+
mode: "serve",
|
|
24
|
+
progressActive: false,
|
|
25
|
+
assetFiles: 0,
|
|
26
|
+
assetTotalKb: 0,
|
|
27
|
+
assetGzipKb: 0,
|
|
28
|
+
compressionFiles: 0,
|
|
29
|
+
compressionTotalKb: 0,
|
|
30
|
+
compressionGzipKb: 0,
|
|
31
|
+
compressionCaptureActive: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** @param {unknown} value @returns {number} */
|
|
35
|
+
function parseKb(value) {
|
|
36
|
+
const numeric = Number.parseFloat(String(value ?? "0").replace(/[^0-9.]/g, ""));
|
|
37
|
+
return Number.isFinite(numeric) ? numeric : 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function clearBuildProgressLine() {
|
|
41
|
+
if (!process.stdout.isTTY || !buildOutputState.progressActive) return;
|
|
42
|
+
process.stdout.write("\r\x1b[2K");
|
|
43
|
+
buildOutputState.progressActive = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @param {string} text */
|
|
47
|
+
function writeBuildProgressLine(text) {
|
|
48
|
+
if (!process.stdout.isTTY) return;
|
|
49
|
+
const maxLength = Math.max(24, (process.stdout.columns || 120) - 1);
|
|
50
|
+
const line = text.length > maxLength ? `${text.slice(0, Math.max(0, maxLength - 1))}…` : text;
|
|
51
|
+
process.stdout.write(`\r\x1b[2K${line}\x1b[0K`);
|
|
52
|
+
buildOutputState.progressActive = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resetBuildOutputState() {
|
|
56
|
+
buildOutputState.progressActive = false;
|
|
57
|
+
buildOutputState.assetFiles = 0;
|
|
58
|
+
buildOutputState.assetTotalKb = 0;
|
|
59
|
+
buildOutputState.assetGzipKb = 0;
|
|
60
|
+
buildOutputState.compressionFiles = 0;
|
|
61
|
+
buildOutputState.compressionTotalKb = 0;
|
|
62
|
+
buildOutputState.compressionGzipKb = 0;
|
|
63
|
+
buildOutputState.compressionCaptureActive = false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function flushBuildOutputSummaries() {
|
|
67
|
+
if (buildOutputState.mode !== "build") return;
|
|
68
|
+
clearBuildProgressLine();
|
|
69
|
+
|
|
70
|
+
if (buildOutputState.assetFiles > 0) {
|
|
71
|
+
const msg = formatBuildInfoSummaryMessage(`✓ Bundled ${buildOutputState.assetFiles} files (${buildOutputState.assetTotalKb.toFixed(2)} kB${buildOutputState.assetGzipKb > 0 ? `, gzip ${buildOutputState.assetGzipKb.toFixed(2)} kB` : ""})`);
|
|
72
|
+
originalConsoleLog(msg);
|
|
73
|
+
captureLogMessage("server", "log", msg, null);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (buildOutputState.compressionFiles > 0) {
|
|
77
|
+
const msg = formatBuildInfoSummaryMessage(`✓ Gzip compressed ${buildOutputState.compressionFiles} files (${buildOutputState.compressionTotalKb.toFixed(2)} kB → ${buildOutputState.compressionGzipKb.toFixed(2)} kB)`);
|
|
78
|
+
originalConsoleLog(msg);
|
|
79
|
+
captureLogMessage("server", "log", msg, null);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
resetBuildOutputState();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function supportsColorOutput() {
|
|
86
|
+
return !!process.stdout?.isTTY && process.env.NO_COLOR !== "1";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @param {string} name @returns {string} */
|
|
90
|
+
function formatNeedleHeader(name) {
|
|
91
|
+
if (!supportsColorOutput()) return `[${name}]`;
|
|
92
|
+
if (name.startsWith("needle-")) {
|
|
93
|
+
const suffix = name.substring("needle-".length);
|
|
94
|
+
return `\x1b[32m[\x1b[0m\x1b[32mneedle-\x1b[0m\x1b[1;32m${suffix}\x1b[0m\x1b[32m]\x1b[0m`;
|
|
95
|
+
}
|
|
96
|
+
if (name.startsWith("needle:")) {
|
|
97
|
+
const suffix = name.substring("needle:".length);
|
|
98
|
+
return `\x1b[32m[\x1b[0m\x1b[32mneedle:\x1b[0m\x1b[1;32m${suffix}\x1b[0m\x1b[32m]\x1b[0m`;
|
|
99
|
+
}
|
|
100
|
+
return `\x1b[32m[\x1b[0m\x1b[1;32m${name}\x1b[0m\x1b[32m]\x1b[0m`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @param {string} body @returns {string} */
|
|
104
|
+
function formatBuildInfoSummaryMessage(body) {
|
|
105
|
+
return `${formatNeedleHeader("needle-buildinfo")}\n${body}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @param {unknown[]} args @returns {string} */
|
|
109
|
+
function normalizeConsoleArgs(args) {
|
|
110
|
+
return args.map(arg => typeof arg === "string" ? arg : stringifyLog(arg)).join(" ");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @param {unknown[]} args @returns {boolean} */
|
|
114
|
+
function tryHandleBuildConsoleOutput(args) {
|
|
115
|
+
if (buildOutputState.mode !== "build") return false;
|
|
116
|
+
|
|
117
|
+
const raw = normalizeConsoleArgs(args);
|
|
118
|
+
const message = raw.replace(/\u001b\[[0-9;]*m/g, "").trim();
|
|
119
|
+
if (!message.length) return true;
|
|
120
|
+
|
|
121
|
+
if (/^transforming\s*\(/i.test(message)) {
|
|
122
|
+
writeBuildProgressLine(`⏳ ${message}`);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (message.includes("[vite-plugin-compression]:algorithm=")) {
|
|
127
|
+
buildOutputState.compressionCaptureActive = true;
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const assetMatch = message.match(/^dist\/.*?\s+([0-9.,]+)\s*kB(?:\s*[│|]\s*gzip:\s*([0-9.,]+)\s*kB)?$/i);
|
|
132
|
+
if (assetMatch) {
|
|
133
|
+
buildOutputState.assetFiles++;
|
|
134
|
+
buildOutputState.assetTotalKb += parseKb(assetMatch[1]);
|
|
135
|
+
if (assetMatch[2]) buildOutputState.assetGzipKb += parseKb(assetMatch[2]);
|
|
136
|
+
writeBuildProgressLine(`📦 Bundling assets: ${buildOutputState.assetFiles} files`);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (buildOutputState.compressionCaptureActive) {
|
|
141
|
+
const compressionMatch = message.match(/^dist\/.*?\s+([0-9.]+)\s*kb\s*\/\s*gzip:\s*([0-9.]+)\s*kb$/i);
|
|
142
|
+
if (compressionMatch) {
|
|
143
|
+
buildOutputState.compressionFiles++;
|
|
144
|
+
buildOutputState.compressionTotalKb += parseKb(compressionMatch[1]);
|
|
145
|
+
buildOutputState.compressionGzipKb += parseKb(compressionMatch[2]);
|
|
146
|
+
writeBuildProgressLine(`🗜️ Compressing: ${buildOutputState.compressionFiles} files`);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (message.startsWith("✓ built in")) {
|
|
152
|
+
flushBuildOutputSummaries();
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
flushBuildOutputSummaries();
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {{command?: string} | undefined} [options]
|
|
162
|
+
* @returns {(() => void) | undefined}
|
|
163
|
+
*/
|
|
164
|
+
export function patchConsoleLogs(options = undefined) {
|
|
21
165
|
if (didPatch) return unpatchFunction;
|
|
22
166
|
didPatch = true;
|
|
167
|
+
buildOutputState.mode = options?.command === "build" ? "build" : "serve";
|
|
168
|
+
resetBuildOutputState();
|
|
23
169
|
|
|
24
170
|
console.log = (...args) => {
|
|
171
|
+
if (tryHandleBuildConsoleOutput(args)) return;
|
|
25
172
|
originalConsoleLog(...args);
|
|
26
173
|
captureLogMessage("server", 'log', args, null);
|
|
27
174
|
};
|
|
28
175
|
console.error = (...args) => {
|
|
176
|
+
flushBuildOutputSummaries();
|
|
29
177
|
originalConsoleError(...args);
|
|
30
178
|
captureLogMessage("server", 'error', args, null);
|
|
31
179
|
};
|
|
32
180
|
console.warn = (...args) => {
|
|
181
|
+
flushBuildOutputSummaries();
|
|
33
182
|
originalConsoleWarn(...args);
|
|
34
183
|
captureLogMessage("server", 'warn', args, null);
|
|
35
184
|
};
|
|
36
185
|
console.info = (...args) => {
|
|
186
|
+
if (tryHandleBuildConsoleOutput(args)) return;
|
|
37
187
|
originalConsoleInfo(...args);
|
|
38
188
|
captureLogMessage("server", 'info', args, null);
|
|
39
189
|
};
|
|
40
190
|
console.debug = (...args) => {
|
|
191
|
+
if (tryHandleBuildConsoleOutput(args)) return;
|
|
41
192
|
originalConsoleDebug(...args);
|
|
42
193
|
captureLogMessage("server", 'debug', args, null);
|
|
43
194
|
};
|
|
44
195
|
|
|
45
196
|
// Restore original console methods
|
|
46
197
|
unpatchFunction = () => {
|
|
198
|
+
flushBuildOutputSummaries();
|
|
47
199
|
didPatch = false;
|
|
48
200
|
console.log = originalConsoleLog;
|
|
49
201
|
console.error = originalConsoleError;
|
|
50
202
|
console.warn = originalConsoleWarn;
|
|
51
203
|
console.info = originalConsoleInfo;
|
|
52
204
|
console.debug = originalConsoleDebug;
|
|
205
|
+
buildOutputState.mode = "serve";
|
|
53
206
|
}
|
|
54
207
|
return unpatchFunction;
|
|
55
208
|
}
|
|
56
209
|
|
|
57
210
|
|
|
58
211
|
let isCapturing = false;
|
|
59
|
-
/** @type {Set<
|
|
212
|
+
/** @type {Set<unknown>} */
|
|
60
213
|
const isCapturingLogMessage = new Set();
|
|
61
214
|
|
|
62
|
-
/** @type {Array<{ process: ProcessType, key: string, log:
|
|
215
|
+
/** @type {Array<{ process: ProcessType, key: string, log:unknown, timestamp:number, connectionId: string | null }>} */
|
|
63
216
|
const queue = new Array();
|
|
64
217
|
|
|
65
218
|
/**
|
|
66
219
|
* @param {ProcessType} process
|
|
67
220
|
* @param {string} key
|
|
68
|
-
* @param {
|
|
221
|
+
* @param {unknown} log
|
|
69
222
|
* @param {string | null} connectionId - Optional connection ID for client logs.
|
|
70
223
|
* @param {number} [time] - Optional timestamp, defaults to current time.
|
|
71
224
|
*/
|
|
@@ -109,10 +262,10 @@ export function captureLogMessage(process, key, log, connectionId, time = Date.n
|
|
|
109
262
|
|
|
110
263
|
/**
|
|
111
264
|
* Stringifies a log message, handling circular references and formatting.
|
|
112
|
-
* @param {
|
|
113
|
-
* @param {Set<
|
|
265
|
+
* @param {unknown} log
|
|
266
|
+
* @param {Set<unknown>} [seen]
|
|
114
267
|
*/
|
|
115
|
-
function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
268
|
+
function stringifyLog(log, seen = /** @type {Set<unknown>} */ (new Set()), depth = 0) {
|
|
116
269
|
const isServer = typeof window === "undefined";
|
|
117
270
|
const stringify_limits = {
|
|
118
271
|
string: isServer ? 100_000 : 1_000,
|
|
@@ -154,8 +307,9 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
154
307
|
|| log instanceof BigUint64Array
|
|
155
308
|
|| log instanceof Float64Array
|
|
156
309
|
) {
|
|
157
|
-
|
|
158
|
-
|
|
310
|
+
const logArr = /** @type {ArrayLike<unknown>} */ (/** @type {unknown} */ (log));
|
|
311
|
+
seen.add(logArr);
|
|
312
|
+
return stringifyArray(logArr);
|
|
159
313
|
}
|
|
160
314
|
if (typeof log === "object") {
|
|
161
315
|
|
|
@@ -169,11 +323,12 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
169
323
|
return `<Error: ${log.message}\nStack: ${log.stack}>`;
|
|
170
324
|
}
|
|
171
325
|
|
|
172
|
-
const
|
|
326
|
+
const logObj = /** @type {Record<string, unknown>} */ (log);
|
|
327
|
+
const keys = Object.keys(logObj);
|
|
173
328
|
let res = "{";
|
|
174
329
|
for (let i = 0; i < keys.length; i++) {
|
|
175
330
|
const key = keys[i];
|
|
176
|
-
let value =
|
|
331
|
+
let value = logObj[key];
|
|
177
332
|
if (i >= stringify_limits.object_keys) {
|
|
178
333
|
res += `, ... <truncated ${keys.length - i} keys>`;
|
|
179
334
|
break;
|
|
@@ -208,6 +363,7 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
208
363
|
|
|
209
364
|
return String(log);
|
|
210
365
|
|
|
366
|
+
/** @param {ArrayLike<unknown>} arr @returns {string} */
|
|
211
367
|
function stringifyArray(arr) {
|
|
212
368
|
let res = "";
|
|
213
369
|
for (let i = 0; i < arr.length; i++) {
|
|
@@ -340,6 +496,11 @@ function onExit() {
|
|
|
340
496
|
filestreams.forEach((stream) => stream.end());
|
|
341
497
|
filestreams.clear();
|
|
342
498
|
}
|
|
499
|
+
|
|
500
|
+
export function closeLogStreams() {
|
|
501
|
+
onExit();
|
|
502
|
+
}
|
|
503
|
+
|
|
343
504
|
const events = ['SIGTERM', 'SIGINT', 'beforeExit', 'rejectionHandled', 'uncaughtException', 'exit'];
|
|
344
505
|
for (const event of events) {
|
|
345
506
|
process.on(event, onExit);
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: needle-engine
|
|
3
|
+
description: Automatically provides Needle Engine context when working in a Needle Engine web project. Use this skill when editing TypeScript components, Vite config, GLB assets, or anything related to @needle-tools/engine.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Needle Engine
|
|
7
|
+
|
|
8
|
+
You are an expert in Needle Engine — a web-first 3D engine built on Three.js with a Unity/Blender-based workflow.
|
|
9
|
+
|
|
10
|
+
## Key concepts
|
|
11
|
+
|
|
12
|
+
**Needle Engine** ships 3D scenes from Unity (or Blender) as GLB files and renders them in the browser using Three.js. TypeScript components attached to GameObjects in Unity are serialized into the GLB and re-hydrated at runtime in the browser.
|
|
13
|
+
|
|
14
|
+
### Embedding in HTML
|
|
15
|
+
```html
|
|
16
|
+
<!-- The <needle-engine> web component creates and manages a 3D context -->
|
|
17
|
+
<needle-engine src="assets/scene.glb"></needle-engine>
|
|
18
|
+
```
|
|
19
|
+
Access the context programmatically: `document.querySelector("needle-engine").context`
|
|
20
|
+
|
|
21
|
+
### Component lifecycle (mirrors Unity MonoBehaviour)
|
|
22
|
+
```ts
|
|
23
|
+
import { Behaviour, serializable, registerType } from "@needle-tools/engine";
|
|
24
|
+
|
|
25
|
+
@registerType
|
|
26
|
+
export class MyComponent extends Behaviour {
|
|
27
|
+
@serializable() myValue: number = 1;
|
|
28
|
+
|
|
29
|
+
awake() {} // called once when instantiated
|
|
30
|
+
start() {} // called once on first frame
|
|
31
|
+
update() {} // called every frame
|
|
32
|
+
onEnable() {}
|
|
33
|
+
onDisable() {}
|
|
34
|
+
onDestroy() {}
|
|
35
|
+
onBeforeRender(_frame: XRFrame | null) {}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Serialization
|
|
40
|
+
- `@registerType` — makes the class discoverable by the GLB deserializer
|
|
41
|
+
- `@serializable()` — marks a field for GLB deserialization (primitives)
|
|
42
|
+
- `@serializable(Object3D)` — for Three.js object references
|
|
43
|
+
- `@serializable(Texture)` — for textures (import Texture from "three")
|
|
44
|
+
- `@serializable(RGBAColor)` — for colors
|
|
45
|
+
|
|
46
|
+
### Accessing the scene
|
|
47
|
+
```ts
|
|
48
|
+
this.context.scene // THREE.Scene
|
|
49
|
+
this.context.mainCamera // active camera (THREE.Camera)
|
|
50
|
+
this.context.renderer // THREE.WebGLRenderer
|
|
51
|
+
this.context.time.frame // current frame number
|
|
52
|
+
this.context.time.deltaTime // seconds since last frame
|
|
53
|
+
this.gameObject // the THREE.Object3D this component is on
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Finding components
|
|
57
|
+
```ts
|
|
58
|
+
this.gameObject.getComponent(MyComponent)
|
|
59
|
+
this.gameObject.getComponentInChildren(MyComponent)
|
|
60
|
+
this.context.scene.getComponentInChildren(MyComponent)
|
|
61
|
+
|
|
62
|
+
// Global search (import as standalone functions from "@needle-tools/engine")
|
|
63
|
+
import { findObjectOfType, findObjectsOfType } from "@needle-tools/engine";
|
|
64
|
+
findObjectOfType(MyComponent, this.context)
|
|
65
|
+
findObjectsOfType(MyComponent, this.context)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Input handling
|
|
69
|
+
```ts
|
|
70
|
+
// Polling
|
|
71
|
+
if (this.context.input.getPointerDown(0)) { /* pointer pressed */ }
|
|
72
|
+
if (this.context.input.getKeyDown("Space")) { /* space pressed */ }
|
|
73
|
+
|
|
74
|
+
// Event-based (NEPointerEvent works across mouse, touch, and XR controllers)
|
|
75
|
+
this.gameObject.addEventListener("pointerdown", (e: NEPointerEvent) => { });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Physics & raycasting
|
|
79
|
+
```ts
|
|
80
|
+
// Default raycasts hit visible geometry — no colliders needed
|
|
81
|
+
// Uses mesh BVH (bounding volume hierarchy) for accelerated raycasting, BVH is generated on a worker
|
|
82
|
+
const hits = this.context.physics.raycast();
|
|
83
|
+
|
|
84
|
+
// Physics-based raycasts (require colliders, uses Rapier physics engine)
|
|
85
|
+
const physicsHits = this.context.physics.raycastPhysics();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Networking & multiplayer
|
|
89
|
+
Needle Engine has built-in multiplayer. Add a `SyncedRoom` component to enable networking.
|
|
90
|
+
|
|
91
|
+
- `@syncField()` — automatically syncs a field across all connected clients
|
|
92
|
+
- Primitives (string, number, boolean) sync automatically on change
|
|
93
|
+
- Complex types (arrays/objects) require reassignment to trigger sync: `this.myArray = this.myArray`
|
|
94
|
+
- Key components: `SyncedRoom`, `SyncedTransform`, `PlayerSync`, `Voip`
|
|
95
|
+
- Uses WebSockets + optional WebRTC peer-to-peer connections
|
|
96
|
+
|
|
97
|
+
### WebXR (VR & AR)
|
|
98
|
+
Needle Engine has built-in WebXR support for VR and AR across Meta Quest, Apple Vision Pro, and mobile AR.
|
|
99
|
+
|
|
100
|
+
- Add the `WebXR` component to enable VR/AR sessions
|
|
101
|
+
- Use `XRRig` to define the user's starting position — the user is parented to the rig during XR sessions
|
|
102
|
+
- Available components: `WebXRImageTracking`, `WebXRPlaneTracking`, `XRControllerModel`, `NeedleXRSession`
|
|
103
|
+
|
|
104
|
+
## Creating a new project
|
|
105
|
+
|
|
106
|
+
Use `create-needle` to scaffold a new Needle Engine project:
|
|
107
|
+
```bash
|
|
108
|
+
npm create needle my-app # default Vite template
|
|
109
|
+
npm create needle my-app -t react # React template
|
|
110
|
+
npm create needle my-app -t vue # Vue.js template
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Available templates: `vite` (default), `react`, `vue`, `sveltekit`, `svelte`, `nextjs`, `react-three-fiber`.
|
|
114
|
+
|
|
115
|
+
Use `npm create needle --list` to see all available templates.
|
|
116
|
+
|
|
117
|
+
## Vite plugin system
|
|
118
|
+
|
|
119
|
+
Needle Engine ships a set of Vite plugins via `needlePlugins(command, config, userSettings)`. Custom project plugins go in `vite.config.ts`.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { defineConfig } from "vite";
|
|
123
|
+
import { needlePlugins } from "@needle-tools/engine/vite";
|
|
124
|
+
|
|
125
|
+
export default defineConfig(async ({ command }) => ({
|
|
126
|
+
plugins: [
|
|
127
|
+
...(await needlePlugins(command, {}, {})),
|
|
128
|
+
],
|
|
129
|
+
}));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Deployment
|
|
133
|
+
|
|
134
|
+
Projects can be deployed to:
|
|
135
|
+
- **Needle Cloud** — official hosting with automatic optimization (`npx needle-cloud deploy`)
|
|
136
|
+
- **Vercel** / **Netlify** — standard web hosting
|
|
137
|
+
- **itch.io** — for games and interactive experiences
|
|
138
|
+
- **Any static host** — Needle Engine projects are standard Vite web apps
|
|
139
|
+
|
|
140
|
+
From Unity, use built-in deployment components (e.g. `DeployToNeedleCloud`, `DeployToNetlify`).
|
|
141
|
+
|
|
142
|
+
## Progressive loading (`@needle-tools/gltf-progressive`)
|
|
143
|
+
|
|
144
|
+
Needle Engine includes `@needle-tools/gltf-progressive` for progressive streaming of 3D models and textures. It creates a tiny initial file with embedded low-quality proxy geometry, then streams higher-quality LODs on demand. Results in ~90% smaller initial downloads with instant display.
|
|
145
|
+
|
|
146
|
+
Works standalone with any three.js project:
|
|
147
|
+
```ts
|
|
148
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
149
|
+
import { WebGLRenderer } from "three";
|
|
150
|
+
import { useNeedleProgressive } from "@needle-tools/gltf-progressive";
|
|
151
|
+
|
|
152
|
+
const gltfLoader = new GLTFLoader();
|
|
153
|
+
const renderer = new WebGLRenderer();
|
|
154
|
+
|
|
155
|
+
// Register once — progressive loading happens automatically for all subsequent loads
|
|
156
|
+
useNeedleProgressive(gltfLoader, renderer);
|
|
157
|
+
|
|
158
|
+
gltfLoader.load(url, (gltf) => scene.add(gltf.scene));
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
In Needle Engine projects, progressive loading is built in and can be configured via the **Compression & LOD Settings** component in Unity.
|
|
162
|
+
|
|
163
|
+
## Important URLs
|
|
164
|
+
- Docs: https://engine.needle.tools/docs/
|
|
165
|
+
- Samples: https://engine.needle.tools/samples/
|
|
166
|
+
- GitHub: https://github.com/needle-tools/needle-engine-support
|
|
167
|
+
- npm: https://www.npmjs.com/package/@needle-tools/engine
|
|
168
|
+
|
|
169
|
+
## Searching the documentation
|
|
170
|
+
|
|
171
|
+
Use the `needle_search` MCP tool to find relevant docs, forum posts, and community answers.
|
|
172
|
+
|
|
173
|
+
## Common gotchas
|
|
174
|
+
- Components must use `@registerType` or they won't be instantiated from GLB (this is handled automatically when exporting from Unity or Blender, but must be added manually for hand-written components)
|
|
175
|
+
- GLB assets are in `assets/`, static files in `include/` or `public/`
|
package/plugins/common/worker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { needleLog } from '../vite/logging.js';
|
|
1
2
|
|
|
2
3
|
|
|
3
4
|
|
|
@@ -51,12 +52,12 @@ export function rollupFixWorkerImport(opts = { logFail: true }) {
|
|
|
51
52
|
regexMatchedWorkerCode = true;
|
|
52
53
|
// console.log("WORKER?", url)
|
|
53
54
|
if (url?.startsWith("/")) {
|
|
54
|
-
|
|
55
|
+
needleLog("rollup", `Rewrite worker import in ${chunk.fileName}`, "log", { leadingNewline: true, dimBody: false });
|
|
55
56
|
// Make url file-relative
|
|
56
57
|
const newUrl = url.replace(/^\//, "");
|
|
57
58
|
// For CORS issues we need to use importScripts: https://linear.app/needle/issue/NE-6572#comment-ea5dc65e
|
|
58
59
|
const output = `/* new-worker */ new Worker(URL.createObjectURL(new Blob(["import '" + \`\${new URL('./${newUrl}', import.meta.url).toString()}\` + "';"], { type: 'text/javascript' }))`;
|
|
59
|
-
|
|
60
|
+
needleLog("rollup", "Did rewrite worker output to: " + output, "log", { leadingNewline: true });
|
|
60
61
|
return output;
|
|
61
62
|
// return `new Worker(new URL("./${newUrl}", import.meta.url)`;
|
|
62
63
|
}
|
|
@@ -70,7 +71,7 @@ export function rollupFixWorkerImport(opts = { logFail: true }) {
|
|
|
70
71
|
}
|
|
71
72
|
if (opts?.logFail !== false) {
|
|
72
73
|
const str = `[...]${code.substring(newWorkerStartIndex, newWorkerStartIndex + 200)}[...]`
|
|
73
|
-
|
|
74
|
+
needleLog("rollup", `Worker import in ${chunk.fileName} was not rewritten: ${str}`, "warn", { leadingNewline: true, dimBody: false });
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
return res;
|
|
@@ -116,7 +117,7 @@ function fixWorkerSelfLocation(filename, code) {
|
|
|
116
117
|
const fixedCode = workerCode.replace("self.location", "import.meta.url");
|
|
117
118
|
code = code.substring(0, startIndex) + fixedCode + code.substring(endIndex + 1);
|
|
118
119
|
lastIndex = startIndex + fixedCode.length;
|
|
119
|
-
|
|
120
|
+
needleLog("rollup", `Rewrite worker 'self.location' to 'import.meta.url' in ${filename}`, "log", { leadingNewline: true, dimBody: false });
|
|
120
121
|
} else {
|
|
121
122
|
lastIndex = endIndex;
|
|
122
123
|
}
|
|
@@ -112,10 +112,48 @@ export type userSettings = {
|
|
|
112
112
|
debugLicense?: boolean;
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* When enabled, external CDN URLs are downloaded at build time and bundled locally.
|
|
116
|
+
* This creates fully self-contained deployments that work without internet access.
|
|
117
|
+
*
|
|
118
|
+
* - `true` — enable with all features (download everything)
|
|
119
|
+
* - `"auto"` — automatically detect which features the project uses and only include those
|
|
120
|
+
* - `{ enabled: true }` — same as `true`
|
|
121
|
+
* - `{ enabled: true, features: "auto" }` — same as `"auto"`
|
|
122
|
+
* - `{ enabled: true, features: ["draco", "ktx2"] }` — only include specific features
|
|
123
|
+
* - `{ enabled: true, excludeFeatures: ["xr"] }` — include all except specific features
|
|
124
|
+
* - `{ enabled: true, features: "auto", excludeFeatures: ["skybox"] }` — auto-detect but exclude specific features
|
|
125
|
+
*
|
|
126
|
+
* Available features:
|
|
127
|
+
* - `"draco"` — Draco mesh decoders
|
|
128
|
+
* - `"ktx2"` — KTX2/Basis texture transcoders
|
|
129
|
+
* - `"materialx"` — MaterialX WASM shader compiler
|
|
130
|
+
* - `"xr"` — WebXR input profiles (controllers/hands)
|
|
131
|
+
* - `"skybox"` — Skybox/environment textures
|
|
132
|
+
* - `"fonts"` — Google Fonts CSS + font files
|
|
133
|
+
* - `"needle-fonts"` — Needle font assets (MSDF, etc.)
|
|
134
|
+
* - `"needle-models"` — Needle models
|
|
135
|
+
* - `"needle-avatars"` — Needle avatars
|
|
136
|
+
* - `"polyhaven"` — Polyhaven HDRIs/models
|
|
137
|
+
* - `"cdn-scripts"` — Third-party scripts (QRCode.js, vConsole, HLS.js)
|
|
138
|
+
* - `"github-content"` — GitHub raw content files
|
|
139
|
+
* - `"threejs-models"` — three.js example models
|
|
140
|
+
* - `"needle-uploads"` — Needle uploads assets
|
|
116
141
|
*/
|
|
117
|
-
makeFilesLocal?: boolean | {
|
|
142
|
+
makeFilesLocal?: boolean | "auto" | {
|
|
118
143
|
enabled: boolean;
|
|
144
|
+
/** URL patterns to exclude from making local */
|
|
145
|
+
exclude?: string[];
|
|
146
|
+
/** Target platform preset */
|
|
147
|
+
platform?: "discord" | "facebook-instant" | null;
|
|
148
|
+
/**
|
|
149
|
+
* Feature categories to include.
|
|
150
|
+
* - `"auto"` — Automatically detect which features the project uses.
|
|
151
|
+
* - `FeatureName[]` — Explicit list. When set, ONLY these features are processed.
|
|
152
|
+
* - When omitted, ALL features are included.
|
|
153
|
+
*/
|
|
154
|
+
features?: "auto" | Array<"draco" | "ktx2" | "materialx" | "xr" | "skybox" | "fonts" | "needle-fonts" | "needle-models" | "needle-avatars" | "polyhaven" | "cdn-scripts" | "github-content" | "threejs-models" | "needle-uploads">;
|
|
155
|
+
/** Feature categories to exclude. Applied after `features` (including after auto-detection). */
|
|
156
|
+
excludeFeatures?: Array<"draco" | "ktx2" | "materialx" | "xr" | "skybox" | "fonts" | "needle-fonts" | "needle-models" | "needle-avatars" | "polyhaven" | "cdn-scripts" | "github-content" | "threejs-models" | "needle-uploads">;
|
|
119
157
|
}
|
|
120
158
|
|
|
121
159
|
/**
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const pluginName = "needle-ai";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Needle Engine Claude skill installer.
|
|
12
|
+
*
|
|
13
|
+
* Writes a Needle Engine skill to `.claude/skills/needle-engine/SKILL.md`.
|
|
14
|
+
* Claude Code auto-loads skills based on their description frontmatter, so
|
|
15
|
+
* Claude will automatically have Needle Engine context when working in the project.
|
|
16
|
+
*
|
|
17
|
+
* The skill is only written if `.claude/` already exists in the project root
|
|
18
|
+
* (i.e. the developer is already using Claude Code). Old skill files are
|
|
19
|
+
* always overwritten so the skill stays up to date with the engine version.
|
|
20
|
+
*
|
|
21
|
+
* @param {"build" | "serve"} command
|
|
22
|
+
* @param {{} | undefined | null} config
|
|
23
|
+
* @param {import('../types/index.js').userSettings} userSettings
|
|
24
|
+
* @returns {import('vite').Plugin | null}
|
|
25
|
+
*/
|
|
26
|
+
export const needleAI = (command, config, userSettings) => {
|
|
27
|
+
return {
|
|
28
|
+
name: pluginName,
|
|
29
|
+
enforce: "pre",
|
|
30
|
+
buildStart() {
|
|
31
|
+
installClaudeSkill();
|
|
32
|
+
},
|
|
33
|
+
configureServer() {
|
|
34
|
+
installClaudeSkill();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Read the engine version from our own package.json */
|
|
40
|
+
function getEngineVersion() {
|
|
41
|
+
try {
|
|
42
|
+
const pkgPath = join(__dirname, "../../package.json");
|
|
43
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
44
|
+
return pkg.version || "unknown";
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return "unknown";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeSkill(claudeDir, version) {
|
|
52
|
+
const skillDir = join(claudeDir, "skills", "needle-engine");
|
|
53
|
+
if (!existsSync(skillDir)) {
|
|
54
|
+
mkdirSync(skillDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
57
|
+
const templatePath = join(__dirname, "../common/needle-engine-skill.md");
|
|
58
|
+
const template = readFileSync(templatePath, "utf8");
|
|
59
|
+
const content = template.replace("{{VERSION}}", version);
|
|
60
|
+
writeFileSync(skillPath, content, "utf8");
|
|
61
|
+
return skillPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function installClaudeSkill() {
|
|
65
|
+
const claudeDir = join(process.cwd(), ".claude");
|
|
66
|
+
if (!existsSync(claudeDir)) return; // only install if developer uses Claude Code
|
|
67
|
+
|
|
68
|
+
const version = getEngineVersion();
|
|
69
|
+
const path = writeSkill(claudeDir, version);
|
|
70
|
+
console.log(`[${pluginName}] Installed Needle Engine Claude skill → ${path}`);
|
|
71
|
+
}
|
package/plugins/vite/alias.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createWriteStream, existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { needleLog } from './logging.js';
|
|
3
4
|
|
|
4
5
|
const projectDir = process.cwd() + "/";
|
|
5
6
|
|
|
@@ -77,7 +78,7 @@ export const needleViteAlias = (command, config, userSettings) => {
|
|
|
77
78
|
|
|
78
79
|
let outputDebugFile = null;
|
|
79
80
|
function log(...msg) {
|
|
80
|
-
|
|
81
|
+
needleLog("needle-alias", msg.join(" "));
|
|
81
82
|
if (debug) logToFile(...msg);
|
|
82
83
|
}
|
|
83
84
|
function logToFile(...msg) {
|
|
@@ -91,7 +92,7 @@ export const needleViteAlias = (command, config, userSettings) => {
|
|
|
91
92
|
const timestamp = new Date().toISOString();
|
|
92
93
|
outputDebugFile.write("\n\n\n--------------------------\n");
|
|
93
94
|
outputDebugFile.write(`[needle-alias] Logging to: ${outputFilePath} (${timestamp})\n`);
|
|
94
|
-
log("
|
|
95
|
+
log("Logging to:", outputFilePath);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
|
|
@@ -99,7 +100,7 @@ export const needleViteAlias = (command, config, userSettings) => {
|
|
|
99
100
|
const aliasPlugin = {
|
|
100
101
|
name: "needle-alias",
|
|
101
102
|
config(config) {
|
|
102
|
-
if (debug)
|
|
103
|
+
if (debug) log('ProjectDirectory: ' + projectDir);
|
|
103
104
|
if (!config.resolve) config.resolve = {};
|
|
104
105
|
if (!config.resolve.alias) config.resolve.alias = {};
|
|
105
106
|
const aliasDict = config.resolve.alias;
|
|
@@ -119,7 +120,7 @@ export const needleViteAlias = (command, config, userSettings) => {
|
|
|
119
120
|
config.optimizeDeps.exclude ??= [];
|
|
120
121
|
if (!config.optimizeDeps.include?.includes('@needle-tools/engine')) {
|
|
121
122
|
config.optimizeDeps.exclude.push('@needle-tools/engine');
|
|
122
|
-
log("
|
|
123
|
+
log("Detected local @needle-tools/engine package → will exclude it from optimization");
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -132,7 +133,7 @@ export const needleViteAlias = (command, config, userSettings) => {
|
|
|
132
133
|
if (typeof entry === "function") res = entry(name, 0, name);
|
|
133
134
|
testResults.push({ name, entry: res });
|
|
134
135
|
}
|
|
135
|
-
|
|
136
|
+
log('Aliases: ' + JSON.stringify(testResults));
|
|
136
137
|
}
|
|
137
138
|
},
|
|
138
139
|
}
|