#!/usr/bin/env node const { spawnSync } = require('child_process'); const REQUIRED_FILES = [ '.claude-plugin/plugin.json', '.mcp.json', 'README.md', 'bin/tihao.js', 'install.js', 'package.json', 'skill-package-manifest.json', 'memory-templates/tihao-sourcing-profile.json', 'mcp/src/server.js', 'mcp/src/features/fmode-image-analysis/image-analysis.js', 'mcp/src/tools/fmode-image-analysis.js', 'mcp/src/tools/tihao-brief-sourcing-run.js', 'mcp/src/tools/tihao-preference-update.js', 'skills/fmode-image-analysis/SKILL.md', 'skills/tihao/SKILL.md', 'skills/tihao/references/user-workflow.md', 'skills/tihao/references/output-format.md', 'docs/install-guide.md', 'docs/skill-database-record.json', 'docs/payment-package-links.md', 'docs/business-proof-action-order.zh-CN.md', 'docs/tihao-handoff-index.md' ]; const FORBIDDEN_PATH_PATTERNS = [ /^outputs?\//, /^node_modules\//, /^\.git\//, /^\.codex-run\//, /^\.npm-cache/, /^\.claude\//, /^\.vscode\//, /^dist\//, /^tmp\//, /^coverage\//, /(^|\/)\.env($|\.)/, /(^|\/)\.npmrc$/, /(^|\/)package-lock\.json$/, /\.(log|tgz|zip)$/i ]; function main() { const result = runNpmPackJson(); if (result.status !== 0) { process.stderr.write(result.stderr || result.stdout || ''); throw new Error('npm pack --dry-run --json failed'); } const pack = parsePackJson(result.stdout); assert(pack.name === '@vocmarket/tihao', 'package name should match public package'); assert(pack.version === require('../package.json').version, 'pack version should match package.json'); const files = (pack.files || []).map(file => normalizePath(file.path)); const fileSet = new Set(files); for (const file of REQUIRED_FILES) { assert(fileSet.has(file), `package should include ${file}`); } const forbidden = files.filter(file => FORBIDDEN_PATH_PATTERNS.some(pattern => pattern.test(file))); assert(forbidden.length === 0, `package should not include forbidden files: ${forbidden.join(', ')}`); assert(files.every(file => !file.startsWith('skills/tihao-creator-sourcing/')), 'package should not ship legacy skill directory'); assert(files.every(file => file !== 'bin/tihao-sourcing.js'), 'package should use tihao as the primary CLI file'); assert(files.every(file => !file.includes('sessionToken')), 'package file paths should not mention sessionToken'); assert(files.every(file => !file.includes('Authorization')), 'package file paths should not mention Authorization'); assert(files.every(file => !file.includes('\\')), 'package paths should use npm forward-slash paths'); console.log(JSON.stringify({ ok: true, name: pack.name, version: pack.version, entryCount: files.length, size: pack.size, unpackedSize: pack.unpackedSize, requiredCount: REQUIRED_FILES.length }, null, 2)); } function runNpmPackJson() { const args = ['pack', '--dry-run', '--json']; if (process.env.npm_execpath) { return spawnSync(process.execPath, [process.env.npm_execpath, ...args], { cwd: process.cwd(), encoding: 'utf8', shell: false }); } if (process.platform === 'win32') { return spawnSync('npm.cmd', args, { cwd: process.cwd(), encoding: 'utf8', shell: true }); } return spawnSync('npm', args, { cwd: process.cwd(), encoding: 'utf8', shell: false }); } function parsePackJson(stdout) { let parsed; try { parsed = JSON.parse(String(stdout || '').trim()); } catch (error) { throw new Error(`failed to parse npm pack json: ${error.message}`); } const pack = Array.isArray(parsed) ? parsed[0] : parsed; assert(pack && Array.isArray(pack.files), 'npm pack json should include files array'); return pack; } function normalizePath(file) { return String(file || '').replace(/\\/g, '/'); } function assert(condition, message) { if (!condition) throw new Error(message); } if (require.main === module) main();