capix-code 2.2.3 → 2.2.5
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/package.json +7 -2
- package/scripts/postinstall.cjs +88 -32
- package/scripts/assert-artifact.sh +0 -31
- package/scripts/assert-customer-brand.sh +0 -54
- package/scripts/assert-upstream-brand.sh +0 -46
- package/scripts/bootstrap.sh +0 -38
- package/scripts/build-manifest.mjs +0 -219
- package/scripts/build.sh +0 -154
- package/scripts/checksum.sh +0 -8
- package/scripts/dev-install.sh +0 -30
- package/scripts/dev.sh +0 -35
- package/scripts/install-config.sh +0 -66
- package/scripts/install.sh +0 -115
- package/scripts/package-customer.sh +0 -28
- package/scripts/package.sh +0 -84
- package/scripts/prepare-npm-meta.mjs +0 -12
- package/scripts/prepare-npm-platform.mjs +0 -59
- package/scripts/rebrand.sh +0 -247
- package/scripts/resolve-version.mjs +0 -126
- package/scripts/validate-manifest.mjs +0 -167
- package/scripts/verify-runtime-provider.ts +0 -9
- package/scripts/write-release-entry.mjs +0 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capix-code",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"description": "Capix Code — decentralized AI coding agent with GPU marketplace",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/",
|
|
12
|
-
"scripts/",
|
|
12
|
+
"scripts/postinstall.cjs",
|
|
13
13
|
"dist/customer/config/",
|
|
14
14
|
"README.md",
|
|
15
15
|
"LICENSE"
|
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
"lint": "eslint src/",
|
|
23
23
|
"format": "prettier --write .",
|
|
24
24
|
"format:check": "prettier --check .",
|
|
25
|
+
"pretest": "node scripts/prepare-native-addons.mjs node_modules",
|
|
25
26
|
"test": "vitest run",
|
|
26
27
|
"test:watch": "vitest",
|
|
27
28
|
"test:coverage": "vitest run --coverage",
|
|
28
29
|
"manifest:validate": "node scripts/validate-manifest.mjs manifest/release-manifest.json",
|
|
29
30
|
"manifest:build": "node scripts/build-manifest.mjs",
|
|
30
31
|
"resolve-version": "node scripts/resolve-version.mjs",
|
|
32
|
+
"release:check": "node scripts/check-release-consistency.mjs",
|
|
31
33
|
"prepack": "node scripts/prepare-npm-meta.mjs",
|
|
32
34
|
"pack:npm-platform": "node scripts/prepare-npm-platform.mjs",
|
|
33
35
|
"postinstall": "node scripts/postinstall.cjs"
|
|
@@ -35,12 +37,15 @@
|
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@ai-sdk/provider": "3.0.8",
|
|
37
39
|
"@capix/runtime-provider": "file:packages/runtime-provider",
|
|
40
|
+
"@capix/agent-runtime": "file:packages/agent-runtime",
|
|
38
41
|
"@commitlint/config-conventional": "^21.2.0",
|
|
39
42
|
"@eslint/js": "^9.0.0",
|
|
40
43
|
"@opencode-ai/plugin": "1.17.18",
|
|
41
44
|
"@opencode-ai/sdk": "1.17.18",
|
|
45
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
42
46
|
"@types/node": "^22.0.0",
|
|
43
47
|
"@vitest/coverage-v8": "^2.1.0",
|
|
48
|
+
"better-sqlite3": "^12.11.1",
|
|
44
49
|
"bun": "^1.3.14",
|
|
45
50
|
"commitlint": "^21.2.1",
|
|
46
51
|
"eslint": "^9.0.0",
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -1,63 +1,119 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const {
|
|
2
|
+
const { execFileSync } = require('child_process');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const crypto = require('crypto');
|
|
7
|
-
if (process.env.CI || process.env.GITHUB_ACTIONS) {
|
|
7
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS) {
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
8
10
|
const VERSION = require('../package.json').version;
|
|
9
11
|
const plat = process.platform === 'win32' ? 'win32' : process.platform;
|
|
10
12
|
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
11
13
|
const ext = process.platform === 'win32' ? 'zip' : 'tar.gz';
|
|
12
14
|
const NAME = `capix-code-${VERSION}-${plat}-${arch}-unsigned`;
|
|
13
|
-
const URL = `https://github.com/CapIX-Protocol/
|
|
15
|
+
const URL = `https://github.com/CapIX-Protocol/CapIX-Code/releases/download/v${VERSION}/${NAME}.${ext}`;
|
|
14
16
|
const CHECKSUM_URL = `${URL}.sha256`;
|
|
15
17
|
const ROOT = path.join(os.homedir(), '.capix-code');
|
|
16
|
-
const TMP = path.join(os.tmpdir(), `capix-install-${VERSION}`);
|
|
18
|
+
const TMP = path.join(os.tmpdir(), `capix-install-${VERSION}-${process.pid}`);
|
|
17
19
|
const archivePath = path.join(TMP, `${NAME}.${ext}`);
|
|
18
|
-
fs.
|
|
20
|
+
fs.rmSync(TMP, { recursive: true, force: true });
|
|
19
21
|
fs.mkdirSync(TMP, { recursive: true });
|
|
20
22
|
console.log(`Capix Code v${VERSION} (${plat}-${arch})`);
|
|
21
|
-
|
|
23
|
+
execFileSync('curl', ['-fsSL', '-o', archivePath, URL], { stdio: 'inherit' });
|
|
22
24
|
const checksumPath = path.join(TMP, 'checksum.sha256');
|
|
23
|
-
|
|
25
|
+
execFileSync('curl', ['-fsSL', '-o', checksumPath, CHECKSUM_URL], { stdio: 'inherit' });
|
|
24
26
|
const expected = fs.readFileSync(checksumPath, 'utf8').trim().split(/\s+/)[0];
|
|
25
27
|
const actual = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
|
|
26
|
-
if (expected !== actual) {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
if (expected !== actual) {
|
|
29
|
+
console.error(`Checksum mismatch!`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
if (process.platform === 'win32') {
|
|
33
|
+
execFileSync('tar', ['-xf', archivePath, '-C', TMP], { stdio: 'inherit' });
|
|
34
|
+
} else {
|
|
35
|
+
execFileSync('tar', ['-xzf', archivePath, '-C', TMP], { stdio: 'inherit' });
|
|
36
|
+
}
|
|
29
37
|
const src = path.join(TMP, 'customer');
|
|
30
|
-
if (!fs.existsSync(src)) {
|
|
38
|
+
if (!fs.existsSync(src)) {
|
|
39
|
+
console.error('Missing customer/');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
31
42
|
const backup = ROOT + '.bak';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
fs.mkdirSync(
|
|
35
|
-
for (const dir of ['bin', 'engine', 'runtime', 'config', 'mcp']) {
|
|
43
|
+
const next = `${ROOT}.next-${process.pid}`;
|
|
44
|
+
fs.rmSync(next, { recursive: true, force: true });
|
|
45
|
+
fs.mkdirSync(next, { recursive: true });
|
|
46
|
+
for (const dir of ['bin', 'engine', 'runtime', 'config', 'mcp', 'commands']) {
|
|
36
47
|
const s = path.join(src, dir);
|
|
37
|
-
if (fs.existsSync(s))
|
|
48
|
+
if (fs.existsSync(s)) fs.cpSync(s, path.join(next, dir), { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
const executableSuffix = process.platform === 'win32' ? '.exe' : '';
|
|
51
|
+
const nextLauncher = path.join(next, 'bin', `capix-code${executableSuffix}`);
|
|
52
|
+
const nextEngine = path.join(next, 'engine', `capix-engine${executableSuffix}`);
|
|
53
|
+
if (!fs.existsSync(nextLauncher) || !fs.existsSync(nextEngine)) {
|
|
54
|
+
console.error('The Capix Code release is missing its native launcher or engine.');
|
|
55
|
+
fs.rmSync(next, { recursive: true, force: true });
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
if (process.platform !== 'win32') {
|
|
59
|
+
fs.chmodSync(nextLauncher, 0o755);
|
|
60
|
+
fs.chmodSync(nextEngine, 0o755);
|
|
38
61
|
}
|
|
39
|
-
|
|
40
|
-
console.log('Installing runtime deps...');
|
|
41
|
-
execSync('npm install --omit=dev --ignore-scripts', { cwd: path.join(ROOT, 'runtime'), stdio: 'inherit' });
|
|
42
|
-
const mcpDir = path.join(ROOT, 'mcp');
|
|
62
|
+
const mcpDir = path.join(next, 'mcp');
|
|
43
63
|
if (!fs.existsSync(path.join(mcpDir, 'node_modules', '@modelcontextprotocol'))) {
|
|
44
|
-
console.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
fs.chmodSync(path.join(mcpDir, 'capix-mcp.js'), 0o755);
|
|
64
|
+
console.error('The Capix Code release is missing its bundled MCP runtime.');
|
|
65
|
+
fs.rmSync(next, { recursive: true, force: true });
|
|
66
|
+
process.exit(1);
|
|
48
67
|
}
|
|
49
|
-
|
|
68
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
69
|
+
if (fs.existsSync(ROOT)) fs.renameSync(ROOT, backup);
|
|
70
|
+
try {
|
|
71
|
+
fs.renameSync(next, ROOT);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (fs.existsSync(backup) && !fs.existsSync(ROOT)) fs.renameSync(backup, ROOT);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
const launcher = path.join(ROOT, 'bin', `capix-code${executableSuffix}`);
|
|
77
|
+
const engine = path.join(ROOT, 'engine', `capix-engine${executableSuffix}`);
|
|
78
|
+
const cfgDir = path.join(os.homedir(), '.config', 'capix-code');
|
|
50
79
|
fs.mkdirSync(cfgDir, { recursive: true });
|
|
51
80
|
const rt = path.join(ROOT, 'runtime');
|
|
52
81
|
const pu = `file://${rt}/packages/runtime-provider/src/index.ts`;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
const configPath = path.join(cfgDir, 'capix-code.json');
|
|
83
|
+
if (!fs.existsSync(configPath)) {
|
|
84
|
+
fs.writeFileSync(
|
|
85
|
+
configPath,
|
|
86
|
+
JSON.stringify(
|
|
87
|
+
{
|
|
88
|
+
model: 'capix/auto',
|
|
89
|
+
enabled_providers: ['capix'],
|
|
90
|
+
plugin: [path.join(rt, 'src', 'native-bridge.ts'), path.join(rt, 'src', 'plugin.ts')],
|
|
91
|
+
provider: {
|
|
92
|
+
capix: {
|
|
93
|
+
npm: pu,
|
|
94
|
+
name: 'Capix',
|
|
95
|
+
models: {
|
|
96
|
+
auto: {
|
|
97
|
+
name: 'Capix Auto',
|
|
98
|
+
limit: { context: 128000, output: 64000 },
|
|
99
|
+
api: { url: 'https://www.capix.network/api/v1', npm: pu },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
null,
|
|
106
|
+
2
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
58
110
|
if (process.platform === 'darwin') {
|
|
59
|
-
try {
|
|
60
|
-
|
|
111
|
+
try {
|
|
112
|
+
execFileSync('codesign', ['--force', '--sign', '-', launcher], { stdio: 'ignore' });
|
|
113
|
+
} catch {}
|
|
114
|
+
try {
|
|
115
|
+
execFileSync('codesign', ['--force', '--sign', '-', engine], { stdio: 'ignore' });
|
|
116
|
+
} catch {}
|
|
61
117
|
}
|
|
62
118
|
fs.rmSync(TMP, { recursive: true, force: true });
|
|
63
119
|
if (fs.existsSync(backup)) fs.rmSync(backup, { recursive: true });
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
ROOT="${1:?artifact directory required}"
|
|
4
|
-
SUFFIX=""
|
|
5
|
-
test -f "$ROOT/bin/capix-code.exe" && SUFFIX=".exe"
|
|
6
|
-
required=(
|
|
7
|
-
"bin/capix-code$SUFFIX"
|
|
8
|
-
"engine/capix-engine$SUFFIX"
|
|
9
|
-
runtime/src/plugin.ts
|
|
10
|
-
runtime/src/native-bridge.ts
|
|
11
|
-
runtime/src/broker.ts
|
|
12
|
-
runtime/src/sandbox.ts
|
|
13
|
-
runtime/src/capix-provider.ts
|
|
14
|
-
runtime/src/ai-sdk-provider.ts
|
|
15
|
-
runtime/packages/runtime-provider/package.json
|
|
16
|
-
runtime/node_modules/@capix/runtime-provider/package.json
|
|
17
|
-
config/capix-defaults.json
|
|
18
|
-
config/defaults.json
|
|
19
|
-
mcp/capix-mcp.js
|
|
20
|
-
)
|
|
21
|
-
for path in "${required[@]}"; do
|
|
22
|
-
test -e "$ROOT/$path" || { echo "✗ artifact missing $path"; exit 1; }
|
|
23
|
-
done
|
|
24
|
-
test -x "$ROOT/engine/capix-engine$SUFFIX" || { echo "✗ bundled engine is not executable"; exit 1; }
|
|
25
|
-
test -x "$ROOT/bin/capix-code$SUFFIX" || { echo "✗ native launcher is not executable"; exit 1; }
|
|
26
|
-
grep -q '"name": "@capix/runtime-provider"' "$ROOT/runtime/packages/runtime-provider/package.json"
|
|
27
|
-
grep -q '"model": "{env:CAPIX_MODEL:capix/auto}"' "$ROOT/config/defaults.json"
|
|
28
|
-
grep -q 'SuperGemma' "$ROOT/config/defaults.json"
|
|
29
|
-
grep -q '/oauth/authorize' "$ROOT/runtime/src/broker.ts"
|
|
30
|
-
grep -q '/oauth/token' "$ROOT/runtime/src/broker.ts"
|
|
31
|
-
echo "✓ artifact contains pinned engine, plugin, broker, sandbox and runtime provider"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
ROOT="${1:?artifact directory required}"
|
|
4
|
-
FORBIDDEN='opencode|vast|hetzner|void|vscode'
|
|
5
|
-
|
|
6
|
-
if find "$ROOT" -path "$ROOT/runtime/node_modules" -prune -o -type f -print | sed "s|$ROOT/||" | grep -Eiq "$FORBIDDEN"; then
|
|
7
|
-
echo "✗ customer artifact contains a forbidden filename"
|
|
8
|
-
exit 1
|
|
9
|
-
fi
|
|
10
|
-
|
|
11
|
-
for command in "--help" "--version" "doctor"; do
|
|
12
|
-
output="$($ROOT/bin/capix-code $command 2>&1)"
|
|
13
|
-
if printf '%s' "$output" | grep -Eiq "$FORBIDDEN"; then
|
|
14
|
-
echo "✗ forbidden customer-visible brand in: capix-code $command"
|
|
15
|
-
exit 1
|
|
16
|
-
fi
|
|
17
|
-
done
|
|
18
|
-
|
|
19
|
-
# The embedded engine is not invoked directly by customers, but every help
|
|
20
|
-
# surface it can render through the launcher must still be Capix-only.
|
|
21
|
-
for args in "--help" "run --help"; do
|
|
22
|
-
output="$($ROOT/engine/capix-engine $args 2>&1)"
|
|
23
|
-
if printf '%s' "$output" | grep -Eiq "$FORBIDDEN"; then
|
|
24
|
-
echo "✗ forbidden predecessor brand in embedded engine output: $args"
|
|
25
|
-
exit 1
|
|
26
|
-
fi
|
|
27
|
-
done
|
|
28
|
-
|
|
29
|
-
# Native account commands must never fall through to the bundled engine's
|
|
30
|
-
# command parser. An unsigned CI artifact has no user keychain credential, so
|
|
31
|
-
# the expected result is a clean Capix-only sign-in instruction.
|
|
32
|
-
set +e
|
|
33
|
-
status_output="$($ROOT/bin/capix-code status 2>&1)"
|
|
34
|
-
status_code=$?
|
|
35
|
-
set -e
|
|
36
|
-
if [ "$status_code" -eq 0 ]; then
|
|
37
|
-
# A developer machine may already have a valid native keychain session.
|
|
38
|
-
printf '%s' "$status_output" | grep -Fq '"accountId"' || {
|
|
39
|
-
echo "✗ authenticated status did not return canonical Capix account data"; exit 1;
|
|
40
|
-
}
|
|
41
|
-
else
|
|
42
|
-
printf '%s' "$status_output" | grep -Fq 'capix-code login' || {
|
|
43
|
-
echo "✗ status did not return the native Capix authentication instruction"; exit 1;
|
|
44
|
-
}
|
|
45
|
-
fi
|
|
46
|
-
if printf '%s' "$status_output" | grep -Eiq "$FORBIDDEN"; then
|
|
47
|
-
echo "✗ status leaked a forbidden inherited brand"; exit 1
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
if grep -ERiq "$FORBIDDEN" "$ROOT/config"; then
|
|
51
|
-
echo "✗ forbidden customer-visible brand in configuration"
|
|
52
|
-
exit 1
|
|
53
|
-
fi
|
|
54
|
-
echo "✓ customer-visible artifact surfaces use Capix branding only"
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
ROOT="${1:?prepared engine source required}"
|
|
4
|
-
# Internal import/package symbols intentionally retain their upstream ABI.
|
|
5
|
-
# This expression targets customer-readable identity and command prose only.
|
|
6
|
-
FORBIDDEN='OpenCode|OC \||run opencode|close opencode|Starting opencode|opencode -s|opencode (models|auth|does|version|server|with|to|upgrade|mcp|session)|Thank you[^\n]*opencode'
|
|
7
|
-
SURFACES=(
|
|
8
|
-
packages/tui/src/logo.ts
|
|
9
|
-
packages/tui/src/app.tsx
|
|
10
|
-
packages/tui/src/util/presentation.ts
|
|
11
|
-
packages/capix-code/src/cli/ui.ts
|
|
12
|
-
packages/capix-code/src/index.ts
|
|
13
|
-
packages/capix-code/src/cli/error.ts
|
|
14
|
-
packages/capix-code/src/cli/cmd/run.ts
|
|
15
|
-
packages/capix-code/src/cli/cmd/run/splash.ts
|
|
16
|
-
packages/capix-code/src/cli/cmd/run/footer.permission.tsx
|
|
17
|
-
packages/capix-code/src/cli/cmd/run/footer.prompt.tsx
|
|
18
|
-
packages/capix-code/src/cli/cmd/run/permission.shared.ts
|
|
19
|
-
packages/capix-code/src/provider/error.ts
|
|
20
|
-
packages/capix-code/src/cli/cmd/attach.ts
|
|
21
|
-
packages/capix-code/src/cli/cmd/upgrade.ts
|
|
22
|
-
packages/capix-code/src/cli/cmd/uninstall.ts
|
|
23
|
-
packages/capix-code/src/cli/cmd/serve.ts
|
|
24
|
-
packages/capix-code/src/cli/cmd/web.ts
|
|
25
|
-
packages/capix-code/src/cli/cmd/pr.ts
|
|
26
|
-
packages/capix-code/src/cli/cmd/tui.ts
|
|
27
|
-
packages/capix-code/src/cli/network.ts
|
|
28
|
-
)
|
|
29
|
-
for file in "${SURFACES[@]}"; do
|
|
30
|
-
test -f "$ROOT/$file" || { echo "✗ missing customer presentation source: $file"; exit 1; }
|
|
31
|
-
if grep -Eq "$FORBIDDEN" "$ROOT/$file"; then
|
|
32
|
-
echo "✗ predecessor branding remains in customer presentation source: $file"
|
|
33
|
-
grep -En "$FORBIDDEN" "$ROOT/$file" | head -n 10
|
|
34
|
-
exit 1
|
|
35
|
-
fi
|
|
36
|
-
done
|
|
37
|
-
grep -Fq 'CAPIX CODE' "$ROOT/packages/capix-code/src/cli/ui.ts" || {
|
|
38
|
-
echo "✗ Capix Code startup wordmark is missing"
|
|
39
|
-
exit 1
|
|
40
|
-
}
|
|
41
|
-
grep -Fq 'export const identity' "$ROOT/packages/tui/src/logo.ts" &&
|
|
42
|
-
grep -Fq 'CAPIX CODE' "$ROOT/packages/tui/src/logo.ts" || {
|
|
43
|
-
echo "✗ Capix Code TUI identity is missing"
|
|
44
|
-
exit 1
|
|
45
|
-
}
|
|
46
|
-
echo "✓ prepared engine customer presentation sources are Capix-only"
|
package/scripts/bootstrap.sh
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# bootstrap.sh — clone upstream source and prepare capix-code.
|
|
3
|
-
set -euo pipefail
|
|
4
|
-
|
|
5
|
-
DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
6
|
-
CAPIX_CODE_DIR="${CAPIX_CODE_DIR:-$DIR/upstream}"
|
|
7
|
-
|
|
8
|
-
# TODO: Update this SHA when upgrading upstream
|
|
9
|
-
CAPIX_CODE_REF="${CAPIX_CODE_REF:-9976269ab1accfc9f9dc98a4a688c516934de422}"
|
|
10
|
-
|
|
11
|
-
if [ -d "$CAPIX_CODE_DIR/.git" ]; then
|
|
12
|
-
echo "✓ $CAPIX_CODE_DIR already cloned."
|
|
13
|
-
else
|
|
14
|
-
echo "▸ Cloning upstream source into $CAPIX_CODE_DIR (SHA: $CAPIX_CODE_REF)…"
|
|
15
|
-
git clone https://github.com/anomalyco/opencode.git "$CAPIX_CODE_DIR"
|
|
16
|
-
git -C "$CAPIX_CODE_DIR" checkout "$CAPIX_CODE_REF"
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
ACTUAL_REF="$(git -C "$CAPIX_CODE_DIR" rev-parse HEAD)"
|
|
20
|
-
if [ "$ACTUAL_REF" != "$CAPIX_CODE_REF" ]; then
|
|
21
|
-
echo "✗ Source checkout is at $ACTUAL_REF; expected pinned ref $CAPIX_CODE_REF"
|
|
22
|
-
echo " Use a new CAPIX_CODE_DIR or restore the pinned checkout before continuing."
|
|
23
|
-
exit 1
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
# A prepared checkout is intentionally changed by scripts/rebrand.sh. Treat the
|
|
27
|
-
# expected renamed package as an idempotent success, while still rejecting an
|
|
28
|
-
# unrelated dirty checkout before the first rebrand.
|
|
29
|
-
if [ -d "$CAPIX_CODE_DIR/packages/capix-code" ]; then
|
|
30
|
-
echo "✓ Pinned Capix Code source already prepared: $ACTUAL_REF"
|
|
31
|
-
exit 0
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
git -C "$CAPIX_CODE_DIR" diff --quiet || {
|
|
35
|
-
echo "✗ Source tree has unstaged changes before Capix preparation"
|
|
36
|
-
exit 1
|
|
37
|
-
}
|
|
38
|
-
echo "✓ Pinned source ready: $ACTUAL_REF"
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build (materialize) manifest/release-manifest.json from real per-platform
|
|
3
|
-
* release entries produced by write-release-entry.mjs.
|
|
4
|
-
*
|
|
5
|
-
* A publishable manifest is assembled ONLY from real artifact metadata — it
|
|
6
|
-
* never copies through TEMPLATE placeholders. If a required platform entry is
|
|
7
|
-
* missing or carries invalid metadata, the build fails closed.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* node scripts/build-manifest.mjs \
|
|
11
|
-
* --entries release-artifacts \
|
|
12
|
-
* --base-url https://github.com/CapIX-Protocol/Capix-Code/releases/download \
|
|
13
|
-
* --stable-version v1.2.4 \
|
|
14
|
-
* --source-sha <40-hex> \
|
|
15
|
-
* --launcher-version 1.2.4 --opencode-version 1.17.18 \
|
|
16
|
-
* --plugin-version 1.2.4 --provider-version 1.2.4 \
|
|
17
|
-
* [--out manifest/release-manifest.json] [--require-all]
|
|
18
|
-
*
|
|
19
|
-
* The stable version is the single source of truth consumed by the installer's
|
|
20
|
-
* `latest` resolver and by validate-manifest.
|
|
21
|
-
*/
|
|
22
|
-
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
23
|
-
import { basename, join, resolve } from 'node:path';
|
|
24
|
-
import { pathToFileURL } from 'node:url';
|
|
25
|
-
|
|
26
|
-
const REQUIRED_PLATFORMS = ['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64'];
|
|
27
|
-
const SEMVER_TAG = /^v\d+\.\d+\.\d+$/;
|
|
28
|
-
const SHA1 = /^[0-9a-f]{40}$/;
|
|
29
|
-
const SHA256 = /^[0-9a-f]{64}$/;
|
|
30
|
-
const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
31
|
-
|
|
32
|
-
function parseArgs(argv) {
|
|
33
|
-
const args = {
|
|
34
|
-
entries: 'release-artifacts',
|
|
35
|
-
out: 'manifest/release-manifest.json',
|
|
36
|
-
requireAll: false,
|
|
37
|
-
};
|
|
38
|
-
for (let i = 0; i < argv.length; i++) {
|
|
39
|
-
const a = argv[i];
|
|
40
|
-
switch (a) {
|
|
41
|
-
case '--entries':
|
|
42
|
-
args.entries = argv[++i];
|
|
43
|
-
break;
|
|
44
|
-
case '--base-url':
|
|
45
|
-
args.baseUrl = argv[++i];
|
|
46
|
-
break;
|
|
47
|
-
case '--stable-version':
|
|
48
|
-
args.stableVersion = argv[++i];
|
|
49
|
-
break;
|
|
50
|
-
case '--source-sha':
|
|
51
|
-
args.sourceSha = argv[++i];
|
|
52
|
-
break;
|
|
53
|
-
case '--launcher-version':
|
|
54
|
-
args.launcherVersion = argv[++i];
|
|
55
|
-
break;
|
|
56
|
-
case '--opencode-version':
|
|
57
|
-
args.opencodeVersion = argv[++i];
|
|
58
|
-
break;
|
|
59
|
-
case '--plugin-version':
|
|
60
|
-
args.pluginVersion = argv[++i];
|
|
61
|
-
break;
|
|
62
|
-
case '--provider-version':
|
|
63
|
-
args.providerVersion = argv[++i];
|
|
64
|
-
break;
|
|
65
|
-
case '--out':
|
|
66
|
-
args.out = argv[++i];
|
|
67
|
-
break;
|
|
68
|
-
case '--require-all':
|
|
69
|
-
args.requireAll = true;
|
|
70
|
-
break;
|
|
71
|
-
default:
|
|
72
|
-
throw new Error(`unknown argument: ${a}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return args;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function requireArg(args, name) {
|
|
79
|
-
const v = args[name];
|
|
80
|
-
if (!v)
|
|
81
|
-
throw new Error(
|
|
82
|
-
`${name} is required (pass --${name.replace(/([A-Z])/g, '-$1').toLowerCase()})`
|
|
83
|
-
);
|
|
84
|
-
return v;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Read every capix-code-*-{platform}-{arch}.release.json in the entries dir. */
|
|
88
|
-
export function readReleaseEntries(entriesDir) {
|
|
89
|
-
const files = readdirSync(entriesDir).filter((f) => /\.release\.json$/i.test(f));
|
|
90
|
-
const entries = new Map();
|
|
91
|
-
for (const f of files) {
|
|
92
|
-
let parsed;
|
|
93
|
-
try {
|
|
94
|
-
parsed = JSON.parse(readFileSync(join(entriesDir, f), 'utf8'));
|
|
95
|
-
} catch (err) {
|
|
96
|
-
throw new Error(`cannot parse ${f}: ${err.message}`);
|
|
97
|
-
}
|
|
98
|
-
if (parsed.product !== 'capix-code') throw new Error(`${f}: product is not capix-code`);
|
|
99
|
-
const platform = parsed.platform;
|
|
100
|
-
if (!platform || !REQUIRED_PLATFORMS.includes(platform)) {
|
|
101
|
-
throw new Error(`${f}: unrecognized platform "${platform}"`);
|
|
102
|
-
}
|
|
103
|
-
if (entries.has(platform))
|
|
104
|
-
throw new Error(`duplicate release entry for platform ${platform} (${f})`);
|
|
105
|
-
entries.set(platform, parsed);
|
|
106
|
-
}
|
|
107
|
-
return entries;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Build the manifest object from a map of platform -> release entry.
|
|
112
|
-
* Pure function — testable without touching the filesystem.
|
|
113
|
-
*/
|
|
114
|
-
export function buildManifestFromEntries(entries, opts) {
|
|
115
|
-
const {
|
|
116
|
-
baseUrl,
|
|
117
|
-
stableVersion,
|
|
118
|
-
sourceSha,
|
|
119
|
-
launcherVersion,
|
|
120
|
-
opencodeVersion,
|
|
121
|
-
pluginVersion,
|
|
122
|
-
providerVersion,
|
|
123
|
-
} = opts;
|
|
124
|
-
const errors = [];
|
|
125
|
-
if (!SEMVER_TAG.test(stableVersion ?? ''))
|
|
126
|
-
errors.push('--stable-version must be vMAJOR.MINOR.PATCH');
|
|
127
|
-
if (!SHA1.test(sourceSha ?? ''))
|
|
128
|
-
errors.push('--source-sha must be a 40-character lowercase hex commit SHA');
|
|
129
|
-
for (const [name, v] of [
|
|
130
|
-
['launcher', launcherVersion],
|
|
131
|
-
['opencode', opencodeVersion],
|
|
132
|
-
['plugin', pluginVersion],
|
|
133
|
-
['provider', providerVersion],
|
|
134
|
-
]) {
|
|
135
|
-
if (!SEMVER.test(v ?? '')) errors.push(`--${name}-version must be MAJOR.MINOR.PATCH`);
|
|
136
|
-
}
|
|
137
|
-
if (!baseUrl || !/^https:\/\//.test(baseUrl)) errors.push('--base-url must be an https URL');
|
|
138
|
-
|
|
139
|
-
const present = [...entries.keys()];
|
|
140
|
-
if (opts.requireAll) {
|
|
141
|
-
for (const p of REQUIRED_PLATFORMS)
|
|
142
|
-
if (!entries.has(p)) errors.push(`missing required platform entry: ${p}`);
|
|
143
|
-
}
|
|
144
|
-
if (errors.length) throw new Error(errors.join('; '));
|
|
145
|
-
|
|
146
|
-
const platforms = {};
|
|
147
|
-
for (const p of REQUIRED_PLATFORMS) {
|
|
148
|
-
const e = entries.get(p);
|
|
149
|
-
if (!e) continue; // when not --require-all, only publish what exists
|
|
150
|
-
if (!SHA256.test(e.sha256 ?? ''))
|
|
151
|
-
throw new Error(`${p}: release entry sha256 is not a valid 64-hex digest`);
|
|
152
|
-
if (typeof e.sizeBytes !== 'number' || e.sizeBytes <= 0)
|
|
153
|
-
throw new Error(`${p}: release entry sizeBytes must be positive`);
|
|
154
|
-
const tag = stableVersion;
|
|
155
|
-
platforms[p] = {
|
|
156
|
-
url: `${baseUrl}/${tag}/${e.artifact}`,
|
|
157
|
-
sha256: e.sha256,
|
|
158
|
-
// Unsigned artifacts ship a null signatureUrl; strict signing is a
|
|
159
|
-
// separate, later gate. Never TEMPLATE.
|
|
160
|
-
signatureUrl: e.signed ? `${baseUrl}/${tag}/${e.artifact}.sig` : null,
|
|
161
|
-
sizeBytes: e.sizeBytes,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const createdAt = process.env.SOURCE_DATE_EPOCH
|
|
166
|
-
? new Date(Number(process.env.SOURCE_DATE_EPOCH) * 1000).toISOString()
|
|
167
|
-
: new Date().toISOString();
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
id: `rel_capix_code_${stableVersion.slice(1).replace(/\./g, '_')}`,
|
|
171
|
-
schemaVersion: 1,
|
|
172
|
-
stableVersion,
|
|
173
|
-
createdAt,
|
|
174
|
-
createdBy: 'release-engineering',
|
|
175
|
-
immutable: true,
|
|
176
|
-
launcher: { sourceSha, version: launcherVersion },
|
|
177
|
-
opencode: { sourceSha, version: opencodeVersion },
|
|
178
|
-
plugin: { sourceSha, version: pluginVersion },
|
|
179
|
-
provider: { sourceSha, version: providerVersion },
|
|
180
|
-
acpVersion: '1',
|
|
181
|
-
ipcVersion: '1',
|
|
182
|
-
apiRange: { min: 'v1', max: 'v1' },
|
|
183
|
-
platforms,
|
|
184
|
-
sbomRef: `${baseUrl}/${stableVersion}/sbom.spdx.json`,
|
|
185
|
-
provenanceRef: `${baseUrl}/${stableVersion}/provenance.json`,
|
|
186
|
-
signatureRef: null,
|
|
187
|
-
thirdPartyNoticesRef: `${baseUrl}/${stableVersion}/NOTICE`,
|
|
188
|
-
rollbackConstraints: ['retain-n-1'],
|
|
189
|
-
publishedPlatforms: present,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function main() {
|
|
194
|
-
const args = parseArgs(process.argv.slice(2));
|
|
195
|
-
const entries = readReleaseEntries(args.entries);
|
|
196
|
-
const manifest = buildManifestFromEntries(entries, {
|
|
197
|
-
baseUrl: requireArg(args, 'baseUrl'),
|
|
198
|
-
stableVersion: requireArg(args, 'stableVersion'),
|
|
199
|
-
sourceSha: requireArg(args, 'sourceSha'),
|
|
200
|
-
launcherVersion: requireArg(args, 'launcherVersion'),
|
|
201
|
-
opencodeVersion: requireArg(args, 'opencodeVersion'),
|
|
202
|
-
pluginVersion: requireArg(args, 'pluginVersion'),
|
|
203
|
-
providerVersion: requireArg(args, 'providerVersion'),
|
|
204
|
-
requireAll: args.requireAll,
|
|
205
|
-
});
|
|
206
|
-
writeFileSync(args.out, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
207
|
-
console.log(
|
|
208
|
-
`✓ wrote ${args.out} (stable=${manifest.stableVersion}, platforms=${Object.keys(manifest.platforms).join(',')})`
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
213
|
-
try {
|
|
214
|
-
main();
|
|
215
|
-
} catch (err) {
|
|
216
|
-
console.error(`✗ ${err.message}`);
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
219
|
-
}
|