@textcortex/zenocode 0.1.14 → 0.1.16

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.
@@ -1,28 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from "node:child_process";
3
- import { realpathSync } from "node:fs";
4
- import fs from "node:fs/promises";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import process from "node:process";
8
- import { fileURLToPath } from "node:url";
9
- import { patchZenocodeBinaryText } from "./branding-patch.mjs";
10
- import { openCodeRef, openCodeVersion } from "./opencode-version.mjs";
2
+ import { spawn } from 'node:child_process';
3
+ import { realpathSync } from 'node:fs';
4
+ import fs from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import process from 'node:process';
8
+ import { fileURLToPath } from 'node:url';
9
+ import {
10
+ openCodeRef,
11
+ openCodeVersion,
12
+ zenocodeRuntimeVersion,
13
+ } from './opencode-version.mjs';
14
+ import { applyZenocodeSourceBranding } from './source-branding.mjs';
11
15
 
12
16
  const currentFilePath = realpathSync(fileURLToPath(import.meta.url));
13
17
  const __dirname = path.dirname(currentFilePath);
14
- const appRoot = path.resolve(__dirname, "..");
15
- const defaultOutputDir = path.join(appRoot, ".zenocode", "brand-build");
16
- export const defaultOpenCodeForkUrl = "https://github.com/sst/opencode";
18
+ const appRoot = path.resolve(__dirname, '..');
19
+ const defaultOutputDir = path.join(appRoot, '.zenocode', 'brand-build');
20
+ export const defaultOpenCodeForkUrl = 'https://github.com/sst/opencode';
17
21
  export const defaultOpenCodeRef = openCodeRef;
18
22
  export const defaultOpenCodeVersion = openCodeVersion;
19
- export const defaultOpenCodeBuildArgs = ["--skip-install"];
23
+ export { zenocodeRuntimeVersion };
24
+ export const defaultOpenCodeBuildArgs = [];
20
25
  export const trustedPublishingRepository = {
21
- type: "git",
22
- url: "git+https://github.com/textcortex/platform.git",
26
+ type: 'git',
27
+ url: 'git+https://github.com/textcortex/platform.git',
23
28
  };
24
- const displayNameOnlyBrandingRefs = new Set([defaultOpenCodeRef]);
25
-
26
29
  const forkUrl =
27
30
  process.env.ZENOCODE_OPENCODE_FORK_URL ||
28
31
  process.env.CODECORTEX_OPENCODE_FORK_URL ||
@@ -39,65 +42,70 @@ const outputDir =
39
42
  const wrapperPackageName =
40
43
  process.env.ZENOCODE_BRANDED_PACKAGE ||
41
44
  process.env.CODECORTEX_BRANDED_PACKAGE ||
42
- "@textcortex/zenocode-ai";
45
+ '@textcortex/zenocode-ai';
43
46
  const wrapperBinName =
44
47
  process.env.ZENOCODE_BRANDED_BIN_NAME ||
45
48
  process.env.CODECORTEX_BRANDED_BIN_NAME ||
46
- "zenocode";
49
+ 'zenocode';
47
50
  const publishEnabled =
48
- (process.env.ZENOCODE_PUBLISH || process.env.CODECORTEX_PUBLISH) === "1";
51
+ (process.env.ZENOCODE_PUBLISH || process.env.CODECORTEX_PUBLISH) === '1';
49
52
  const publishTag =
50
53
  process.env.ZENOCODE_PUBLISH_TAG ||
51
54
  process.env.CODECORTEX_PUBLISH_TAG ||
52
- "latest";
55
+ 'latest';
53
56
  const publishProvenance =
54
- (process.env.ZENOCODE_NPM_PROVENANCE || process.env.CODECORTEX_NPM_PROVENANCE) === "1";
57
+ (process.env.ZENOCODE_NPM_PROVENANCE ||
58
+ process.env.CODECORTEX_NPM_PROVENANCE) === '1';
55
59
  const configuredBuildArgs = (
56
60
  process.env.ZENOCODE_OPENCODE_BUILD_ARGS ||
57
61
  process.env.CODECORTEX_OPENCODE_BUILD_ARGS ||
58
- ""
62
+ ''
59
63
  ).trim();
60
64
  const buildArgs = configuredBuildArgs
61
65
  ? configuredBuildArgs.split(/\s+/).filter(Boolean)
62
66
  : defaultOpenCodeBuildArgs;
63
67
  const cliArgs = process.argv.slice(2);
64
68
 
65
- export function deriveOpenCodeVersionFromRef(ref, fallbackVersion = defaultOpenCodeVersion) {
69
+ export function deriveOpenCodeVersionFromRef(
70
+ ref,
71
+ fallbackVersion = defaultOpenCodeVersion
72
+ ) {
66
73
  const match = ref.match(/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+.*)?$/);
67
74
  return match?.[1] || fallbackVersion;
68
75
  }
69
76
 
70
- export function allowsDisplayNameOnlyBranding(ref) {
71
- return displayNameOnlyBrandingRefs.has(ref);
72
- }
73
-
74
77
  function _command(name) {
75
- if (process.platform === "win32" && ["npm", "pnpm", "npx"].includes(name)) {
78
+ if (process.platform === 'win32' && ['npm', 'pnpm', 'npx'].includes(name)) {
76
79
  return `${name}.cmd`;
77
80
  }
78
81
  return name;
79
82
  }
80
83
 
81
84
  function _packageParts(packageName) {
82
- if (packageName.startsWith("@")) {
83
- const [scope, baseName] = packageName.split("/");
85
+ if (packageName.startsWith('@')) {
86
+ const [scope, baseName] = packageName.split('/');
84
87
  return { scope, baseName };
85
88
  }
86
- return { scope: "", baseName: packageName };
89
+ return { scope: '', baseName: packageName };
87
90
  }
88
91
 
89
92
  function _runtimeBinaryPrefix(packageName) {
90
93
  const { baseName } = _packageParts(packageName);
91
- return baseName.replace(/-ai$/, "");
94
+ return baseName.replace(/-ai$/, '');
92
95
  }
93
96
 
94
- export function mapBrandedBinaryPackageName(originalPackageName, runtimePackageName = wrapperPackageName) {
95
- if (!originalPackageName.startsWith("opencode-")) {
96
- throw new Error(`Unsupported OpenCode binary package name: ${originalPackageName}`);
97
+ export function mapBrandedBinaryPackageName(
98
+ originalPackageName,
99
+ runtimePackageName = wrapperPackageName
100
+ ) {
101
+ if (!originalPackageName.startsWith('opencode-')) {
102
+ throw new Error(
103
+ `Unsupported OpenCode binary package name: ${originalPackageName}`
104
+ );
97
105
  }
98
106
  const { scope } = _packageParts(runtimePackageName);
99
107
  const prefix = _runtimeBinaryPrefix(runtimePackageName);
100
- const suffix = originalPackageName.slice("opencode".length);
108
+ const suffix = originalPackageName.slice('opencode'.length);
101
109
  const unscopedName = `${prefix}${suffix}`;
102
110
  return scope ? `${scope}/${unscopedName}` : unscopedName;
103
111
  }
@@ -105,18 +113,20 @@ export function mapBrandedBinaryPackageName(originalPackageName, runtimePackageN
105
113
  export function buildWrapperBinMap(packageName, binName) {
106
114
  const { baseName } = _packageParts(packageName);
107
115
  const binMap = {
108
- [baseName]: "./bin/opencode",
109
- opencode: "./bin/opencode",
116
+ [baseName]: './bin/opencode',
117
+ opencode: './bin/opencode',
110
118
  };
111
119
  if (binName && !(binName in binMap)) {
112
- binMap[binName] = "./bin/opencode";
120
+ binMap[binName] = './bin/opencode';
113
121
  }
114
122
  return binMap;
115
123
  }
116
124
 
117
125
  function compareSemver(left, right) {
118
126
  const parse = (value) => {
119
- const match = value.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
127
+ const match = value.match(
128
+ /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/
129
+ );
120
130
  if (!match) return null;
121
131
  return {
122
132
  major: Number(match[1]),
@@ -132,7 +142,7 @@ function compareSemver(left, right) {
132
142
  throw new Error(`Invalid semver comparison: ${left} vs ${right}`);
133
143
  }
134
144
 
135
- for (const key of ["major", "minor", "patch"]) {
145
+ for (const key of ['major', 'minor', 'patch']) {
136
146
  if (lhs[key] > rhs[key]) return 1;
137
147
  if (lhs[key] < rhs[key]) return -1;
138
148
  }
@@ -141,8 +151,8 @@ function compareSemver(left, right) {
141
151
  if (lhs.prerelease === null) return 1;
142
152
  if (rhs.prerelease === null) return -1;
143
153
 
144
- const leftParts = lhs.prerelease.split(".");
145
- const rightParts = rhs.prerelease.split(".");
154
+ const leftParts = lhs.prerelease.split('.');
155
+ const rightParts = rhs.prerelease.split('.');
146
156
  const length = Math.max(leftParts.length, rightParts.length);
147
157
  for (let index = 0; index < length; index += 1) {
148
158
  const leftPart = leftParts[index];
@@ -169,17 +179,21 @@ function compareSemver(left, right) {
169
179
  async function run(command, args, options = {}) {
170
180
  return new Promise((resolve, reject) => {
171
181
  const child = spawn(command, args, {
172
- stdio: "inherit",
182
+ stdio: 'inherit',
173
183
  ...options,
174
184
  });
175
185
 
176
- child.on("error", reject);
177
- child.on("exit", (code) => {
186
+ child.on('error', reject);
187
+ child.on('exit', (code) => {
178
188
  if (code === 0) {
179
189
  resolve();
180
190
  return;
181
191
  }
182
- reject(new Error(`${command} ${args.join(" ")} failed with exit code ${String(code ?? 1)}`));
192
+ reject(
193
+ new Error(
194
+ `${command} ${args.join(' ')} failed with exit code ${String(code ?? 1)}`
195
+ )
196
+ );
183
197
  });
184
198
  });
185
199
  }
@@ -187,25 +201,27 @@ async function run(command, args, options = {}) {
187
201
  async function runAndCapture(command, args, options = {}) {
188
202
  return new Promise((resolve, reject) => {
189
203
  const child = spawn(command, args, {
190
- stdio: ["ignore", "pipe", "pipe"],
204
+ stdio: ['ignore', 'pipe', 'pipe'],
191
205
  ...options,
192
206
  });
193
207
 
194
- let stdout = "";
195
- let stderr = "";
196
- child.stdout?.on("data", (chunk) => {
197
- stdout += chunk.toString("utf-8");
208
+ let stdout = '';
209
+ let stderr = '';
210
+ child.stdout?.on('data', (chunk) => {
211
+ stdout += chunk.toString('utf-8');
198
212
  });
199
- child.stderr?.on("data", (chunk) => {
200
- stderr += chunk.toString("utf-8");
213
+ child.stderr?.on('data', (chunk) => {
214
+ stderr += chunk.toString('utf-8');
201
215
  });
202
- child.on("error", reject);
203
- child.on("exit", (code) => {
216
+ child.on('error', reject);
217
+ child.on('exit', (code) => {
204
218
  if (code === 0) {
205
219
  resolve({ stdout, stderr });
206
220
  return;
207
221
  }
208
- const error = new Error(`${command} ${args.join(" ")} failed with exit code ${String(code ?? 1)}`);
222
+ const error = new Error(
223
+ `${command} ${args.join(' ')} failed with exit code ${String(code ?? 1)}`
224
+ );
209
225
  error.stdout = stdout;
210
226
  error.stderr = stderr;
211
227
  reject(error);
@@ -213,11 +229,11 @@ async function runAndCapture(command, args, options = {}) {
213
229
  });
214
230
  }
215
231
 
216
- async function requireCommand(command, args = ["--version"]) {
232
+ async function requireCommand(command, args = ['--version']) {
217
233
  try {
218
- await run(command, args, { stdio: "ignore" });
234
+ await run(command, args, { stdio: 'ignore' });
219
235
  } catch (error) {
220
- if (error?.code === "ENOENT") {
236
+ if (error?.code === 'ENOENT') {
221
237
  throw new Error(`Missing required command: ${command}`);
222
238
  }
223
239
  throw error;
@@ -225,41 +241,57 @@ async function requireCommand(command, args = ["--version"]) {
225
241
  }
226
242
 
227
243
  async function readJson(filePath) {
228
- return JSON.parse(await fs.readFile(filePath, "utf-8"));
244
+ return JSON.parse(await fs.readFile(filePath, 'utf-8'));
229
245
  }
230
246
 
231
247
  async function writeJson(filePath, payload) {
232
- await fs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
248
+ await fs.writeFile(
249
+ filePath,
250
+ `${JSON.stringify(payload, null, 2)}\n`,
251
+ 'utf-8'
252
+ );
233
253
  }
234
254
 
235
255
  export function selectLatestTarball(entries) {
236
- const tarballs = entries.filter((entry) => entry.endsWith(".tgz")).sort((left, right) => {
237
- const leftVersion = left.match(/-(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+.*)?\.tgz$/)?.[1];
238
- const rightVersion = right.match(/-(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+.*)?\.tgz$/)?.[1];
239
- if (leftVersion && rightVersion) {
240
- return compareSemver(leftVersion, rightVersion);
241
- }
242
- if (leftVersion) return 1;
243
- if (rightVersion) return -1;
244
- return left.localeCompare(right);
245
- });
256
+ const tarballs = entries
257
+ .filter((entry) => entry.endsWith('.tgz'))
258
+ .sort((left, right) => {
259
+ const leftVersion = left.match(
260
+ /-(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+.*)?\.tgz$/
261
+ )?.[1];
262
+ const rightVersion = right.match(
263
+ /-(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+.*)?\.tgz$/
264
+ )?.[1];
265
+ if (leftVersion && rightVersion) {
266
+ return compareSemver(leftVersion, rightVersion);
267
+ }
268
+ if (leftVersion) return 1;
269
+ if (rightVersion) return -1;
270
+ return left.localeCompare(right);
271
+ });
246
272
  return tarballs[tarballs.length - 1] || null;
247
273
  }
248
274
 
249
275
  async function maybePublishedVersion(packageName) {
250
- const npmCommand = _command("npm");
276
+ const npmCommand = _command('npm');
251
277
  try {
252
278
  const result = await runAndCapture(
253
279
  npmCommand,
254
- ["view", packageName, "version", "--json"],
255
- { env: { ...process.env, NODE_AUTH_TOKEN: process.env.NODE_AUTH_TOKEN || process.env.NPM_TOKEN || "" } },
280
+ ['view', packageName, 'version', '--json'],
281
+ {
282
+ env: {
283
+ ...process.env,
284
+ NODE_AUTH_TOKEN:
285
+ process.env.NODE_AUTH_TOKEN || process.env.NPM_TOKEN || '',
286
+ },
287
+ }
256
288
  );
257
- const value = JSON.parse(result.stdout.trim() || "null");
258
- if (typeof value === "string" && value) return value;
289
+ const value = JSON.parse(result.stdout.trim() || 'null');
290
+ if (typeof value === 'string' && value) return value;
259
291
  return null;
260
292
  } catch (error) {
261
- const stderr = String(error?.stderr || "");
262
- if (stderr.includes("E404") || stderr.includes("404")) {
293
+ const stderr = String(error?.stderr || '');
294
+ if (stderr.includes('E404') || stderr.includes('404')) {
263
295
  return null;
264
296
  }
265
297
  throw error;
@@ -267,8 +299,8 @@ async function maybePublishedVersion(packageName) {
267
299
  }
268
300
 
269
301
  async function packPackage(packageDir) {
270
- const npmCommand = _command("npm");
271
- await run(npmCommand, ["pack"], { cwd: packageDir });
302
+ const npmCommand = _command('npm');
303
+ await run(npmCommand, ['pack'], { cwd: packageDir });
272
304
  const tarball = selectLatestTarball(await fs.readdir(packageDir));
273
305
  if (!tarball) {
274
306
  throw new Error(`npm pack did not produce a tarball in ${packageDir}`);
@@ -276,13 +308,16 @@ async function packPackage(packageDir) {
276
308
  return path.join(packageDir, tarball);
277
309
  }
278
310
 
279
- export function buildPublishCommandArgs(tarballPath, { tag, provenance = false } = {}) {
280
- const args = ["publish", tarballPath, "--access", "public"];
311
+ export function buildPublishCommandArgs(
312
+ tarballPath,
313
+ { tag, provenance = false } = {}
314
+ ) {
315
+ const args = ['publish', tarballPath, '--access', 'public'];
281
316
  if (provenance) {
282
- args.push("--provenance");
317
+ args.push('--provenance');
283
318
  }
284
319
  if (tag) {
285
- args.push("--tag", tag);
320
+ args.push('--tag', tag);
286
321
  }
287
322
  return args;
288
323
  }
@@ -295,7 +330,7 @@ async function publishTarballIfNeeded(packageName, version, tarballPath, cwd) {
295
330
  const comparison = compareSemver(version, publishedVersion);
296
331
  if (comparison < 0) {
297
332
  throw new Error(
298
- `Refusing to publish ${packageName}@${version}; npm already has newer version ${publishedVersion}.`,
333
+ `Refusing to publish ${packageName}@${version}; npm already has newer version ${publishedVersion}.`
299
334
  );
300
335
  }
301
336
  if (comparison === 0) {
@@ -303,14 +338,21 @@ async function publishTarballIfNeeded(packageName, version, tarballPath, cwd) {
303
338
  }
304
339
  }
305
340
 
306
- const npmCommand = _command("npm");
307
- await run(npmCommand, buildPublishCommandArgs(tarballPath, { tag: publishTag, provenance: publishProvenance }), { cwd });
341
+ const npmCommand = _command('npm');
342
+ await run(
343
+ npmCommand,
344
+ buildPublishCommandArgs(tarballPath, {
345
+ tag: publishTag,
346
+ provenance: publishProvenance,
347
+ }),
348
+ { cwd }
349
+ );
308
350
  return { published: true, skipped: false };
309
351
  }
310
352
 
311
353
  async function readPublishTarget(packageDir) {
312
- const pkg = await readJson(path.join(packageDir, "package.json"));
313
- if (typeof pkg?.name !== "string" || typeof pkg?.version !== "string") {
354
+ const pkg = await readJson(path.join(packageDir, 'package.json'));
355
+ if (typeof pkg?.name !== 'string' || typeof pkg?.version !== 'string') {
314
356
  throw new Error(`Invalid package metadata in ${packageDir}`);
315
357
  }
316
358
  const tarball = selectLatestTarball(await fs.readdir(packageDir));
@@ -326,7 +368,7 @@ async function readPublishTarget(packageDir) {
326
368
  }
327
369
 
328
370
  async function publishExistingPackages(artifactDir) {
329
- const packageRoot = path.join(artifactDir, "npm-binaries");
371
+ const packageRoot = path.join(artifactDir, 'npm-binaries');
330
372
  const entries = await fs.readdir(packageRoot, { withFileTypes: true });
331
373
  const binaryPackageDirs = entries
332
374
  .filter((entry) => entry.isDirectory())
@@ -340,7 +382,7 @@ async function publishExistingPackages(artifactDir) {
340
382
  for (const packageDir of binaryPackageDirs) {
341
383
  targets.push(await readPublishTarget(packageDir));
342
384
  }
343
- targets.push(await readPublishTarget(path.join(artifactDir, "npm-package")));
385
+ targets.push(await readPublishTarget(path.join(artifactDir, 'npm-package')));
344
386
 
345
387
  const published = [];
346
388
  const skipped = [];
@@ -349,7 +391,7 @@ async function publishExistingPackages(artifactDir) {
349
391
  target.packageName,
350
392
  target.version,
351
393
  target.tarballPath,
352
- target.cwd,
394
+ target.cwd
353
395
  );
354
396
  if (result.published) {
355
397
  published.push(target.packageName);
@@ -359,53 +401,24 @@ async function publishExistingPackages(artifactDir) {
359
401
  }
360
402
 
361
403
  if (published.length) {
362
- console.log(`Published packages: ${published.join(", ")}`);
404
+ console.log(`Published packages: ${published.join(', ')}`);
363
405
  }
364
406
  if (skipped.length) {
365
- console.log(`Skipped already-published packages: ${skipped.join(", ")}`);
407
+ console.log(`Skipped already-published packages: ${skipped.join(', ')}`);
366
408
  }
367
409
  }
368
410
 
369
- function _binaryFilename() {
370
- return process.platform === "win32" ? "opencode.exe" : "opencode";
371
- }
372
-
373
- async function adHocSignBinary(binaryPath) {
374
- if (process.platform !== "darwin") return;
375
- await run("codesign", ["--force", "--sign", "-", binaryPath], {
376
- stdio: "ignore",
377
- });
378
- }
379
-
380
- async function patchBinaryAtPath(binaryPath) {
381
- const buffer = await fs.readFile(binaryPath);
382
- const originalLength = buffer.length;
383
- const patch = patchZenocodeBinaryText(buffer.toString("latin1"));
384
- if (!patch.patched) {
385
- throw new Error(`Branding patch did not match binary ${binaryPath}`);
386
- }
387
- const patchedMandatoryBranding = patch.patches.logo || patch.patches.footer;
388
- if (!patchedMandatoryBranding && !allowsDisplayNameOnlyBranding(forkRef)) {
389
- throw new Error(
390
- `Branding patch only matched display names for ${binaryPath}; update the logo/footer patch signatures before building ${forkRef}.`,
391
- );
392
- }
393
- const nextBuffer = Buffer.from(patch.text, "latin1");
394
- if (nextBuffer.length !== originalLength) {
395
- throw new Error(`Branding patch changed binary length for ${binaryPath}`);
396
- }
397
- await fs.writeFile(binaryPath, nextBuffer);
398
- if (process.platform !== "win32") {
399
- await fs.chmod(binaryPath, 0o755);
400
- }
401
- await adHocSignBinary(binaryPath);
411
+ export function binaryFilenameForPackage(packageName) {
412
+ return packageName.includes('-windows-') ? 'opencode.exe' : 'opencode';
402
413
  }
403
414
 
404
- function _buildWrapperExecutable({ runtimePackageName, binName }) {
415
+ /** Build the npm dispatcher that selects only a Zenocode-owned platform binary. */
416
+ export function buildWrapperExecutable({ runtimePackageName }) {
405
417
  const { scope, baseName } = _packageParts(runtimePackageName);
406
418
  const scopeLiteral = JSON.stringify(scope);
407
- const packagePrefixLiteral = JSON.stringify(_runtimeBinaryPrefix(runtimePackageName));
408
- const binNameLiteral = JSON.stringify(binName || "");
419
+ const packagePrefixLiteral = JSON.stringify(
420
+ _runtimeBinaryPrefix(runtimePackageName)
421
+ );
409
422
  const packageBaseLiteral = JSON.stringify(baseName);
410
423
 
411
424
  return `#!/usr/bin/env node
@@ -427,17 +440,11 @@ function run(target) {
427
440
  process.exit(code)
428
441
  }
429
442
 
430
- const envPath = process.env.OPENCODE_BIN_PATH
431
- if (envPath) {
432
- run(envPath)
433
- }
434
-
435
443
  const scriptPath = fs.realpathSync(__filename)
436
444
  const scriptDir = path.dirname(scriptPath)
437
445
  const packageScope = ${scopeLiteral}
438
446
  const packagePrefix = ${packagePrefixLiteral}
439
447
  const packageBaseName = ${packageBaseLiteral}
440
- const brandedBinName = ${binNameLiteral}
441
448
 
442
449
  const platformMap = {
443
450
  darwin: "darwin",
@@ -556,8 +563,7 @@ const names = (() => {
556
563
 
557
564
  function candidatePackageNames(name) {
558
565
  const scopedName = packageScope ? packageScope + "/" + name : name
559
- const legacyName = name.replace(new RegExp("^" + packagePrefix), "opencode")
560
- return packageScope ? [scopedName, legacyName] : [scopedName]
566
+ return [scopedName]
561
567
  }
562
568
 
563
569
  function resolveFromPackage(packageName) {
@@ -606,44 +612,51 @@ async function buildWrapperPackage({
606
612
  version,
607
613
  binaryPackages,
608
614
  }) {
609
- const wrapperDir = path.join(artifactDir, "npm-package");
615
+ const wrapperDir = path.join(artifactDir, 'npm-package');
610
616
  await fs.rm(wrapperDir, { recursive: true, force: true });
611
- await fs.mkdir(path.join(wrapperDir, "bin"), { recursive: true });
617
+ await fs.mkdir(path.join(wrapperDir, 'bin'), { recursive: true });
612
618
 
613
619
  const wrapperPkg = {
614
620
  name: packageName,
615
621
  version,
616
- license: "MIT",
617
- type: "commonjs",
622
+ license: 'MIT',
623
+ type: 'commonjs',
618
624
  repository: trustedPublishingRepository,
619
625
  bin: buildWrapperBinMap(packageName, binName),
620
626
  optionalDependencies: Object.fromEntries(
621
- binaryPackages.map((pkg) => [pkg.brandedName, pkg.version]),
627
+ binaryPackages.map((pkg) => [pkg.brandedName, pkg.version])
622
628
  ),
623
- files: [
624
- "bin",
625
- "LICENSE",
626
- ],
629
+ files: ['bin', 'LICENSE'],
627
630
  };
628
631
 
629
- await fs.copyFile(licensePath, path.join(wrapperDir, "LICENSE"));
632
+ await fs.copyFile(licensePath, path.join(wrapperDir, 'LICENSE'));
630
633
  await fs.writeFile(
631
- path.join(wrapperDir, "bin", "opencode"),
632
- _buildWrapperExecutable({ runtimePackageName: packageName, binName }),
633
- { encoding: "utf-8", mode: 0o755 },
634
+ path.join(wrapperDir, 'bin', 'opencode'),
635
+ buildWrapperExecutable({ runtimePackageName: packageName }),
636
+ { encoding: 'utf-8', mode: 0o755 }
634
637
  );
635
- if (process.platform !== "win32") {
636
- await fs.chmod(path.join(wrapperDir, "bin", "opencode"), 0o755);
638
+ if (process.platform !== 'win32') {
639
+ await fs.chmod(path.join(wrapperDir, 'bin', 'opencode'), 0o755);
637
640
  }
638
- await writeJson(path.join(wrapperDir, "package.json"), wrapperPkg);
641
+ await writeJson(path.join(wrapperDir, 'package.json'), wrapperPkg);
639
642
 
640
643
  const tarballPath = await packPackage(wrapperDir);
641
- const publishResult = await publishTarballIfNeeded(packageName, version, tarballPath, wrapperDir);
644
+ const publishResult = await publishTarballIfNeeded(
645
+ packageName,
646
+ version,
647
+ tarballPath,
648
+ wrapperDir
649
+ );
642
650
  return { tarballPath, publishResult };
643
651
  }
644
652
 
645
- async function prepareBinaryPackages({ distDir, artifactDir, runtimePackageName }) {
646
- const packageRoot = path.join(artifactDir, "npm-binaries");
653
+ async function prepareBinaryPackages({
654
+ distDir,
655
+ artifactDir,
656
+ runtimePackageName,
657
+ runtimeVersion,
658
+ }) {
659
+ const packageRoot = path.join(artifactDir, 'npm-binaries');
647
660
  await fs.rm(packageRoot, { recursive: true, force: true });
648
661
  await fs.mkdir(packageRoot, { recursive: true });
649
662
 
@@ -652,30 +665,46 @@ async function prepareBinaryPackages({ distDir, artifactDir, runtimePackageName
652
665
  for (const entry of entries) {
653
666
  if (!entry.isDirectory()) continue;
654
667
  const sourceDir = path.join(distDir, entry.name);
655
- const packageJsonPath = path.join(sourceDir, "package.json");
668
+ const packageJsonPath = path.join(sourceDir, 'package.json');
656
669
  let pkg;
657
670
  try {
658
671
  pkg = await readJson(packageJsonPath);
659
672
  } catch {
660
673
  continue;
661
674
  }
662
- if (typeof pkg?.name !== "string" || typeof pkg?.version !== "string") {
675
+ if (typeof pkg?.name !== 'string' || typeof pkg?.version !== 'string') {
663
676
  continue;
664
677
  }
665
- if (!pkg.name.startsWith("opencode-")) {
678
+ if (!pkg.name.startsWith('opencode-')) {
666
679
  continue;
667
680
  }
668
681
 
669
- const brandedName = mapBrandedBinaryPackageName(pkg.name, runtimePackageName);
670
- const targetDir = path.join(packageRoot, brandedName.replace("/", "__"));
682
+ const brandedName = mapBrandedBinaryPackageName(
683
+ pkg.name,
684
+ runtimePackageName
685
+ );
686
+ const targetDir = path.join(packageRoot, brandedName.replace('/', '__'));
671
687
  await fs.cp(sourceDir, targetDir, { recursive: true });
672
688
  pkg.name = brandedName;
689
+ pkg.version = runtimeVersion;
673
690
  pkg.repository = trustedPublishingRepository;
674
- await writeJson(path.join(targetDir, "package.json"), pkg);
675
- await patchBinaryAtPath(path.join(targetDir, "bin", _binaryFilename()));
691
+ await writeJson(path.join(targetDir, 'package.json'), pkg);
692
+ const binaryPath = path.join(
693
+ targetDir,
694
+ 'bin',
695
+ binaryFilenameForPackage(pkg.name)
696
+ );
697
+ if (process.platform !== 'win32') {
698
+ await fs.chmod(binaryPath, 0o755);
699
+ }
676
700
 
677
701
  const tarballPath = await packPackage(targetDir);
678
- const publishResult = await publishTarballIfNeeded(brandedName, pkg.version, tarballPath, targetDir);
702
+ const publishResult = await publishTarballIfNeeded(
703
+ brandedName,
704
+ pkg.version,
705
+ tarballPath,
706
+ targetDir
707
+ );
679
708
  packages.push({
680
709
  dirName: entry.name,
681
710
  originalName: entry.name,
@@ -691,12 +720,14 @@ async function prepareBinaryPackages({ distDir, artifactDir, runtimePackageName
691
720
  throw new Error(`No OpenCode binary packages found under ${distDir}`);
692
721
  }
693
722
 
694
- return packages.sort((left, right) => left.originalName.localeCompare(right.originalName));
723
+ return packages.sort((left, right) =>
724
+ left.originalName.localeCompare(right.originalName)
725
+ );
695
726
  }
696
727
 
697
728
  function pickCurrentPlatformBinary(distPackages) {
698
- const platformMap = { darwin: "darwin", linux: "linux", win32: "windows" };
699
- const archMap = { x64: "x64", arm64: "arm64" };
729
+ const platformMap = { darwin: 'darwin', linux: 'linux', win32: 'windows' };
730
+ const archMap = { x64: 'x64', arm64: 'arm64' };
700
731
  const expectedPlatform = platformMap[process.platform] || process.platform;
701
732
  const expectedArch = archMap[process.arch] || process.arch;
702
733
  const prefix = _runtimeBinaryPrefix(wrapperPackageName);
@@ -712,64 +743,85 @@ function pickCurrentPlatformBinary(distPackages) {
712
743
  }
713
744
 
714
745
  async function main() {
715
- if (cliArgs.includes("-h") || cliArgs.includes("--help")) {
716
- console.log("Build branded Zenocode OpenCode runtime packages from upstream.");
717
- console.log("");
718
- console.log("Environment variables:");
719
- console.log(` ZENOCODE_OPENCODE_FORK_URL Fork URL (default: ${forkUrl})`);
720
- console.log(" ZENOCODE_OPENCODE_REF Branch/tag/commit to build");
721
- console.log(` ZENOCODE_BRANDED_OUTPUT_DIR Output directory (default: ${outputDir})`);
722
- console.log(` ZENOCODE_BRANDED_PACKAGE Wrapper package name (default: ${wrapperPackageName})`);
723
- console.log(` ZENOCODE_BRANDED_BIN_NAME Extra bin name in wrapper (default: ${wrapperBinName})`);
724
- console.log(" ZENOCODE_OPENCODE_BUILD_ARGS Additional args for upstream build script");
725
- console.log(" ZENOCODE_PUBLISH=1 Publish packages to npm");
726
- console.log(` ZENOCODE_PUBLISH_TAG npm tag (default: ${publishTag})`);
727
- console.log(" --publish-existing Publish tarballs from the output directory without rebuilding");
728
- console.log(" Legacy CODECORTEX_* env names are still accepted for compatibility.");
746
+ if (cliArgs.includes('-h') || cliArgs.includes('--help')) {
747
+ console.log(
748
+ 'Build branded Zenocode OpenCode runtime packages from upstream.'
749
+ );
750
+ console.log('');
751
+ console.log('Environment variables:');
752
+ console.log(
753
+ ` ZENOCODE_OPENCODE_FORK_URL Fork URL (default: ${forkUrl})`
754
+ );
755
+ console.log(' ZENOCODE_OPENCODE_REF Branch/tag/commit to build');
756
+ console.log(
757
+ ` ZENOCODE_BRANDED_OUTPUT_DIR Output directory (default: ${outputDir})`
758
+ );
759
+ console.log(
760
+ ` ZENOCODE_BRANDED_PACKAGE Wrapper package name (default: ${wrapperPackageName})`
761
+ );
762
+ console.log(
763
+ ` ZENOCODE_BRANDED_BIN_NAME Extra bin name in wrapper (default: ${wrapperBinName})`
764
+ );
765
+ console.log(
766
+ ' ZENOCODE_OPENCODE_BUILD_ARGS Additional args for upstream build script'
767
+ );
768
+ console.log(' ZENOCODE_PUBLISH=1 Publish packages to npm');
769
+ console.log(
770
+ ` ZENOCODE_PUBLISH_TAG npm tag (default: ${publishTag})`
771
+ );
772
+ console.log(
773
+ ' --publish-existing Publish tarballs from the output directory without rebuilding'
774
+ );
775
+ console.log(
776
+ ' Legacy CODECORTEX_* env names are still accepted for compatibility.'
777
+ );
729
778
  return;
730
779
  }
731
780
 
732
- const npmCommand = _command("npm");
781
+ const npmCommand = _command('npm');
733
782
  await requireCommand(npmCommand);
734
783
 
735
784
  const artifactDir = path.resolve(outputDir);
736
785
 
737
- if (cliArgs.includes("--publish-existing")) {
786
+ if (cliArgs.includes('--publish-existing')) {
738
787
  await publishExistingPackages(artifactDir);
739
788
  return;
740
789
  }
741
790
 
742
- const gitCommand = _command("git");
743
- const bunCommand = _command("bun");
791
+ const gitCommand = _command('git');
792
+ const bunCommand = _command('bun');
744
793
  await requireCommand(gitCommand);
745
794
  await requireCommand(bunCommand);
746
795
 
747
- const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "zenocode-opencode-"));
748
- const checkoutDir = path.join(tempRoot, "opencode");
749
- const distDir = path.join(checkoutDir, "packages", "opencode", "dist");
796
+ const tempRoot = await fs.mkdtemp(
797
+ path.join(os.tmpdir(), 'zenocode-opencode-')
798
+ );
799
+ const checkoutDir = path.join(tempRoot, 'opencode');
800
+ const distDir = path.join(checkoutDir, 'packages', 'opencode', 'dist');
750
801
 
751
802
  try {
752
- const cloneArgs = ["clone", "--depth=1"];
803
+ const cloneArgs = ['clone', '--depth=1'];
753
804
  if (forkRef) {
754
- cloneArgs.push("--branch", forkRef);
805
+ cloneArgs.push('--branch', forkRef);
755
806
  }
756
807
  cloneArgs.push(forkUrl, checkoutDir);
757
808
  await run(gitCommand, cloneArgs);
758
809
 
759
- await run(bunCommand, ["install"], { cwd: checkoutDir });
810
+ await applyZenocodeSourceBranding(checkoutDir);
811
+ await run(bunCommand, ['install'], { cwd: checkoutDir });
760
812
  await run(
761
813
  bunCommand,
762
- ["./packages/opencode/script/build.ts", "--single", ...buildArgs],
814
+ ['./packages/opencode/script/build.ts', ...buildArgs],
763
815
  {
764
816
  cwd: checkoutDir,
765
817
  env: {
766
818
  ...process.env,
767
- OPENCODE_CHANNEL: process.env.OPENCODE_CHANNEL || "latest",
819
+ OPENCODE_CHANNEL: process.env.OPENCODE_CHANNEL || 'latest',
768
820
  OPENCODE_VERSION:
769
821
  process.env.OPENCODE_VERSION ||
770
822
  deriveOpenCodeVersionFromRef(forkRef, defaultOpenCodeVersion),
771
823
  },
772
- },
824
+ }
773
825
  );
774
826
 
775
827
  await fs.mkdir(artifactDir, { recursive: true });
@@ -777,16 +829,17 @@ async function main() {
777
829
  distDir,
778
830
  artifactDir,
779
831
  runtimePackageName: wrapperPackageName,
832
+ runtimeVersion: zenocodeRuntimeVersion,
780
833
  });
781
834
 
782
835
  const version = binaryPackages[0]?.version;
783
836
  if (!version) {
784
- throw new Error("Unable to determine branded runtime version");
837
+ throw new Error('Unable to determine branded runtime version');
785
838
  }
786
839
 
787
840
  const wrapperResult = await buildWrapperPackage({
788
841
  artifactDir,
789
- licensePath: path.join(checkoutDir, "LICENSE"),
842
+ licensePath: path.join(checkoutDir, 'LICENSE'),
790
843
  packageName: wrapperPackageName,
791
844
  binName: wrapperBinName,
792
845
  version,
@@ -794,42 +847,63 @@ async function main() {
794
847
  });
795
848
 
796
849
  const selectedPackage = pickCurrentPlatformBinary(
797
- binaryPackages.map((pkg) => pkg.brandedName.split("/").at(-1)),
850
+ binaryPackages.map((pkg) => pkg.brandedName.split('/').at(-1))
798
851
  );
799
- const localBinaryPackage = binaryPackages.find((pkg) => pkg.brandedName.endsWith(`/${selectedPackage}`))
800
- || binaryPackages[0];
801
- const localBinaryDir = path.join(artifactDir, "bin");
802
- const localBinaryName = process.platform === "win32" ? "zenocode-opencode.exe" : "zenocode-opencode";
852
+ const localBinaryPackage =
853
+ binaryPackages.find((pkg) =>
854
+ pkg.brandedName.endsWith(`/${selectedPackage}`)
855
+ ) || binaryPackages[0];
856
+ const localBinaryDir = path.join(artifactDir, 'bin');
857
+ const localBinaryName =
858
+ process.platform === 'win32'
859
+ ? 'zenocode-opencode.exe'
860
+ : 'zenocode-opencode';
803
861
  const localBinaryPath = path.join(localBinaryDir, localBinaryName);
804
862
 
805
863
  await fs.mkdir(localBinaryDir, { recursive: true });
806
864
  await fs.copyFile(
807
- path.join(localBinaryPackage.packageDir, "bin", _binaryFilename()),
808
- localBinaryPath,
865
+ path.join(
866
+ localBinaryPackage.packageDir,
867
+ 'bin',
868
+ binaryFilenameForPackage(localBinaryPackage.originalName)
869
+ ),
870
+ localBinaryPath
809
871
  );
810
- if (process.platform !== "win32") {
872
+ if (process.platform !== 'win32') {
811
873
  await fs.chmod(localBinaryPath, 0o755);
812
874
  }
813
875
 
814
- console.log("\nZenocode branded build complete.");
876
+ console.log('\nZenocode branded build complete.');
815
877
  console.log(`Branded binary: ${localBinaryPath}`);
816
878
  console.log(`Wrapper package tarball: ${wrapperResult.tarballPath}`);
817
879
  for (const pkg of binaryPackages) {
818
880
  console.log(`Binary package tarball: ${pkg.tarballPath}`);
819
881
  }
820
882
  if (publishEnabled) {
821
- const publishedBinaries = binaryPackages.filter((pkg) => pkg.publishResult.published).map((pkg) => pkg.brandedName);
822
- const skippedBinaries = binaryPackages.filter((pkg) => pkg.publishResult.skipped).map((pkg) => pkg.brandedName);
883
+ const publishedBinaries = binaryPackages
884
+ .filter((pkg) => pkg.publishResult.published)
885
+ .map((pkg) => pkg.brandedName);
886
+ const skippedBinaries = binaryPackages
887
+ .filter((pkg) => pkg.publishResult.skipped)
888
+ .map((pkg) => pkg.brandedName);
823
889
  if (publishedBinaries.length) {
824
- console.log(`Published binary packages: ${publishedBinaries.join(", ")}`);
890
+ console.log(
891
+ `Published binary packages: ${publishedBinaries.join(', ')}`
892
+ );
825
893
  }
826
894
  if (skippedBinaries.length) {
827
- console.log(`Skipped already-published binary packages: ${skippedBinaries.join(", ")}`);
895
+ console.log(
896
+ `Skipped already-published binary packages: ${skippedBinaries.join(', ')}`
897
+ );
828
898
  }
829
899
  if (wrapperResult.publishResult.published) {
830
- console.log(`Published wrapper package: ${wrapperPackageName} (tag: ${publishTag}).`);
900
+ console.log(
901
+ `Published wrapper package: ${wrapperPackageName} (tag: ${publishTag}).`
902
+ );
831
903
  } else if (wrapperResult.publishResult.skipped) {
832
- console.log(`Skipped already-published wrapper package: ${wrapperPackageName}.`);
904
+ console.log(
905
+ `Skipped already-published wrapper package: ${wrapperPackageName}.`
906
+ );
833
907
  }
834
908
  }
835
909
  console.log(`\nRun with local binary:`);
@@ -849,7 +923,10 @@ function resolveExecutablePath(value) {
849
923
  }
850
924
  }
851
925
 
852
- if (process.argv[1] && resolveExecutablePath(process.argv[1]) === currentFilePath) {
926
+ if (
927
+ process.argv[1] &&
928
+ resolveExecutablePath(process.argv[1]) === currentFilePath
929
+ ) {
853
930
  main().catch((error) => {
854
931
  console.error(error instanceof Error ? error.message : String(error));
855
932
  process.exit(1);