import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; function findKey() { // 1. Check if key provided via CLI if (process.argv[3]) return process.argv[3]; // 2. Check local project directory (gitignored) const localKey = path.join(process.cwd(), 'scripts', 'key.pem'); if (fs.existsSync(localKey)) return localKey; // 3. Check common SSH key locations in ~/.ssh const sshDir = path.join(process.env.HOME, '.ssh'); const commonKeys = ['id_rsa', 'id_ecdsa', 'id_ed25519']; for (const keyName of commonKeys) { const fullPath = path.join(sshDir, keyName); if (fs.existsSync(fullPath)) return fullPath; } return null; } const KEY_PATH = findKey(); const INPUT_SOURCE = process.argv[2] || path.join(process.cwd(), 'extension'); const TEMP_DIR = path.join(process.cwd(), 'temp_extension_build'); function pack() { console.log('šŸ“¦ Packing extension to CRX...'); if (!KEY_PATH) { console.error('āŒ Error: No private key found.'); console.log('Tried local scripts/key.pem and common ~/.ssh/id_* keys.'); console.log('You can provide a path manually: npm run pack-extension '); process.exit(1); } let extensionDir = INPUT_SOURCE; let isTemp = false; try { // If input is a zip file, unzip it first if (INPUT_SOURCE.endsWith('.zip')) { console.log('🤐 Unzipping extension...'); if (fs.existsSync(TEMP_DIR)) fs.rmSync(TEMP_DIR, { recursive: true, force: true }); fs.mkdirSync(TEMP_DIR); execSync(`unzip -o "${INPUT_SOURCE}" -d "${TEMP_DIR}"`, { stdio: 'pipe' }); extensionDir = TEMP_DIR; isTemp = true; } // Resolve actual extension directory (handle subdirs in zip) if (!fs.existsSync(path.join(extensionDir, 'manifest.json'))) { const subdirs = fs.readdirSync(extensionDir).filter(f => fs.statSync(path.join(extensionDir, f)).isDirectory()); if (subdirs.length === 1 && fs.existsSync(path.join(extensionDir, subdirs[0], 'manifest.json'))) { extensionDir = path.join(extensionDir, subdirs[0]); console.log(`šŸ“‚ Found manifest in subdirectory: ${extensionDir}`); } else { console.error(`āŒ Error: manifest.json not found in ${extensionDir}`); process.exit(1); } } // Determine output filename let outputName; if (INPUT_SOURCE.endsWith('.zip')) { outputName = path.basename(INPUT_SOURCE).replace('.zip', '.crx'); } else { const manifest = JSON.parse(fs.readFileSync(path.join(extensionDir, 'manifest.json'), 'utf8')); outputName = `tools-app-extension-v${manifest.version}.crx`; } const outputFull = path.join(process.cwd(), outputName); // Get version for logging const manifest = JSON.parse(fs.readFileSync(path.join(extensionDir, 'manifest.json'), 'utf8')); const version = manifest.version; console.log(`šŸ”‘ Using key: ${KEY_PATH}`); console.log(`šŸ“‚ Source: ${extensionDir} (v${version})`); console.log(`šŸš€ Running crx3 via npx...`); // Command: npx -y crx3 --key --crx execSync(`npx -y crx3 --key "${KEY_PATH}" --crx "${outputFull}" "${extensionDir}"`, { stdio: 'inherit' }); // Cleanup any file that crx3 might have created automatically based on temp dir name const unintendedFile = extensionDir + '.crx'; if (fs.existsSync(unintendedFile) && unintendedFile !== outputFull) { fs.unlinkSync(unintendedFile); } console.log(`\nāœ… Success! Extension packed to: ${outputFull}`); } catch (error) { console.error('\nāŒ Failed to pack extension.'); if (error.message.includes('algorithm')) { console.error('āš ļø Note: Chrome CRX format requires RSA or ECDSA (P-256) keys.'); } else { console.error('Error details:', error.message); } process.exit(1); } finally { if (isTemp && fs.existsSync(TEMP_DIR)) { fs.rmSync(TEMP_DIR, { recursive: true, force: true }); } } } pack();