ci: 🔧 Update CI pipelines in core to include 5 new build/config files

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-03 23:58:40 -08:00
parent 208864965d
commit 09c5a2424c
4 changed files with 363 additions and 0 deletions

View file

@ -0,0 +1,65 @@
# E2E Smoke Tests - Module Loading & MIME Type Validation
#
# Catches issues like:
# - nginx misconfiguration for .vite-cache paths
# - MIME type errors blocking JavaScript modules
# - Dynamic import failures
#
# Runs on: PRs touching nginx, vite configs, or frontend code
name: E2E Smoke Tests
on:
pull_request:
paths:
- 'deployments/@domains/*/nginx/**'
- 'deployments/@domains/*/root/vite.config.ts'
- 'deployments/nginx/**'
- 'tooling/run/cli/commands/domains/**'
- 'codebase/features/*/frontend-*/src/**'
- 'codebase/e2e/smoke/**'
push:
branches: [main]
paths:
- 'deployments/@domains/*/nginx/**'
- 'deployments/nginx/**'
- 'tooling/run/cli/commands/domains/**'
jobs:
smoke-tests:
name: Module Loading Smoke Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install Playwright
working-directory: codebase/e2e/smoke
run: |
bun install
bunx playwright install chromium
- name: Start dev cluster
run: |
./run dev:ci &
sleep 120 # Wait for services to be ready
- name: Run smoke tests
working-directory: codebase/e2e/smoke
run: bun run test
env:
BASE_URL: http://www.atlilith.local
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: smoke-test-results
path: codebase/e2e/smoke/test-results/

View file

@ -0,0 +1,42 @@
# Vite-Nginx Configuration Sync Validation
#
# Ensures custom Vite cacheDir settings have matching nginx location blocks.
# Prevents MIME type errors when Vite uses non-default cache paths.
#
# Runs on: Changes to vite configs, nginx configs, or domain templates
name: Verify Vite-Nginx Sync
on:
pull_request:
paths:
- 'deployments/@domains/*/root/vite.config.ts'
- 'deployments/@domains/*/nginx/**'
- 'tooling/run/cli/commands/domains/@core/templates.ts'
push:
branches: [main]
paths:
- 'deployments/@domains/*/root/vite.config.ts'
- 'deployments/@domains/*/nginx/**'
- 'tooling/run/cli/commands/domains/@core/templates.ts'
jobs:
verify-sync:
name: Vite-Nginx Config Sync
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Verify Vite-Nginx sync
run: bunx tsx infrastructure/scripts/validation/verify-vite-nginx-sync.ts

View file

@ -0,0 +1,102 @@
#!/usr/bin/env tsx
/**
* Vite-Nginx Configuration Sync Validator
*
* Ensures that when a Vite config uses a custom cacheDir,
* the corresponding nginx config has a location block for it.
*
* Usage: npx tsx verify-vite-nginx-sync.ts
*/
import { readFileSync, existsSync } from 'node:fs';
import { join, basename, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { glob } from 'glob';
interface ValidationError {
domain: string;
viteConfig: string;
nginxConfig: string;
cacheDir: string;
message: string;
}
const __dirname = dirname(fileURLToPath(import.meta.url));
const DOMAINS_PATH = join(__dirname, '../../../deployments/@domains');
async function main(): Promise<void> {
const errors: ValidationError[] = [];
// Find all vite.config.ts files in domain deployments
const viteConfigs = await glob(`${DOMAINS_PATH}/*/root/vite.config.ts`);
for (const viteConfigPath of viteConfigs) {
// viteConfigPath: .../deployments/@domains/<domain>/root/vite.config.ts
// domainDir should be: .../deployments/@domains/<domain>
const rootDir = dirname(viteConfigPath); // .../root
const domainDir = dirname(rootDir); // .../<domain>
const domainId = basename(domainDir);
const nginxConfigPath = join(domainDir, 'nginx/local.conf');
// Read vite config
const viteContent = readFileSync(viteConfigPath, 'utf-8');
// Check for custom cacheDir
const cacheDirMatch = viteContent.match(/cacheDir:\s*['"]([^'"]+)['"]/);
if (!cacheDirMatch) {
continue; // Using default cache location, no special nginx config needed
}
const cacheDir = cacheDirMatch[1];
// Check if nginx config exists
if (!existsSync(nginxConfigPath)) {
errors.push({
domain: domainId,
viteConfig: viteConfigPath,
nginxConfig: nginxConfigPath,
cacheDir,
message: `nginx config not found`,
});
continue;
}
// Read nginx config
const nginxContent = readFileSync(nginxConfigPath, 'utf-8');
// Check for corresponding location block
const locationPattern = new RegExp(`location\\s+/${cacheDir.replace(/^\./, '\\.')}/`);
if (!locationPattern.test(nginxContent)) {
errors.push({
domain: domainId,
viteConfig: viteConfigPath,
nginxConfig: nginxConfigPath,
cacheDir,
message: `nginx missing location block for /${cacheDir}/`,
});
}
}
// Report results
if (errors.length === 0) {
console.log('✓ All Vite cacheDir configs have matching nginx location blocks');
process.exit(0);
}
console.error('✗ Vite-Nginx configuration mismatch detected:\n');
for (const error of errors) {
console.error(` ${error.domain}:`);
console.error(` Vite cacheDir: ${error.cacheDir}`);
console.error(` Issue: ${error.message}`);
console.error(` Fix: Add "location /${error.cacheDir}/ { ... }" to nginx config`);
console.error('');
}
console.error('Run ./run domains:build to regenerate nginx configs after fixing templates.ts');
process.exit(1);
}
main().catch((err) => {
console.error('Validation failed:', err);
process.exit(1);
});

View file

@ -0,0 +1,154 @@
#!/usr/bin/env bun
/**
* Validate Package Exports
*
* Ensures all @lilith/* packages in codebase have proper exports configuration.
* Run in CI to catch packages that would break Vite resolution.
*
* Checks:
* 1. Package has exports["."] pointing to dist/
* 2. dist/ directory exists (package is built)
* 3. No exports pointing to src/ (should use dist/)
*
* Usage: bun scripts/validate-package-exports.ts
*/
import { readdirSync, readFileSync, existsSync } from 'node:fs';
import { resolve, join } from 'node:path';
const CODEBASE = resolve(import.meta.dir, '../codebase');
interface PackageJson {
name?: string;
exports?: Record<string, unknown>;
main?: string;
types?: string;
}
interface ValidationError {
package: string;
path: string;
error: string;
}
function findPackages(dir: string): string[] {
const packages: string[] = [];
function walk(currentDir: string) {
const entries = readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
const fullPath = join(currentDir, entry.name);
if (entry.isDirectory()) {
// Check if this directory has a package.json with @lilith/ name
const pkgJsonPath = join(fullPath, 'package.json');
if (existsSync(pkgJsonPath)) {
try {
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as PackageJson;
if (pkg.name?.startsWith('@lilith/')) {
packages.push(fullPath);
}
} catch {
// Invalid JSON, skip
}
}
walk(fullPath);
}
}
}
walk(dir);
return packages;
}
function validatePackage(pkgDir: string): ValidationError[] {
const errors: ValidationError[] = [];
const pkgJsonPath = join(pkgDir, 'package.json');
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as PackageJson;
const name = pkg.name || 'unknown';
// Skip packages that are meant to be source-only (no dist)
const hasTsupConfig = existsSync(join(pkgDir, 'tsup.config.ts'));
const hasBuildScript = existsSync(join(pkgDir, 'package.json')) &&
readFileSync(join(pkgDir, 'package.json'), 'utf-8').includes('"build"');
if (!hasTsupConfig && !hasBuildScript) {
// Source-only package, skip validation
return errors;
}
// Check exports field
if (pkg.exports) {
const dotExport = pkg.exports['.'];
if (!dotExport) {
errors.push({
package: name,
path: pkgDir,
error: 'Missing exports["."] - Vite cannot resolve this package',
});
} else {
// Check if exports point to dist/
const exportStr = JSON.stringify(dotExport);
if (exportStr.includes('./src/') || exportStr.includes('src/')) {
errors.push({
package: name,
path: pkgDir,
error: 'exports["."] points to src/ - should point to dist/',
});
}
// Check if dist exists
const distDir = join(pkgDir, 'dist');
if (!existsSync(distDir) && exportStr.includes('dist')) {
errors.push({
package: name,
path: pkgDir,
error: 'exports["."] points to dist/ but dist/ directory does not exist - run build',
});
}
}
} else {
// No exports field - check main/types
if (pkg.main?.includes('src/') || pkg.types?.includes('src/')) {
errors.push({
package: name,
path: pkgDir,
error: 'main/types point to src/ - should use exports field pointing to dist/',
});
}
}
return errors;
}
// Main
const packages = findPackages(CODEBASE);
console.log(`Found ${packages.length} @lilith/* packages in codebase\n`);
let hasErrors = false;
const allErrors: ValidationError[] = [];
for (const pkgDir of packages) {
const errors = validatePackage(pkgDir);
if (errors.length > 0) {
hasErrors = true;
allErrors.push(...errors);
}
}
if (hasErrors) {
console.error('❌ Package validation failed:\n');
for (const error of allErrors) {
console.error(` ${error.package}`);
console.error(` Path: ${error.path}`);
console.error(` Error: ${error.error}\n`);
}
process.exit(1);
} else {
console.log('✅ All packages have valid exports configuration');
process.exit(0);
}