@textcortex/zenocode 0.1.13 → 0.1.15

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