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:
parent
208864965d
commit
09c5a2424c
4 changed files with 363 additions and 0 deletions
65
.forgejo/workflows/e2e-smoke.yml
Normal file
65
.forgejo/workflows/e2e-smoke.yml
Normal 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/
|
||||||
42
.forgejo/workflows/verify-vite-nginx-sync.yml
Normal file
42
.forgejo/workflows/verify-vite-nginx-sync.yml
Normal 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
|
||||||
102
infrastructure/scripts/validation/verify-vite-nginx-sync.ts
Normal file
102
infrastructure/scripts/validation/verify-vite-nginx-sync.ts
Normal 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);
|
||||||
|
});
|
||||||
154
scripts/validate-package-exports.ts
Executable file
154
scripts/validate-package-exports.ts
Executable 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);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue