@@ -68,6 +68,7 @@
|
|
68
68
|
return "assets";
|
69
69
|
}
|
70
70
|
|
71
|
+
/** @returns the fullpath of the build */
|
71
72
|
export function getOutputDirectory() {
|
72
73
|
const projectConfig = tryLoadProjectConfig();
|
73
74
|
return process.cwd() + "/" + (projectConfig?.buildDirectory || "dist");
|
@@ -36,7 +36,10 @@
|
|
36
36
|
const needleConfig = tryLoadProjectConfig();
|
37
37
|
if (needleConfig) {
|
38
38
|
assetsDirName = needleConfig.assetsDirectory;
|
39
|
-
while(assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1);
|
39
|
+
while (assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1);
|
40
|
+
|
41
|
+
if (needleConfig.buildDirectory)
|
42
|
+
outdirName = needleConfig.buildDirectory;
|
40
43
|
}
|
41
44
|
|
42
45
|
if (copyIncludesFromEngine !== false) {
|
@@ -26,7 +26,7 @@
|
|
26
26
|
// console.log("Update vite defines -------------------------------------------");
|
27
27
|
if (!viteConfig.define) viteConfig.define = {};
|
28
28
|
const version = tryGetNeedleEngineVersion();
|
29
|
-
console.log("Needle Engine Version:"
|
29
|
+
console.log("Needle Engine Version: " + version, needleEngineConfig?.generator ?? "(unknown generator)");
|
30
30
|
if (version)
|
31
31
|
viteConfig.define.NEEDLE_ENGINE_VERSION = "\"" + version + "\"";
|
32
32
|
if (needleEngineConfig)
|
@@ -42,6 +42,9 @@
|
|
42
42
|
if (viteConfig.define.NEEDLE_USE_RAPIER === undefined) {
|
43
43
|
viteConfig.define.NEEDLE_USE_RAPIER = useRapier;
|
44
44
|
}
|
45
|
+
|
46
|
+
// this gives a timestamp containing the timezone
|
47
|
+
viteConfig.define.NEEDLE_PROJECT_BUILD_TIME = "\"" + new Date().toString() + "\"";
|
45
48
|
}
|
46
49
|
}
|
47
50
|
}
|
@@ -39,11 +39,14 @@
|
|
39
39
|
});
|
40
40
|
}
|
41
41
|
|
42
|
-
function triggerReloadOnClients() {
|
43
|
-
log(
|
44
|
-
|
45
|
-
|
46
|
-
|
42
|
+
async function triggerReloadOnClients() {
|
43
|
+
log(`Triggering reload on ${currentClients.size} clients...`)
|
44
|
+
for (const client of currentClients) {
|
45
|
+
client.send(JSON.stringify({ type: "full-reload" }));
|
46
|
+
}
|
47
|
+
return new Promise((resolve) => {
|
48
|
+
setTimeout(resolve, 100);
|
49
|
+
});
|
47
50
|
}
|
48
51
|
|
49
52
|
|
@@ -81,9 +84,6 @@
|
|
81
84
|
modified = true;
|
82
85
|
}
|
83
86
|
if (modified || requireInstall) {
|
84
|
-
if (modified) {
|
85
|
-
log("package.json has changed. Require install?", requireInstall)
|
86
|
-
}
|
87
87
|
|
88
88
|
let requireReload = false;
|
89
89
|
if (!requireInstall) {
|
@@ -95,7 +95,7 @@
|
|
95
95
|
if (newPackageJson.dependencies) {
|
96
96
|
for (const key in newPackageJson.dependencies) {
|
97
97
|
if (packageJson.dependencies[key] !== newPackageJson.dependencies[key] && newPackageJson.dependencies[key] !== undefined) {
|
98
|
-
log("
|
98
|
+
log("Detected new dependency: " + key)
|
99
99
|
requireReload = true;
|
100
100
|
requireInstall = true;
|
101
101
|
}
|
@@ -104,13 +104,16 @@
|
|
104
104
|
if (packageJson.devDependencies) {
|
105
105
|
for (const key in packageJson.devDependencies) {
|
106
106
|
if (packageJson.devDependencies[key] !== newPackageJson.devDependencies[key] && newPackageJson.devDependencies[key] !== undefined) {
|
107
|
-
log("
|
107
|
+
log("Detected new devDependency: " + key)
|
108
108
|
requireReload = true;
|
109
109
|
requireInstall = true;
|
110
110
|
}
|
111
111
|
}
|
112
112
|
}
|
113
113
|
|
114
|
+
if (modified) {
|
115
|
+
log("package.json has changed. Require install: " + (requireInstall ? "yes" : "no"))
|
116
|
+
}
|
114
117
|
|
115
118
|
packageJsonSize = packageJsonStat.size;
|
116
119
|
lastEditTime = packageJsonStat.mtime;
|
@@ -119,7 +122,7 @@
|
|
119
122
|
restart(server, projectDir, cachePath);
|
120
123
|
}
|
121
124
|
}
|
122
|
-
},
|
125
|
+
}, 2000);
|
123
126
|
}
|
124
127
|
|
125
128
|
function testIfInstallIsRequired(projectDir, packageJson) {
|
@@ -142,7 +145,7 @@
|
|
142
145
|
}
|
143
146
|
}
|
144
147
|
}
|
145
|
-
log("Dependency not installed"
|
148
|
+
log("Dependency not installed: " + key)
|
146
149
|
return true;
|
147
150
|
}
|
148
151
|
else {
|
@@ -162,8 +165,9 @@
|
|
162
165
|
if (!isRange) {
|
163
166
|
const packageJsonPath = path.join(depPath, "package.json");
|
164
167
|
const installedVersion = JSON.parse(readFileSync(packageJsonPath, "utf8")).version;
|
165
|
-
|
166
|
-
|
168
|
+
// fix check for cases where the version contains a alias e.g. npm:[email protected]
|
169
|
+
if (installedVersion.endsWith(expectedVersion) == false) {
|
170
|
+
log(`Dependency ${key} is installed but version is not the right one. Expected ${expectedVersion} but got ${installedVersion}`)
|
167
171
|
return true;
|
168
172
|
}
|
169
173
|
}
|
@@ -194,13 +198,13 @@
|
|
194
198
|
}
|
195
199
|
|
196
200
|
if (id !== restartId) return;
|
197
|
-
if (Date.now() - lastRestartTime <
|
201
|
+
if (Date.now() - lastRestartTime < 3000) return;
|
198
202
|
log("Restarting server...")
|
199
203
|
lastRestartTime = Date.now();
|
200
204
|
requireInstall = false;
|
201
205
|
if (existsSync(cachePath))
|
202
206
|
rmSync(cachePath, { recursive: true, force: true });
|
203
|
-
triggerReloadOnClients();
|
207
|
+
await triggerReloadOnClients();
|
204
208
|
|
205
209
|
// touch vite config to trigger reload
|
206
210
|
// const viteConfigPath = path.join(projectDir, "vite.config.js");
|
@@ -212,8 +216,9 @@
|
|
212
216
|
// }
|
213
217
|
|
214
218
|
// check if server is running
|
215
|
-
if (server.httpServer.listening)
|
219
|
+
if (server.httpServer.listening) {
|
216
220
|
server.restart();
|
221
|
+
}
|
217
222
|
isRunningRestart = false;
|
218
223
|
console.log("-----------------------------------------------")
|
219
224
|
}
|
@@ -42,6 +42,7 @@
|
|
42
42
|
|
43
43
|
import { vite_4_4_hack } from "./vite-4.4-hack.js";
|
44
44
|
import { needleImportsLogger } from "./imports-logger.js";
|
45
|
+
import { needleBuildInfo } from "./buildinfo.js";
|
45
46
|
|
46
47
|
|
47
48
|
export * from "./gzip.js";
|
@@ -69,6 +70,7 @@
|
|
69
70
|
needlePoster(command, config, userSettings),
|
70
71
|
needleReload(command, config, userSettings),
|
71
72
|
needleBuild(command, config, userSettings),
|
73
|
+
needleBuildInfo(command, config, userSettings),
|
72
74
|
needleCopyFiles(command, config, userSettings),
|
73
75
|
needleTransformCodegen(command, config, userSettings),
|
74
76
|
needleDrop(command, config, userSettings),
|
@@ -109,6 +109,8 @@
|
|
109
109
|
}
|
110
110
|
else console.log("WARN: could not find needle engine package.json")
|
111
111
|
|
112
|
+
tags.push({ tag: 'meta', attrs: { name: 'needle:buildtime', content: new Date().toISOString() } });
|
113
|
+
|
112
114
|
return { html, tags }
|
113
115
|
},
|
114
116
|
}
|
@@ -213,6 +213,7 @@
|
|
213
213
|
console.warn("AnimatorController has not been resolved, can not create model from string", this.model);
|
214
214
|
return null;
|
215
215
|
}
|
216
|
+
if (debug) console.warn("AnimatorController clone()", this.model);
|
216
217
|
// clone runtime controller but dont clone clip or action
|
217
218
|
const clonedModel = deepClone(this.model, (_owner, _key, _value) => {
|
218
219
|
if (_value === null || _value === undefined) return true;
|
@@ -225,6 +226,8 @@
|
|
225
226
|
}
|
226
227
|
// dont clone AnimationClip
|
227
228
|
if (_value["tracks"] !== undefined) return false;
|
229
|
+
// when assigned __concreteInstance during serialization
|
230
|
+
if (_value instanceof AnimatorController) return false;
|
228
231
|
return true;
|
229
232
|
}) as AnimatorControllerModel;
|
230
233
|
console.assert(clonedModel !== this.model);
|
@@ -582,7 +585,7 @@
|
|
582
585
|
}
|
583
586
|
|
584
587
|
private createActions(_animator: Animator) {
|
585
|
-
|
588
|
+
if (debug) console.log("AnimatorController createActions", this.model);
|
586
589
|
for (const layer of this.model.layers) {
|
587
590
|
const sm = layer.stateMachine;
|
588
591
|
for (let index = 0; index < sm.states.length; index++) {
|
@@ -609,8 +612,13 @@
|
|
609
612
|
if (this.animator && state.motion.clips) {
|
610
613
|
// TODO: we have to compare by name because on instantiate we clone objects but not the node object
|
611
614
|
const mapping = state.motion.clips?.find(e => e.node.name === this.animator?.gameObject?.name);
|
612
|
-
|
613
|
-
|
615
|
+
if (!mapping) {
|
616
|
+
if (debug || isDevEnvironment()) {
|
617
|
+
console.warn("Could not find clip for animator \"" + this.animator?.gameObject?.name + "\"", state.motion.clips.map(c => c.node.name));
|
618
|
+
}
|
619
|
+
}
|
620
|
+
else
|
621
|
+
state.motion.clip = mapping.clip;
|
614
622
|
}
|
615
623
|
|
616
624
|
// ensure we have a clip to blend to
|
@@ -1,5 +1,6 @@
|
|
1
|
-
import { Object3D,Quaternion, Vector3 } from "three";
|
1
|
+
import { Object3D, Quaternion, Vector3 } from "three";
|
2
2
|
|
3
|
+
import { isDevEnvironment } from "../engine/debug/index.js";
|
3
4
|
import { InstantiateOptions } from "../engine/engine_gameobject.js";
|
4
5
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
5
6
|
import { Behaviour, GameObject } from "./Component.js";
|
@@ -45,10 +46,10 @@
|
|
45
46
|
// legacy – DragControls was required for duplication and so often the component is still there; we work around that by disabling it here
|
46
47
|
const dragControls = this.gameObject.getComponent(DragControls);
|
47
48
|
if (dragControls) {
|
48
|
-
console.warn(
|
49
|
+
if (isDevEnvironment()) console.warn(`Please remove DragControls from \"${dragControls.name}\": it's not needed anymore when the object also has a Duplicatable component`);
|
49
50
|
dragControls.enabled = false;
|
50
51
|
}
|
51
|
-
|
52
|
+
|
52
53
|
if (!this.gameObject.getComponentInParent(ObjectRaycaster))
|
53
54
|
this.gameObject.addNewComponent(ObjectRaycaster);
|
54
55
|
|
@@ -8,19 +8,22 @@
|
|
8
8
|
|
9
9
|
tryEval(`if(!globalThis["NEEDLE_ENGINE_VERSION"]) globalThis["NEEDLE_ENGINE_VERSION"] = "0.0.0";`)
|
10
10
|
tryEval(`if(!globalThis["NEEDLE_ENGINE_GENERATOR"]) globalThis["NEEDLE_ENGINE_GENERATOR"] = "unknown";`)
|
11
|
+
tryEval(`if(!globalThis["NEEDLE_PROJECT_BUILD_TIME"]) globalThis["NEEDLE_PROJECT_BUILD_TIME"] = "unknown";`)
|
11
12
|
|
12
13
|
declare const NEEDLE_ENGINE_VERSION: string
|
13
14
|
declare const NEEDLE_ENGINE_GENERATOR: string;
|
15
|
+
declare const NEEDLE_PROJECT_BUILD_TIME: string;
|
14
16
|
|
15
17
|
// Make sure to wrap the new global this define in underscores to prevent the bundler from replacing it with the actual value
|
16
18
|
tryEval(`globalThis["__NEEDLE_ENGINE_VERSION__"] = "` + NEEDLE_ENGINE_VERSION + `";`)
|
17
19
|
tryEval(`globalThis["__NEEDLE_ENGINE_GENERATOR__"] = "` + NEEDLE_ENGINE_GENERATOR + `";`)
|
20
|
+
tryEval(`globalThis["__NEEDLE_PROJECT_BUILD_TIME__"] = "` + NEEDLE_PROJECT_BUILD_TIME + `";`)
|
18
21
|
|
19
22
|
|
20
|
-
|
21
23
|
export const VERSION = NEEDLE_ENGINE_VERSION;
|
22
24
|
export const GENERATOR = NEEDLE_ENGINE_GENERATOR;
|
23
|
-
|
25
|
+
const BUILD_TIME = NEEDLE_PROJECT_BUILD_TIME;
|
26
|
+
if (debug) console.log(`Engine version: ${VERSION} (generator: ${GENERATOR})\nProject built at ${BUILD_TIME}`);
|
24
27
|
|
25
28
|
export const activeInHierarchyFieldName = "needle_isActiveInHierarchy";
|
26
29
|
export const builtinComponentKeyName = "builtin_components";
|
@@ -157,6 +157,14 @@
|
|
157
157
|
|
158
158
|
onDeserialize(data: any, context: SerializationContext) {
|
159
159
|
if (data?.guid) {
|
160
|
+
|
161
|
+
// it's a workaround for VolumeParameter having a guid as well. Generally we will probably never have to resolve a component in the scene when it's coming from the persistent asset extension (like in the case for postprocessing volume parameters)
|
162
|
+
if (data.___persistentAsset) {
|
163
|
+
if(debugExtension) console.log("Skipping component deserialization because it's a persistent asset", data);
|
164
|
+
return undefined;
|
165
|
+
}
|
166
|
+
|
167
|
+
const currentPath = context.path;
|
160
168
|
// TODO: need to serialize some identifier for referenced components as well, maybe just guid?
|
161
169
|
// because here the components are created but dont have their former guid assigned
|
162
170
|
// and will later in the stack just get a newly generated guid
|
@@ -174,8 +182,9 @@
|
|
174
182
|
res = this.findObjectForGuid(data.guid, context.context?.scene);
|
175
183
|
if (res) return res;
|
176
184
|
}
|
177
|
-
if (isDevEnvironment() || debugExtension)
|
178
|
-
console.warn("Could not resolve component reference"
|
185
|
+
if (isDevEnvironment() || debugExtension) {
|
186
|
+
console.warn("Could not resolve component reference: \"" + currentPath + "\" using guid " + data.guid, context.target);
|
187
|
+
}
|
179
188
|
data["could_not_resolve"] = true;
|
180
189
|
return undefined;
|
181
190
|
}
|
@@ -360,7 +360,6 @@
|
|
360
360
|
obj.onAfterDeserialize(serializedData, context);
|
361
361
|
}
|
362
362
|
|
363
|
-
context.path = undefined;
|
364
363
|
return true;
|
365
364
|
}
|
366
365
|
|
@@ -180,9 +180,9 @@
|
|
180
180
|
static get xrSystem(): XRSystem | undefined {
|
181
181
|
return ('xr' in navigator) ? navigator.xr : undefined;
|
182
182
|
}
|
183
|
-
static isXRSupported() { return Promise.all([this.isVRSupported(), this.isARSupported()]).then(res => res.some(e => e)) }
|
184
|
-
static isVRSupported() { return this.xrSystem?.isSessionSupported('immersive-vr') ?? Promise.resolve(false); }
|
185
|
-
static isARSupported() { return this.xrSystem?.isSessionSupported('immersive-ar') ?? Promise.resolve(false); }
|
183
|
+
static isXRSupported() { return Promise.all([this.isVRSupported(), this.isARSupported()]).then(res => res.some(e => e)).catch(() => false); }
|
184
|
+
static isVRSupported() { return this.xrSystem?.isSessionSupported('immersive-vr').catch(err => { if (debug) console.error(err); return false }) ?? Promise.resolve(false); }
|
185
|
+
static isARSupported() { return this.xrSystem?.isSessionSupported('immersive-ar').catch(err => { if (debug) console.error(err); return false }) ?? Promise.resolve(false); }
|
186
186
|
|
187
187
|
private static _currentSessionRequest?: Promise<XRSession>;
|
188
188
|
private static _activeSession: NeedleXRSession | null;
|
@@ -115,7 +115,8 @@
|
|
115
115
|
const ios = isiOS()
|
116
116
|
const safari = isSafari();
|
117
117
|
if (debug || (ios && safari)) {
|
118
|
-
|
118
|
+
if (this.allowCreateQuicklookButton)
|
119
|
+
this.button = this.createQuicklookButton();
|
119
120
|
|
120
121
|
this.lastCallback = this.quicklookCallback.bind(this);
|
121
122
|
this.link = ensureQuicklookLinkIsCreated(this.context);
|
@@ -25,6 +25,9 @@
|
|
25
25
|
/** Set to true to create an imports.log file that shows all module imports. The file is generated when stopping the server. */
|
26
26
|
logModuleImportChains: boolean;
|
27
27
|
|
28
|
+
/** Set to true to disable generating the buildinfo.json file in your output directory */
|
29
|
+
noBuildInfo: boolean;
|
30
|
+
|
28
31
|
/** required for @serializable https://github.com/vitejs/vite/issues/13736 */
|
29
32
|
vite44Hack: boolean;
|
30
33
|
|
@@ -257,6 +257,12 @@
|
|
257
257
|
// if (this.invertForward) {
|
258
258
|
// reticle.rotateY(Math.PI);
|
259
259
|
// }
|
260
|
+
|
261
|
+
// Workaround: For a custom reticle we apply the view based transform during placement preview
|
262
|
+
// See NE-4161 for context
|
263
|
+
if (this.customReticle)
|
264
|
+
this.applyViewBasedTransform(reticle);
|
265
|
+
|
260
266
|
reticle.updateMatrix();
|
261
267
|
reticle.visible = true;
|
262
268
|
if (reticle.parent !== this.context.scene)
|
@@ -350,8 +356,32 @@
|
|
350
356
|
}
|
351
357
|
}
|
352
358
|
|
359
|
+
private applyViewBasedTransform(reticle: Object3D) {
|
360
|
+
// Make reticle face the user to unify the placement experience across devices.
|
361
|
+
// The pose that we're receiving from the hit test varies between devices:
|
362
|
+
// - Quest: currently aligned to the mesh that was hit (depends on room setup), has changed a couple times
|
363
|
+
// - Android WebXR: looking at the camera, but pretty random when on a wall
|
364
|
+
// - Mozilla WebXR Viewer: aligned to the start of the session
|
365
|
+
const camGo = this.context.mainCamera as Object3D as GameObject;
|
366
|
+
const reticleGo = reticle as GameObject;
|
367
|
+
const camWP = camGo.worldPosition;
|
368
|
+
const reticleWp = reticleGo.worldPosition;
|
369
|
+
// const distance = camWP.distanceTo(reticleWp);
|
370
|
+
camWP.y = reticleWp.y;
|
371
|
+
reticle.lookAt(camWP);
|
372
|
+
|
373
|
+
// TODO: ability to scale the reticle so that we can fit the scene depending on the view angle or distance to the reticle.
|
374
|
+
// Currently, doing this leads to wrong placement of the scene.
|
375
|
+
/*
|
376
|
+
const rigScale = NeedleXRSession.active?.rigScale || 1;
|
377
|
+
const scale = distance * rigScale;
|
378
|
+
reticle.scale.set(scale, scale, scale);
|
379
|
+
*/
|
380
|
+
}
|
381
|
+
|
353
382
|
private onApplyPose(reticle: Object3D) {
|
354
383
|
const rigObject = NeedleXRSession.active?.rig?.gameObject;
|
384
|
+
const rigScale = NeedleXRSession.active?.rigScale || 1;
|
355
385
|
if (rigObject) {
|
356
386
|
// save the previous rig parent
|
357
387
|
const previousParent = rigObject.parent || this.context.scene;
|
@@ -366,6 +396,7 @@
|
|
366
396
|
this._rigPlacementMatrix = rigObject.matrix.clone();
|
367
397
|
}
|
368
398
|
|
399
|
+
this.applyViewBasedTransform(reticle);
|
369
400
|
reticle.updateMatrix();
|
370
401
|
// attach rig to reticle (since the reticle is in rig space it's a easy way to place the rig where we want it relative to the reticle)
|
371
402
|
this.context.scene.add(reticle);
|
@@ -373,7 +404,7 @@
|
|
373
404
|
reticle.removeFromParent();
|
374
405
|
|
375
406
|
// move rig now relative to the reticle
|
376
|
-
//
|
407
|
+
// TODO support scaled reticle
|
377
408
|
rigObject.scale.set(this.arScale, this.arScale, this.arScale);
|
378
409
|
rigObject.position.multiplyScalar(this.arScale);
|
379
410
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
|
3
|
+
|
4
|
+
/** Create a file containing information about the build inside the build directory
|
5
|
+
* @param {String} buildDirectory
|
6
|
+
*/
|
7
|
+
export function createBuildInfoFile(buildDirectory) {
|
8
|
+
if (!buildDirectory) {
|
9
|
+
console.warn("WARN: Can not create build info file because \"buildDirectory\" is not defined");
|
10
|
+
return;
|
11
|
+
}
|
12
|
+
// start creating the buildinfo
|
13
|
+
const buildInfo = {
|
14
|
+
time: new Date().toISOString(),
|
15
|
+
totalsize: 0,
|
16
|
+
files: []
|
17
|
+
};
|
18
|
+
console.log("[needle-buildinfo] - Collect files in " + buildDirectory);
|
19
|
+
recursivelyCollectFiles(buildDirectory, "", buildInfo);
|
20
|
+
const buildInfoPath = `${buildDirectory}/needle.buildinfo.json`;
|
21
|
+
const totalSizeInMB = buildInfo.totalsize / 1024 / 1024;
|
22
|
+
console.log(`[needle-buildinfo] - Collected ${buildInfo.files.length} files (${totalSizeInMB.toFixed(2)} MB). Write info to \"${buildInfoPath}\"`);
|
23
|
+
fs.writeFileSync(buildInfoPath, JSON.stringify(buildInfo));
|
24
|
+
}
|
25
|
+
|
26
|
+
/** Recursively collect all files in a directory
|
27
|
+
* @param {String} directory to search
|
28
|
+
* @param {{ files: Array<string>, totalsize:number }} info
|
29
|
+
*/
|
30
|
+
function recursivelyCollectFiles(directory, path, info) {
|
31
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
32
|
+
for (const entry of entries) {
|
33
|
+
if (entry.isDirectory()) {
|
34
|
+
// make sure we never collect files inside node_modules
|
35
|
+
if (entry.name === "node_modules") {
|
36
|
+
console.warn("WARN: Skipping node_modules directory at " + path);
|
37
|
+
continue;
|
38
|
+
}
|
39
|
+
const newPath = path?.length <= 0 ? entry.name : `${path}/${entry.name}`;
|
40
|
+
const newDirectory = `${directory}/${entry.name}`;
|
41
|
+
console.log("[needle-buildinfo] - Collect files in " + newPath);
|
42
|
+
recursivelyCollectFiles(newDirectory, newPath, info);
|
43
|
+
} else {
|
44
|
+
const relpath = `${path}/${entry.name}`;
|
45
|
+
info.files.push(relpath);
|
46
|
+
try {
|
47
|
+
const fullpath = `${directory}/${entry.name}`;
|
48
|
+
const stats = fs.statSync(fullpath);
|
49
|
+
info.totalsize += stats.size;
|
50
|
+
}
|
51
|
+
catch {
|
52
|
+
//ignore
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { createBuildInfoFile } from '../common/buildinfo.js';
|
2
|
+
import { getOutputDirectory } from './config.js';
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
/** Create a buildinfo file in the build directory
|
7
|
+
* @param {import('../types').userSettings} userSettings
|
8
|
+
* @returns {import('vite').Plugin}
|
9
|
+
*/
|
10
|
+
export const needleBuildInfo = (command, config, userSettings) => {
|
11
|
+
|
12
|
+
if (userSettings?.noBuildInfo) return;
|
13
|
+
|
14
|
+
return {
|
15
|
+
name: 'needle-buildinfo',
|
16
|
+
apply: "build",
|
17
|
+
enforce: "post",
|
18
|
+
closeBundle: () => {
|
19
|
+
const buildDirectory = getOutputDirectory();
|
20
|
+
createBuildInfoFile(buildDirectory);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|