@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.
- package/README.md +36 -1
- package/package.json +1 -1
- package/scripts/build-branded-opencode.mjs +292 -215
- package/scripts/build-branded-opencode.test.mjs +85 -59
- package/scripts/opencode-version.mjs +5 -3
- package/scripts/run-zenocode.mjs +600 -763
- package/scripts/run-zenocode.test.mjs +625 -430
- package/scripts/source-branding.mjs +721 -0
- package/scripts/source-branding.test.mjs +106 -0
- package/scripts/branding-patch.mjs +0 -193
- package/scripts/branding-patch.test.mjs +0 -89
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from
|
|
3
|
-
import { realpathSync } from
|
|
4
|
-
import fs from
|
|
5
|
-
import os from
|
|
6
|
-
import path from
|
|
7
|
-
import process from
|
|
8
|
-
import { fileURLToPath } from
|
|
9
|
-
import {
|
|
10
|
-
|
|
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,
|
|
16
|
-
export const defaultOpenCodeForkUrl =
|
|
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
|
|
23
|
+
export { zenocodeRuntimeVersion };
|
|
24
|
+
export const defaultOpenCodeBuildArgs = [];
|
|
20
25
|
export const trustedPublishingRepository = {
|
|
21
|
-
type:
|
|
22
|
-
url:
|
|
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
|
-
|
|
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
|
-
|
|
49
|
+
'zenocode';
|
|
47
50
|
const publishEnabled =
|
|
48
|
-
(process.env.ZENOCODE_PUBLISH || process.env.CODECORTEX_PUBLISH) ===
|
|
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
|
-
|
|
55
|
+
'latest';
|
|
53
56
|
const publishProvenance =
|
|
54
|
-
(process.env.ZENOCODE_NPM_PROVENANCE ||
|
|
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(
|
|
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 ===
|
|
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:
|
|
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(
|
|
95
|
-
|
|
96
|
-
|
|
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(
|
|
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]:
|
|
109
|
-
opencode:
|
|
116
|
+
[baseName]: './bin/opencode',
|
|
117
|
+
opencode: './bin/opencode',
|
|
110
118
|
};
|
|
111
119
|
if (binName && !(binName in binMap)) {
|
|
112
|
-
binMap[binName] =
|
|
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(
|
|
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 [
|
|
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:
|
|
182
|
+
stdio: 'inherit',
|
|
173
183
|
...options,
|
|
174
184
|
});
|
|
175
185
|
|
|
176
|
-
child.on(
|
|
177
|
-
child.on(
|
|
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(
|
|
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: [
|
|
204
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
191
205
|
...options,
|
|
192
206
|
});
|
|
193
207
|
|
|
194
|
-
let stdout =
|
|
195
|
-
let stderr =
|
|
196
|
-
child.stdout?.on(
|
|
197
|
-
stdout += chunk.toString(
|
|
208
|
+
let stdout = '';
|
|
209
|
+
let stderr = '';
|
|
210
|
+
child.stdout?.on('data', (chunk) => {
|
|
211
|
+
stdout += chunk.toString('utf-8');
|
|
198
212
|
});
|
|
199
|
-
child.stderr?.on(
|
|
200
|
-
stderr += chunk.toString(
|
|
213
|
+
child.stderr?.on('data', (chunk) => {
|
|
214
|
+
stderr += chunk.toString('utf-8');
|
|
201
215
|
});
|
|
202
|
-
child.on(
|
|
203
|
-
child.on(
|
|
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(
|
|
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 = [
|
|
232
|
+
async function requireCommand(command, args = ['--version']) {
|
|
217
233
|
try {
|
|
218
|
-
await run(command, args, { stdio:
|
|
234
|
+
await run(command, args, { stdio: 'ignore' });
|
|
219
235
|
} catch (error) {
|
|
220
|
-
if (error?.code ===
|
|
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,
|
|
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(
|
|
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
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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(
|
|
276
|
+
const npmCommand = _command('npm');
|
|
251
277
|
try {
|
|
252
278
|
const result = await runAndCapture(
|
|
253
279
|
npmCommand,
|
|
254
|
-
[
|
|
255
|
-
{
|
|
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() ||
|
|
258
|
-
if (typeof 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(
|
|
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(
|
|
271
|
-
await run(npmCommand, [
|
|
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(
|
|
280
|
-
|
|
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(
|
|
317
|
+
args.push('--provenance');
|
|
283
318
|
}
|
|
284
319
|
if (tag) {
|
|
285
|
-
args.push(
|
|
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(
|
|
307
|
-
await run(
|
|
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,
|
|
313
|
-
if (typeof pkg?.name !==
|
|
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,
|
|
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,
|
|
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
|
|
370
|
-
return
|
|
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
|
-
|
|
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(
|
|
408
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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:
|
|
617
|
-
type:
|
|
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,
|
|
632
|
+
await fs.copyFile(licensePath, path.join(wrapperDir, 'LICENSE'));
|
|
630
633
|
await fs.writeFile(
|
|
631
|
-
path.join(wrapperDir,
|
|
632
|
-
|
|
633
|
-
{ encoding:
|
|
634
|
+
path.join(wrapperDir, 'bin', 'opencode'),
|
|
635
|
+
buildWrapperExecutable({ runtimePackageName: packageName }),
|
|
636
|
+
{ encoding: 'utf-8', mode: 0o755 }
|
|
634
637
|
);
|
|
635
|
-
if (process.platform !==
|
|
636
|
-
await fs.chmod(path.join(wrapperDir,
|
|
638
|
+
if (process.platform !== 'win32') {
|
|
639
|
+
await fs.chmod(path.join(wrapperDir, 'bin', 'opencode'), 0o755);
|
|
637
640
|
}
|
|
638
|
-
await writeJson(path.join(wrapperDir,
|
|
641
|
+
await writeJson(path.join(wrapperDir, 'package.json'), wrapperPkg);
|
|
639
642
|
|
|
640
643
|
const tarballPath = await packPackage(wrapperDir);
|
|
641
|
-
const publishResult = await publishTarballIfNeeded(
|
|
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({
|
|
646
|
-
|
|
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,
|
|
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 !==
|
|
675
|
+
if (typeof pkg?.name !== 'string' || typeof pkg?.version !== 'string') {
|
|
663
676
|
continue;
|
|
664
677
|
}
|
|
665
|
-
if (!pkg.name.startsWith(
|
|
678
|
+
if (!pkg.name.startsWith('opencode-')) {
|
|
666
679
|
continue;
|
|
667
680
|
}
|
|
668
681
|
|
|
669
|
-
const brandedName = mapBrandedBinaryPackageName(
|
|
670
|
-
|
|
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,
|
|
675
|
-
|
|
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(
|
|
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) =>
|
|
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:
|
|
699
|
-
const archMap = { x64:
|
|
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(
|
|
716
|
-
console.log(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
console.log(
|
|
720
|
-
console.log(
|
|
721
|
-
console.log(
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
console.log(
|
|
725
|
-
console.log(
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
console.log(
|
|
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(
|
|
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(
|
|
786
|
+
if (cliArgs.includes('--publish-existing')) {
|
|
738
787
|
await publishExistingPackages(artifactDir);
|
|
739
788
|
return;
|
|
740
789
|
}
|
|
741
790
|
|
|
742
|
-
const gitCommand = _command(
|
|
743
|
-
const bunCommand = _command(
|
|
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(
|
|
748
|
-
|
|
749
|
-
|
|
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 = [
|
|
803
|
+
const cloneArgs = ['clone', '--depth=1'];
|
|
753
804
|
if (forkRef) {
|
|
754
|
-
cloneArgs.push(
|
|
805
|
+
cloneArgs.push('--branch', forkRef);
|
|
755
806
|
}
|
|
756
807
|
cloneArgs.push(forkUrl, checkoutDir);
|
|
757
808
|
await run(gitCommand, cloneArgs);
|
|
758
809
|
|
|
759
|
-
await
|
|
810
|
+
await applyZenocodeSourceBranding(checkoutDir);
|
|
811
|
+
await run(bunCommand, ['install'], { cwd: checkoutDir });
|
|
760
812
|
await run(
|
|
761
813
|
bunCommand,
|
|
762
|
-
[
|
|
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 ||
|
|
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(
|
|
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,
|
|
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(
|
|
850
|
+
binaryPackages.map((pkg) => pkg.brandedName.split('/').at(-1))
|
|
798
851
|
);
|
|
799
|
-
const localBinaryPackage =
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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(
|
|
808
|
-
|
|
865
|
+
path.join(
|
|
866
|
+
localBinaryPackage.packageDir,
|
|
867
|
+
'bin',
|
|
868
|
+
binaryFilenameForPackage(localBinaryPackage.originalName)
|
|
869
|
+
),
|
|
870
|
+
localBinaryPath
|
|
809
871
|
);
|
|
810
|
-
if (process.platform !==
|
|
872
|
+
if (process.platform !== 'win32') {
|
|
811
873
|
await fs.chmod(localBinaryPath, 0o755);
|
|
812
874
|
}
|
|
813
875
|
|
|
814
|
-
console.log(
|
|
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
|
|
822
|
-
|
|
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(
|
|
890
|
+
console.log(
|
|
891
|
+
`Published binary packages: ${publishedBinaries.join(', ')}`
|
|
892
|
+
);
|
|
825
893
|
}
|
|
826
894
|
if (skippedBinaries.length) {
|
|
827
|
-
console.log(
|
|
895
|
+
console.log(
|
|
896
|
+
`Skipped already-published binary packages: ${skippedBinaries.join(', ')}`
|
|
897
|
+
);
|
|
828
898
|
}
|
|
829
899
|
if (wrapperResult.publishResult.published) {
|
|
830
|
-
console.log(
|
|
900
|
+
console.log(
|
|
901
|
+
`Published wrapper package: ${wrapperPackageName} (tag: ${publishTag}).`
|
|
902
|
+
);
|
|
831
903
|
} else if (wrapperResult.publishResult.skipped) {
|
|
832
|
-
console.log(
|
|
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 (
|
|
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);
|