@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.
- package/README.md +48 -3
- package/package.json +8 -3
- package/scripts/build-branded-opencode.mjs +357 -189
- package/scripts/build-branded-opencode.test.mjs +99 -40
- package/scripts/opencode-version.mjs +5 -3
- package/scripts/run-zenocode.mjs +601 -755
- package/scripts/run-zenocode.test.mjs +648 -390
- package/scripts/source-branding.mjs +608 -0
- package/scripts/source-branding.test.mjs +76 -0
- package/scripts/branding-patch.mjs +0 -193
- package/scripts/branding-patch.test.mjs +0 -89
|
@@ -1,24 +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
|
|
20
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
49
|
+
'zenocode';
|
|
43
50
|
const publishEnabled =
|
|
44
|
-
(process.env.ZENOCODE_PUBLISH || process.env.CODECORTEX_PUBLISH) ===
|
|
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
|
-
|
|
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(
|
|
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 ===
|
|
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:
|
|
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(
|
|
89
|
-
|
|
90
|
-
|
|
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(
|
|
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]:
|
|
103
|
-
opencode:
|
|
116
|
+
[baseName]: './bin/opencode',
|
|
117
|
+
opencode: './bin/opencode',
|
|
104
118
|
};
|
|
105
119
|
if (binName && !(binName in binMap)) {
|
|
106
|
-
binMap[binName] =
|
|
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(
|
|
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 [
|
|
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:
|
|
182
|
+
stdio: 'inherit',
|
|
167
183
|
...options,
|
|
168
184
|
});
|
|
169
185
|
|
|
170
|
-
child.on(
|
|
171
|
-
child.on(
|
|
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(
|
|
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: [
|
|
204
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
185
205
|
...options,
|
|
186
206
|
});
|
|
187
207
|
|
|
188
|
-
let stdout =
|
|
189
|
-
let stderr =
|
|
190
|
-
child.stdout?.on(
|
|
191
|
-
stdout += chunk.toString(
|
|
208
|
+
let stdout = '';
|
|
209
|
+
let stderr = '';
|
|
210
|
+
child.stdout?.on('data', (chunk) => {
|
|
211
|
+
stdout += chunk.toString('utf-8');
|
|
192
212
|
});
|
|
193
|
-
child.stderr?.on(
|
|
194
|
-
stderr += chunk.toString(
|
|
213
|
+
child.stderr?.on('data', (chunk) => {
|
|
214
|
+
stderr += chunk.toString('utf-8');
|
|
195
215
|
});
|
|
196
|
-
child.on(
|
|
197
|
-
child.on(
|
|
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(
|
|
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 = [
|
|
232
|
+
async function requireCommand(command, args = ['--version']) {
|
|
211
233
|
try {
|
|
212
|
-
await run(command, args, { stdio:
|
|
234
|
+
await run(command, args, { stdio: 'ignore' });
|
|
213
235
|
} catch (error) {
|
|
214
|
-
if (error?.code ===
|
|
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,
|
|
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(
|
|
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(
|
|
276
|
+
const npmCommand = _command('npm');
|
|
231
277
|
try {
|
|
232
278
|
const result = await runAndCapture(
|
|
233
279
|
npmCommand,
|
|
234
|
-
[
|
|
235
|
-
{
|
|
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() ||
|
|
238
|
-
if (typeof 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(
|
|
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(
|
|
251
|
-
await run(npmCommand, [
|
|
252
|
-
const
|
|
253
|
-
if (!
|
|
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,
|
|
308
|
+
return path.join(packageDir, tarball);
|
|
257
309
|
}
|
|
258
310
|
|
|
259
|
-
export function buildPublishCommandArgs(
|
|
260
|
-
|
|
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(
|
|
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(
|
|
284
|
-
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
|
+
);
|
|
285
350
|
return { published: true, skipped: false };
|
|
286
351
|
}
|
|
287
352
|
|
|
288
|
-
function
|
|
289
|
-
|
|
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
|
|
293
|
-
|
|
294
|
-
await
|
|
295
|
-
|
|
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
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
if (
|
|
314
|
-
|
|
402
|
+
|
|
403
|
+
if (published.length) {
|
|
404
|
+
console.log(`Published packages: ${published.join(', ')}`);
|
|
315
405
|
}
|
|
316
|
-
|
|
317
|
-
|
|
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
|
|
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(
|
|
327
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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:
|
|
536
|
-
type:
|
|
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,
|
|
632
|
+
await fs.copyFile(licensePath, path.join(wrapperDir, 'LICENSE'));
|
|
548
633
|
await fs.writeFile(
|
|
549
|
-
path.join(wrapperDir,
|
|
550
|
-
|
|
551
|
-
{ encoding:
|
|
634
|
+
path.join(wrapperDir, 'bin', 'opencode'),
|
|
635
|
+
buildWrapperExecutable({ runtimePackageName: packageName }),
|
|
636
|
+
{ encoding: 'utf-8', mode: 0o755 }
|
|
552
637
|
);
|
|
553
|
-
if (process.platform !==
|
|
554
|
-
await fs.chmod(path.join(wrapperDir,
|
|
638
|
+
if (process.platform !== 'win32') {
|
|
639
|
+
await fs.chmod(path.join(wrapperDir, 'bin', 'opencode'), 0o755);
|
|
555
640
|
}
|
|
556
|
-
await writeJson(path.join(wrapperDir,
|
|
641
|
+
await writeJson(path.join(wrapperDir, 'package.json'), wrapperPkg);
|
|
557
642
|
|
|
558
643
|
const tarballPath = await packPackage(wrapperDir);
|
|
559
|
-
const publishResult = await publishTarballIfNeeded(
|
|
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({
|
|
564
|
-
|
|
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,
|
|
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 !==
|
|
675
|
+
if (typeof pkg?.name !== 'string' || typeof pkg?.version !== 'string') {
|
|
581
676
|
continue;
|
|
582
677
|
}
|
|
583
|
-
if (!pkg.name.startsWith(
|
|
678
|
+
if (!pkg.name.startsWith('opencode-')) {
|
|
584
679
|
continue;
|
|
585
680
|
}
|
|
586
681
|
|
|
587
|
-
const brandedName = mapBrandedBinaryPackageName(
|
|
588
|
-
|
|
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
|
-
|
|
592
|
-
|
|
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(
|
|
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) =>
|
|
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:
|
|
616
|
-
const archMap = { x64:
|
|
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(
|
|
633
|
-
console.log(
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
console.log(
|
|
637
|
-
console.log(
|
|
638
|
-
console.log(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
console.log(
|
|
642
|
-
console.log(
|
|
643
|
-
|
|
644
|
-
|
|
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
|
|
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 = [
|
|
803
|
+
const cloneArgs = ['clone', '--depth=1'];
|
|
662
804
|
if (forkRef) {
|
|
663
|
-
cloneArgs.push(
|
|
805
|
+
cloneArgs.push('--branch', forkRef);
|
|
664
806
|
}
|
|
665
807
|
cloneArgs.push(forkUrl, checkoutDir);
|
|
666
808
|
await run(gitCommand, cloneArgs);
|
|
667
809
|
|
|
668
|
-
await
|
|
810
|
+
await applyZenocodeSourceBranding(checkoutDir);
|
|
811
|
+
await run(bunCommand, ['install'], { cwd: checkoutDir });
|
|
669
812
|
await run(
|
|
670
813
|
bunCommand,
|
|
671
|
-
[
|
|
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 ||
|
|
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(
|
|
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,
|
|
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(
|
|
850
|
+
binaryPackages.map((pkg) => pkg.brandedName.split('/').at(-1))
|
|
707
851
|
);
|
|
708
|
-
const localBinaryPackage =
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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(
|
|
717
|
-
|
|
865
|
+
path.join(
|
|
866
|
+
localBinaryPackage.packageDir,
|
|
867
|
+
'bin',
|
|
868
|
+
binaryFilenameForPackage(localBinaryPackage.originalName)
|
|
869
|
+
),
|
|
870
|
+
localBinaryPath
|
|
718
871
|
);
|
|
719
|
-
if (process.platform !==
|
|
872
|
+
if (process.platform !== 'win32') {
|
|
720
873
|
await fs.chmod(localBinaryPath, 0o755);
|
|
721
874
|
}
|
|
722
875
|
|
|
723
|
-
console.log(
|
|
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
|
|
731
|
-
|
|
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(
|
|
890
|
+
console.log(
|
|
891
|
+
`Published binary packages: ${publishedBinaries.join(', ')}`
|
|
892
|
+
);
|
|
734
893
|
}
|
|
735
894
|
if (skippedBinaries.length) {
|
|
736
|
-
console.log(
|
|
895
|
+
console.log(
|
|
896
|
+
`Skipped already-published binary packages: ${skippedBinaries.join(', ')}`
|
|
897
|
+
);
|
|
737
898
|
}
|
|
738
899
|
if (wrapperResult.publishResult.published) {
|
|
739
|
-
console.log(
|
|
900
|
+
console.log(
|
|
901
|
+
`Published wrapper package: ${wrapperPackageName} (tag: ${publishTag}).`
|
|
902
|
+
);
|
|
740
903
|
} else if (wrapperResult.publishResult.skipped) {
|
|
741
|
-
console.log(
|
|
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 (
|
|
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);
|