Files
hy 7b057c05f8 refactor: reorganize scripts and update paths for build and release processes
- Removed obsolete test script `test_crawl4ai_fit_markdown.py`.
- Deleted `verify-package-main-obfuscation.mjs` as it is no longer needed.
- Updated paths in `app-update-publish-config.test.ts` to reflect new script locations.
- Refactored `package-content-policy.test.ts` to use new paths for content policy scripts.
- Adjusted `package-main-obfuscation.test.ts` to point to the new obfuscation script locations.
- Modified `package-size-budget.test.ts` to use updated paths for size budget scripts.
- Changed `r2-update-metadata-contract.test.ts` to reference the new metadata script.
- Updated `release-fragment-contract.test.ts` to use the new fragment contract script.
- Refactored `release-storage-layout.test.ts` to point to the new storage layout script.
- Added `runtime-trace.test.ts` to test runtime tracing functionality.
- Updated `node-pty-native-release.test.ts` to reflect new paths for native release scripts.
2026-07-20 17:03:38 +08:00

326 lines
12 KiB
JavaScript

import crypto from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
import { createRequire } from 'node:module'
import { nodeFileTrace } from '@vercel/nft'
import semver from 'semver'
const require = createRequire(import.meta.url)
const nftPackage = require('@vercel/nft/package.json')
const normalizePath = (value) => value.replaceAll(path.sep, '/')
const sha256File = (filePath) =>
crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex')
const assertString = (value, label) => {
if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} must be a string`)
}
export const loadRuntimeTracePolicy = (policyPath) => {
const policy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
if (policy.schemaVersion !== 1) throw new Error('runtime trace policy schemaVersion must be 1')
if (!Array.isArray(policy.traceIgnore))
throw new Error('runtime trace policy traceIgnore is missing')
if (!Array.isArray(policy.explicitIncludes)) {
throw new Error('runtime trace policy explicitIncludes is missing')
}
if (!Array.isArray(policy.warningRules) || policy.warningRules.length === 0) {
throw new Error('runtime trace policy warningRules are missing')
}
if (!Array.isArray(policy.requiredFiles)) {
throw new Error('runtime trace policy requiredFiles is missing')
}
const ids = new Set()
for (const rule of policy.warningRules) {
assertString(rule.id, 'warning rule id')
assertString(rule.kind, `warning rule ${rule.id} kind`)
if (!rule.package && !rule.packagePattern) {
throw new Error(`warning rule ${rule.id} must define package or packagePattern`)
}
if (rule.package) assertString(rule.package, `warning rule ${rule.id} package`)
if (rule.packagePattern) new RegExp(rule.packagePattern, 'u')
assertString(rule.reason, `warning rule ${rule.id} reason`)
if (!['parse', 'resolve'].includes(rule.kind)) {
throw new Error(`warning rule ${rule.id} has unsupported kind ${rule.kind}`)
}
if (ids.has(rule.id)) throw new Error(`duplicate runtime trace policy id: ${rule.id}`)
ids.add(rule.id)
if (rule.pathPattern) new RegExp(rule.pathPattern, 'u')
if (rule.specifierPattern) new RegExp(rule.specifierPattern, 'u')
}
for (const include of policy.explicitIncludes) {
assertString(include.package, 'explicit include package')
assertString(include.reason, `explicit include ${include.package} reason`)
assertString(include.test, `explicit include ${include.package} test`)
}
for (const required of policy.requiredFiles) {
assertString(required.id, 'required file id')
assertString(required.package, `required file ${required.id} package`)
assertString(required.path, `required file ${required.id} path`)
if (path.isAbsolute(required.path) || required.path.split(/[\\/]/u).includes('..')) {
throw new Error(`required file ${required.id} must stay inside its package`)
}
}
return policy
}
export const packageNamesFromTracePath = (filePath) => {
const parts = normalizePath(filePath).split('/')
const names = []
for (let index = 0; index < parts.length; index += 1) {
if (parts[index] !== 'node_modules') continue
const first = parts[index + 1]
if (!first || first === '.pnpm') continue
const name = first.startsWith('@') ? `${first}/${parts[index + 2] || ''}` : first
if (name && !name.endsWith('/')) names.push(name)
}
return [...new Set(names)]
}
const parseWarning = (warning) => {
const message = String(warning?.message || warning)
const parseMatch = message.match(/^Failed to parse (.+?) as (?:script|module):/u)
if (parseMatch) {
const filePath = normalizePath(parseMatch[1])
return {
kind: 'parse',
message,
filePath,
package: packageNamesFromTracePath(filePath).at(-1) || ''
}
}
const resolveMatch = message.match(
/^Failed to resolve dependency "([^"]+)":[\s\S]+? loaded from (.+)$/u
)
if (resolveMatch) {
const importerPath = normalizePath(resolveMatch[2].trim())
return {
kind: 'resolve',
message,
specifier: resolveMatch[1],
importerPath,
package: packageNamesFromTracePath(importerPath).at(-1) || ''
}
}
return { kind: 'unknown', message, package: '' }
}
const classifyWarning = (warning, rules) => {
const parsed = parseWarning(warning)
const rule = rules.find((candidate) => {
if (candidate.kind !== parsed.kind) return false
if (candidate.package && candidate.package !== parsed.package) return false
if (
candidate.packagePattern &&
!new RegExp(candidate.packagePattern, 'u').test(parsed.package)
) {
return false
}
if (
candidate.pathPattern &&
!new RegExp(candidate.pathPattern, 'u').test(parsed.filePath || '')
) {
return false
}
if (
candidate.specifierPattern &&
!new RegExp(candidate.specifierPattern, 'u').test(parsed.specifier || '')
) {
return false
}
return true
})
return { ...parsed, ruleId: rule?.id || null }
}
const findInstalledPackageJson = (rootDirectory, packageName) =>
path.join(rootDirectory, 'node_modules', ...packageName.split('/'), 'package.json')
const readInstalledPackage = (rootDirectory, packageName) => {
const packageJsonPath = findInstalledPackageJson(rootDirectory, packageName)
if (!fs.existsSync(packageJsonPath)) {
throw new Error(`runtime dependency is not installed: ${packageName}`)
}
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
if (!manifest.version) throw new Error(`runtime dependency has no version: ${packageName}`)
return { manifest, packageJsonPath }
}
const collectOptionalPeerChecks = ({ rootDirectory, tracedFiles, recursivelyDiscoveredRoots }) => {
const checks = new Map()
for (const relativePath of tracedFiles) {
if (!relativePath.endsWith('/package.json') && relativePath !== 'package.json') continue
const absolutePath = path.join(rootDirectory, relativePath)
if (!fs.existsSync(absolutePath)) continue
let manifest
try {
manifest = JSON.parse(fs.readFileSync(absolutePath, 'utf8'))
} catch {
continue
}
for (const [peerName, metadata] of Object.entries(manifest.peerDependenciesMeta || {})) {
if (!metadata?.optional || !recursivelyDiscoveredRoots.has(peerName)) continue
const range = manifest.peerDependencies?.[peerName]
if (!range) continue
const installed = readInstalledPackage(rootDirectory, peerName).manifest.version
if (!semver.satisfies(installed, range, { includePrerelease: true })) {
throw new Error(
`${manifest.name || absolutePath} requires optional peer ${peerName}@${range}, installed ${installed}`
)
}
checks.set(`${manifest.name}:${peerName}`, {
consumer: manifest.name,
peer: peerName,
range,
installed
})
}
}
return [...checks.values()].sort((left, right) =>
`${left.consumer}:${left.peer}`.localeCompare(`${right.consumer}:${right.peer}`)
)
}
export const traceRuntimeDependencies = async ({
rootDirectory,
entryPaths,
directPackageNames,
policyPath,
platform = process.platform,
arch = process.arch
}) => {
const root = path.resolve(rootDirectory)
const policy = loadRuntimeTracePolicy(policyPath)
const rootPackage = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
const entries = [...new Set(entryPaths.map((item) => path.resolve(item)))].sort()
for (const entry of entries) {
if (!entry.startsWith(`${root}${path.sep}`) || !fs.existsSync(entry)) {
throw new Error(`runtime trace entry is invalid: ${entry}`)
}
}
const trace = await nodeFileTrace(entries, {
base: root,
processCwd: root,
mixedModules: true,
ignore: policy.traceIgnore
})
const tracedFiles = [...trace.fileList].map(normalizePath).sort()
const warnings = [...trace.warnings].map((warning) =>
classifyWarning(warning, policy.warningRules)
)
const unknownWarnings = warnings.filter((warning) => !warning.ruleId)
if (unknownWarnings.length > 0) {
throw new Error(
`runtime trace has ${String(unknownWarnings.length)} unclassified warning(s):\n${unknownWarnings
.slice(0, 10)
.map((warning) => warning.message)
.join('\n\n')}`
)
}
const tracedPackageNames = new Set(tracedFiles.flatMap(packageNamesFromTracePath))
const direct = [...new Set(directPackageNames || [])].filter((name) => name !== 'electron')
const runtimeRoots = new Set(direct)
for (const name of Object.keys(rootPackage.dependencies || {})) {
if (name !== 'electron' && tracedPackageNames.has(name)) runtimeRoots.add(name)
}
for (const include of policy.explicitIncludes) runtimeRoots.add(include.package)
const missingRequiredRoots = policy.requiredFiles
.filter((required) => !runtimeRoots.has(required.package))
.map((required) => required.package)
if (missingRequiredRoots.length > 0) {
throw new Error(
`runtime trace did not reach required packages: ${[...new Set(missingRequiredRoots)].join(', ')}`
)
}
const undeclared = [...runtimeRoots].filter((name) => !rootPackage.dependencies?.[name]).sort()
if (undeclared.length > 0) {
throw new Error(
`runtime trace found undeclared production dependencies: ${undeclared.join(', ')}`
)
}
const packages = [...runtimeRoots].sort().map((name) => {
const installed = readInstalledPackage(root, name)
const sources = []
if (direct.includes(name)) sources.push('bundle-import')
if (tracedPackageNames.has(name)) sources.push('recursive-trace')
if (policy.explicitIncludes.some((include) => include.package === name)) {
sources.push('policy-include')
}
return {
name,
version: String(installed.manifest.version),
sources,
manifestSha256: sha256File(installed.packageJsonPath)
}
})
const recursivelyDiscoveredRoots = new Set(
[...runtimeRoots].filter((name) => !direct.includes(name))
)
const usedRuleIds = new Set(warnings.map((warning) => warning.ruleId))
const unusedIncludes = policy.explicitIncludes.filter(
(include) => !tracedPackageNames.has(include.package) && !direct.includes(include.package)
)
if (unusedIncludes.length > 0) {
throw new Error(
`runtime trace policy has unused explicit includes: ${unusedIncludes
.map((include) => include.package)
.join(', ')}`
)
}
const report = {
schemaVersion: 1,
tracer: { name: '@vercel/nft', version: nftPackage.version },
target: { platform, arch },
entries: entries.map((entry) => ({
path: normalizePath(path.relative(root, entry)),
sha256: sha256File(entry)
})),
packages,
optionalPeerChecks: collectOptionalPeerChecks({
rootDirectory: root,
tracedFiles,
recursivelyDiscoveredRoots
}),
requiredFiles: policy.requiredFiles,
trace: {
fileCount: tracedFiles.length,
files: tracedFiles,
ignoredWarnings: warnings
.map((warning) => ({
ruleId: warning.ruleId,
kind: warning.kind,
package: warning.package,
specifier: warning.specifier,
path: warning.filePath ? normalizePath(path.relative(root, warning.filePath)) : undefined,
message: normalizePath(warning.message).replaceAll(normalizePath(root), '<root>')
}))
.sort((left, right) =>
`${left.ruleId}:${left.package}:${left.message}`.localeCompare(
`${right.ruleId}:${right.package}:${right.message}`
)
),
usedWarningRules: [...usedRuleIds].sort()
},
policy: {
path: normalizePath(path.relative(root, policyPath)),
sha256: sha256File(policyPath),
explicitIncludes: policy.explicitIncludes
}
}
return report
}
export const writeRuntimeTraceReport = (reportPath, report) => {
fs.mkdirSync(path.dirname(reportPath), { recursive: true })
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8')
}