|
| 1 | +import { expect, test } from 'vitest'; |
| 2 | +import fs from 'node:fs'; |
| 3 | +import path from 'node:path'; |
| 4 | + |
| 5 | +/** |
| 6 | + * Every CLI invocation eagerly evaluates the static import closure of |
| 7 | + * `src/cli.ts`. Importing `node:http` (or `node:https`) for a VALUE inside that |
| 8 | + * closure initializes undici and, under `NODE_USE_SYSTEM_CA=1`, the platform |
| 9 | + * trust store as well -- ~79ms added to every warm command on macOS, including |
| 10 | + * ones that never speak HTTP (the default daemon transport is a `node:net` |
| 11 | + * socket). The HTTP daemon transport, remote-artifact upload and download all |
| 12 | + * load these modules on demand instead. |
| 13 | + * |
| 14 | + * Type-only imports are free and stay allowed; this only rejects value imports. |
| 15 | + */ |
| 16 | + |
| 17 | +const srcRoot = path.resolve(import.meta.dirname, '..'); |
| 18 | +// `from './x.ts'` / `from "./x.ts"`, excluding `import type ... from`. |
| 19 | +const STATIC_IMPORT = |
| 20 | + /(?:^|\n)\s*(?:import|export)\s+(?!type\s)([^;]*?)\s*from\s*['"]([^'"]+)['"]/g; |
| 21 | + |
| 22 | +function collectStaticImports(source: string): { clause: string; specifier: string }[] { |
| 23 | + const found: { clause: string; specifier: string }[] = []; |
| 24 | + STATIC_IMPORT.lastIndex = 0; |
| 25 | + let match: RegExpExecArray | null = null; |
| 26 | + while ((match = STATIC_IMPORT.exec(source)) !== null) { |
| 27 | + found.push({ clause: match[1] ?? '', specifier: match[2] ?? '' }); |
| 28 | + } |
| 29 | + return found; |
| 30 | +} |
| 31 | + |
| 32 | +function resolveRelative(fromFile: string, specifier: string): string | null { |
| 33 | + const candidate = path.resolve(path.dirname(fromFile), specifier); |
| 34 | + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; |
| 35 | + for (const suffix of ['.ts', '.tsx', '/index.ts']) { |
| 36 | + const withSuffix = `${candidate}${suffix}`; |
| 37 | + if (fs.existsSync(withSuffix)) return withSuffix; |
| 38 | + } |
| 39 | + return null; |
| 40 | +} |
| 41 | + |
| 42 | +function eagerClosureOfCli(): string[] { |
| 43 | + const queue = [path.join(srcRoot, 'cli.ts')]; |
| 44 | + const visited = new Set<string>(); |
| 45 | + while (queue.length > 0) { |
| 46 | + const current = queue.pop(); |
| 47 | + if (!current || visited.has(current)) continue; |
| 48 | + visited.add(current); |
| 49 | + for (const { specifier } of collectStaticImports(fs.readFileSync(current, 'utf8'))) { |
| 50 | + if (!specifier.startsWith('.')) continue; |
| 51 | + const resolved = resolveRelative(current, specifier); |
| 52 | + if (resolved) queue.push(resolved); |
| 53 | + } |
| 54 | + } |
| 55 | + return [...visited]; |
| 56 | +} |
| 57 | + |
| 58 | +test('the CLI startup import closure never value-imports node:http or node:https', () => { |
| 59 | + const offenders: string[] = []; |
| 60 | + for (const file of eagerClosureOfCli()) { |
| 61 | + for (const { clause, specifier } of collectStaticImports(fs.readFileSync(file, 'utf8'))) { |
| 62 | + if (specifier !== 'node:http' && specifier !== 'node:https') continue; |
| 63 | + // `import type http from` is caught by the regex's negative lookahead; |
| 64 | + // `import { type IncomingMessage } from` is a value import of nothing. |
| 65 | + const importsOnlyTypes = clause |
| 66 | + .replace(/^\{|\}$/g, '') |
| 67 | + .split(',') |
| 68 | + .every((binding) => binding.trim() === '' || binding.trim().startsWith('type ')); |
| 69 | + if (importsOnlyTypes) continue; |
| 70 | + offenders.push(`${path.relative(srcRoot, file)} -> ${specifier}`); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + expect( |
| 75 | + offenders, |
| 76 | + 'Load node:http / node:https on demand instead: a value import here costs every warm CLI ' + |
| 77 | + 'command ~79ms of undici + system-CA initialization.', |
| 78 | + ).toEqual([]); |
| 79 | +}); |
| 80 | + |
| 81 | +test('the CLI startup import closure is reachable and non-trivial', () => { |
| 82 | + // Guards the test above from silently passing because the walk found nothing. |
| 83 | + expect(eagerClosureOfCli().length).toBeGreaterThan(50); |
| 84 | +}); |
0 commit comments