Skip to content

Commit 04494b3

Browse files
committed
refactor: lazy load node modules to avoid Webpack bundling issues
1 parent 4f37b6e commit 04494b3

23 files changed

Lines changed: 282 additions & 173 deletions

src/bin.ts

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
* Provides cross-platform bin path lookup, command execution, and path normalization.
44
*/
55

6-
import fs from 'node:fs'
7-
import path from 'node:path'
8-
96
import { getHome } from './env/home'
107
import { getAppdata, getLocalappdata } from './env/windows'
118
import { getXdgDataHome } from './env/xdg'
@@ -16,13 +13,40 @@ import { readJsonSync } from './fs'
1613
import { isPath, normalizePath } from './paths/normalize'
1714
import { spawn } from './spawn'
1815

19-
// ============================================================================
20-
// Private Helper Functions
21-
// ============================================================================
16+
let _fs: typeof import('node:fs') | undefined
17+
/**
18+
* Lazily load the fs module to avoid Webpack errors.
19+
* Uses non-'node:' prefixed require to prevent Webpack bundling issues.
20+
*
21+
* @private
22+
*/
23+
/*@__NO_SIDE_EFFECTS__*/
24+
function getFs() {
25+
if (_fs === undefined) {
26+
// Use non-'node:' prefixed require to avoid Webpack errors.
27+
28+
_fs = /*@__PURE__*/ require('fs')
29+
}
30+
return _fs as typeof import('node:fs')
31+
}
32+
33+
let _path: typeof import('node:path') | undefined
34+
/**
35+
* Lazily load the path module to avoid Webpack errors.
36+
* Uses non-'node:' prefixed require to prevent Webpack bundling issues.
37+
*
38+
* @returns The Node.js path module
39+
* @private
40+
*/
41+
/*@__NO_SIDE_EFFECTS__*/
42+
function getPath() {
43+
if (_path === undefined) {
44+
// Use non-'node:' prefixed require to avoid Webpack errors.
2245

23-
// ============================================================================
24-
// Types and Interfaces
25-
// ============================================================================
46+
_path = /*@__PURE__*/ require('path')
47+
}
48+
return _path as typeof import('node:path')
49+
}
2650

2751
/**
2852
* Options for the which function.
@@ -42,10 +66,6 @@ export interface WhichOptions {
4266
cwd?: string | undefined
4367
}
4468

45-
// ============================================================================
46-
// Public API (alphabetically sorted)
47-
// ============================================================================
48-
4969
/**
5070
* Execute a binary with the given arguments.
5171
*/
@@ -96,7 +116,8 @@ export function findRealBin(
96116
binName: string,
97117
commonPaths: string[] = [],
98118
): string | undefined {
99-
// fs, path, and which are imported at the top
119+
const fs = getFs()
120+
const path = getPath()
100121

101122
// Try common locations first.
102123
for (const binPath of commonPaths) {
@@ -140,7 +161,8 @@ export function findRealBin(
140161
* Find the real npm executable, bypassing any aliases and shadow bins.
141162
*/
142163
export function findRealNpm(): string {
143-
// fs and path are imported at the top
164+
const fs = getFs()
165+
const path = getPath()
144166

145167
// Try to find npm in the same directory as the node executable.
146168
const nodeDir = path.dirname(process.execPath)
@@ -174,7 +196,7 @@ export function findRealNpm(): string {
174196
* Find the real pnpm executable, bypassing any aliases and shadow bins.
175197
*/
176198
export function findRealPnpm(): string {
177-
// path is imported at the top
199+
const path = getPath()
178200

179201
// Try common pnpm locations.
180202
const commonPaths = WIN32
@@ -205,7 +227,7 @@ export function findRealPnpm(): string {
205227
* Find the real yarn executable, bypassing any aliases and shadow bins.
206228
*/
207229
export function findRealYarn(): string {
208-
// path is imported at the top
230+
const path = getPath()
209231

210232
// Try common yarn locations.
211233
const commonPaths = [
@@ -239,7 +261,8 @@ export function isShadowBinPath(dirPath: string | undefined): boolean {
239261
* Handles Windows .cmd wrappers and Unix shell scripts, resolving them to the actual .js files they execute.
240262
*/
241263
export function resolveRealBinSync(binPath: string): string {
242-
// fs and path are imported at the top
264+
const fs = getFs()
265+
const path = getPath()
243266

244267
// If it's not an absolute path, try to find it in PATH first
245268
if (!path.isAbsolute(binPath)) {

src/constants/agents.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export const NPM_BIN_PATH = /*@__PURE__*/ (() => {
2828
// because cli.js exports a function that must be invoked, not executed directly.
2929
export const NPM_REAL_EXEC_PATH = /*@__PURE__*/ (() => {
3030
try {
31-
const { existsSync } = /*@__PURE__*/ require('node:fs')
32-
const path = /*@__PURE__*/ require('node:path')
31+
const { existsSync } = /*@__PURE__*/ require('fs')
32+
const path = /*@__PURE__*/ require('path')
3333
// module is imported at the top
3434
// Find npm binary using which.
3535
const npmBin = which.sync('npm', { nothrow: true })

src/cover/code.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {
1616
V8FileCoverage,
1717
} from './types'
1818

19-
let _path: typeof import('path') | undefined
19+
let _path: typeof import('node:path') | undefined
2020
/**
2121
* Lazily load the path module to avoid Webpack errors.
2222
* @private
@@ -26,9 +26,9 @@ function getPath() {
2626
if (_path === undefined) {
2727
// Use non-'node:' prefixed require to avoid Webpack errors.
2828

29-
_path = /*@__PURE__*/ require('node:path')
29+
_path = /*@__PURE__*/ require('path')
3030
}
31-
return _path as typeof import('path')
31+
return _path as typeof import('node:path')
3232
}
3333

3434
/**

src/debug.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ function getDebugJsInstance(namespace: string) {
6464
return inst
6565
}
6666

67-
let _util: typeof import('util') | undefined
67+
let _util: typeof import('node:util') | undefined
6868
/**
6969
* Lazily load the util module.
7070
* @private
@@ -74,9 +74,9 @@ function getUtil() {
7474
if (_util === undefined) {
7575
// Use non-'node:' prefixed require to avoid Webpack errors.
7676

77-
_util = /*@__PURE__*/ require('node:util')
77+
_util = /*@__PURE__*/ require('util')
7878
}
79-
return _util as typeof import('util')
79+
return _util as typeof import('node:util')
8080
}
8181

8282
/**

src/dlx/binary.ts

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
/** @fileoverview DLX binary execution utilities for Socket ecosystem. */
22

3-
import { createHash } from 'crypto'
4-
5-
import os from 'os'
6-
7-
import path from 'path'
8-
9-
import { WIN32 } from '../constants/platform'
3+
import { getArch, getPlatform, WIN32 } from '../constants/platform'
104
import { DLX_BINARY_CACHE_TTL } from '../constants/time'
115

126
import { generateCacheKey } from './cache'
@@ -17,25 +11,61 @@ import { isObjectObject } from '../objects'
1711
import { normalizePath } from '../paths/normalize'
1812
import { getSocketDlxDir } from '../paths/socket'
1913
import { processLock } from '../process-lock'
20-
import type { SpawnExtra, SpawnOptions } from '../spawn'
2114
import { spawn } from '../spawn'
2215

23-
let _fs: typeof import('fs') | undefined
16+
import type { ChecksumAlgorithm } from './manifest'
17+
import type { SpawnExtra, SpawnOptions } from '../spawn'
18+
19+
let _crypto: typeof import('node:crypto') | undefined
20+
/**
21+
* Lazily load the crypto module to avoid Webpack errors.
22+
* Uses non-'node:' prefixed require to prevent Webpack bundling issues.
23+
*
24+
* @private
25+
*/
26+
/*@__NO_SIDE_EFFECTS__*/
27+
function getCrypto() {
28+
if (_crypto === undefined) {
29+
// Use non-'node:' prefixed require to avoid Webpack errors.
30+
31+
_crypto = /*@__PURE__*/ require('crypto')
32+
}
33+
return _crypto as typeof import('node:crypto')
34+
}
35+
36+
let _fs: typeof import('node:fs') | undefined
2437
/**
2538
* Lazily load the fs module to avoid Webpack errors.
2639
* Uses non-'node:' prefixed require to prevent Webpack bundling issues.
2740
*
28-
* @returns The Node.js fs module
2941
* @private
3042
*/
3143
/*@__NO_SIDE_EFFECTS__*/
3244
function getFs() {
3345
if (_fs === undefined) {
3446
// Use non-'node:' prefixed require to avoid Webpack errors.
3547

36-
_fs = /*@__PURE__*/ require('node:fs')
48+
_fs = /*@__PURE__*/ require('fs')
3749
}
38-
return _fs as typeof import('fs')
50+
return _fs as typeof import('node:fs')
51+
}
52+
53+
let _path: typeof import('node:path') | undefined
54+
/**
55+
* Lazily load the path module to avoid Webpack errors.
56+
* Uses non-'node:' prefixed require to prevent Webpack bundling issues.
57+
*
58+
* @returns The Node.js path module
59+
* @private
60+
*/
61+
/*@__NO_SIDE_EFFECTS__*/
62+
function getPath() {
63+
if (_path === undefined) {
64+
// Use non-'node:' prefixed require to avoid Webpack errors.
65+
66+
_path = /*@__PURE__*/ require('path')
67+
}
68+
return _path as typeof import('node:path')
3969
}
4070

4171
export interface DlxBinaryOptions {
@@ -180,7 +210,7 @@ export interface DlxMetadata {
180210
* Get metadata file path for a cached binary.
181211
*/
182212
function getMetadataPath(cacheEntryPath: string): string {
183-
return path.join(cacheEntryPath, '.dlx-metadata.json')
213+
return getPath().join(cacheEntryPath, '.dlx-metadata.json')
184214
}
185215

186216
/**
@@ -227,20 +257,22 @@ async function downloadBinaryFile(
227257
): Promise<string> {
228258
// Use process lock to prevent concurrent downloads.
229259
// Lock is placed in the cache entry directory as 'concurrency.lock'.
260+
const crypto = getCrypto()
261+
const fs = getFs()
262+
const path = getPath()
230263
const cacheEntryDir = path.dirname(destPath)
231264
const lockPath = path.join(cacheEntryDir, 'concurrency.lock')
232265

233266
return await processLock.withLock(
234267
lockPath,
235268
async () => {
236-
const fs = getFs()
237269
// Check if file was downloaded while waiting for lock.
238270
if (fs.existsSync(destPath)) {
239271
const stats = await fs.promises.stat(destPath)
240272
if (stats.size > 0) {
241273
// File exists, compute and return checksum.
242274
const fileBuffer = await fs.promises.readFile(destPath)
243-
const hasher = createHash('sha256')
275+
const hasher = crypto.createHash('sha256')
244276
hasher.update(fileBuffer)
245277
return hasher.digest('hex')
246278
}
@@ -260,7 +292,7 @@ async function downloadBinaryFile(
260292

261293
// Compute checksum of downloaded file.
262294
const fileBuffer = await fs.promises.readFile(destPath)
263-
const hasher = createHash('sha256')
295+
const hasher = crypto.createHash('sha256')
264296
hasher.update(fileBuffer)
265297
const actualChecksum = hasher.digest('hex')
266298

@@ -312,9 +344,9 @@ async function writeMetadata(
312344
cache_key: cacheKey,
313345
timestamp: Date.now(),
314346
checksum,
315-
checksum_algorithm: 'sha256',
316-
platform: os.platform(),
317-
arch: os.arch(),
347+
checksum_algorithm: 'sha256' as ChecksumAlgorithm,
348+
platform: getPlatform(),
349+
arch: getArch(),
318350
size,
319351
source: {
320352
type: 'download',
@@ -329,9 +361,9 @@ async function writeMetadata(
329361
const spec = `${url}:${binaryName}`
330362
await dlxManifest.setBinaryEntry(spec, cacheKey, {
331363
checksum,
332-
checksum_algorithm: 'sha256',
333-
platform: os.platform(),
334-
arch: os.arch(),
364+
checksum_algorithm: metadata.checksum_algorithm,
365+
platform: metadata.platform,
366+
arch: metadata.arch,
335367
size,
336368
source: {
337369
type: 'download',
@@ -359,6 +391,7 @@ export async function cleanDlxCache(
359391

360392
let cleaned = 0
361393
const now = Date.now()
394+
const path = getPath()
362395
const entries = await fs.promises.readdir(cacheDir)
363396

364397
for (const entry of entries) {
@@ -428,19 +461,18 @@ export async function dlxBinary(
428461
url,
429462
yes,
430463
} = { __proto__: null, ...options } as DlxBinaryOptions
431-
464+
const fs = getFs()
465+
const path = getPath()
432466
// Map --yes flag to force behavior (auto-approve/skip prompts)
433467
const force = yes === true ? true : userForce
434-
435468
// Generate cache paths similar to pnpm/npx structure.
436469
const cacheDir = getDlxCachePath()
437-
const binaryName = name || `binary-${process.platform}-${os.arch()}`
470+
const binaryName = name || `binary-${process.platform}-${getArch()}`
438471
// Create spec from URL and binary name for unique cache identity.
439472
const spec = `${url}:${binaryName}`
440473
const cacheKey = generateCacheKey(spec)
441474
const cacheEntryDir = path.join(cacheDir, cacheKey)
442475
const binaryPath = normalizePath(path.join(cacheEntryDir, binaryName))
443-
const fs = getFs()
444476

445477
let downloaded = false
446478
let computedChecksum = checksum
@@ -542,7 +574,7 @@ export async function dlxBinary(
542574
...spawnOptions,
543575
env: {
544576
...spawnOptions?.env,
545-
PATH: `${cacheEntryDir}${path.delimiter}${process.env['PATH'] || ''}`,
577+
PATH: `${cacheEntryDir}${getPath().delimiter}${process.env['PATH'] || ''}`,
546578
},
547579
shell: true,
548580
}
@@ -572,16 +604,16 @@ export async function downloadBinary(
572604
name,
573605
url,
574606
} = { __proto__: null, ...options } as DlxBinaryOptions
575-
607+
const fs = getFs()
608+
const path = getPath()
576609
// Generate cache paths similar to pnpm/npx structure.
577610
const cacheDir = getDlxCachePath()
578-
const binaryName = name || `binary-${process.platform}-${os.arch()}`
611+
const binaryName = name || `binary-${process.platform}-${getArch()}`
579612
// Create spec from URL and binary name for unique cache identity.
580613
const spec = `${url}:${binaryName}`
581614
const cacheKey = generateCacheKey(spec)
582615
const cacheEntryDir = path.join(cacheDir, cacheKey)
583616
const binaryPath = normalizePath(path.join(cacheEntryDir, binaryName))
584-
const fs = getFs()
585617

586618
let downloaded = false
587619

@@ -670,6 +702,7 @@ export function executeBinary(
670702
//
671703
// Since our binaries are downloaded to a custom cache directory that's not in PATH,
672704
// we must prepend the cache directory to PATH so cmd.exe can locate the binary.
705+
const path = getPath()
673706
const cacheEntryDir = path.dirname(binaryPath)
674707
const finalSpawnOptions = needsShell
675708
? {
@@ -717,6 +750,7 @@ export async function listDlxCache(): Promise<
717750

718751
const results = []
719752
const now = Date.now()
753+
const path = getPath()
720754
const entries = await fs.promises.readdir(cacheDir)
721755

722756
for (const entry of entries) {

0 commit comments

Comments
 (0)