Skip to content

Commit 2fa0920

Browse files
committed
fix: publish release bundle assets
1 parent e6c383d commit 2fa0920

5 files changed

Lines changed: 276 additions & 7 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Release Bundle
2+
3+
on:
4+
release:
5+
types:
6+
- published
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
upload-bundle-assets:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Check out repository
16+
uses: actions/checkout@v4
17+
18+
- name: Build release bundle assets
19+
run: |
20+
set -eu
21+
sh scripts/release/build-bundle-release.sh --tag "${GITHUB_REF_NAME}" --output-dir dist
22+
23+
- name: Upload release assets
24+
uses: softprops/action-gh-release@v2
25+
with:
26+
tag_name: ${{ github.ref_name }}
27+
files: |
28+
dist/opencode-config-bundle-${{ github.ref_name }}.tar.gz
29+
dist/opencode-config-bundle-${{ github.ref_name }}-checksums.txt

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ This bundle provides drop-in OpenCode agent configurations that route work throu
88

99
## Contract Ownership
1010

11-
This bundle **implements** the V2 bundle contract defined by the [opencode-agents](https://github.com/sven1103-agent/opencode-agents) repository. The contract schema and specification are maintained in the CLI repository.
11+
This bundle consumes the bundle contract defined by the [opencode-config-cli](https://github.com/sven1103-agent/opencode-config-cli) repository. The CLI owns the manifest schema, validation rules, and GitHub release distribution contract.
1212

13-
For the full contract specification, see [Bundle Manifest Reference](https://github.com/sven1103-agent/opencode-agents/blob/main/docs/opencode-helper-cli.md#bundle-manifest-reference) in the opencode-agents documentation.
13+
For the full contract specification, see [Bundle Contract](https://github.com/sven1103-agent/opencode-config-cli/blob/main/docs/specs/bundle-contract.md).
1414

1515
## Versioning
1616

@@ -47,15 +47,22 @@ A valid V2 bundle must include:
4747
Register this bundle as a config source:
4848

4949
```bash
50-
opencode-helper source add qbicsoftware/opencode-config-bundle --name qbic
50+
oc source add qbicsoftware/opencode-config-bundle --name qbic
5151
```
5252

53-
Apply a preset to your project:
53+
Apply a prerelease preset to your project:
5454

5555
```bash
56-
opencode-helper bundle apply qbic --preset openai --project-root ./myproject
56+
oc bundle apply qbic --version 1.0.0-alpha.1 --preset openai --project-root ./myproject
5757
```
5858

59+
The repository publishes release bundle assets during GitHub release publication. Each release uploads:
60+
61+
- `opencode-config-bundle-<tag>.tar.gz`
62+
- `opencode-config-bundle-<tag>-checksums.txt`
63+
64+
These explicit assets are the supported distribution format for GitHub-release bundle sources. Do not rely on GitHub's auto-generated source archives for `oc` bundle resolution.
65+
5966
## Design Philosophy
6067

6168
### Planning-First Execution

opencode-bundle.manifest.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
2-
"manifest_version": 1,
2+
"manifest_version": "1.0.0",
33
"bundle_name": "qbic-opencode-config-bundle",
4-
"bundle_version": "v1.0.0",
4+
"bundle_version": "1.0.0-alpha.1",
5+
"source_repo": "https://github.com/qbicsoftware/opencode-config-bundle",
6+
"release_tag": "1.0.0-alpha.1",
57
"presets": [
68
{
79
"name": "openai",
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
usage() {
6+
cat <<EOF
7+
Usage: build-bundle-release.sh --tag <tag> [--output-dir <dir>]
8+
EOF
9+
}
10+
11+
sha256_file() {
12+
file=$1
13+
if command -v sha256sum >/dev/null 2>&1; then
14+
sha256sum "$file" | awk '{print $1}'
15+
else
16+
shasum -a 256 "$file" | awk '{print $1}'
17+
fi
18+
}
19+
20+
TAG=
21+
OUTPUT_DIR=${PWD}
22+
23+
while [ "$#" -gt 0 ]; do
24+
case "$1" in
25+
--tag)
26+
shift
27+
TAG=${1:-}
28+
;;
29+
--output-dir)
30+
shift
31+
OUTPUT_DIR=${1:-}
32+
;;
33+
-h|--help)
34+
usage
35+
exit 0
36+
;;
37+
*)
38+
printf 'error: unknown option: %s\n' "$1" >&2
39+
usage >&2
40+
exit 1
41+
;;
42+
esac
43+
shift
44+
done
45+
46+
[ -n "$TAG" ] || { printf 'error: --tag is required\n' >&2; exit 1; }
47+
48+
SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd)
49+
REPO_ROOT=$(CDPATH= cd "$SCRIPT_DIR/../.." && pwd)
50+
OUTPUT_DIR=$(mkdir -p "$OUTPUT_DIR" && CDPATH= cd "$OUTPUT_DIR" && pwd)
51+
52+
BUNDLE_ROOT="opencode-config-bundle-$TAG"
53+
ARCHIVE_NAME="$BUNDLE_ROOT.tar.gz"
54+
CHECKSUMS_NAME="$BUNDLE_ROOT-checksums.txt"
55+
56+
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/opencode-config-bundle.XXXXXX")
57+
trap 'rm -rf "$TMP_DIR"' EXIT INT TERM
58+
59+
STAGE_ROOT="$TMP_DIR/$BUNDLE_ROOT"
60+
mkdir -p "$STAGE_ROOT/.opencode/schemas"
61+
62+
cp "$REPO_ROOT/opencode.openai.json" "$STAGE_ROOT/opencode.openai.json"
63+
cp "$REPO_ROOT/opencode.mixed.json" "$STAGE_ROOT/opencode.mixed.json"
64+
cp "$REPO_ROOT/opencode.big-pickle.json" "$STAGE_ROOT/opencode.big-pickle.json"
65+
cp "$REPO_ROOT/opencode.minimax.json" "$STAGE_ROOT/opencode.minimax.json"
66+
cp "$REPO_ROOT/opencode.kimi.json" "$STAGE_ROOT/opencode.kimi.json"
67+
cp "$REPO_ROOT/.opencode/schemas/handoff.schema.json" "$STAGE_ROOT/.opencode/schemas/handoff.schema.json"
68+
cp "$REPO_ROOT/.opencode/schemas/result.schema.json" "$STAGE_ROOT/.opencode/schemas/result.schema.json"
69+
70+
cat > "$STAGE_ROOT/opencode-bundle.manifest.json" <<EOF
71+
{
72+
"manifest_version": "1.0.0",
73+
"bundle_name": "qbic-opencode-config-bundle",
74+
"bundle_version": "$TAG",
75+
"source_repo": "https://github.com/qbicsoftware/opencode-config-bundle",
76+
"release_tag": "$TAG",
77+
"presets": [
78+
{
79+
"name": "openai",
80+
"description": "OpenAI-based multi-tier agent configuration with planning-first workflow",
81+
"entrypoint": "opencode.openai.json",
82+
"prompt_files": []
83+
},
84+
{
85+
"name": "mixed",
86+
"description": "Mixed model stack (Claude for routing/planning/review, Codex for execution)",
87+
"entrypoint": "opencode.mixed.json",
88+
"prompt_files": []
89+
},
90+
{
91+
"name": "kimi",
92+
"description": "Kimi-based multi-tier agent configuration",
93+
"entrypoint": "opencode.kimi.json",
94+
"prompt_files": []
95+
},
96+
{
97+
"name": "big-pickle",
98+
"description": "Big Pickle model-based multi-tier agent configuration",
99+
"entrypoint": "opencode.big-pickle.json",
100+
"prompt_files": []
101+
},
102+
{
103+
"name": "minimax",
104+
"description": "MiniMax-based multi-tier agent configuration",
105+
"entrypoint": "opencode.minimax.json",
106+
"prompt_files": []
107+
}
108+
]
109+
}
110+
EOF
111+
112+
touch -t 198001010000 "$STAGE_ROOT" "$STAGE_ROOT/.opencode" "$STAGE_ROOT/.opencode/schemas"
113+
touch -t 198001010000 "$STAGE_ROOT/opencode-bundle.manifest.json"
114+
touch -t 198001010000 "$STAGE_ROOT/opencode.openai.json" "$STAGE_ROOT/opencode.mixed.json"
115+
touch -t 198001010000 "$STAGE_ROOT/opencode.big-pickle.json" "$STAGE_ROOT/opencode.minimax.json" "$STAGE_ROOT/opencode.kimi.json"
116+
touch -t 198001010000 "$STAGE_ROOT/.opencode/schemas/handoff.schema.json" "$STAGE_ROOT/.opencode/schemas/result.schema.json"
117+
118+
LIST_FILE="$TMP_DIR/tar.list"
119+
cat > "$LIST_FILE" <<EOF
120+
$BUNDLE_ROOT
121+
$BUNDLE_ROOT/opencode-bundle.manifest.json
122+
$BUNDLE_ROOT/opencode.openai.json
123+
$BUNDLE_ROOT/opencode.mixed.json
124+
$BUNDLE_ROOT/opencode.big-pickle.json
125+
$BUNDLE_ROOT/opencode.minimax.json
126+
$BUNDLE_ROOT/opencode.kimi.json
127+
$BUNDLE_ROOT/.opencode
128+
$BUNDLE_ROOT/.opencode/schemas
129+
$BUNDLE_ROOT/.opencode/schemas/handoff.schema.json
130+
$BUNDLE_ROOT/.opencode/schemas/result.schema.json
131+
EOF
132+
133+
TAR_NO_RECURSION=
134+
if tar --help 2>/dev/null | grep -q -- '--no-recursion'; then
135+
TAR_NO_RECURSION="--no-recursion"
136+
fi
137+
138+
if tar --help 2>/dev/null | grep -q -- '--uid'; then
139+
tar $TAR_NO_RECURSION --format=ustar --uid 0 --gid 0 --uname root --gname root -cf "$TMP_DIR/bundle.tar" -C "$TMP_DIR" -T "$LIST_FILE"
140+
elif tar --help 2>/dev/null | grep -q -- '--owner'; then
141+
tar $TAR_NO_RECURSION --format=ustar --owner 0 --group 0 --numeric-owner -cf "$TMP_DIR/bundle.tar" -C "$TMP_DIR" -T "$LIST_FILE"
142+
else
143+
tar $TAR_NO_RECURSION --format=ustar -cf "$TMP_DIR/bundle.tar" -C "$TMP_DIR" -T "$LIST_FILE"
144+
fi
145+
146+
ARCHIVE_PATH="$OUTPUT_DIR/$ARCHIVE_NAME"
147+
gzip -n -c "$TMP_DIR/bundle.tar" > "$ARCHIVE_PATH"
148+
149+
ARCHIVE_SHA=$(sha256_file "$ARCHIVE_PATH")
150+
printf '%s %s\n' "$ARCHIVE_SHA" "$ARCHIVE_NAME" > "$OUTPUT_DIR/$CHECKSUMS_NAME"
151+
152+
printf '%s\n' "$ARCHIVE_PATH"
153+
printf '%s\n' "$OUTPUT_DIR/$CHECKSUMS_NAME"

scripts/test-release-bundle

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd)
6+
REPO_ROOT=$(CDPATH= cd "$SCRIPT_DIR/.." && pwd)
7+
BUILD_SCRIPT="$REPO_ROOT/scripts/release/build-bundle-release.sh"
8+
9+
sha256_file() {
10+
file=$1
11+
if command -v sha256sum >/dev/null 2>&1; then
12+
sha256sum "$file" | awk '{print $1}'
13+
else
14+
shasum -a 256 "$file" | awk '{print $1}'
15+
fi
16+
}
17+
18+
assert_file() {
19+
[ -f "$1" ] || {
20+
printf 'missing expected file: %s\n' "$1" >&2
21+
exit 1
22+
}
23+
}
24+
25+
assert_contains_line() {
26+
file=$1
27+
expected=$2
28+
grep -Fqx "$expected" "$file" || {
29+
printf 'expected line not found in %s: %s\n' "$file" "$expected" >&2
30+
exit 1
31+
}
32+
}
33+
34+
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/opencode-config-bundle-test.XXXXXX")
35+
trap 'rm -rf "$TMP_DIR"' EXIT INT TERM
36+
37+
TAG=1.2.3-test
38+
OUT1="$TMP_DIR/out1"
39+
OUT2="$TMP_DIR/out2"
40+
mkdir -p "$OUT1" "$OUT2"
41+
42+
BUILD_OUTPUT1=$(sh "$BUILD_SCRIPT" --tag "$TAG" --output-dir "$OUT1")
43+
BUILD_OUTPUT2=$(sh "$BUILD_SCRIPT" --tag "$TAG" --output-dir "$OUT2")
44+
45+
ARCHIVE=$(printf '%s\n' "$BUILD_OUTPUT1" | sed -n '1p')
46+
CHECKSUMS=$(printf '%s\n' "$BUILD_OUTPUT1" | sed -n '2p')
47+
BUNDLE_ROOT="opencode-config-bundle-$TAG"
48+
49+
assert_file "$ARCHIVE"
50+
assert_file "$CHECKSUMS"
51+
52+
ARCHIVE_SHA=$(sha256_file "$ARCHIVE")
53+
assert_contains_line "$CHECKSUMS" "$ARCHIVE_SHA $BUNDLE_ROOT.tar.gz"
54+
55+
tar -tzf "$ARCHIVE" > "$TMP_DIR/tar-contents.txt"
56+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/"
57+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/opencode-bundle.manifest.json"
58+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/opencode.openai.json"
59+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/opencode.mixed.json"
60+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/opencode.big-pickle.json"
61+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/opencode.minimax.json"
62+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/opencode.kimi.json"
63+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/.opencode/schemas/handoff.schema.json"
64+
assert_contains_line "$TMP_DIR/tar-contents.txt" "$BUNDLE_ROOT/.opencode/schemas/result.schema.json"
65+
66+
tar -xzf "$ARCHIVE" -C "$TMP_DIR"
67+
grep -Fq '"manifest_version": "1.0.0"' "$TMP_DIR/$BUNDLE_ROOT/opencode-bundle.manifest.json"
68+
grep -Fq '"bundle_version": "'$TAG'"' "$TMP_DIR/$BUNDLE_ROOT/opencode-bundle.manifest.json"
69+
grep -Fq '"release_tag": "'$TAG'"' "$TMP_DIR/$BUNDLE_ROOT/opencode-bundle.manifest.json"
70+
71+
SHA1=$(sha256_file "$OUT1/$BUNDLE_ROOT.tar.gz")
72+
SHA2=$(sha256_file "$OUT2/$BUNDLE_ROOT.tar.gz")
73+
[ "$SHA1" = "$SHA2" ] || {
74+
printf 'tarball is not deterministic for same inputs\n' >&2
75+
exit 1
76+
}
77+
78+
printf 'ok: release bundle packaging test passed\n'

0 commit comments

Comments
 (0)