roxify 1.14.1 → 1.14.3

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
@@ -3,7 +3,7 @@ import { 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 * as cliProgress from './stub-progress.js';
6
- import { encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI, } from './utils/rust-cli-wrapper.js';
6
+ import { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI, } from './utils/rust-cli-wrapper.js';
7
7
  async function loadJsEngine() {
8
8
  const indexMod = await import('./index.js');
9
9
  const packMod = await import('./pack.js');
@@ -20,7 +20,7 @@ async function loadJsEngine() {
20
20
  VFSIndexEntry: undefined,
21
21
  };
22
22
  }
23
- const VERSION = '1.14.0';
23
+ const VERSION = '1.14.3';
24
24
  function getDirectorySize(dirPath) {
25
25
  let totalSize = 0;
26
26
  try {
@@ -462,6 +462,38 @@ async function encodeCommand(args) {
462
462
  else {
463
463
  const resolvedInput = resolvedInputs[0];
464
464
  const st = statSync(resolvedInput);
465
+ // Calculate total size for deciding whether to use Rust CLI streaming
466
+ const totalInputSize = st.isDirectory()
467
+ ? getDirectorySize(resolvedInput)
468
+ : st.size;
469
+ // Use Rust CLI for large files (> 1GB) to avoid Buffer.concat 4GB limit
470
+ const STREAMING_THRESHOLD = 1024 * 1024 * 1024; // 1GB
471
+ if (totalInputSize > STREAMING_THRESHOLD && isRustBinaryAvailable()) {
472
+ console.log('Using native Rust encoder (streaming for large payload)\n');
473
+ const encodeStart = Date.now();
474
+ let lastPct = 0;
475
+ await encodeWithRustCLI(resolvedInput, resolvedOutput, parsed.level ? Number(parsed.level) : 6, parsed.passphrase, parsed.encrypt || 'aes', parsed.outputName || (st.isDirectory() ? basename(resolvedInput) : undefined), undefined, // ramBudgetMb
476
+ (current, total, step) => {
477
+ const pct = Math.floor((current / total) * 100);
478
+ if (pct !== lastPct) {
479
+ lastPct = pct;
480
+ if (barStarted) {
481
+ encodeBar.update(pct, { step });
482
+ }
483
+ }
484
+ });
485
+ const encodeTime = Date.now() - encodeStart;
486
+ if (barStarted) {
487
+ encodeBar.update(100, { step: 'done', elapsed: String(Math.floor(encodeTime / 1000)) });
488
+ encodeBar.stop();
489
+ }
490
+ console.log(`\nSuccess!`);
491
+ console.log(` Input: ${(totalInputSize / 1024 / 1024).toFixed(2)} MB`);
492
+ console.log(` Time: ${encodeTime}ms`);
493
+ console.log(` Saved: ${resolvedOutput}`);
494
+ console.log(' ');
495
+ return;
496
+ }
465
497
  if (st.isDirectory()) {
466
498
  currentEncodeStep = 'Reading files';
467
499
  const { index, stream, totalSize } = await js.packPathsGenerator([resolvedInput], dirname(resolvedInput), onProgress);
@@ -591,33 +623,24 @@ async function decodeCommand(args) {
591
623
  }
592
624
  const resolvedInput = resolve(inputPath);
593
625
  const resolvedOutput = parsed.output || outputPath || '.';
626
+ if (!isRustBinaryAvailable()) {
627
+ console.error('Error: Rust decoder binary not found');
628
+ process.exit(1);
629
+ }
594
630
  try {
595
631
  console.log(' ');
596
632
  console.log('Decoding... (Using native Rust decoder)\n');
597
633
  const startTime = Date.now();
598
634
  const decodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
599
635
  decodeBar.start(100, 0, { step: 'Decoding', elapsed: '0' });
600
- const js = await loadJsEngine();
601
- const result = await js.decodePngToBinary(readFileSync(resolvedInput));
602
- if (result.files && result.files.length > 0) {
603
- const outputDir = resolve(resolvedOutput);
604
- for (const file of result.files) {
605
- const dest = join(outputDir, file.path);
606
- const parent = dirname(dest);
607
- try {
608
- await import('fs/promises').then(({ mkdir }) => mkdir(parent, { recursive: true }));
609
- }
610
- catch { }
611
- writeFileSync(dest, file.buf);
612
- }
613
- }
614
- else if (result.buf) {
615
- const outputFile = resolvedOutput === '.' ? join(process.cwd(), result.meta?.name || basename(resolvedInput).replace(/\.[^.]+$/, '')) : resolvedOutput;
616
- writeFileSync(outputFile, result.buf);
617
- }
618
- else {
619
- throw new Error('Decoded result is empty');
620
- }
636
+ await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, parsed.dict, parsed.ramBudgetMb, (current, total, step) => {
637
+ const pct = total > 0 ? Math.floor((current / total) * 100) : 0;
638
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
639
+ decodeBar.update(Math.min(pct, 99), {
640
+ step: step || 'Decoding',
641
+ elapsed: String(elapsed),
642
+ });
643
+ });
621
644
  const decodeTime = Date.now() - startTime;
622
645
  decodeBar.update(100, { step: 'done', elapsed: String(Math.floor(decodeTime / 1000)) });
623
646
  decodeBar.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.14.1",
3
+ "version": "1.14.3",
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",
@@ -12,11 +12,8 @@
12
12
  "files": [
13
13
  "dist/**/*.js",
14
14
  "dist/**/*.d.ts",
15
- "dist/roxify_native*",
16
- "dist/rox-macos-universal",
17
15
  "scripts/postinstall.cjs",
18
- "roxify_native-*.node",
19
- "libroxify_native-*.node",
16
+ "scripts/download-binary.cjs",
20
17
  "README.md",
21
18
  "LICENSE"
22
19
  ],
@@ -96,4 +93,4 @@
96
93
  "node": ">=18.0.0"
97
94
  },
98
95
  "dependencies": {}
99
- }
96
+ }
@@ -0,0 +1,259 @@
1
+ const https = require('https');
2
+ const { createWriteStream, existsSync, mkdirSync, chmodSync, unlinkSync } = require('fs');
3
+ const { join } = require('path');
4
+ const { platform, arch } = require('os');
5
+
6
+ const GITHUB_REPO = 'RoxasYTB/roxify';
7
+ const DIST_DIR = join(__dirname, '..', 'dist');
8
+ const root = join(__dirname, '..');
9
+
10
+ function getPlatformBinary() {
11
+ const os = platform();
12
+ const cpu = arch();
13
+
14
+ const map = {
15
+ linux: { x64: 'roxify_native', arm64: 'roxify_native' },
16
+ win32: { x64: 'roxify_native.exe', arm64: 'roxify_native.exe' },
17
+ darwin: { x64: 'rox-macos-universal', arm64: 'rox-macos-universal' },
18
+ };
19
+
20
+ return (map[os] && map[os][cpu]) || null;
21
+ }
22
+
23
+ function getReleaseAssetName(version) {
24
+ const os = platform();
25
+ const cpu = arch();
26
+
27
+ // Map platform/arch to release asset names as uploaded by GitHub Actions
28
+ const map = {
29
+ linux: {
30
+ x64: `roxify_native-x86_64-unknown-linux-gnu`,
31
+ arm64: `roxify_native-aarch64-unknown-linux-gnu`
32
+ },
33
+ win32: {
34
+ x64: `roxify_native-x86_64-pc-windows-msvc.exe`,
35
+ arm64: `roxify_native-aarch64-pc-windows-msvc.exe`
36
+ },
37
+ darwin: {
38
+ x64: `rox-macos-universal`,
39
+ arm64: `rox-macos-universal`
40
+ },
41
+ };
42
+
43
+ return (map[os] && map[os][cpu]) || null;
44
+ }
45
+
46
+ function getNativeLibAssetName(version) {
47
+ const os = platform();
48
+ const cpu = arch();
49
+
50
+ const triples = {
51
+ linux: { x64: 'x86_64-unknown-linux-gnu', arm64: 'aarch64-unknown-linux-gnu' },
52
+ win32: { x64: 'x86_64-pc-windows-msvc', arm64: 'aarch64-pc-windows-msvc' },
53
+ darwin: { x64: 'x86_64-apple-darwin', arm64: 'aarch64-apple-darwin' },
54
+ };
55
+
56
+ const triple = triples[os] && triples[os][cpu];
57
+ if (!triple) return null;
58
+
59
+ return `roxify_native-${triple}.node`;
60
+ }
61
+
62
+ function downloadFile(url, dest, maxRedirects = 5) {
63
+ return new Promise((resolve, reject) => {
64
+ if (maxRedirects <= 0) {
65
+ reject(new Error('Too many redirects'));
66
+ return;
67
+ }
68
+
69
+ const file = createWriteStream(dest);
70
+ const request = https.get(url, { timeout: 60000 }, (response) => {
71
+ // Handle redirects
72
+ if (response.statusCode === 302 || response.statusCode === 301) {
73
+ const location = response.headers.location;
74
+ if (location) {
75
+ file.close();
76
+ // Clean up partial file
77
+ try { unlinkSync(dest); } catch { }
78
+ downloadFile(location, dest, maxRedirects - 1).then(resolve).catch(reject);
79
+ return;
80
+ }
81
+ }
82
+
83
+ if (response.statusCode !== 200) {
84
+ file.close();
85
+ try { unlinkSync(dest); } catch { }
86
+ reject(new Error(`HTTP ${response.statusCode} for ${url}`));
87
+ return;
88
+ }
89
+
90
+ let downloaded = 0;
91
+ response.on('data', (chunk) => {
92
+ downloaded += chunk.length;
93
+ });
94
+
95
+ response.pipe(file);
96
+
97
+ file.on('finish', () => {
98
+ file.close();
99
+ console.log(`roxify: Downloaded ${(downloaded / 1024 / 1024).toFixed(2)} MB`);
100
+ resolve();
101
+ });
102
+ });
103
+
104
+ request.on('error', (err) => {
105
+ file.close();
106
+ try { unlinkSync(dest); } catch { }
107
+ reject(err);
108
+ });
109
+
110
+ request.on('timeout', () => {
111
+ request.destroy();
112
+ file.close();
113
+ try { unlinkSync(dest); } catch { }
114
+ reject(new Error('Request timeout'));
115
+ });
116
+ });
117
+ }
118
+
119
+ async function getLatestReleaseVersion() {
120
+ const url = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
121
+
122
+ return new Promise((resolve, reject) => {
123
+ https.get(url, {
124
+ headers: {
125
+ 'User-Agent': 'roxify-install',
126
+ 'Accept': 'application/vnd.github.v3+json'
127
+ },
128
+ timeout: 10000
129
+ }, (response) => {
130
+ if (response.statusCode !== 200) {
131
+ reject(new Error(`GitHub API HTTP ${response.statusCode}`));
132
+ return;
133
+ }
134
+
135
+ let data = '';
136
+ response.on('data', chunk => data += chunk);
137
+ response.on('end', () => {
138
+ try {
139
+ const release = JSON.parse(data);
140
+ const version = release.tag_name.replace(/^v/, '');
141
+ resolve(version);
142
+ } catch (e) {
143
+ reject(new Error('Failed to parse GitHub API response'));
144
+ }
145
+ });
146
+ }).on('error', reject).on('timeout', function () { this.destroy(); reject(new Error('API timeout')); });
147
+ });
148
+ }
149
+
150
+ async function downloadBinary() {
151
+ let version;
152
+ try {
153
+ version = require('../package.json').version;
154
+ } catch (e) {
155
+ version = '1.14.2';
156
+ }
157
+
158
+ // Try to get latest release version from GitHub
159
+ try {
160
+ const latestVersion = await getLatestReleaseVersion();
161
+ if (latestVersion && latestVersion !== version) {
162
+ console.log(`roxify: Latest release is v${latestVersion} (package is v${version})`);
163
+ version = latestVersion;
164
+ }
165
+ } catch (e) {
166
+ console.log(`roxify: Using package version v${version} (could not check latest: ${e.message})`);
167
+ }
168
+
169
+ const binaryName = getPlatformBinary();
170
+ const assetName = getReleaseAssetName(version);
171
+
172
+ if (!binaryName || !assetName) {
173
+ console.log(`roxify: Unsupported platform ${platform()}/${arch()}`);
174
+ return false;
175
+ }
176
+
177
+ if (!existsSync(DIST_DIR)) {
178
+ mkdirSync(DIST_DIR, { recursive: true });
179
+ }
180
+
181
+ const destPath = join(DIST_DIR, binaryName);
182
+
183
+ // Check if already exists
184
+ if (existsSync(destPath)) {
185
+ console.log(`roxify: CLI binary already exists at ${destPath}`);
186
+ return true;
187
+ }
188
+
189
+ const url = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${assetName}`;
190
+
191
+ console.log(`roxify: Downloading ${assetName} from GitHub...`);
192
+ console.log(`roxify: URL: ${url}`);
193
+
194
+ try {
195
+ await downloadFile(url, destPath);
196
+
197
+ if (platform() !== 'win32') {
198
+ try { chmodSync(destPath, 0o755); } catch { }
199
+ }
200
+
201
+ console.log(`roxify: CLI binary ready at ${destPath}`);
202
+ return true;
203
+ } catch (e) {
204
+ console.log(`roxify: Download failed: ${e.message}`);
205
+ // Clean up partial download
206
+ try { unlinkSync(destPath); } catch { }
207
+ return false;
208
+ }
209
+ }
210
+
211
+ async function downloadNativeLib() {
212
+ let version;
213
+ try {
214
+ version = require('../package.json').version;
215
+ } catch (e) {
216
+ version = '1.14.2';
217
+ }
218
+
219
+ // Try to get latest release version from GitHub
220
+ try {
221
+ const latestVersion = await getLatestReleaseVersion();
222
+ if (latestVersion && latestVersion !== version) {
223
+ version = latestVersion;
224
+ }
225
+ } catch (e) {
226
+ // Use package version
227
+ }
228
+
229
+ const assetName = getNativeLibAssetName(version);
230
+
231
+ if (!assetName) {
232
+ console.log(`roxify: Unsupported platform for native lib ${platform()}/${arch()}`);
233
+ return false;
234
+ }
235
+
236
+ const destPath = join(root, assetName);
237
+
238
+ // Check if already exists
239
+ if (existsSync(destPath)) {
240
+ console.log(`roxify: Native lib already exists at ${destPath}`);
241
+ return true;
242
+ }
243
+
244
+ const url = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${assetName}`;
245
+
246
+ console.log(`roxify: Downloading native lib ${assetName}...`);
247
+
248
+ try {
249
+ await downloadFile(url, destPath);
250
+ console.log(`roxify: Native lib ready at ${destPath}`);
251
+ return true;
252
+ } catch (e) {
253
+ console.log(`roxify: Native lib download failed: ${e.message}`);
254
+ try { unlinkSync(destPath); } catch { }
255
+ return false;
256
+ }
257
+ }
258
+
259
+ module.exports = { downloadBinary, downloadNativeLib, getPlatformBinary, getLatestReleaseVersion };
@@ -1,11 +1,20 @@
1
1
  const { execSync } = require('child_process');
2
- const { existsSync, copyFileSync, mkdirSync, readdirSync, unlinkSync } = require('fs');
2
+ const { existsSync, copyFileSync, mkdirSync, readdirSync, unlinkSync, chmodSync } = require('fs');
3
3
  const { join } = require('path');
4
4
  const { platform, arch } = require('os');
5
5
 
6
6
  const root = join(__dirname, '..');
7
7
  const distDir = join(root, 'dist');
8
8
 
9
+ // Try to import download helper
10
+ try {
11
+ var { downloadBinary, downloadNativeLib } = require('./download-binary.cjs');
12
+ } catch (e) {
13
+ console.log('roxify: Download helper not available, will use local build only');
14
+ var downloadBinary = null;
15
+ var downloadNativeLib = null;
16
+ }
17
+
9
18
  function hasCargo() {
10
19
  try {
11
20
  execSync('cargo --version', { stdio: 'ignore', timeout: 5000 });
@@ -18,165 +27,182 @@ function getTriples() {
18
27
  const cpu = arch();
19
28
  const map = {
20
29
  linux: { x64: ['x86_64-unknown-linux-gnu'], arm64: ['aarch64-unknown-linux-gnu'] },
21
- win32: { x64: ['x86_64-pc-windows-msvc', 'x86_64-pc-windows-gnu'], arm64: ['aarch64-pc-windows-msvc'] },
30
+ win32: { x64: ['x86_64-pc-windows-msvc'], arm64: ['aarch64-pc-windows-msvc'] },
22
31
  darwin: { x64: ['x86_64-apple-darwin'], arm64: ['aarch64-apple-darwin'] },
23
32
  };
24
33
  return (map[os] && map[os][cpu]) || [];
25
34
  }
26
35
 
27
- function getLibExt() {
28
- if (platform() === 'win32') return 'dll';
29
- if (platform() === 'darwin') return 'dylib';
30
- return 'so';
31
- }
32
-
33
36
  function getBinaryName() {
34
37
  return platform() === 'win32' ? 'roxify_native.exe' : 'roxify_native';
35
38
  }
36
39
 
37
- function findExistingBinary() {
38
- const name = getBinaryName();
39
- const triples = getTriples();
40
- const candidates = [join(distDir, name), join(root, 'target', 'release', name)];
41
- for (const t of triples) {
42
- candidates.push(join(root, 'target', t, 'release', name));
43
- }
44
- for (const c of candidates) {
45
- if (existsSync(c)) return c;
46
- }
47
- return null;
40
+ function getMacosBinaryName() {
41
+ return 'rox-macos-universal';
48
42
  }
49
43
 
50
- function findExistingNativeLib() {
51
- const triples = getTriples();
52
- const ext = getLibExt();
53
- const prefix = platform() === 'win32' ? '' : 'lib';
54
- for (const t of triples) {
55
- const specific = join(root, `roxify_native-${t}.node`);
56
- if (existsSync(specific)) return { path: specific, triple: t };
57
- }
58
- for (const t of triples) {
59
- for (const profile of ['release', 'fastdev']) {
60
- const paths = [
61
- join(root, 'target', t, profile, `${prefix}roxify_native.${ext}`),
62
- join(root, 'target', profile, `${prefix}roxify_native.${ext}`),
63
- ];
64
- for (const p of paths) {
65
- if (existsSync(p)) return { path: p, triple: t };
66
- }
67
- }
68
- }
69
- return null;
70
- }
71
-
72
- function buildNative(target) {
73
- if (!hasCargo()) return false;
74
- const args = target ? ` --target ${target}` : '';
75
- console.log(`roxify: Building native lib${args}...`);
76
- try {
77
- execSync(`cargo build --release --lib${args}`, { cwd: root, stdio: 'inherit', timeout: 600000 });
78
- return true;
79
- } catch {
80
- console.log('roxify: Native lib build failed');
81
- return false;
82
- }
83
- }
84
-
85
- function buildBinary() {
44
+ function buildBinaryLocally() {
86
45
  if (!hasCargo()) {
87
- console.log('roxify: Cargo not found, skipping native build (TypeScript fallback will be used)');
46
+ console.log('roxify: Cargo not found, will try to download prebuilt binary');
88
47
  return false;
89
48
  }
90
- console.log('roxify: Building native CLI binary...');
49
+ console.log('roxify: Building native CLI binary locally...');
91
50
  try {
92
51
  execSync('cargo build --release --bin roxify_native', { cwd: root, stdio: 'inherit', timeout: 600000 });
93
52
  return true;
94
- } catch {
95
- console.log('roxify: Native build failed, TypeScript fallback will be used');
53
+ } catch (e) {
54
+ console.log('roxify: Local build failed, will try to download prebuilt binary');
96
55
  return false;
97
56
  }
98
57
  }
99
58
 
100
- function ensureCliBinary() {
59
+ async function ensureCliBinary() {
101
60
  if (!existsSync(distDir)) mkdirSync(distDir, { recursive: true });
102
- const dest = join(distDir, getBinaryName());
103
- if (existsSync(dest)) return;
104
61
 
105
- const existing = findExistingBinary();
106
- if (existing && existing !== dest) {
107
- copyFileSync(existing, dest);
62
+ const binaryName = platform() === 'darwin' ? getMacosBinaryName() : getBinaryName();
63
+ const dest = join(distDir, binaryName);
64
+
65
+ if (existsSync(dest)) {
66
+ console.log(`roxify: CLI binary already exists at ${dest}`);
67
+ return true;
68
+ }
69
+
70
+ // Check if built locally exists
71
+ const localBuilt = join(root, 'target', 'release', getBinaryName());
72
+ if (existsSync(localBuilt)) {
73
+ copyFileSync(localBuilt, dest);
108
74
  if (platform() !== 'win32') {
109
- try { require('fs').chmodSync(dest, 0o755); } catch { }
75
+ try { chmodSync(dest, 0o755); } catch { }
110
76
  }
111
- console.log(`roxify: Copied CLI binary from ${existing}`);
112
- return;
77
+ console.log(`roxify: CLI binary copied from local build`);
78
+ return true;
79
+ }
80
+
81
+ if (process.env.ROXIFY_SKIP_BUILD === '1') return false;
82
+
83
+ // PRIMARY: Try download from GitHub releases (fast, no compilation)
84
+ if (downloadBinary && !process.env.ROXIFY_FORCE_LOCAL_BUILD) {
85
+ console.log(`roxify: Attempting to download prebuilt binary from GitHub...`);
86
+ const downloaded = await downloadBinary();
87
+ if (downloaded) {
88
+ console.log(`roxify: Successfully downloaded prebuilt binary`);
89
+ return true;
90
+ }
91
+ console.log(`roxify: Download failed, will try local build...`);
113
92
  }
114
- if (existing) return;
115
- if (process.env.ROXIFY_SKIP_BUILD === '1') return;
116
93
 
117
- if (buildBinary()) {
118
- const built = join(root, 'target', 'release', getBinaryName());
119
- if (existsSync(built)) {
120
- copyFileSync(built, dest);
94
+ // FALLBACK: Try local build (requires Cargo)
95
+ if (buildBinaryLocally()) {
96
+ if (existsSync(localBuilt)) {
97
+ copyFileSync(localBuilt, dest);
121
98
  if (platform() !== 'win32') {
122
- try { require('fs').chmodSync(dest, 0o755); } catch { }
99
+ try { chmodSync(dest, 0o755); } catch { }
123
100
  }
124
- console.log('roxify: CLI binary built and copied to dist/');
101
+ console.log(`roxify: CLI binary built locally`);
102
+ return true;
125
103
  }
126
104
  }
105
+
106
+ console.log('roxify: No native binary available, TypeScript fallback will be used');
107
+ return false;
127
108
  }
128
109
 
129
- function ensureNativeLib() {
110
+ async function ensureNativeLib() {
130
111
  const triples = getTriples();
131
112
  if (!triples.length) return;
132
113
 
133
- for (const t of triples) {
134
- if (existsSync(join(root, `roxify_native-${t}.node`))) return;
135
- }
114
+ const triple = triples[0];
115
+ const dest = join(root, `roxify_native-${triple}.node`);
116
+
117
+ if (existsSync(dest)) return;
118
+
119
+ // Check if built locally
120
+ const ext = platform() === 'win32' ? 'dll' : (platform() === 'darwin' ? 'dylib' : 'so');
121
+ const prefix = platform() === 'win32' ? '' : 'lib';
122
+ const localBuilt = join(root, 'target', 'release', `${prefix}roxify_native.${ext}`);
136
123
 
137
- const found = findExistingNativeLib();
138
- if (found) {
139
- const dest = join(root, `roxify_native-${found.triple}.node`);
140
- copyFileSync(found.path, dest);
141
- console.log(`roxify: Copied native lib → ${dest}`);
124
+ if (existsSync(localBuilt)) {
125
+ copyFileSync(localBuilt, dest);
126
+ console.log(`roxify: Native lib copied from local build → ${dest}`);
142
127
  return;
143
128
  }
144
129
 
145
130
  if (process.env.ROXIFY_SKIP_BUILD === '1') return;
146
131
 
147
- const triple = triples[0];
148
- if (buildNative(null)) {
149
- const ext = getLibExt();
150
- const prefix = platform() === 'win32' ? '' : 'lib';
151
- const built = join(root, 'target', 'release', `${prefix}roxify_native.${ext}`);
152
- if (existsSync(built)) {
153
- const dest = join(root, `roxify_native-${triple}.node`);
154
- copyFileSync(built, dest);
155
- console.log(`roxify: Native lib built → ${dest}`);
132
+ // PRIMARY: Try download from GitHub releases
133
+ if (downloadNativeLib && !process.env.ROXIFY_FORCE_LOCAL_BUILD) {
134
+ console.log(`roxify: Attempting to download native lib from GitHub...`);
135
+ const downloaded = await downloadNativeLib();
136
+ if (downloaded) {
137
+ console.log(`roxify: Successfully downloaded native lib`);
138
+ return;
156
139
  }
140
+ console.log(`roxify: Native lib download failed, will try local build...`);
157
141
  }
158
- }
159
142
 
160
- if (process.env.ROXIFY_ENABLE_RUST_CLI === '1') {
161
- ensureCliBinary();
143
+ // FALLBACK: Try build locally
144
+ if (hasCargo()) {
145
+ console.log(`roxify: Building native lib locally...`);
146
+ try {
147
+ execSync('cargo build --release --lib', { cwd: root, stdio: 'inherit', timeout: 600000 });
148
+ if (existsSync(localBuilt)) {
149
+ copyFileSync(localBuilt, dest);
150
+ console.log(`roxify: Native lib built locally → ${dest}`);
151
+ }
152
+ } catch {
153
+ console.log('roxify: Native lib build failed, TypeScript fallback will be used');
154
+ }
155
+ }
162
156
  }
163
- ensureNativeLib();
164
157
 
165
- // Cleanup: remove other platform .node artifacts to reduce disk usage after install
158
+ // Always ensure CLI binary is available (download first, build as fallback)
159
+ (async () => {
160
+ await ensureCliBinary();
161
+ await ensureNativeLib();
162
+
163
+ // Summary
164
+ console.log('');
165
+ console.log('roxify: Post-install complete');
166
+ console.log(`roxify: Platform: ${platform()}/${arch()}`);
167
+ if (!existsSync(join(distDir, platform() === 'darwin' ? getMacosBinaryName() : getBinaryName()))) {
168
+ console.log('roxify: WARNING: No native CLI binary available - will use TypeScript fallback');
169
+ }
170
+ })();
171
+
172
+ // Cleanup: remove ALL other platform .node artifacts
166
173
  try {
167
174
  const triples = getTriples();
168
- if (triples && triples.length) {
169
- const files = readdirSync(root);
170
- for (const f of files) {
171
- // match roxify_native-<triple>.node or libroxify_native-<triple>.node
172
- const m = f.match(/^(lib)?roxify_native-(.+)\.node$/);
173
- if (m) {
174
- const fileTriple = m[2];
175
- // keep if fileTriple is one of the allowed triples
176
- if (!triples.includes(fileTriple)) {
177
- try { unlinkSync(join(root, f)); console.log(`roxify: removed ${f}`); } catch (e) { }
178
- }
175
+ const files = readdirSync(root);
176
+ for (const f of files) {
177
+ const m = f.match(/^roxify_native-(.+)\.node$/);
178
+ if (m) {
179
+ const fileTriple = m[1];
180
+ if (!triples.includes(fileTriple)) {
181
+ try {
182
+ unlinkSync(join(root, f));
183
+ console.log(`roxify: removed unused native lib ${f}`);
184
+ } catch (e) { }
179
185
  }
180
186
  }
181
187
  }
182
188
  } catch (e) { }
189
+
190
+ // Cleanup: remove ALL other platform CLI binaries from dist/
191
+ try {
192
+ const os = platform();
193
+ const distFiles = existsSync(distDir) ? readdirSync(distDir) : [];
194
+ const keepName = os === 'darwin' ? getMacosBinaryName() : getBinaryName();
195
+
196
+ for (const f of distFiles) {
197
+ // Skip if it's the correct binary for this platform
198
+ if (f === keepName) continue;
199
+
200
+ // Remove any roxify_native binary
201
+ if (f.match(/^roxify_native/) || f === 'rox-macos-universal') {
202
+ try {
203
+ unlinkSync(join(distDir, f));
204
+ console.log(`roxify: removed unused binary dist/${f}`);
205
+ } catch (e) { }
206
+ }
207
+ }
208
+ } catch (e) { }
Binary file
Binary file
Binary file
Binary file
Binary file