roxify 1.5.2 → 1.5.4

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/dist/minpng.d.ts CHANGED
@@ -1,4 +1,18 @@
1
+ /**
2
+ * Encode a buffer of raw RGB data into a minimal PNG (MinPNG format).
3
+ *
4
+ * @param rgb - Buffer of RGB data.
5
+ * @param width - Image width.
6
+ * @param height - Image height.
7
+ * @returns Promise resolving to a PNG Buffer.
8
+ */
1
9
  export declare function encodeMinPng(rgb: Buffer, width: number, height: number): Promise<Buffer>;
10
+ /**
11
+ * Decode a minimal PNG (MinPNG) buffer into raw RGB data and dimensions.
12
+ *
13
+ * @param pngBuf - Buffer containing a MinPNG image.
14
+ * @returns Promise resolving to an object with buf, width, and height, or null if invalid.
15
+ */
2
16
  export declare function decodeMinPng(pngBuf: Buffer): Promise<{
3
17
  buf: Buffer;
4
18
  width: number;
package/dist/minpng.js CHANGED
@@ -51,6 +51,14 @@ function zigzagOrderIndices(width, height) {
51
51
  }
52
52
  return indices;
53
53
  }
54
+ /**
55
+ * Encode a buffer of raw RGB data into a minimal PNG (MinPNG format).
56
+ *
57
+ * @param rgb - Buffer of RGB data.
58
+ * @param width - Image width.
59
+ * @param height - Image height.
60
+ * @returns Promise resolving to a PNG Buffer.
61
+ */
54
62
  export async function encodeMinPng(rgb, width, height) {
55
63
  const w = width, h = height;
56
64
  const idx = (x, y) => (y * w + x) * 3;
@@ -177,6 +185,12 @@ export async function encodeMinPng(rgb, width, height) {
177
185
  }
178
186
  return Buffer.concat(output);
179
187
  }
188
+ /**
189
+ * Decode a minimal PNG (MinPNG) buffer into raw RGB data and dimensions.
190
+ *
191
+ * @param pngBuf - Buffer containing a MinPNG image.
192
+ * @returns Promise resolving to an object with buf, width, and height, or null if invalid.
193
+ */
180
194
  export async function decodeMinPng(pngBuf) {
181
195
  const rawData = native.sharpToRaw(pngBuf);
182
196
  const data = rawData.pixels;
@@ -1,2 +1,17 @@
1
1
  import { DecodeOptions, DecodeResult } from './types.js';
2
+ /**
3
+ * Decode a ROX PNG or buffer into the original binary payload or files list.
4
+ *
5
+ * @param input - Buffer or path to a PNG file.
6
+ * @param opts - Optional decode options.
7
+ * @returns A Promise resolving to DecodeResult ({ buf, meta } or { files }).
8
+ *
9
+ * @example
10
+ * ```js
11
+ * import { decodePngToBinary } from 'roxify';
12
+ * const png = fs.readFileSync('out.png');
13
+ * const res = await decodePngToBinary(png);
14
+ * console.log(res.meta?.name, res.buf.toString('utf8'));
15
+ * ```
16
+ */
2
17
  export declare function decodePngToBinary(input: Buffer | string, opts?: DecodeOptions): Promise<DecodeResult>;
@@ -58,6 +58,21 @@ function convertToPng(buf, format) {
58
58
  catch (e) { }
59
59
  }
60
60
  }
61
+ /**
62
+ * Decode a ROX PNG or buffer into the original binary payload or files list.
63
+ *
64
+ * @param input - Buffer or path to a PNG file.
65
+ * @param opts - Optional decode options.
66
+ * @returns A Promise resolving to DecodeResult ({ buf, meta } or { files }).
67
+ *
68
+ * @example
69
+ * ```js
70
+ * import { decodePngToBinary } from 'roxify';
71
+ * const png = fs.readFileSync('out.png');
72
+ * const res = await decodePngToBinary(png);
73
+ * console.log(res.meta?.name, res.buf.toString('utf8'));
74
+ * ```
75
+ */
61
76
  export async function decodePngToBinary(input, opts = {}) {
62
77
  let pngBuf;
63
78
  if (Buffer.isBuffer(input)) {
@@ -347,8 +362,17 @@ export async function decodePngToBinary(input, opts = {}) {
347
362
  logicalData = rawRGB;
348
363
  }
349
364
  else {
365
+ if (process.env.ROX_DEBUG || opts.debugDir) {
366
+ console.log('DEBUG: about to call cropAndReconstitute, debugDir=', opts.debugDir);
367
+ }
350
368
  const reconstructed = await cropAndReconstitute(processedBuf, opts.debugDir);
369
+ if (process.env.ROX_DEBUG || opts.debugDir) {
370
+ console.log('DEBUG: cropAndReconstitute returned, reconstructed len=', reconstructed.length);
371
+ }
351
372
  const rawData = native.sharpToRaw(reconstructed);
373
+ if (process.env.ROX_DEBUG || opts.debugDir) {
374
+ console.log('DEBUG: rawData from reconstructed:', rawData.width, 'x', rawData.height, 'pixels=', Math.floor(rawData.pixels.length / 3));
375
+ }
352
376
  logicalWidth = rawData.width;
353
377
  logicalHeight = rawData.height;
354
378
  logicalData = Buffer.from(rawData.pixels);
@@ -596,6 +620,16 @@ export async function decodePngToBinary(input, opts = {}) {
596
620
  const markerEndBytes = colorsToBytes(MARKER_END);
597
621
  console.log('DEBUG: MARKER_END index:', pixelBytes.indexOf(markerEndBytes));
598
622
  }
623
+ if (opts.debugDir) {
624
+ try {
625
+ console.log('DEBUG: writing extracted pixel bytes to', opts.debugDir);
626
+ writeFileSync(join(opts.debugDir, 'extracted-pixel-bytes.bin'), pixelBytes);
627
+ writeFileSync(join(opts.debugDir, 'extracted-pixel-head.hex'), pixelBytes.slice(0, 512).toString('hex'));
628
+ }
629
+ catch (e) {
630
+ console.log('DEBUG: failed writing extracted bytes', e?.message ?? e);
631
+ }
632
+ }
599
633
  }
600
634
  try {
601
635
  let idx = 0;
@@ -663,7 +697,203 @@ export async function decodePngToBinary(input, opts = {}) {
663
697
  throw new IncorrectPassphraseError(`Incorrect passphrase (screenshot mode, zstd failed: ` +
664
698
  errMsg +
665
699
  ')');
666
- throw new DataFormatError(`Screenshot mode zstd decompression failed: ` + errMsg);
700
+ // Fallback: try reconstituting the image and re-extracting the pixels
701
+ try {
702
+ if (process.env.ROX_DEBUG)
703
+ console.log('DEBUG: decompress failed, attempting cropAndReconstitute fallback');
704
+ const reconstructed = await cropAndReconstitute(processedBuf, opts.debugDir);
705
+ const raw2 = native.sharpToRaw(reconstructed);
706
+ let logicalData2 = Buffer.from(raw2.pixels);
707
+ let logicalWidth2 = raw2.width;
708
+ let logicalHeight2 = raw2.height;
709
+ // find startIdx2 (linear)
710
+ let startIdx2 = -1;
711
+ const totalPixels2 = (logicalData2.length / 3) | 0;
712
+ for (let i2 = 0; i2 <= totalPixels2 - MARKER_START.length; i2++) {
713
+ let match2 = true;
714
+ for (let mi2 = 0; mi2 < MARKER_START.length && match2; mi2++) {
715
+ const offset2 = (i2 + mi2) * 3;
716
+ if (logicalData2[offset2] !== MARKER_START[mi2].r ||
717
+ logicalData2[offset2 + 1] !== MARKER_START[mi2].g ||
718
+ logicalData2[offset2 + 2] !== MARKER_START[mi2].b) {
719
+ match2 = false;
720
+ }
721
+ }
722
+ if (match2) {
723
+ startIdx2 = i2;
724
+ break;
725
+ }
726
+ }
727
+ if (startIdx2 === -1) {
728
+ // try 2D scan
729
+ let found2D2 = false;
730
+ for (let y = 0; y < logicalHeight2 && !found2D2; y++) {
731
+ for (let x = 0; x <= logicalWidth2 - MARKER_START.length && !found2D2; x++) {
732
+ let match = true;
733
+ for (let mi = 0; mi < MARKER_START.length && match; mi++) {
734
+ const idx = (y * logicalWidth2 + (x + mi)) * 3;
735
+ if (idx + 2 >= logicalData2.length ||
736
+ logicalData2[idx] !== MARKER_START[mi].r ||
737
+ logicalData2[idx + 1] !== MARKER_START[mi].g ||
738
+ logicalData2[idx + 2] !== MARKER_START[mi].b) {
739
+ match = false;
740
+ }
741
+ }
742
+ if (match) {
743
+ // compute rectangle
744
+ let endX = x + MARKER_START.length - 1;
745
+ let endY = y;
746
+ for (let scanY = y; scanY < logicalHeight2; scanY++) {
747
+ let rowHasData = false;
748
+ for (let scanX = x; scanX < logicalWidth2; scanX++) {
749
+ const scanIdx = (scanY * logicalWidth2 + scanX) * 3;
750
+ if (scanIdx + 2 < logicalData2.length) {
751
+ const r = logicalData2[scanIdx];
752
+ const g = logicalData2[scanIdx + 1];
753
+ const b = logicalData2[scanIdx + 2];
754
+ const isBackground = (r === 100 && g === 120 && b === 110) ||
755
+ (r === 0 && g === 0 && b === 0) ||
756
+ (r >= 50 &&
757
+ r <= 220 &&
758
+ g >= 50 &&
759
+ g <= 220 &&
760
+ b >= 50 &&
761
+ b <= 220 &&
762
+ Math.abs(r - g) < 70 &&
763
+ Math.abs(r - b) < 70 &&
764
+ Math.abs(g - b) < 70);
765
+ if (!isBackground) {
766
+ rowHasData = true;
767
+ if (scanX > endX)
768
+ endX = scanX;
769
+ }
770
+ }
771
+ }
772
+ if (rowHasData) {
773
+ endY = scanY;
774
+ }
775
+ else if (scanY > y) {
776
+ break;
777
+ }
778
+ }
779
+ const rectWidth = endX - x + 1;
780
+ const rectHeight = endY - y + 1;
781
+ const newDataLen = rectWidth * rectHeight * 3;
782
+ const newData = Buffer.allocUnsafe(newDataLen);
783
+ let writeIdx = 0;
784
+ for (let ry = y; ry <= endY; ry++) {
785
+ for (let rx = x; rx <= endX; rx++) {
786
+ const idx = (ry * logicalWidth2 + rx) * 3;
787
+ newData[writeIdx++] = logicalData2[idx];
788
+ newData[writeIdx++] = logicalData2[idx + 1];
789
+ newData[writeIdx++] = logicalData2[idx + 2];
790
+ }
791
+ }
792
+ logicalData2 = newData;
793
+ logicalWidth2 = rectWidth;
794
+ logicalHeight2 = rectHeight;
795
+ startIdx2 = 0;
796
+ found2D2 = true;
797
+ }
798
+ }
799
+ }
800
+ if (!found2D2)
801
+ throw new DataFormatError('Screenshot fallback failed: START not found');
802
+ }
803
+ // compute endStartPixel2
804
+ const curTotalPixels2 = (logicalData2.length / 3) | 0;
805
+ const lastLineStart2 = (logicalHeight2 - 1) * logicalWidth2;
806
+ const endMarkerStartCol2 = logicalWidth2 - MARKER_END.length;
807
+ let endStartPixel2 = -1;
808
+ if (lastLineStart2 + endMarkerStartCol2 < curTotalPixels2) {
809
+ let matchEnd2 = true;
810
+ for (let mi = 0; mi < MARKER_END.length && matchEnd2; mi++) {
811
+ const pixelIdx = lastLineStart2 + endMarkerStartCol2 + mi;
812
+ if (pixelIdx >= curTotalPixels2) {
813
+ matchEnd2 = false;
814
+ break;
815
+ }
816
+ const offset = pixelIdx * 3;
817
+ if (logicalData2[offset] !== MARKER_END[mi].r ||
818
+ logicalData2[offset + 1] !== MARKER_END[mi].g ||
819
+ logicalData2[offset + 2] !== MARKER_END[mi].b) {
820
+ matchEnd2 = false;
821
+ }
822
+ }
823
+ if (matchEnd2) {
824
+ endStartPixel2 =
825
+ lastLineStart2 + endMarkerStartCol2 - startIdx2;
826
+ if (process.env.ROX_DEBUG) {
827
+ console.log('DEBUG: Found END marker in fallback at last line');
828
+ }
829
+ }
830
+ }
831
+ if (endStartPixel2 === -1) {
832
+ if (process.env.ROX_DEBUG) {
833
+ console.log('DEBUG: END marker not found in fallback; using end of grid');
834
+ }
835
+ endStartPixel2 = curTotalPixels2 - startIdx2;
836
+ }
837
+ const dataPixelCount2 = endStartPixel2 - (MARKER_START.length + 1);
838
+ const pixelBytes2 = Buffer.allocUnsafe(dataPixelCount2 * 3);
839
+ for (let i2 = 0; i2 < dataPixelCount2; i2++) {
840
+ const srcOffset = (startIdx2 + MARKER_START.length + 1 + i2) * 3;
841
+ const dstOffset = i2 * 3;
842
+ pixelBytes2[dstOffset] = logicalData2[srcOffset];
843
+ pixelBytes2[dstOffset + 1] = logicalData2[srcOffset + 1];
844
+ pixelBytes2[dstOffset + 2] = logicalData2[srcOffset + 2];
845
+ }
846
+ // try decompressing fallback payload
847
+ const foundPX = pixelBytes2.indexOf(PIXEL_MAGIC);
848
+ if (process.env.ROX_DEBUG)
849
+ console.log('DEBUG: PIXEL_MAGIC index in fallback:', foundPX);
850
+ if (pixelBytes2.length >= PIXEL_MAGIC.length) {
851
+ let ii = 0;
852
+ const at0 = pixelBytes2
853
+ .slice(0, PIXEL_MAGIC.length)
854
+ .equals(PIXEL_MAGIC);
855
+ if (at0)
856
+ ii = PIXEL_MAGIC.length;
857
+ else {
858
+ const found = pixelBytes2.indexOf(PIXEL_MAGIC);
859
+ if (found !== -1)
860
+ ii = found + PIXEL_MAGIC.length;
861
+ }
862
+ if (ii > 0) {
863
+ const version2 = pixelBytes2[ii++];
864
+ const nameLen2 = pixelBytes2[ii++];
865
+ const payloadLen2 = pixelBytes2.readUInt32BE(ii + nameLen2);
866
+ const rawPayload2 = pixelBytes2.slice(ii + nameLen2 + 4, ii + nameLen2 + 4 + payloadLen2);
867
+ let payload2 = tryDecryptIfNeeded(rawPayload2, opts.passphrase);
868
+ payload2 = await tryDecompress(payload2, (info) => {
869
+ if (opts.onProgress)
870
+ opts.onProgress(info);
871
+ });
872
+ if (!payload2.slice(0, MAGIC.length).equals(MAGIC)) {
873
+ throw new DataFormatError('Screenshot fallback failed: missing ROX1 magic after decompression');
874
+ }
875
+ payload2 = payload2.slice(MAGIC.length);
876
+ if (opts.files) {
877
+ const unpacked2 = unpackBuffer(payload2, opts.files);
878
+ if (unpacked2) {
879
+ if (opts.onProgress)
880
+ opts.onProgress({ phase: 'done' });
881
+ progressBar?.stop();
882
+ return { files: unpacked2.files, meta: { name } };
883
+ }
884
+ }
885
+ if (opts.onProgress)
886
+ opts.onProgress({ phase: 'done' });
887
+ progressBar?.stop();
888
+ return { buf: payload2, meta: { name } };
889
+ }
890
+ }
891
+ throw new DataFormatError('Screenshot mode zstd decompression failed: ' + errMsg);
892
+ }
893
+ catch (e2) {
894
+ // If fallback fails, rethrow original error
895
+ throw new DataFormatError(`Screenshot mode zstd decompression failed: ` + errMsg);
896
+ }
667
897
  }
668
898
  if (!payload.slice(0, MAGIC.length).equals(MAGIC)) {
669
899
  throw new DataFormatError('Invalid ROX format (pixel mode: missing ROX1 magic after decompression)');
@@ -1,2 +1,23 @@
1
1
  import { EncodeOptions } from './types.js';
2
+ /**
3
+ * Encode a buffer or array of buffers into a PNG image (ROX format).
4
+ *
5
+ * @param input - The buffer or array of buffers to encode.
6
+ * @param opts - Optional encoding options.
7
+ * @returns A Promise that resolves to a PNG Buffer containing the encoded data.
8
+ *
9
+ * @example
10
+ * ```js
11
+ * import { encodeBinaryToPng } from 'roxify';
12
+ *
13
+ * const png = await encodeBinaryToPng(Buffer.from('hello'), {
14
+ * mode: 'screenshot',
15
+ * name: 'hello.txt',
16
+ * compressionLevel: 19,
17
+ * outputFormat: 'png',
18
+ * });
19
+ *
20
+ * // write to disk using fs.writeFileSync('out.png', png)
21
+ * ```
22
+ */
2
23
  export declare function encodeBinaryToPng(input: Buffer | Buffer[], opts?: EncodeOptions): Promise<Buffer>;
@@ -6,6 +6,27 @@ import { crc32 } from './crc.js';
6
6
  import { colorsToBytes } from './helpers.js';
7
7
  import { native } from './native.js';
8
8
  import { parallelZstdCompress } from './zstd.js';
9
+ /**
10
+ * Encode a buffer or array of buffers into a PNG image (ROX format).
11
+ *
12
+ * @param input - The buffer or array of buffers to encode.
13
+ * @param opts - Optional encoding options.
14
+ * @returns A Promise that resolves to a PNG Buffer containing the encoded data.
15
+ *
16
+ * @example
17
+ * ```js
18
+ * import { encodeBinaryToPng } from 'roxify';
19
+ *
20
+ * const png = await encodeBinaryToPng(Buffer.from('hello'), {
21
+ * mode: 'screenshot',
22
+ * name: 'hello.txt',
23
+ * compressionLevel: 19,
24
+ * outputFormat: 'png',
25
+ * });
26
+ *
27
+ * // write to disk using fs.writeFileSync('out.png', png)
28
+ * ```
29
+ */
9
30
  export async function encodeBinaryToPng(input, opts = {}) {
10
31
  let progressBar = null;
11
32
  if (opts.showProgress) {
@@ -1,8 +1,17 @@
1
1
  /**
2
- * List files in a Rox PNG archive without decoding the full payload.
3
- * Returns the file list if available, otherwise null.
4
- * @param pngBuf - PNG data
5
- * @public
2
+ * List files stored inside a ROX PNG without fully extracting it.
3
+ * Returns `null` if no file list could be found.
4
+ *
5
+ * @param pngBuf - Buffer containing a PNG file.
6
+ * @param opts - Options to include sizes.
7
+ * @returns Promise resolving to an array of file names or objects with sizes.
8
+ *
9
+ * @example
10
+ * ```js
11
+ * import { listFilesInPng } from 'roxify';
12
+ * const files = await listFilesInPng(fs.readFileSync('out.png'), { includeSizes: true });
13
+ * console.log(files);
14
+ * ```
6
15
  */
7
16
  export declare function listFilesInPng(pngBuf: Buffer, opts?: {
8
17
  includeSizes?: boolean;
@@ -11,7 +20,16 @@ export declare function listFilesInPng(pngBuf: Buffer, opts?: {
11
20
  size: number;
12
21
  }[] | null>;
13
22
  /**
14
- * Detect if a PNG/ROX buffer contains an encrypted payload (requires passphrase)
15
- * Returns true if encryption flag indicates AES or XOR.
23
+ * Check if a PNG contains an encrypted payload requiring a passphrase.
24
+ *
25
+ * @param pngBuf - Buffer containing a PNG file.
26
+ * @returns Promise resolving to `true` if the PNG requires a passphrase.
27
+ *
28
+ * @example
29
+ * ```js
30
+ * import { hasPassphraseInPng } from 'roxify';
31
+ * const needPass = await hasPassphraseInPng(fs.readFileSync('out.png'));
32
+ * console.log('needs passphrase?', needPass);
33
+ * ```
16
34
  */
17
35
  export declare function hasPassphraseInPng(pngBuf: Buffer): Promise<boolean>;
@@ -6,10 +6,19 @@ import { PassphraseRequiredError } from './errors.js';
6
6
  import { native } from './native.js';
7
7
  import { cropAndReconstitute } from './reconstitution.js';
8
8
  /**
9
- * List files in a Rox PNG archive without decoding the full payload.
10
- * Returns the file list if available, otherwise null.
11
- * @param pngBuf - PNG data
12
- * @public
9
+ * List files stored inside a ROX PNG without fully extracting it.
10
+ * Returns `null` if no file list could be found.
11
+ *
12
+ * @param pngBuf - Buffer containing a PNG file.
13
+ * @param opts - Options to include sizes.
14
+ * @returns Promise resolving to an array of file names or objects with sizes.
15
+ *
16
+ * @example
17
+ * ```js
18
+ * import { listFilesInPng } from 'roxify';
19
+ * const files = await listFilesInPng(fs.readFileSync('out.png'), { includeSizes: true });
20
+ * console.log(files);
21
+ * ```
13
22
  */
14
23
  export async function listFilesInPng(pngBuf, opts = {}) {
15
24
  try {
@@ -412,8 +421,17 @@ async function getFileSizesFromPng(pngBuf) {
412
421
  return null;
413
422
  }
414
423
  /**
415
- * Detect if a PNG/ROX buffer contains an encrypted payload (requires passphrase)
416
- * Returns true if encryption flag indicates AES or XOR.
424
+ * Check if a PNG contains an encrypted payload requiring a passphrase.
425
+ *
426
+ * @param pngBuf - Buffer containing a PNG file.
427
+ * @returns Promise resolving to `true` if the PNG requires a passphrase.
428
+ *
429
+ * @example
430
+ * ```js
431
+ * import { hasPassphraseInPng } from 'roxify';
432
+ * const needPass = await hasPassphraseInPng(fs.readFileSync('out.png'));
433
+ * console.log('needs passphrase?', needPass);
434
+ * ```
417
435
  */
418
436
  export async function hasPassphraseInPng(pngBuf) {
419
437
  try {
@@ -1 +1,8 @@
1
+ /**
2
+ * Reconstitute a PNG buffer by cropping and reconstructing the image.
3
+ *
4
+ * @param input - Buffer containing the PNG image.
5
+ * @param debugDir - Optional directory to write debug images.
6
+ * @returns Promise resolving to the reconstituted PNG buffer.
7
+ */
1
8
  export declare function cropAndReconstitute(input: Buffer, debugDir?: string): Promise<Buffer>;
@@ -1,4 +1,34 @@
1
+ import { writeFileSync } from 'fs';
2
+ import { join } from 'path';
1
3
  import { native } from './native.js';
4
+ /**
5
+ * Reconstitute a PNG buffer by cropping and reconstructing the image.
6
+ *
7
+ * @param input - Buffer containing the PNG image.
8
+ * @param debugDir - Optional directory to write debug images.
9
+ * @returns Promise resolving to the reconstituted PNG buffer.
10
+ */
2
11
  export async function cropAndReconstitute(input, debugDir) {
3
- return Buffer.from(native.cropAndReconstitute(input));
12
+ const out = Buffer.from(native.cropAndReconstitute(input));
13
+ if (debugDir) {
14
+ try {
15
+ const meta = native.sharpMetadata(input);
16
+ const doubled = native.sharpResizeImage(input, meta.width * 2, meta.height * 2, 'nearest');
17
+ console.log('DEBUG: writing doubled.png to', debugDir);
18
+ writeFileSync(join(debugDir, 'doubled.png'), Buffer.from(doubled));
19
+ }
20
+ catch (e) {
21
+ console.log('DEBUG: failed to write doubled.png', e?.message ?? e);
22
+ }
23
+ try {
24
+ console.log('DEBUG: writing reconstructed.png and reconstructed-pixels.bin to', debugDir);
25
+ writeFileSync(join(debugDir, 'reconstructed.png'), out);
26
+ const raw = native.sharpToRaw(out);
27
+ writeFileSync(join(debugDir, 'reconstructed-pixels.bin'), Buffer.from(raw.pixels));
28
+ }
29
+ catch (e) {
30
+ console.log('DEBUG: failed to write reconstructed artifacts', e?.message ?? e);
31
+ }
32
+ }
33
+ return out;
4
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Ultra-lightweight PNG steganography with native Rust acceleration. Encode binary data into PNG images with zstd compression.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,8 +27,9 @@
27
27
  "build:native:windows": "cargo build --release --lib --target x86_64-pc-windows-msvc && cp target/x86_64-pc-windows-msvc/release/roxify_native.dll libroxify_native-x86_64-pc-windows-msvc.dll",
28
28
  "build:cli": "cargo build --release --bin roxify_native && cp target/release/roxify_native dist/roxify-cli",
29
29
  "build:all": "npm run build:native && npm run build && npm run build:cli",
30
+ "build:cross": "node scripts/build-all-platforms.js && npm run build && npm run build:cli",
30
31
  "postbuild:native": "node scripts/copy-native.js",
31
- "prepublishOnly": "npm run build:all",
32
+ "prepublishOnly": "npm run build:cross",
32
33
  "test": "npm run build && node test/run-all-tests.js",
33
34
  "cli": "node dist/cli.js"
34
35
  },