mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 09:37:33 +00:00
resolveCommand only checks what it resolves, so a file that also builds its own wsl.exe exec no longer passes the import check on a resolveCommand call alone. Note editor --remote wsl+<distro> launches as out of scope.
165 lines
6.0 KiB
JavaScript
165 lines
6.0 KiB
JavaScript
import { globSync, readFileSync } from 'node:fs'
|
|
import { isBuiltin } from 'node:module'
|
|
import path from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import { diffEntries, parseEntryBaseline } from './ratchet-baseline.mjs'
|
|
import { collectModuleSpecifiers } from './static-module-specifiers.mjs'
|
|
import { isTestOnlySourcePath } from './test-only-source-path.mjs'
|
|
|
|
const ROOT = path.resolve(import.meta.dirname, '..', '..')
|
|
const PACKAGE_NAME = '@orca/process-host'
|
|
const PACKAGE_DIRECTORY = 'src/packages/process-host'
|
|
const PACKAGE_SOURCE = `${PACKAGE_DIRECTORY}/src/`
|
|
const BASELINE_PATH = 'config/process-host-direct-import-baseline.txt'
|
|
// A file that builds its own wsl.exe exec must check the internal distro allowlist itself; only
|
|
// programs taken from resolveCommand are checked centrally, and those never match this pattern.
|
|
const WSL_EXE_PROGRAM = /['"`]wsl\.exe['"`]|resolveWslExecutablePath\(|buildWslExecArgs\(/
|
|
const WSL_ALLOWLIST_CHECK = 'assertWslDistroAllowed('
|
|
const SOURCE_PATTERNS = [
|
|
'*.{ts,tsx,mts,cts,js,mjs,cjs}',
|
|
...['src', 'config', 'tests', 'mobile/src', 'mobile/app', 'mobile/scripts'].map(
|
|
(directory) => `${directory}/**/*.{ts,tsx,mts,cts,js,mjs,cjs}`
|
|
)
|
|
]
|
|
|
|
function normalized(file) {
|
|
return file.replaceAll('\\', '/')
|
|
}
|
|
|
|
function packageName(specifier) {
|
|
return specifier.startsWith('@')
|
|
? specifier.split('/').slice(0, 2).join('/')
|
|
: specifier.split('/')[0]
|
|
}
|
|
|
|
function publicEntry(specifier, manifest) {
|
|
const subpath = specifier === PACKAGE_NAME ? '.' : `.${specifier.slice(PACKAGE_NAME.length)}`
|
|
return Object.hasOwn(manifest.exports ?? {}, subpath)
|
|
}
|
|
|
|
function packageRelativeImport(file, specifier) {
|
|
if (!specifier.startsWith('.')) {
|
|
return null
|
|
}
|
|
return path.posix.normalize(path.posix.join(path.posix.dirname(file), specifier))
|
|
}
|
|
|
|
export function assessProcessHostImports(
|
|
sources,
|
|
manifest,
|
|
baseline,
|
|
specifiersFor = collectModuleSpecifiers
|
|
) {
|
|
const direct = new Set()
|
|
const violations = []
|
|
const dependencies = new Set([
|
|
...Object.keys(manifest.dependencies ?? {}),
|
|
...Object.keys(manifest.optionalDependencies ?? {}),
|
|
...Object.keys(manifest.peerDependencies ?? {})
|
|
])
|
|
for (const [file, contents] of sources) {
|
|
const ownSource = file.startsWith(PACKAGE_SOURCE)
|
|
for (const sourceSpecifier of specifiersFor(file, contents)) {
|
|
const specifier = normalized(sourceSpecifier)
|
|
const local = packageRelativeImport(file, specifier)
|
|
const selfImport = specifier === PACKAGE_NAME || specifier.startsWith(`${PACKAGE_NAME}/`)
|
|
const testCorpusImport =
|
|
isTestOnlySourcePath(file) && local?.startsWith(`${PACKAGE_SOURCE}__fixtures__/`)
|
|
if (selfImport && !publicEntry(specifier, manifest)) {
|
|
violations.push(`${file}: ${sourceSpecifier} is not a public process-host export`)
|
|
}
|
|
if (
|
|
!ownSource &&
|
|
!testCorpusImport &&
|
|
(local === PACKAGE_DIRECTORY || local?.startsWith(`${PACKAGE_DIRECTORY}/`))
|
|
) {
|
|
violations.push(
|
|
`${file}: import process-host through ${PACKAGE_NAME}, not ${sourceSpecifier}`
|
|
)
|
|
}
|
|
if (ownSource) {
|
|
if (local !== null && !local.startsWith(PACKAGE_SOURCE)) {
|
|
violations.push(
|
|
`${file}: process-host cannot import outside its source: ${sourceSpecifier}`
|
|
)
|
|
} else if (path.posix.isAbsolute(specifier) || path.win32.isAbsolute(specifier)) {
|
|
violations.push(
|
|
`${file}: process-host cannot import an absolute path: ${sourceSpecifier}`
|
|
)
|
|
} else if (
|
|
local === null &&
|
|
!selfImport &&
|
|
!isBuiltin(specifier) &&
|
|
!dependencies.has(packageName(specifier)) &&
|
|
!(
|
|
isTestOnlySourcePath(file) &&
|
|
Object.hasOwn(manifest.devDependencies ?? {}, packageName(specifier))
|
|
)
|
|
) {
|
|
violations.push(`${file}: undeclared process-host dependency: ${sourceSpecifier}`)
|
|
}
|
|
} else if (
|
|
file.startsWith('src/') &&
|
|
!isTestOnlySourcePath(file) &&
|
|
(specifier === 'child_process' || specifier === 'node:child_process')
|
|
) {
|
|
direct.add(file)
|
|
}
|
|
}
|
|
}
|
|
for (const file of direct) {
|
|
const contents = sources.get(file) ?? ''
|
|
if (WSL_EXE_PROGRAM.test(contents) && !contents.includes(WSL_ALLOWLIST_CHECK)) {
|
|
violations.push(`${file}: direct wsl.exe exec must call assertWslDistroAllowed`)
|
|
}
|
|
}
|
|
return {
|
|
direct: [...direct].sort(),
|
|
...diffEntries(direct, baseline),
|
|
violations: violations.sort()
|
|
}
|
|
}
|
|
|
|
export function collectProcessHostSources(root = ROOT) {
|
|
return new Map(
|
|
globSync(SOURCE_PATTERNS, {
|
|
cwd: root,
|
|
exclude: ['**/node_modules/**', '**/.*/**', '**/dist/**', '**/out/**', '**/build/**']
|
|
})
|
|
.map(normalized)
|
|
.sort()
|
|
.map((file) => [file, readFileSync(path.join(root, file), 'utf8')])
|
|
)
|
|
}
|
|
|
|
export function main(root = ROOT, sharedSources, specifiersFor = collectModuleSpecifiers) {
|
|
const manifest = JSON.parse(
|
|
readFileSync(path.join(root, PACKAGE_DIRECTORY, 'package.json'), 'utf8')
|
|
)
|
|
const baseline = parseEntryBaseline(readFileSync(path.join(root, BASELINE_PATH), 'utf8'))
|
|
const sources = sharedSources ?? collectProcessHostSources(root)
|
|
if (
|
|
![...sources.keys()].some(
|
|
(file) => file.startsWith(PACKAGE_SOURCE) && !isTestOnlySourcePath(file)
|
|
)
|
|
) {
|
|
throw new Error(`Missing process-host implementation under ${PACKAGE_SOURCE}`)
|
|
}
|
|
const result = assessProcessHostImports(sources, manifest, baseline, specifiersFor)
|
|
const failures = [
|
|
...result.violations,
|
|
...result.added.map((file) => `${file}: new direct child_process import; use ${PACKAGE_NAME}`),
|
|
...result.stale.map((file) => `${file}: remove stale direct-import baseline entry`)
|
|
]
|
|
if (failures.length > 0) {
|
|
console.error(`[process-host-imports]\n${failures.join('\n')}`)
|
|
return 1
|
|
}
|
|
console.log(`[process-host-imports] ok; ${result.direct.length} legacy direct importers remain`)
|
|
return 0
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
process.exitCode = main()
|
|
}
|