roxify 1.12.1 → 1.12.2

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/cli.js CHANGED
@@ -5,8 +5,8 @@ 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
- import { encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
9
- const VERSION = '1.12.0';
8
+ import { decodeWithRustCLI, encodeWithRustCLI, isRustBinaryAvailable, } from './utils/rust-cli-wrapper.js';
9
+ const VERSION = '1.12.2';
10
10
  function getDirectorySize(dirPath) {
11
11
  let totalSize = 0;
12
12
  try {
@@ -303,22 +303,35 @@ async function encodeCommand(args) {
303
303
  const startTime = Date.now();
304
304
  const encodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
305
305
  let barValue = 0;
306
- encodeBar.start(100, 0, { step: 'Encoding', elapsed: '0' });
307
- const progressInterval = setInterval(() => {
308
- barValue = Math.min(barValue + 1, 99);
306
+ encodeBar.start(100, 0, { step: 'Starting', elapsed: '0' });
307
+ const onProgress = (pct) => {
308
+ barValue = Math.max(barValue, pct);
309
309
  const elapsed = Math.floor((Date.now() - startTime) / 1000);
310
- encodeBar.update(barValue, {
311
- step: 'Encoding',
312
- elapsed: String(elapsed),
313
- });
314
- }, 500);
310
+ let step = 'Encoding';
311
+ if (pct < 30)
312
+ step = 'Packing files';
313
+ else if (pct < 80)
314
+ step = 'Compressing';
315
+ else if (pct < 100)
316
+ step = 'Writing PNG';
317
+ else
318
+ step = 'Done';
319
+ encodeBar.update(Math.min(barValue, 99), { step, elapsed: String(elapsed) });
320
+ };
321
+ const smoothInterval = setInterval(() => {
322
+ if (barValue < 99) {
323
+ barValue = Math.min(barValue + 1, 99);
324
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
325
+ encodeBar.update(barValue, { step: barValue < 30 ? 'Packing files' : barValue < 80 ? 'Compressing' : 'Writing PNG', elapsed: String(elapsed) });
326
+ }
327
+ }, 2000);
315
328
  const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
316
329
  const fileName = basename(inputPaths[0]);
317
- await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 19, parsed.passphrase, encryptType, fileName);
318
- clearInterval(progressInterval);
330
+ await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 3, parsed.passphrase, encryptType, fileName, onProgress);
331
+ clearInterval(smoothInterval);
319
332
  const encodeTime = Date.now() - startTime;
320
333
  encodeBar.update(100, {
321
- step: 'done',
334
+ step: 'Done',
322
335
  elapsed: String(Math.floor(encodeTime / 1000)),
323
336
  });
324
337
  encodeBar.stop();
@@ -360,28 +373,18 @@ async function encodeCommand(args) {
360
373
  const encodeBar = new cliProgress.SingleBar({
361
374
  format: ' {bar} {percentage}% | {step} | {elapsed}s',
362
375
  }, cliProgress.Presets.shades_classic);
363
- let barStarted = false;
364
376
  const startEncode = Date.now();
365
377
  let currentEncodeStep = 'Starting';
366
378
  let displayedPct = 0;
367
379
  let targetPct = 0;
368
380
  const TICK_MS = 100;
369
381
  const PCT_STEP = 1;
382
+ encodeBar.start(100, 0, { step: currentEncodeStep, elapsed: '0' });
370
383
  const encodeHeartbeat = setInterval(() => {
371
384
  const elapsed = Date.now() - startEncode;
372
- if (!barStarted) {
373
- encodeBar.start(100, Math.floor(displayedPct), {
374
- step: currentEncodeStep,
375
- elapsed: '0',
376
- });
377
- barStarted = true;
378
- }
379
385
  if (displayedPct < targetPct) {
380
386
  displayedPct = Math.min(displayedPct + PCT_STEP, targetPct);
381
387
  }
382
- else if (displayedPct < 99) {
383
- displayedPct = Math.min(displayedPct + PCT_STEP, 99);
384
- }
385
388
  encodeBar.update(Math.floor(displayedPct), {
386
389
  step: currentEncodeStep,
387
390
  elapsed: String(Math.floor(elapsed / 1000)),
@@ -538,13 +541,11 @@ async function encodeCommand(args) {
538
541
  const output = await encodeBinaryToPng(inputBuffer, options);
539
542
  const encodeTime = Date.now() - startEncode;
540
543
  clearInterval(encodeHeartbeat);
541
- if (barStarted) {
542
- encodeBar.update(100, {
543
- step: 'done',
544
- elapsed: String(Math.floor(encodeTime / 1000)),
545
- });
546
- encodeBar.stop();
547
- }
544
+ encodeBar.update(100, {
545
+ step: 'Done',
546
+ elapsed: String(Math.floor(encodeTime / 1000)),
547
+ });
548
+ encodeBar.stop();
548
549
  writeFileSync(resolvedOutput, output);
549
550
  const outputSize = (output.length / 1024 / 1024).toFixed(2);
550
551
  const inputSize = (inputSizeVal / 1024 / 1024).toFixed(2);
@@ -575,6 +576,42 @@ async function decodeCommand(args) {
575
576
  }
576
577
  const resolvedInput = resolve(inputPath);
577
578
  const resolvedOutput = parsed.output || outputPath || 'decoded.bin';
579
+ const inputFileSize = statSync(resolvedInput).size;
580
+ if (isRustBinaryAvailable() && !parsed.dict && inputFileSize > 10 * 1024 * 1024) {
581
+ try {
582
+ console.log(' ');
583
+ console.log(`Decoding... (Using native Rust decoder)\n`);
584
+ const decodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
585
+ const startDecode = Date.now();
586
+ let barValue = 0;
587
+ decodeBar.start(100, 0, { step: 'Reading PNG', elapsed: '0' });
588
+ const onProgress = (pct) => {
589
+ barValue = Math.max(barValue, pct);
590
+ const elapsed = Math.floor((Date.now() - startDecode) / 1000);
591
+ let step = 'Reading PNG';
592
+ if (pct >= 10 && pct < 60)
593
+ step = 'Decompressing';
594
+ else if (pct >= 60 && pct < 100)
595
+ step = 'Extracting files';
596
+ else if (pct >= 100)
597
+ step = 'Done';
598
+ decodeBar.update(Math.min(barValue, 99), { step, elapsed: String(elapsed) });
599
+ };
600
+ await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, onProgress);
601
+ const decodeTime = Date.now() - startDecode;
602
+ decodeBar.update(100, { step: 'Done', elapsed: String(Math.floor(decodeTime / 1000)) });
603
+ decodeBar.stop();
604
+ console.log(`\nSuccess!`);
605
+ console.log(` Time: ${decodeTime}ms`);
606
+ console.log(` Output: ${resolve(resolvedOutput)}`);
607
+ console.log(' ');
608
+ return;
609
+ }
610
+ catch (err) {
611
+ console.warn('\nRust decoder failed, falling back to TypeScript decoder...');
612
+ console.warn(`Reason: ${err.message}\n`);
613
+ }
614
+ }
578
615
  try {
579
616
  const options = {};
580
617
  if (parsed.passphrase) {
@@ -601,28 +638,19 @@ async function decodeCommand(args) {
601
638
  const decodeBar = new cliProgress.SingleBar({
602
639
  format: ' {bar} {percentage}% | {step} | {elapsed}s',
603
640
  }, cliProgress.Presets.shades_classic);
604
- let barStarted = false;
605
641
  const startDecode = Date.now();
606
642
  let currentPct = 0;
607
643
  let targetPct = 0;
608
- let currentStep = 'Decoding';
644
+ let currentStep = 'Reading PNG';
645
+ decodeBar.start(100, 0, { step: currentStep, elapsed: '0' });
609
646
  const heartbeat = setInterval(() => {
610
647
  if (currentPct < targetPct) {
611
648
  currentPct = Math.min(currentPct + 2, targetPct);
612
649
  }
613
- if (!barStarted && targetPct > 0) {
614
- decodeBar.start(100, Math.floor(currentPct), {
615
- step: currentStep,
616
- elapsed: String(Math.floor((Date.now() - startDecode) / 1000)),
617
- });
618
- barStarted = true;
619
- }
620
- else if (barStarted) {
621
- decodeBar.update(Math.floor(currentPct), {
622
- step: currentStep,
623
- elapsed: String(Math.floor((Date.now() - startDecode) / 1000)),
624
- });
625
- }
650
+ decodeBar.update(Math.floor(currentPct), {
651
+ step: currentStep,
652
+ elapsed: String(Math.floor((Date.now() - startDecode) / 1000)),
653
+ });
626
654
  }, 100);
627
655
  options.onProgress = (info) => {
628
656
  if (info.phase === 'decompress_start') {
@@ -633,7 +661,7 @@ async function decodeCommand(args) {
633
661
  info.loaded &&
634
662
  info.total) {
635
663
  targetPct = 50 + Math.floor((info.loaded / info.total) * 40);
636
- currentStep = `Decompressing (${info.loaded}/${info.total})`;
664
+ currentStep = 'Decompressing';
637
665
  }
638
666
  else if (info.phase === 'decompress_done') {
639
667
  targetPct = 90;
@@ -648,14 +676,12 @@ async function decodeCommand(args) {
648
676
  const result = await decodePngToBinary(inputBuffer, options);
649
677
  const decodeTime = Date.now() - startDecode;
650
678
  clearInterval(heartbeat);
651
- if (barStarted) {
652
- currentPct = 100;
653
- decodeBar.update(100, {
654
- step: 'done',
655
- elapsed: String(Math.floor(decodeTime / 1000)),
656
- });
657
- decodeBar.stop();
658
- }
679
+ currentPct = 100;
680
+ decodeBar.update(100, {
681
+ step: 'Done',
682
+ elapsed: String(Math.floor(decodeTime / 1000)),
683
+ });
684
+ decodeBar.stop();
659
685
  if (result.files) {
660
686
  const baseDir = parsed.output || outputPath || '.';
661
687
  const totalBytes = result.files.reduce((s, f) => s + f.buf.length, 0);
Binary file
@@ -1,9 +1,14 @@
1
+ import cliProgress from 'cli-progress';
1
2
  export declare class SingleBar {
2
- constructor(...args: any[]);
3
- start(...args: any[]): void;
4
- update(...args: any[]): void;
5
- stop(...args: any[]): void;
3
+ private bar;
4
+ constructor(opts?: any, preset?: any);
5
+ start(total: number, startValue: number, payload?: any): void;
6
+ update(value: number, payload?: any): void;
7
+ stop(): void;
6
8
  }
7
9
  export declare const Presets: {
8
- shades_classic: {};
10
+ legacy: cliProgress.Preset;
11
+ rect: cliProgress.Preset;
12
+ shades_classic: cliProgress.Preset;
13
+ shades_grey: cliProgress.Preset;
9
14
  };
@@ -1,9 +1,22 @@
1
+ import cliProgress from 'cli-progress';
1
2
  export class SingleBar {
2
- constructor(...args) { }
3
- start(...args) { }
4
- update(...args) { }
5
- stop(...args) { }
3
+ constructor(opts, preset) {
4
+ this.bar = new cliProgress.SingleBar({
5
+ ...opts,
6
+ hideCursor: true,
7
+ forceRedraw: true,
8
+ barCompleteChar: '\u2588',
9
+ barIncompleteChar: '\u2591',
10
+ }, preset || cliProgress.Presets.shades_classic);
11
+ }
12
+ start(total, startValue, payload) {
13
+ this.bar.start(total, startValue, payload);
14
+ }
15
+ update(value, payload) {
16
+ this.bar.update(value, payload);
17
+ }
18
+ stop() {
19
+ this.bar.stop();
20
+ }
6
21
  }
7
- export const Presets = {
8
- shades_classic: {},
9
- };
22
+ export const Presets = cliProgress.Presets;
@@ -1,4 +1,7 @@
1
1
  declare function findRustBinary(): string | null;
2
2
  export { findRustBinary };
3
3
  export declare function isRustBinaryAvailable(): boolean;
4
- export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string): Promise<void>;
4
+ export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string, onProgress?: (pct: number) => void): Promise<void>;
5
+ export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], onProgress?: (pct: number) => void): Promise<{
6
+ usedRust: boolean;
7
+ }>;
@@ -128,7 +128,7 @@ export function isRustBinaryAvailable() {
128
128
  }
129
129
  import { chmodSync, mkdtempSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
130
130
  import { tmpdir } from 'os';
131
- export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
131
+ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name, onProgress) {
132
132
  const cliPath = findRustBinary();
133
133
  if (!cliPath) {
134
134
  throw new Error('Rust CLI binary not found');
@@ -145,7 +145,7 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
145
145
  return dest;
146
146
  }
147
147
  return new Promise((resolve, reject) => {
148
- const args = ['encode', '--level', String(compressionLevel)];
148
+ const args = ['encode', '--level', String(compressionLevel), '--progress'];
149
149
  let supportsName = false;
150
150
  if (name) {
151
151
  try {
@@ -173,7 +173,9 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
173
173
  const runSpawn = (exePath) => {
174
174
  let proc;
175
175
  try {
176
- proc = spawn(exePath, args, { stdio: 'inherit' });
176
+ proc = spawn(exePath, args, {
177
+ stdio: ['inherit', 'inherit', 'pipe'],
178
+ });
177
179
  }
178
180
  catch (err) {
179
181
  if (!triedExtract) {
@@ -188,6 +190,20 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
188
190
  }
189
191
  return reject(err);
190
192
  }
193
+ if (proc.stderr && onProgress) {
194
+ let stderrBuf = '';
195
+ proc.stderr.on('data', (chunk) => {
196
+ stderrBuf += chunk.toString();
197
+ const lines = stderrBuf.split('\n');
198
+ stderrBuf = lines.pop() || '';
199
+ for (const line of lines) {
200
+ const match = line.match(/PROGRESS:(\d+)/);
201
+ if (match) {
202
+ onProgress(parseInt(match[1], 10));
203
+ }
204
+ }
205
+ });
206
+ }
191
207
  proc.on('error', (err) => {
192
208
  if (!triedExtract) {
193
209
  triedExtract = true;
@@ -219,3 +235,53 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
219
235
  runSpawn(cliPath);
220
236
  });
221
237
  }
238
+ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, onProgress) {
239
+ const cliPath = findRustBinary();
240
+ if (!cliPath) {
241
+ throw new Error('Rust CLI binary not found');
242
+ }
243
+ return new Promise((resolve, reject) => {
244
+ const args = ['decompress', '--progress'];
245
+ if (passphrase) {
246
+ args.push('--passphrase', passphrase);
247
+ }
248
+ if (files && files.length > 0) {
249
+ args.push('--files', JSON.stringify(files));
250
+ }
251
+ args.push(inputPath, outputPath);
252
+ const proc = spawn(cliPath, args, {
253
+ stdio: ['inherit', 'pipe', 'pipe'],
254
+ });
255
+ let stdout = '';
256
+ if (proc.stdout) {
257
+ proc.stdout.on('data', (chunk) => {
258
+ const text = chunk.toString();
259
+ stdout += text;
260
+ process.stdout.write(text);
261
+ });
262
+ }
263
+ if (proc.stderr && onProgress) {
264
+ let stderrBuf = '';
265
+ proc.stderr.on('data', (chunk) => {
266
+ stderrBuf += chunk.toString();
267
+ const lines = stderrBuf.split('\n');
268
+ stderrBuf = lines.pop() || '';
269
+ for (const line of lines) {
270
+ const match = line.match(/PROGRESS:(\d+)/);
271
+ if (match) {
272
+ onProgress(parseInt(match[1], 10));
273
+ }
274
+ }
275
+ });
276
+ }
277
+ proc.on('error', (err) => reject(err));
278
+ proc.on('close', (code) => {
279
+ if (code === 0) {
280
+ resolve({ usedRust: true });
281
+ }
282
+ else {
283
+ reject(new Error(`Rust decoder exited with status ${code}`));
284
+ }
285
+ });
286
+ });
287
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.12.1",
3
+ "version": "1.12.2",
4
4
  "type": "module",
5
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",
@@ -99,11 +99,15 @@
99
99
  "author": "",
100
100
  "license": "SEE LICENSE IN LICENSE",
101
101
  "devDependencies": {
102
+ "@types/cli-progress": "^3.11.6",
102
103
  "@types/node": "^22.0.0",
103
104
  "pkg": "^5.8.1",
104
105
  "typescript": "^5.6.0"
105
106
  },
106
107
  "engines": {
107
108
  "node": ">=18.0.0"
109
+ },
110
+ "dependencies": {
111
+ "cli-progress": "^3.12.0"
108
112
  }
109
113
  }