roxify 1.5.11 → 1.6.1

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/README.md ADDED
@@ -0,0 +1,377 @@
1
+ # RoxCompressor Transform
2
+
3
+ > Encode binary data into PNG images and decode them back. Fast, efficient, with optional encryption and native Rust acceleration.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/roxify.svg)](https://www.npmjs.com/package/roxify)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ ## Features
9
+
10
+ - ⚡ **Blazing Fast**: Native Rust acceleration via N-API — **1GB/s** throughput on modern hardware
11
+ - 🚀 **Optimized Compression**: Multi-threaded Zstd compression (level 19) with parallel processing
12
+ - 🔒 **Secure**: AES-256-GCM encryption support with PBKDF2 key derivation
13
+ - 🎨 **Multiple modes**: Compact, chunk, pixel, and screenshot modes
14
+ - 📦 **CLI & API**: Use as command-line tool or JavaScript library
15
+ - 🔄 **Lossless**: Perfect roundtrip encoding/decoding
16
+ - 📖 **Full TSDoc**: Complete TypeScript documentation
17
+ - 🦀 **Rust Powered**: Optional native module for extreme performance (falls back to pure JS)
18
+
19
+ ## Real-world benchmarks 🔧
20
+
21
+ **Highlights**
22
+
23
+ - Practical benchmarks on large codebase datasets showing significant compression and high throughput while handling many small files efficiently.
24
+
25
+ **Results**
26
+
27
+ | Dataset | Files | Original | Compressed | Ratio | Time | Throughput | Notes |
28
+ | -------- | ------: | -------: | ---------: | --------: | -----: | ---------: | ------------------------------------------- |
29
+ | 4,000 MB | 731,340 | 3.93 GB | 111.42 MB | **2.8%** | 26.9 s | 149.4 MB/s | gzip: 2.26 GB (57.5%); 7z: 1.87 GB (47.6%) |
30
+ | 1,000 MB | 141,522 | 1.03 GB | 205 MB | **19.4%** | ~6.2 s | ≈170 MB/s | shows benefits for many-small-file datasets |
31
+
32
+ ### Methodology
33
+
34
+ - Compression: multithreaded Zstd (level 19) and Brotli (configurable).
35
+ - Setup: parallel I/O and multithreaded compression on modern SSD-backed systems.
36
+ - Measurements: wall-clock time; throughput = original size / time; comparisons against gzip and 7z with typical defaults.
37
+ - Reproducibility: full benchmark details, commands and raw data are available in `docs/BENCHMARK_FINAL_REPORT.md`.
38
+
39
+ These results demonstrate Roxify's strength for packaging large codebases and many-small-file archives where speed and a good compression/throughput trade-off matter.
40
+
41
+ ## Documentation
42
+
43
+ - 📘 **[CLI Documentation](./docs/CLI.md)** - Complete command-line usage guide
44
+ - 📗 **[JavaScript SDK](./docs/JAVASCRIPT_SDK.md)** - Programmatic API reference with examples
45
+ - 📙 **[Quick Start](#quick-start)** - Get started in 2 minutes
46
+
47
+ ## Installation
48
+
49
+ ### As CLI tool (npx)
50
+
51
+ No installation needed! Use directly with npx:
52
+
53
+ ```bash
54
+ npx rox encode input.zip output.png
55
+ npx rox decode output.png original.zip
56
+ ```
57
+
58
+ ### As library
59
+
60
+ ```bash
61
+ npm install roxify
62
+ ```
63
+
64
+ ## CLI Usage
65
+
66
+ ### Quick Start
67
+
68
+ ```bash
69
+ # Encode a file
70
+ npx rox encode document.pdf document.png
71
+
72
+ # Decode it back
73
+ npx rox decode document.png document.pdf
74
+
75
+ # With encryption
76
+ npx rox encode secret.zip secret.png -p mypassword
77
+ npx rox decode secret.png secret.zip -p mypassword
78
+ ```
79
+
80
+ ### CLI Commands
81
+
82
+ #### `encode` - Encode file to PNG
83
+
84
+ ```bash
85
+ npx rox encode <input> [output] [options]
86
+ ```
87
+
88
+ **Options:**
89
+
90
+ - `-p, --passphrase <pass>` - Encrypt with passphrase (AES-256-GCM)
91
+ - `-m, --mode <mode>` - Encoding mode: `compact|chunk|pixel|screenshot` (default: `screenshot`)
92
+ - `-q, --quality <0-11>` - Brotli compression quality (default: `1`)
93
+ - `0` = fastest, largest
94
+ - `11` = slowest, smallest
95
+ - `-e, --encrypt <type>` - Encryption: `auto|aes|xor|none` (default: `aes` if passphrase)
96
+ - `--no-compress` - Disable compression
97
+ - `-o, --output <path>` - Output file path
98
+
99
+ **Examples:**
100
+
101
+ ```bash
102
+ # Basic encoding
103
+ npx rox encode data.bin output.png
104
+
105
+ # Fast compression for large files
106
+ npx rox encode large-video.mp4 output.png -q 0
107
+
108
+ # High compression for small files
109
+ npx rox encode config.json output.png -q 11
110
+
111
+ # With encryption
112
+ npx rox encode secret.pdf secure.png -p "my secure password"
113
+
114
+ # Compact mode (smallest PNG)
115
+ npx rox encode data.bin tiny.png -m compact
116
+
117
+ # Screenshot mode (recommended, looks like a real image)
118
+ npx rox encode archive.tar.gz screenshot.png -m screenshot
119
+ ```
120
+
121
+ #### `decode` - Decode PNG to file
122
+
123
+ ```bash
124
+ npx rox decode <input> [output] [options]
125
+ ```
126
+
127
+ **Options:**
128
+
129
+ - `-p, --passphrase <pass>` - Decryption passphrase
130
+ - `-o, --output <path>` - Output file path (auto-detected from metadata if not provided)
131
+
132
+ **Examples:**
133
+
134
+ ```bash
135
+ # Basic decoding
136
+ npx rox decode encoded.png output.bin
137
+
138
+ # Auto-detect filename from metadata
139
+ npx rox decode encoded.png
140
+
141
+ # With decryption
142
+ npx rox decode encrypted.png output.pdf -p "my secure password"
143
+ ```
144
+
145
+ ## JavaScript API
146
+
147
+ ### Basic Usage
148
+
149
+ ```typescript
150
+ import { encodeBinaryToPng, decodePngToBinary } from 'roxify';
151
+ import { readFileSync, writeFileSync } from 'fs';
152
+
153
+ const input = readFileSync('input.zip');
154
+ const png = await encodeBinaryToPng(input, {
155
+ mode: 'screenshot',
156
+ name: 'input.zip',
157
+ });
158
+ writeFileSync('output.png', png);
159
+
160
+ const encoded = readFileSync('output.png');
161
+ const result = await decodePngToBinary(encoded);
162
+ writeFileSync(result.meta?.name || 'output.bin', result.buf);
163
+ ```
164
+
165
+ ### With Encryption
166
+
167
+ ```typescript
168
+ const png = await encodeBinaryToPng(input, {
169
+ mode: 'screenshot',
170
+ passphrase: 'my-secret-password',
171
+ encrypt: 'aes',
172
+ name: 'secret.zip',
173
+ });
174
+
175
+ const result = await decodePngToBinary(encoded, {
176
+ passphrase: 'my-secret-password',
177
+ });
178
+ ```
179
+
180
+ ### Fast Compression
181
+
182
+ ```typescript
183
+ const png = await encodeBinaryToPng(largeBuffer, {
184
+ mode: 'screenshot',
185
+ brQuality: 0,
186
+ name: 'large-file.bin',
187
+ });
188
+
189
+ const png = await encodeBinaryToPng(smallBuffer, {
190
+ mode: 'compact',
191
+ brQuality: 11,
192
+ name: 'config.json',
193
+ });
194
+ ```
195
+
196
+ ### Encoding Modes
197
+
198
+ #### `screenshot` (Recommended)
199
+
200
+ Encodes data as RGB pixel values, optimized for screenshot-like appearance. Best balance of size and compatibility.
201
+
202
+ ```typescript
203
+ const png = await encodeBinaryToPng(data, { mode: 'screenshot' });
204
+ ```
205
+
206
+ #### `compact` (Smallest)
207
+
208
+ Minimal 1x1 PNG with data in custom chunk. Fastest and smallest.
209
+
210
+ ```typescript
211
+ const png = await encodeBinaryToPng(data, { mode: 'compact' });
212
+ ```
213
+
214
+ #### `pixel`
215
+
216
+ Encodes data as RGB pixel values without screenshot optimization.
217
+
218
+ ```typescript
219
+ const png = await encodeBinaryToPng(data, { mode: 'pixel' });
220
+ ```
221
+
222
+ #### `chunk`
223
+
224
+ Standard PNG with data in custom rXDT chunk.
225
+
226
+ ```typescript
227
+ const png = await encodeBinaryToPng(data, { mode: 'chunk' });
228
+ ```
229
+
230
+ ## API Reference
231
+
232
+ ### `encodeBinaryToPng(input, options)`
233
+
234
+ Encodes binary data into a PNG image.
235
+
236
+ **Parameters:**
237
+
238
+ - `input: Buffer` - The binary data to encode
239
+ - `options?: EncodeOptions` - Encoding options
240
+
241
+ **Returns:** `Promise<Buffer>` - The encoded PNG
242
+
243
+ **Options:**
244
+
245
+ ```typescript
246
+ interface EncodeOptions {
247
+ compression?: 'br' | 'none';
248
+
249
+ passphrase?: string;
250
+
251
+ name?: string;
252
+
253
+ mode?: 'compact' | 'chunk' | 'pixel' | 'screenshot';
254
+
255
+ encrypt?: 'auto' | 'aes' | 'xor' | 'none';
256
+
257
+ output?: 'auto' | 'png' | 'rox';
258
+
259
+ includeName?: boolean;
260
+
261
+ brQuality?: number;
262
+ }
263
+ ```
264
+
265
+ ### `decodePngToBinary(pngBuf, options)`
266
+
267
+ Decodes a PNG image back to binary data.
268
+
269
+ **Parameters:**
270
+
271
+ - `pngBuf: Buffer` - The PNG image to decode
272
+ - `options?: DecodeOptions` - Decoding options
273
+
274
+ **Returns:** `Promise<DecodeResult>` - The decoded data and metadata
275
+
276
+ **Options:**
277
+
278
+ ```typescript
279
+ interface DecodeOptions {
280
+ passphrase?: string;
281
+ }
282
+ ```
283
+
284
+ **Result:**
285
+
286
+ ```typescript
287
+ interface DecodeResult {
288
+ buf: Buffer;
289
+
290
+ meta?: {
291
+ name?: string;
292
+ };
293
+ }
294
+ ```
295
+
296
+ ## Performance Tips
297
+
298
+ ### For Large Files (>10 MB)
299
+
300
+ ```bash
301
+ # Use quality 0 for fastest encoding
302
+ npx rox encode large.bin output.png -q 0
303
+ ```
304
+
305
+ ```typescript
306
+ const png = await encodeBinaryToPng(largeFile, {
307
+ mode: 'screenshot',
308
+ brQuality: 0,
309
+ });
310
+ ```
311
+
312
+ ### For Small Files (<1 MB)
313
+
314
+ ```bash
315
+ # Use quality 11 for best compression
316
+ npx rox encode small.json output.png -q 11 -m compact
317
+ ```
318
+
319
+ ```typescript
320
+ const png = await encodeBinaryToPng(smallFile, {
321
+ mode: 'compact',
322
+ brQuality: 11,
323
+ });
324
+ ```
325
+
326
+ ### Benchmark Results
327
+
328
+ File: 3.8 MB binary
329
+
330
+ - **Quality 0**: ~500-800ms, output ~1.2 MB
331
+ - **Quality 1** (default): ~1-2s, output ~800 KB
332
+ - **Quality 5**: ~8-12s, output ~750 KB
333
+ - **Quality 11**: ~20-30s, output ~720 KB
334
+
335
+ ## Error Handling
336
+
337
+ ```typescript
338
+ try {
339
+ const result = await decodePngToBinary(encoded, {
340
+ passphrase: 'wrong-password',
341
+ });
342
+ } catch (err) {
343
+ if (err.message.includes('Incorrect passphrase')) {
344
+ console.error('Wrong password!');
345
+ } else if (err.message.includes('Invalid ROX format')) {
346
+ console.error('Not a valid RoxCompressor PNG');
347
+ } else {
348
+ console.error('Decode failed:', err.message);
349
+ }
350
+ }
351
+ ```
352
+
353
+ ## Security
354
+
355
+ - **AES-256-GCM**: Authenticated encryption with 100,000 PBKDF2 iterations
356
+ - **XOR cipher**: Simple obfuscation (not cryptographically secure)
357
+ - **No encryption**: Data is compressed but not encrypted
358
+
359
+ ⚠️ **Warning**: Use strong passphrases for sensitive data. The `xor` encryption mode is not secure and should only be used for obfuscation.
360
+
361
+ ## License
362
+
363
+ MIT © RoxCompressor
364
+
365
+ ## Contributing
366
+
367
+ Contributions welcome! Please open an issue or PR on GitHub.
368
+
369
+ ## Links
370
+
371
+ - [GitHub Repository](https://github.com/RoxasYTB/roxify)
372
+ - [npm Package](https://www.npmjs.com/package/roxify)
373
+ - [Report Issues](https://github.com/RoxasYTB/roxify/issues)
374
+
375
+ ## CI / Multi-platform builds
376
+
377
+ This project runs continuous integration on Linux, Windows and macOS via GitHub Actions. Native modules are built on each platform and attached to the workflow (and release) as artifacts. On releases we also publish platform artifacts to GitHub Releases. For npm publishing, set the `NPM_TOKEN` secret in your repository settings to allow automated publishes on release.
@@ -0,0 +1 @@
1
+ export declare const TEST_EMIT = 42;
@@ -0,0 +1 @@
1
+ export const TEST_EMIT = 42;
package/dist/cli.js CHANGED
@@ -1,12 +1,32 @@
1
1
  #!/usr/bin/env node
2
- import { mkdirSync, readFileSync, statSync, writeFileSync } from 'fs';
2
+ import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from 'fs';
3
3
  import { open } from 'fs/promises';
4
4
  import { basename, dirname, join, resolve } from 'path';
5
5
  import { DataFormatError, decodePngToBinary, encodeBinaryToPng, hasPassphraseInPng, IncorrectPassphraseError, listFilesInPng, PassphraseRequiredError, } from './index.js';
6
6
  import { packPathsGenerator, unpackBuffer } from './pack.js';
7
7
  import * as cliProgress from './stub-progress.js';
8
8
  import { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
9
- const VERSION = '1.4.0';
9
+ const VERSION = '1.6.1';
10
+ function getDirectorySize(dirPath) {
11
+ let totalSize = 0;
12
+ try {
13
+ const entries = readdirSync(dirPath, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ const fullPath = join(dirPath, entry.name);
16
+ if (entry.isDirectory()) {
17
+ totalSize += getDirectorySize(fullPath);
18
+ }
19
+ else if (entry.isFile()) {
20
+ try {
21
+ totalSize += statSync(fullPath).size;
22
+ }
23
+ catch (e) { }
24
+ }
25
+ }
26
+ }
27
+ catch (e) { }
28
+ return totalSize;
29
+ }
10
30
  async function readLargeFile(filePath) {
11
31
  const st = statSync(filePath);
12
32
  if (st.size <= 2 * 1024 * 1024 * 1024) {
@@ -177,7 +197,6 @@ async function encodeCommand(args) {
177
197
  safeCwd = process.cwd();
178
198
  }
179
199
  catch (e) {
180
- // ENOENT: fallback sur racine
181
200
  safeCwd = '/';
182
201
  }
183
202
  const resolvedInputs = inputPaths.map((p) => resolve(safeCwd, p));
@@ -195,7 +214,6 @@ async function encodeCommand(args) {
195
214
  catch (e) {
196
215
  resolvedOutput = join('/', parsed.output || outputPath || outputName);
197
216
  }
198
- // Check for empty directories *before* attempting native Rust encoder.
199
217
  try {
200
218
  const anyDir = inputPaths.some((p) => {
201
219
  try {
@@ -214,10 +232,15 @@ async function encodeCommand(args) {
214
232
  }
215
233
  }
216
234
  }
235
+ catch (e) { }
236
+ let anyInputDir = false;
237
+ try {
238
+ anyInputDir = resolvedInputs.some((p) => statSync(p).isDirectory());
239
+ }
217
240
  catch (e) {
218
- // ignore errors from the quick pre-check and proceed to try Rust encoding
241
+ anyInputDir = false;
219
242
  }
220
- if (isRustBinaryAvailable() && !parsed.forceTs) {
243
+ if (isRustBinaryAvailable() && !parsed.forceTs && !anyInputDir) {
221
244
  try {
222
245
  console.log(`Encoding to ${resolvedOutput} (Using native Rust encoder)\n`);
223
246
  const startTime = Date.now();
@@ -246,11 +269,7 @@ async function encodeCommand(args) {
246
269
  let inputSize = 0;
247
270
  if (inputPaths.length === 1 &&
248
271
  fstatSync(resolvedInputs[0]).isDirectory()) {
249
- const { execSync } = await import('child_process');
250
- const sizeOutput = execSync(`du -sb "${resolvedInputs[0]}"`, {
251
- encoding: 'utf-8',
252
- });
253
- inputSize = parseInt(sizeOutput.split(/\s+/)[0]);
272
+ inputSize = getDirectorySize(resolvedInputs[0]);
254
273
  }
255
274
  else {
256
275
  inputSize = fstatSync(resolvedInputs[0]).size;
@@ -656,6 +675,39 @@ async function listCommand(args) {
656
675
  process.exit(1);
657
676
  }
658
677
  const resolvedInput = resolve(inputPath);
678
+ if (isRustBinaryAvailable()) {
679
+ try {
680
+ const { findRustBinary } = await import('./utils/rust-cli-wrapper.js');
681
+ const cliPath = findRustBinary();
682
+ if (cliPath) {
683
+ const { execSync } = await import('child_process');
684
+ try {
685
+ const help = execSync(`"${cliPath}" --help`, { encoding: 'utf-8' });
686
+ if (!help.includes('list')) {
687
+ throw new Error('native CLI does not support list');
688
+ }
689
+ const output = execSync(`"${cliPath}" list "${resolvedInput}"`, {
690
+ encoding: 'utf-8',
691
+ stdio: ['pipe', 'pipe', 'inherit'],
692
+ timeout: 30000,
693
+ });
694
+ const fileList = JSON.parse(output.trim());
695
+ console.log(`Files in ${resolvedInput}:`);
696
+ for (const file of fileList) {
697
+ if (typeof file === 'string') {
698
+ console.log(` ${file}`);
699
+ }
700
+ else {
701
+ console.log(` ${file.name} (${file.size} bytes)`);
702
+ }
703
+ }
704
+ return;
705
+ }
706
+ catch (e) { }
707
+ }
708
+ }
709
+ catch (err) { }
710
+ }
659
711
  try {
660
712
  const inputBuffer = readFileSync(resolvedInput);
661
713
  const fileList = await listFilesInPng(inputBuffer, {
package/dist/index.d.ts CHANGED
@@ -5,9 +5,10 @@ export * from './utils/encoder.js';
5
5
  export * from './utils/errors.js';
6
6
  export * from './utils/helpers.js';
7
7
  export * from './utils/inspection.js';
8
+ export { native } from './utils/native.js';
8
9
  export * from './utils/optimization.js';
9
10
  export * from './utils/reconstitution.js';
11
+ export { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
10
12
  export * from './utils/types.js';
11
13
  export * from './utils/zstd.js';
12
- export { decodeMinPng, encodeMinPng } from './minpng.js';
13
14
  export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
package/dist/index.js CHANGED
@@ -5,9 +5,10 @@ export * from './utils/encoder.js';
5
5
  export * from './utils/errors.js';
6
6
  export * from './utils/helpers.js';
7
7
  export * from './utils/inspection.js';
8
+ export { native } from './utils/native.js';
8
9
  export * from './utils/optimization.js';
9
10
  export * from './utils/reconstitution.js';
11
+ export { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
10
12
  export * from './utils/types.js';
11
13
  export * from './utils/zstd.js';
12
- export { decodeMinPng, encodeMinPng } from './minpng.js';
13
14
  export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
package/dist/rox.exe ADDED
Binary file
Binary file
Binary file
@@ -697,7 +697,6 @@ export async function decodePngToBinary(input, opts = {}) {
697
697
  throw new IncorrectPassphraseError(`Incorrect passphrase (screenshot mode, zstd failed: ` +
698
698
  errMsg +
699
699
  ')');
700
- // Fallback: try reconstituting the image and re-extracting the pixels
701
700
  try {
702
701
  if (process.env.ROX_DEBUG)
703
702
  console.log('DEBUG: decompress failed, attempting cropAndReconstitute fallback');
@@ -706,7 +705,6 @@ export async function decodePngToBinary(input, opts = {}) {
706
705
  let logicalData2 = Buffer.from(raw2.pixels);
707
706
  let logicalWidth2 = raw2.width;
708
707
  let logicalHeight2 = raw2.height;
709
- // find startIdx2 (linear)
710
708
  let startIdx2 = -1;
711
709
  const totalPixels2 = (logicalData2.length / 3) | 0;
712
710
  for (let i2 = 0; i2 <= totalPixels2 - MARKER_START.length; i2++) {
@@ -725,7 +723,6 @@ export async function decodePngToBinary(input, opts = {}) {
725
723
  }
726
724
  }
727
725
  if (startIdx2 === -1) {
728
- // try 2D scan
729
726
  let found2D2 = false;
730
727
  for (let y = 0; y < logicalHeight2 && !found2D2; y++) {
731
728
  for (let x = 0; x <= logicalWidth2 - MARKER_START.length && !found2D2; x++) {
@@ -740,7 +737,6 @@ export async function decodePngToBinary(input, opts = {}) {
740
737
  }
741
738
  }
742
739
  if (match) {
743
- // compute rectangle
744
740
  let endX = x + MARKER_START.length - 1;
745
741
  let endY = y;
746
742
  for (let scanY = y; scanY < logicalHeight2; scanY++) {
@@ -800,7 +796,6 @@ export async function decodePngToBinary(input, opts = {}) {
800
796
  if (!found2D2)
801
797
  throw new DataFormatError('Screenshot fallback failed: START not found');
802
798
  }
803
- // compute endStartPixel2
804
799
  const curTotalPixels2 = (logicalData2.length / 3) | 0;
805
800
  const lastLineStart2 = (logicalHeight2 - 1) * logicalWidth2;
806
801
  const endMarkerStartCol2 = logicalWidth2 - MARKER_END.length;
@@ -843,7 +838,6 @@ export async function decodePngToBinary(input, opts = {}) {
843
838
  pixelBytes2[dstOffset + 1] = logicalData2[srcOffset + 1];
844
839
  pixelBytes2[dstOffset + 2] = logicalData2[srcOffset + 2];
845
840
  }
846
- // try decompressing fallback payload
847
841
  const foundPX = pixelBytes2.indexOf(PIXEL_MAGIC);
848
842
  if (process.env.ROX_DEBUG)
849
843
  console.log('DEBUG: PIXEL_MAGIC index in fallback:', foundPX);
@@ -891,7 +885,6 @@ export async function decodePngToBinary(input, opts = {}) {
891
885
  throw new DataFormatError('Screenshot mode zstd decompression failed: ' + errMsg);
892
886
  }
893
887
  catch (e2) {
894
- // If fallback fails, rethrow original error
895
888
  throw new DataFormatError(`Screenshot mode zstd decompression failed: ` + errMsg);
896
889
  }
897
890
  }
@@ -17,7 +17,6 @@ import { EncodeOptions } from './types.js';
17
17
  * outputFormat: 'png',
18
18
  * });
19
19
  *
20
- * // write to disk using fs.writeFileSync('out.png', png)
21
- * ```
20
+ * * ```
22
21
  */
23
22
  export declare function encodeBinaryToPng(input: Buffer | Buffer[], opts?: EncodeOptions): Promise<Buffer>;
@@ -24,8 +24,7 @@ import { parallelZstdCompress } from './zstd.js';
24
24
  * outputFormat: 'png',
25
25
  * });
26
26
  *
27
- * // write to disk using fs.writeFileSync('out.png', png)
28
- * ```
27
+ * * ```
29
28
  */
30
29
  export async function encodeBinaryToPng(input, opts = {}) {
31
30
  let progressBar = null;
@@ -149,6 +148,48 @@ export async function encodeBinaryToPng(input, opts = {}) {
149
148
  const payloadTotalLen = payload.reduce((a, b) => a + b.length, 0);
150
149
  if (opts.onProgress)
151
150
  opts.onProgress({ phase: 'meta_prep_done', loaded: payloadTotalLen });
151
+ if (typeof native.nativeEncodePngWithNameAndFilelist === 'function' &&
152
+ opts.includeFileList &&
153
+ opts.fileList) {
154
+ const fileName = opts.name || undefined;
155
+ let sizeMap = null;
156
+ if (!Array.isArray(input)) {
157
+ try {
158
+ const unpack = unpackBuffer(input);
159
+ if (unpack) {
160
+ sizeMap = {};
161
+ for (const ef of unpack.files)
162
+ sizeMap[ef.path] = ef.buf.length;
163
+ }
164
+ }
165
+ catch (e) { }
166
+ }
167
+ const normalized = opts.fileList.map((f) => {
168
+ if (typeof f === 'string')
169
+ return { name: f, size: sizeMap && sizeMap[f] ? sizeMap[f] : 0 };
170
+ if (f && typeof f === 'object') {
171
+ if (f.name)
172
+ return { name: f.name, size: f.size ?? 0 };
173
+ if (f.path)
174
+ return { name: f.path, size: f.size ?? 0 };
175
+ }
176
+ return { name: String(f), size: 0 };
177
+ });
178
+ const fileListJson = JSON.stringify(normalized);
179
+ const flatPayload = Buffer.concat(payload);
180
+ if (opts.passphrase && opts.encrypt && opts.encrypt !== 'auto') {
181
+ const result = native.nativeEncodePngWithEncryptionNameAndFilelist(flatPayload, compressionLevel, opts.passphrase, opts.encrypt, fileName, fileListJson);
182
+ if (opts.onProgress)
183
+ opts.onProgress({ phase: 'done' });
184
+ return Buffer.from(result);
185
+ }
186
+ else {
187
+ const result = native.nativeEncodePngWithNameAndFilelist(flatPayload, compressionLevel, fileName, fileListJson);
188
+ if (opts.onProgress)
189
+ opts.onProgress({ phase: 'done' });
190
+ return Buffer.from(result);
191
+ }
192
+ }
152
193
  const metaParts = [];
153
194
  const includeName = opts.includeName === undefined ? true : !!opts.includeName;
154
195
  if (includeName && opts.name) {
@@ -23,6 +23,46 @@ import { cropAndReconstitute } from './reconstitution.js';
23
23
  export async function listFilesInPng(pngBuf, opts = {}) {
24
24
  try {
25
25
  const chunks = native.extractPngChunks(pngBuf);
26
+ const fileListChunk = chunks.find((c) => c.name === 'rXFL');
27
+ if (fileListChunk) {
28
+ const data = Buffer.from(fileListChunk.data);
29
+ const parsedFiles = JSON.parse(data.toString('utf8'));
30
+ if (parsedFiles.length > 0 &&
31
+ typeof parsedFiles[0] === 'object' &&
32
+ (parsedFiles[0].name || parsedFiles[0].path)) {
33
+ const objs = parsedFiles.map((p) => ({
34
+ name: p.name ?? p.path,
35
+ size: typeof p.size === 'number' ? p.size : 0,
36
+ }));
37
+ return objs.sort((a, b) => a.name.localeCompare(b.name));
38
+ }
39
+ const files = parsedFiles;
40
+ return files.sort();
41
+ }
42
+ const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
43
+ if (metaChunk) {
44
+ const dataBuf = Buffer.from(metaChunk.data);
45
+ const markerIdx = dataBuf.indexOf(Buffer.from('rXFL'));
46
+ if (markerIdx !== -1 && markerIdx + 8 <= dataBuf.length) {
47
+ const jsonLen = dataBuf.readUInt32BE(markerIdx + 4);
48
+ const jsonStart = markerIdx + 8;
49
+ const jsonEnd = jsonStart + jsonLen;
50
+ if (jsonEnd <= dataBuf.length) {
51
+ const parsedFiles = JSON.parse(dataBuf.slice(jsonStart, jsonEnd).toString('utf8'));
52
+ if (parsedFiles.length > 0 &&
53
+ typeof parsedFiles[0] === 'object' &&
54
+ (parsedFiles[0].name || parsedFiles[0].path)) {
55
+ const objs = parsedFiles.map((p) => ({
56
+ name: p.name ?? p.path,
57
+ size: typeof p.size === 'number' ? p.size : 0,
58
+ }));
59
+ return objs.sort((a, b) => a.name.localeCompare(b.name));
60
+ }
61
+ const files = parsedFiles;
62
+ return files.sort();
63
+ }
64
+ }
65
+ }
26
66
  const ihdr = chunks.find((c) => c.name === 'IHDR');
27
67
  const idatChunks = chunks.filter((c) => c.name === 'IDAT');
28
68
  if (ihdr && idatChunks.length > 0) {
@@ -101,23 +141,7 @@ export async function listFilesInPng(pngBuf, opts = {}) {
101
141
  return;
102
142
  }
103
143
  const names = parsedFiles;
104
- if (opts.includeSizes) {
105
- getFileSizesFromPng(pngBuf)
106
- .then((sizes) => {
107
- if (sizes) {
108
- resolve(names
109
- .map((f) => ({ name: f, size: sizes[f] ?? 0 }))
110
- .sort((a, b) => a.name.localeCompare(b.name)));
111
- }
112
- else {
113
- resolve(names.sort());
114
- }
115
- })
116
- .catch(() => resolve(names.sort()));
117
- }
118
- else {
119
- resolve(names.sort());
120
- }
144
+ resolve(names.sort());
121
145
  }
122
146
  catch (e) {
123
147
  resolved = true;
@@ -202,14 +226,6 @@ export async function listFilesInPng(pngBuf, opts = {}) {
202
226
  return objs.sort((a, b) => a.name.localeCompare(b.name));
203
227
  }
204
228
  const files = parsedFiles;
205
- if (opts.includeSizes) {
206
- const sizes = await getFileSizesFromPng(pngBuf);
207
- if (sizes) {
208
- return files
209
- .map((f) => ({ name: f, size: sizes[f] ?? 0 }))
210
- .sort((a, b) => a.name.localeCompare(b.name));
211
- }
212
- }
213
229
  return files.sort();
214
230
  }
215
231
  }
@@ -272,14 +288,6 @@ export async function listFilesInPng(pngBuf, opts = {}) {
272
288
  return objs.sort((a, b) => a.name.localeCompare(b.name));
273
289
  }
274
290
  const files = parsedFiles;
275
- if (opts.includeSizes) {
276
- const sizes = await getFileSizesFromPng(reconstructed);
277
- if (sizes) {
278
- return files
279
- .map((f) => ({ name: f, size: sizes[f] ?? 0 }))
280
- .sort((a, b) => a.name.localeCompare(b.name));
281
- }
282
- }
283
291
  return files.sort();
284
292
  }
285
293
  }
@@ -360,14 +368,6 @@ export async function listFilesInPng(pngBuf, opts = {}) {
360
368
  return objs.sort((a, b) => a.name.localeCompare(b.name));
361
369
  }
362
370
  const files = parsedFiles;
363
- if (opts.includeSizes) {
364
- const sizes = await getFileSizesFromPng(pngBuf);
365
- if (sizes) {
366
- return files
367
- .map((f) => ({ name: f, size: sizes[f] ?? 0 }))
368
- .sort((a, b) => a.name.localeCompare(b.name));
369
- }
370
- }
371
371
  return files.sort();
372
372
  }
373
373
  const metaChunk = chunks.find((c) => c.name === CHUNK_TYPE);
@@ -1,35 +1,21 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { createRequire } from 'module';
3
3
  import { arch, platform } from 'os';
4
- import { dirname, join, resolve } from 'path';
5
- import { fileURLToPath } from 'url';
4
+ import { join, resolve } from 'path';
6
5
  function getNativeModule() {
7
6
  let moduleDir;
8
7
  let nativeRequire;
9
8
  if (typeof __dirname !== 'undefined') {
10
- // Mode CJS - variables globales disponibles
11
9
  moduleDir = __dirname;
12
- // @ts-ignore
13
10
  nativeRequire = require;
14
11
  }
15
12
  else {
16
- // Try ESM import.meta.url first (may throw in CJS/bundled contexts), otherwise fallback to CWD
13
+ moduleDir = process.cwd();
17
14
  try {
18
- // @ts-ignore - import.meta.url exists in proper ESM contexts
19
- const __filename = fileURLToPath(import.meta.url);
20
- moduleDir = dirname(__filename);
21
- nativeRequire = createRequire(import.meta.url);
15
+ nativeRequire = require;
22
16
  }
23
17
  catch {
24
- // Fallback (bundled CJS without __dirname): use current working directory
25
- moduleDir = process.cwd();
26
- try {
27
- // @ts-ignore
28
- nativeRequire = require;
29
- }
30
- catch {
31
- nativeRequire = createRequire(process.cwd());
32
- }
18
+ nativeRequire = createRequire(process.cwd() + '/package.json');
33
19
  }
34
20
  }
35
21
  function getNativePath() {
@@ -53,10 +39,12 @@ function getNativeModule() {
53
39
  if (!target || !ext) {
54
40
  throw new Error(`Unsupported platform: ${currentPlatform}`);
55
41
  }
56
- const prebuiltPath = join(moduleDir, '../../libroxify_native.node');
57
- const bundlePath = join(moduleDir, '../libroxify_native.node');
58
- // compute repo root by walking up from moduleDir (fallback to process.cwd())
59
- // @ts-ignore
42
+ const prebuiltPath = join(moduleDir, '../../roxify_native.node');
43
+ const prebuiltLibPath = join(moduleDir, '../../libroxify_native.node');
44
+ const bundlePath = join(moduleDir, '../roxify_native.node');
45
+ const bundleLibPath = join(moduleDir, '../libroxify_native.node');
46
+ const bundlePathWithTarget = join(moduleDir, `../roxify_native-${target}.node`);
47
+ const bundleLibPathWithTarget = join(moduleDir, `../libroxify_native-${target}.node`);
60
48
  console.debug('[native] moduleDir', moduleDir);
61
49
  let root = moduleDir && moduleDir !== '.' ? moduleDir : process.cwd();
62
50
  while (root.length > 1 &&
@@ -67,48 +55,54 @@ function getNativeModule() {
67
55
  break;
68
56
  root = parent;
69
57
  }
70
- const localTargetRelease = resolve(root, `target/release/libroxify_native${ext === 'node' ? '.node' : `-${target}.${ext}`}`);
71
- const localReleaseGeneric = resolve(root, 'target/release/libroxify_native.so');
72
- const nodeModulesBase = resolve(root, 'node_modules/roxify');
73
- const nodeModulesTarget = resolve(nodeModulesBase, `libroxify_native-${target}.${ext}`);
74
- const nodeModulesGeneric = resolve(nodeModulesBase, ext === 'node' ? 'libroxify_native.node' : `libroxify_native.${ext}`);
75
- const bundleTarget = resolve(moduleDir, `../libroxify_native-${target}.${ext}`);
76
- const bundleGeneric = resolve(moduleDir, bundlePath);
77
- const candidates = [
78
- localTargetRelease,
79
- localReleaseGeneric,
80
- nodeModulesTarget,
81
- nodeModulesGeneric,
82
- bundleTarget,
83
- bundleGeneric,
84
- prebuiltPath,
85
- ];
86
- // use built-in fs.existsSync (static import to work in ESM and CJS)
58
+ const bundleNode = resolve(moduleDir, '../roxify_native.node');
59
+ const bundleLibNode = resolve(moduleDir, '../libroxify_native.node');
60
+ const bundleNodeWithTarget = resolve(moduleDir, `../roxify_native-${target}.node`);
61
+ const bundleLibNodeWithTarget = resolve(moduleDir, `../libroxify_native-${target}.node`);
62
+ const repoNode = resolve(root, 'roxify_native.node');
63
+ const repoLibNode = resolve(root, 'libroxify_native.node');
64
+ const repoNodeWithTarget = resolve(root, `roxify_native-${target}.node`);
65
+ const repoLibNodeWithTarget = resolve(root, `libroxify_native-${target}.node`);
66
+ const targetNode = resolve(root, 'target/release/roxify_native.node');
67
+ const targetSo = resolve(root, 'target/release/roxify_native.so');
68
+ const targetLibSo = resolve(root, 'target/release/libroxify_native.so');
69
+ const nodeModulesNode = resolve(root, 'node_modules/roxify/roxify_native.node');
70
+ const nodeModulesNodeWithTarget = resolve(root, `node_modules/roxify/roxify_native-${target}.node`);
71
+ const prebuiltNode = resolve(moduleDir, '../../roxify_native.node');
72
+ const prebuiltLibNode = resolve(moduleDir, '../../libroxify_native.node');
73
+ const prebuiltNodeWithTarget = resolve(moduleDir, `../../roxify_native-${target}.node`);
74
+ const prebuiltLibNodeWithTarget = resolve(moduleDir, `../../libroxify_native-${target}.node`);
75
+ // Support multiple possible OS triples (e.g. windows-gnu and windows-msvc)
76
+ const targets = targetAlt ? [target, targetAlt] : [target];
77
+ const candidates = [];
78
+ for (const t of targets) {
79
+ const bundleNodeWithT = resolve(moduleDir, `../roxify_native-${t}.node`);
80
+ const bundleLibNodeWithT = resolve(moduleDir, `../libroxify_native-${t}.node`);
81
+ const repoNodeWithT = resolve(root, `roxify_native-${t}.node`);
82
+ const repoLibNodeWithT = resolve(root, `libroxify_native-${t}.node`);
83
+ const nodeModulesNodeWithT = resolve(root, `node_modules/roxify/roxify_native-${t}.node`);
84
+ const prebuiltNodeWithT = resolve(moduleDir, `../../roxify_native-${t}.node`);
85
+ const prebuiltLibNodeWithT = resolve(moduleDir, `../../libroxify_native-${t}.node`);
86
+ candidates.push(bundleLibNodeWithT, bundleNodeWithT, repoLibNodeWithT, repoNodeWithT, nodeModulesNodeWithT, prebuiltLibNodeWithT, prebuiltNodeWithT);
87
+ }
88
+ candidates.push(bundleLibNode, bundleNode, repoLibNode, repoNode, targetNode, targetLibSo, targetSo, nodeModulesNode, prebuiltLibNode, prebuiltNode);
87
89
  for (const c of candidates) {
88
90
  try {
89
91
  if (!existsSync(c))
90
92
  continue;
91
- // If it's a .so (native build) but Node expects .node extension, create a .node symlink
92
93
  if (c.endsWith('.so')) {
93
94
  const nodeAlias = c.replace(/\.so$/, '.node');
94
95
  try {
95
96
  if (!existsSync(nodeAlias)) {
96
- // copy the .so to a .node so Node treats it as a native addon
97
- // @ts-ignore
98
97
  require('fs').copyFileSync(c, nodeAlias);
99
98
  }
100
- // debug
101
- // @ts-ignore
102
99
  console.debug('[native] using node alias', nodeAlias);
103
100
  return nodeAlias;
104
101
  }
105
102
  catch (e) {
106
- // fallback to original .so (might fail to load via require)
107
103
  return c;
108
104
  }
109
105
  }
110
- // debug
111
- // @ts-ignore
112
106
  console.debug('[native] using path', c);
113
107
  return c;
114
108
  }
@@ -1,2 +1,4 @@
1
+ declare function findRustBinary(): string | null;
2
+ export { findRustBinary };
1
3
  export declare function isRustBinaryAvailable(): boolean;
2
4
  export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string): Promise<void>;
@@ -1,21 +1,11 @@
1
- import { spawn } from 'child_process';
1
+ import { execSync, spawn } from 'child_process';
2
2
  import { existsSync } from 'fs';
3
3
  import { dirname, join } from 'path';
4
- import { fileURLToPath } from 'url';
5
4
  let moduleDir;
6
- try {
7
- // CJS bundlers may provide __dirname; prefer it when available
8
- if (typeof __dirname !== 'undefined') {
9
- // @ts-ignore
10
- moduleDir = __dirname;
11
- }
12
- else {
13
- // @ts-ignore - import.meta.url exists in ESM
14
- const __filename = fileURLToPath(import.meta.url);
15
- moduleDir = dirname(__filename);
16
- }
5
+ if (typeof __dirname !== 'undefined') {
6
+ moduleDir = __dirname;
17
7
  }
18
- catch {
8
+ else {
19
9
  moduleDir = process.cwd();
20
10
  }
21
11
  function findRustBinary() {
@@ -23,7 +13,85 @@ function findRustBinary() {
23
13
  ? ['roxify_native.exe', 'roxify-cli.exe', 'roxify_cli.exe']
24
14
  : ['roxify_native', 'roxify-cli', 'roxify_cli'];
25
15
  const baseDir = typeof moduleDir !== 'undefined' ? moduleDir : process.cwd();
26
- // Check immediate locations (for packaged CLI)
16
+ if (process.pkg) {
17
+ const snapshotPaths = [
18
+ join(baseDir, '..', '..', 'target', 'release'),
19
+ join(baseDir, '..', 'target', 'release'),
20
+ join(baseDir, 'target', 'release'),
21
+ ];
22
+ for (const basePath of snapshotPaths) {
23
+ for (const name of binNames) {
24
+ const binPath = join(basePath, name);
25
+ if (existsSync(binPath)) {
26
+ return binPath;
27
+ }
28
+ }
29
+ }
30
+ try {
31
+ const execDir = require('path').dirname(process.execPath || '');
32
+ if (execDir) {
33
+ const execCandidates = [
34
+ join(execDir, 'tools', 'roxify', 'dist'),
35
+ join(execDir, 'tools', 'roxify'),
36
+ join(execDir, '..', 'tools', 'roxify', 'dist'),
37
+ join(execDir, '..', 'tools', 'roxify'),
38
+ join(execDir, 'tools', 'roxify', 'roxify_native.exe'),
39
+ ];
40
+ for (const c of execCandidates) {
41
+ for (const name of binNames) {
42
+ const p = c.endsWith(name) ? c : join(c, name);
43
+ if (existsSync(p)) {
44
+ return p;
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ catch (e) { }
51
+ }
52
+ try {
53
+ let paths = [];
54
+ if (process.platform === 'win32') {
55
+ try {
56
+ const out = execSync('where rox', { encoding: 'utf-8' }).trim();
57
+ if (out)
58
+ paths = out
59
+ .split(/\r?\n/)
60
+ .map((s) => s.trim())
61
+ .filter(Boolean);
62
+ }
63
+ catch (e) { }
64
+ }
65
+ else {
66
+ try {
67
+ const out = execSync('which rox', { encoding: 'utf-8' }).trim();
68
+ if (out)
69
+ paths = [out.trim()];
70
+ }
71
+ catch (e) { }
72
+ }
73
+ for (const p of paths) {
74
+ try {
75
+ const d = dirname(p);
76
+ const candidates = [
77
+ d,
78
+ join(d, 'dist'),
79
+ join(d, '..', 'dist'),
80
+ join(d, '..'),
81
+ ];
82
+ for (const c of candidates) {
83
+ for (const name of binNames) {
84
+ const candidate = join(c, name);
85
+ if (existsSync(candidate)) {
86
+ return candidate;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ catch (e) { }
92
+ }
93
+ }
94
+ catch (e) { }
27
95
  for (const name of binNames) {
28
96
  const local = join(baseDir, name);
29
97
  if (existsSync(local)) {
@@ -33,8 +101,15 @@ function findRustBinary() {
33
101
  if (existsSync(parentLocal)) {
34
102
  return parentLocal;
35
103
  }
104
+ const parentParentLocal = join(baseDir, '..', '..', name);
105
+ if (existsSync(parentParentLocal)) {
106
+ return parentParentLocal;
107
+ }
108
+ const nodeModulesPath = join(baseDir, '..', '..', '..', '..', name);
109
+ if (existsSync(nodeModulesPath)) {
110
+ return nodeModulesPath;
111
+ }
36
112
  }
37
- // Check target/release (for development)
38
113
  const targetRelease = join(baseDir, '..', '..', 'target', 'release');
39
114
  for (const name of binNames) {
40
115
  const targetPath = join(targetRelease, name);
@@ -44,33 +119,100 @@ function findRustBinary() {
44
119
  }
45
120
  return null;
46
121
  }
122
+ export { findRustBinary };
47
123
  export function isRustBinaryAvailable() {
48
124
  return findRustBinary() !== null;
49
125
  }
126
+ import { chmodSync, mkdtempSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
127
+ import { tmpdir } from 'os';
50
128
  export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
51
129
  const cliPath = findRustBinary();
52
130
  if (!cliPath) {
53
131
  throw new Error('Rust CLI binary not found');
54
132
  }
133
+ function extractToTemp(pathToRead) {
134
+ const buf = readFileSync(pathToRead);
135
+ const tmp = mkdtempSync(join(tmpdir(), 'roxify-'));
136
+ const dest = join(tmp, pathToRead.replace(/.*[\\/]/, ''));
137
+ writeFileSync(dest, buf);
138
+ try {
139
+ chmodSync(dest, 0o755);
140
+ }
141
+ catch (e) { }
142
+ return dest;
143
+ }
55
144
  return new Promise((resolve, reject) => {
56
145
  const args = ['encode', '--level', String(compressionLevel)];
146
+ let supportsName = false;
57
147
  if (name) {
58
- args.push('--name', name);
148
+ try {
149
+ const helpOut = execSync(`"${cliPath}" --help`, {
150
+ encoding: 'utf8',
151
+ timeout: 2000,
152
+ });
153
+ if (helpOut && helpOut.includes('--name'))
154
+ supportsName = true;
155
+ }
156
+ catch (e) {
157
+ supportsName = false;
158
+ }
159
+ if (supportsName) {
160
+ args.push('--name', name);
161
+ }
59
162
  }
60
163
  if (passphrase) {
61
164
  args.push('--passphrase', passphrase);
62
165
  args.push('--encrypt', encryptType);
63
166
  }
64
167
  args.push(inputPath, outputPath);
65
- const proc = spawn(cliPath, args, { stdio: 'inherit' });
66
- proc.on('error', (err) => reject(err));
67
- proc.on('close', (code) => {
68
- if (code === 0) {
69
- resolve();
168
+ let triedExtract = false;
169
+ let tempExe;
170
+ const runSpawn = (exePath) => {
171
+ let proc;
172
+ try {
173
+ proc = spawn(exePath, args, { stdio: 'inherit' });
70
174
  }
71
- else {
72
- reject(new Error(`Rust encoder exited with status ${code}`));
175
+ catch (err) {
176
+ if (!triedExtract) {
177
+ triedExtract = true;
178
+ try {
179
+ tempExe = extractToTemp(cliPath);
180
+ }
181
+ catch (ex) {
182
+ return reject(ex);
183
+ }
184
+ return runSpawn(tempExe);
185
+ }
186
+ return reject(err);
73
187
  }
74
- });
188
+ proc.on('error', (err) => {
189
+ if (!triedExtract) {
190
+ triedExtract = true;
191
+ try {
192
+ tempExe = extractToTemp(cliPath);
193
+ }
194
+ catch (ex) {
195
+ return reject(ex);
196
+ }
197
+ return runSpawn(tempExe);
198
+ }
199
+ reject(err);
200
+ });
201
+ proc.on('close', (code) => {
202
+ if (tempExe) {
203
+ try {
204
+ unlinkSync(tempExe);
205
+ }
206
+ catch (e) { }
207
+ }
208
+ if (code === 0) {
209
+ resolve();
210
+ }
211
+ else {
212
+ reject(new Error(`Rust encoder exited with status ${code}`));
213
+ }
214
+ });
215
+ };
216
+ runSpawn(cliPath);
75
217
  });
76
218
  }
Binary file
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.5.11",
4
- "description": "Ultra-lightweight PNG steganography with native Rust acceleration. Encode binary data into PNG images with zstd compression.",
3
+ "version": "1.6.1",
5
4
  "type": "module",
5
+ "description": "Ultra-lightweight PNG steganography with native Rust acceleration. Encode binary data into PNG images with zstd compression.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
@@ -11,33 +11,63 @@
11
11
  },
12
12
  "files": [
13
13
  "dist",
14
+ "roxify_native.node",
14
15
  "libroxify_native.node",
15
- "libroxify_native-*.so",
16
- "libroxify_native-*.dylib",
17
- "libroxify_native-*.dll",
18
- "libroxify_native-x86_64-pc-windows-gnu.node",
19
- "libroxify_native-x86_64-pc-windows-msvc.node",
16
+ "roxify_native-x86_64-pc-windows-gnu.node",
17
+ "roxify_native-x86_64-pc-windows-msvc.node",
18
+ "roxify_native-x86_64-unknown-linux-gnu.node",
19
+ "libroxify_native-x86_64-unknown-linux-gnu.node",
20
20
  "README.md",
21
21
  "LICENSE"
22
22
  ],
23
23
  "scripts": {
24
24
  "build": "tsc",
25
+ "prebuild:pkg": "node scripts/copy-cli-binary.js",
25
26
  "build:native": "cargo build --release --lib",
26
- "build:native:linux": "cargo build --release --lib --target x86_64-unknown-linux-gnu && cp target/x86_64-unknown-linux-gnu/release/libroxify_native.so libroxify_native-x86_64-unknown-linux-gnu.so",
27
- "build:native:macos-x64": "cargo build --release --lib --target x86_64-apple-darwin && cp target/x86_64-apple-darwin/release/libroxify_native.dylib libroxify_native-x86_64-apple-darwin.dylib",
28
- "build:native:macos-arm": "cargo build --release --lib --target aarch64-apple-darwin && cp target/aarch64-apple-darwin/release/libroxify_native.dylib libroxify_native-aarch64-apple-darwin.dylib",
29
- "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",
27
+ "build:native:linux": "cargo build --release --lib --target x86_64-unknown-linux-gnu",
28
+ "build:native:macos-x64": "cargo build --release --lib --target x86_64-apple-darwin",
29
+ "build:native:macos-arm": "cargo build --release --lib --target aarch64-apple-darwin",
30
+ "build:native:windows": "cargo build --release --lib --target x86_64-pc-windows-msvc",
30
31
  "build:cli": "cargo build --release --bin roxify_native && cp target/release/roxify_native dist/roxify-cli",
32
+ "build:cli:windows": "cargo build --release --bin roxify_native --target x86_64-pc-windows-gnu && node scripts/copy-cli-binary.js",
31
33
  "build:all": "npm run build:native && npm run build && npm run build:cli",
32
34
  "build:cross": "node scripts/build-all-platforms.js && npm run build && npm run build:cli",
35
+ "build:native:fast": "cargo build -p roxify_native --release --lib --no-default-features",
36
+ "build:native:quick-release": "FAST_RELEASE=1 cargo build --profile fastdev -p roxify_native --lib --no-default-features",
37
+ "build:native:super-fast": "USE_SYSTEM_ZSTD=1 FAST_RELEASE=1 cargo build --profile fastdev -p roxify_native --lib --no-default-features -j 1",
38
+ "build:native:low-cpu": "LOW_CPU=1 USE_SYSTEM_ZSTD=1 FAST_RELEASE=1 MAX_JOBS=1 node scripts/build-native-targets.cjs",
39
+ "build:native:targets": "node scripts/build-native-targets.cjs",
40
+ "build:native:targets:fast": "FAST_RELEASE=1 node scripts/build-native-targets.cjs",
41
+ "build:pkg": "npm run build && npx pkg . --target node18-win-x64 --output dist/rox.exe --compress Brotli --public",
33
42
  "postbuild:native": "node scripts/copy-native.js",
34
- "prepublishOnly": "npm run build:cross",
35
- "test": "npm run build && node test/run-all-tests.js",
43
+ "clean:targets": "node scripts/clean-artifacts.js",
44
+ "release:prepare": "npm run build && npm run build:native:targets && node scripts/prepare-release.cjs",
45
+ "release:github": "npm run release:prepare && node scripts/create-gh-release.cjs",
46
+ "package:prepare": "npm run build && npm run build:native:targets && node scripts/pack-npm.cjs",
47
+ "publish:npm": "npm run package:prepare && echo 'Run npm publish --access public'",
48
+ "release:flow": "node scripts/release-flow.cjs",
49
+ "release:flow:auto": "AUTO_PUBLISH=1 node scripts/release-flow.cjs",
50
+ "publish": "node scripts/publish.cjs",
51
+ "prepublishOnly": "npm run build && npm run build:native && npm run postbuild:native && npm run clean:targets",
52
+ "test": "npm run build && node ./test/run-all-tests.cjs",
53
+ "test:integration": "node scripts/run-integration-tests.cjs",
36
54
  "cli": "node dist/cli.js"
37
55
  },
56
+ "pkg": {
57
+ "targets": [
58
+ "node18-win-x64"
59
+ ],
60
+ "outputPath": "dist",
61
+ "scripts": [
62
+ "dist/**/*.js"
63
+ ],
64
+ "assets": [
65
+ "dist/roxify_native.exe"
66
+ ]
67
+ },
38
68
  "repository": {
39
69
  "type": "git",
40
- "url": "https://github.com/yourusername/roxify.git"
70
+ "url": "https://github.com/RoxasYTB/roxify.git"
41
71
  },
42
72
  "keywords": [
43
73
  "steganography",
@@ -53,9 +83,9 @@
53
83
  ],
54
84
  "author": "",
55
85
  "license": "MIT",
56
- "dependencies": {},
57
86
  "devDependencies": {
58
87
  "@types/node": "^22.0.0",
88
+ "pkg": "^5.8.1",
59
89
  "typescript": "^5.6.0"
60
90
  },
61
91
  "engines": {
Binary file
package/dist/roxify-cli DELETED
Binary file