easy-setup.mjs
1 #!/usr/bin/env node 2 3 import fs from 'node:fs' 4 import path from 'node:path' 5 import { spawnSync } from 'node:child_process' 6 7 const isWindows = process.platform === 'win32' 8 const args = new Set(process.argv.slice(2)) 9 const startAfterSetup = args.has('--start') || args.has('--prod') 10 const productionMode = args.has('--prod') 11 const skipInstall = args.has('--skip-install') 12 const cwd = process.cwd() 13 14 function log(message) { 15 process.stdout.write(`[setup] ${message}\n`) 16 } 17 18 function fail(message, code = 1) { 19 process.stderr.write(`[setup] ERROR: ${message}\n`) 20 process.exit(code) 21 } 22 23 function run(command, commandArgs, options = {}) { 24 const printable = `${command} ${commandArgs.join(' ')}`.trim() 25 log(`$ ${printable}`) 26 const result = spawnSync(command, commandArgs, { 27 cwd, 28 stdio: 'inherit', 29 ...(isWindows && { shell: true }), 30 ...options, 31 }) 32 if (result.error) fail(result.error.message) 33 if ((result.status ?? 1) !== 0) { 34 fail(`Command failed: ${printable}`, result.status ?? 1) 35 } 36 } 37 38 function runOptional(command, commandArgs, options = {}) { 39 const printable = `${command} ${commandArgs.join(' ')}`.trim() 40 log(`$ ${printable}`) 41 const result = spawnSync(command, commandArgs, { 42 cwd, 43 stdio: 'inherit', 44 ...(isWindows && { shell: true }), 45 ...options, 46 }) 47 if (result.error || (result.status ?? 1) !== 0) { 48 log(`Optional step failed: ${printable}`) 49 return false 50 } 51 return true 52 } 53 54 function ensureNodeVersion() { 55 const version = process.versions.node 56 const [majorRaw, minorRaw] = version.split('.') 57 const major = Number.parseInt(majorRaw || '0', 10) 58 const minor = Number.parseInt(minorRaw || '0', 10) 59 if (major < 22 || (major === 22 && minor < 6)) { 60 fail(`Detected Node ${version}. SwarmClaw requires Node 22.6 or newer.`) 61 } 62 log(`Node ${version} detected.`) 63 } 64 65 function ensureNpm() { 66 const result = spawnSync('npm', ['--version'], { cwd, encoding: 'utf8', ...(isWindows && { shell: true }) }) 67 if (result.error || (result.status ?? 1) !== 0) { 68 fail('npm was not found. Install npm and rerun this setup command.') 69 } 70 log(`npm ${String(result.stdout || '').trim()} detected.`) 71 } 72 73 function commandExists(name) { 74 const lookup = process.platform === 'win32' ? 'where' : 'which' 75 const result = spawnSync(lookup, [name], { cwd, encoding: 'utf8', ...(isWindows && { shell: true }) }) 76 return !result.error && (result.status ?? 1) === 0 77 } 78 79 function ensureProjectRoot() { 80 const pkgPath = path.join(cwd, 'package.json') 81 if (!fs.existsSync(pkgPath)) { 82 fail(`package.json was not found in ${cwd}. Run this command from the SwarmClaw project root.`) 83 } 84 } 85 86 function ensureEnvFile() { 87 const envPath = path.join(cwd, '.env.local') 88 if (!fs.existsSync(envPath)) { 89 fs.writeFileSync( 90 envPath, 91 '# SwarmClaw local environment variables\n# ACCESS_KEY and CREDENTIAL_SECRET are auto-generated on first app run.\n', 92 'utf8', 93 ) 94 log('Created .env.local.') 95 } else { 96 log('.env.local already exists.') 97 } 98 } 99 100 function ensureDataDir() { 101 const dataDir = path.join(cwd, 'data') 102 fs.mkdirSync(dataDir, { recursive: true }) 103 log(`Data directory ready at ${dataDir}.`) 104 } 105 106 function printNextSteps() { 107 process.stdout.write('\n') 108 log('Setup complete.') 109 process.stdout.write('\n') 110 process.stdout.write('Next steps:\n') 111 process.stdout.write('1. Run `npm run dev`.\n') 112 process.stdout.write('2. Open http://localhost:3456 in your browser.\n') 113 process.stdout.write('3. Copy the access key printed in the terminal and finish the setup wizard.\n') 114 process.stdout.write(' Or run `npx swarmclaw setup init` for interactive CLI setup.\n') 115 process.stdout.write('4. For updates later, run `npm run update:easy`.\n') 116 } 117 118 function main() { 119 ensureProjectRoot() 120 ensureNodeVersion() 121 ensureNpm() 122 ensureDataDir() 123 ensureEnvFile() 124 125 if (!skipInstall) { 126 run('npm', ['install']) 127 } else { 128 log('Skipping dependency install (--skip-install).') 129 } 130 131 runOptional('node', ['./scripts/ensure-sandbox-browser-image.mjs']) 132 if (!commandExists('docker')) { 133 log('Docker not detected. SwarmClaw will use the host Playwright runtime until Docker Desktop is installed.') 134 } 135 136 if (productionMode) { 137 run('npm', ['run', 'build']) 138 } 139 140 if (startAfterSetup) { 141 run('npm', ['run', productionMode ? 'start' : 'dev']) 142 return 143 } 144 145 printNextSteps() 146 } 147 148 main()