Needle Engine

Changes between version 3.48.0-experimental.7 and 3.47.3-beta
Files changed (11) hide show
  1. plugins/vite/build-pipeline.js +62 -233
  2. plugins/common/buildinfo.js +3 -4
  3. plugins/vite/buildinfo.js +14 -20
  4. plugins/vite/copyfiles.js +67 -66
  5. plugins/common/files.js +0 -31
  6. plugins/vite/index.js +1 -1
  7. plugins/common/timers.js +0 -8
  8. src/engine/engine_element.ts +1 -0
  9. src/engine/engine_utils_screenshot.ts +1 -1
  10. plugins/types/needleConfig.d.ts +0 -3
  11. plugins/types/userconfig.d.ts +5 -23
plugins/vite/build-pipeline.js CHANGED
@@ -1,85 +1,21 @@
1
- import { ChildProcess, exec } from 'child_process';
2
- import { getOutputDirectory, loadConfig } from './config.js';
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
4
- import { copyFilesSync } from '../common/files.js';
5
- import { delay } from '../common/timers.js';
1
+ import { ChildProcess, exec, execSync, spawn } from 'child_process';
2
+ import { getOutputDirectory, loadConfig, tryLoadProjectConfig } from './config.js';
3
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs';
6
4
 
7
- /**
8
- * @param {import('../types').userSettings} config
9
- * @returns {boolean}
10
- */
11
- function validateCloudBuildConfiguration(config) {
12
- if (config.buildPipeline != undefined) {
13
- if (process.env.CI) {
14
- if ((process.env.NEEDLE_CLOUD_TOKEN == undefined || process.env.NEEDLE_CLOUD_TOKEN.length <= 0)) {
15
- const isGithubAction = process.env.CI && process.env.GITHUB_ACTION;
16
- let msg = `Missing Needle Cloud access token. Please set your Needle Cloud token via \`process.env.NEEDLE_CLOUD_TOKEN\`.`;
17
- if (isGithubAction) {
18
- msg += `
19
- Make sure to pass the token as a secret to the Github action.
20
- For this you may have to modify your .github workflow yaml file to include the following code:
21
- env:
22
- - NEEDLE_CLOUD_TOKEN: \${{ secrets.NEEDLE_CLOUD_TOKEN }}
23
- `;
24
- let error = `${msg}`;
25
- throw new Error(error);
26
- }
27
- }
28
- }
29
- }
30
-
31
- return true;
32
- }
33
-
34
5
  // see https://linear.app/needle/issue/NE-3798
35
6
 
7
+ export let buildPipelineTask;
36
8
 
37
- /** @type {Promise<any>|null} */
38
- let buildPipelineTask;
39
- /** @type {null | {tempDirectory:string, outputDirectory:string}} */
40
- let buildPipelineTaskResults = null;
41
-
42
- export function waitForBuildPipelineToFinish() {
43
- return buildPipelineTask;
44
- }
45
-
46
- /** This time is set when a build plugin is triggered to run
47
- * We increase the time by 10-20 seconds each time because we might have a multi step process
48
- * where the build pipeline is triggered in the SSR build (which we can not always detect)
49
- * and the final client build is triggered later (but the build pipeline is still running and waiting)
50
- */
51
- let maxOutputDirectoryCreatedWaitTime = 0;
52
9
  /**
53
- * @param {boolean} debugLog
54
- */
55
- function increaseMaxWaitTime(debugLog) {
56
- maxOutputDirectoryCreatedWaitTime = Date.now();
57
- let maxWaitTime = 10_000;
58
- if (process.env.CI) {
59
- maxWaitTime += 50_000;
60
- }
61
- maxOutputDirectoryCreatedWaitTime += maxWaitTime;
62
- if (debugLog) {
63
- log(`Increased max wait time by ${maxWaitTime / 1000}sec until ${new Date(maxOutputDirectoryCreatedWaitTime).toISOString()}`);
64
- }
65
- }
66
-
67
- /**
68
10
  * Runs the needle build pipeline as part of the vite build process
69
- * @param {string} command
70
- * @param {import('vite').UserConfig} config
71
11
  * @param {import('../types').userSettings} userSettings
72
- * @returns {Promise<import('vite').Plugin | null>}
12
+ * @returns {import('vite').Plugin}
73
13
  */
74
14
  export const needleBuildPipeline = async (command, config, userSettings) => {
75
15
 
76
16
  // we only want to run compression here if this is a distribution build
77
17
  // this is handled however in the `apply` hook
78
- if (userSettings.noBuildPipeline) return null;
79
- if (userSettings.buildPipeline?.enabled === false) {
80
- log("Skipping build pipeline because it is disabled in the user settings via `buildPipeline.enabled: false`");
81
- return null;
82
- }
18
+ if (userSettings.noBuildPipeline) return;
83
19
 
84
20
  const packageJsonPath = process.cwd() + "/package.json";
85
21
  await fixPackageJson(packageJsonPath);
@@ -100,98 +36,43 @@
100
36
  if (!shouldRun) {
101
37
  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\".");
102
38
  await new Promise((resolve, _) => setTimeout(resolve, 1000));
103
- return null;
39
+ return;
104
40
  }
105
41
 
106
- if (process.env.CI) {
107
- log("Running in CI environment");
108
- }
109
-
110
- validateCloudBuildConfiguration(userSettings);
111
-
112
- const verboseOutput = userSettings?.buildPipeline?.verbose || false;
113
- let taskHasCompleted = false;
114
-
42
+ /** @type {Promise<any>|null} */
43
+ let task = null;
44
+ let taskFinished = false;
45
+ let taskSucceeded = false;
115
46
  return {
116
47
  name: 'needle-buildpipeline',
117
48
  enforce: "post",
118
- apply: (_conf, env) => {
119
- if (verboseOutput) {
120
- log("Apply:", env);
121
- }
122
- // Don't run for SSR builds (e.g. sveltekit).
123
- // Unfortunately this is always falls in vite 4.3 so we can not rely on it solely
124
- if (env.ssrBuild) return false;
125
- // Dont run if there's already a build pipeline task running
126
- if (env.command === "build") {
127
- increaseMaxWaitTime(verboseOutput);
128
- if (buildPipelineTask) {
129
- return false;
130
- }
131
- return true;
132
- }
133
- return false;
49
+ apply: 'build',
50
+ buildEnd() {
51
+ // start the compression process once vite is done copying the files
52
+ task = invokeBuildPipeline(userSettings).then((res) => {
53
+ taskFinished = true;
54
+ taskSucceeded = res;
55
+ });
56
+ buildPipelineTask = task;
134
57
  },
135
- buildEnd(error) {
136
- increaseMaxWaitTime(verboseOutput);
137
-
138
- if (verboseOutput) {
139
- log("Build end:", error ?? "No error");
140
- }
141
- if (error) {
142
- // if there was an error during the build we should not run the build pipeline
143
- }
144
- else {
145
- if (buildPipelineTask) {
146
- log("Build pipeline already running...");
147
- return;
148
- }
149
- let taskSucceeded = false;
150
- // start the compression process once vite is done copying the files
151
- buildPipelineTask = invokeBuildPipeline(userSettings)
152
- .then((res) => {
153
- taskSucceeded = res;
154
- })
155
- .finally(() => {
156
- taskHasCompleted = true;
157
- if (!taskSucceeded) {
158
- throw new Error("[needle-buildpipeline] - Build pipeline failed. Check the logs for more information.");
159
- }
160
- else {
161
- log("Finished successfully");
162
- }
163
- });
164
- }
165
- },
166
58
  closeBundle() {
167
- if (!buildPipelineTask) {
168
- return;
169
- }
170
- if (!taskHasCompleted) {
171
- log("Waiting for build pipeline to finish...");
172
- }
173
- // // this is the last hook that is called, so we can wait for the task to finish here
174
- return buildPipelineTask = buildPipelineTask?.then(() => {
175
- // Copy the results to their final output directory.
176
- if (buildPipelineTaskResults != null) {
177
- log("Copying files from temporary output directory to final output directory...");
178
- const ctx = { count: 0 }
179
- copyFilesSync(buildPipelineTaskResults.tempDirectory, buildPipelineTaskResults.outputDirectory, true, ctx);
180
- log(`Copied ${ctx.count} file(s)`);
181
- }
182
- else {
183
- log("No files to copy - build pipeline did not run or did not finish successfully");
184
- }
59
+ log("Waiting for postprocessing to finish...");
60
+ // this is the last hook that is called, so we can wait for the task to finish here
61
+ if (taskFinished) return;
62
+ // delay the final log slightly to give other plugins a chance to log their stuff
63
+ wait(100).then(() => {
64
+ if (!taskFinished) log("Waiting for postprocessing to finish...")
185
65
  });
66
+ return task.then(_ => {
67
+ log("finished", taskSucceeded ? "successfully" : "with errors");
68
+ });
186
69
  },
187
70
  }
188
71
  }
189
72
 
190
-
191
73
  /**
192
74
  * Previously we did always install the build pipeline and run an extra command to invoke the build pipeline.
193
75
  * This is now done automatically by the needle build pipeline plugin - so we update all legacy projects to use the new method.
194
- * @param {string} packageJsonPath
195
76
  */
196
77
  async function fixPackageJson(packageJsonPath) {
197
78
  if (!existsSync(packageJsonPath)) {
@@ -208,11 +89,9 @@
208
89
  writeFileSync(packageJsonPath, fixed);
209
90
  }
210
91
 
211
- /** @param {any} args */
212
92
  function log(...args) {
213
93
  console.log("[needle-buildpipeline]", ...args);
214
94
  }
215
- /** @param {any} args */
216
95
  function warn(...args) {
217
96
  console.warn("WARN: [needle-buildpipeline]", ...args);
218
97
  }
@@ -225,113 +104,63 @@
225
104
  const installPath = "node_modules/@needle-tools/gltf-build-pipeline";
226
105
  const fullInstallPath = process.cwd() + "/" + installPath;
227
106
  const existsLocally = existsSync(fullInstallPath);
228
- if (existsLocally) {
229
- log("Found local build pipeline installation at " + fullInstallPath);
107
+ if (!existsLocally) {
108
+ // await execSync("npx --yes @needle-tools/n")
109
+ // throw new Error(`ERR: Build pipeline not found at \"${fullInstallPath}\". \nTo disable this plugin you can pass \"noBuildPipeline: true\" to the needle config in vite.config.js`);
110
+ warn("@needle-tools/gltf-build-pipeline not found in package - using latest version")
230
111
  }
231
- await delay(500);
112
+ await wait(500);
232
113
  const outputDirectory = getOutputDirectory() + "/assets";
233
- const startWaitTime = Date.now();
234
- const maxEndTime = startWaitTime + 120_000;
235
- /** wait until the output directory exists
236
- * @param {number} iteration
237
- * @returns {Promise<boolean>}
238
- */
114
+ // wait until the output directory exists - this depends on speed
239
115
  function waitForOutputDirectory(iteration) {
240
- // we wait for the output directory
241
116
  if (!existsSync(outputDirectory)) {
242
- if (maxOutputDirectoryCreatedWaitTime != 0 && Date.now() > maxOutputDirectoryCreatedWaitTime) {
117
+ if (iteration > 10) {
243
118
  return Promise.resolve(false);
244
119
  }
245
- else if (Date.now() > maxEndTime) {
246
- log("Max wait time exceeded - aborting...");
247
- return Promise.resolve(false);
248
- }
249
- if (iteration <= 0) log(`Waiting for output directory to be created... (${outputDirectory})`);
250
- return delay(1000).then(() => waitForOutputDirectory(iteration + 1));
120
+ if (iteration <= 0) log("Waiting for output directory to be created...");
121
+ return wait(1000).then(() => waitForOutputDirectory(iteration + 1));
251
122
  }
252
123
  return Promise.resolve(true);
253
124
  }
254
125
  if (!await waitForOutputDirectory(0)) {
255
- warn(`Output directory not found/created at \"${outputDirectory}\" - aborting...`);
256
- return false;
126
+ warn("Directory not found at " + outputDirectory);
127
+ return;
257
128
  }
258
- const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf") || f.endsWith(".vrm") || f.endsWith(".fbx"));
259
- log(`${files.length} file(s) to process in ${outputDirectory}`);
129
+ const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf"));
130
+ log(files.length + " file(s) to process");
260
131
 
261
132
  /** @type {null | ChildProcess} */
262
- let proc = null;
133
+ let sub = null;
263
134
 
264
- const cloudAccessToken = process.env.NEEDLE_CLOUD_TOKEN;
265
- const runInCloud = typeof cloudAccessToken === "string" && cloudAccessToken.length > 0;
266
- // if a user has defined the build pipeline settings object but not passed in a token we should print out some information
267
- // or perhaps log an error / prevent the build from running completely
268
- if (opts.buildPipeline && !runInCloud && process.env.CI) {
269
- warn(`No cloud access token found. Please set it via process.env.NEEDLE_CLOUD_TOKEN`);
270
- return false;
271
- }
272
-
273
-
274
- // put the processed files first in a temporary directory. They will be moved to the output directory at the end of the buildstep
275
- // this is so that processes like sveltekit-static-adapter can run first and does not override already compressed files
276
- const tempOutputPath = process.cwd() + "/node_modules/.needle/build-pipeline/output";
277
- if (existsSync(tempOutputPath)) {
278
- log("Removing temporary output directory at " + tempOutputPath);
279
- rmSync(tempOutputPath, { recursive: true, force: true });
280
- }
281
- mkdirSync(tempOutputPath, { recursive: true });
282
-
283
- /** @param {number} code */
284
- function onExit(code) {
285
- if (code === 0)
286
- buildPipelineTaskResults = {
287
- tempDirectory: tempOutputPath,
288
- outputDirectory: outputDirectory
289
- }
290
- }
291
-
292
- // allow running the build pipeline in the cloud. It requires and access token to be set in the vite.config.js
293
- // this can be set via e.g. process.env.NEEDLE_CLOUD_TOKEN
294
- if (runInCloud) {
295
- if (!cloudAccessToken || !(typeof cloudAccessToken === "string") || cloudAccessToken.length <= 0) {
296
- throw new Error("No cloud access token configured. Please set it via process.env.NEEDLE_CLOUD_TOKEN or in the vite.config.js");
297
- }
298
- let cmd = `npx --yes @needle-tools/cloud-cli@latest optimize "${outputDirectory}" --accessToken ${cloudAccessToken}`;
299
- const projectName = opts.buildPipeline?.projectName;
300
- if (projectName) {
301
- cmd += ` --name "${projectName}"`;
302
- }
303
- if (opts.buildPipeline?.verbose === true) {
304
- cmd += " --verbose";
305
- }
306
- cmd += " --outdir \"" + tempOutputPath + "\"";
307
- console.log("\n")
308
- log("Running compression in cloud");
309
- proc = exec(cmd);
310
- }
311
- else if (existsLocally) {
135
+ if (existsLocally) {
312
136
  const cmd = `needle-gltf transform "${outputDirectory}"`;
313
137
  log("Running command \"" + cmd + "\" at " + process.cwd() + "...");
314
- proc = exec(cmd, { cwd: installPath });
138
+ sub = exec(cmd, { cwd: installPath });
315
139
  }
316
140
  else {
317
- const version = opts.buildPipeline?.version || "latest";
318
- const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}" \"${tempOutputPath}\"`;
319
- log(`Running compression locally with version ${version}`);
320
- proc = exec(cmd);
141
+ const version = opts.buildPipelineVersion || "latest";
142
+ const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}"`;
143
+ log("Running command \"" + cmd);
144
+ sub = exec(cmd);
321
145
  }
322
- /** @param {any} data */
323
- function onLog(data) {
146
+ sub.stdout.on('data', data => {
324
147
  if (data.length <= 0) return;
325
148
  // ensure that it doesnt end with a newline
326
- while (data.endsWith("\n")) data = data.slice(0, -1);
327
- log(data);
328
- }
329
- proc.stdout?.on('data', onLog);
330
- proc.stderr?.on('data', onLog);
149
+ if (data.endsWith("\n")) data = data.slice(0, -1);
150
+ console.log(data);
151
+ });
152
+ sub.stderr.on('data', console.error);
331
153
  return new Promise((resolve, reject) => {
332
- proc.on('exit', (code) => {
333
- onExit(code || 0);
154
+ sub.on('exit', (code) => {
334
155
  resolve(code === 0);
335
156
  });
336
157
  });
337
158
  }
159
+
160
+ function wait(ms) {
161
+ return new Promise((resolve, reject) => {
162
+ setTimeout(() => {
163
+ resolve();
164
+ }, ms);
165
+ });
166
+ }
plugins/common/buildinfo.js CHANGED
@@ -16,11 +16,11 @@
16
16
  totalsize: 0,
17
17
  files: []
18
18
  };
19
- console.log(`[needle-buildinfo] - Begin collecting files in \"${buildDirectory}\"`);
19
+ console.log("[needle-buildinfo] - Collect files in " + buildDirectory);
20
20
  recursivelyCollectFiles(buildDirectory, "", buildInfo);
21
21
  const buildInfoPath = `${buildDirectory}/needle.buildinfo.json`;
22
22
  const totalSizeInMB = buildInfo.totalsize / 1024 / 1024;
23
- console.log(`[needle-buildinfo] - Collected ${buildInfo.files.length} files (${totalSizeInMB.toFixed(2)} MB). Writing build info to \"${buildInfoPath}\"`);
23
+ console.log(`[needle-buildinfo] - Collected ${buildInfo.files.length} files (${totalSizeInMB.toFixed(2)} MB). Write info to \"${buildInfoPath}\"`);
24
24
  fs.writeFileSync(buildInfoPath, JSON.stringify(buildInfo));
25
25
  }
26
26
 
@@ -39,11 +39,10 @@
39
39
  }
40
40
  const newPath = path?.length <= 0 ? entry.name : `${path}/${entry.name}`;
41
41
  const newDirectory = `${directory}/${entry.name}`;
42
- console.log(`[needle-buildinfo] - Collect files in \"/${newPath}\"`);
42
+ console.log("[needle-buildinfo] - Collect files in " + newPath);
43
43
  recursivelyCollectFiles(newDirectory, newPath, info);
44
44
  } else {
45
45
  const relpath = `${path}/${entry.name}`;
46
- // console.log("[needle-buildinfo] - New File: " + relpath);
47
46
  const filehash = crypto.createHash('sha256');
48
47
  const fullpath = `${directory}/${entry.name}`;
49
48
  filehash.update(fs.readFileSync(fullpath));
plugins/vite/buildinfo.js CHANGED
@@ -1,38 +1,32 @@
1
1
  import { createBuildInfoFile } from '../common/buildinfo.js';
2
- import { waitForBuildPipelineToFinish } from './build-pipeline.js';
2
+ import { buildPipelineTask } from './build-pipeline.js';
3
3
  import { getOutputDirectory } from './config.js';
4
4
 
5
- let level = 0;
6
5
 
6
+
7
7
  /** Create a buildinfo file in the build directory
8
8
  * @param {import('../types').userSettings} userSettings
9
- * @returns {import('vite').Plugin | null}
9
+ * @returns {import('vite').Plugin}
10
10
  */
11
11
  export const needleBuildInfo = (command, config, userSettings) => {
12
12
 
13
- if (userSettings?.noBuildInfo) return null;
13
+ if (userSettings?.noBuildInfo) return;
14
14
 
15
15
  return {
16
16
  name: 'needle-buildinfo',
17
17
  apply: "build",
18
18
  enforce: "post",
19
- buildStart: () => {
20
- level++;
21
- },
22
19
  closeBundle: async () => {
23
- if (--level > 0) {
24
- console.log("[needle-buildinfo] - Skipped because of nested build");
25
- return;
26
- }
27
- const task = waitForBuildPipelineToFinish();
28
- if (task instanceof Promise) {
29
- console.log("[needle-buildinfo] - Waiting for build pipeline to finish");
30
- await task.catch(() => { }).finally(() => console.log("[needle-buildinfo] - Build pipeline finished!"));
31
- }
32
- // wait for gzip
33
- await delay(500);
34
- const buildDirectory = getOutputDirectory();
35
- createBuildInfoFile(buildDirectory);
20
+ return new Promise(async res => {
21
+ if (buildPipelineTask instanceof Promise) {
22
+ await buildPipelineTask.catch(() => { });
23
+ }
24
+ // wait for gzip
25
+ await delay(500);
26
+ const buildDirectory = getOutputDirectory();
27
+ createBuildInfoFile(buildDirectory);
28
+ res();
29
+ });
36
30
  }
37
31
  }
38
32
  }
plugins/vite/copyfiles.js CHANGED
@@ -2,55 +2,40 @@
2
2
  import { resolve, join, isAbsolute } from 'path'
3
3
  import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir } from 'fs';
4
4
  import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
5
- import { copyFilesSync } from '../common/files.js';
6
5
 
7
- const pluginName = "needle-copy-files";
8
6
 
9
-
10
7
  /** copy files on build from assets to dist
11
8
  * @param {import('../types').userSettings} userSettings
12
- * @returns {import('vite').Plugin | null}
13
9
  */
14
10
  export const needleCopyFiles = (command, config, userSettings) => {
15
11
 
16
12
  if (config?.noCopy === true || userSettings?.noCopy === true) {
17
- return null;
13
+ return;
18
14
  }
19
15
 
20
16
  return {
21
17
  name: 'needle-copy-files',
22
- // explicitly don't enforce post or pre because we want to run before the build-pipeline plugin
23
- enforce: "pre",
24
- apply: (_conf, env) => {
25
- if (env.ssrBuild === true) return false;
26
- return true;
27
- },
28
18
  buildStart() {
29
- return run("start", config);
19
+ return run(false, config);
30
20
  },
31
21
  closeBundle() {
32
- return run("end", config);
22
+ return run(true, config);
33
23
  },
34
24
  }
35
25
  }
36
26
 
37
- /**
38
- * @param {"start" | "end"} buildstep
39
- * @param {import('../types').userSettings} config
40
- */
41
- async function run(buildstep, config) {
42
- console.log(`[${pluginName}] - Copy files at ${buildstep}`);
27
+ async function run(isBuild, config) {
43
28
  const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true;
44
29
 
45
30
  const baseDir = process.cwd();
46
- const override = buildstep === "start";
31
+ const pluginName = "needle-copy-files";
47
32
 
48
33
  let assetsDirName = "assets";
49
34
  let outdirName = "dist";
50
35
 
51
36
  const needleConfig = tryLoadProjectConfig();
52
37
  if (needleConfig) {
53
- assetsDirName = needleConfig.assetsDirectory || assetsDirName;
38
+ assetsDirName = needleConfig.assetsDirectory;
54
39
  while (assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1);
55
40
 
56
41
  if (needleConfig.buildDirectory)
@@ -63,61 +48,60 @@
63
48
  if (existsSync(engineIncludeDir)) {
64
49
  console.log(`[${pluginName}] - Copy engine include to ${baseDir}/include`)
65
50
  const projectIncludeDir = resolve(baseDir, 'include');
66
- copyFilesSync(engineIncludeDir, projectIncludeDir);
51
+ copyRecursiveSync(engineIncludeDir, projectIncludeDir);
67
52
  }
68
53
  }
69
54
 
70
- const outDir = resolve(baseDir, outdirName);
71
- if (!existsSync(outDir)) {
72
- mkdirSync(outDir);
73
- }
55
+ if (isBuild) {
56
+ const outDir = resolve(baseDir, outdirName);
57
+ if (!existsSync(outDir)) {
58
+ mkdirSync(outDir);
59
+ }
74
60
 
75
- // copy a list of files or directories declared in build.copy = [] in the needle.config.json
76
- /*
77
- "build": {
78
- "copy": ["myFolder", "myFile.txt"]
79
- }
80
- */
81
- if (needleConfig?.build?.copy) {
82
- const arr = needleConfig.build.copy;
83
- for (let i = 0; i < arr.length; i++) {
84
- const entry = arr[i];
85
- if (Array.isArray(entry)) {
86
- console.log("WARN: build.copy can only contain string paths to copy to. Found array instead.");
87
- continue;
61
+ // copy a list of files or directories declared in build.copy = [] in the needle.config.json
62
+ /*
63
+ "build": {
64
+ "copy": ["myFolder", "myFile.txt"]
65
+ }
66
+ */
67
+ if (needleConfig?.build?.copy) {
68
+ const arr = needleConfig.build.copy;
69
+ for (let i = 0; i < arr.length; i++) {
70
+ const entry = arr[i];
71
+ if (Array.isArray(entry)) {
72
+ console.log("WARN: build.copy can only contain string paths to copy to. Found array instead.");
73
+ continue;
74
+ }
75
+ const src = resolve(baseDir, entry);
76
+ const dest = resolvePath(outDir, entry);
77
+ if (existsSync(src)) {
78
+ console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
79
+ copyRecursiveSync(src, dest);
80
+ }
88
81
  }
89
- const src = resolve(baseDir, entry);
90
- const dest = resolvePath(outDir, entry);
91
- if (existsSync(src) && dest) {
92
- console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
93
- copyFilesSync(src, dest, override);
94
- }
95
82
  }
96
- }
97
83
 
98
- // copy assets dir
99
- const assetsDir = resolve(baseDir, assetsDirName);
100
- if (existsSync(assetsDir)) {
101
- console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
102
- const targetDir = resolve(outDir, 'assets');
103
- copyFilesSync(assetsDir, targetDir, override);
84
+ // copy assets dir
85
+ const assetsDir = resolve(baseDir, assetsDirName);
86
+ if (existsSync(assetsDir)) {
87
+ console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
88
+ const targetDir = resolve(outDir, 'assets');
89
+ copyRecursiveSync(assetsDir, targetDir);
90
+ }
91
+ else console.log(`WARN: No assets directory found. Skipping copy of ${assetsDirName} resolved to ${assetsDir}`)
92
+ // copy include dir
93
+ const includeDir = resolve(baseDir, 'include');
94
+ if (existsSync(includeDir)) {
95
+ console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
96
+ const targetDir = resolve(outDir, 'include');
97
+ copyRecursiveSync(includeDir, targetDir);
98
+ }
104
99
  }
105
- else console.log(`WARN: No assets directory found. Skipping copy of ${assetsDirName} resolved to ${assetsDir}`)
106
-
107
- // copy include dir
108
- const includeDir = resolve(baseDir, 'include');
109
- if (existsSync(includeDir)) {
110
- console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
111
- const targetDir = resolve(outDir, 'include');
112
- copyFilesSync(includeDir, targetDir, override);
113
- }
114
100
  }
115
101
 
116
102
  /** resolves relative or absolute paths to a path inside the out directory
117
103
  * for example D:/myFile.txt would resolve to outDir/myFile.txt
118
104
  * wherereas "some/relative/path" would become outDir/some/relative/path
119
- * @param {string} outDir
120
- * @param {string} pathValue
121
105
  */
122
106
  function resolvePath(outDir, pathValue) {
123
107
  if (isAbsolute(pathValue)) {
@@ -126,12 +110,29 @@
126
110
  var stats = exists && statSync(pathValue);
127
111
  if (stats.isDirectory()) {
128
112
  const dirName = pathValue.replaceAll('\\', '/').split('/').pop();
129
- if (!dirName) return null;
130
113
  return resolve(outDir, dirName);
131
114
  }
132
115
  const fileName = pathValue.replaceAll('\\', '/').split('/').pop();
133
- if (!fileName) return null;
134
116
  return resolve(outDir, fileName);
135
117
  }
136
118
  return resolve(outDir, pathValue);
137
- }
119
+ }
120
+
121
+ function copyRecursiveSync(src, dest) {
122
+ if (dest === null) {
123
+ console.log(`[${pluginName}] - Copy ${src} to ${dest} - dest is null`)
124
+ return;
125
+ }
126
+ var exists = existsSync(src);
127
+ var stats = exists && statSync(src);
128
+ var isDirectory = exists && stats.isDirectory();
129
+ if (isDirectory) {
130
+ if (!existsSync(dest))
131
+ mkdirSync(dest, { recursive: true });
132
+ readdirSync(src).forEach(function (childItemName) {
133
+ copyRecursiveSync(join(src, childItemName), join(dest, childItemName));
134
+ });
135
+ } else {
136
+ copyFileSync(src, dest);
137
+ }
138
+ };
plugins/common/files.js DELETED
@@ -1,31 +0,0 @@
1
- import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
2
- import { join } from "path";
3
-
4
- /**
5
- * @param {string} src
6
- * @param {string} dest
7
- * @param {boolean} override
8
- * @param {{count:number} | undefined} ctx
9
- */
10
- export function copyFilesSync(src, dest, override = true, ctx = undefined) {
11
- if (dest === null) {
12
- console.log(`[needle-copy] - Copy ${src} to ${dest} - dest is null`)
13
- return;
14
- }
15
- let exists = existsSync(src);
16
- let stats = exists && statSync(src);
17
- let isDirectory = exists && typeof stats != "boolean" && stats.isDirectory();
18
- if (isDirectory) {
19
- if (!existsSync(dest))
20
- mkdirSync(dest, { recursive: true });
21
- readdirSync(src).forEach(function (childItemName) {
22
- // recurse
23
- copyFilesSync(join(src, childItemName), join(dest, childItemName), override, ctx);
24
- });
25
- } else if (!exists || override) {
26
- if (ctx) {
27
- ctx.count += 1;
28
- }
29
- copyFileSync(src, dest);
30
- }
31
- };
plugins/vite/index.js CHANGED
@@ -96,7 +96,7 @@
96
96
  }
97
97
  * ```
98
98
  * @param {string} command
99
- * @param {import('../types/index.js').userSettings} userSettings
99
+ * @param {import('../types').userSettings} userSettings
100
100
  */
101
101
  export const needlePlugins = async (command, config, userSettings) => {
102
102
 
plugins/common/timers.js DELETED
@@ -1,8 +0,0 @@
1
-
2
-
3
- /**
4
- * @param {number} ms
5
- */
6
- export function delay(ms) {
7
- return new Promise(res => setTimeout(res, ms));
8
- }
src/engine/engine_element.ts CHANGED
@@ -140,6 +140,7 @@
140
140
  width: max(600px, 100%);
141
141
  height: max(300px, 100%);
142
142
  touch-action: none;
143
+ -webkit-tap-highlight-color: transparent;
143
144
  }
144
145
 
145
146
  @media (max-width: 600px) {
src/engine/engine_utils_screenshot.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Camera, Color, ColorRepresentation, PerspectiveCamera } from "three";
2
2
 
3
3
  import { Renderer } from "../engine-components/Renderer.js";
4
+ import { RGBAColor } from "./api.js";
4
5
  import { getComponentsInChildren } from "./engine_components.js";
5
6
  import { ContextRegistry } from "./engine_context_registry.js";
6
7
  import { Context } from "./engine_setup.js";
7
- import { RGBAColor } from "./api.js";
8
8
 
9
9
  declare type ScreenshotImageMimeType = "image/webp" | "image/png";
10
10
 
plugins/types/needleConfig.d.ts CHANGED
@@ -22,7 +22,4 @@
22
22
  assetsDirectory?: string;
23
23
  scriptsDirectory?: string;
24
24
  codegenDirectory?: string;
25
- build?: {
26
- copy?: Array<string>;
27
- }
28
25
  }
plugins/types/userconfig.d.ts CHANGED
@@ -5,9 +5,6 @@
5
5
  webpack: object | undefined
6
6
  }
7
7
 
8
- /**
9
- * Settings for the Needle plugin
10
- */
11
8
  export type userSettings = {
12
9
 
13
10
  /** disable needle asap plugin */
@@ -23,8 +20,6 @@
23
20
 
24
21
  /** disable automatic copying of files to include and output directory (dist) */
25
22
  noCopy?: boolean;
26
- /** When enabled the needle-engine include directory will be copied */
27
- copyIncludesFromEngine?: boolean;
28
23
  /** set to false to tree-shake rapier physics engine to the reduce bundle size */
29
24
  useRapier?: boolean;
30
25
  noDependencyWatcher?: boolean;
@@ -49,25 +44,12 @@
49
44
 
50
45
  /** Set to true to disable the needle build pipeline (running compression and optimization as a postprocessing step on the exported glTF files) */
51
46
  noBuildPipeline?: boolean;
47
+ /** Set to a specific version of the Needle Build Pipeline.
48
+ * @default "latest"
49
+ * @example "2.2.0-alpha"
50
+ */
51
+ buildPipelineVersion?: string;
52
52
 
53
- /**
54
- * Use to configure optimized builds
55
- */
56
- buildPipeline?: {
57
- /** Set to false to prevent the build pipeline from running */
58
- enabled?: boolean,
59
- /** Set a project name (cloud only) */
60
- projectName?: string,
61
- /** Enable for verbose log output */
62
- verbose?: boolean,
63
-
64
- /** Set to a specific version of the Needle Build Pipeline.
65
- * @default "latest"
66
- * @example "2.2.0-alpha"
67
- */
68
- version?: string;
69
- }
70
-
71
53
  /** required for @serializable https://github.com/vitejs/vite/issues/13736 */
72
54
  vite44Hack?: boolean;