import { readFileSync } from 'node:fs' import { join } from 'node:path' const publicPackages = ['packages/core', 'packages/cli', 'packages/mcp', 'packages/vue'] interface PackageJson { name: string main?: string types?: string files?: string[] bin?: Record | string exports?: unknown publishConfig?: Record } const errors: string[] = [] function readPackageJson(packageDir: string): PackageJson { return JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')) } function checkRuntimePath(packageName: string, field: string, value: string): void { if (value.endsWith('.ts') && !value.endsWith('.d.ts')) { errors.push(`${packageName}: ${field} must not point to runtime TypeScript (${value})`) } if (value.startsWith('./src/')) { errors.push(`${packageName}: ${field} must not point to source files (${value})`) } } function walkExports(packageName: string, value: unknown, path: string[] = []): void { if (typeof value === 'string') { const key = path.at(-1) if (key !== 'types' && key !== 'bun') checkRuntimePath(packageName, `exports.${path.join('.')}`, value) return } if (!value || typeof value !== 'object') return for (const [key, child] of Object.entries(value)) { walkExports(packageName, child, [...path, key]) } } for (const packageDir of publicPackages) { const pkg = readPackageJson(packageDir) if (!pkg.files?.includes('dist')) { errors.push(`${pkg.name}: files must include dist`) } if (pkg.main) checkRuntimePath(pkg.name, 'main', pkg.main) if (typeof pkg.bin === 'string') { checkRuntimePath(pkg.name, 'bin', pkg.bin) } else if (pkg.bin) { for (const [name, target] of Object.entries(pkg.bin)) { checkRuntimePath(pkg.name, `bin.${name}`, target) } } walkExports(pkg.name, pkg.exports) if ( pkg.publishConfig && ('exports' in pkg.publishConfig || 'main' in pkg.publishConfig || 'types' in pkg.publishConfig) ) { errors.push(`${pkg.name}: publishConfig must not rewrite runtime entrypoints`) } } if (errors.length > 0) { console.error(errors.join('\n')) process.exit(1) } console.log('Package metadata is publish-safe.')