-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathopencode-launcher.js
More file actions
187 lines (158 loc) · 5.67 KB
/
opencode-launcher.js
File metadata and controls
187 lines (158 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env node
/**
* opencode-launcher.js — Bootstrap launcher for the OpenCode sync system.
*
* This is the entry point that wrapper scripts (opencode-push, opencode-pull,
* etc.) call. It lives at ~/.config/opencode/scripts/opencode-launcher.js at
* runtime and handles two things before delegating to the real sync logic:
*
* 1. Self-update: fetch remote, check if scripts/ changed on remote,
* and pull just those files (never touches user config).
* 2. Dependency bootstrap: ensure node_modules/ exists when scripts/package.json does.
*
* After that it execs opencode-sync-core.js with all original arguments.
*
* Design goals: minimal, stable, cross-platform (macOS + Windows), Node 18+.
*/
"use strict";
const { execSync } = require("node:child_process");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
// ---------------------------------------------------------------------------
// Paths
// ---------------------------------------------------------------------------
const configRoot =
process.env.SYNC_CONFIG_ROOT ||
path.join(os.homedir(), ".config", "opencode");
const scriptsDir = path.join(configRoot, "scripts");
const coreScript = path.join(scriptsDir, "opencode-sync-core.js");
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Run a git command in the config repo. Returns stdout (trimmed). */
function git(args) {
return execSync(`git ${args}`, {
cwd: configRoot,
encoding: "utf8",
stdio: "pipe",
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
}).trim();
}
/** Check whether the config root is a git repository. */
function isGitRepo() {
return fs.existsSync(path.join(configRoot, ".git"));
}
function normalizeArgs(argv) {
const args = [...argv];
const command = args[0];
const subcommand = args[1];
if (
(command === "push" || command === "pull") &&
(subcommand === "help" || subcommand === "status")
) {
return [subcommand, ...args.slice(2)];
}
return args;
}
// ---------------------------------------------------------------------------
// 1. Self-update check
// ---------------------------------------------------------------------------
// Runtime-critical paths inside the git repo that the launcher manages.
// If any of these change on remote, we pull them before running sync logic.
const RUNTIME_PATHS = ["scripts/"];
/**
* Fetch remote and update runtime-critical files if they changed.
* Returns the list of updated file paths (empty array if nothing changed).
*/
function selfUpdate() {
// Not a git repo yet — first-time use; skip entirely.
if (!isGitRepo()) return [];
// Fetch remote. Failure is non-fatal (offline is fine).
try {
git("fetch origin main");
} catch {
console.warn("⚠️ Fetch failed (offline?), skipping self-update");
return [];
}
// Check if remote HEAD differs from local for runtime-critical files.
let diff;
try {
diff = git(
`diff --name-only HEAD origin/main -- ${RUNTIME_PATHS.join(" ")}`
);
} catch {
// diff fails if HEAD doesn't exist yet (fresh repo). Skip.
return [];
}
if (!diff) return [];
const changed = diff.split("\n").filter(Boolean);
// Pull ONLY the runtime files — never overwrite user config.
try {
git(`checkout origin/main -- ${RUNTIME_PATHS.join(" ")}`);
console.log("🔄 Scripts updated from remote");
} catch (err) {
console.warn("⚠️ Failed to update scripts:", err.message);
return [];
}
return changed;
}
// ---------------------------------------------------------------------------
// 2. Dependency check
// ---------------------------------------------------------------------------
/**
* Ensure node_modules/ is present when scripts/package.json exists.
* Also re-installs if package.json or package-lock.json changed.
*/
function ensureDependencies(updatedFiles) {
const pkgPath = path.join(scriptsDir, "package.json");
if (!fs.existsSync(pkgPath)) return;
const modulesDir = path.join(scriptsDir, "node_modules");
const needInstall =
!fs.existsSync(modulesDir) ||
updatedFiles.some(
(f) =>
f === "scripts/package.json" || f === "scripts/package-lock.json"
);
if (!needInstall) return;
console.log("📦 Installing dependencies...");
try {
execSync("npm install", {
cwd: scriptsDir,
encoding: "utf8",
stdio: "pipe",
});
console.log("✅ Dependencies installed");
} catch (err) {
console.error("❌ npm install failed:", (err.stderr || err.message).trim());
process.exit(1);
}
}
// ---------------------------------------------------------------------------
// 3. Delegate to opencode-sync-core.js
// ---------------------------------------------------------------------------
function delegate() {
if (!fs.existsSync(coreScript)) {
console.error(`❌ Core script not found: ${coreScript}`);
console.error(" Run a pull first, or reinstall the sync scripts.");
process.exit(1);
}
const args = normalizeArgs(process.argv.slice(2)).join(" ");
try {
execSync(`node "${coreScript}" ${args}`, {
cwd: configRoot,
stdio: "inherit",
env: process.env,
});
} catch (err) {
// execSync throws on non-zero exit. The child already printed its output
// via stdio: "inherit", so just propagate the exit code.
process.exit(err.status || 1);
}
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
const updatedFiles = selfUpdate();
ensureDependencies(updatedFiles);
delegate();