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.
2122import { spawnSync } from 'node:child_process'
2223import { existsSync , mkdirSync , readFileSync , rmSync } from 'node:fs'
2324
24- import { tmpdir } from 'node:os'
25+ import os from 'node:os'
2526
2627import path from 'node:path'
2728import 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 ( / ^ c a t a l o g : \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+
287296main ( ) . then ( code => process . exit ( code ) )
0 commit comments