@needle-tools/gltf-build-pipeline 3.0.0-next.03f5d79 → 3.0.0-next.3fbb1fc
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 +25 -0
- package/README.md +1 -1
- package/dist/cli/index.js +63 -33
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ 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-alpha] - 2026-05-26
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
- CLI now requires JWT authentication via `--auth-token`
|
|
11
|
+
- The package ships as a CLI-only right now
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Job cache**: file-level cache that skips an entire transform when the same input + config was already processed
|
|
15
|
+
- **Audio compression**: external audio references are re-encoded to Opus/OGG; supported input formats are WAV, FLAC, and AIFF (lossy formats pass through unchanged)
|
|
16
|
+
- **PMREM standalone CLI command**: `needle-gltf pmrem <input>` converts EXR and Radiance HDR files to PMREM KTX2
|
|
17
|
+
- **Multiple file inputs**: `transform` now accepts comma-separated paths (e.g. `needle-gltf transform a.glb,b.glb out/`)
|
|
18
|
+
- Auth surfaces clean "token expired" message when the JWT `exp` claim is past
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Cache restore now also restores external files (KTX2, PMREM) alongside the main GLB
|
|
22
|
+
- PMREM NaN generation that caused artifacts in certain EXR files
|
|
23
|
+
- xxHash WASM init race condition, now resolved with `ensureHashReady` gate
|
|
24
|
+
- LOD file detection no longer matches against directory names
|
|
25
|
+
- toktx: probes `ktx` binary to match what the compression step actually runs
|
|
26
|
+
- `getConfig` now returns a fresh copy instead of a shared mutable reference
|
|
27
|
+
- `stats` tolerates undefined errors/warnings in `addFrom`
|
|
28
|
+
- Material-only GLBs are no longer incorrectly pruned
|
|
29
|
+
- `createOpaqueExtension` return type fixed, downstream casts removed
|
|
30
|
+
- `stats` command exits with code 0
|
|
31
|
+
|
|
7
32
|
## [2.15.0] - 2026-03-20
|
|
8
33
|
- add: EXR environment map compression (PMREM / FASTHDR) for Needle Engine, supporting both Unity and Blender exports
|
|
9
34
|
- fix: cache hashing stability (circular reference handling)
|
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ The `gltf` value may also be a string shorthand for `usecase` — `"gltf": "prod
|
|
|
61
61
|
| `usecase` | `"default" \| "product" \| "world"` | `"default"` | High-level optimisation profile. |
|
|
62
62
|
| `textures.lods` | `boolean` | `true` | Generate progressive texture LODs. |
|
|
63
63
|
| `meshes.lods` | `boolean` | `true` | Generate progressive mesh LODs. |
|
|
64
|
-
| `audio.enabled` | `boolean` | `true` | Compress externally referenced
|
|
64
|
+
| `audio.enabled` | `boolean` | `true` | Compress externally referenced lossless audio files (`.wav`, `.flac`, `.aiff`/`.aif`) to Opus in an `.ogg` container, rewriting the URIs in the glTF. Lossy formats (`.mp3`, `.ogg`, `.m4a`, `.aac`) pass through unchanged to avoid compounding compression artifacts. Requires `ffmpeg` with `libopus` on `PATH`; when missing the build logs a warning and leaves the audio files untouched. |
|
|
65
65
|
| `audio.bitrate` | `number` (kbps) | `128` | Opus encoding bitrate. 96 is near-transparent for most music; 128 matches YouTube's music tier and is the safe "won't sound bad" default. Drop to 48–64 for known mono/UI-only assets. |
|
|
66
66
|
| `exr.enabled` | `boolean` | `true` | Convert EXR textures to KTX2 HDR via PMREM. When `false`, EXR textures (bundled and externally referenced) are left as-is so a downstream step can handle them. |
|
|
67
67
|
|
package/dist/cli/index.js
CHANGED
|
@@ -138,7 +138,7 @@ function listAudioRefs() {
|
|
|
138
138
|
function clearAudioRegistry() {
|
|
139
139
|
_refs = [];
|
|
140
140
|
}
|
|
141
|
-
var AUDIO_EXTENSIONS = [".wav"], _audioRegex = new RegExp(`(${AUDIO_EXTENSIONS.map((e) => "\\" + e).join("|")})$`, "i");
|
|
141
|
+
var AUDIO_EXTENSIONS = [".wav", ".flac", ".aiff", ".aif"], _audioRegex = new RegExp(`(${AUDIO_EXTENSIONS.map((e) => "\\" + e).join("|")})$`, "i");
|
|
142
142
|
function isAudioUri(value) {
|
|
143
143
|
return _audioRegex.test(value);
|
|
144
144
|
}
|
|
@@ -1190,7 +1190,7 @@ function registerExtensions(node) {
|
|
|
1190
1190
|
import { createTransform } from "@gltf-transform/functions";
|
|
1191
1191
|
|
|
1192
1192
|
// src/utils/version.gen.ts
|
|
1193
|
-
var version = "3.0.0-next.
|
|
1193
|
+
var version = "3.0.0-next.3fbb1fc";
|
|
1194
1194
|
|
|
1195
1195
|
// src/utils/version.ts
|
|
1196
1196
|
function getVersion(_marklocal = !0) {
|
|
@@ -1406,6 +1406,11 @@ function getFileName(filepath) {
|
|
|
1406
1406
|
let parts = filepath.replaceAll("\\", "/").split("/");
|
|
1407
1407
|
return parts[parts.length - 1];
|
|
1408
1408
|
}
|
|
1409
|
+
function parseInputPaths(raw) {
|
|
1410
|
+
if (!raw.includes(",")) return [raw];
|
|
1411
|
+
let segments = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1412
|
+
return segments.length < 2 ? [raw] : !segments.some((s) => existsSync3(s)) && existsSync3(raw) ? [raw] : segments;
|
|
1413
|
+
}
|
|
1409
1414
|
async function foreachGLTF(args, callback) {
|
|
1410
1415
|
let initDir = process.env.INIT_CWD, context = {
|
|
1411
1416
|
baseDirectory: initDir,
|
|
@@ -3896,7 +3901,7 @@ function trackPipelineEnd(info) {
|
|
|
3896
3901
|
}
|
|
3897
3902
|
|
|
3898
3903
|
// src/auth/verify.ts
|
|
3899
|
-
import { jwtVerify, importJWK } from "jose";
|
|
3904
|
+
import { jwtVerify, importJWK, decodeProtectedHeader } from "jose";
|
|
3900
3905
|
|
|
3901
3906
|
// src/auth/keys.ts
|
|
3902
3907
|
var PROD_KEYS = [
|
|
@@ -3921,28 +3926,51 @@ var AuthError = class extends Error {
|
|
|
3921
3926
|
constructor(message) {
|
|
3922
3927
|
super(message), this.name = "AuthError";
|
|
3923
3928
|
}
|
|
3924
|
-
};
|
|
3929
|
+
}, LOCAL_LICENSE_URL = "http://localhost:8424/api/license", LOCAL_FETCH_TIMEOUT_MS = 2e3;
|
|
3930
|
+
async function tryFetchLocalToken(org) {
|
|
3931
|
+
try {
|
|
3932
|
+
let url = new URL(LOCAL_LICENSE_URL);
|
|
3933
|
+
org && url.searchParams.set("org", org);
|
|
3934
|
+
let res = await fetch(url, {
|
|
3935
|
+
signal: AbortSignal.timeout(LOCAL_FETCH_TIMEOUT_MS)
|
|
3936
|
+
});
|
|
3937
|
+
if (!res.ok) return null;
|
|
3938
|
+
let jwt = (await res.json()).needle_license_jwt;
|
|
3939
|
+
return typeof jwt == "string" && jwt.length > 0 ? jwt : null;
|
|
3940
|
+
} catch {
|
|
3941
|
+
return null;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
async function verifyWithKey(token, key, opts) {
|
|
3945
|
+
if (!key.alg) throw new AuthError(`trusted key '${key.kid ?? "?"}' is missing 'alg'`);
|
|
3946
|
+
let keyObj = await importJWK(key, key.alg);
|
|
3947
|
+
await jwtVerify(token, keyObj, opts?.requireExp ? { requiredClaims: ["exp"] } : void 0);
|
|
3948
|
+
}
|
|
3925
3949
|
async function verifyAuthToken(token) {
|
|
3926
3950
|
if (!token)
|
|
3927
3951
|
throw new AuthError("no token provided");
|
|
3928
3952
|
if (token.startsWith("nc_"))
|
|
3929
3953
|
throw new AuthError("NEEDLE_CLOUD_TOKEN is a needle-cloud access token (nc_*), not a JWT.");
|
|
3930
3954
|
try {
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
throw new AuthError("token header missing 'kid'");
|
|
3955
|
+
let header = decodeProtectedHeader(token);
|
|
3956
|
+
if (header.kid) {
|
|
3934
3957
|
let match = TRUSTED_KEYS.find((k) => k.kid === header.kid);
|
|
3935
3958
|
if (!match)
|
|
3936
3959
|
throw new AuthError(`token signed by unknown key '${header.kid}'`);
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3960
|
+
await verifyWithKey(token, match, { requireExp: !0 });
|
|
3961
|
+
} else {
|
|
3962
|
+
let lastError = null;
|
|
3963
|
+
for (let key of TRUSTED_KEYS)
|
|
3964
|
+
try {
|
|
3965
|
+
await verifyWithKey(token, key);
|
|
3966
|
+
return;
|
|
3967
|
+
} catch (err) {
|
|
3968
|
+
lastError = err;
|
|
3969
|
+
}
|
|
3970
|
+
if (lastError instanceof AuthError) throw lastError;
|
|
3971
|
+
let message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
3972
|
+
throw new AuthError(message || "no trusted key could verify this token");
|
|
3973
|
+
}
|
|
3946
3974
|
} catch (err) {
|
|
3947
3975
|
if (err instanceof AuthError) throw err;
|
|
3948
3976
|
if (err && typeof err == "object" && "code" in err) {
|
|
@@ -3956,17 +3984,17 @@ async function verifyAuthToken(token) {
|
|
|
3956
3984
|
throw new AuthError(message);
|
|
3957
3985
|
}
|
|
3958
3986
|
}
|
|
3959
|
-
async function requireAuth(cliToken) {
|
|
3987
|
+
async function requireAuth(cliToken, org) {
|
|
3960
3988
|
let token = typeof cliToken == "string" && cliToken.length > 0 ? cliToken : process.env.NEEDLE_CLOUD_TOKEN;
|
|
3961
|
-
if (!token)
|
|
3962
|
-
throw new AuthError("pass --auth-token or set NEEDLE_CLOUD_TOKEN");
|
|
3989
|
+
if ((!token || typeof token == "string" && token.length === 0) && (token = await tryFetchLocalToken(org ?? process.env.NEEDLE_CLOUD_ORG)), !token)
|
|
3990
|
+
throw new AuthError("pass --auth-token or set NEEDLE_CLOUD_TOKEN (or log in to the needle-cloud CLI)");
|
|
3963
3991
|
await verifyAuthToken(token);
|
|
3964
3992
|
}
|
|
3965
3993
|
|
|
3966
3994
|
// src/cli/index.ts
|
|
3967
|
-
async function gateAuth(authToken, logger) {
|
|
3995
|
+
async function gateAuth(authToken, org, logger) {
|
|
3968
3996
|
try {
|
|
3969
|
-
await requireAuth(authToken);
|
|
3997
|
+
await requireAuth(authToken, typeof org == "string" ? org : void 0);
|
|
3970
3998
|
} catch (err) {
|
|
3971
3999
|
let message = err instanceof AuthError || err instanceof Error ? err.message : String(err);
|
|
3972
4000
|
logger.error(`[Needle Build Pipeline] Auth failed: ${message}`), process.exit(ERROR_CODES.AUTH_FAILED);
|
|
@@ -3980,8 +4008,8 @@ Caches are limited to ${cacheSizeLimit} MB disc space by default.
|
|
|
3980
4008
|
`).action(async ({ logger }) => {
|
|
3981
4009
|
logger.info("Clearing caches"), clearCache(logger);
|
|
3982
4010
|
}).command("stats", "Calculate glTF stats").help(`
|
|
3983
|
-
`).argument("<input>", "Path to a glTF, GLB or VRM file OR a directory").argument("<output>", "Output path for the stats", { default: null, validator: program.STRING }).option("--verbose", "Enable verbose output", { 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 }).action(async ({ logger, args, options }) => {
|
|
3984
|
-
await gateAuth(options.authToken, logger), logger.level = options.verbose ? "debug" : "info", await ensureHashReady(), logger.info('Calculating stats for "' + args.input + '"');
|
|
4011
|
+
`).argument("<input>", "Path to a glTF, GLB or VRM file OR a directory").argument("<output>", "Output path for the stats", { default: null, validator: program.STRING }).option("--verbose", "Enable verbose output", { 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 }).action(async ({ logger, args, options }) => {
|
|
4012
|
+
await gateAuth(options.authToken, options.org, logger), logger.level = options.verbose ? "debug" : "info", await ensureHashReady(), logger.info('Calculating stats for "' + args.input + '"');
|
|
3985
4013
|
let input = args.input.toString(), filestats = new Array();
|
|
3986
4014
|
await foreachGLTF(input, async (file) => {
|
|
3987
4015
|
let stats = await calculateStats(file, { verbose: !!options.verbose });
|
|
@@ -3992,13 +4020,15 @@ Caches are limited to ${cacheSizeLimit} MB disc space by default.
|
|
|
3992
4020
|
}).command("transform", "Transform (progressive loading) and compress glTF, GLB or VRM files").help(`
|
|
3993
4021
|
This will produce multiple versions of the input file, each with a different level of optimization.
|
|
3994
4022
|
Each version will be compressed and written to the output directory.
|
|
3995
|
-
`).argument("<input>", "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed", { validator: program.STRING }).argument("<output>", "The output file or directory. It can be an absolute path or a relative path to the input file. If not provided, the input file will be overwritten.", { default: null, validator: program.STRING }).option("--compress", "When enabled files will be compressed", { validator: program.BOOLEAN, default: !0 }).option("--progressive", "When enabled files will processed to be progressively loaded", { validator: program.BOOLEAN, default: !0 }).option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Possible options: [${UsecaseOptions.join(", ")}]`, { validator: program.STRING }).option("--config <config>", 'The path to a JSON file containing the compression settings. This can also be a needle.config.json file with a "compression" object or usecase string.', { validator: program.STRING }).option("--cache", `Use "--cache False" to disable caching.
|
|
3996
|
-
When caching is enabled then previously processed files will be detected and loaded from disc cache instead of being processed again if settings or the file contents have not changed.`, { validator: program.BOOLEAN, default: !0 }).option("--debug", 'Enable for more verbose outout with "--debug True"', { validator: program.BOOLEAN, default: !1 }).option("--verbose", 'Enable for more verbose outout 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 }).action(async ({ logger, args, options }) => {
|
|
3997
|
-
if (await gateAuth(options.authToken, logger), logger.level = options.debug || options.verbose ? "debug" : "info", options.compress === !1 && options.progressive === !1) {
|
|
4023
|
+
`).argument("<input>", "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed. Multiple files can be separated by commas.", { validator: program.STRING }).argument("<output>", "The output file or directory. It can be an absolute path or a relative path to the input file. If not provided, the input file will be overwritten.", { default: null, validator: program.STRING }).option("--compress", "When enabled files will be compressed", { validator: program.BOOLEAN, default: !0 }).option("--progressive", "When enabled files will processed to be progressively loaded", { validator: program.BOOLEAN, default: !0 }).option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Possible options: [${UsecaseOptions.join(", ")}]`, { validator: program.STRING }).option("--config <config>", 'The path to a JSON file containing the compression settings. This can also be a needle.config.json file with a "compression" object or usecase string.', { validator: program.STRING }).option("--cache", `Use "--cache False" to disable caching.
|
|
4024
|
+
When caching is enabled then previously processed files will be detected and loaded from disc cache instead of being processed again if settings or the file contents have not changed.`, { validator: program.BOOLEAN, default: !0 }).option("--debug", 'Enable for more verbose outout with "--debug True"', { validator: program.BOOLEAN, default: !1 }).option("--verbose", 'Enable for more verbose outout 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 }).action(async ({ logger, args, options }) => {
|
|
4025
|
+
if (await gateAuth(options.authToken, options.org, logger), logger.level = options.debug || options.verbose ? "debug" : "info", options.compress === !1 && options.progressive === !1) {
|
|
3998
4026
|
logger.error(`[Needle Build Pipeline] v${getVersion()} You need to enable at least one of the options: --compress or --progressive`);
|
|
3999
4027
|
return;
|
|
4000
4028
|
}
|
|
4001
|
-
await ensureHashReady()
|
|
4029
|
+
await ensureHashReady();
|
|
4030
|
+
let inputPaths = parseInputPaths(args.input.toString()), input = inputPaths[0], isMultiInput = inputPaths.length > 1;
|
|
4031
|
+
logger.info(`[Needle Build Pipeline] v${getVersion()} \u2014 Transform ${isMultiInput ? `${inputPaths.length} files` : `'${input}'`}`);
|
|
4002
4032
|
let pipelineMode = [options.progressive && "progressive", options.compress && "compress"].filter(Boolean).join("+");
|
|
4003
4033
|
trackPipelineStart({ mode: pipelineMode, usecase: options.usecase?.toString() });
|
|
4004
4034
|
let stats = {
|
|
@@ -4007,19 +4037,19 @@ When caching is enabled then previously processed files will be detected and loa
|
|
|
4007
4037
|
totalFilesProcessed: 0,
|
|
4008
4038
|
totalFileSizeInMB: 0,
|
|
4009
4039
|
totalFileSizeInMBBefore: 0
|
|
4010
|
-
},
|
|
4040
|
+
}, config = getConfig(options.config?.toString(), input, {
|
|
4011
4041
|
logger
|
|
4012
4042
|
});
|
|
4013
4043
|
options.usecase && (logger.info("Using usecase: " + options.usecase), config = { ...createConfig(options.usecase.toString()), ...config }, config.usecase != "default" && config.usecase != options.usecase.toString() ? logger.warn(`Conflicting usecase. Your specific config at "${options.config}" has a different usecase than the one provided by the CLI: "${options.usecase}". Using the usecase from the config file.`) : config.usecase = options.usecase.toString());
|
|
4014
|
-
let inputIsDirectory = isDirectory(input), output = args.output?.toString();
|
|
4015
|
-
!inputIsDirectory && !output && (output = input);
|
|
4044
|
+
let inputIsDirectory = !isMultiInput && isDirectory(input), output = args.output?.toString();
|
|
4045
|
+
isMultiInput && output && !isDirectory(output) && (logger.error("When <input> contains multiple files then <output> must be a directory"), trackError(new Error("Invalid args: multi-input but output is not a directory"), { mode: pipelineMode }), process.exit(ERROR_CODES.INVALID_ARGS)), !inputIsDirectory && !isMultiInput && !output && (output = input);
|
|
4016
4046
|
let outputIsDirectory = output && isDirectory(output);
|
|
4017
4047
|
if (inputIsDirectory && output && !outputIsDirectory && (logger.error("When <input> is a directory then <output> must also be a directory"), trackError(new Error("Invalid args: input is directory but output is not"), { mode: pipelineMode }), process.exit(ERROR_CODES.INVALID_ARGS)), output && path7.isAbsolute(output) === !1) {
|
|
4018
4048
|
let base = ensureIsDirectory(input);
|
|
4019
4049
|
output = resolve3(base, output);
|
|
4020
4050
|
}
|
|
4021
4051
|
let set = /* @__PURE__ */ new Set(), useCache = options.cache !== !1, debug = options.debug === !0, verbose = options.verbose === !0;
|
|
4022
|
-
await foreachGLTF(
|
|
4052
|
+
await foreachGLTF(inputPaths, async (file) => {
|
|
4023
4053
|
if (existsSync13(file)) {
|
|
4024
4054
|
if (file != input && options.progressive === !0 && isLOD(file)) {
|
|
4025
4055
|
logger.debug(`\u2192 Ignore existing LOD file at ${file}`);
|
|
@@ -4109,8 +4139,8 @@ When caching is enabled then previously processed files will be detected and loa
|
|
|
4109
4139
|
producing <name>.pmrem.ktx2. This is the same processing the Needle build pipeline
|
|
4110
4140
|
applies to EXR textures inside GLB/GLTF files, exposed as a standalone command for
|
|
4111
4141
|
raw .exr / .hdr inputs.
|
|
4112
|
-
`).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: null, 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 }).hide().action(async ({ logger, args, options }) => {
|
|
4113
|
-
await gateAuth(options.authToken, logger), logger.level = options.debug || options.verbose ? "debug" : "info", await ensureHashReady();
|
|
4142
|
+
`).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: null, 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 }) => {
|
|
4143
|
+
await gateAuth(options.authToken, options.org, logger), logger.level = options.debug || options.verbose ? "debug" : "info", await ensureHashReady();
|
|
4114
4144
|
let input = args.input.toString();
|
|
4115
4145
|
existsSync13(input) || (logger.error(`Input does not exist: ${input}`), process.exit(ERROR_CODES.INVALID_ARGS));
|
|
4116
4146
|
let inputFiles = [], inputIsDirectory = isDirectory(input);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.3fbb1fc",
|
|
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": {
|