-
Notifications
You must be signed in to change notification settings - Fork 0
235 lines (227 loc) · 10.3 KB
/
Copy pathci.yml
File metadata and controls
235 lines (227 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
name: CI
on:
pull_request:
push:
branches: [main]
# Live-CDN checks run on demand: Actions tab → CI → "Run workflow"
# (optionally with a version), or on any push to main.
workflow_dispatch:
inputs:
version:
description: "tinycloud version to smoke-test (empty = latest)"
required: false
jobs:
unit:
name: Wrapper unit + e2e tests
runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: shellcheck install.sh scripts/smoke-test.sh skills/tinycloud/scripts/preflight.sh
plugin-metadata:
name: Plugin + skill metadata
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: JSON metadata is valid
run: |
python3 - <<'EOF'
import json
for p in [".claude-plugin/marketplace.json", ".claude-plugin/plugin.json",
"skills/tinycloud/tinycloud-skill.json"]:
json.load(open(p))
print("OK", p)
EOF
- name: Plugin + marketplace versions match package.json
# These three drifted apart once: plugin.json and marketplace.json sat
# at 0.3.19 through the 0.3.20 and 0.3.21 releases because nothing
# checked them. The npm package version is the release version, so
# every version field in the plugin metadata must equal it.
run: |
python3 - <<'EOF'
import json, sys
pkg = json.load(open("package.json"))["version"]
plugin = json.load(open(".claude-plugin/plugin.json"))["version"]
mkt = json.load(open(".claude-plugin/marketplace.json"))
found = {
"package.json": pkg,
".claude-plugin/plugin.json": plugin,
".claude-plugin/marketplace.json metadata.version": mkt["metadata"]["version"],
}
for entry in mkt["plugins"]:
found[f'.claude-plugin/marketplace.json plugins[{entry["name"]}].version'] = entry["version"]
bad = {k: v for k, v in found.items() if v != pkg}
for k, v in found.items():
print(f'{"ok " if v == pkg else "DRIFT"} {k} = {v}')
if bad:
print(f"\nVersion drift against package.json ({pkg}): {bad}", file=sys.stderr)
sys.exit(1)
print(f"\nAll plugin metadata versions match package.json ({pkg}).")
EOF
- name: Skill manifest version matches package.json
# tinycloud-skill.json's skill_version tracks the release too (the
# floor fields min_version/supported_range are checked separately
# against preflight.sh, and may legitimately sit below on a no-raise
# release -- so only skill_version is pinned here).
run: |
python3 - <<'EOF'
import json, sys
pkg = json.load(open("package.json"))["version"]
skill = json.load(open("skills/tinycloud/tinycloud-skill.json"))["skill_version"]
print(f"package.json={pkg} tinycloud-skill.json skill_version={skill}")
if skill != pkg:
print(f"skill_version {skill} != package.json {pkg}", file=sys.stderr)
sys.exit(1)
print("ok")
EOF
- name: Preflight requirements match tinycloud-skill.json
run: |
python3 - <<'EOF'
import json, re, sys
skill = json.load(open("skills/tinycloud/tinycloud-skill.json"))["tinycloud"]
script = open("skills/tinycloud/scripts/preflight.sh").read()
manifest = set(skill["required_features"])
preflight = set(re.search(r'REQUIRED_FEATURES="([^"]+)"', script).group(1).split())
if manifest != preflight:
print("Feature mismatch:", manifest.symmetric_difference(preflight))
sys.exit(1)
min_version = re.search(r'MIN_VERSION="([^"]+)"', script).group(1)
if min_version != skill["min_version"]:
print(f"min_version mismatch: preflight={min_version} manifest={skill['min_version']}")
sys.exit(1)
max_excl = re.search(r'MAX_VERSION_EXCLUSIVE="([^"]+)"', script).group(1)
range_max = re.search(r'<\s*([0-9][\w.+-]*)', skill["supported_range"]).group(1)
if max_excl != range_max:
print(f"supported_range upper bound mismatch: preflight={max_excl} manifest=<{range_max}")
sys.exit(1)
print(f"In sync: {len(manifest)} features, range >={min_version} <{max_excl}")
EOF
- name: Every skill has frontmatter description
run: |
for f in skills/*/SKILL.md; do
head -1 "$f" | grep -q '^---$' || { echo "FAIL: $f missing frontmatter"; exit 1; }
grep -q '^description:' "$f" || { echo "FAIL: $f missing description"; exit 1; }
echo "OK $f"
done
smoke:
name: Install + smoke (${{ matrix.os }}, ${{ matrix.version || 'latest' }})
runs-on: ${{ matrix.os }}
# Live-CDN checks don't gate PRs (a CDN gap would fail every PR); they run
# on pushes to main and manual dispatch instead.
if: github.event_name != 'pull_request'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
version: ["", "0.3.0"]
env:
TINYCLOUD_INSTALL_DIR: ${{ github.workspace }}/tc/bin
steps:
- uses: actions/checkout@v4
- name: Install via this repo's installer
run: |
if [ -n "${{ matrix.version || github.event.inputs.version }}" ]; then
bash install.sh --version "${{ matrix.version || github.event.inputs.version }}"
else
bash install.sh
fi
- name: Validate the deployed endpoint (latest only)
if: matrix.version == ''
# Resolution-only check — the binary itself was already downloaded and
# smoke-tested by the previous step; a second ~90MB install of the
# same tarball proves nothing extra.
run: |
curl -fsSL https://app.cloudglue.dev/tinycloud.sh -o "$RUNNER_TEMP/deployed.sh"
if URL=$(bash "$RUNNER_TEMP/deployed.sh" --print-url 2>/dev/null); then
curl -fsSIL "$URL" >/dev/null
echo "deployed endpoint resolves: $URL"
else
echo "::warning::deployed tinycloud.sh predates --print-url (redeploy pending); skipping endpoint validation"
fi
- name: Contract smoke tests
run: |
TINYCLOUD_CMD="$TINYCLOUD_INSTALL_DIR/tinycloud" \
EXPECTED_VERSION="${{ matrix.version }}" \
bash scripts/smoke-test.sh
- name: Skill preflight (exit 13 no-credentials, or exit 11 below skill floor)
# The skill's min_version can be ahead of the CDN binary (docs land
# with the release that introduces them), and the pinned matrix leg
# stays on an old version forever — so derive the expected exit code
# from the installed binary vs the skill floor instead of hardcoding.
run: |
export PATH="$TINYCLOUD_INSTALL_DIR:$PATH"
MIN=$(python3 -c "import json; print(json.load(open('skills/tinycloud/tinycloud-skill.json'))['tinycloud']['min_version'])")
VER=$(tinycloud --version --json </dev/null | python3 -c "import json,sys; print(json.load(sys.stdin)['version'])")
set +e
out=$(bash skills/tinycloud/scripts/preflight.sh)
code=$?
set -e
echo "$out (exit $code; binary $VER, skill floor $MIN)"
LOWEST=$(printf '%s\n%s\n' "$VER" "$MIN" | sort -t. -k1,1n -k2,2n -k3,3n | head -n1)
if [ "$LOWEST" != "$MIN" ]; then
[ "$code" -eq 11 ] || { echo "expected exit 11 (binary $VER below skill floor $MIN)"; exit 1; }
echo "$out" | grep -q "below required"
else
[ "$code" -eq 13 ] || { echo "expected exit 13 (missing credentials)"; exit 1; }
echo "$out" | grep -q "credentials missing"
fi
- name: Skill preflight (missing binary → exit 10)
run: |
set +e
out=$(PATH=/usr/bin:/bin bash skills/tinycloud/scripts/preflight.sh)
code=$?
set -e
echo "$out (exit $code)"
[ "$code" -eq 10 ] || { echo "expected exit 10 (binary missing)"; exit 1; }
- name: Skill-creator scaffolder contract
run: |
export PATH="$TINYCLOUD_INSTALL_DIR:$PATH"
SCAFFOLDER="$TINYCLOUD_INSTALL_DIR/skills/skill-creator/scripts/init-skill.js"
test -f "$SCAFFOLDER" || { echo "scaffolder missing from distribution"; exit 1; }
node "$SCAFFOLDER" ci-test-skill --description "CI scaffold check" --dir "$RUNNER_TEMP/skills" --pattern workflow
tinycloud workflow validate "$RUNNER_TEMP/skills/ci-test-skill/ci-test-skill.yaml" --json \
| grep -q '"recipe":"ci-test-skill"'
wrapper-e2e-live:
name: npx wrapper against live CDN (${{ matrix.os }})
runs-on: ${{ matrix.os }}
# Live-CDN checks don't gate PRs — see the smoke job comment.
if: github.event_name != 'pull_request'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Pack the wrapper
run: npm pack
- name: First run downloads latest and execs
env:
TINYCLOUD_INSTALL_DIR: ${{ runner.temp }}/tc-root
TINYCLOUD_VERSION: latest
run: |
# </dev/null: pre-0.3.0 binaries open a TUI on this invocation
npx -y ./cloudglue-tinycloud-*.tgz --version --json </dev/null | grep -q '"protocol_version"'
- name: Second run hits the cache (no network)
env:
TINYCLOUD_INSTALL_DIR: ${{ runner.temp }}/tc-root
TINYCLOUD_VERSION: latest
TINYCLOUD_DIST_URL: http://127.0.0.1:1 # unreachable — must not be needed
run: |
# resolve the cached version dir and pin to it (latest would re-resolve)
v=$(ls "$TINYCLOUD_INSTALL_DIR/versions" | head -1)
TINYCLOUD_VERSION="$v" npx -y ./cloudglue-tinycloud-*.tgz --version --json </dev/null \
| grep -q '"protocol_version"'