@needle-tools/gltf-build-pipeline 3.0.0-beta.1 → 3.0.0-beta.3
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 +13 -0
- package/dist/cli/index.js +73 -40
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to this package will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [3.0.0-beta.3] - 2026-06-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Pipeline errors (including compression failures and unhandled exceptions) are now written to the log file to aid diagnosis.
|
|
11
|
+
|
|
12
|
+
## [3.0.0-beta.2] - 2026-06-10
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Authentication failures are now written to a log file (with secrets redacted) to make auth issues easier to diagnose.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- The "Auth failed" message now includes the pipeline version.
|
|
19
|
+
|
|
7
20
|
## [3.0.0-beta.1] - 2026-06-10
|
|
8
21
|
|
|
9
22
|
### Fixed
|
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import path5 from "path";
|
|
|
8
8
|
import { Logger, NodeIO as NodeIO2, PropertyType as PropertyType12, Verbosity as Verbosity2 } from "@gltf-transform/core";
|
|
9
9
|
import { ALL_EXTENSIONS as ALL_EXTENSIONS3 } from "@gltf-transform/extensions";
|
|
10
10
|
import draco3d2 from "draco3dgltf";
|
|
11
|
-
import { existsSync as
|
|
11
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
12
12
|
import { MeshoptDecoder as MeshoptDecoder2, MeshoptEncoder as MeshoptEncoder3 } from "meshoptimizer";
|
|
13
13
|
import { dedup as dedup2, metalRough as metalRough2, prune as prune2, resample } from "@gltf-transform/functions";
|
|
14
14
|
|
|
@@ -1194,7 +1194,7 @@ function registerExtensions(node) {
|
|
|
1194
1194
|
import { createTransform } from "@gltf-transform/functions";
|
|
1195
1195
|
|
|
1196
1196
|
// src/utils/version.gen.ts
|
|
1197
|
-
var version = "3.0.0-beta.
|
|
1197
|
+
var version = "3.0.0-beta.3";
|
|
1198
1198
|
|
|
1199
1199
|
// src/utils/version.ts
|
|
1200
1200
|
function getVersion(_marklocal = !0) {
|
|
@@ -3607,12 +3607,35 @@ function computeOpusOggUri(sourceUri) {
|
|
|
3607
3607
|
return (dir === "." || dir === "" ? newName : `${dir}/${newName}`).replaceAll("\\", "/");
|
|
3608
3608
|
}
|
|
3609
3609
|
|
|
3610
|
+
// src/utils/logfile.ts
|
|
3611
|
+
import { appendFileSync, existsSync as existsSync11, mkdirSync as mkdirSync8, renameSync, statSync as statSync5 } from "fs";
|
|
3612
|
+
import { join as join7 } from "path";
|
|
3613
|
+
import { homedir } from "os";
|
|
3614
|
+
var LOG_DIR = join7(homedir(), ".needle", "optimization-pipeline", "logs"), LOG_FILE = join7(LOG_DIR, "pipeline.log"), PREV_LOG_FILE = join7(LOG_DIR, "pipeline.prev.log"), MAX_LOG_BYTES = 1024 * 1024;
|
|
3615
|
+
function redactSecrets(message) {
|
|
3616
|
+
return message.replace(/([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1").replace(/\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, "[REDACTED_JWT]").replace(/\bnc_[A-Za-z0-9_-]{6,}\b/g, "[REDACTED_TOKEN]");
|
|
3617
|
+
}
|
|
3618
|
+
function logToFile(message) {
|
|
3619
|
+
try {
|
|
3620
|
+
mkdirSync8(LOG_DIR, { recursive: !0 });
|
|
3621
|
+
try {
|
|
3622
|
+
existsSync11(LOG_FILE) && statSync5(LOG_FILE).size > MAX_LOG_BYTES && renameSync(LOG_FILE, PREV_LOG_FILE);
|
|
3623
|
+
} catch {
|
|
3624
|
+
}
|
|
3625
|
+
appendFileSync(LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${redactSecrets(message)}
|
|
3626
|
+
`);
|
|
3627
|
+
} catch {
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3610
3631
|
// src/scripts/pack-gltf.ts
|
|
3611
3632
|
async function packGLTF(inputFile, outputFile, options) {
|
|
3612
3633
|
options.logger || (options.logger = new Logger(Verbosity2.INFO));
|
|
3613
3634
|
let { debug, stats, packedFiles, useCache, logger } = options;
|
|
3614
|
-
if (!
|
|
3615
|
-
|
|
3635
|
+
if (!existsSync12(inputFile)) {
|
|
3636
|
+
let message = `WARN: can not pack, file does not exist: "${inputFile}"`;
|
|
3637
|
+
return logger.error(message), logToFile(message), !1;
|
|
3638
|
+
}
|
|
3616
3639
|
let cacheKey;
|
|
3617
3640
|
if (useCache && isMeshLOD(inputFile)) {
|
|
3618
3641
|
let existing = readFileSync8(inputFile);
|
|
@@ -3628,7 +3651,7 @@ async function packGLTF(inputFile, outputFile, options) {
|
|
|
3628
3651
|
try {
|
|
3629
3652
|
let config = options.config;
|
|
3630
3653
|
outputFile ? outputFile = getOutputPath(inputFile, outputFile) : outputFile = inputFile;
|
|
3631
|
-
let prevSizeInMB =
|
|
3654
|
+
let prevSizeInMB = statSync6(inputFile).size / 1024 / 1024;
|
|
3632
3655
|
logger.debug(`\u2192 Compressing ${prevSizeInMB.toFixed(1)} MB: ${inputFile}`);
|
|
3633
3656
|
let startTime = Date.now(), io = new NodeIO2().registerExtensions(ALL_EXTENSIONS3).registerExtensions(ALL_EXTENSIONS).registerExtensions([
|
|
3634
3657
|
NEEDLE_compression_texture,
|
|
@@ -3690,32 +3713,32 @@ async function packGLTF(inputFile, outputFile, options) {
|
|
|
3690
3713
|
let uri = tex.getURI();
|
|
3691
3714
|
if (uri && !uri.startsWith("data:")) {
|
|
3692
3715
|
let absPath = path5.resolve(outDir, uri);
|
|
3693
|
-
|
|
3716
|
+
existsSync12(absPath) && options.externalFiles.push(absPath);
|
|
3694
3717
|
}
|
|
3695
3718
|
}
|
|
3696
3719
|
for (let buf of root.listBuffers()) {
|
|
3697
3720
|
let uri = buf.getURI();
|
|
3698
3721
|
if (uri && !uri.startsWith("data:")) {
|
|
3699
3722
|
let absPath = path5.resolve(outDir, uri);
|
|
3700
|
-
|
|
3723
|
+
existsSync12(absPath) && options.externalFiles.push(absPath);
|
|
3701
3724
|
}
|
|
3702
3725
|
}
|
|
3703
3726
|
for (let ref of listAudioRefs()) {
|
|
3704
3727
|
let uri = ref.newUri ?? ref.sourceUri, absPath = path5.resolve(outDir, uri);
|
|
3705
|
-
|
|
3728
|
+
existsSync12(absPath) && options.externalFiles.push(absPath);
|
|
3706
3729
|
}
|
|
3707
3730
|
}
|
|
3708
|
-
logger.debug(`\u2190 Writing to ${outputFile}`), await writeNodeIO(io, outputFile, document), useCache && cacheKey != null &&
|
|
3709
|
-
let duration = Date.now() - startTime, sizeInMB =
|
|
3731
|
+
logger.debug(`\u2190 Writing to ${outputFile}`), await writeNodeIO(io, outputFile, document), useCache && cacheKey != null && existsSync12(outputFile) && addToCache(cacheKey, readFileSync8(outputFile));
|
|
3732
|
+
let duration = Date.now() - startTime, sizeInMB = statSync6(outputFile).size / 1024 / 1024, filename = path5.basename(outputFile), gpuMemoryInMB = totalGPUTextureMemory / 1024 / 1024;
|
|
3710
3733
|
return logger.info(`\u2190 Compressed '${filename}' ${prevSizeInMB.toFixed(1)} MB \u2192 ${sizeInMB.toFixed(1)} MB (GPU: ${gpuMemoryInMB.toFixed(1)} MB textures) in ${(duration / 1e3).toFixed(1)} sec`), stats && (stats.totalFilesProcessed++, stats.totalFileSizeInMB += sizeInMB, stats.totalFileSizeInMBBefore += prevSizeInMB, stats.totalGPUMemoryInMB += gpuMemoryInMB), !0;
|
|
3711
3734
|
} catch (err) {
|
|
3712
3735
|
if (err instanceof TestAssertionError) throw err;
|
|
3713
3736
|
let shortedStackTrace = err instanceof Error && err.stack ? err.stack.split(`
|
|
3714
3737
|
`).slice(0, 2).join(`
|
|
3715
|
-
`) : ""
|
|
3716
|
-
if (logger.error(`Packing of "${path5.basename(inputFile)}" using version ${getVersion()} failed with error:
|
|
3738
|
+
`) : "", message = `Packing of "${path5.basename(inputFile)}" using version ${getVersion()} failed with error:
|
|
3717
3739
|
${err instanceof Error ? err.message : String(err)}
|
|
3718
|
-
${shortedStackTrace}
|
|
3740
|
+
${shortedStackTrace}`;
|
|
3741
|
+
if (logger.error(message), logToFile(message), debug) throw err;
|
|
3719
3742
|
return !1;
|
|
3720
3743
|
}
|
|
3721
3744
|
}
|
|
@@ -3737,7 +3760,7 @@ async function calculateStats(inputfile, options) {
|
|
|
3737
3760
|
|
|
3738
3761
|
// src/config/index.ts
|
|
3739
3762
|
import { Logger as Logger3, Verbosity as Verbosity4 } from "@gltf-transform/core";
|
|
3740
|
-
import { existsSync as
|
|
3763
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
|
|
3741
3764
|
import path6, { dirname as dirname7, resolve as resolve2 } from "path";
|
|
3742
3765
|
var UsecaseOptions = ["product", "world"], defaultConfig = {
|
|
3743
3766
|
usecase: "default"
|
|
@@ -3752,9 +3775,9 @@ function createConfig(usecase) {
|
|
|
3752
3775
|
function getConfig(configPath, fallbackSearchDirectory, opts) {
|
|
3753
3776
|
let logger = opts?.logger;
|
|
3754
3777
|
if (logger || (logger = new Logger3(Verbosity4.WARN)), configPath != null) {
|
|
3755
|
-
if (configPath = resolve2(process.cwd(), configPath), !
|
|
3778
|
+
if (configPath = resolve2(process.cwd(), configPath), !existsSync13(configPath))
|
|
3756
3779
|
throw new Error(`No config found at "${configPath}"`);
|
|
3757
|
-
if (
|
|
3780
|
+
if (statSync7(configPath).isDirectory()) {
|
|
3758
3781
|
let res2 = searchConfig(configPath, logger);
|
|
3759
3782
|
if (res2)
|
|
3760
3783
|
return res2;
|
|
@@ -3775,9 +3798,9 @@ function getConfig(configPath, fallbackSearchDirectory, opts) {
|
|
|
3775
3798
|
}
|
|
3776
3799
|
var _cachedConfig;
|
|
3777
3800
|
function searchConfig(directory, logger) {
|
|
3778
|
-
for (
|
|
3801
|
+
for (statSync7(directory).isFile() && (directory = dirname7(directory)), directory = directory.replaceAll("\\", "/"); directory.length > 0; ) {
|
|
3779
3802
|
let configPath = path6.join(directory, "needle.config.json");
|
|
3780
|
-
if (
|
|
3803
|
+
if (existsSync13(configPath)) {
|
|
3781
3804
|
let content = readFileSync9(configPath, "utf8"), config = resolveConfig(configPath, content, logger);
|
|
3782
3805
|
if (config)
|
|
3783
3806
|
return config;
|
|
@@ -3809,7 +3832,7 @@ function resolveConfig(filename, content, logger) {
|
|
|
3809
3832
|
}
|
|
3810
3833
|
|
|
3811
3834
|
// src/cli/index.ts
|
|
3812
|
-
import { existsSync as
|
|
3835
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11, readdirSync as readdirSync3, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
3813
3836
|
import path7, { basename as basename4, dirname as dirname8, resolve as resolve3 } from "path";
|
|
3814
3837
|
|
|
3815
3838
|
// src/constants.ts
|
|
@@ -3875,6 +3898,13 @@ function trackError(error, context) {
|
|
|
3875
3898
|
return;
|
|
3876
3899
|
}
|
|
3877
3900
|
recentErrors.set(key, { time: now, count: (cached?.count || 0) + 1 });
|
|
3901
|
+
let ctx = [
|
|
3902
|
+
context?.mode && `mode=${context.mode}`,
|
|
3903
|
+
context?.usecase && `usecase=${context.usecase}`,
|
|
3904
|
+
context?.file && `file=${context.file}`
|
|
3905
|
+
].filter(Boolean).join(", ");
|
|
3906
|
+
logToFile(`[error] ${errorName}: ${message}${ctx ? ` (${ctx})` : ""}${stack ? `
|
|
3907
|
+
${stack}` : ""}`);
|
|
3878
3908
|
let properties = {
|
|
3879
3909
|
version: getVersion(),
|
|
3880
3910
|
error_name: errorName + (cached && cached.count > 1 ? ` x${cached.count}` : ""),
|
|
@@ -3926,9 +3956,9 @@ var PROD_KEYS = [
|
|
|
3926
3956
|
var trusted = [...PROD_KEYS], TRUSTED_KEYS = trusted;
|
|
3927
3957
|
|
|
3928
3958
|
// src/auth/verify.ts
|
|
3929
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
3930
|
-
import { join as
|
|
3931
|
-
import { homedir } from "os";
|
|
3959
|
+
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
|
|
3960
|
+
import { join as join9 } from "path";
|
|
3961
|
+
import { homedir as homedir2 } from "os";
|
|
3932
3962
|
var AuthError = class extends Error {
|
|
3933
3963
|
constructor(message) {
|
|
3934
3964
|
super(message), this.name = "AuthError";
|
|
@@ -3936,29 +3966,32 @@ var AuthError = class extends Error {
|
|
|
3936
3966
|
}, LOCAL_FETCH_TIMEOUT_MS = 2e3;
|
|
3937
3967
|
function getLocalServerToken() {
|
|
3938
3968
|
try {
|
|
3939
|
-
let file =
|
|
3940
|
-
if (!
|
|
3969
|
+
let file = join9(homedir2(), ".needle", "local-server-token");
|
|
3970
|
+
if (!existsSync14(file)) return null;
|
|
3941
3971
|
let data = JSON.parse(readFileSync10(file, "utf-8"));
|
|
3942
3972
|
return typeof data.token == "string" ? data.token : null;
|
|
3943
|
-
} catch {
|
|
3944
|
-
return null;
|
|
3973
|
+
} catch (err) {
|
|
3974
|
+
return logToFile(`[auth] failed reading local-server-token (${err instanceof Error ? err.name : "unknown error"})`), null;
|
|
3945
3975
|
}
|
|
3946
3976
|
}
|
|
3947
3977
|
async function tryFetchLocalToken(org) {
|
|
3978
|
+
let baseUrl = process.env.NEEDLE_LICENSE_SERVER_URL || "http://localhost:8424/api/license", safeServer = "local license server";
|
|
3948
3979
|
try {
|
|
3949
|
-
let
|
|
3950
|
-
org && url.searchParams.set("org", org)
|
|
3980
|
+
let url = new URL(baseUrl);
|
|
3981
|
+
org && url.searchParams.set("org", org), safeServer = `${url.origin}${url.pathname}`;
|
|
3951
3982
|
let headers = {}, serverToken = getLocalServerToken();
|
|
3952
3983
|
serverToken && (headers.Authorization = `Bearer ${serverToken}`);
|
|
3953
3984
|
let res = await fetch(url, {
|
|
3954
3985
|
signal: AbortSignal.timeout(LOCAL_FETCH_TIMEOUT_MS),
|
|
3955
3986
|
headers
|
|
3956
3987
|
});
|
|
3957
|
-
if (!res.ok)
|
|
3988
|
+
if (!res.ok)
|
|
3989
|
+
return logToFile(`[auth] license server (${safeServer}) returned HTTP ${res.status} ${res.statusText}${org ? ` \u2014 verify the --org value "${org}"` : ""}`), null;
|
|
3958
3990
|
let jwt = (await res.json()).needle_license_jwt;
|
|
3959
|
-
return typeof jwt == "string" && jwt.length > 0 ? jwt : null;
|
|
3960
|
-
} catch {
|
|
3961
|
-
|
|
3991
|
+
return typeof jwt == "string" && jwt.length > 0 ? jwt : (logToFile(`[auth] license server (${safeServer}) responded without a license token${org ? ` for org "${org}"` : ""}`), null);
|
|
3992
|
+
} catch (err) {
|
|
3993
|
+
let msg = err instanceof Error ? err.message : String(err);
|
|
3994
|
+
return logToFile(`[auth] could not reach license server (${safeServer})${org ? ` for org "${org}"` : ""}: ${msg}`), null;
|
|
3962
3995
|
}
|
|
3963
3996
|
}
|
|
3964
3997
|
async function verifyWithKey(token, key) {
|
|
@@ -4023,7 +4056,7 @@ async function gateAuth(authToken, org, logger) {
|
|
|
4023
4056
|
await requireAuth(authToken, typeof org == "string" ? org : void 0);
|
|
4024
4057
|
} catch (err) {
|
|
4025
4058
|
let message = err instanceof AuthError || err instanceof Error ? err.message : String(err);
|
|
4026
|
-
logger.error(`[Needle Build Pipeline] Auth failed: ${message}`), process.exit(ERROR_CODES.AUTH_FAILED);
|
|
4059
|
+
logger.error(`[Needle Build Pipeline] v${getVersion()} Auth failed: ${message}`), logToFile(`[Needle Build Pipeline] v${getVersion()} Auth failed: ${message}`), process.exit(ERROR_CODES.AUTH_FAILED);
|
|
4027
4060
|
}
|
|
4028
4061
|
}
|
|
4029
4062
|
program.cast(!1);
|
|
@@ -4077,7 +4110,7 @@ When caching is enabled then previously processed files will be detected and loa
|
|
|
4077
4110
|
}
|
|
4078
4111
|
let set = /* @__PURE__ */ new Set(), useCache = options.cache !== !1, debug = options.debug === !0, verbose = options.verbose === !0;
|
|
4079
4112
|
await foreachGLTF(inputPaths, async (file) => {
|
|
4080
|
-
if (
|
|
4113
|
+
if (existsSync15(file)) {
|
|
4081
4114
|
if (file != input && options.progressive === !0 && isLOD(file)) {
|
|
4082
4115
|
logger.debug(`\u2192 Ignore existing LOD file at ${file}`);
|
|
4083
4116
|
return;
|
|
@@ -4093,12 +4126,12 @@ When caching is enabled then previously processed files will be detected and loa
|
|
|
4093
4126
|
config
|
|
4094
4127
|
}, file) : void 0;
|
|
4095
4128
|
if (jobKey) {
|
|
4096
|
-
let outputDir = output ?
|
|
4129
|
+
let outputDir = output ? existsSync15(output) && isDirectory(output) ? output : dirname8(output) : dirname8(file), restored = tryRestoreJobResult(jobKey, outputDir, logger);
|
|
4097
4130
|
if (restored) {
|
|
4098
|
-
let inputFileSize =
|
|
4131
|
+
let inputFileSize = statSync8(file).size / 1024 / 1024;
|
|
4099
4132
|
for (let f of restored)
|
|
4100
4133
|
if (set.add(f), stats) {
|
|
4101
|
-
let sizeInMB =
|
|
4134
|
+
let sizeInMB = statSync8(f).size / 1024 / 1024;
|
|
4102
4135
|
stats.totalFilesProcessed++, stats.totalFileSizeInMB += sizeInMB, stats.totalFileSizeInMBBefore += inputFileSize;
|
|
4103
4136
|
}
|
|
4104
4137
|
logger.info(`\u2192 [CACHE] Restored ${restored.length} file(s) for job ${jobKey}`);
|
|
@@ -4169,7 +4202,7 @@ raw .exr / .hdr inputs.
|
|
|
4169
4202
|
`).argument("<input>", "Path to a .exr/.hdr file OR a directory containing .exr/.hdr files (recursive)", { validator: program.STRING }).argument("<output>", "Output file (when input is a single .exr/.hdr) or output directory. Defaults to writing next to the input file.", { default: "", validator: program.STRING }).option("--cache", 'Use "--cache False" to disable caching.', { validator: program.BOOLEAN, default: !0 }).option("--debug", 'Enable verbose output with "--debug True"', { validator: program.BOOLEAN, default: !1 }).option("--verbose", 'Enable verbose output with "--verbose True"', { validator: program.BOOLEAN, default: !1 }).option("--auth-token <jwt>", "Required. A JWT minted by needle-cloud (alternatively set NEEDLE_CLOUD_TOKEN). Note: an `nc_*` needle-cloud access token is NOT a JWT and will be rejected.", { validator: program.STRING }).option("--org <org>", "Organization ID for local license-server fallback (or set NEEDLE_CLOUD_ORG).", { validator: program.STRING }).hide().action(async ({ logger, args, options }) => {
|
|
4170
4203
|
warnUnknownOptions(options, logger), await gateAuth(options.authToken, options.org, logger), logger.level = options.debug || options.verbose ? "debug" : "info", await ensureHashReady();
|
|
4171
4204
|
let input = args.input.toString();
|
|
4172
|
-
|
|
4205
|
+
existsSync15(input) || (logger.error(`Input does not exist: ${input}`), process.exit(ERROR_CODES.INVALID_ARGS));
|
|
4173
4206
|
let inputFiles = [], inputIsDirectory = isDirectory(input);
|
|
4174
4207
|
if (inputIsDirectory ? collectPmremInputFiles(input, inputFiles) : isPmremInputFile(input) ? inputFiles.push(input) : (logger.error(`Input is not an .exr/.hdr file or directory: ${input}`), process.exit(ERROR_CODES.INVALID_ARGS)), inputFiles.length === 0) {
|
|
4175
4208
|
logger.warn(`No .exr/.hdr files found in ${input}`);
|
|
@@ -4208,7 +4241,7 @@ function collectPmremInputFiles(dir, out) {
|
|
|
4208
4241
|
for (let entry of readdirSync3(dir)) {
|
|
4209
4242
|
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
4210
4243
|
let full = path7.join(dir, entry);
|
|
4211
|
-
|
|
4244
|
+
statSync8(full).isDirectory() ? collectPmremInputFiles(full, out) : isPmremInputFile(entry) && out.push(full);
|
|
4212
4245
|
}
|
|
4213
4246
|
}
|
|
4214
4247
|
function resolvePmremOutputPath(srcPath, input, inputIsDirectory, outputArg) {
|
|
@@ -4219,7 +4252,7 @@ function resolvePmremOutputPath(srcPath, input, inputIsDirectory, outputArg) {
|
|
|
4219
4252
|
let rel = path7.relative(input, srcPath), relDir = dirname8(rel), outDir = relDir === "." ? outputArg : path7.join(outputArg, relDir);
|
|
4220
4253
|
return ensureIsDirectory(outDir), path7.join(outDir, outName);
|
|
4221
4254
|
}
|
|
4222
|
-
return
|
|
4255
|
+
return existsSync15(outputArg) && isDirectory(outputArg) ? path7.join(outputArg, outName) : outputArg.toLowerCase().endsWith(".ktx2") ? (ensureIsDirectory(dirname8(outputArg)), outputArg) : (ensureIsDirectory(outputArg), path7.join(outputArg, outName));
|
|
4223
4256
|
}
|
|
4224
4257
|
program.run().catch((err) => {
|
|
4225
4258
|
if (!(err.message.includes("Unknown command") && !err.message.includes("Did you mean")))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.3",
|
|
4
4
|
"description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|