@socketsecurity/lib 5.7.0 → 5.8.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.8.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.8.0) - 2026-03-10
9
+
10
+ ### Added
11
+
12
+ - **archives**: Added secure archive extraction utilities with support for ZIP, TAR, TAR.GZ, and TGZ formats
13
+ - Configurable limits: `maxFileSize` (default 100MB), `maxTotalSize` (default 1GB)
14
+ - Cross-platform path normalization
15
+ - External dependencies: adm-zip@0.5.16, tar-fs@3.1.2 (bundled, +212KB)
16
+ - Security features: path traversal protection, file size limits, total size limits, symlink blocking
17
+ - Strip option to remove leading path components (like tar `--strip-components`)
18
+ - `detectArchiveFormat()` - Detect archive type from file extension
19
+ - `extractArchive()` - Generic extraction with auto-format detection
20
+ - `extractTar()`, `extractTarGz()`, `extractZip()` - Format-specific extractors
21
+
22
+ - **releases/github**: Added archive extraction support for GitHub releases
23
+ - Auto-detects format from asset filename
24
+ - Enhanced `downloadAndExtractZip()` to use generic archive helpers
25
+ - Supports ZIP, TAR, TAR.GZ, and TGZ assets
26
+ - `downloadAndExtractArchive()` - Generic archive download and extraction
27
+
28
+ ### Changed
29
+
30
+ - **dependencies**: Deduplicated 14 external bundle packages to single versions using pnpm overrides and patches
31
+
8
32
  ## [5.7.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.7.0) - 2026-02-12
9
33
 
10
34
  ### Added
@@ -67,7 +91,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
67
91
  - **provenance**: Fixed incorrect package name in provenance workflow
68
92
  - Changed from `@socketregistry/lib` to `@socketsecurity/lib`
69
93
 
70
-
71
94
  ## [5.6.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.6.0) - 2026-02-08
72
95
 
73
96
  ### Added
@@ -1073,6 +1096,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1073
1096
  This release completely refactors the environment variable system, consolidating 60+ individual env constant files into grouped getter modules with AsyncLocalStorage-based test rewiring.
1074
1097
 
1075
1098
  **Consolidated env files** - Individual files replaced with grouped modules:
1099
+
1076
1100
  - `env/github.ts` - All GitHub-related env vars (GITHUB_TOKEN, GH_TOKEN, GITHUB_API_URL, etc.)
1077
1101
  - `env/socket.ts` - Socket-specific env vars (SOCKET_API_TOKEN, SOCKET_CACACHE_DIR, etc.)
1078
1102
  - `env/socket-cli.ts` - Socket CLI env vars (SOCKET_CLI_API_TOKEN, SOCKET_CLI_CONFIG, etc.)
@@ -1084,6 +1108,7 @@ This release completely refactors the environment variable system, consolidating
1084
1108
  - `env/test.ts` - Test framework env vars (VITEST, JEST_WORKER_ID)
1085
1109
 
1086
1110
  **Constants → Getter functions** - All env constants converted to functions:
1111
+
1087
1112
  ```typescript
1088
1113
  // Before (v1.x):
1089
1114
  import { GITHUB_TOKEN } from '#env/github-token'
@@ -1093,6 +1118,7 @@ import { getGithubToken } from '#env/github'
1093
1118
  ```
1094
1119
 
1095
1120
  **Deleted files** - Removed 60+ individual env constant files:
1121
+
1096
1122
  - `env/github-token.ts`, `env/socket-api-token.ts`, etc. → Consolidated into grouped files
1097
1123
  - `env/getters.ts` → Functions moved to their respective grouped files
1098
1124
 
@@ -1122,6 +1148,7 @@ afterEach(() => {
1122
1148
  ```
1123
1149
 
1124
1150
  **Features:**
1151
+
1125
1152
  - Allows toggling between snapshot and live behavior
1126
1153
  - Compatible with `vi.stubEnv()` as fallback
1127
1154
 
@@ -1224,7 +1251,7 @@ afterEach(() => {
1224
1251
  ### Added
1225
1252
 
1226
1253
  - Added `dlx-package` module for installing and executing npm packages directly
1227
- - Content-addressed caching using SHA256 hash (like npm's _npx)
1254
+ - Content-addressed caching using SHA256 hash (like npm's \_npx)
1228
1255
  - Auto-force for version ranges (^, ~, >, <) to get latest within range
1229
1256
  - Cross-platform support with comprehensive tests (30 tests)
1230
1257
  - Parses scoped and unscoped package specs correctly
package/README.md CHANGED
@@ -7,47 +7,219 @@
7
7
  [![Follow @SocketSecurity](https://img.shields.io/twitter/follow/SocketSecurity?style=social)](https://twitter.com/SocketSecurity)
8
8
  [![Follow @socket.dev on Bluesky](https://img.shields.io/badge/Follow-@socket.dev-1DA1F2?style=social&logo=bluesky)](https://bsky.app/profile/socket.dev)
9
9
 
10
- Core library for [Socket.dev](https://socket.dev/) tools.
10
+ Core infrastructure library for [Socket.dev](https://socket.dev/) security tools. Provides utilities for file system operations, process spawning, HTTP requests, environment detection, logging, spinners, and more.
11
+
12
+ ## Prerequisites
13
+
14
+ **Node.js 22 or higher** is required.
11
15
 
12
16
  ## Install
13
17
 
14
18
  ```bash
19
+ # Using pnpm (recommended)
15
20
  pnpm add @socketsecurity/lib
21
+
22
+ # Using npm
23
+ npm install @socketsecurity/lib
24
+
25
+ # Using yarn
26
+ yarn add @socketsecurity/lib
16
27
  ```
17
28
 
18
- ## Usage
29
+ ## Quick Start
19
30
 
20
31
  ```typescript
21
- // Tree-shakeable exports
22
32
  import { Spinner } from '@socketsecurity/lib/spinner'
23
- import { readJsonFile } from '@socketsecurity/lib/fs'
24
- import { NODE_MODULES } from '@socketsecurity/lib/constants/packages'
33
+ import { getDefaultLogger } from '@socketsecurity/lib/logger'
34
+ import { readJson } from '@socketsecurity/lib/fs'
35
+
36
+ const logger = getDefaultLogger()
37
+ const spinner = Spinner({ text: 'Loading package.json...' })
25
38
 
26
- const spinner = Spinner({ text: 'Loading...' })
27
39
  spinner.start()
28
- const pkg = await readJsonFile('./package.json')
29
- spinner.stop()
40
+ const pkg = await readJson('./package.json')
41
+ spinner.successAndStop('Loaded successfully')
42
+
43
+ logger.success(`Package: ${pkg.name}@${pkg.version}`)
30
44
  ```
31
45
 
46
+ ## Documentation
47
+
48
+ - [Getting Started](./docs/getting-started.md) - Prerequisites, installation, and first examples
49
+ - [Visual Effects](./docs/visual-effects.md) - Spinners, loggers, themes, and progress indicators
50
+ - [File System](./docs/file-system.md) - File operations, globs, paths, and safe deletion
51
+ - [HTTP Utilities](./docs/http-utilities.md) - Making requests, downloading files, and retry logic
52
+ - [Process Utilities](./docs/process-utilities.md) - Spawning processes, IPC, and locks
53
+ - [Package Management](./docs/package-management.md) - npm/pnpm/yarn detection and operations
54
+ - [Environment](./docs/environment.md) - CI detection, env getters, and platform checks
55
+ - [Constants](./docs/constants.md) - Node versions, npm URLs, and platform values
56
+ - [Examples](./docs/examples.md) - Real-world usage patterns
57
+ - [Troubleshooting](./docs/troubleshooting.md) - Common issues and solutions
58
+
32
59
  ## What's Inside
33
60
 
34
- - **Visual Effects** → logger, spinner, themes
35
- - **File System** → fs, globs, paths
36
- - **Package Management** → dlx, npm, pnpm, yarn
37
- - **Process & Spawn** → process spawning
38
- - **Environment** env getters
39
- - **Constants** node, npm, platform
40
- - **Utilities** arrays, objects, promises, strings
61
+ ### Visual Effects
62
+
63
+ Spinners, colored loggers, themes, progress bars, and terminal output formatting.
64
+
65
+ - `Spinner` - Animated CLI spinners with progress tracking
66
+ - `getDefaultLogger()` - Colored console logger with symbols
67
+ - `LOG_SYMBOLS` - Colored terminal symbols (✓, ✗, ⚠, ℹ, →)
68
+ - `setTheme()` - Customize colors across the library
69
+
70
+ ### File System
71
+
72
+ Cross-platform file operations with safe deletion and convenient wrappers.
73
+
74
+ - `readFileUtf8()`, `readFileBinary()` - Read files as text or binary
75
+ - `readJson()`, `writeJson()` - Parse and format JSON files
76
+ - `safeDelete()` - Protected deletion with safety checks
77
+ - `findUp()`, `findUpSync()` - Traverse up to find files
78
+ - `safeMkdir()` - Create directories without EEXIST errors
79
+ - `validateFiles()` - Check file readability (useful for Yarn PnP, pnpm)
80
+
81
+ ### HTTP Utilities
82
+
83
+ Native Node.js HTTP/HTTPS requests with retry logic and redirects.
84
+
85
+ - `httpJson()` - Fetch and parse JSON from APIs
86
+ - `httpText()` - Fetch text/HTML content
87
+ - `httpDownload()` - Download files with progress callbacks
88
+ - `httpRequest()` - Full control over requests and responses
89
+ - Automatic redirects, exponential backoff retries, timeout support
90
+
91
+ ### Process Management
92
+
93
+ Spawn child processes safely with cross-platform support.
94
+
95
+ - `spawn()` - Promise-based process spawning with output capture
96
+ - `spawnSync()` - Synchronous version for blocking operations
97
+ - Array-based arguments prevent command injection
98
+ - Automatic Windows `.cmd`/`.bat` handling
99
+ - `ProcessLock` - Ensure only one instance runs at a time
100
+ - `setupIPC()` - Inter-process communication
101
+
102
+ ### Environment Detection
103
+
104
+ Type-safe environment variable access and platform detection.
105
+
106
+ - `getCI()` - Detect CI environment
107
+ - `getNodeEnv()` - Get NODE_ENV value
108
+ - `isTest()` - Check if running tests
109
+ - `getHome()` - Home directory (Unix/Linux/macOS)
110
+ - Test rewiring with `setEnv()`, `resetEnv()`
111
+
112
+ ### Package Management
113
+
114
+ Detect and work with npm, pnpm, and yarn.
115
+
116
+ - `detectPackageManager()` - Identify package manager from lock files
117
+ - Package manifest operations
118
+ - Lock file management
119
+
120
+ ### Constants
121
+
122
+ Pre-defined values for Node.js, npm, and platform detection.
123
+
124
+ - `getNodeMajorVersion()` - Get current Node.js major version
125
+ - `WIN32`, `DARWIN` - Platform booleans (use `!WIN32 && !DARWIN` for Linux)
126
+ - `getAbortSignal()` - Global abort signal
127
+
128
+ ### Utilities
129
+
130
+ Helpers for arrays, objects, strings, promises, sorting, and more.
131
+
132
+ - Arrays, objects, strings manipulation
133
+ - Promise utilities and queues
134
+ - Natural sorting
135
+ - Version comparison
136
+ - Error handling with causes
137
+
138
+ ## Features
139
+
140
+ - **Tree-shakeable exports** - Import only what you need
141
+ - **Cross-platform** - Works on Windows, macOS, and Linux
142
+ - **TypeScript-first** - Full type safety with .d.ts files
143
+ - **Zero dependencies** (for core HTTP - uses Node.js native modules)
144
+ - **Well-tested** - 84% coverage with comprehensive test suite
145
+ - **Security-focused** - Safe defaults, command injection protection
146
+ - **CommonJS output** - Compatible with Node.js tooling
147
+
148
+ ## Common Use Cases
149
+
150
+ ### Running Shell Commands
151
+
152
+ ```typescript
153
+ import { spawn } from '@socketsecurity/lib/spawn'
154
+
155
+ const result = await spawn('git', ['status'])
156
+ console.log(result.stdout)
157
+ ```
158
+
159
+ ### Making API Requests
160
+
161
+ ```typescript
162
+ import { httpJson } from '@socketsecurity/lib/http-request'
163
+
164
+ const data = await httpJson('https://api.example.com/data')
165
+ ```
166
+
167
+ ### Visual Feedback
168
+
169
+ ```typescript
170
+ import { Spinner } from '@socketsecurity/lib/spinner'
171
+
172
+ const spinner = Spinner({ text: 'Processing...' })
173
+ spinner.start()
174
+ // ... do work ...
175
+ spinner.successAndStop('Complete!')
176
+ ```
177
+
178
+ ### Safe File Deletion
179
+
180
+ ```typescript
181
+ import { safeDelete } from '@socketsecurity/lib/fs'
182
+
183
+ // Protected against deleting parent directories
184
+ await safeDelete('./build')
185
+ ```
186
+
187
+ ## Troubleshooting
188
+
189
+ **Module not found**: Verify you're importing from the correct path:
190
+
191
+ ```typescript
192
+ // Correct
193
+ import { Spinner } from '@socketsecurity/lib/spinner'
194
+
195
+ // Wrong
196
+ import { Spinner } from '@socketsecurity/lib'
197
+ ```
198
+
199
+ **Node version error**: This library requires Node.js 22+. Check your version:
200
+
201
+ ```bash
202
+ node --version
203
+ ```
204
+
205
+ For more issues, see the [Troubleshooting Guide](./docs/troubleshooting.md).
41
206
 
42
207
  ## Development
43
208
 
44
209
  ```bash
45
- pnpm install # Install
46
- pnpm build # Build
47
- pnpm test # Test
210
+ pnpm install # Install dependencies
211
+ pnpm build # Build the library
212
+ pnpm test # Run tests
213
+ pnpm run cover # Run tests with coverage
48
214
  pnpm dev # Watch mode
215
+ pnpm run lint # Check code style
216
+ pnpm run fix # Fix formatting issues
49
217
  ```
50
218
 
219
+ ## Contributing
220
+
221
+ Contributions are welcome! Please read the [CLAUDE.md](./CLAUDE.md) file for development guidelines and coding standards.
222
+
51
223
  ## License
52
224
 
53
225
  MIT
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Archive format type.
3
+ */
4
+ export type ArchiveFormat = 'tar' | 'tar.gz' | 'tgz' | 'zip';
5
+ /**
6
+ * Options for archive extraction.
7
+ */
8
+ export interface ExtractOptions {
9
+ /** Suppress log messages */
10
+ quiet?: boolean;
11
+ /** Strip leading path components (like tar --strip-components) */
12
+ strip?: number;
13
+ /** Maximum size of a single extracted file in bytes (default: 100MB) */
14
+ maxFileSize?: number;
15
+ /** Maximum total extracted size in bytes (default: 1GB) */
16
+ maxTotalSize?: number;
17
+ }
18
+ /**
19
+ * Detect archive format from file path.
20
+ *
21
+ * @param filePath - Path to archive file
22
+ * @returns Archive format or null if unknown
23
+ */
24
+ export declare function detectArchiveFormat(filePath: string): ArchiveFormat | null;
25
+ /**
26
+ * Extract a tar archive to a directory.
27
+ *
28
+ * @param archivePath - Path to tar file
29
+ * @param outputDir - Directory to extract to
30
+ * @param options - Extraction options
31
+ */
32
+ export declare function extractTar(archivePath: string, outputDir: string, options?: ExtractOptions): Promise<void>;
33
+ /**
34
+ * Extract a gzipped tar archive to a directory.
35
+ *
36
+ * @param archivePath - Path to tar.gz or tgz file
37
+ * @param outputDir - Directory to extract to
38
+ * @param options - Extraction options
39
+ */
40
+ export declare function extractTarGz(archivePath: string, outputDir: string, options?: ExtractOptions): Promise<void>;
41
+ /**
42
+ * Extract a zip archive to a directory.
43
+ *
44
+ * @param archivePath - Path to zip file
45
+ * @param outputDir - Directory to extract to
46
+ * @param options - Extraction options
47
+ */
48
+ export declare function extractZip(archivePath: string, outputDir: string, options?: ExtractOptions): Promise<void>;
49
+ /**
50
+ * Extract an archive to a directory.
51
+ * Automatically detects format from file extension.
52
+ *
53
+ * @param archivePath - Path to archive file
54
+ * @param outputDir - Directory to extract to
55
+ * @param options - Extraction options
56
+ * @throws Error if archive format is not supported
57
+ */
58
+ export declare function extractArchive(archivePath: string, outputDir: string, options?: ExtractOptions): Promise<void>;
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ /* Socket Lib - Built with esbuild */
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var archives_exports = {};
31
+ __export(archives_exports, {
32
+ detectArchiveFormat: () => detectArchiveFormat,
33
+ extractArchive: () => extractArchive,
34
+ extractTar: () => extractTar,
35
+ extractTarGz: () => extractTarGz,
36
+ extractZip: () => extractZip
37
+ });
38
+ module.exports = __toCommonJS(archives_exports);
39
+ var import_node_fs = require("node:fs");
40
+ var import_promises = require("node:stream/promises");
41
+ var import_node_zlib = require("node:zlib");
42
+ var import_adm_zip = __toESM(require("./external/adm-zip.js"));
43
+ var import_tar_fs = __toESM(require("./external/tar-fs.js"));
44
+ var import_fs = require("./fs.js");
45
+ var import_normalize = require("./paths/normalize.js");
46
+ let _path;
47
+ // @__NO_SIDE_EFFECTS__
48
+ function getPath() {
49
+ if (_path === void 0) {
50
+ _path = require("path");
51
+ }
52
+ return _path;
53
+ }
54
+ const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024;
55
+ const DEFAULT_MAX_TOTAL_SIZE = 1024 * 1024 * 1024;
56
+ function validatePathWithinBase(targetPath, baseDir, entryName) {
57
+ const path = /* @__PURE__ */ getPath();
58
+ const resolvedTarget = path.resolve(targetPath);
59
+ const resolvedBase = path.resolve(baseDir);
60
+ if (!resolvedTarget.startsWith(resolvedBase + path.sep) && resolvedTarget !== resolvedBase) {
61
+ throw new Error(
62
+ `Path traversal attempt detected: entry "${entryName}" would extract to "${resolvedTarget}" outside target directory "${resolvedBase}"`
63
+ );
64
+ }
65
+ }
66
+ function detectArchiveFormat(filePath) {
67
+ const lower = filePath.toLowerCase();
68
+ if (lower.endsWith(".tar.gz")) {
69
+ return "tar.gz";
70
+ }
71
+ if (lower.endsWith(".tgz")) {
72
+ return "tgz";
73
+ }
74
+ if (lower.endsWith(".tar")) {
75
+ return "tar";
76
+ }
77
+ if (lower.endsWith(".zip")) {
78
+ return "zip";
79
+ }
80
+ return null;
81
+ }
82
+ async function extractTar(archivePath, outputDir, options = {}) {
83
+ const {
84
+ maxFileSize = DEFAULT_MAX_FILE_SIZE,
85
+ maxTotalSize = DEFAULT_MAX_TOTAL_SIZE,
86
+ strip = 0
87
+ } = options;
88
+ const normalizedOutputDir = (0, import_normalize.normalizePath)(outputDir);
89
+ await (0, import_fs.safeMkdir)(normalizedOutputDir);
90
+ let totalExtractedSize = 0;
91
+ let destroyScheduled = false;
92
+ const extractStream = import_tar_fs.default.extract(normalizedOutputDir, {
93
+ map: (header) => {
94
+ if (destroyScheduled) {
95
+ return header;
96
+ }
97
+ if (header.type === "symlink" || header.type === "link") {
98
+ destroyScheduled = true;
99
+ process.nextTick(() => {
100
+ extractStream.destroy(
101
+ new Error(
102
+ `Symlink detected in archive: ${header.name}. Symlinks are not supported for security reasons.`
103
+ )
104
+ );
105
+ });
106
+ return header;
107
+ }
108
+ if (header.size && header.size > maxFileSize) {
109
+ destroyScheduled = true;
110
+ process.nextTick(() => {
111
+ extractStream.destroy(
112
+ new Error(
113
+ `File size exceeds limit: ${header.name} (${header.size} bytes > ${maxFileSize} bytes)`
114
+ )
115
+ );
116
+ });
117
+ return header;
118
+ }
119
+ if (header.size) {
120
+ totalExtractedSize += header.size;
121
+ if (totalExtractedSize > maxTotalSize) {
122
+ destroyScheduled = true;
123
+ process.nextTick(() => {
124
+ extractStream.destroy(
125
+ new Error(
126
+ `Total extracted size exceeds limit: ${totalExtractedSize} bytes > ${maxTotalSize} bytes`
127
+ )
128
+ );
129
+ });
130
+ return header;
131
+ }
132
+ }
133
+ return header;
134
+ },
135
+ strip
136
+ });
137
+ extractStream.on("error", () => {
138
+ });
139
+ const readStream = (0, import_node_fs.createReadStream)(archivePath);
140
+ try {
141
+ await (0, import_promises.pipeline)(readStream, extractStream);
142
+ } catch (error) {
143
+ readStream.destroy();
144
+ throw error;
145
+ }
146
+ }
147
+ async function extractTarGz(archivePath, outputDir, options = {}) {
148
+ const {
149
+ maxFileSize = DEFAULT_MAX_FILE_SIZE,
150
+ maxTotalSize = DEFAULT_MAX_TOTAL_SIZE,
151
+ strip = 0
152
+ } = options;
153
+ const normalizedOutputDir = (0, import_normalize.normalizePath)(outputDir);
154
+ await (0, import_fs.safeMkdir)(normalizedOutputDir);
155
+ let totalExtractedSize = 0;
156
+ let destroyScheduled = false;
157
+ const extractStream = import_tar_fs.default.extract(normalizedOutputDir, {
158
+ map: (header) => {
159
+ if (destroyScheduled) {
160
+ return header;
161
+ }
162
+ if (header.type === "symlink" || header.type === "link") {
163
+ destroyScheduled = true;
164
+ process.nextTick(() => {
165
+ extractStream.destroy(
166
+ new Error(
167
+ `Symlink detected in archive: ${header.name}. Symlinks are not supported for security reasons.`
168
+ )
169
+ );
170
+ });
171
+ return header;
172
+ }
173
+ if (header.size && header.size > maxFileSize) {
174
+ destroyScheduled = true;
175
+ process.nextTick(() => {
176
+ extractStream.destroy(
177
+ new Error(
178
+ `File size exceeds limit: ${header.name} (${header.size} bytes > ${maxFileSize} bytes)`
179
+ )
180
+ );
181
+ });
182
+ return header;
183
+ }
184
+ if (header.size) {
185
+ totalExtractedSize += header.size;
186
+ if (totalExtractedSize > maxTotalSize) {
187
+ destroyScheduled = true;
188
+ process.nextTick(() => {
189
+ extractStream.destroy(
190
+ new Error(
191
+ `Total extracted size exceeds limit: ${totalExtractedSize} bytes > ${maxTotalSize} bytes`
192
+ )
193
+ );
194
+ });
195
+ return header;
196
+ }
197
+ }
198
+ return header;
199
+ },
200
+ strip
201
+ });
202
+ extractStream.on("error", () => {
203
+ });
204
+ const readStream = (0, import_node_fs.createReadStream)(archivePath);
205
+ try {
206
+ await (0, import_promises.pipeline)(readStream, (0, import_node_zlib.createGunzip)(), extractStream);
207
+ } catch (error) {
208
+ readStream.destroy();
209
+ throw error;
210
+ }
211
+ }
212
+ async function extractZip(archivePath, outputDir, options = {}) {
213
+ const {
214
+ maxFileSize = DEFAULT_MAX_FILE_SIZE,
215
+ maxTotalSize = DEFAULT_MAX_TOTAL_SIZE,
216
+ strip = 0
217
+ } = options;
218
+ const normalizedOutputDir = (0, import_normalize.normalizePath)(outputDir);
219
+ await (0, import_fs.safeMkdir)(normalizedOutputDir);
220
+ const zip = new import_adm_zip.default(archivePath);
221
+ const path = /* @__PURE__ */ getPath();
222
+ const entries = zip.getEntries();
223
+ let totalExtractedSize = 0;
224
+ for (const entry of entries) {
225
+ if (entry.isDirectory) {
226
+ continue;
227
+ }
228
+ const uncompressedSize = entry.header.size;
229
+ if (uncompressedSize > maxFileSize) {
230
+ throw new Error(
231
+ `File size exceeds limit: ${entry.entryName} (${uncompressedSize} bytes > ${maxFileSize} bytes)`
232
+ );
233
+ }
234
+ totalExtractedSize += uncompressedSize;
235
+ if (totalExtractedSize > maxTotalSize) {
236
+ throw new Error(
237
+ `Total extracted size exceeds limit: ${totalExtractedSize} bytes > ${maxTotalSize} bytes`
238
+ );
239
+ }
240
+ const parts = entry.entryName.split("/");
241
+ if (parts.length <= strip) {
242
+ continue;
243
+ }
244
+ const strippedPath = parts.slice(strip).join("/");
245
+ const targetPath = path.join(normalizedOutputDir, strippedPath);
246
+ validatePathWithinBase(targetPath, normalizedOutputDir, entry.entryName);
247
+ }
248
+ if (strip === 0) {
249
+ for (const entry of entries) {
250
+ if (!entry.isDirectory) {
251
+ const targetPath = path.join(normalizedOutputDir, entry.entryName);
252
+ validatePathWithinBase(targetPath, normalizedOutputDir, entry.entryName);
253
+ }
254
+ }
255
+ zip.extractAllTo(normalizedOutputDir, true);
256
+ } else {
257
+ const path2 = /* @__PURE__ */ getPath();
258
+ const entries2 = zip.getEntries();
259
+ const dirsToCreate = /* @__PURE__ */ new Set();
260
+ for (const entry of entries2) {
261
+ if (entry.isDirectory) {
262
+ continue;
263
+ }
264
+ const parts = entry.entryName.split("/");
265
+ if (parts.length <= strip) {
266
+ continue;
267
+ }
268
+ const strippedPath = parts.slice(strip).join("/");
269
+ const targetPath = path2.join(normalizedOutputDir, strippedPath);
270
+ dirsToCreate.add(path2.dirname(targetPath));
271
+ }
272
+ await Promise.all(Array.from(dirsToCreate).map((dir) => (0, import_fs.safeMkdir)(dir)));
273
+ for (const entry of entries2) {
274
+ if (entry.isDirectory) {
275
+ continue;
276
+ }
277
+ const parts = entry.entryName.split("/");
278
+ if (parts.length <= strip) {
279
+ continue;
280
+ }
281
+ const strippedPath = parts.slice(strip).join("/");
282
+ const targetPath = path2.join(normalizedOutputDir, strippedPath);
283
+ zip.extractEntryTo(entry, path2.dirname(targetPath), false, true);
284
+ }
285
+ }
286
+ }
287
+ async function extractArchive(archivePath, outputDir, options = {}) {
288
+ const format = detectArchiveFormat(archivePath);
289
+ if (!format) {
290
+ const path = /* @__PURE__ */ getPath();
291
+ const ext = path.extname(archivePath).toLowerCase();
292
+ throw new Error(
293
+ `Unsupported archive format${ext ? ` (extension: ${ext})` : ""}: ${archivePath}. Supported formats: .zip, .tar, .tar.gz, .tgz`
294
+ );
295
+ }
296
+ switch (format) {
297
+ case "zip":
298
+ return await extractZip(archivePath, outputDir, options);
299
+ case "tar":
300
+ return await extractTar(archivePath, outputDir, options);
301
+ case "tar.gz":
302
+ case "tgz":
303
+ return await extractTarGz(archivePath, outputDir, options);
304
+ }
305
+ }
306
+ // Annotate the CommonJS export names for ESM import in node:
307
+ 0 && (module.exports = {
308
+ detectArchiveFormat,
309
+ extractArchive,
310
+ extractTar,
311
+ extractTarGz,
312
+ extractZip
313
+ });
package/dist/arrays.js CHANGED
@@ -57,10 +57,9 @@ function arrayChunk(arr, size) {
57
57
  throw new Error("Chunk size must be greater than 0");
58
58
  }
59
59
  const { length } = arr;
60
- const actualChunkSize = Math.min(length, chunkSize);
61
60
  const chunks = [];
62
- for (let i = 0; i < length; i += actualChunkSize) {
63
- chunks.push(arr.slice(i, i + actualChunkSize));
61
+ for (let i = 0; i < length; i += chunkSize) {
62
+ chunks.push(arr.slice(i, i + chunkSize));
64
63
  }
65
64
  return chunks;
66
65
  }