|
| 1 | +#!/usr/bin/env node |
| 2 | +/** |
| 3 | + * ClawGod build script. |
| 4 | + * |
| 5 | + * Reads src/ files and install script templates, then produces final |
| 6 | + * install.sh and install.ps1 with heredoc content embedded. |
| 7 | + * |
| 8 | + * src/ layout: |
| 9 | + * generic/ - shared by both platforms |
| 10 | + * unix/ - install.sh only |
| 11 | + * windows/ - install.ps1 only |
| 12 | + * template/ - install.sh and install.ps1 templates |
| 13 | + * |
| 14 | + * Platform dirs override generic/ for same-named files. |
| 15 | + */ |
| 16 | + |
| 17 | +const { readFileSync, writeFileSync, existsSync } = require('fs'); |
| 18 | +const { join } = require('path'); |
| 19 | + |
| 20 | +const root = __dirname; |
| 21 | +const src = join(root, 'src'); |
| 22 | + |
| 23 | +// ─── Placeholder → source file mapping ─────────────────────────────── |
| 24 | + |
| 25 | +const PLACEHOLDER_MAP = { |
| 26 | + 'extract-natives.mjs': 'extractor.js', |
| 27 | + 'post-process.mjs': 'post-processor.js', |
| 28 | + 'repatch.mjs': 'repatcher.js', |
| 29 | + 'cli.cjs': 'wrapper.js', |
| 30 | + 'patch.mjs': 'patcher.js', |
| 31 | + 'features.json': 'features.json', |
| 32 | + 'npm-fetch.mjs': 'npm-fetch.js', |
| 33 | +}; |
| 34 | + |
| 35 | +// Build a content map for a specific platform. |
| 36 | +// Lookup order: {platform}/ → generic/ |
| 37 | +function buildContentMap(platform) { |
| 38 | + const map = {}; |
| 39 | + for (const [placeholder, filename] of Object.entries(PLACEHOLDER_MAP)) { |
| 40 | + let found = null; |
| 41 | + for (const dir of [platform, 'generic']) { |
| 42 | + const p = join(src, dir, filename); |
| 43 | + if (existsSync(p)) { found = p; break; } |
| 44 | + } |
| 45 | + if (!found) { |
| 46 | + // Some files only exist in one platform's template (e.g. npm-fetch.mjs |
| 47 | + // is PS1-only). Skip silently — if the template doesn't use the |
| 48 | + // placeholder, no harm done. |
| 49 | + continue; |
| 50 | + } |
| 51 | + map[placeholder] = readFileSync(found, 'utf8'); |
| 52 | + } |
| 53 | + return map; |
| 54 | +} |
| 55 | + |
| 56 | +// ─── Template rendering ────────────────────────────────────────────── |
| 57 | + |
| 58 | +function renderTemplate(templatePath, contentMap) { |
| 59 | + let template = readFileSync(templatePath, 'utf8'); |
| 60 | + |
| 61 | + for (const [name, content] of Object.entries(contentMap)) { |
| 62 | + const placeholder = `{{CONTENT:${name}}}`; |
| 63 | + const replacement = content.endsWith('\n') ? content.slice(0, -1) : content; |
| 64 | + // Function-form replace: avoids $$/$&/$1 special pattern interpretation. |
| 65 | + template = template.replaceAll(placeholder, () => replacement); |
| 66 | + } |
| 67 | + |
| 68 | + const unresolved = template.match(/{{CONTENT:[^}]+}}/g); |
| 69 | + if (unresolved) { |
| 70 | + throw new Error(`Unresolved placeholders in ${templatePath}: ${[...new Set(unresolved)].join(', ')}`); |
| 71 | + } |
| 72 | + |
| 73 | + return template; |
| 74 | +} |
| 75 | + |
| 76 | +// ─── Build ─────────────────────────────────────────────────────────── |
| 77 | + |
| 78 | +function build() { |
| 79 | + const shTemplate = join(src, 'template', 'install.sh'); |
| 80 | + const ps1Template = join(src, 'template', 'install.ps1'); |
| 81 | + |
| 82 | + if (!existsSync(shTemplate)) { |
| 83 | + console.error('Template not found:', shTemplate); |
| 84 | + process.exit(1); |
| 85 | + } |
| 86 | + if (!existsSync(ps1Template)) { |
| 87 | + console.error('Template not found:', ps1Template); |
| 88 | + process.exit(1); |
| 89 | + } |
| 90 | + |
| 91 | + const shMap = buildContentMap('unix'); |
| 92 | + const ps1Map = buildContentMap('windows'); |
| 93 | + |
| 94 | + const shContent = renderTemplate(shTemplate, shMap); |
| 95 | + const ps1Content = renderTemplate(ps1Template, ps1Map); |
| 96 | + |
| 97 | + writeFileSync(join(root, 'install.sh'), shContent, 'utf8'); |
| 98 | + writeFileSync(join(root, 'install.ps1'), ps1Content, 'utf8'); |
| 99 | + |
| 100 | + console.log(`Built: install.sh (${(shContent.length / 1024).toFixed(0)} KB)`); |
| 101 | + console.log(`Built: install.ps1 (${(ps1Content.length / 1024).toFixed(0)} KB)`); |
| 102 | +} |
| 103 | + |
| 104 | +build(); |
0 commit comments