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
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,10 @@ jobs:
# failed with "vite: not found" while this job stayed green.
- name: Docker build (production args)
run: docker build --build-arg NODE_ENV=production -t frontend-ci-check .

- name: Verify served assets and runtime configuration
run: |
docker run -d --name frontend-smoke -p 127.0.0.1:8080:8080 \
-e VITE_OSWATCHER_API_URI=http://localhost:4000/ frontend-ci-check
trap 'docker rm -f frontend-smoke' EXIT
node scripts/smoke-test.mjs http://127.0.0.1:8080
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public; there is no login flow and no token injection.
4. This frontend is not meant to run standalone against a self-hosted backend: point it at an `oswatcher-deploy` stack (Docker Compose orchestration for the GraphQL API, Neo4j, and MinIO). `.env` is conventionally a symlink to `../oswatcher-deploy/.env` when both repos are checked out as siblings.

#### Build Configuration
- Production base path set to `/frontend/` in Vite config (assumes deployment behind `oswatcher-deploy`'s reverse proxy)
- Base path is `/` in all modes: the container and `oswatcher-deploy` serve the app at the domain root

### Documentation Structure
The project includes comprehensive technical reference documentation:
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ Parsing and formatting utilities for PDB data:
- **Environment**: Local API endpoints

### 2. Production Build
- **Base Path**: `/frontend/` (sub-path deployment)
- **Base Path**: `/` (served at the domain root)
- **Optimization**: Code splitting, minification
- **Assets**: Hashed filenames for caching
- **Environment**: Production API endpoints
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ These variables are included for local development setup of the complete system

### Vite Configuration

The `vite.config.ts` sets different base paths for development vs production:
The `vite.config.ts` uses the domain root in development and production, matching the container and deployment proxy:

```typescript
base: process.env.NODE_ENV === 'production' ? '/frontend/' : '/'
base: '/'
```

### TypeScript Environment Types
Expand Down
41 changes: 41 additions & 0 deletions scripts/smoke-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Test the served container, including failures hidden by SPA fallback HTTP 200s.
import assert from 'node:assert/strict'
import { setTimeout as delay } from 'node:timers/promises'

const base = process.argv[2] || 'http://127.0.0.1:8080'
const expectedApi = 'http://localhost:4000/'
let html
for (let attempt = 0; attempt < 30; attempt++) {
try {
const response = await fetch(base, { signal: AbortSignal.timeout(2000) })
assert.equal(response.status, 200)
html = await response.text()
break
} catch (error) {
if (attempt === 29) throw error
await delay(500)
}
}

const assets = [...html.matchAll(/(?:src|href)="([^"]+\.(?:js|css))"/g)].map(match => match[1])
assert(assets.some(path => path.endsWith('.js')), 'Page must load JavaScript')
assert(assets.some(path => path.endsWith('.css')), 'Page must load CSS')
let configuredApi = false
for (const path of assets) {
assert(path.startsWith('/assets/'), `Asset must load from the root: ${path}`)
const response = await fetch(new URL(path, base), { signal: AbortSignal.timeout(10000) })
assert.equal(response.status, 200, `Asset status: ${path}`)
const type = response.headers.get('content-type') || ''
assert(type.includes(path.endsWith('.js') ? 'javascript' : 'text/css'),
`Asset returned ${type}, possibly the SPA HTML fallback: ${path}`)
const content = await response.text()
assert(!content.includes('__OSW_API_URI__'), `Unresolved API placeholder: ${path}`)
configuredApi ||= content.includes(expectedApi)
}
assert(configuredApi, 'Runtime API URL must appear in the served bundle')

// Vue Router uses history mode; direct navigation must also serve the app.
const route = await fetch(new URL('/inspect/smoke-test', base), { signal: AbortSignal.timeout(10000) })
assert.equal(route.status, 200)
assert.equal(await route.text(), html, 'Deep links must serve the same app shell')
console.log(`PASS: ${assets.length} JS/CSS assets, runtime API URL, and history-route fallback`)
3 changes: 2 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export default defineConfig({
vue(),
vueJsx()
],
base: process.env.NODE_ENV === 'production' ? '/frontend/' : '/',
// The container and oswatcher-deploy both serve the app at the domain root.
base: '/',
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
Expand Down
Loading