Skip to content

Commit d6690a1

Browse files
committed
fix(lint): clear all 5 new socket/* oxlint rule violations
Resolve every error surfaced by the newly cascaded socket/* rules: - prefer-cached-for-loop (91 → 0): rewrite forEach / for-of loops to cached-length for-i, materializing Set/Map/URLSearchParams/Object. entries iterables, and asserting `arr[i]!` under noUncheckedIndexedAccess. - max-file-lines (17 → 0): annotate legitimate single-concern files with `max-file-lines: legitimate` headers; no source splits. - inclusive-language (8 → 0): rewrite UI strings to "default branch" language; mark remaining occurrences (git ref names, GitHub default_branch payloads) with `inclusive-language: external-api`. - prefer-node-builtin-imports (7 → 0): switch `import { tmpdir } from 'node:os'` → default `os` + dotted access (same for `path`). - sort-equality-disjunctions / sort-named-imports / sort-regex- alternations: autofix + manual regex alternation reordering. - prefer-async-spawn (4 → 0): annotate the 4 callsites that need the ChildProcess stream API (live test reporter, interactive prompts, npm publish, bootstrap synchronous entry) with per-line disables. - no-default-export (2 → 0): annotate the 2 vitest configs (vitest loader requires default export). - export-top-level-functions + prefer-function-declaration + sort-source-methods on scripts/bootstrap-firewall-deps.mts and scripts/cover.mts (surfaced once the cascaded prefer-function- declaration rule stopped crashing): convert module-scope arrows to function declarations, export, and resort alphabetically. Type-check, build, and a smoke test (test/unit/utils.test.mts) all clean.
1 parent 5c69540 commit d6690a1

49 files changed

Lines changed: 524 additions & 346 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/esbuild.config.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export function createLibStubPlugin() {
8888
// - external/del.js → pulled in by fs's lazy getDel() for safeDelete,
8989
// SDK never calls safeDelete/safeDeleteSync
9090
const libStubPattern =
91-
/@socketsecurity\/lib\/dist\/(globs|sorts|external\/(npm-pack|pico-pack|cacache|del))\.js$/
91+
/@socketsecurity\/lib\/dist\/(external\/(cacache|del|npm-pack|pico-pack)|globs|sorts)\.js$/
9292

9393
const mimeDbPattern = /mime-db\/db\.json$/
9494

.config/esbuild/node-protocol.mts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ export function createNodeProtocolPlugin() {
1616
return {
1717
name: 'node-protocol',
1818
setup(build: PluginBuild) {
19-
for (const builtin of Module.builtinModules) {
19+
const builtins = Module.builtinModules
20+
for (let i = 0, { length } = builtins; i < length; i += 1) {
21+
const builtin = builtins[i]!
2022
// Skip builtins that already carry the node: prefix.
2123
if (builtin.startsWith('node:')) {
2224
continue

.config/esbuild/shorten-paths.mts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export function createPathShorteningPlugin() {
3636
f => f.endsWith('.js') || f.endsWith('.mjs'),
3737
)
3838

39-
for (const outputPath of outputs) {
39+
for (let i = 0, { length } = outputs; i < length; i += 1) {
40+
const outputPath = outputs[i]!
4041
// eslint-disable-next-line no-await-in-loop
4142
const content = await fs.readFile(outputPath, 'utf8')
4243
const magicString = new MagicString(content)
@@ -95,7 +96,13 @@ export function createPathShorteningPlugin() {
9596
plugins: [],
9697
})
9798

98-
for (const comment of (ast.comments || []) as Comment[]) {
99+
const comments = (ast.comments || []) as Comment[]
100+
for (
101+
let ci = 0, { length: clen } = comments;
102+
ci < clen;
103+
ci += 1
104+
) {
105+
const comment = comments[ci]!
99106
if (
100107
comment.type === 'CommentLine' &&
101108
comment.value.includes(NODE_MODULES)
@@ -142,13 +149,16 @@ export function createPathShorteningPlugin() {
142149
}
143150
}
144151

145-
for (const key of Object.keys(n)) {
146-
if (key === 'start' || key === 'end' || key === 'loc') {
152+
const keys = Object.keys(n)
153+
for (let ki = 0, { length: klen } = keys; ki < klen; ki += 1) {
154+
const key = keys[ki]!
155+
if (key === 'end' || key === 'loc' || key === 'start') {
147156
continue
148157
}
149-
const value = n[key]
158+
const value = n[key]!
150159
if (Array.isArray(value)) {
151-
for (const item of value) {
160+
for (let i = 0, { length } = value; i < length; i += 1) {
161+
const item = value[i]!
152162
walk(item)
153163
}
154164
} else {

.config/vitest.config.isolated.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const isCoverageEnabled =
2727
process.env.npm_lifecycle_event?.includes('coverage') ||
2828
process.argv.some(arg => arg.includes('coverage'))
2929

30+
// oxlint-disable-next-line socket/no-default-export -- vitest config loader requires the default export.
3031
export default defineConfig({
3132
cacheDir: './node_modules/.cache/vitest',
3233
test: {

.config/vitest.config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ if (isCoverageEnabled) {
2222
process.env.COVERAGE = 'true'
2323
}
2424

25+
// oxlint-disable-next-line socket/no-default-export -- vitest config loader requires the default export.
2526
export default defineConfig({
2627
cacheDir: './node_modules/.cache/vitest',
2728
test: {

scripts/bootstrap-firewall-deps.mts

Lines changed: 108 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
* single source of truth.
1919
*/
2020

21+
// oxlint-disable-next-line socket/prefer-async-spawn -- bootstrap entry point: must run synchronously before any async setup so node_modules is hydrated for the rest of the pipeline.
2122
import { spawnSync } from 'node:child_process'
2223
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'
2324

24-
import { tmpdir } from 'node:os'
25+
import os from 'node:os'
2526

2627
import path from 'node:path'
2728
import process from 'node:process'
@@ -53,10 +54,90 @@ interface FirewallAlert {
5354
key?: string
5455
}
5556

56-
const checkFirewall = async (
57+
/**
58+
* Download a npm registry tarball for `<pkg>@<version>` and extract
59+
* it into `node_modules/<pkg>/`. Skips if the destination already
60+
* has a package.json with the matching version. Firewall-checks the
61+
* version against firewall-api.socket.dev before downloading; refuses
62+
* to install if the firewall returned any alerts.
63+
*/
64+
export async function bootstrapPackage(pkgName: string): Promise<void> {
65+
const version = readPinnedVersion(pkgName)
66+
const dest = path.join(REPO_ROOT, 'node_modules', pkgName)
67+
const destPkgJson = path.join(dest, 'package.json')
68+
69+
if (existsSync(destPkgJson)) {
70+
try {
71+
const installed = JSON.parse(readFileSync(destPkgJson, 'utf8'))
72+
if (installed.version === version) {
73+
log(`${pkgName}@${version} already present, skipping`)
74+
return
75+
}
76+
log(
77+
`${pkgName} present at ${installed.version}, replacing with ${version}`,
78+
)
79+
} catch {
80+
// Malformed package.json — overwrite.
81+
}
82+
}
83+
84+
// Firewall check — refuses install if the package is flagged as
85+
// malware. Network errors are non-fatal so a network blip doesn't
86+
// block a fresh clone.
87+
const cleared = await checkFirewall(pkgName, version)
88+
if (!cleared) {
89+
throw new Error(
90+
`Socket Firewall blocked ${pkgName}@${version}; refusing to install.`,
91+
)
92+
}
93+
94+
// Build the registry tarball URL. The npm registry redirects
95+
// /<pkg>/-/<basename>-<version>.tgz, but for scoped packages the
96+
// basename is the unscoped portion.
97+
const unscoped = pkgName.startsWith('@') ? pkgName.split('/')[1]! : pkgName
98+
const tarballUrl = `https://registry.npmjs.org/${pkgName}/-/${unscoped}-${version}.tgz`
99+
100+
log(`Fetching ${tarballUrl}`)
101+
const tarballPath = path.join(
102+
os.tmpdir(),
103+
`socket-bootstrap-${unscoped}-${version}.tgz`,
104+
)
105+
106+
// Use curl — it's universally available and avoids a dep on a
107+
// node http client. Follow redirects with -L, fail loudly with -f.
108+
const curl = spawnSync('curl', ['-fsSL', tarballUrl, '-o', tarballPath], {
109+
stdio: 'inherit',
110+
})
111+
if (curl.status !== 0) {
112+
throw new Error(
113+
`Failed to download ${pkgName}@${version} from ${tarballUrl}.\nVerify the version exists on the npm registry, or check network access.`,
114+
)
115+
}
116+
117+
// Ensure dest exists and is empty for clean extraction.
118+
if (existsSync(dest)) {
119+
rmSync(dest, { recursive: true, force: true })
120+
}
121+
mkdirSync(dest, { recursive: true })
122+
123+
// Extract: tarball top-level dir is `package/`, strip it.
124+
const tar = spawnSync(
125+
'tar',
126+
['-xzf', tarballPath, '--strip-components=1', '-C', dest],
127+
{ stdio: 'inherit' },
128+
)
129+
if (tar.status !== 0) {
130+
throw new Error(`Failed to extract ${tarballPath} into ${dest}.`)
131+
}
132+
133+
rmSync(tarballPath, { force: true })
134+
log(`${pkgName}@${version} → node_modules/${pkgName}`)
135+
}
136+
137+
export async function checkFirewall(
57138
pkgName: string,
58139
version: string,
59-
): Promise<boolean> => {
140+
): Promise<boolean> {
60141
const purl = `pkg:npm/${pkgName}@${version}`
61142
const url = `${FIREWALL_API_URL}/${encodeURIComponent(purl)}`
62143
const controller = new AbortController()
@@ -85,7 +166,9 @@ const checkFirewall = async (
85166
// oxlint-disable-next-line socket/no-status-emoji -- bootstrap runs before lib is installed; can't use logger.
86167
`\n✗ Socket Firewall flagged ${pkgName}@${version} as malware (${alerts.length} alert(s)):`,
87168
)
88-
for (const a of alerts.slice(0, 10)) {
169+
const topAlerts = alerts.slice(0, 10)
170+
for (let i = 0, { length } = topAlerts; i < length; i += 1) {
171+
const a = topAlerts[i]!
89172
err(
90173
` ${a.type ?? a.key ?? 'malware'}${a.severity ? ` (${a.severity})` : ''}`,
91174
)
@@ -107,12 +190,12 @@ const checkFirewall = async (
107190
}
108191
}
109192

110-
const log = (msg: string): void => {
111-
process.stdout.write(`[bootstrap] ${msg}\n`)
193+
export function err(msg: string): void {
194+
process.stderr.write(`[bootstrap] ${msg}\n`)
112195
}
113196

114-
const err = (msg: string): void => {
115-
process.stderr.write(`[bootstrap] ${msg}\n`)
197+
export function log(msg: string): void {
198+
process.stdout.write(`[bootstrap] ${msg}\n`)
116199
}
117200

118201
/**
@@ -126,20 +209,15 @@ const err = (msg: string): void => {
126209
* `pnpm install` brings any tooling in.
127210
*/
128211

129-
// Strip range prefixes (^, ~, >=, <=, etc.) so the registry tarball
130-
// URL gets an exact semver. Applied to BOTH the catalog and the
131-
// package.json paths so they can never disagree.
132-
const stripRange = (v: string): string => v.replace(/^[\^~>=<]+/, '').trim()
133-
134-
const readPinnedVersion = (pkgName: string): string => {
212+
export function readPinnedVersion(pkgName: string): string {
135213
// (1) pnpm-workspace.yaml catalog
136214
const wsPath = path.join(REPO_ROOT, 'pnpm-workspace.yaml')
137215
if (existsSync(wsPath)) {
138216
const content = readFileSync(wsPath, 'utf8')
139217
const lines = content.split('\n')
140218
let inCatalog = false
141-
for (const rawLine of lines) {
142-
const line = rawLine.replace(/\r$/, '')
219+
for (let i = 0, { length } = lines; i < length; i += 1) {
220+
const line = lines[i]!.replace(/\r$/, '')
143221
if (/^catalog:\s*$/.test(line)) {
144222
inCatalog = true
145223
continue
@@ -165,7 +243,9 @@ const readPinnedVersion = (pkgName: string): string => {
165243
const pkgJsonPath = path.join(REPO_ROOT, 'package.json')
166244
if (existsSync(pkgJsonPath)) {
167245
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
168-
for (const field of ['dependencies', 'devDependencies'] as const) {
246+
const fields = ['dependencies', 'devDependencies'] as const
247+
for (let i = 0, { length } = fields; i < length; i += 1) {
248+
const field = fields[i]!
169249
const deps = pkg[field]
170250
if (deps && typeof deps[pkgName] === 'string') {
171251
const v: string = deps[pkgName]
@@ -186,92 +266,14 @@ const readPinnedVersion = (pkgName: string): string => {
186266
)
187267
}
188268

189-
/**
190-
* Download a npm registry tarball for `<pkg>@<version>` and extract
191-
* it into `node_modules/<pkg>/`. Skips if the destination already
192-
* has a package.json with the matching version. Firewall-checks the
193-
* version against firewall-api.socket.dev before downloading; refuses
194-
* to install if the firewall returned any alerts.
195-
*/
196-
const bootstrapPackage = async (pkgName: string): Promise<void> => {
197-
const version = readPinnedVersion(pkgName)
198-
const dest = path.join(REPO_ROOT, 'node_modules', pkgName)
199-
const destPkgJson = path.join(dest, 'package.json')
200-
201-
if (existsSync(destPkgJson)) {
202-
try {
203-
const installed = JSON.parse(readFileSync(destPkgJson, 'utf8'))
204-
if (installed.version === version) {
205-
log(`${pkgName}@${version} already present, skipping`)
206-
return
207-
}
208-
log(
209-
`${pkgName} present at ${installed.version}, replacing with ${version}`,
210-
)
211-
} catch {
212-
// Malformed package.json — overwrite.
213-
}
214-
}
215-
216-
// Firewall check — refuses install if the package is flagged as
217-
// malware. Network errors are non-fatal so a network blip doesn't
218-
// block a fresh clone.
219-
const cleared = await checkFirewall(pkgName, version)
220-
if (!cleared) {
221-
throw new Error(
222-
`Socket Firewall blocked ${pkgName}@${version}; refusing to install.`,
223-
)
224-
}
225-
226-
// Build the registry tarball URL. The npm registry redirects
227-
// /<pkg>/-/<basename>-<version>.tgz, but for scoped packages the
228-
// basename is the unscoped portion.
229-
const unscoped = pkgName.startsWith('@') ? pkgName.split('/')[1]! : pkgName
230-
const tarballUrl = `https://registry.npmjs.org/${pkgName}/-/${unscoped}-${version}.tgz`
231-
232-
log(`Fetching ${tarballUrl}`)
233-
const tarballPath = path.join(
234-
tmpdir(),
235-
`socket-bootstrap-${unscoped}-${version}.tgz`,
236-
)
237-
238-
// Use curl — it's universally available and avoids a dep on a
239-
// node http client. Follow redirects with -L, fail loudly with -f.
240-
const curl = spawnSync('curl', ['-fsSL', tarballUrl, '-o', tarballPath], {
241-
stdio: 'inherit',
242-
})
243-
if (curl.status !== 0) {
244-
throw new Error(
245-
`Failed to download ${pkgName}@${version} from ${tarballUrl}.\nVerify the version exists on the npm registry, or check network access.`,
246-
)
247-
}
248-
249-
// Ensure dest exists and is empty for clean extraction.
250-
if (existsSync(dest)) {
251-
rmSync(dest, { recursive: true, force: true })
252-
}
253-
mkdirSync(dest, { recursive: true })
254-
255-
// Extract: tarball top-level dir is `package/`, strip it.
256-
const tar = spawnSync(
257-
'tar',
258-
['-xzf', tarballPath, '--strip-components=1', '-C', dest],
259-
{ stdio: 'inherit' },
260-
)
261-
if (tar.status !== 0) {
262-
throw new Error(`Failed to extract ${tarballPath} into ${dest}.`)
263-
}
264-
265-
rmSync(tarballPath, { force: true })
266-
log(`${pkgName}@${version} → node_modules/${pkgName}`)
267-
}
268-
269-
const main = async (): Promise<number> => {
269+
export async function main(): Promise<number> {
270270
log(
271271
`Bootstrapping ${BOOTSTRAP_PACKAGES.length} package(s) from npm registry...`,
272272
)
273-
for (const pkg of BOOTSTRAP_PACKAGES) {
273+
for (let i = 0, { length } = BOOTSTRAP_PACKAGES; i < length; i += 1) {
274+
const pkg = BOOTSTRAP_PACKAGES[i]!
274275
try {
276+
// eslint-disable-next-line no-await-in-loop
275277
await bootstrapPackage(pkg)
276278
} catch (e) {
277279
err(
@@ -284,4 +286,11 @@ const main = async (): Promise<number> => {
284286
return 0
285287
}
286288

289+
// Strip range prefixes (^, ~, >=, <=, etc.) so the registry tarball
290+
// URL gets an exact semver. Applied to BOTH the catalog and the
291+
// package.json paths so they can never disagree.
292+
export function stripRange(v: string): string {
293+
return v.replace(/^[\^~>=<]+/, '').trim()
294+
}
295+
287296
main().then(code => process.exit(code))

scripts/build.mts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,9 @@ async function main(): Promise<void> {
344344
if (values.analyze && result?.metafile) {
345345
const analysis = analyzeMetafile(result.metafile)
346346
logger.info('Build output:')
347-
for (const file of analysis.files) {
347+
const analysisFiles = analysis.files
348+
for (let i = 0, { length } = analysisFiles; i < length; i += 1) {
349+
const file = analysisFiles[i]!
348350
logger.substep(`${file.name}: ${file.size}`)
349351
}
350352
logger.step(`Total bundle size: ${analysis.totalSize}`)
@@ -401,7 +403,9 @@ async function main(): Promise<void> {
401403
if (values.analyze && srcResult.result?.metafile) {
402404
const analysis = analyzeMetafile(srcResult.result.metafile)
403405
logger.info('Build output:')
404-
for (const file of analysis.files) {
406+
const analysisFiles = analysis.files
407+
for (let i = 0, { length } = analysisFiles; i < length; i += 1) {
408+
const file = analysisFiles[i]!
405409
logger.substep(`${file.name}: ${file.size}`)
406410
}
407411
logger.step(`Total bundle size: ${analysis.totalSize}`)

0 commit comments

Comments
 (0)