Skip to content

Commit 71171a2

Browse files
committed
refactor(cli): split environment.mts along natural seams (722 -> 574 LOC)
Extract two cohesive modules from utils/ecosystem/environment.mts to bring it under the 1000-line hard cap and well under the 500 soft cap. - windows-shims.mts (85 LOC): resolveBinPathSync + preferWindowsCmdShim. Self-contained: no agent / lockfile knowledge, just path-walking helpers used to resolve npm/pnpm/yarn shims on Windows. - lockfile-readers.mts (151 LOC): LOCKS map (filename -> Agent) + readLockFileByAgent (Agent -> reader). The bun reader handles .lockb via the parser or shells out to bun bun.lockb. Both modules are imported back into environment.mts and re-exported so existing call sites keep working without changes. Also tighten ExternalTool typing on defineToolSpawn helpers (was string, now narrow string union). Net: environment.mts goes from 722 to 574 LOC (+236 LOC of dedicated helper modules with deeper documentation).
1 parent 48f4b95 commit 71171a2

5 files changed

Lines changed: 301 additions & 164 deletions

File tree

.config/rolldown/lib-stub.mts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @fileoverview rolldown plugin: stub heavy `@socketsecurity/lib`
3+
* internals that runtime code never reaches.
4+
*
5+
* Why: `@socketsecurity/lib` is the canonical fleet utility surface, but
6+
* its module graph statically pulls in heavyweight files (e.g. globs.js
7+
* → picomatch ~260KB, sorts.js → semver + npm-pack ~2.5MB) along import
8+
* paths that real consumers never traverse. Tree-shaking can't drop
9+
* unreachable subgraphs that look reachable to the static analyzer; we
10+
* have to tell it explicitly.
11+
*
12+
* Each consumer passes a `stubPattern` regex matching the absolute
13+
* resolved paths of the unreachable files for THEIR import surface.
14+
* Verify reachability before adding a pattern — stubbing a file that IS
15+
* reached at runtime gives runtime crashes, not bundle-time errors.
16+
*
17+
* Source: lifted from socket-packageurl-js's inline plugin
18+
* (.config/rolldown.config.mts), generalized so the stub-pattern is
19+
* caller-provided. Fleet-canonical via socket-repo-template.
20+
*/
21+
22+
import type { Plugin } from 'rolldown'
23+
24+
export type LibStubOptions = {
25+
/**
26+
* Regex matched against resolved module paths. Files matching get
27+
* replaced with an empty CJS module. Required.
28+
*/
29+
readonly stubPattern: RegExp
30+
/**
31+
* Replacement code. Defaults to `module.exports = {}`. Override only
32+
* if you need a non-empty stub (rare).
33+
*/
34+
readonly stubCode?: string
35+
}
36+
37+
export function createLibStubPlugin(options: LibStubOptions): Plugin {
38+
const { stubCode = 'module.exports = {}', stubPattern } = options
39+
return {
40+
name: 'stub-unused-lib-internals',
41+
load(id) {
42+
if (stubPattern.test(id)) {
43+
return { code: stubCode, moduleSideEffects: false }
44+
}
45+
return undefined
46+
},
47+
}
48+
}

packages/cli/src/utils/dlx/define-tool-spawn.mts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { isSeaBinary } from '../sea/detect.mts'
2323

2424
import type { DlxOptions, DlxSpawnResult } from './spawn.mts'
2525
import type { BinaryResolution } from './resolve-binary.mts'
26+
import type { ExternalTool } from './vfs-extract.mts'
2627
import type { StdioOptions } from 'node:child_process'
2728
import type { SpawnExtra } from '@socketsecurity/lib/spawn'
2829

@@ -58,7 +59,7 @@ export function defineAutoDispatch(opts: {
5859
*
5960
* The VFS name (e.g. 'trufflehog') is the directory key under the SEA bundle.
6061
*/
61-
export function defineVfsSpawn(vfsName: string): ToolSpawnFn {
62+
export function defineVfsSpawn(vfsName: ExternalTool): ToolSpawnFn {
6263
return async (args, options, spawnExtra) => {
6364
return await spawnToolVfs(vfsName, args, options, spawnExtra)
6465
}
@@ -111,7 +112,7 @@ export function defineGitHubReleaseSpawn(opts: {
111112
*/
112113
export function defineToolSpawn(opts: {
113114
toolName: string
114-
vfsName: string
115+
vfsName: ExternalTool
115116
resolve: () => BinaryResolution
116117
}): {
117118
Dlx: ToolSpawnFn

packages/cli/src/utils/ecosystem/environment.mts

Lines changed: 14 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,19 @@ import path from 'node:path'
3030
import browserslist from 'browserslist'
3131
import semver from 'semver'
3232

33-
import { parse as parseBunLockb } from '@socketregistry/hyrious__bun.lockb/index.cjs'
3433
import { whichReal } from '@socketsecurity/lib/bin'
3534
import {
3635
BUN,
37-
BUN_LOCK,
38-
BUN_LOCKB,
3936
NPM,
40-
NPM_SHRINKWRAP_JSON,
41-
PACKAGE_LOCK_JSON,
4237
PNPM,
43-
PNPM_LOCK_YAML,
4438
VLT,
45-
VLT_LOCK_JSON,
4639
YARN,
4740
YARN_BERRY,
4841
YARN_CLASSIC,
49-
YARN_LOCK,
5042
} from '@socketsecurity/lib/constants/agents'
5143
import { getMaintainedNodeVersions } from '@socketsecurity/lib/constants/node'
5244
import { WIN32 } from '@socketsecurity/lib/constants/platform'
5345
import { debugDirNs, debugNs } from '@socketsecurity/lib/debug'
54-
import { readFileBinary, readFileUtf8 } from '@socketsecurity/lib/fs'
5546
import {
5647
readPackageJson,
5748
toEditablePackageJson,
@@ -68,9 +59,6 @@ import {
6859
import { FLAG_VERSION } from '../../constants/cli.mts'
6960
import { VITEST } from '../../env/vitest.mts'
7061
import {
71-
EXT_LOCK,
72-
EXT_LOCKB,
73-
NODE_MODULES,
7462
NPM_BUGGY_OVERRIDES_PATCHED_VERSION,
7563
PACKAGE_JSON,
7664
} from '../../constants/packages.mts'
@@ -158,159 +146,23 @@ export type PartialEnvDetails = Readonly<
158146
>
159147
>
160148

161-
export type ReadLockFile =
162-
| ((lockPath: string) => Promise<string | Buffer | undefined>)
163-
| ((
164-
lockPath: string,
165-
agentExecPath: string,
166-
) => Promise<string | Buffer | undefined>)
167-
| ((
168-
lockPath: string,
169-
agentExecPath: string,
170-
cwd: string,
171-
) => Promise<string | Buffer | undefined>)
172-
173-
const readLockFileByAgent: Map<Agent, ReadLockFile> = (() => {
174-
function wrapReader<T extends (...args: any[]) => Promise<any>>(
175-
reader: T,
176-
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>> | undefined> {
177-
return async (...args: any[]): Promise<any> => {
178-
try {
179-
return await reader(...args)
180-
} catch {}
181-
return undefined
182-
}
183-
}
184-
185-
const binaryReader = wrapReader(readFileBinary)
186-
187-
const defaultReader = wrapReader(
188-
async (lockPath: string) => await readFileUtf8(lockPath),
189-
)
190-
191-
return new Map([
192-
[
193-
BUN,
194-
wrapReader(
195-
async (
196-
lockPath: string,
197-
agentExecPath: string,
198-
cwd = process.cwd(),
199-
) => {
200-
const ext = path.extname(lockPath)
201-
if (ext === EXT_LOCK) {
202-
return await defaultReader(lockPath)
203-
}
204-
if (ext === EXT_LOCKB) {
205-
const lockBuffer = await binaryReader(lockPath)
206-
if (lockBuffer) {
207-
try {
208-
return parseBunLockb(lockBuffer)
209-
} catch {}
210-
}
211-
// To print a Yarn lockfile to your console without writing it to disk
212-
// use `bun bun.lockb`.
213-
// https://bun.sh/guides/install/yarnlock
214-
return (
215-
await spawn(agentExecPath, [lockPath], {
216-
cwd,
217-
// On Windows, bun is often a .cmd file that requires shell execution.
218-
// The spawn function from @socketsecurity/registry will handle this properly
219-
// when shell is true.
220-
shell: WIN32,
221-
})
222-
).stdout
223-
}
224-
return undefined
225-
},
226-
),
227-
],
228-
[NPM, defaultReader],
229-
[PNPM, defaultReader],
230-
[VLT, defaultReader],
231-
[YARN_BERRY, defaultReader],
232-
[YARN_CLASSIC, defaultReader],
233-
])
234-
})()
235-
236-
// The order of LOCKS properties IS significant as it affects iteration order.
237-
const LOCKS: Record<string, Agent> = {
238-
[BUN_LOCK]: BUN,
239-
[BUN_LOCKB]: BUN,
240-
// If both package-lock.json and npm-shrinkwrap.json are present in the root
241-
// of a project, npm-shrinkwrap.json will take precedence and package-lock.json
242-
// will be ignored.
243-
// https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json#package-lockjson-vs-npm-shrinkwrapjson
244-
[NPM_SHRINKWRAP_JSON]: NPM,
245-
[PACKAGE_LOCK_JSON]: NPM,
246-
[PNPM_LOCK_YAML]: PNPM,
247-
[YARN_LOCK]: YARN_CLASSIC,
248-
[VLT_LOCK_JSON]: VLT,
249-
// Lastly, look for a hidden lockfile which is present if .npmrc has package-lock=false:
250-
// https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json#hidden-lockfiles
251-
//
252-
// Unlike the other LOCKS keys this key contains a directory AND filename so
253-
// it has to be handled differently.
254-
[`${NODE_MODULES}/${DOT_PACKAGE_LOCK_JSON}`]: NPM,
255-
}
256-
257-
export function resolveBinPathSync(binPath: string): string {
258-
// Simple implementation that tries to resolve a bin path to its actual entry point.
259-
// This is used on Windows to resolve shims like `npm` or `npm.cmd` to their .js entry point.
260-
if (!fs.existsSync(binPath)) {
261-
return binPath
262-
}
263-
264-
try {
265-
// Try to read the file synchronously
266-
const content = fs.readFileSync(binPath, 'utf8')
267-
// Look for common patterns in npm/node shims:
268-
// - node "C:\path\to\npm-cli.js" "$@"
269-
// - "%_prog%" "%dp0%\node_modules\npm\bin\npm-cli.js" %*
270-
const nodePathMatch = content.match(
271-
/(?:node\s+["']|"%dp0%\\)([^"'\s]+(?:npm-cli|pnpm|yarn)\.(?:c?js|mjs))["'\s]/i,
272-
)
273-
if (nodePathMatch && nodePathMatch.length > 1 && nodePathMatch[1]) {
274-
const matchedPath = nodePathMatch[1]
275-
const resolvedPath = path.isAbsolute(matchedPath)
276-
? matchedPath
277-
: path.resolve(path.dirname(binPath), matchedPath)
278-
return resolvedPath
279-
}
280-
} catch {
281-
// If we can't read/parse the file, just return the original path
282-
}
283-
return binPath
284-
}
285-
286-
export function preferWindowsCmdShim(binPath: string, binName: string): string {
287-
// Only Windows uses .cmd shims
288-
if (!WIN32) {
289-
return binPath
290-
}
291-
292-
// Relative paths might be shell commands or aliases, not file paths with potential shims
293-
if (!path.isAbsolute(binPath)) {
294-
return binPath
295-
}
296-
297-
// If the path already has an extension (.exe, .bat, etc.), it is probably a Windows executable
298-
if (path.extname(binPath) !== '') {
299-
return binPath
300-
}
149+
// Lockfile registration + per-agent reader Map extracted to keep this file
150+
// under the 1000-line cap. Re-export ReadLockFile for back-compat.
151+
import {
152+
LOCKS,
153+
readLockFileByAgent,
154+
} from './lockfile-readers.mts'
301155

302-
// Ensures binPath actually points to the expected binary, not a parent directory that happens to match `binName`
303-
// For example, if binPath is C:\foo\npm\something and binName is npm, we shouldn't replace it
304-
if (path.basename(binPath).toLowerCase() !== binName.toLowerCase()) {
305-
return binPath
306-
}
156+
export type { ReadLockFile } from './lockfile-readers.mts'
307157

308-
// Finally attempt to construct a .cmd shim from binPath
309-
const cmdShim = path.join(path.dirname(binPath), `${binName}.cmd`)
158+
// Windows-shim helpers extracted to keep this file under the 1000-line cap.
159+
// Imported for local use AND re-exported so existing import paths keep working.
160+
import {
161+
preferWindowsCmdShim,
162+
resolveBinPathSync,
163+
} from './windows-shims.mts'
310164

311-
// Ensure shim exists, otherwise fallback to binPath
312-
return fs.existsSync(cmdShim) ? cmdShim : binPath
313-
}
165+
export { preferWindowsCmdShim, resolveBinPathSync }
314166

315167
export async function getAgentExecPath(agent: Agent): Promise<string> {
316168
const binName = binByAgent.get(agent)!

0 commit comments

Comments
 (0)