Skip to content

Commit d8e038d

Browse files
committed
feat: add API version negotiation and post-deploy contract validation
- Server reads X-OpenBoot-Version header from CLI requests - Returns X-OpenBoot-Upgrade: true if CLI is older than MIN_CLI_VERSION - Deploy job now runs smoke-test-api.sh and golden-path/test.sh after health check
1 parent 873244a commit d8e038d

File tree

2 files changed

+29
-0
lines changed

2 files changed

+29
-0
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,12 @@ jobs:
133133
echo "API: $(echo $HEALTH_RESPONSE | jq -r '.checks.api')"
134134
echo "Database: $(echo $HEALTH_RESPONSE | jq -r '.checks.database')"
135135
echo "Version: $(echo $HEALTH_RESPONSE | jq -r '.version')"
136+
137+
- name: Post-deploy smoke test
138+
run: ./scripts/smoke-test-api.sh https://openboot.dev
139+
140+
- name: Post-deploy contract validation
141+
run: |
142+
pip install jsonschema
143+
git clone --depth 1 https://github.com/openbootdotdev/openboot-contract.git /tmp/contract
144+
SERVER_URL=https://openboot.dev /tmp/contract/golden-path/test.sh

src/hooks.server.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { RESERVED_ALIASES } from '$lib/server/validation';
44

55
const INSTALL_SCRIPT_URL = 'https://raw.githubusercontent.com/openbootdotdev/openboot/main/scripts/install.sh';
66

7+
// Minimum CLI version that is compatible with the current API.
8+
// Bump this when a breaking API change is deployed.
9+
const MIN_CLI_VERSION = '0.25.0';
10+
711
const SECURITY_HEADERS: Record<string, string> = {
812
'X-Frame-Options': 'DENY',
913
'X-Content-Type-Options': 'nosniff',
@@ -36,6 +40,13 @@ function withSecurityHeaders(response: Response): Response {
3640
});
3741
}
3842

43+
function isVersionOlderThan(version: string, minVersion: string): boolean {
44+
const parse = (v: string) => v.replace(/^v/, '').split('.').map(Number);
45+
const [aMaj = 0, aMin = 0, aPat = 0] = parse(version);
46+
const [bMaj = 0, bMin = 0, bPat = 0] = parse(minVersion);
47+
return aMaj < bMaj || (aMaj === bMaj && (aMin < bMin || (aMin === bMin && aPat < bPat)));
48+
}
49+
3950
export const handle: Handle = async ({ event, resolve }) => {
4051
const path = event.url.pathname;
4152

@@ -157,6 +168,15 @@ export const handle: Handle = async ({ event, resolve }) => {
157168
const response = await resolve(event);
158169
const securedResponse = withSecurityHeaders(response);
159170

171+
// Version negotiation: if CLI sends X-OpenBoot-Version, check compatibility.
172+
const cliVersion = event.request.headers.get('x-openboot-version');
173+
if (cliVersion && cliVersion !== 'dev') {
174+
securedResponse.headers.set('X-OpenBoot-Min-Version', MIN_CLI_VERSION);
175+
if (isVersionOlderThan(cliVersion, MIN_CLI_VERSION)) {
176+
securedResponse.headers.set('X-OpenBoot-Upgrade', 'true');
177+
}
178+
}
179+
160180
// Prevent indexing of non-content routes
161181
const noindexPrefixes = ['/api/', '/dashboard/', '/cli-auth/', '/install'];
162182
const noindexSuffixes = ['/install', '/config', '/og'];

0 commit comments

Comments
 (0)