Files
ci/scripts/postinstall.js
T
CI 7a20784c87 fix: remove hardcoded /home/jchery paths, use __OPENCODE_DIR__ template token resolved at install time
Command markdown files now use __OPENCODE_DIR__ placeholder instead of
hardcoded user path. Both postinstall.js and install.sh perform template
replacement when copying files to ~/.config/opencode/, making CI portable
across any user/machine/container.
2026-05-29 16:08:46 +00:00

142 lines
5.0 KiB
JavaScript

#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const OPENCODE_DIR = path.join(process.env.HOME || "/root", ".config", "opencode");
function getPackageDir() {
try {
return path.resolve(__dirname, "..");
} catch {
return null;
}
}
function isGlobalInstall() {
if (process.env.npm_config_global === "true") return true;
if (process.env.npm_config_global === "1") return true;
return false;
}
function copyFile(src, dest, force, templateVars) {
if (!fs.existsSync(src)) return { copied: 0, skipped: 0 };
const dir = path.dirname(dest);
fs.mkdirSync(dir, { recursive: true });
if (fs.existsSync(dest) && !force) {
try {
const srcContent = applyTemplate(fs.readFileSync(src, "utf8"), templateVars);
const destContent = fs.readFileSync(dest, "utf8");
if (srcContent === destContent) return { copied: 0, skipped: 1 };
} catch {}
return { copied: 0, skipped: 1 };
}
const content = applyTemplate(fs.readFileSync(src, "utf8"), templateVars);
fs.writeFileSync(dest, content, "utf8");
return { copied: 1, skipped: 0 };
}
function applyTemplate(content, vars) {
if (!vars) return content;
let result = content;
for (const [key, value] of Object.entries(vars)) {
result = result.replaceAll(key, value);
}
return result;
}
function install() {
const pkgDir = getPackageDir();
if (!pkgDir) {
console.log("CI postinstall: Could not determine package directory. Skipping.");
return;
}
const opencodeDir = path.join(pkgDir, "opencode");
if (!fs.existsSync(opencodeDir)) {
console.log("CI postinstall: opencode/ directory not found. Skipping.");
return;
}
if (!isGlobalInstall()) {
console.log("CI postinstall: Not a global install. Skipping opencode integration.");
console.log(" Run `npx ci-install` or `./scripts/install.sh` to install manually.");
return;
}
const templateVars = {
__OPENCODE_DIR__: OPENCODE_DIR,
};
let copied = 0;
let skipped = 0;
function copyGlob(srcDir, destDir, pattern) {
if (!fs.existsSync(srcDir)) return;
const entries = fs.readdirSync(srcDir).filter((f) => {
if (pattern instanceof RegExp) return pattern.test(f);
return f.startsWith(pattern);
});
for (const entry of entries) {
const result = copyFile(path.join(srcDir, entry), path.join(destDir, entry), false, templateVars);
copied += result.copied;
skipped += result.skipped;
}
}
fs.mkdirSync(path.join(OPENCODE_DIR, "agents"), { recursive: true });
fs.mkdirSync(path.join(OPENCODE_DIR, "command"), { recursive: true });
fs.mkdirSync(path.join(OPENCODE_DIR, "ci", "contexts"), { recursive: true });
fs.mkdirSync(path.join(OPENCODE_DIR, "ci", "references"), { recursive: true });
fs.mkdirSync(path.join(OPENCODE_DIR, "ci", "workflows"), { recursive: true });
copyGlob(path.join(opencodeDir, "agents"), path.join(OPENCODE_DIR, "agents"), /^ci-/);
copyGlob(path.join(opencodeDir, "command"), path.join(OPENCODE_DIR, "command"), /^ci-/);
copyGlob(path.join(opencodeDir, "ci", "contexts"), path.join(OPENCODE_DIR, "ci", "contexts"), /\.md$/);
copyGlob(path.join(opencodeDir, "ci", "references"), path.join(OPENCODE_DIR, "ci", "references"), /\.md$/);
copyGlob(path.join(opencodeDir, "ci", "workflows"), path.join(OPENCODE_DIR, "ci", "workflows"), /\.md$/);
const versionFile = path.join(opencodeDir, "ci", "VERSION");
if (fs.existsSync(versionFile)) {
const result = copyFile(versionFile, path.join(OPENCODE_DIR, "ci", "VERSION"), false, templateVars);
copied += result.copied;
skipped += result.skipped;
}
const ciJsonPath = path.join(opencodeDir, "opencode.json");
const targetJsonPath = path.join(OPENCODE_DIR, "opencode.json");
if (fs.existsSync(ciJsonPath)) {
if (!fs.existsSync(targetJsonPath)) {
const content = applyTemplate(fs.readFileSync(ciJsonPath, "utf8"), templateVars);
fs.writeFileSync(targetJsonPath, content, "utf8");
} else {
try {
const existing = JSON.parse(fs.readFileSync(targetJsonPath, "utf8"));
const ciJson = JSON.parse(applyTemplate(fs.readFileSync(ciJsonPath, "utf8"), templateVars));
existing.permission = existing.permission || {};
existing.permission.read = existing.permission.read || {};
existing.permission.external_directory = existing.permission.external_directory || {};
for (const [k, v] of Object.entries(ciJson.permission?.read || {})) {
if (!existing.permission.read[k]) existing.permission.read[k] = v;
}
for (const [k, v] of Object.entries(ciJson.permission?.external_directory || {})) {
if (!existing.permission.external_directory[k]) existing.permission.external_directory[k] = v;
}
fs.writeFileSync(targetJsonPath, JSON.stringify(existing, null, 2));
} catch {}
}
}
console.log(`CI postinstall: ${copied} files installed, ${skipped} skipped.`);
}
try {
install();
} catch (err) {
console.log("CI postinstall: Non-fatal error:", err.message);
}