@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 +29 -2
- package/README.md +190 -18
- package/dist/archives.d.ts +58 -0
- package/dist/archives.js +313 -0
- package/dist/arrays.js +2 -3
- package/dist/cache-with-ttl.js +21 -6
- package/dist/constants/node.js +2 -1
- package/dist/cover/formatters.js +5 -3
- package/dist/dlx/package.js +1 -1
- package/dist/external/@npmcli/package-json.js +352 -824
- package/dist/external/adm-zip.js +2695 -0
- package/dist/external/debug.js +183 -7
- package/dist/external/external-pack.js +19 -1409
- package/dist/external/libnpmexec.js +2 -2
- package/dist/external/npm-pack.js +18777 -19997
- package/dist/external/pico-pack.js +29 -5
- package/dist/external/spdx-pack.js +41 -263
- package/dist/external/tar-fs.js +3053 -0
- package/dist/git.js +22 -4
- package/dist/github.js +7 -8
- package/dist/globs.js +20 -1
- package/dist/http-request.js +1 -1
- package/dist/memoization.js +22 -13
- package/dist/package-extensions.js +4 -2
- package/dist/packages/normalize.js +3 -0
- package/dist/process-lock.js +4 -2
- package/dist/releases/github.d.ts +40 -0
- package/dist/releases/github.js +122 -22
- package/dist/spawn.js +1 -1
- package/dist/spinner.js +1 -1
- package/dist/stdio/progress.js +2 -2
- package/package.json +38 -15
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
|
[](https://twitter.com/SocketSecurity)
|
|
8
8
|
[](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
|
-
##
|
|
29
|
+
## Quick Start
|
|
19
30
|
|
|
20
31
|
```typescript
|
|
21
|
-
// Tree-shakeable exports
|
|
22
32
|
import { Spinner } from '@socketsecurity/lib/spinner'
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
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
|
|
29
|
-
spinner.
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
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 #
|
|
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>;
|
package/dist/archives.js
ADDED
|
@@ -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 +=
|
|
63
|
-
chunks.push(arr.slice(i, i +
|
|
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
|
}
|