-
Notifications
You must be signed in to change notification settings - Fork 5
235 lines (212 loc) · 9.6 KB
/
Copy pathscaffold-e2e.yml
File metadata and controls
235 lines (212 loc) · 9.6 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
# Scaffold E2E — the first-run experience as a regression gate (#2908).
#
# Two failure classes broke `npm create objectstack` in the past and neither
# was visible to unit tests:
# 1. The bundled template drifted from the published framework (pinned
# ^6.0.0 while the registry was at 14.x; used APIs removed in 14.x).
# 2. Nothing ever exercised the published package end-to-end, so registry
# breakage would only be found by a real new user.
#
# Job 1 (PRs touching the scaffolder / docker example) scaffolds with the
# repo-built scaffolder and runs the generated project against the registry:
# install → validate → build → boot → health probes → docker build/run.
# Job 2 (nightly) does the same via the *published* `create-objectstack@latest`
# across every template in the registry — the new-user canary.
name: Scaffold E2E
on:
pull_request:
paths:
- 'packages/create-objectstack/**'
- 'examples/docker/**'
- 'docker/**'
- '.github/workflows/scaffold-e2e.yml'
schedule:
- cron: '23 3 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
scaffold-local:
name: Scaffold with repo dist
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Enable Corepack
run: corepack enable
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build the scaffolder
run: pnpm --filter create-objectstack build
- name: Scaffold a project
run: |
cd "$RUNNER_TEMP"
node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" e2e-app --skip-install --skip-skills
- name: Install generated project (registry deps)
run: |
cd "$RUNNER_TEMP/e2e-app"
# The scaffolder pins ^<repo version>. Right after a version bump
# lands but before the release publishes, that version is not on the
# registry yet — fall back to the latest published release so the
# template is still exercised against the real registry.
if ! npm install --no-fund --no-audit; then
echo "::warning::repo version not published yet — falling back to latest"
node -e '
const fs = require("fs");
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
for (const deps of [pkg.dependencies, pkg.devDependencies]) {
if (!deps) continue;
for (const d of Object.keys(deps)) {
if (d.startsWith("@objectstack/")) deps[d] = "latest";
}
}
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
'
npm install --no-fund --no-audit
fi
- name: Validate and build the generated project
run: |
cd "$RUNNER_TEMP/e2e-app"
npm run validate
npm run build
- name: Boot from the artifact and probe health
run: |
cd "$RUNNER_TEMP/e2e-app"
npx os start --artifact ./dist/objectstack.json --port 8080 > server.log 2>&1 &
SERVER_PID=$!
ok=""
for i in $(seq 1 30); do
if curl -fsS http://localhost:8080/api/v1/health > /dev/null 2>&1; then ok=1; break; fi
sleep 2
done
if [ -z "$ok" ]; then
echo "::error::server never became healthy"; cat server.log; exit 1
fi
curl -fsS http://localhost:8080/api/v1/ready
kill "$SERVER_PID"
- name: 'Skills boundary: only the curated skills/ catalog installs'
# 15.1 third-party eval: the repo-internal dogfood-verification skill
# (.claude/skills/) leaked into scaffolded projects. Two leak paths,
# two assertions against THIS checkout:
# 1. The scaffolder/docs command is `skills add …/framework/skills
# --all` — the /skills subpath is the hard boundary, because the
# skills CLI's --all implies --skill '*' which INCLUDES
# metadata.internal skills. Install from the subpath and assert
# set-equality with the curated catalog.
# 2. Interactive `skills add …/framework` (no --all) relies on
# metadata.internal hiding — assert repo-root discovery surfaces
# exactly the curated set and nothing internal.
run: |
cd "$RUNNER_TEMP"
node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" skills-probe --skip-install --skip-skills
cd skills-probe
expected=$(cd "$GITHUB_WORKSPACE/skills" && ls -d objectstack-*/ | tr -d '/' | sort)
expected_count=$(echo "$expected" | wc -l | tr -d ' ')
npx -y skills add "$GITHUB_WORKSPACE/skills" --all --copy
installed=$(find . -name SKILL.md -not -path '*/node_modules/*' -exec dirname {} \; | xargs -rn1 basename | sort -u)
echo "installed skills:"; echo "$installed"
if [ "$installed" != "$expected" ]; then
echo "::error::scaffolded skill set != curated skills/ catalog (internal-skill leak or missing skill)"
printf 'expected:\n%s\n' "$expected"
exit 1
fi
# Strip ANSI color codes — the skills CLI colors the count even
# when piped ("Found \e[32m9\e[39m skills"), which broke the greps.
listing=$(npx -y skills add "$GITHUB_WORKSPACE" --list 2>&1 | sed 's/\x1b\[[0-9;]*m//g')
echo "$listing"
if echo "$listing" | grep -q 'dogfood-verification'; then
echo "::error::repo-root discovery surfaced an internal skill (metadata.internal marker missing?)"
exit 1
fi
if ! echo "$listing" | grep -q "Found $expected_count skill"; then
echo "::error::repo-root discovery does not match the curated catalog (expected $expected_count skills) — a SKILL.md outside skills/ is missing metadata.internal"
exit 1
fi
- name: Build official runtime image from this checkout
# examples/docker builds FROM ghcr.io/objectstack-ai/objectstack.
# Build that base HERE from docker/Dockerfile instead of pulling, so
# (a) PRs exercise the official runtime Dockerfile itself, and
# (b) the e2e stays hermetic — no dependency on a prior release
# having published the tag (chicken-and-egg on the very first one).
run: |
docker build -t ghcr.io/objectstack-ai/objectstack:latest \
--build-arg OS_CLI_VERSION=latest \
"$GITHUB_WORKSPACE/docker"
- name: Docker build and run (examples/docker)
run: |
cd "$RUNNER_TEMP/e2e-app"
cp "$GITHUB_WORKSPACE"/examples/docker/Dockerfile \
"$GITHUB_WORKSPACE"/examples/docker/docker-compose.yml \
"$GITHUB_WORKSPACE"/examples/docker/.dockerignore .
docker build --pull=false -t e2e-app .
docker run -d --name e2e -p 18080:8080 \
-e OS_SECRET_KEY="$(openssl rand -hex 32)" \
-e OS_AUTH_SECRET="$(openssl rand -hex 32)" \
e2e-app
ok=""
for i in $(seq 1 30); do
if curl -fsS http://localhost:18080/api/v1/health > /dev/null 2>&1; then ok=1; break; fi
sleep 2
done
if [ -z "$ok" ]; then
echo "::error::container never became healthy"; docker logs e2e; exit 1
fi
docker rm -f e2e
registry-canary:
name: 'Registry canary: ${{ matrix.template }}'
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
template: [blank, todo, compliance, content, contracts, procurement]
steps:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Scaffold with published create-objectstack@latest
run: |
cd "$RUNNER_TEMP"
npx -y create-objectstack@latest canary-app -t ${{ matrix.template }} --skip-skills
# Remote templates ship `build` (same gates) but not always `validate`.
- name: Gate the generated project
run: |
cd "$RUNNER_TEMP/canary-app"
if node -e "process.exit(JSON.parse(require('fs').readFileSync('package.json','utf8')).scripts?.validate ? 0 : 1)"; then
npm run validate
fi
npm run build
- name: Boot and probe health (blank only)
if: matrix.template == 'blank'
run: |
cd "$RUNNER_TEMP/canary-app"
npx os start --artifact ./dist/objectstack.json --port 8080 > server.log 2>&1 &
SERVER_PID=$!
ok=""
for i in $(seq 1 30); do
if curl -fsS http://localhost:8080/api/v1/health > /dev/null 2>&1; then ok=1; break; fi
sleep 2
done
if [ -z "$ok" ]; then
echo "::error::server never became healthy"; cat server.log; exit 1
fi
curl -fsS http://localhost:8080/api/v1/ready
kill "$SERVER_PID"