7zip-native 0.0.5 → 0.0.6
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 +16 -10
- package/dist/index.cjs +71 -5
- package/dist/index.d.cts +10 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.mjs +72 -6
- package/package.json +3 -1
- package/prebuilds/darwin-arm64/7zip_native.node +0 -0
- package/prebuilds/darwin-x64/7zip_native.node +0 -0
- package/prebuilds/win32-arm64/7z.dll +0 -0
- package/prebuilds/win32-arm64/7zip_native.node +0 -0
- package/prebuilds/win32-x64/7z.dll +0 -0
- package/prebuilds/win32-x64/7zip_native.node +0 -0
- package/scripts/build-native.mjs +119 -0
- package/scripts/clean-upstream-build.mjs +9 -0
- package/scripts/verify-prebuild.mjs +29 -0
package/README.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
# 7zip-native
|
|
5
5
|
|
|
6
|
+
### Current version `26.05`
|
|
7
|
+
Upstream 7-Zip archive engine from https://github.com/ip7z/7zip/
|
|
8
|
+
|
|
6
9
|
Native Node-API bindings for the 7-Zip archive engine. Use a typed, Promise-based Node.js API to inspect archives, read entry data, stream large files, or extract entries without launching `7z`, `7zz`, or another archive executable.
|
|
7
10
|
|
|
8
11
|
The package includes the native addon and the matching 7-Zip engine for each supported target. Compression and decompression are provided by the upstream 7-Zip implementation.
|
|
@@ -13,7 +16,7 @@ The package includes the native addon and the matching 7-Zip engine for each sup
|
|
|
13
16
|
npm install 7zip-native
|
|
14
17
|
```
|
|
15
18
|
|
|
16
|
-
The package requires Node.js 18 or newer.
|
|
19
|
+
The package requires Node.js 18 or newer.
|
|
17
20
|
|
|
18
21
|
## Quick start
|
|
19
22
|
|
|
@@ -141,12 +144,16 @@ This respects backpressure and ends the destination when extraction succeeds. A
|
|
|
141
144
|
```ts
|
|
142
145
|
await archive.extractToFolder('./unpacked', {
|
|
143
146
|
paths: ['chapter/page1.jpg', 'chapter/page2.jpg'], // alternatively, use ids
|
|
147
|
+
overwrite: true,
|
|
148
|
+
onProgress: ({filesExtracted, totalFiles, bytesProcessed, totalBytes, percent}) => {
|
|
149
|
+
console.log(`${filesExtracted}/${totalFiles} files, ${percent}% (${bytesProcessed}/${totalBytes} bytes)`);
|
|
150
|
+
},
|
|
144
151
|
maxBytes: 2n * 1024n * 1024n * 1024n,
|
|
145
152
|
signal: abortController.signal,
|
|
146
153
|
});
|
|
147
154
|
```
|
|
148
155
|
|
|
149
|
-
Folder extraction accepts either `ids` or exact `paths`; omit both to extract every entry, and do not provide both together. Paths use archive-internal `/` separators. Extraction is sequential and uses bounded memory. Its default total output limit is 4 GiB; `maxBytes: null` disables that limit.
|
|
156
|
+
Folder extraction accepts either `ids` or exact `paths`; omit both to extract every entry, and do not provide both together. Paths use archive-internal `/` separators. Extraction is sequential and uses bounded memory. Its default total output limit is 4 GiB; `maxBytes: null` disables that limit. Set `onProgress` to receive the number of regular files selected and fully extracted, the bytes emitted so far, the total uncompressed bytes, and the calculated percentage. It is called before extraction, as data chunks are processed, and after each file completes. By default, existing files are not overwritten; set `overwrite: true` to replace existing regular files. Replacement is committed only after that entry has been fully extracted, using a temporary file alongside the destination so a failure does not truncate the existing file. This can temporarily require extra disk space up to the size of the entry being replaced. Symlink or non-file targets are rejected. Unsafe archive paths, path collisions, and symlinked destination components are also rejected. Archive links are written as regular file data; the API does not create symbolic or hard links. If extraction fails partway through, files already written (or partially written) may remain in the destination.
|
|
150
157
|
|
|
151
158
|
### Concurrency
|
|
152
159
|
|
|
@@ -200,16 +207,15 @@ See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for third-party redistrib
|
|
|
200
207
|
|
|
201
208
|
## Platform support
|
|
202
209
|
|
|
203
|
-
The
|
|
210
|
+
The package includes prebuilt binaries for:
|
|
204
211
|
|
|
205
|
-
| Platform |
|
|
206
|
-
| --- | --- |
|
|
207
|
-
| Linux | x64,
|
|
208
|
-
|
|
|
209
|
-
|
|
|
210
|
-
| Windows | x64, ARM64 | Build and runtime tests |
|
|
212
|
+
| Platform | Architectures |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| Linux | x64, x86 (ia32), ARM32, ARM64 |
|
|
215
|
+
| macOS | x64, ARM64 |
|
|
216
|
+
| Windows | x64, ARM64 |
|
|
211
217
|
|
|
212
|
-
|
|
218
|
+
Windows x86 (ia32) is not supported by the published prebuilt binaries.
|
|
213
219
|
|
|
214
220
|
## Electron
|
|
215
221
|
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var node_fs = require('node:fs');
|
|
4
4
|
var promises$1 = require('node:fs/promises');
|
|
5
|
+
var node_crypto = require('node:crypto');
|
|
5
6
|
var node_module = require('node:module');
|
|
6
7
|
var node_os = require('node:os');
|
|
7
8
|
var node_path = require('node:path');
|
|
@@ -124,6 +125,19 @@ async function ensureDirectory(directoryPath) {
|
|
|
124
125
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Destination path is not a real directory: ${directoryPath}`);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
128
|
+
async function assertReplaceableOutput(filePath) {
|
|
129
|
+
try {
|
|
130
|
+
const details = await promises$1.lstat(filePath);
|
|
131
|
+
if (!details.isFile()) {
|
|
132
|
+
throw new ArchiveError('ERR_ARCHIVE_PATH', `Cannot replace a non-file extraction target: ${filePath}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (error instanceof ArchiveError || error.code !== 'ENOENT') {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
127
141
|
async function ensureExtractionRoot(root) {
|
|
128
142
|
try {
|
|
129
143
|
const details = await promises$1.lstat(root);
|
|
@@ -365,6 +379,12 @@ class Archive {
|
|
|
365
379
|
if (!options || typeof options !== 'object') {
|
|
366
380
|
throw new TypeError('Folder extraction options must be an object');
|
|
367
381
|
}
|
|
382
|
+
if (options.overwrite !== undefined && typeof options.overwrite !== 'boolean') {
|
|
383
|
+
throw new TypeError('Folder extraction overwrite option must be a boolean');
|
|
384
|
+
}
|
|
385
|
+
if (options.onProgress !== undefined && typeof options.onProgress !== 'function') {
|
|
386
|
+
throw new TypeError('Folder extraction onProgress option must be a function');
|
|
387
|
+
}
|
|
368
388
|
options.signal?.throwIfAborted();
|
|
369
389
|
const maxBytes = normalizeMaxBytes(options.maxBytes, 'maxBytes');
|
|
370
390
|
const entries = await this.entries();
|
|
@@ -410,6 +430,26 @@ class Archive {
|
|
|
410
430
|
options.signal?.throwIfAborted();
|
|
411
431
|
await ensureExtractionRoot(root);
|
|
412
432
|
let extractedBytes = 0n;
|
|
433
|
+
let filesExtracted = 0;
|
|
434
|
+
const totalFiles = plans.filter(plan => !plan.isDirectory).length;
|
|
435
|
+
const reportProgress = () => {
|
|
436
|
+
let percent = 0;
|
|
437
|
+
if (expectedBytes > 0n) {
|
|
438
|
+
const scaledPercent = extractedBytes * 10000n / expectedBytes;
|
|
439
|
+
percent = Number(scaledPercent > 9900n ? 9900n : scaledPercent) / 100;
|
|
440
|
+
}
|
|
441
|
+
if (filesExtracted === totalFiles) {
|
|
442
|
+
percent = 100;
|
|
443
|
+
}
|
|
444
|
+
options.onProgress?.({
|
|
445
|
+
filesExtracted,
|
|
446
|
+
totalFiles,
|
|
447
|
+
bytesProcessed: extractedBytes,
|
|
448
|
+
totalBytes: expectedBytes,
|
|
449
|
+
percent,
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
reportProgress();
|
|
413
453
|
for (const plan of plans) {
|
|
414
454
|
options.signal?.throwIfAborted();
|
|
415
455
|
if (plan.isDirectory) {
|
|
@@ -417,7 +457,6 @@ class Archive {
|
|
|
417
457
|
continue;
|
|
418
458
|
}
|
|
419
459
|
await ensureOutputParents(root, plan.targetPath);
|
|
420
|
-
const output = node_fs.createWriteStream(plan.targetPath, { flags: 'wx', mode: 0o600 });
|
|
421
460
|
const limit = new node_stream.Transform({
|
|
422
461
|
transform(chunk, _encoding, callback) {
|
|
423
462
|
const size = BigInt(chunk.length);
|
|
@@ -426,13 +465,40 @@ class Archive {
|
|
|
426
465
|
return;
|
|
427
466
|
}
|
|
428
467
|
extractedBytes += size;
|
|
468
|
+
try {
|
|
469
|
+
reportProgress();
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
429
475
|
callback(null, chunk);
|
|
430
476
|
},
|
|
431
477
|
});
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
478
|
+
if (options.overwrite) {
|
|
479
|
+
await assertReplaceableOutput(plan.targetPath);
|
|
480
|
+
const temporaryPath = node_path.join(node_path.dirname(plan.targetPath), `.7zip-native-${node_crypto.randomUUID()}.tmp`);
|
|
481
|
+
try {
|
|
482
|
+
await promises.pipeline(this.createReadStream(plan.entry.id, {
|
|
483
|
+
signal: options.signal,
|
|
484
|
+
maxBytes: null,
|
|
485
|
+
}), limit, node_fs.createWriteStream(temporaryPath, { flags: 'wx', mode: 0o600 }), { signal: options.signal });
|
|
486
|
+
await assertReplaceableOutput(plan.targetPath);
|
|
487
|
+
await promises$1.rename(temporaryPath, plan.targetPath);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
await promises$1.rm(temporaryPath, { force: true });
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
await promises.pipeline(this.createReadStream(plan.entry.id, {
|
|
496
|
+
signal: options.signal,
|
|
497
|
+
maxBytes: null,
|
|
498
|
+
}), limit, node_fs.createWriteStream(plan.targetPath, { flags: 'wx', mode: 0o600 }), { signal: options.signal });
|
|
499
|
+
}
|
|
500
|
+
filesExtracted++;
|
|
501
|
+
reportProgress();
|
|
436
502
|
}
|
|
437
503
|
}
|
|
438
504
|
async read(idOrPath, options) {
|
package/dist/index.d.cts
CHANGED
|
@@ -19,6 +19,13 @@ interface ReadOptions {
|
|
|
19
19
|
interface OpenOptions {
|
|
20
20
|
readonly password?: string;
|
|
21
21
|
}
|
|
22
|
+
interface ExtractToFolderProgress {
|
|
23
|
+
readonly filesExtracted: number;
|
|
24
|
+
readonly totalFiles: number;
|
|
25
|
+
readonly bytesProcessed: bigint;
|
|
26
|
+
readonly totalBytes: bigint;
|
|
27
|
+
readonly percent: number;
|
|
28
|
+
}
|
|
22
29
|
interface ReadStreamOptions {
|
|
23
30
|
readonly highWaterMark?: number;
|
|
24
31
|
readonly maxBytes?: number | bigint | null;
|
|
@@ -27,6 +34,8 @@ interface ReadStreamOptions {
|
|
|
27
34
|
interface ExtractToFolderOptions {
|
|
28
35
|
readonly ids?: readonly EntryId[];
|
|
29
36
|
readonly paths?: readonly string[];
|
|
37
|
+
readonly overwrite?: boolean;
|
|
38
|
+
readonly onProgress?: (progress: ExtractToFolderProgress) => void;
|
|
30
39
|
readonly maxBytes?: number | bigint | null;
|
|
31
40
|
readonly signal?: AbortSignal;
|
|
32
41
|
}
|
|
@@ -60,4 +69,4 @@ declare class Archive {
|
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
export { Archive, ArchiveError };
|
|
63
|
-
export type { ArchiveEntry, ArchiveErrorCode, EntryId, EntrySelector, ExtractToFolderOptions, OpenOptions, ReadOptions, ReadStreamOptions };
|
|
72
|
+
export type { ArchiveEntry, ArchiveErrorCode, EntryId, EntrySelector, ExtractToFolderOptions, ExtractToFolderProgress, OpenOptions, ReadOptions, ReadStreamOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -19,6 +19,13 @@ interface ReadOptions {
|
|
|
19
19
|
interface OpenOptions {
|
|
20
20
|
readonly password?: string;
|
|
21
21
|
}
|
|
22
|
+
interface ExtractToFolderProgress {
|
|
23
|
+
readonly filesExtracted: number;
|
|
24
|
+
readonly totalFiles: number;
|
|
25
|
+
readonly bytesProcessed: bigint;
|
|
26
|
+
readonly totalBytes: bigint;
|
|
27
|
+
readonly percent: number;
|
|
28
|
+
}
|
|
22
29
|
interface ReadStreamOptions {
|
|
23
30
|
readonly highWaterMark?: number;
|
|
24
31
|
readonly maxBytes?: number | bigint | null;
|
|
@@ -27,6 +34,8 @@ interface ReadStreamOptions {
|
|
|
27
34
|
interface ExtractToFolderOptions {
|
|
28
35
|
readonly ids?: readonly EntryId[];
|
|
29
36
|
readonly paths?: readonly string[];
|
|
37
|
+
readonly overwrite?: boolean;
|
|
38
|
+
readonly onProgress?: (progress: ExtractToFolderProgress) => void;
|
|
30
39
|
readonly maxBytes?: number | bigint | null;
|
|
31
40
|
readonly signal?: AbortSignal;
|
|
32
41
|
}
|
|
@@ -60,4 +69,4 @@ declare class Archive {
|
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
export { Archive, ArchiveError };
|
|
63
|
-
export type { ArchiveEntry, ArchiveErrorCode, EntryId, EntrySelector, ExtractToFolderOptions, OpenOptions, ReadOptions, ReadStreamOptions };
|
|
72
|
+
export type { ArchiveEntry, ArchiveErrorCode, EntryId, EntrySelector, ExtractToFolderOptions, ExtractToFolderProgress, OpenOptions, ReadOptions, ReadStreamOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ interface ReadOptions {
|
|
|
19
19
|
interface OpenOptions {
|
|
20
20
|
readonly password?: string;
|
|
21
21
|
}
|
|
22
|
+
interface ExtractToFolderProgress {
|
|
23
|
+
readonly filesExtracted: number;
|
|
24
|
+
readonly totalFiles: number;
|
|
25
|
+
readonly bytesProcessed: bigint;
|
|
26
|
+
readonly totalBytes: bigint;
|
|
27
|
+
readonly percent: number;
|
|
28
|
+
}
|
|
22
29
|
interface ReadStreamOptions {
|
|
23
30
|
readonly highWaterMark?: number;
|
|
24
31
|
readonly maxBytes?: number | bigint | null;
|
|
@@ -27,6 +34,8 @@ interface ReadStreamOptions {
|
|
|
27
34
|
interface ExtractToFolderOptions {
|
|
28
35
|
readonly ids?: readonly EntryId[];
|
|
29
36
|
readonly paths?: readonly string[];
|
|
37
|
+
readonly overwrite?: boolean;
|
|
38
|
+
readonly onProgress?: (progress: ExtractToFolderProgress) => void;
|
|
30
39
|
readonly maxBytes?: number | bigint | null;
|
|
31
40
|
readonly signal?: AbortSignal;
|
|
32
41
|
}
|
|
@@ -60,4 +69,4 @@ declare class Archive {
|
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
export { Archive, ArchiveError };
|
|
63
|
-
export type { ArchiveEntry, ArchiveErrorCode, EntryId, EntrySelector, ExtractToFolderOptions, OpenOptions, ReadOptions, ReadStreamOptions };
|
|
72
|
+
export type { ArchiveEntry, ArchiveErrorCode, EntryId, EntrySelector, ExtractToFolderOptions, ExtractToFolderProgress, OpenOptions, ReadOptions, ReadStreamOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, createWriteStream } from 'node:fs';
|
|
2
|
-
import { lstat, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { rename, rm, lstat, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { createRequire } from 'node:module';
|
|
4
5
|
import { availableParallelism } from 'node:os';
|
|
5
6
|
import { dirname, resolve, join, relative, sep } from 'node:path';
|
|
@@ -121,6 +122,19 @@ async function ensureDirectory(directoryPath) {
|
|
|
121
122
|
throw new ArchiveError('ERR_ARCHIVE_PATH', `Destination path is not a real directory: ${directoryPath}`);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
125
|
+
async function assertReplaceableOutput(filePath) {
|
|
126
|
+
try {
|
|
127
|
+
const details = await lstat(filePath);
|
|
128
|
+
if (!details.isFile()) {
|
|
129
|
+
throw new ArchiveError('ERR_ARCHIVE_PATH', `Cannot replace a non-file extraction target: ${filePath}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (error instanceof ArchiveError || error.code !== 'ENOENT') {
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
124
138
|
async function ensureExtractionRoot(root) {
|
|
125
139
|
try {
|
|
126
140
|
const details = await lstat(root);
|
|
@@ -362,6 +376,12 @@ class Archive {
|
|
|
362
376
|
if (!options || typeof options !== 'object') {
|
|
363
377
|
throw new TypeError('Folder extraction options must be an object');
|
|
364
378
|
}
|
|
379
|
+
if (options.overwrite !== undefined && typeof options.overwrite !== 'boolean') {
|
|
380
|
+
throw new TypeError('Folder extraction overwrite option must be a boolean');
|
|
381
|
+
}
|
|
382
|
+
if (options.onProgress !== undefined && typeof options.onProgress !== 'function') {
|
|
383
|
+
throw new TypeError('Folder extraction onProgress option must be a function');
|
|
384
|
+
}
|
|
365
385
|
options.signal?.throwIfAborted();
|
|
366
386
|
const maxBytes = normalizeMaxBytes(options.maxBytes, 'maxBytes');
|
|
367
387
|
const entries = await this.entries();
|
|
@@ -407,6 +427,26 @@ class Archive {
|
|
|
407
427
|
options.signal?.throwIfAborted();
|
|
408
428
|
await ensureExtractionRoot(root);
|
|
409
429
|
let extractedBytes = 0n;
|
|
430
|
+
let filesExtracted = 0;
|
|
431
|
+
const totalFiles = plans.filter(plan => !plan.isDirectory).length;
|
|
432
|
+
const reportProgress = () => {
|
|
433
|
+
let percent = 0;
|
|
434
|
+
if (expectedBytes > 0n) {
|
|
435
|
+
const scaledPercent = extractedBytes * 10000n / expectedBytes;
|
|
436
|
+
percent = Number(scaledPercent > 9900n ? 9900n : scaledPercent) / 100;
|
|
437
|
+
}
|
|
438
|
+
if (filesExtracted === totalFiles) {
|
|
439
|
+
percent = 100;
|
|
440
|
+
}
|
|
441
|
+
options.onProgress?.({
|
|
442
|
+
filesExtracted,
|
|
443
|
+
totalFiles,
|
|
444
|
+
bytesProcessed: extractedBytes,
|
|
445
|
+
totalBytes: expectedBytes,
|
|
446
|
+
percent,
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
reportProgress();
|
|
410
450
|
for (const plan of plans) {
|
|
411
451
|
options.signal?.throwIfAborted();
|
|
412
452
|
if (plan.isDirectory) {
|
|
@@ -414,7 +454,6 @@ class Archive {
|
|
|
414
454
|
continue;
|
|
415
455
|
}
|
|
416
456
|
await ensureOutputParents(root, plan.targetPath);
|
|
417
|
-
const output = createWriteStream(plan.targetPath, { flags: 'wx', mode: 0o600 });
|
|
418
457
|
const limit = new Transform({
|
|
419
458
|
transform(chunk, _encoding, callback) {
|
|
420
459
|
const size = BigInt(chunk.length);
|
|
@@ -423,13 +462,40 @@ class Archive {
|
|
|
423
462
|
return;
|
|
424
463
|
}
|
|
425
464
|
extractedBytes += size;
|
|
465
|
+
try {
|
|
466
|
+
reportProgress();
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
426
472
|
callback(null, chunk);
|
|
427
473
|
},
|
|
428
474
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
475
|
+
if (options.overwrite) {
|
|
476
|
+
await assertReplaceableOutput(plan.targetPath);
|
|
477
|
+
const temporaryPath = join(dirname(plan.targetPath), `.7zip-native-${randomUUID()}.tmp`);
|
|
478
|
+
try {
|
|
479
|
+
await pipeline(this.createReadStream(plan.entry.id, {
|
|
480
|
+
signal: options.signal,
|
|
481
|
+
maxBytes: null,
|
|
482
|
+
}), limit, createWriteStream(temporaryPath, { flags: 'wx', mode: 0o600 }), { signal: options.signal });
|
|
483
|
+
await assertReplaceableOutput(plan.targetPath);
|
|
484
|
+
await rename(temporaryPath, plan.targetPath);
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
await rm(temporaryPath, { force: true });
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
await pipeline(this.createReadStream(plan.entry.id, {
|
|
493
|
+
signal: options.signal,
|
|
494
|
+
maxBytes: null,
|
|
495
|
+
}), limit, createWriteStream(plan.targetPath, { flags: 'wx', mode: 0o600 }), { signal: options.signal });
|
|
496
|
+
}
|
|
497
|
+
filesExtracted++;
|
|
498
|
+
reportProgress();
|
|
433
499
|
}
|
|
434
500
|
}
|
|
435
501
|
async read(idOrPath, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "7zip-native",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Native 7zip bindings for Node.js",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"dist/",
|
|
31
31
|
"prebuilds/",
|
|
32
32
|
"native/",
|
|
33
|
+
"scripts/",
|
|
33
34
|
"vendor/7zip/",
|
|
34
35
|
"binding.gyp",
|
|
35
36
|
"README.md",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"url": "https://github.com/sponsors/ollm"
|
|
49
50
|
},
|
|
50
51
|
"scripts": {
|
|
52
|
+
"install": "node scripts/verify-prebuild.mjs",
|
|
51
53
|
"prepare": "npm run build && node scripts/clean-upstream-build.mjs",
|
|
52
54
|
"build": "npm run build:native && npm run eslint && npx rollup -c && npm run typecheck",
|
|
53
55
|
"build:native": "node scripts/build-native.mjs",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {spawn} from 'node:child_process';
|
|
2
|
+
import { copyFile, mkdir, rm } from 'node:fs/promises';
|
|
3
|
+
import {createRequire} from 'node:module';
|
|
4
|
+
import {availableParallelism} from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import {fileURLToPath} from 'node:url';
|
|
7
|
+
|
|
8
|
+
const root = resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
9
|
+
const bundleDirectory = join(root, 'vendor', '7zip', 'CPP', '7zip', 'Bundles', 'Format7zF');
|
|
10
|
+
const releaseDirectory = join(root, 'build', 'Release');
|
|
11
|
+
const targetArch = process.env.BUILD_ARCH || process.arch;
|
|
12
|
+
const target = `${process.platform}-${targetArch}`;
|
|
13
|
+
const engineName = process.platform === 'win32'
|
|
14
|
+
? '7z.dll'
|
|
15
|
+
: process.platform === 'darwin' ? 'lib7z.dylib' : '7z.so';
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
|
|
18
|
+
function run(command, args, cwd, env = process.env)
|
|
19
|
+
{
|
|
20
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
21
|
+
const child = spawn(command, args, {cwd, env, stdio: 'inherit'});
|
|
22
|
+
child.once('error', rejectPromise);
|
|
23
|
+
child.once('close', (code, signal) => {
|
|
24
|
+
if(code === 0)
|
|
25
|
+
{
|
|
26
|
+
resolvePromise();
|
|
27
|
+
}
|
|
28
|
+
else
|
|
29
|
+
{
|
|
30
|
+
rejectPromise(new Error(`${command} exited with ${signal ?? code}`));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if(process.platform === 'win32')
|
|
37
|
+
{
|
|
38
|
+
const upstreamArch = targetArch === 'ia32' ? 'x86' : targetArch;
|
|
39
|
+
await run('nmake', ['/f', 'makefile', `PLATFORM=${upstreamArch}`], bundleDirectory);
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
{
|
|
43
|
+
const args = [
|
|
44
|
+
'-C',
|
|
45
|
+
bundleDirectory,
|
|
46
|
+
'-f',
|
|
47
|
+
'makefile.gcc',
|
|
48
|
+
`-j${process.env.BUILD_JOBS ?? Math.min(availableParallelism(), 2)}`,
|
|
49
|
+
];
|
|
50
|
+
const buildEnv = {...process.env};
|
|
51
|
+
if(targetArch === 'ia32')
|
|
52
|
+
{
|
|
53
|
+
args.push('MY_ARCH=-m32', 'CC=gcc', 'CXX=g++');
|
|
54
|
+
buildEnv.CXXFLAGS = `${buildEnv.CXXFLAGS ?? ''} -m32`.trim();
|
|
55
|
+
buildEnv.LDFLAGS = `${buildEnv.LDFLAGS ?? ''} -m32`.trim();
|
|
56
|
+
}
|
|
57
|
+
else if(targetArch === 'arm')
|
|
58
|
+
{
|
|
59
|
+
const compilerPrefix = 'arm-linux-gnueabihf-';
|
|
60
|
+
args.push(
|
|
61
|
+
`CC=${compilerPrefix}gcc`,
|
|
62
|
+
`CXX=${compilerPrefix}g++`,
|
|
63
|
+
'MY_ARCH=-march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard',
|
|
64
|
+
);
|
|
65
|
+
buildEnv.CC = `${compilerPrefix}gcc`;
|
|
66
|
+
buildEnv.CXX = `${compilerPrefix}g++`;
|
|
67
|
+
buildEnv.CXXFLAGS = `${buildEnv.CXXFLAGS ?? ''} -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard`.trim();
|
|
68
|
+
buildEnv.LDFLAGS = `${buildEnv.LDFLAGS ?? ''} -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard`.trim();
|
|
69
|
+
}
|
|
70
|
+
if(process.platform === 'darwin')
|
|
71
|
+
{
|
|
72
|
+
args.push(
|
|
73
|
+
'LIB2=-lpthread',
|
|
74
|
+
'PROG=lib7z',
|
|
75
|
+
'SHARED_EXT=.dylib',
|
|
76
|
+
'LDFLAGS=-shared -fPIC -Wl,-install_name,@rpath/lib7z.dylib',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
await run('make', args, root, buildEnv);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const upstreamEnginePath = process.platform === 'win32'
|
|
83
|
+
? join(bundleDirectory, targetArch === 'ia32' ? 'x86' : targetArch, engineName)
|
|
84
|
+
: join(bundleDirectory, '_o', engineName);
|
|
85
|
+
const nodeGypArgs = [
|
|
86
|
+
require.resolve('node-gyp/bin/node-gyp.js'),
|
|
87
|
+
'rebuild',
|
|
88
|
+
];
|
|
89
|
+
const nodeGypEnv = {...process.env};
|
|
90
|
+
if(targetArch !== process.arch)
|
|
91
|
+
{
|
|
92
|
+
nodeGypArgs.push(`--arch=${targetArch}`);
|
|
93
|
+
if(targetArch === 'ia32')
|
|
94
|
+
{
|
|
95
|
+
nodeGypEnv.CC = 'gcc';
|
|
96
|
+
nodeGypEnv.CXX = 'g++';
|
|
97
|
+
nodeGypEnv.CXXFLAGS = `${nodeGypEnv.CXXFLAGS ?? ''} -m32`.trim();
|
|
98
|
+
nodeGypEnv.LDFLAGS = `${nodeGypEnv.LDFLAGS ?? ''} -m32`.trim();
|
|
99
|
+
}
|
|
100
|
+
else if(targetArch === 'arm')
|
|
101
|
+
{
|
|
102
|
+
nodeGypEnv.CC = 'arm-linux-gnueabihf-gcc';
|
|
103
|
+
nodeGypEnv.CXX = 'arm-linux-gnueabihf-g++';
|
|
104
|
+
nodeGypEnv.CXXFLAGS = `${nodeGypEnv.CXXFLAGS ?? ''} -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard`.trim();
|
|
105
|
+
nodeGypEnv.LDFLAGS = `${nodeGypEnv.LDFLAGS ?? ''} -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard`.trim();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
await run(process.execPath, nodeGypArgs, root, nodeGypEnv);
|
|
109
|
+
|
|
110
|
+
await mkdir(releaseDirectory, {recursive: true});
|
|
111
|
+
const releaseEnginePath = join(releaseDirectory, engineName);
|
|
112
|
+
await copyFile(upstreamEnginePath, releaseEnginePath);
|
|
113
|
+
const prebuildDirectory = join(root, 'prebuilds', target);
|
|
114
|
+
await rm(prebuildDirectory, {recursive: true, force: true});
|
|
115
|
+
await mkdir(prebuildDirectory, {recursive: true});
|
|
116
|
+
await Promise.all([
|
|
117
|
+
copyFile(join(releaseDirectory, '7zip_native.node'), join(prebuildDirectory, '7zip_native.node')),
|
|
118
|
+
copyFile(releaseEnginePath, join(prebuildDirectory, engineName)),
|
|
119
|
+
]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {rm} from 'node:fs/promises';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
|
|
4
|
+
const upstreamBuildDirectory = new URL(
|
|
5
|
+
'../vendor/7zip/CPP/7zip/Bundles/Format7zF/_o/',
|
|
6
|
+
import.meta.url,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
await rm(fileURLToPath(upstreamBuildDirectory), {recursive: true, force: true});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {existsSync} from 'node:fs';
|
|
2
|
+
import {dirname, join} from 'node:path';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
|
|
5
|
+
const packageDirectory = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const target = `${process.platform}-${process.arch}`;
|
|
7
|
+
const engineName = process.platform === 'win32'
|
|
8
|
+
? '7z.dll'
|
|
9
|
+
: process.platform === 'darwin' ? 'lib7z.dylib' : '7z.so';
|
|
10
|
+
const runtimeDirectories = [
|
|
11
|
+
join(packageDirectory, `../prebuilds/${target}`),
|
|
12
|
+
join(packageDirectory, '../build/Release'),
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
if(runtimeDirectories.some(directory =>
|
|
16
|
+
existsSync(join(directory, '7zip_native.node')) && existsSync(join(directory, engineName))))
|
|
17
|
+
{
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if(existsSync(join(packageDirectory, '../.git')))
|
|
22
|
+
{
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
throw new Error(
|
|
27
|
+
`No prebuilt 7zip-native binaries are available for ${target}. `
|
|
28
|
+
+ 'Check the supported platforms in the package README.',
|
|
29
|
+
);
|