@forgeax/engine-codec 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +261 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/basis-encode-fast-preset.unit.test.d.ts +2 -0
- package/dist/__tests__/basis-encode-fast-preset.unit.test.d.ts.map +1 -0
- package/dist/__tests__/basis-encode-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/basis-encode-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/basis-transcoder.unit.test.d.ts +2 -0
- package/dist/__tests__/basis-transcoder.unit.test.d.ts.map +1 -0
- package/dist/__tests__/block-format.unit.test.d.ts +2 -0
- package/dist/__tests__/block-format.unit.test.d.ts.map +1 -0
- package/dist/__tests__/byte-identical-falsification.test.d.ts +2 -0
- package/dist/__tests__/byte-identical-falsification.test.d.ts.map +1 -0
- package/dist/__tests__/codec-errors.test-d.d.ts +2 -0
- package/dist/__tests__/codec-errors.test-d.d.ts.map +1 -0
- package/dist/__tests__/codec-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/codec-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/compression-field.unit.test.d.ts +19 -0
- package/dist/__tests__/compression-field.unit.test.d.ts.map +1 -0
- package/dist/__tests__/encode-pixel-limit.unit.test.d.ts +2 -0
- package/dist/__tests__/encode-pixel-limit.unit.test.d.ts.map +1 -0
- package/dist/__tests__/encode-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/encode-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-basis-capability-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/ktx2-basis-capability-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-errors.test.d.ts +2 -0
- package/dist/__tests__/ktx2-errors.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-parse-scheme0.test.d.ts +2 -0
- package/dist/__tests__/ktx2-parse-scheme0.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-parse-scheme2.test.d.ts +2 -0
- package/dist/__tests__/ktx2-parse-scheme2.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-scheme1-and-transcode-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/ktx2-scheme1-and-transcode-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ktx2-to-texture.integration.test.d.ts +2 -0
- package/dist/__tests__/ktx2-to-texture.integration.test.d.ts.map +1 -0
- package/dist/__tests__/transcode-select-target.unit.test.d.ts +2 -0
- package/dist/__tests__/transcode-select-target.unit.test.d.ts.map +1 -0
- package/dist/__tests__/wasm-init-smoke.test.d.ts +2 -0
- package/dist/__tests__/wasm-init-smoke.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-deterministic.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-deterministic.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-encode-singleton.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-encode-singleton.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zstd-singleton.unit.test.d.ts +2 -0
- package/dist/__tests__/zstd-singleton.unit.test.d.ts.map +1 -0
- package/dist/basis-transcoder.d.ts +86 -0
- package/dist/basis-transcoder.d.ts.map +1 -0
- package/dist/block-format.d.ts +41 -0
- package/dist/block-format.d.ts.map +1 -0
- package/dist/encode/basis-encode.d.ts +68 -0
- package/dist/encode/basis-encode.d.ts.map +1 -0
- package/dist/encode/index.d.ts +10 -0
- package/dist/encode/index.d.ts.map +1 -0
- package/dist/encode/index.mjs +207 -0
- package/dist/encode/index.mjs.map +1 -0
- package/dist/encode-impl.d.ts +30 -0
- package/dist/encode-impl.d.ts.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +610 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ktx2.d.ts +111 -0
- package/dist/ktx2.d.ts.map +1 -0
- package/dist/transcode.d.ts +55 -0
- package/dist/transcode.d.ts.map +1 -0
- package/dist/wasm/basis-types.d.ts +179 -0
- package/dist/wasm/basis-types.d.ts.map +1 -0
- package/dist/zstd.d.ts +28 -0
- package/dist/zstd.d.ts.map +1 -0
- package/package.json +71 -0
- package/pkg/basis_transcoder.mjs +2 -0
- package/pkg/basis_transcoder.wasm +0 -0
- package/pkg/encode/basis_encoder.mjs +2 -0
- package/pkg/encode/basis_encoder.wasm +0 -0
- package/scripts/build-wasm.mjs +370 -0
- package/scripts/content-key.mjs +85 -0
- package/scripts/ensure-wasm.mjs +38 -0
- package/scripts/fetch-basis.mjs +150 -0
- package/scripts/fetch-wasm.mjs +97 -0
- package/src/__tests__/basis-encode-fast-preset.unit.test.ts +204 -0
- package/src/__tests__/basis-encode-lifecycle.unit.test.ts +249 -0
- package/src/__tests__/basis-transcoder.unit.test.ts +151 -0
- package/src/__tests__/block-format.unit.test.ts +215 -0
- package/src/__tests__/byte-identical-falsification.test.ts +164 -0
- package/src/__tests__/codec-errors.test-d.ts +67 -0
- package/src/__tests__/codec-errors.unit.test.ts +128 -0
- package/src/__tests__/compression-field.unit.test.ts +245 -0
- package/src/__tests__/encode-pixel-limit.unit.test.ts +117 -0
- package/src/__tests__/encode-roundtrip.unit.test.ts +239 -0
- package/src/__tests__/ktx2-basis-capability-matrix.unit.test.ts +241 -0
- package/src/__tests__/ktx2-errors.test.ts +239 -0
- package/src/__tests__/ktx2-parse-scheme0.test.ts +263 -0
- package/src/__tests__/ktx2-parse-scheme2.test.ts +220 -0
- package/src/__tests__/ktx2-scheme1-and-transcode-errors.unit.test.ts +178 -0
- package/src/__tests__/ktx2-to-texture.integration.test.ts +384 -0
- package/src/__tests__/transcode-select-target.unit.test.ts +167 -0
- package/src/__tests__/wasm-init-smoke.test.ts +102 -0
- package/src/__tests__/zstd-deterministic.unit.test.ts +49 -0
- package/src/__tests__/zstd-encode-singleton.unit.test.ts +63 -0
- package/src/__tests__/zstd-roundtrip.unit.test.ts +87 -0
- package/src/__tests__/zstd-singleton.unit.test.ts +105 -0
- package/src/basis-transcoder.ts +315 -0
- package/src/block-format.ts +128 -0
- package/src/encode/basis-encode.ts +264 -0
- package/src/encode/index.ts +19 -0
- package/src/encode-impl.ts +103 -0
- package/src/errors.ts +79 -0
- package/src/index.ts +38 -0
- package/src/ktx2.ts +469 -0
- package/src/transcode.ts +99 -0
- package/src/wasm/basis-types.ts +220 -0
- package/src/zstd.ts +85 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// build-wasm.mjs -- Compile the pinned basis_universal C++ source into two
|
|
3
|
+
// self-contained WASM modules via bare emcc (plan-strategy D-1, path B):
|
|
4
|
+
//
|
|
5
|
+
// 1. transcoder (slim) -> pkg/basis_transcoder.mjs + .wasm (runtime-safe)
|
|
6
|
+
// 2. encoder -> pkg/encode/basis_encoder.mjs + .wasm (build-time)
|
|
7
|
+
//
|
|
8
|
+
// Why bare emcc and not emcmake+cmake (D-1): CI has only the emsdk toolchain
|
|
9
|
+
// (emscripten-core/setup-emsdk@v16) and no cmake setup step; the fbx package
|
|
10
|
+
// already builds ufbx this way. The emcc flag set below is lifted verbatim from
|
|
11
|
+
// the upstream CMake build files and anchored to them in comments so a pin bump
|
|
12
|
+
// surfaces any flag drift at review time:
|
|
13
|
+
// transcoder: vendor/basis/webgl/transcoder/CMakeLists.txt
|
|
14
|
+
// encoder: vendor/basis/webgl/encoder/CMakeLists.txt
|
|
15
|
+
//
|
|
16
|
+
// Prerequisites:
|
|
17
|
+
// 1. emcc on PATH (emsdk 6.0.2; see .github/workflows/ci.yml build-artifacts)
|
|
18
|
+
// 2. node scripts/fetch-basis.mjs (vendors the pinned source)
|
|
19
|
+
//
|
|
20
|
+
// Usage: node packages/codec/scripts/build-wasm.mjs
|
|
21
|
+
|
|
22
|
+
import { execFileSync } from 'node:child_process';
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const CODEC_ROOT = join(__dirname, '..');
|
|
29
|
+
const VENDOR = join(CODEC_ROOT, 'vendor', 'basis');
|
|
30
|
+
const PKG = join(CODEC_ROOT, 'pkg');
|
|
31
|
+
const PKG_ENCODE = join(PKG, 'encode');
|
|
32
|
+
const REPRODUCIBLE_PATH = `-ffile-prefix-map=${CODEC_ROOT}=/forgeax/codec`;
|
|
33
|
+
|
|
34
|
+
if (!existsSync(join(VENDOR, 'transcoder', 'basisu_transcoder.cpp'))) {
|
|
35
|
+
console.error('basis source not found. Run `node scripts/fetch-basis.mjs` first.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Compile source lists (mirror upstream CMakeLists SRC_LIST) --------------
|
|
40
|
+
// Paths relative to VENDOR. C++ sources compiled by emcc directly; the C zstd
|
|
41
|
+
// source is pre-compiled to an object first (C++ std flag must not touch it).
|
|
42
|
+
|
|
43
|
+
// Transcoder SRC_LIST -- webgl/transcoder/CMakeLists.txt.
|
|
44
|
+
const TRANSCODER_CPP = [
|
|
45
|
+
'transcoder/basisu_transcoder.cpp',
|
|
46
|
+
'webgl/transcoder/basis_wrappers.cpp',
|
|
47
|
+
];
|
|
48
|
+
// zstddeclib.c (decompress-only) for the transcoder KTX2_ZSTANDARD path.
|
|
49
|
+
const TRANSCODER_C = 'zstd/zstddeclib.c';
|
|
50
|
+
|
|
51
|
+
// Encoder SRC_LIST -- webgl/encoder/CMakeLists.txt (non-threaded wasm32 target).
|
|
52
|
+
const ENCODER_CPP = [
|
|
53
|
+
'webgl/transcoder/basis_wrappers.cpp',
|
|
54
|
+
'transcoder/basisu_transcoder.cpp',
|
|
55
|
+
'encoder/basisu_backend.cpp',
|
|
56
|
+
'encoder/basisu_basis_file.cpp',
|
|
57
|
+
'encoder/basisu_comp.cpp',
|
|
58
|
+
'encoder/basisu_enc.cpp',
|
|
59
|
+
'encoder/basisu_etc.cpp',
|
|
60
|
+
'encoder/basisu_frontend.cpp',
|
|
61
|
+
'encoder/basisu_gpu_texture.cpp',
|
|
62
|
+
'encoder/basisu_pvrtc1_4.cpp',
|
|
63
|
+
'encoder/basisu_resampler.cpp',
|
|
64
|
+
'encoder/basisu_resample_filters.cpp',
|
|
65
|
+
'encoder/basisu_ssim.cpp',
|
|
66
|
+
'encoder/basisu_uastc_enc.cpp',
|
|
67
|
+
'encoder/basisu_bc7e_scalar.cpp',
|
|
68
|
+
'encoder/basisu_dds_export.cpp',
|
|
69
|
+
'encoder/basisu_bc7enc.cpp',
|
|
70
|
+
'encoder/basisu_kernels_sse.cpp',
|
|
71
|
+
'encoder/basisu_opencl.cpp',
|
|
72
|
+
'encoder/pvpngreader.cpp',
|
|
73
|
+
'encoder/jpgd.cpp',
|
|
74
|
+
'encoder/3rdparty/android_astc_decomp.cpp',
|
|
75
|
+
'encoder/basisu_uastc_hdr_4x4_enc.cpp',
|
|
76
|
+
'encoder/basisu_astc_hdr_6x6_enc.cpp',
|
|
77
|
+
'encoder/basisu_astc_hdr_common.cpp',
|
|
78
|
+
'encoder/basisu_astc_ldr_common.cpp',
|
|
79
|
+
'encoder/basisu_astc_ldr_encode.cpp',
|
|
80
|
+
'encoder/basisu_astc_ldr_fencode.cpp',
|
|
81
|
+
'encoder/basisu_tinyexr.cpp',
|
|
82
|
+
'encoder/basisu_xbc7_encode.cpp',
|
|
83
|
+
];
|
|
84
|
+
// Encoder needs the full zstd (compress + decompress), not the declib.
|
|
85
|
+
const ENCODER_C = 'zstd/zstd.c';
|
|
86
|
+
|
|
87
|
+
// --- Drift guard (D-1) -------------------------------------------------------
|
|
88
|
+
// Every .cpp we name must exist in the vendored tree; and every .cpp present in
|
|
89
|
+
// encoder/ (the upstream compile universe) must be either named above or
|
|
90
|
+
// explicitly excluded here. This turns a pin bump that adds/removes/renames an
|
|
91
|
+
// encoder source into a hard build error instead of a silent miscompile.
|
|
92
|
+
const ENCODER_EXCLUDED = new Set([
|
|
93
|
+
// SSE kernels: BASISU_SUPPORT_SSE=0 for WASM (webgl/encoder/CMakeLists.txt).
|
|
94
|
+
'encoder/basisu_bc15_spmd.cpp',
|
|
95
|
+
'encoder/basisu_bc15_spmd_sse.cpp',
|
|
96
|
+
// Standalone WASI C-API path (encoder/basisu_wasm_api.*): a different build
|
|
97
|
+
// target from the webgl embind wrapper (basis_wrappers.cpp). Not in the
|
|
98
|
+
// upstream webgl/encoder CMake SRC_LIST -- excluded from the embind build.
|
|
99
|
+
'encoder/basisu_wasm_api.cpp',
|
|
100
|
+
'encoder/basisu_wasm_transcoder_api.cpp',
|
|
101
|
+
// OpenCL kernel source is header-embedded; basisu_opencl.cpp is the only unit.
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
function assertSourcesExist(list) {
|
|
105
|
+
for (const rel of list) {
|
|
106
|
+
if (!existsSync(join(VENDOR, rel))) {
|
|
107
|
+
throw new Error(`source drift: expected vendored file missing: ${rel}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function assertNoUnclaimedEncoderCpp() {
|
|
113
|
+
const encDir = join(VENDOR, 'encoder');
|
|
114
|
+
const named = new Set([...ENCODER_CPP.filter((p) => p.startsWith('encoder/')), ...ENCODER_EXCLUDED]);
|
|
115
|
+
const found = [];
|
|
116
|
+
for (const name of readdirSync(encDir)) {
|
|
117
|
+
if (!name.endsWith('.cpp')) continue;
|
|
118
|
+
found.push(`encoder/${name}`);
|
|
119
|
+
}
|
|
120
|
+
const unclaimed = found.filter((p) => !named.has(p));
|
|
121
|
+
if (unclaimed.length > 0) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`source drift: encoder/*.cpp present in vendored tree but not in build list ` +
|
|
124
|
+
`nor ENCODER_EXCLUDED: ${unclaimed.join(', ')}. Update build-wasm.mjs after a pin bump.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- Encoder source pixel-limit patch (CI-red fix, human decision) -----------
|
|
130
|
+
// The vendored basis_wrappers.cpp caps the encoder's total source texels for the
|
|
131
|
+
// non-wasm64 (wasm32) build at BASISU_ENCODER_MAX_SOURCE_IMAGE_PIXELS: default
|
|
132
|
+
// (1024*1024*4) = 4 Mpx, higher-limit (1024*1024*12) = 12 Mpx. A 4096x4096 game
|
|
133
|
+
// texture is 16.78 Mpx (= 1024*1024*16), above both, so encode() returns 0 bytes
|
|
134
|
+
// -> ktx2-encode-failed. Human decision: 4096^2 is a reasonable engine ceiling,
|
|
135
|
+
// so raise both non-wasm64 limits to (1024*1024*16), matching the wasm64 branch.
|
|
136
|
+
//
|
|
137
|
+
// vendor/basis is gitignored + re-fetched every build (fetch-basis.mjs), so the
|
|
138
|
+
// override must be applied at build time, not committed to the source file. A
|
|
139
|
+
// -D flag can NOT do this: the source uses `#define` (an unconditional macro
|
|
140
|
+
// definition), and a command-line -D of the same macro collides with the
|
|
141
|
+
// in-source #define (redefinition). So we deterministically rewrite the two
|
|
142
|
+
// #else-branch literals in place. Idempotent: after the first patch the old
|
|
143
|
+
// (1024*1024*4)/(1024*1024*12) literals no longer exist, so a re-run is a no-op.
|
|
144
|
+
const ENCODER_MAX_PIXELS_PATCH = [
|
|
145
|
+
{
|
|
146
|
+
from: '#define BASISU_ENCODER_MAX_SOURCE_IMAGE_PIXELS (1024*1024*4)',
|
|
147
|
+
to: '#define BASISU_ENCODER_MAX_SOURCE_IMAGE_PIXELS (1024*1024*16)',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
from: '#define BASISU_ENCODER_MAX_SOURCE_IMAGE_PIXELS_HIGHER_LIMIT (1024*1024*12)',
|
|
151
|
+
to: '#define BASISU_ENCODER_MAX_SOURCE_IMAGE_PIXELS_HIGHER_LIMIT (1024*1024*16)',
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
function patchEncoderPixelLimit() {
|
|
156
|
+
const wrappers = join(VENDOR, 'webgl', 'transcoder', 'basis_wrappers.cpp');
|
|
157
|
+
let src = readFileSync(wrappers, 'utf8');
|
|
158
|
+
let changed = false;
|
|
159
|
+
for (const { from, to } of ENCODER_MAX_PIXELS_PATCH) {
|
|
160
|
+
if (src.includes(from)) {
|
|
161
|
+
src = src.replace(from, to);
|
|
162
|
+
changed = true;
|
|
163
|
+
} else if (!src.includes(to)) {
|
|
164
|
+
// Neither the pre-patch literal nor the target is present: the upstream
|
|
165
|
+
// source shape drifted (pin bump). Fail loud rather than silently skip.
|
|
166
|
+
throw new Error(
|
|
167
|
+
`encoder pixel-limit patch anchor missing in basis_wrappers.cpp: ${from}. ` +
|
|
168
|
+
'Re-check the non-wasm64 BASISU_ENCODER_MAX_SOURCE_IMAGE_PIXELS defines after a pin bump.',
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (changed) {
|
|
173
|
+
writeFileSync(wrappers, src);
|
|
174
|
+
console.log('[patch] raised non-wasm64 encoder pixel limits to 1024*1024*16 (4096^2)');
|
|
175
|
+
} else {
|
|
176
|
+
console.log('[patch] encoder pixel limits already at 1024*1024*16, skipping');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- emcc flag sets (anchored to upstream CMakeLists) ------------------------
|
|
181
|
+
const INCLUDE = ['-I', join(VENDOR, 'transcoder')];
|
|
182
|
+
|
|
183
|
+
// Transcoder compile defines -- webgl/transcoder/CMakeLists.txt
|
|
184
|
+
// target_compile_definitions. Slim: encoding off, HDR + BC7 + KTX2 + zstd on,
|
|
185
|
+
// ATC/PVRTC2/FXT1/EAC-RG11 off.
|
|
186
|
+
const TRANSCODER_DEFS = [
|
|
187
|
+
'-DNDEBUG=1',
|
|
188
|
+
'-DBASISD_SUPPORT_UASTC_HDR=1',
|
|
189
|
+
'-DBASISD_SUPPORT_UASTC=1',
|
|
190
|
+
'-DBASISD_SUPPORT_BC7=1',
|
|
191
|
+
'-DBASISD_SUPPORT_ATC=0',
|
|
192
|
+
'-DBASISD_SUPPORT_ASTC_HIGHER_OPAQUE_QUALITY=0',
|
|
193
|
+
'-DBASISD_SUPPORT_PVRTC2=0',
|
|
194
|
+
'-DBASISD_SUPPORT_FXT1=0',
|
|
195
|
+
'-DBASISD_SUPPORT_ETC2_EAC_RG11=0',
|
|
196
|
+
'-DBASISU_SUPPORT_ENCODING=0',
|
|
197
|
+
'-DBASISD_ENABLE_DEBUG_FLAGS=1',
|
|
198
|
+
'-DBASISD_SUPPORT_KTX2=1',
|
|
199
|
+
'-DBASISD_SUPPORT_KTX2_ZSTD=1',
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
// Encoder compile defines -- webgl/encoder/CMakeLists.txt COMMON_DEFS.
|
|
203
|
+
// SSE off (no WASM SSE), encoding + XUASTC on. UASTC_HDR is implied on by the
|
|
204
|
+
// encoder unconditionally; KTX2 macros default on in the encoder path.
|
|
205
|
+
const ENCODER_DEFS = [
|
|
206
|
+
'-DNDEBUG=1',
|
|
207
|
+
'-DBASISD_SUPPORT_UASTC=1',
|
|
208
|
+
'-DBASISD_SUPPORT_BC7=1',
|
|
209
|
+
'-DBASISD_SUPPORT_ATC=0',
|
|
210
|
+
'-DBASISD_SUPPORT_ASTC_HIGHER_OPAQUE_QUALITY=0',
|
|
211
|
+
'-DBASISD_SUPPORT_PVRTC2=0',
|
|
212
|
+
'-DBASISD_SUPPORT_FXT1=0',
|
|
213
|
+
'-DBASISD_SUPPORT_ETC2_EAC_RG11=0',
|
|
214
|
+
'-DBASISU_SUPPORT_ENCODING=1',
|
|
215
|
+
'-DBASISU_SUPPORT_SSE=0',
|
|
216
|
+
'-DBASISD_SUPPORT_XUASTC=1',
|
|
217
|
+
'-DBASISD_SUPPORT_KTX2_ZSTD=1',
|
|
218
|
+
'-DBASISU_SUPPORT_ASTCENC=0',
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
// Shared link flags. --bind (embind) + MODULARIZE factory named BASIS (upstream
|
|
222
|
+
// EXPORT_NAME) + ES6 output + web,node dual environment (fbx precedent).
|
|
223
|
+
// EXPORT_ES6 replaces the CMake .js suffix so node/vite consume a .mjs module.
|
|
224
|
+
const TRANSCODER_LINK = [
|
|
225
|
+
'--bind',
|
|
226
|
+
'-O3',
|
|
227
|
+
'-s', 'ALLOW_MEMORY_GROWTH=1',
|
|
228
|
+
'-s', 'MALLOC=emmalloc',
|
|
229
|
+
'-s', 'MODULARIZE=1',
|
|
230
|
+
'-s', 'EXPORT_NAME=BASIS',
|
|
231
|
+
'-s', 'EXPORT_ES6=1',
|
|
232
|
+
'-s', 'ENVIRONMENT=web,node',
|
|
233
|
+
'-s', 'ASSERTIONS=0',
|
|
234
|
+
'-s', "EXPORTED_RUNTIME_METHODS=['HEAP8']",
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
// Encoder base link flags -- webgl/encoder/CMakeLists.txt LINK_BASE + Release
|
|
238
|
+
// CONFIG_LINK. Non-threaded target only (USE_PTHREADS omitted): determinism for
|
|
239
|
+
// byte-equal re-encode (R-11) and no worker infra needed. INITIAL_MEMORY 128MB
|
|
240
|
+
// + 2MB stack are upstream encoder requirements for real image sizes.
|
|
241
|
+
const ENCODER_LINK = [
|
|
242
|
+
'--bind',
|
|
243
|
+
'-O3',
|
|
244
|
+
'-s', 'ALLOW_MEMORY_GROWTH=1',
|
|
245
|
+
'-s', 'INITIAL_MEMORY=134217728',
|
|
246
|
+
'-s', 'STACK_SIZE=2097152',
|
|
247
|
+
'-s', 'MODULARIZE=1',
|
|
248
|
+
'-s', 'EXPORT_NAME=BASIS',
|
|
249
|
+
'-s', 'EXPORT_ES6=1',
|
|
250
|
+
'-s', 'ENVIRONMENT=web,node',
|
|
251
|
+
'-s', 'ASSERTIONS=0',
|
|
252
|
+
'-s', "EXPORTED_RUNTIME_METHODS=['HEAP8']",
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
const COMMON_CFLAGS = ['-O3', REPRODUCIBLE_PATH, '-fno-strict-aliasing', '-std=c++17'];
|
|
256
|
+
|
|
257
|
+
function emcc(args, label) {
|
|
258
|
+
console.log(`\n[${label}] emcc ${args.length} args ...`);
|
|
259
|
+
execFileSync('emcc', args, { stdio: 'inherit', cwd: VENDOR });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function abs(list) {
|
|
263
|
+
return list.map((rel) => join(VENDOR, rel));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function sizeKB(p) {
|
|
267
|
+
return (statSync(p).size / 1024).toFixed(0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function buildTranscoder() {
|
|
271
|
+
mkdirSync(PKG, { recursive: true });
|
|
272
|
+
// Compile the C zstd unit to an object first (C++ std flag excluded).
|
|
273
|
+
const zstdObj = join(PKG, 'zstddeclib.transcoder.o');
|
|
274
|
+
emcc(
|
|
275
|
+
[
|
|
276
|
+
'-O3',
|
|
277
|
+
REPRODUCIBLE_PATH,
|
|
278
|
+
'-fno-strict-aliasing',
|
|
279
|
+
'-DNDEBUG=1',
|
|
280
|
+
'-DBASISD_SUPPORT_KTX2_ZSTD=1',
|
|
281
|
+
'-c',
|
|
282
|
+
join(VENDOR, TRANSCODER_C),
|
|
283
|
+
'-o',
|
|
284
|
+
zstdObj,
|
|
285
|
+
],
|
|
286
|
+
'transcoder:zstd',
|
|
287
|
+
);
|
|
288
|
+
emcc(
|
|
289
|
+
[
|
|
290
|
+
...COMMON_CFLAGS,
|
|
291
|
+
...TRANSCODER_DEFS,
|
|
292
|
+
...INCLUDE,
|
|
293
|
+
...abs(TRANSCODER_CPP),
|
|
294
|
+
zstdObj,
|
|
295
|
+
...TRANSCODER_LINK,
|
|
296
|
+
'-o', join(PKG, 'basis_transcoder.mjs'),
|
|
297
|
+
],
|
|
298
|
+
'transcoder:link',
|
|
299
|
+
);
|
|
300
|
+
const wasm = join(PKG, 'basis_transcoder.wasm');
|
|
301
|
+
console.log(` -> ${wasm} (${sizeKB(wasm)} KB)`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function buildEncoder() {
|
|
305
|
+
mkdirSync(PKG_ENCODE, { recursive: true });
|
|
306
|
+
const zstdObj = join(PKG, 'zstd.encoder.o');
|
|
307
|
+
emcc(
|
|
308
|
+
[
|
|
309
|
+
'-O3',
|
|
310
|
+
REPRODUCIBLE_PATH,
|
|
311
|
+
'-fno-strict-aliasing',
|
|
312
|
+
'-DNDEBUG=1',
|
|
313
|
+
'-c',
|
|
314
|
+
join(VENDOR, ENCODER_C),
|
|
315
|
+
'-o',
|
|
316
|
+
zstdObj,
|
|
317
|
+
],
|
|
318
|
+
'encoder:zstd',
|
|
319
|
+
);
|
|
320
|
+
emcc(
|
|
321
|
+
[
|
|
322
|
+
...COMMON_CFLAGS,
|
|
323
|
+
...ENCODER_DEFS,
|
|
324
|
+
...INCLUDE,
|
|
325
|
+
...abs(ENCODER_CPP),
|
|
326
|
+
zstdObj,
|
|
327
|
+
...ENCODER_LINK,
|
|
328
|
+
'-o', join(PKG_ENCODE, 'basis_encoder.mjs'),
|
|
329
|
+
],
|
|
330
|
+
'encoder:link',
|
|
331
|
+
);
|
|
332
|
+
const wasm = join(PKG_ENCODE, 'basis_encoder.wasm');
|
|
333
|
+
console.log(` -> ${wasm} (${sizeKB(wasm)} KB)`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function main() {
|
|
337
|
+
assertSourcesExist(TRANSCODER_CPP);
|
|
338
|
+
assertSourcesExist(ENCODER_CPP);
|
|
339
|
+
assertSourcesExist([TRANSCODER_C, ENCODER_C]);
|
|
340
|
+
assertNoUnclaimedEncoderCpp();
|
|
341
|
+
|
|
342
|
+
// Raise the non-wasm64 encoder source-pixel ceiling to 4096^2 before the
|
|
343
|
+
// encoder compile so 4096x4096 game textures encode instead of returning 0.
|
|
344
|
+
patchEncoderPixelLimit();
|
|
345
|
+
|
|
346
|
+
buildTranscoder();
|
|
347
|
+
buildEncoder();
|
|
348
|
+
|
|
349
|
+
// Drop the intermediate emcc object files: they are recompiled from source on
|
|
350
|
+
// every run (no .o-keyed caching) and are NOT part of the shipped runtime. The
|
|
351
|
+
// release publisher packs pkg/ with `tar -C pkg .`, so leaving them here would
|
|
352
|
+
// bloat the content-keyed tarball with non-runtime bytes. pkg/ then holds only
|
|
353
|
+
// the four runtime files (basis_transcoder.{mjs,wasm} + encode/basis_encoder.
|
|
354
|
+
// {mjs,wasm}) that fetch-wasm.mjs extracts back.
|
|
355
|
+
for (const o of [join(PKG, 'zstddeclib.transcoder.o'), join(PKG, 'zstd.encoder.o')]) {
|
|
356
|
+
rmSync(o, { force: true });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log('\nBuild complete:');
|
|
360
|
+
console.log(` ${join(PKG, 'basis_transcoder.mjs')} + .wasm`);
|
|
361
|
+
console.log(` ${join(PKG_ENCODE, 'basis_encoder.mjs')} + .wasm`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
main();
|
|
366
|
+
} catch (e) {
|
|
367
|
+
console.error(`\nbuild-wasm failed: ${e.message}`);
|
|
368
|
+
console.error('Is emcc on PATH? Install: https://emscripten.org/docs/getting_started/downloads.html');
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// content-key.mjs — Single source of truth for the basis-wasm release asset key.
|
|
2
|
+
//
|
|
3
|
+
// The pkg/ bundle (basis_transcoder.{mjs,wasm} + encode/basis_encoder.{mjs,wasm})
|
|
4
|
+
// is an emcc build artifact — NOT committed to git (zero-binary invariant, see
|
|
5
|
+
// packages/codec/.gitignore). It is published to the `wasm-artifacts` GitHub
|
|
6
|
+
// Release, content-keyed so a bundle is uploaded once per unique source state
|
|
7
|
+
// and fetched deterministically.
|
|
8
|
+
//
|
|
9
|
+
// Like @forgeax/engine-wgpu-wasm (and unlike @forgeax/engine-fbx's lone .wasm),
|
|
10
|
+
// the codec pkg/ carries multiple files — two emscripten glue .mjs + two .wasm,
|
|
11
|
+
// nested one level (encode/) — so the release asset is the WHOLE pkg/ packed as
|
|
12
|
+
// a .tar.gz.
|
|
13
|
+
//
|
|
14
|
+
// What determines pkg/ output, and therefore the content key:
|
|
15
|
+
// - scripts/fetch-basis.mjs — pins the exact basis_universal commit
|
|
16
|
+
// (VERSION.commit) + the vendored subtree list. The upstream C++ source is
|
|
17
|
+
// itself gitignored (downloaded on demand), but this pin uniquely fixes it,
|
|
18
|
+
// so hashing the pin script is equivalent to hashing the source tree.
|
|
19
|
+
// - scripts/build-wasm.mjs — the emcc flag sets, compile/link source lists,
|
|
20
|
+
// defines, and the encoder pixel-limit patch. Anything here changes the
|
|
21
|
+
// emitted .wasm/.mjs bit-for-bit.
|
|
22
|
+
// Hashing exactly these two scripts (not the transient vendor/ tree) keeps the
|
|
23
|
+
// key stable across machines and CI while still shifting on any real change.
|
|
24
|
+
//
|
|
25
|
+
// Both scripts/fetch-wasm.mjs (consumer) and the CI publish job import this
|
|
26
|
+
// module so producer and consumer never disagree on the asset name (Pipeline
|
|
27
|
+
// Isolation + SSOT: architecture-principles #1, #4).
|
|
28
|
+
|
|
29
|
+
import { createHash } from 'node:crypto';
|
|
30
|
+
import { readFile } from 'node:fs/promises';
|
|
31
|
+
import { join, dirname, relative } from 'node:path';
|
|
32
|
+
import { fileURLToPath } from 'node:url';
|
|
33
|
+
|
|
34
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
export const PKG_ROOT = join(__dirname, '..');
|
|
36
|
+
|
|
37
|
+
export const RELEASE_TAG = 'wasm-artifacts';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compute the full SHA-256 (hex) over every input that determines the pkg/
|
|
41
|
+
* output. Path is included in the hash so a rename alone shifts the key.
|
|
42
|
+
*
|
|
43
|
+
* Inputs are the two build scripts that fully determine the emitted bundle:
|
|
44
|
+
* fetch-basis.mjs (source pin) + build-wasm.mjs (emcc flags + patch).
|
|
45
|
+
*/
|
|
46
|
+
export async function computeContentSha256() {
|
|
47
|
+
const inputs = [
|
|
48
|
+
join(PKG_ROOT, 'scripts', 'fetch-basis.mjs'),
|
|
49
|
+
join(PKG_ROOT, 'scripts', 'build-wasm.mjs'),
|
|
50
|
+
];
|
|
51
|
+
const hash = createHash('sha256');
|
|
52
|
+
for (const path of inputs) {
|
|
53
|
+
const rel = relative(PKG_ROOT, path).split('\\').join('/');
|
|
54
|
+
const content = await readFile(path);
|
|
55
|
+
hash.update(rel);
|
|
56
|
+
hash.update('\0');
|
|
57
|
+
hash.update(content);
|
|
58
|
+
hash.update('\0');
|
|
59
|
+
}
|
|
60
|
+
return hash.digest('hex');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function buildAssetName(sha8) {
|
|
64
|
+
return `basis-wasm-pkg-${sha8}.tar.gz`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Convenience: returns { sha256, sha8, assetName }. */
|
|
68
|
+
export async function resolveAsset() {
|
|
69
|
+
const sha256 = await computeContentSha256();
|
|
70
|
+
const sha8 = sha256.slice(0, 8);
|
|
71
|
+
return { sha256, sha8, assetName: buildAssetName(sha8) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// CLI: `node scripts/content-key.mjs` prints the asset name (used by CI shell).
|
|
75
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
76
|
+
resolveAsset()
|
|
77
|
+
.then(({ sha256, assetName }) => {
|
|
78
|
+
// Two lines: full sha then asset name, so shell can pick either.
|
|
79
|
+
process.stdout.write(`${sha256}\n${assetName}\n`);
|
|
80
|
+
})
|
|
81
|
+
.catch((e) => {
|
|
82
|
+
console.error('content-key: failed to compute:', e.message || e);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ensure-wasm.mjs — postinstall best-effort hydration of the codec pkg/ WASM.
|
|
3
|
+
// Thin config over scripts/lib/ensure-wasm-lib.mjs (shared with -fbx, -wgpu-wasm).
|
|
4
|
+
//
|
|
5
|
+
// Why no compile fallback here (matches -wgpu-wasm + -fbx): the basis_universal
|
|
6
|
+
// encoder is a multi-minute -O3 emcc compile; doing it inside `bun install`
|
|
7
|
+
// would block every dependency install for minutes with no progress. The
|
|
8
|
+
// compile fallback lives in Studio's setup.ts (fetch → build:wasm, with
|
|
9
|
+
// progress). Bare install stays fast; a consumer with neither a release nor a
|
|
10
|
+
// setup.ts run gets the buildHint below.
|
|
11
|
+
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PKG = join(SCRIPT_DIR, '..', 'pkg');
|
|
18
|
+
const presenceMarkers = [
|
|
19
|
+
join(PKG, 'basis_transcoder.wasm'),
|
|
20
|
+
join(PKG, 'encode', 'basis_encoder.wasm'),
|
|
21
|
+
];
|
|
22
|
+
if (presenceMarkers.every(existsSync)) {
|
|
23
|
+
process.stdout.write('[codec] pkg/ WASM already present -- skipping fetch.\n');
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
const { ensureWasm } = await import('../../../scripts/lib/ensure-wasm-lib.mjs');
|
|
27
|
+
|
|
28
|
+
process.exit(
|
|
29
|
+
ensureWasm({
|
|
30
|
+
pkgLabel: 'codec',
|
|
31
|
+
presenceMarkers,
|
|
32
|
+
fetchScript: join(SCRIPT_DIR, 'fetch-wasm.mjs'),
|
|
33
|
+
skipEnv: 'FORGEAX_SKIP_CODEC_WASM_FETCH',
|
|
34
|
+
buildHint:
|
|
35
|
+
'run Studio `bun fx setup` (fetch → compile fallback), or build locally with\n' +
|
|
36
|
+
' pnpm -F @forgeax/engine-codec build:wasm (needs Emscripten emcc)',
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// fetch-basis.mjs -- Download + pin the BinomialLLC/basis_universal C++ source
|
|
3
|
+
// tree needed to self-build the transcoder + encoder WASM (see build-wasm.mjs).
|
|
4
|
+
//
|
|
5
|
+
// Mirrors the fbx/ufbx precedent (packages/fbx/scripts/fetch-ufbx.mjs): a
|
|
6
|
+
// pinned VERSION constant + GitHub download + skip-if-exists idempotency. The
|
|
7
|
+
// vendored source lands in packages/codec/vendor/basis/ which is gitignored
|
|
8
|
+
// (zero-binary invariant, AC-12) -- never committed, always re-fetched from the
|
|
9
|
+
// pinned commit locally / in CI.
|
|
10
|
+
//
|
|
11
|
+
// Pin: plan-strategy D-2. The commit below is the exact checkout the
|
|
12
|
+
// knowledge-base source analyses (transcoder API / encoder modes / CMake
|
|
13
|
+
// flags) were produced against, so build-wasm.mjs's flag set stays in lockstep
|
|
14
|
+
// with the KB. `git describe` on this commit = v2_10_final_snapshot-62-g1b33fd5
|
|
15
|
+
// (basis lib version "2.50").
|
|
16
|
+
//
|
|
17
|
+
// Usage: node packages/codec/scripts/fetch-basis.mjs
|
|
18
|
+
|
|
19
|
+
import { execSync } from 'node:child_process';
|
|
20
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { dirname, join } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const CODEC_ROOT = join(__dirname, '..');
|
|
27
|
+
const VENDOR_DIR = join(CODEC_ROOT, 'vendor', 'basis');
|
|
28
|
+
|
|
29
|
+
// --- Pin (D-2) --------------------------------------------------------------
|
|
30
|
+
const VERSION = {
|
|
31
|
+
commit: '1b33fd5098c6e7b58324146b8f5518cbb4cdfb72',
|
|
32
|
+
describe: 'v2_10_final_snapshot-62-g1b33fd5',
|
|
33
|
+
lib: '2.50',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Subtrees copied out of the upstream tree into VENDOR_DIR. Whole directories
|
|
37
|
+
// are copied (not a hand-picked .cpp list) so every relative #include of a
|
|
38
|
+
// sibling .h / .inc / .inl resolves without a second manifest to maintain --
|
|
39
|
+
// the .cpp compile list lives in build-wasm.mjs, guarded there against drift.
|
|
40
|
+
const SUBTREES = [
|
|
41
|
+
'transcoder',
|
|
42
|
+
'encoder',
|
|
43
|
+
'zstd',
|
|
44
|
+
'webgl/transcoder',
|
|
45
|
+
];
|
|
46
|
+
const EXTRA_FILES = ['LICENSE'];
|
|
47
|
+
|
|
48
|
+
// Optional offline fallback: a pinned local clone materialised by the KB
|
|
49
|
+
// reference sync. Used only if the network fetch fails (e.g. LibreSSL TLS
|
|
50
|
+
// alert on some macOS toolchains). Must be checked out at the pinned commit.
|
|
51
|
+
const LOCAL_CLONE = join(
|
|
52
|
+
CODEC_ROOT,
|
|
53
|
+
'..',
|
|
54
|
+
'..',
|
|
55
|
+
'.forgeax-harness',
|
|
56
|
+
'knowledge-base',
|
|
57
|
+
'references',
|
|
58
|
+
'repos',
|
|
59
|
+
'basis-universal',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const VERSION_MARKER = join(VENDOR_DIR, '.basis-pin');
|
|
63
|
+
|
|
64
|
+
function isAlreadyFetched() {
|
|
65
|
+
if (!existsSync(VERSION_MARKER)) return false;
|
|
66
|
+
const marker = readFileSync(VERSION_MARKER, 'utf8').trim();
|
|
67
|
+
if (marker !== VERSION.commit) return false;
|
|
68
|
+
// Sanity: the two canonical entry sources must be present.
|
|
69
|
+
return (
|
|
70
|
+
existsSync(join(VENDOR_DIR, 'transcoder', 'basisu_transcoder.cpp')) &&
|
|
71
|
+
existsSync(join(VENDOR_DIR, 'encoder', 'basisu_comp.cpp'))
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function copyFromRoot(srcRoot) {
|
|
76
|
+
if (existsSync(VENDOR_DIR)) rmSync(VENDOR_DIR, { recursive: true, force: true });
|
|
77
|
+
mkdirSync(VENDOR_DIR, { recursive: true });
|
|
78
|
+
for (const sub of SUBTREES) {
|
|
79
|
+
const from = join(srcRoot, sub);
|
|
80
|
+
if (!existsSync(from)) throw new Error(`missing subtree in source: ${sub}`);
|
|
81
|
+
cpSync(from, join(VENDOR_DIR, sub), { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
for (const f of EXTRA_FILES) {
|
|
84
|
+
const from = join(srcRoot, f);
|
|
85
|
+
if (!existsSync(from)) throw new Error(`missing file in source: ${f}`);
|
|
86
|
+
cpSync(from, join(VENDOR_DIR, f));
|
|
87
|
+
}
|
|
88
|
+
writeFileSync(VERSION_MARKER, `${VERSION.commit}\n`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function fetchFromGitHub() {
|
|
92
|
+
const url = `https://codeload.github.com/BinomialLLC/basis_universal/tar.gz/${VERSION.commit}`;
|
|
93
|
+
const tmp = mkdtempSync(join(tmpdir(), 'basis-fetch-'));
|
|
94
|
+
const tarPath = join(tmp, 'basis.tar.gz');
|
|
95
|
+
try {
|
|
96
|
+
console.log(`Fetching ${url} ...`);
|
|
97
|
+
const resp = await fetch(url);
|
|
98
|
+
if (!resp.ok) throw new Error(`download failed: HTTP ${resp.status}`);
|
|
99
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
100
|
+
writeFileSync(tarPath, buf);
|
|
101
|
+
console.log(` downloaded ${(buf.length / 1024 / 1024).toFixed(1)} MB, extracting ...`);
|
|
102
|
+
const prefix = `basis_universal-${VERSION.commit}`;
|
|
103
|
+
const memberArgs = [...SUBTREES, ...EXTRA_FILES].map((p) => `${prefix}/${p}`).join(' ');
|
|
104
|
+
// Extract only the needed members, stripping the top-level prefix dir.
|
|
105
|
+
execSync(`tar xzf ${tarPath} -C ${tmp} ${memberArgs}`, { stdio: 'inherit' });
|
|
106
|
+
copyFromRoot(join(tmp, prefix));
|
|
107
|
+
} finally {
|
|
108
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function fetchFromLocalClone() {
|
|
113
|
+
if (!existsSync(LOCAL_CLONE)) return false;
|
|
114
|
+
let head = '';
|
|
115
|
+
try {
|
|
116
|
+
head = execSync('git rev-parse HEAD', { cwd: LOCAL_CLONE, encoding: 'utf8' }).trim();
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (head !== VERSION.commit) {
|
|
121
|
+
console.warn(` local clone at ${head} != pinned ${VERSION.commit}, skipping fallback`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
console.log(` using pinned local clone: ${LOCAL_CLONE}`);
|
|
125
|
+
copyFromRoot(LOCAL_CLONE);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function main() {
|
|
130
|
+
if (isAlreadyFetched()) {
|
|
131
|
+
console.log(`basis source already at pinned ${VERSION.commit}, skipping (delete vendor/basis to re-fetch).`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await fetchFromGitHub();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.warn(`Network fetch failed (${e.message}); trying local clone fallback ...`);
|
|
138
|
+
if (!fetchFromLocalClone()) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Could not obtain basis source: network fetch failed and no pinned local clone at ${LOCAL_CLONE}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
console.log(`Done. Vendored basis ${VERSION.describe} (lib ${VERSION.lib}) -> ${VENDOR_DIR}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch((e) => {
|
|
148
|
+
console.error(e);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|