Skip to content

Commit d9d7c40

Browse files
committed
refactor(dlx): use getFs() pattern and safe mkdir functions
Standardize fs module loading across dlx files to use lazy-loaded getFs() pattern for better Webpack compatibility. Changes to dlx.ts: - Add getFs() helper for lazy fs module loading - Replace all existsSync and fs.promises calls with getFs() - Use safeMkdir/safeMkdirSync instead of raw mkdir calls Changes to dlx-package.ts: - Add getFs() helper for lazy fs module loading - Replace all fs operations with getFs() calls - Use safeMkdir instead of raw mkdir with manual EEXIST handling Changes to dlx-binary.ts: - Use safeMkdir instead of raw mkdir with manual EEXIST handling - Simplifies error handling by using safe functions Benefits: - Consistent fs module loading pattern across all dlx files - Better Webpack bundling compatibility - Eliminates manual EEXIST error handling boilerplate - More robust concurrent directory creation
1 parent 3328e2a commit d9d7c40

3 files changed

Lines changed: 102 additions & 42 deletions

File tree

src/dlx-binary.ts

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

33
import { createHash } from 'node:crypto'
4-
import { existsSync, promises as fs } from 'node:fs'
54
import os from 'node:os'
65
import path from 'node:path'
76

87
import { WIN32 } from '#constants/platform'
98

109
import { generateCacheKey } from './dlx'
1110
import { httpDownload } from './http-request'
12-
import { isDir, readJson, safeDelete } from './fs'
11+
import { isDir, readJson, safeDelete, safeMkdir } from './fs'
1312
import { isObjectObject } from './objects'
1413
import { normalizePath } from './path'
1514
import { getSocketDlxDir } from './paths'
1615
import { processLock } from './process-lock'
1716
import type { SpawnExtra, SpawnOptions } from './spawn'
1817
import { spawn } from './spawn'
1918

19+
let _fs: typeof import('fs') | undefined
20+
/**
21+
* Lazily load the fs module to avoid Webpack errors.
22+
* Uses non-'node:' prefixed require to prevent Webpack bundling issues.
23+
*
24+
* @returns The Node.js fs module
25+
* @private
26+
*/
27+
/*@__NO_SIDE_EFFECTS__*/
28+
function getFs() {
29+
if (_fs === undefined) {
30+
// Use non-'node:' prefixed require to avoid Webpack errors.
31+
_fs = /*@__PURE__*/ require('fs')
32+
}
33+
return _fs as typeof import('fs')
34+
}
35+
2036
export interface DlxBinaryOptions {
2137
/** URL to download the binary from. */
2238
url: string
@@ -139,9 +155,10 @@ async function isCacheValid(
139155
cacheEntryPath: string,
140156
cacheTtl: number,
141157
): Promise<boolean> {
158+
const fs = getFs()
142159
try {
143160
const metaPath = getMetadataPath(cacheEntryPath)
144-
if (!existsSync(metaPath)) {
161+
if (!fs.existsSync(metaPath)) {
145162
return false
146163
}
147164

@@ -181,12 +198,13 @@ async function downloadBinaryFile(
181198
return await processLock.withLock(
182199
lockPath,
183200
async () => {
201+
const fs = getFs()
184202
// Check if file was downloaded while waiting for lock.
185-
if (existsSync(destPath)) {
186-
const stats = await fs.stat(destPath)
203+
if (fs.existsSync(destPath)) {
204+
const stats = await fs.promises.stat(destPath)
187205
if (stats.size > 0) {
188206
// File exists, compute and return checksum.
189-
const fileBuffer = await fs.readFile(destPath)
207+
const fileBuffer = await fs.promises.readFile(destPath)
190208
const hasher = createHash('sha256')
191209
hasher.update(fileBuffer)
192210
return hasher.digest('hex')
@@ -206,7 +224,7 @@ async function downloadBinaryFile(
206224
}
207225

208226
// Compute checksum of downloaded file.
209-
const fileBuffer = await fs.readFile(destPath)
227+
const fileBuffer = await fs.promises.readFile(destPath)
210228
const hasher = createHash('sha256')
211229
hasher.update(fileBuffer)
212230
const actualChecksum = hasher.digest('hex')
@@ -222,7 +240,7 @@ async function downloadBinaryFile(
222240

223241
// Make executable on POSIX systems.
224242
if (!WIN32) {
225-
await fs.chmod(destPath, 0o755)
243+
await fs.promises.chmod(destPath, 0o755)
226244
}
227245

228246
return actualChecksum
@@ -264,7 +282,8 @@ async function writeMetadata(
264282
url,
265283
},
266284
}
267-
await fs.writeFile(metaPath, JSON.stringify(metadata, null, 2))
285+
const fs = getFs()
286+
await fs.promises.writeFile(metaPath, JSON.stringify(metadata, null, 2))
268287
}
269288

270289
/**
@@ -274,14 +293,15 @@ export async function cleanDlxCache(
274293
maxAge: number = /*@__INLINE__*/ require('#constants/time').DLX_BINARY_CACHE_TTL,
275294
): Promise<number> {
276295
const cacheDir = getDlxCachePath()
296+
const fs = getFs()
277297

278-
if (!existsSync(cacheDir)) {
298+
if (!fs.existsSync(cacheDir)) {
279299
return 0
280300
}
281301

282302
let cleaned = 0
283303
const now = Date.now()
284-
const entries = await fs.readdir(cacheDir)
304+
const entries = await fs.promises.readdir(cacheDir)
285305

286306
for (const entry of entries) {
287307
const entryPath = path.join(cacheDir, entry)
@@ -319,7 +339,7 @@ export async function cleanDlxCache(
319339
// If we can't read metadata, check if directory is empty or corrupted.
320340
try {
321341
// eslint-disable-next-line no-await-in-loop
322-
const contents = await fs.readdir(entryPath)
342+
const contents = await fs.promises.readdir(entryPath)
323343
if (!contents.length) {
324344
// Remove empty directory.
325345
// eslint-disable-next-line no-await-in-loop
@@ -358,14 +378,15 @@ export async function dlxBinary(
358378
const cacheKey = generateCacheKey(spec)
359379
const cacheEntryDir = path.join(cacheDir, cacheKey)
360380
const binaryPath = normalizePath(path.join(cacheEntryDir, binaryName))
381+
const fs = getFs()
361382

362383
let downloaded = false
363384
let computedChecksum = checksum
364385

365386
// Check if we need to download.
366387
if (
367388
!force &&
368-
existsSync(cacheEntryDir) &&
389+
fs.existsSync(cacheEntryDir) &&
369390
(await isCacheValid(cacheEntryDir, cacheTtl))
370391
) {
371392
// Binary is cached and valid, read the checksum from metadata.
@@ -396,7 +417,7 @@ export async function dlxBinary(
396417
if (downloaded) {
397418
// Ensure cache directory exists before downloading.
398419
try {
399-
await fs.mkdir(cacheEntryDir, { recursive: true })
420+
await safeMkdir(cacheEntryDir, { recursive: true })
400421
} catch (e) {
401422
const code = (e as NodeJS.ErrnoException).code
402423
if (code === 'EACCES' || code === 'EPERM') {
@@ -423,7 +444,7 @@ export async function dlxBinary(
423444
computedChecksum = await downloadBinaryFile(url, binaryPath, checksum)
424445

425446
// Get file size for metadata.
426-
const stats = await fs.stat(binaryPath)
447+
const stats = await fs.promises.stat(binaryPath)
427448
await writeMetadata(
428449
cacheEntryDir,
429450
cacheKey,
@@ -497,21 +518,22 @@ export async function downloadBinary(
497518
const cacheKey = generateCacheKey(spec)
498519
const cacheEntryDir = path.join(cacheDir, cacheKey)
499520
const binaryPath = normalizePath(path.join(cacheEntryDir, binaryName))
521+
const fs = getFs()
500522

501523
let downloaded = false
502524

503525
// Check if we need to download.
504526
if (
505527
!force &&
506-
existsSync(cacheEntryDir) &&
528+
fs.existsSync(cacheEntryDir) &&
507529
(await isCacheValid(cacheEntryDir, cacheTtl))
508530
) {
509531
// Binary is cached and valid.
510532
downloaded = false
511533
} else {
512534
// Ensure cache directory exists before downloading.
513535
try {
514-
await fs.mkdir(cacheEntryDir, { recursive: true })
536+
await safeMkdir(cacheEntryDir, { recursive: true })
515537
} catch (e) {
516538
const code = (e as NodeJS.ErrnoException).code
517539
if (code === 'EACCES' || code === 'EPERM') {
@@ -538,7 +560,7 @@ export async function downloadBinary(
538560
const computedChecksum = await downloadBinaryFile(url, binaryPath, checksum)
539561

540562
// Get file size for metadata.
541-
const stats = await fs.stat(binaryPath)
563+
const stats = await fs.promises.stat(binaryPath)
542564
await writeMetadata(
543565
cacheEntryDir,
544566
cacheKey,
@@ -623,14 +645,15 @@ export async function listDlxCache(): Promise<
623645
}>
624646
> {
625647
const cacheDir = getDlxCachePath()
648+
const fs = getFs()
626649

627-
if (!existsSync(cacheDir)) {
650+
if (!fs.existsSync(cacheDir)) {
628651
return []
629652
}
630653

631654
const results = []
632655
const now = Date.now()
633-
const entries = await fs.readdir(cacheDir)
656+
const entries = await fs.promises.readdir(cacheDir)
634657

635658
for (const entry of entries) {
636659
const entryPath = path.join(cacheDir, entry)
@@ -661,13 +684,13 @@ export async function listDlxCache(): Promise<
661684

662685
// Find the binary file in the directory.
663686
// eslint-disable-next-line no-await-in-loop
664-
const files = await fs.readdir(entryPath)
687+
const files = await fs.promises.readdir(entryPath)
665688
const binaryFile = files.find(f => !f.startsWith('.'))
666689

667690
if (binaryFile) {
668691
const binaryPath = path.join(entryPath, binaryFile)
669692
// eslint-disable-next-line no-await-in-loop
670-
const binaryStats = await fs.stat(binaryPath)
693+
const binaryStats = await fs.promises.stat(binaryPath)
671694

672695
results.push({
673696
age: now - ((metaObj['timestamp'] as number) || 0),

src/dlx-package.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,35 @@
3030
* - dlxPackage() combines both for convenience
3131
*/
3232

33-
import { existsSync, promises as fs } from 'node:fs'
3433
import path from 'node:path'
3534

3635
import { WIN32 } from './constants/platform'
3736
import { getPacoteCachePath } from './constants/packages'
3837
import { generateCacheKey } from './dlx'
39-
import { readJsonSync } from './fs'
38+
import { readJsonSync, safeMkdir } from './fs'
4039
import { normalizePath } from './path'
4140
import { getSocketDlxDir } from './paths'
4241
import { processLock } from './process-lock'
4342
import type { SpawnExtra, SpawnOptions } from './spawn'
4443
import { spawn } from './spawn'
4544

45+
let _fs: typeof import('fs') | undefined
46+
/**
47+
* Lazily load the fs module to avoid Webpack errors.
48+
* Uses non-'node:' prefixed require to prevent Webpack bundling issues.
49+
*
50+
* @returns The Node.js fs module
51+
* @private
52+
*/
53+
/*@__NO_SIDE_EFFECTS__*/
54+
function getFs() {
55+
if (_fs === undefined) {
56+
// Use non-'node:' prefixed require to avoid Webpack errors.
57+
_fs = /*@__PURE__*/ require('fs')
58+
}
59+
return _fs as typeof import('fs')
60+
}
61+
4662
let _npmPackageArg: typeof import('npm-package-arg') | undefined
4763
/*@__NO_SIDE_EFFECTS__*/
4864
function getNpmPackageArg() {
@@ -183,7 +199,7 @@ async function ensurePackageInstalled(
183199
// Ensure package directory exists before creating lock.
184200
// The lock directory will be created inside this directory.
185201
try {
186-
await fs.mkdir(packageDir, { recursive: true })
202+
await safeMkdir(packageDir, { recursive: true })
187203
} catch (e) {
188204
const code = (e as NodeJS.ErrnoException).code
189205
if (code === 'EACCES' || code === 'EPERM') {
@@ -212,12 +228,13 @@ async function ensurePackageInstalled(
212228
return await processLock.withLock(
213229
lockPath,
214230
async () => {
231+
const fs = getFs()
215232
// Double-check if already installed (unless force).
216233
// Another process may have installed while waiting for lock.
217-
if (!force && existsSync(installedDir)) {
234+
if (!force && fs.existsSync(installedDir)) {
218235
// Verify package.json exists.
219236
const pkgJsonPath = path.join(installedDir, 'package.json')
220-
if (existsSync(pkgJsonPath)) {
237+
if (fs.existsSync(pkgJsonPath)) {
221238
return { installed: false, packageDir }
222239
}
223240
}
@@ -392,6 +409,7 @@ export async function dlxPackage(
392409
export async function downloadPackage(
393410
options: DlxPackageOptions,
394411
): Promise<DownloadPackageResult> {
412+
const fs = getFs()
395413
const {
396414
binaryName,
397415
force: userForce,
@@ -427,10 +445,9 @@ export async function downloadPackage(
427445
const binaryPath = findBinaryPath(packageDir, packageName, binaryName)
428446

429447
// Make binary executable on Unix systems.
430-
if (!WIN32 && existsSync(binaryPath)) {
431-
const { chmodSync } = require('node:fs')
448+
if (!WIN32 && fs.existsSync(binaryPath)) {
432449
try {
433-
chmodSync(binaryPath, 0o755)
450+
fs.chmodSync(binaryPath, 0o755)
434451
} catch {
435452
// Ignore chmod errors.
436453
}

0 commit comments

Comments
 (0)