@textcortex/zenocode 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/package.json +8 -8
- package/scripts/branding-patch.mjs +112 -3
- package/scripts/branding-patch.test.mjs +17 -0
- package/scripts/build-branded-opencode.mjs +520 -78
- package/scripts/build-branded-opencode.test.mjs +25 -0
- package/scripts/{run-codecortex.mjs → run-zenocode.mjs} +323 -80
- package/scripts/run-zenocode.test.mjs +224 -0
- package/scripts/run-codecortex.test.mjs +0 -59
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import process from "node:process";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { patchZenocodeBinaryText } from "./branding-patch.mjs";
|
|
8
10
|
|
|
9
|
-
const
|
|
11
|
+
const currentFilePath = realpathSync(fileURLToPath(import.meta.url));
|
|
12
|
+
const __dirname = path.dirname(currentFilePath);
|
|
10
13
|
const appRoot = path.resolve(__dirname, "..");
|
|
11
14
|
const defaultOutputDir = path.join(appRoot, ".zenocode", "brand-build");
|
|
12
15
|
|
|
@@ -50,6 +53,94 @@ function _command(name) {
|
|
|
50
53
|
return name;
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
function _packageParts(packageName) {
|
|
57
|
+
if (packageName.startsWith("@")) {
|
|
58
|
+
const [scope, baseName] = packageName.split("/");
|
|
59
|
+
return { scope, baseName };
|
|
60
|
+
}
|
|
61
|
+
return { scope: "", baseName: packageName };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _runtimeBinaryPrefix(packageName) {
|
|
65
|
+
const { baseName } = _packageParts(packageName);
|
|
66
|
+
return baseName.replace(/-ai$/, "");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function mapBrandedBinaryPackageName(originalPackageName, runtimePackageName = wrapperPackageName) {
|
|
70
|
+
if (!originalPackageName.startsWith("opencode-")) {
|
|
71
|
+
throw new Error(`Unsupported OpenCode binary package name: ${originalPackageName}`);
|
|
72
|
+
}
|
|
73
|
+
const { scope } = _packageParts(runtimePackageName);
|
|
74
|
+
const prefix = _runtimeBinaryPrefix(runtimePackageName);
|
|
75
|
+
const suffix = originalPackageName.slice("opencode".length);
|
|
76
|
+
const unscopedName = `${prefix}${suffix}`;
|
|
77
|
+
return scope ? `${scope}/${unscopedName}` : unscopedName;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function buildWrapperBinMap(packageName, binName) {
|
|
81
|
+
const { baseName } = _packageParts(packageName);
|
|
82
|
+
const binMap = {
|
|
83
|
+
[baseName]: "./bin/opencode",
|
|
84
|
+
opencode: "./bin/opencode",
|
|
85
|
+
};
|
|
86
|
+
if (binName && !(binName in binMap)) {
|
|
87
|
+
binMap[binName] = "./bin/opencode";
|
|
88
|
+
}
|
|
89
|
+
return binMap;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function compareSemver(left, right) {
|
|
93
|
+
const parse = (value) => {
|
|
94
|
+
const match = value.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
95
|
+
if (!match) return null;
|
|
96
|
+
return {
|
|
97
|
+
major: Number(match[1]),
|
|
98
|
+
minor: Number(match[2]),
|
|
99
|
+
patch: Number(match[3]),
|
|
100
|
+
prerelease: match[4] ?? null,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const lhs = parse(left);
|
|
105
|
+
const rhs = parse(right);
|
|
106
|
+
if (!lhs || !rhs) {
|
|
107
|
+
throw new Error(`Invalid semver comparison: ${left} vs ${right}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
111
|
+
if (lhs[key] > rhs[key]) return 1;
|
|
112
|
+
if (lhs[key] < rhs[key]) return -1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (lhs.prerelease === rhs.prerelease) return 0;
|
|
116
|
+
if (lhs.prerelease === null) return 1;
|
|
117
|
+
if (rhs.prerelease === null) return -1;
|
|
118
|
+
|
|
119
|
+
const leftParts = lhs.prerelease.split(".");
|
|
120
|
+
const rightParts = rhs.prerelease.split(".");
|
|
121
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
122
|
+
for (let index = 0; index < length; index += 1) {
|
|
123
|
+
const leftPart = leftParts[index];
|
|
124
|
+
const rightPart = rightParts[index];
|
|
125
|
+
if (leftPart === undefined) return -1;
|
|
126
|
+
if (rightPart === undefined) return 1;
|
|
127
|
+
const leftNumeric = /^\d+$/.test(leftPart);
|
|
128
|
+
const rightNumeric = /^\d+$/.test(rightPart);
|
|
129
|
+
if (leftNumeric && rightNumeric) {
|
|
130
|
+
const leftValue = Number(leftPart);
|
|
131
|
+
const rightValue = Number(rightPart);
|
|
132
|
+
if (leftValue > rightValue) return 1;
|
|
133
|
+
if (leftValue < rightValue) return -1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (leftNumeric && !rightNumeric) return -1;
|
|
137
|
+
if (!leftNumeric && rightNumeric) return 1;
|
|
138
|
+
if (leftPart > rightPart) return 1;
|
|
139
|
+
if (leftPart < rightPart) return -1;
|
|
140
|
+
}
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
53
144
|
async function run(command, args, options = {}) {
|
|
54
145
|
return new Promise((resolve, reject) => {
|
|
55
146
|
const child = spawn(command, args, {
|
|
@@ -68,6 +159,35 @@ async function run(command, args, options = {}) {
|
|
|
68
159
|
});
|
|
69
160
|
}
|
|
70
161
|
|
|
162
|
+
async function runAndCapture(command, args, options = {}) {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const child = spawn(command, args, {
|
|
165
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
166
|
+
...options,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
let stdout = "";
|
|
170
|
+
let stderr = "";
|
|
171
|
+
child.stdout?.on("data", (chunk) => {
|
|
172
|
+
stdout += chunk.toString("utf-8");
|
|
173
|
+
});
|
|
174
|
+
child.stderr?.on("data", (chunk) => {
|
|
175
|
+
stderr += chunk.toString("utf-8");
|
|
176
|
+
});
|
|
177
|
+
child.on("error", reject);
|
|
178
|
+
child.on("exit", (code) => {
|
|
179
|
+
if (code === 0) {
|
|
180
|
+
resolve({ stdout, stderr });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const error = new Error(`${command} ${args.join(" ")} failed with exit code ${String(code ?? 1)}`);
|
|
184
|
+
error.stdout = stdout;
|
|
185
|
+
error.stderr = stderr;
|
|
186
|
+
reject(error);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
71
191
|
async function requireCommand(command, args = ["--version"]) {
|
|
72
192
|
try {
|
|
73
193
|
await run(command, args, { stdio: "ignore" });
|
|
@@ -83,90 +203,375 @@ async function readJson(filePath) {
|
|
|
83
203
|
return JSON.parse(await fs.readFile(filePath, "utf-8"));
|
|
84
204
|
}
|
|
85
205
|
|
|
86
|
-
async function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
artifactDir,
|
|
90
|
-
packageName,
|
|
91
|
-
binName,
|
|
92
|
-
}) {
|
|
93
|
-
const distEntries = await fs.readdir(distDir, { withFileTypes: true });
|
|
94
|
-
const binaries = {};
|
|
95
|
-
const versions = new Set();
|
|
206
|
+
async function writeJson(filePath, payload) {
|
|
207
|
+
await fs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
208
|
+
}
|
|
96
209
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
210
|
+
async function maybePublishedVersion(packageName) {
|
|
211
|
+
const npmCommand = _command("npm");
|
|
212
|
+
try {
|
|
213
|
+
const result = await runAndCapture(
|
|
214
|
+
npmCommand,
|
|
215
|
+
["view", packageName, "version", "--json"],
|
|
216
|
+
{ env: { ...process.env, NODE_AUTH_TOKEN: process.env.NODE_AUTH_TOKEN || process.env.NPM_TOKEN || "" } },
|
|
217
|
+
);
|
|
218
|
+
const value = JSON.parse(result.stdout.trim() || "null");
|
|
219
|
+
if (typeof value === "string" && value) return value;
|
|
220
|
+
return null;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const stderr = String(error?.stderr || "");
|
|
223
|
+
if (stderr.includes("E404") || stderr.includes("404")) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function packPackage(packageDir) {
|
|
231
|
+
const npmCommand = _command("npm");
|
|
232
|
+
await run(npmCommand, ["pack"], { cwd: packageDir });
|
|
233
|
+
const tarballs = (await fs.readdir(packageDir)).filter((entry) => entry.endsWith(".tgz")).sort();
|
|
234
|
+
if (!tarballs.length) {
|
|
235
|
+
throw new Error(`npm pack did not produce a tarball in ${packageDir}`);
|
|
236
|
+
}
|
|
237
|
+
return path.join(packageDir, tarballs[tarballs.length - 1]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function publishTarballIfNeeded(packageName, version, tarballPath, cwd) {
|
|
241
|
+
if (!publishEnabled) return { published: false, skipped: false };
|
|
242
|
+
|
|
243
|
+
const publishedVersion = await maybePublishedVersion(packageName);
|
|
244
|
+
if (publishedVersion) {
|
|
245
|
+
const comparison = compareSemver(version, publishedVersion);
|
|
246
|
+
if (comparison < 0) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Refusing to publish ${packageName}@${version}; npm already has newer version ${publishedVersion}.`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (comparison === 0) {
|
|
252
|
+
return { published: false, skipped: true };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const npmCommand = _command("npm");
|
|
257
|
+
await run(
|
|
258
|
+
npmCommand,
|
|
259
|
+
["publish", tarballPath, "--access", "public", "--tag", publishTag, "--provenance"],
|
|
260
|
+
{ cwd },
|
|
261
|
+
);
|
|
262
|
+
return { published: true, skipped: false };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function _binaryFilename() {
|
|
266
|
+
return process.platform === "win32" ? "opencode.exe" : "opencode";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function patchBinaryAtPath(binaryPath) {
|
|
270
|
+
const buffer = await fs.readFile(binaryPath);
|
|
271
|
+
const originalLength = buffer.length;
|
|
272
|
+
const patch = patchZenocodeBinaryText(buffer.toString("latin1"));
|
|
273
|
+
if (!patch.patched) {
|
|
274
|
+
throw new Error(`Branding patch did not match binary ${binaryPath}`);
|
|
275
|
+
}
|
|
276
|
+
const nextBuffer = Buffer.from(patch.text, "latin1");
|
|
277
|
+
if (nextBuffer.length !== originalLength) {
|
|
278
|
+
throw new Error(`Branding patch changed binary length for ${binaryPath}`);
|
|
279
|
+
}
|
|
280
|
+
await fs.writeFile(binaryPath, nextBuffer);
|
|
281
|
+
if (process.platform !== "win32") {
|
|
282
|
+
await fs.chmod(binaryPath, 0o755);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function _buildWrapperExecutable({ runtimePackageName, binName }) {
|
|
287
|
+
const { scope, baseName } = _packageParts(runtimePackageName);
|
|
288
|
+
const scopeLiteral = JSON.stringify(scope);
|
|
289
|
+
const packagePrefixLiteral = JSON.stringify(_runtimeBinaryPrefix(runtimePackageName));
|
|
290
|
+
const binNameLiteral = JSON.stringify(binName || "");
|
|
291
|
+
const packageBaseLiteral = JSON.stringify(baseName);
|
|
292
|
+
|
|
293
|
+
return `#!/usr/bin/env node
|
|
294
|
+
|
|
295
|
+
const childProcess = require("child_process")
|
|
296
|
+
const fs = require("fs")
|
|
297
|
+
const path = require("path")
|
|
298
|
+
const os = require("os")
|
|
299
|
+
|
|
300
|
+
function run(target) {
|
|
301
|
+
const result = childProcess.spawnSync(target, process.argv.slice(2), {
|
|
302
|
+
stdio: "inherit",
|
|
303
|
+
})
|
|
304
|
+
if (result.error) {
|
|
305
|
+
console.error(result.error.message)
|
|
306
|
+
process.exit(1)
|
|
307
|
+
}
|
|
308
|
+
const code = typeof result.status === "number" ? result.status : 0
|
|
309
|
+
process.exit(code)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const envPath = process.env.OPENCODE_BIN_PATH
|
|
313
|
+
if (envPath) {
|
|
314
|
+
run(envPath)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const scriptPath = fs.realpathSync(__filename)
|
|
318
|
+
const scriptDir = path.dirname(scriptPath)
|
|
319
|
+
const packageScope = ${scopeLiteral}
|
|
320
|
+
const packagePrefix = ${packagePrefixLiteral}
|
|
321
|
+
const packageBaseName = ${packageBaseLiteral}
|
|
322
|
+
const brandedBinName = ${binNameLiteral}
|
|
323
|
+
|
|
324
|
+
const platformMap = {
|
|
325
|
+
darwin: "darwin",
|
|
326
|
+
linux: "linux",
|
|
327
|
+
win32: "windows",
|
|
328
|
+
}
|
|
329
|
+
const archMap = {
|
|
330
|
+
x64: "x64",
|
|
331
|
+
arm64: "arm64",
|
|
332
|
+
arm: "arm",
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let platform = platformMap[os.platform()]
|
|
336
|
+
if (!platform) {
|
|
337
|
+
platform = os.platform()
|
|
338
|
+
}
|
|
339
|
+
let arch = archMap[os.arch()]
|
|
340
|
+
if (!arch) {
|
|
341
|
+
arch = os.arch()
|
|
342
|
+
}
|
|
343
|
+
const base = packagePrefix + "-" + platform + "-" + arch
|
|
344
|
+
const binary = platform === "windows" ? "opencode.exe" : "opencode"
|
|
345
|
+
|
|
346
|
+
function supportsAvx2() {
|
|
347
|
+
if (arch !== "x64") return false
|
|
348
|
+
|
|
349
|
+
if (platform === "linux") {
|
|
100
350
|
try {
|
|
101
|
-
|
|
102
|
-
if (typeof pkg?.name === "string" && typeof pkg?.version === "string") {
|
|
103
|
-
binaries[pkg.name] = pkg.version;
|
|
104
|
-
versions.add(pkg.version);
|
|
105
|
-
}
|
|
351
|
+
return /(^|\\s)avx2(\\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
106
352
|
} catch {
|
|
107
|
-
|
|
353
|
+
return false
|
|
108
354
|
}
|
|
109
355
|
}
|
|
110
356
|
|
|
111
|
-
if (
|
|
112
|
-
|
|
357
|
+
if (platform === "darwin") {
|
|
358
|
+
try {
|
|
359
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
360
|
+
encoding: "utf8",
|
|
361
|
+
timeout: 1500,
|
|
362
|
+
})
|
|
363
|
+
if (result.status !== 0) return false
|
|
364
|
+
return (result.stdout || "").trim() === "1"
|
|
365
|
+
} catch {
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
113
368
|
}
|
|
114
369
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
370
|
+
if (platform === "windows") {
|
|
371
|
+
const cmd =
|
|
372
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
373
|
+
|
|
374
|
+
for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
375
|
+
try {
|
|
376
|
+
const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], {
|
|
377
|
+
encoding: "utf8",
|
|
378
|
+
timeout: 3000,
|
|
379
|
+
windowsHide: true,
|
|
380
|
+
})
|
|
381
|
+
if (result.status !== 0) continue
|
|
382
|
+
const out = (result.stdout || "").trim().toLowerCase()
|
|
383
|
+
if (out === "true" || out === "1") return true
|
|
384
|
+
if (out === "false" || out === "0") return false
|
|
385
|
+
} catch {
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return false
|
|
118
391
|
}
|
|
119
392
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
await fs.mkdir(path.join(wrapperDir, "bin"), { recursive: true });
|
|
393
|
+
return false
|
|
394
|
+
}
|
|
123
395
|
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
const
|
|
396
|
+
const names = (() => {
|
|
397
|
+
const avx2 = supportsAvx2()
|
|
398
|
+
const baseline = arch === "x64" && !avx2
|
|
127
399
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
400
|
+
if (platform === "linux") {
|
|
401
|
+
const musl = (() => {
|
|
402
|
+
try {
|
|
403
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
409
|
+
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
|
410
|
+
if (text.includes("musl")) return true
|
|
411
|
+
} catch {
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return false
|
|
415
|
+
})()
|
|
416
|
+
|
|
417
|
+
if (musl) {
|
|
418
|
+
if (arch === "x64") {
|
|
419
|
+
if (baseline) return [base + "-baseline-musl", base + "-musl", base + "-baseline", base]
|
|
420
|
+
return [base + "-musl", base + "-baseline-musl", base, base + "-baseline"]
|
|
421
|
+
}
|
|
422
|
+
return [base + "-musl", base]
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (arch === "x64") {
|
|
426
|
+
if (baseline) return [base + "-baseline", base, base + "-baseline-musl", base + "-musl"]
|
|
427
|
+
return [base, base + "-baseline", base + "-musl", base + "-baseline-musl"]
|
|
428
|
+
}
|
|
429
|
+
return [base, base + "-musl"]
|
|
131
430
|
}
|
|
132
431
|
|
|
432
|
+
if (arch === "x64") {
|
|
433
|
+
if (baseline) return [base + "-baseline", base]
|
|
434
|
+
return [base, base + "-baseline"]
|
|
435
|
+
}
|
|
436
|
+
return [base]
|
|
437
|
+
})()
|
|
438
|
+
|
|
439
|
+
function candidatePackageNames(name) {
|
|
440
|
+
const scopedName = packageScope ? packageScope + "/" + name : name
|
|
441
|
+
const legacyName = name.replace(new RegExp("^" + packagePrefix), "opencode")
|
|
442
|
+
return packageScope ? [scopedName, legacyName] : [scopedName]
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function resolveFromPackage(packageName) {
|
|
446
|
+
try {
|
|
447
|
+
const packageJsonPath = require.resolve(packageName + "/package.json", {
|
|
448
|
+
paths: [scriptDir],
|
|
449
|
+
})
|
|
450
|
+
const packageDir = path.dirname(packageJsonPath)
|
|
451
|
+
const candidate = path.join(packageDir, "bin", binary)
|
|
452
|
+
if (fs.existsSync(candidate)) return candidate
|
|
453
|
+
} catch {
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function findBinary() {
|
|
458
|
+
for (const name of names) {
|
|
459
|
+
for (const packageName of candidatePackageNames(name)) {
|
|
460
|
+
const candidate = resolveFromPackage(packageName)
|
|
461
|
+
if (candidate) return candidate
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const resolved = findBinary()
|
|
467
|
+
if (!resolved) {
|
|
468
|
+
const installTargets = names.flatMap((name) => candidatePackageNames(name))
|
|
469
|
+
console.error(
|
|
470
|
+
"It seems that your package manager failed to install the right version of the " +
|
|
471
|
+
packageBaseName +
|
|
472
|
+
" CLI for your platform. You can try manually installing " +
|
|
473
|
+
installTargets.map((name) => "\\""+name+"\\"").join(" or ") +
|
|
474
|
+
" package",
|
|
475
|
+
)
|
|
476
|
+
process.exit(1)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
run(resolved)
|
|
480
|
+
`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function buildWrapperPackage({
|
|
484
|
+
artifactDir,
|
|
485
|
+
licensePath,
|
|
486
|
+
packageName,
|
|
487
|
+
binName,
|
|
488
|
+
version,
|
|
489
|
+
binaryPackages,
|
|
490
|
+
}) {
|
|
491
|
+
const wrapperDir = path.join(artifactDir, "npm-package");
|
|
492
|
+
await fs.rm(wrapperDir, { recursive: true, force: true });
|
|
493
|
+
await fs.mkdir(path.join(wrapperDir, "bin"), { recursive: true });
|
|
494
|
+
|
|
133
495
|
const wrapperPkg = {
|
|
134
496
|
name: packageName,
|
|
135
497
|
version,
|
|
136
|
-
license,
|
|
498
|
+
license: "MIT",
|
|
137
499
|
type: "commonjs",
|
|
138
|
-
bin:
|
|
139
|
-
|
|
140
|
-
|
|
500
|
+
bin: buildWrapperBinMap(packageName, binName),
|
|
501
|
+
optionalDependencies: Object.fromEntries(
|
|
502
|
+
binaryPackages.map((pkg) => [pkg.brandedName, pkg.version]),
|
|
503
|
+
),
|
|
504
|
+
files: [
|
|
505
|
+
"bin",
|
|
506
|
+
"LICENSE",
|
|
507
|
+
],
|
|
141
508
|
};
|
|
142
509
|
|
|
143
|
-
await fs.copyFile(
|
|
144
|
-
|
|
510
|
+
await fs.copyFile(licensePath, path.join(wrapperDir, "LICENSE"));
|
|
511
|
+
await fs.writeFile(
|
|
145
512
|
path.join(wrapperDir, "bin", "opencode"),
|
|
513
|
+
_buildWrapperExecutable({ runtimePackageName: packageName, binName }),
|
|
514
|
+
{ encoding: "utf-8", mode: 0o755 },
|
|
146
515
|
);
|
|
147
|
-
|
|
148
|
-
path.join(
|
|
149
|
-
|
|
150
|
-
);
|
|
151
|
-
await fs.copyFile(path.join(checkoutDir, "LICENSE"), path.join(wrapperDir, "LICENSE"));
|
|
152
|
-
await fs.writeFile(path.join(wrapperDir, "package.json"), `${JSON.stringify(wrapperPkg, null, 2)}\n`, "utf-8");
|
|
516
|
+
if (process.platform !== "win32") {
|
|
517
|
+
await fs.chmod(path.join(wrapperDir, "bin", "opencode"), 0o755);
|
|
518
|
+
}
|
|
519
|
+
await writeJson(path.join(wrapperDir, "package.json"), wrapperPkg);
|
|
153
520
|
|
|
154
|
-
const
|
|
155
|
-
await
|
|
521
|
+
const tarballPath = await packPackage(wrapperDir);
|
|
522
|
+
const publishResult = await publishTarballIfNeeded(packageName, version, tarballPath, wrapperDir);
|
|
523
|
+
return { tarballPath, publishResult };
|
|
524
|
+
}
|
|
156
525
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
526
|
+
async function prepareBinaryPackages({ distDir, artifactDir, runtimePackageName }) {
|
|
527
|
+
const packageRoot = path.join(artifactDir, "npm-binaries");
|
|
528
|
+
await fs.rm(packageRoot, { recursive: true, force: true });
|
|
529
|
+
await fs.mkdir(packageRoot, { recursive: true });
|
|
530
|
+
|
|
531
|
+
const entries = await fs.readdir(distDir, { withFileTypes: true });
|
|
532
|
+
const packages = [];
|
|
533
|
+
for (const entry of entries) {
|
|
534
|
+
if (!entry.isDirectory()) continue;
|
|
535
|
+
const sourceDir = path.join(distDir, entry.name);
|
|
536
|
+
const packageJsonPath = path.join(sourceDir, "package.json");
|
|
537
|
+
let pkg;
|
|
538
|
+
try {
|
|
539
|
+
pkg = await readJson(packageJsonPath);
|
|
540
|
+
} catch {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (typeof pkg?.name !== "string" || typeof pkg?.version !== "string") {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (!pkg.name.startsWith("opencode-")) {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
161
549
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
await
|
|
165
|
-
|
|
550
|
+
const brandedName = mapBrandedBinaryPackageName(pkg.name, runtimePackageName);
|
|
551
|
+
const targetDir = path.join(packageRoot, brandedName.replace("/", "__"));
|
|
552
|
+
await fs.cp(sourceDir, targetDir, { recursive: true });
|
|
553
|
+
pkg.name = brandedName;
|
|
554
|
+
await writeJson(path.join(targetDir, "package.json"), pkg);
|
|
555
|
+
await patchBinaryAtPath(path.join(targetDir, "bin", _binaryFilename()));
|
|
556
|
+
|
|
557
|
+
const tarballPath = await packPackage(targetDir);
|
|
558
|
+
const publishResult = await publishTarballIfNeeded(brandedName, pkg.version, tarballPath, targetDir);
|
|
559
|
+
packages.push({
|
|
560
|
+
dirName: entry.name,
|
|
561
|
+
originalName: entry.name,
|
|
562
|
+
brandedName,
|
|
563
|
+
version: pkg.version,
|
|
564
|
+
packageDir: targetDir,
|
|
565
|
+
tarballPath,
|
|
566
|
+
publishResult,
|
|
166
567
|
});
|
|
167
568
|
}
|
|
168
569
|
|
|
169
|
-
|
|
570
|
+
if (!packages.length) {
|
|
571
|
+
throw new Error(`No OpenCode binary packages found under ${distDir}`);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return packages.sort((left, right) => left.originalName.localeCompare(right.originalName));
|
|
170
575
|
}
|
|
171
576
|
|
|
172
577
|
function pickCurrentPlatformBinary(distPackages) {
|
|
@@ -174,7 +579,8 @@ function pickCurrentPlatformBinary(distPackages) {
|
|
|
174
579
|
const archMap = { x64: "x64", arm64: "arm64" };
|
|
175
580
|
const expectedPlatform = platformMap[process.platform] || process.platform;
|
|
176
581
|
const expectedArch = archMap[process.arch] || process.arch;
|
|
177
|
-
const
|
|
582
|
+
const prefix = _runtimeBinaryPrefix(wrapperPackageName);
|
|
583
|
+
const expectedPrefix = `${prefix}-${expectedPlatform}-${expectedArch}`;
|
|
178
584
|
|
|
179
585
|
const exact = distPackages.find((name) => name === expectedPrefix);
|
|
180
586
|
if (exact) return exact;
|
|
@@ -187,7 +593,7 @@ function pickCurrentPlatformBinary(distPackages) {
|
|
|
187
593
|
|
|
188
594
|
async function main() {
|
|
189
595
|
if (cliArgs.includes("-h") || cliArgs.includes("--help")) {
|
|
190
|
-
console.log("Build
|
|
596
|
+
console.log("Build branded Zenocode OpenCode runtime packages from upstream.");
|
|
191
597
|
console.log("");
|
|
192
598
|
console.log("Environment variables:");
|
|
193
599
|
console.log(` ZENOCODE_OPENCODE_FORK_URL Fork URL (default: ${forkUrl})`);
|
|
@@ -196,7 +602,7 @@ async function main() {
|
|
|
196
602
|
console.log(` ZENOCODE_BRANDED_PACKAGE Wrapper package name (default: ${wrapperPackageName})`);
|
|
197
603
|
console.log(` ZENOCODE_BRANDED_BIN_NAME Extra bin name in wrapper (default: ${wrapperBinName})`);
|
|
198
604
|
console.log(" ZENOCODE_OPENCODE_BUILD_ARGS Additional args for upstream build script");
|
|
199
|
-
console.log(" ZENOCODE_PUBLISH=1 Publish
|
|
605
|
+
console.log(" ZENOCODE_PUBLISH=1 Publish packages to npm");
|
|
200
606
|
console.log(` ZENOCODE_PUBLISH_TAG npm tag (default: ${publishTag})`);
|
|
201
607
|
console.log(" Legacy CODECORTEX_* env names are still accepted for compatibility.");
|
|
202
608
|
return;
|
|
@@ -230,38 +636,64 @@ async function main() {
|
|
|
230
636
|
);
|
|
231
637
|
|
|
232
638
|
await fs.mkdir(artifactDir, { recursive: true });
|
|
233
|
-
const
|
|
234
|
-
checkoutDir,
|
|
639
|
+
const binaryPackages = await prepareBinaryPackages({
|
|
235
640
|
distDir,
|
|
236
641
|
artifactDir,
|
|
237
|
-
|
|
238
|
-
binName: wrapperBinName,
|
|
642
|
+
runtimePackageName: wrapperPackageName,
|
|
239
643
|
});
|
|
240
644
|
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!selectedPackage) {
|
|
245
|
-
throw new Error(`No build artifacts found under ${distDir}`);
|
|
645
|
+
const version = binaryPackages[0]?.version;
|
|
646
|
+
if (!version) {
|
|
647
|
+
throw new Error("Unable to determine branded runtime version");
|
|
246
648
|
}
|
|
247
649
|
|
|
248
|
-
const
|
|
249
|
-
|
|
650
|
+
const wrapperResult = await buildWrapperPackage({
|
|
651
|
+
artifactDir,
|
|
652
|
+
licensePath: path.join(checkoutDir, "LICENSE"),
|
|
653
|
+
packageName: wrapperPackageName,
|
|
654
|
+
binName: wrapperBinName,
|
|
655
|
+
version,
|
|
656
|
+
binaryPackages,
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
const selectedPackage = pickCurrentPlatformBinary(
|
|
660
|
+
binaryPackages.map((pkg) => pkg.brandedName.split("/").at(-1)),
|
|
661
|
+
);
|
|
662
|
+
const localBinaryPackage = binaryPackages.find((pkg) => pkg.brandedName.endsWith(`/${selectedPackage}`))
|
|
663
|
+
|| binaryPackages[0];
|
|
250
664
|
const localBinaryDir = path.join(artifactDir, "bin");
|
|
251
665
|
const localBinaryName = process.platform === "win32" ? "zenocode-opencode.exe" : "zenocode-opencode";
|
|
252
666
|
const localBinaryPath = path.join(localBinaryDir, localBinaryName);
|
|
253
667
|
|
|
254
668
|
await fs.mkdir(localBinaryDir, { recursive: true });
|
|
255
|
-
await fs.copyFile(
|
|
669
|
+
await fs.copyFile(
|
|
670
|
+
path.join(localBinaryPackage.packageDir, "bin", _binaryFilename()),
|
|
671
|
+
localBinaryPath,
|
|
672
|
+
);
|
|
256
673
|
if (process.platform !== "win32") {
|
|
257
674
|
await fs.chmod(localBinaryPath, 0o755);
|
|
258
675
|
}
|
|
259
676
|
|
|
260
677
|
console.log("\nZenocode branded build complete.");
|
|
261
678
|
console.log(`Branded binary: ${localBinaryPath}`);
|
|
262
|
-
console.log(`
|
|
679
|
+
console.log(`Wrapper package tarball: ${wrapperResult.tarballPath}`);
|
|
680
|
+
for (const pkg of binaryPackages) {
|
|
681
|
+
console.log(`Binary package tarball: ${pkg.tarballPath}`);
|
|
682
|
+
}
|
|
263
683
|
if (publishEnabled) {
|
|
264
|
-
|
|
684
|
+
const publishedBinaries = binaryPackages.filter((pkg) => pkg.publishResult.published).map((pkg) => pkg.brandedName);
|
|
685
|
+
const skippedBinaries = binaryPackages.filter((pkg) => pkg.publishResult.skipped).map((pkg) => pkg.brandedName);
|
|
686
|
+
if (publishedBinaries.length) {
|
|
687
|
+
console.log(`Published binary packages: ${publishedBinaries.join(", ")}`);
|
|
688
|
+
}
|
|
689
|
+
if (skippedBinaries.length) {
|
|
690
|
+
console.log(`Skipped already-published binary packages: ${skippedBinaries.join(", ")}`);
|
|
691
|
+
}
|
|
692
|
+
if (wrapperResult.publishResult.published) {
|
|
693
|
+
console.log(`Published wrapper package: ${wrapperPackageName} (tag: ${publishTag}).`);
|
|
694
|
+
} else if (wrapperResult.publishResult.skipped) {
|
|
695
|
+
console.log(`Skipped already-published wrapper package: ${wrapperPackageName}.`);
|
|
696
|
+
}
|
|
265
697
|
}
|
|
266
698
|
console.log(`\nRun with local binary:`);
|
|
267
699
|
console.log(` export ZENOCODE_OPENCODE_BIN_PATH="${localBinaryPath}"`);
|
|
@@ -272,7 +704,17 @@ async function main() {
|
|
|
272
704
|
}
|
|
273
705
|
}
|
|
274
706
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
707
|
+
function resolveExecutablePath(value) {
|
|
708
|
+
try {
|
|
709
|
+
return realpathSync(value);
|
|
710
|
+
} catch {
|
|
711
|
+
return path.resolve(value);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (process.argv[1] && resolveExecutablePath(process.argv[1]) === currentFilePath) {
|
|
716
|
+
main().catch((error) => {
|
|
717
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
718
|
+
process.exit(1);
|
|
719
|
+
});
|
|
720
|
+
}
|