Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/docker-smoke-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: docker-smoke-test

on:
pull_request:

jobs:
docker:
name: Docker (${{ matrix.runtime }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
include:
- runtime: bun
dockerfile: docker-tests/Dockerfile.bun
- runtime: node
dockerfile: docker-tests/Dockerfile.node
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build image
run: docker build -f ${{ matrix.dockerfile }} -t chronicle-${{ matrix.runtime }} .

- name: Run container
run: docker run -d --name chronicle-${{ matrix.runtime }} -p 3000:3000 chronicle-${{ matrix.runtime }}

- name: Wait for server
run: |
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/api/ready > /dev/null 2>&1; then
echo "Server ready"
exit 0
fi
sleep 2
done
echo "Server failed to start"
docker logs chronicle-${{ matrix.runtime }}
exit 1

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24

- name: Smoke tests
run: node docker-tests/smoke-test.mjs

- name: Cleanup
if: always()
run: docker rm -f chronicle-${{ matrix.runtime }} || true
20 changes: 20 additions & 0 deletions docker-tests/Dockerfile.bun
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM oven/bun:1.3 AS builder
WORKDIR /app
COPY package.json bun.lock ./
COPY packages/chronicle/package.json ./packages/chronicle/
RUN bun install --frozen-lockfile
COPY packages/chronicle ./packages/chronicle
RUN cd packages/chronicle && bun build-cli.ts
RUN cd packages/chronicle && bun pm pack --destination /app

FROM oven/bun:1.3-slim AS runner
WORKDIR /app
RUN bun init -y
COPY --from=builder /app/raystack-chronicle-*.tgz ./chronicle.tgz
RUN bun add ./chronicle.tgz && rm chronicle.tgz
COPY examples/basic ./examples/basic
RUN bunx chronicle build --config examples/basic/chronicle.yaml --preset bun

EXPOSE 3000

CMD ["bunx", "chronicle", "start", "--config", "examples/basic/chronicle.yaml", "--port", "3000", "--host", "0.0.0.0"]
20 changes: 20 additions & 0 deletions docker-tests/Dockerfile.node
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM oven/bun:1.3 AS builder
WORKDIR /app
COPY package.json bun.lock ./
COPY packages/chronicle/package.json ./packages/chronicle/
RUN bun install --frozen-lockfile
COPY packages/chronicle ./packages/chronicle
RUN cd packages/chronicle && bun build-cli.ts
RUN cd packages/chronicle && bun pm pack --destination /app

FROM node:24-slim AS runner
WORKDIR /app
RUN npm init -y
COPY --from=builder /app/raystack-chronicle-*.tgz ./chronicle.tgz
RUN npm install ./chronicle.tgz && rm chronicle.tgz
COPY examples/basic ./examples/basic
RUN npx chronicle build --config examples/basic/chronicle.yaml

EXPOSE 3000

CMD ["npx", "chronicle", "start", "--config", "examples/basic/chronicle.yaml", "--port", "3000", "--host", "0.0.0.0"]
76 changes: 76 additions & 0 deletions docker-tests/smoke-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import assert from 'node:assert/strict';

const BASE = process.env.BASE_URL || 'http://localhost:3000';
let failed = 0;

console.log(`Smoke tests against ${BASE}\n`);

// index returns 200 or 307
{
const res = await fetch(`${BASE}/`);
assert.ok(res.status === 200 || res.status === 307, `index: expected 200 or 307, got ${res.status}`);
console.log(' ✓ index returns 200 or 307');
}

// page API returns frontmatter
{
const res = await fetch(`${BASE}/api/page?slug=docs,getting-started`);
assert.equal(res.status, 200, `page API: expected 200, got ${res.status}`);
const data = await res.json();
assert.ok(data.frontmatter, 'page API: missing frontmatter');
assert.ok(data.frontmatter.title, 'page API: missing title');
console.log(' ✓ page API returns frontmatter');
}

// search API returns results
{
const res = await fetch(`${BASE}/api/search`);
assert.equal(res.status, 200, `search API: expected 200, got ${res.status}`);
const data = await res.json();
assert.ok(Array.isArray(data), 'search API: expected array');
assert.ok(data.length > 0, 'search API: expected results');
assert.ok(data[0].url, 'search API: missing url');
console.log(' ✓ search API returns results');
}

// search with query returns matches
{
const res = await fetch(`${BASE}/api/search?query=getting`);
assert.equal(res.status, 200, `search query: expected 200, got ${res.status}`);
const data = await res.json();
assert.ok(data.length > 0, 'search query: expected results');
assert.ok(data[0].match, 'search query: missing match field');
console.log(' ✓ search with query returns matches');
}

// image API resizes PNG
{
const res = await fetch(`${BASE}/api/image?url=${encodeURIComponent('/_content/docs/test-image.png')}&w=320`);
assert.equal(res.status, 200, `image API: expected 200, got ${res.status}`);
const ct = res.headers.get('content-type');
assert.ok(ct.startsWith('image/'), `image API: expected image content-type, got ${ct}`);
console.log(' ✓ image API resizes PNG');
}

// image API returns 400 for missing params
{
const res = await fetch(`${BASE}/api/image`);
assert.equal(res.status, 400, `image 400: expected 400, got ${res.status}`);
console.log(' ✓ image API returns 400 for missing params');
}

// image API returns 400 for invalid width
{
const res = await fetch(`${BASE}/api/image?url=${encodeURIComponent('/_content/docs/test-image.png')}&w=999`);
assert.equal(res.status, 400, `image invalid width: expected 400, got ${res.status}`);
console.log(' ✓ image API returns 400 for invalid width');
}

// image API returns 404 for missing image
{
const res = await fetch(`${BASE}/api/image?url=${encodeURIComponent('/_content/does-not-exist.png')}&w=640`);
assert.equal(res.status, 404, `image 404: expected 404, got ${res.status}`);
console.log(' ✓ image API returns 404 for missing image');
}

console.log('\nALL PASSED');
Binary file added examples/basic/content/docs/test-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/chronicle/src/server/vite-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export async function createViteConfig(
nitro({
serverDir: path.resolve(packageRoot, 'src/server'),
...(preset && { preset }),
ignore: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
}),
mdx({
default: defineFumadocsConfig({
Expand Down
Loading