acceptance.js 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { spawnSync } = require('child_process');
  6. const ROOT = path.resolve(__dirname, '..');
  7. const npmCache = path.join(os.tmpdir(), 'claude-voc-npm-cache-acceptance');
  8. function main() {
  9. fs.mkdirSync(npmCache, { recursive: true });
  10. run('npm', ['run', 'fmode:image:smoke'], ROOT);
  11. run('npm', ['run', 'smoke:budget'], ROOT);
  12. run('npm', ['run', 'smoke:trend-budget'], ROOT);
  13. run('npm', ['run', 'smoke:package'], ROOT);
  14. run('npm', ['run', 'mcp:smoke'], ROOT);
  15. run('npm', ['run', 'acceptance:installer'], ROOT);
  16. run('npm', ['pack', '--dry-run'], ROOT);
  17. const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'voc-workspace-install-'));
  18. run(process.execPath, [path.join(ROOT, 'bin', 'claude-voc.js'), 'workspace', '--smoke'], workspace);
  19. const mcpFile = path.join(workspace, '.mcp.json');
  20. const pluginRoot = path.join(workspace, '.claude', 'plugins', 'voc-intelligence');
  21. const pluginServer = path.join(pluginRoot, 'mcp', 'src', 'server.js');
  22. const skillNames = [
  23. 'xiaohongshu-trend-intelligence',
  24. 'douyin-trend-intelligence',
  25. 'voc-issue-pool',
  26. 'voc-problem-deep-dive',
  27. 'voc-content-plan',
  28. 'voc-speaking-script',
  29. 'voc-competitor-map',
  30. 'voc-business-workflow',
  31. 'fmode-image-analysis',
  32. 'voc-cost-controller'
  33. ];
  34. assert(fs.existsSync(mcpFile), 'workspace install should write .mcp.json');
  35. assert(fs.existsSync(pluginServer), 'workspace install should copy plugin server');
  36. for (const skillName of skillNames) {
  37. assert(
  38. fs.existsSync(path.join(workspace, '.claude', 'skills', skillName, 'SKILL.md')),
  39. `workspace install should write skill entry: ${skillName}`
  40. );
  41. }
  42. const mcp = JSON.parse(fs.readFileSync(mcpFile, 'utf8'));
  43. const server = mcp.mcpServers && mcp.mcpServers.voc;
  44. assert(server, 'workspace .mcp.json should register voc server');
  45. assert(Array.isArray(server.args) && path.isAbsolute(server.args[0]), 'workspace MCP server path should be absolute');
  46. assert(path.resolve(server.args[0]) === path.resolve(pluginServer), 'workspace MCP server path should point to installed plugin server');
  47. console.log(`acceptance ok: ${workspace}`);
  48. }
  49. function run(command, args, cwd) {
  50. const resolved = resolveCommand(command, args);
  51. const result = spawnSync(resolved.command, resolved.args, {
  52. cwd,
  53. stdio: 'inherit',
  54. shell: resolved.shell,
  55. env: {
  56. ...process.env,
  57. npm_config_cache: npmCache,
  58. NPM_CONFIG_CACHE: npmCache
  59. },
  60. maxBuffer: 1024 * 1024 * 100
  61. });
  62. if (result.status !== 0) {
  63. throw new Error(`${command} ${args.join(' ')} failed in ${cwd}`);
  64. }
  65. }
  66. function resolveCommand(command, args) {
  67. if (command === 'npm' && process.env.npm_execpath) {
  68. return { command: process.execPath, args: [process.env.npm_execpath, ...args], shell: false };
  69. }
  70. if (command === 'npm' && process.platform === 'win32') {
  71. return { command: 'npm.cmd', args, shell: true };
  72. }
  73. return { command, args, shell: false };
  74. }
  75. function assert(condition, message) {
  76. if (!condition) throw new Error(message);
  77. }
  78. try {
  79. main();
  80. } catch (error) {
  81. console.error(error && error.stack ? error.stack : String(error));
  82. process.exit(1);
  83. }