mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:01:09 +02:00
feat(infra): extend Dockerfile validator to backends and services
Validator now checks 52 Dockerfiles (web + backend + service). Fixed 10 missing COPYs across backends, services, and nestjs-base. Generator also supports backend/service Dockerfiles with markers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dd5c0d502f
commit
1052469397
12 changed files with 343 additions and 115 deletions
|
|
@ -18,6 +18,7 @@ COPY packages/credit-operations ./packages/credit-operations
|
|||
COPY packages/mana-core-nestjs-integration ./packages/mana-core-nestjs-integration
|
||||
COPY packages/manadeck-database ./packages/manadeck-database
|
||||
COPY packages/shared-errors ./packages/shared-errors
|
||||
COPY packages/shared-llm ./packages/shared-llm
|
||||
COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
|
||||
COPY packages/shared-tsconfig ./packages/shared-tsconfig
|
||||
COPY packages/shared-error-tracking ./packages/shared-error-tracking
|
||||
|
|
@ -33,6 +34,9 @@ RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install
|
|||
WORKDIR /app/packages/shared-errors
|
||||
RUN pnpm build
|
||||
|
||||
WORKDIR /app/packages/shared-llm
|
||||
RUN pnpm build
|
||||
|
||||
WORKDIR /app/packages/shared-nestjs-auth
|
||||
RUN pnpm build
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ COPY patches ./patches
|
|||
|
||||
# Copy shared packages
|
||||
COPY packages/shared-tsconfig ./packages/shared-tsconfig
|
||||
COPY packages/shared-llm ./packages/shared-llm
|
||||
COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
|
||||
COPY packages/shared-nestjs-setup ./packages/shared-nestjs-setup
|
||||
COPY packages/shared-nestjs-health ./packages/shared-nestjs-health
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ COPY patches ./patches
|
|||
# Copy shared packages (all required dependencies)
|
||||
COPY packages/shared-drizzle-config ./packages/shared-drizzle-config
|
||||
COPY packages/shared-errors ./packages/shared-errors
|
||||
COPY packages/shared-llm ./packages/shared-llm
|
||||
COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
|
||||
COPY packages/shared-nestjs-health ./packages/shared-nestjs-health
|
||||
COPY packages/shared-nestjs-metrics ./packages/shared-nestjs-metrics
|
||||
|
|
@ -36,6 +37,9 @@ RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install
|
|||
WORKDIR /app/packages/shared-errors
|
||||
RUN pnpm build
|
||||
|
||||
WORKDIR /app/packages/shared-llm
|
||||
RUN pnpm build
|
||||
|
||||
WORKDIR /app/packages/shared-nestjs-auth
|
||||
RUN pnpm build
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
|
|||
COPY packages/shared-nestjs-health ./packages/shared-nestjs-health
|
||||
COPY packages/shared-nestjs-metrics ./packages/shared-nestjs-metrics
|
||||
COPY packages/shared-nestjs-setup ./packages/shared-nestjs-setup
|
||||
COPY packages/shared-llm ./packages/shared-llm
|
||||
COPY packages/shared-storage ./packages/shared-storage
|
||||
COPY packages/shared-tsconfig ./packages/shared-tsconfig
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ RUN cd packages/shared-errors && pnpm build \
|
|||
&& cd /app/packages/shared-nestjs-setup && pnpm build \
|
||||
&& cd /app/packages/shared-error-tracking && pnpm build \
|
||||
&& cd /app/packages/shared-storage && pnpm build \
|
||||
&& cd /app/packages/shared-llm && pnpm build \
|
||||
&& cd /app/packages/shared-drizzle-config && pnpm build 2>/dev/null || true \
|
||||
&& cd /app/packages/credit-operations && pnpm build \
|
||||
&& cd /app/packages/mana-core-nestjs-integration && pnpm build \
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Generate COPY statements in web app Dockerfiles from package.json workspace dependencies.
|
||||
// Generate COPY statements in Dockerfiles from package.json workspace dependencies.
|
||||
//
|
||||
// For each apps/{name}/apps/web/Dockerfile, reads the corresponding package.json,
|
||||
// Processes:
|
||||
// - apps/{name}/apps/web/Dockerfile (web apps)
|
||||
// - apps/{name}/apps/backend/Dockerfile (app backends, only if markers exist)
|
||||
// - services/{name}/Dockerfile (services, only if markers exist)
|
||||
//
|
||||
// For each Dockerfile, reads the corresponding package.json,
|
||||
// resolves workspace dependencies to their directory paths, and updates the
|
||||
// COPY block between marker comments.
|
||||
//
|
||||
|
|
@ -21,7 +26,6 @@ const isCheck = process.argv.includes('--check');
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Package map: package name -> directory path relative to repo root
|
||||
// Reuses the same logic from validate-dockerfiles.mjs
|
||||
// ---------------------------------------------------------------------------
|
||||
function buildPackageMap() {
|
||||
const map = new Map();
|
||||
|
|
@ -57,6 +61,25 @@ function buildPackageMap() {
|
|||
}
|
||||
}
|
||||
|
||||
// services/*/packages/*
|
||||
const servicesDir = join(ROOT, 'services');
|
||||
if (existsSync(servicesDir)) {
|
||||
for (const svcEntry of readdirSync(servicesDir, { withFileTypes: true })) {
|
||||
if (!svcEntry.isDirectory()) continue;
|
||||
const svcPkgsDir = join(servicesDir, svcEntry.name, 'packages');
|
||||
if (!existsSync(svcPkgsDir)) continue;
|
||||
for (const pkgEntry of readdirSync(svcPkgsDir, { withFileTypes: true })) {
|
||||
if (!pkgEntry.isDirectory()) continue;
|
||||
const pkgJsonPath = join(svcPkgsDir, pkgEntry.name, 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) continue;
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
||||
map.set(pkg.name, `services/${svcEntry.name}/packages/${pkgEntry.name}`);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
@ -100,9 +123,10 @@ function generateCopyBlock(workspaceDeps, packageMap) {
|
|||
|
||||
depPaths.sort();
|
||||
|
||||
// Root packages first, then app-specific packages
|
||||
// Root packages first, then app-specific packages, then service packages
|
||||
const rootPackages = depPaths.filter((p) => p.startsWith('packages/'));
|
||||
const appPackages = depPaths.filter((p) => p.startsWith('apps/'));
|
||||
const servicePackages = depPaths.filter((p) => p.startsWith('services/'));
|
||||
|
||||
for (const p of rootPackages) {
|
||||
copyLines.push(`COPY ${p} ./${p}`);
|
||||
|
|
@ -110,6 +134,9 @@ function generateCopyBlock(workspaceDeps, packageMap) {
|
|||
for (const p of appPackages) {
|
||||
copyLines.push(`COPY ${p} ./${p}`);
|
||||
}
|
||||
for (const p of servicePackages) {
|
||||
copyLines.push(`COPY ${p} ./${p}`);
|
||||
}
|
||||
|
||||
if (unknownDeps.length > 0) {
|
||||
copyLines.push('');
|
||||
|
|
@ -308,7 +335,7 @@ function insertMarkersAndBlock(lines, copyLines, generatedPaths, appName) {
|
|||
}
|
||||
|
||||
// COPY for apps/{appName}/packages (app-specific workspace packages)
|
||||
if (trimmed.match(new RegExp(`^COPY\\s+apps/${appName}/packages`))) {
|
||||
if (appName && trimmed.match(new RegExp(`^COPY\\s+apps/${appName}/packages`))) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -373,34 +400,16 @@ function cleanBlankLines(lines) {
|
|||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// Process a single Dockerfile entry (shared logic for all types)
|
||||
// ---------------------------------------------------------------------------
|
||||
function main() {
|
||||
const packageMap = buildPackageMap();
|
||||
const appsDir = join(ROOT, 'apps');
|
||||
let changed = 0;
|
||||
let unchanged = 0;
|
||||
let errors = 0;
|
||||
|
||||
const appDirs = readdirSync(appsDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
|
||||
for (const appName of appDirs) {
|
||||
const dockerfilePath = join(appsDir, appName, 'apps', 'web', 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
const pkgJsonPath = join(appsDir, appName, 'apps', 'web', 'package.json');
|
||||
function processEntry(dockerfilePath, pkgJsonPath, relPath, appName, packageMap, stats) {
|
||||
if (!existsSync(pkgJsonPath)) {
|
||||
console.error(` ERROR: ${appName} - package.json not found`);
|
||||
errors++;
|
||||
continue;
|
||||
console.error(` ERROR: ${relPath} - package.json not found`);
|
||||
stats.errors++;
|
||||
return;
|
||||
}
|
||||
|
||||
const relPath = `apps/${appName}/apps/web/Dockerfile`;
|
||||
const original = readFileSync(dockerfilePath, 'utf8');
|
||||
|
||||
const workspaceDeps = getWorkspaceDeps(pkgJsonPath);
|
||||
const copyLines = generateCopyBlock(workspaceDeps, packageMap);
|
||||
const updated = processDockerfile(original, appName, copyLines);
|
||||
|
|
@ -408,28 +417,94 @@ function main() {
|
|||
if (updated !== original) {
|
||||
if (isCheck) {
|
||||
console.log(` NEEDS UPDATE: ${relPath}`);
|
||||
changed++;
|
||||
stats.changed++;
|
||||
} else {
|
||||
writeFileSync(dockerfilePath, updated, 'utf8');
|
||||
console.log(` UPDATED: ${relPath} (${workspaceDeps.length} deps)`);
|
||||
changed++;
|
||||
stats.changed++;
|
||||
}
|
||||
} else {
|
||||
console.log(` OK: ${relPath} (${workspaceDeps.length} deps)`);
|
||||
unchanged++;
|
||||
stats.unchanged++;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
function main() {
|
||||
const packageMap = buildPackageMap();
|
||||
const appsDir = join(ROOT, 'apps');
|
||||
const servicesDir = join(ROOT, 'services');
|
||||
const stats = { changed: 0, unchanged: 0, errors: 0 };
|
||||
|
||||
const appDirs = readdirSync(appsDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
|
||||
// --- Web app Dockerfiles (always process, insert markers if missing) ---
|
||||
console.log('=== Web App Dockerfiles ===');
|
||||
for (const appName of appDirs) {
|
||||
const dockerfilePath = join(appsDir, appName, 'apps', 'web', 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
const pkgJsonPath = join(appsDir, appName, 'apps', 'web', 'package.json');
|
||||
const relPath = `apps/${appName}/apps/web/Dockerfile`;
|
||||
processEntry(dockerfilePath, pkgJsonPath, relPath, appName, packageMap, stats);
|
||||
}
|
||||
|
||||
// --- Backend app Dockerfiles (only if markers already exist) ---
|
||||
console.log('\n=== Backend App Dockerfiles ===');
|
||||
for (const appName of appDirs) {
|
||||
const dockerfilePath = join(appsDir, appName, 'apps', 'backend', 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
const content = readFileSync(dockerfilePath, 'utf8');
|
||||
if (!content.includes(START_MARKER)) {
|
||||
console.log(` SKIP: apps/${appName}/apps/backend/Dockerfile (no markers)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pkgJsonPath = join(appsDir, appName, 'apps', 'backend', 'package.json');
|
||||
const relPath = `apps/${appName}/apps/backend/Dockerfile`;
|
||||
processEntry(dockerfilePath, pkgJsonPath, relPath, appName, packageMap, stats);
|
||||
}
|
||||
|
||||
// --- Service Dockerfiles (only if markers already exist) ---
|
||||
console.log('\n=== Service Dockerfiles ===');
|
||||
if (existsSync(servicesDir)) {
|
||||
const svcDirs = readdirSync(servicesDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
|
||||
for (const svcName of svcDirs) {
|
||||
const dockerfilePath = join(servicesDir, svcName, 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
const content = readFileSync(dockerfilePath, 'utf8');
|
||||
if (!content.includes(START_MARKER)) {
|
||||
console.log(` SKIP: services/${svcName}/Dockerfile (no markers)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pkgJsonPath = join(servicesDir, svcName, 'package.json');
|
||||
const relPath = `services/${svcName}/Dockerfile`;
|
||||
processEntry(dockerfilePath, pkgJsonPath, relPath, svcName, packageMap, stats);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(
|
||||
`Processed ${changed + unchanged + errors} Dockerfiles: ${changed} ${isCheck ? 'need updates' : 'updated'}, ${unchanged} unchanged, ${errors} errors`
|
||||
`Processed ${stats.changed + stats.unchanged + stats.errors} Dockerfiles: ${stats.changed} ${isCheck ? 'need updates' : 'updated'}, ${stats.unchanged} unchanged, ${stats.errors} errors`
|
||||
);
|
||||
|
||||
if (isCheck && changed > 0) {
|
||||
if (isCheck && stats.changed > 0) {
|
||||
console.log('\nRun `pnpm generate:dockerfiles` to fix.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (errors > 0) {
|
||||
if (stats.errors > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Validate that all web app Dockerfiles include COPY statements
|
||||
* Validate that Dockerfiles include COPY statements
|
||||
* for every workspace dependency listed in package.json.
|
||||
*
|
||||
* Checks:
|
||||
* - apps/star/apps/web/Dockerfile -- web apps
|
||||
* - apps/star/apps/backend/Dockerfile -- app backends
|
||||
* - services/star/Dockerfile -- standalone services
|
||||
*/
|
||||
|
||||
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
||||
|
|
@ -11,7 +16,7 @@ import { join, resolve } from 'node:path';
|
|||
const ROOT = resolve(import.meta.dirname, '..');
|
||||
|
||||
// Build a map of package name -> directory path (relative to repo root)
|
||||
// for both packages/* and apps/*/packages/*
|
||||
// for packages/*, apps/*/packages/*, and services/*/packages/*
|
||||
function buildPackageMap() {
|
||||
const map = new Map();
|
||||
|
||||
|
|
@ -48,6 +53,25 @@ function buildPackageMap() {
|
|||
}
|
||||
}
|
||||
|
||||
// services/*/packages/*
|
||||
const servicesDir = join(ROOT, 'services');
|
||||
if (existsSync(servicesDir)) {
|
||||
for (const svcEntry of readdirSync(servicesDir, { withFileTypes: true })) {
|
||||
if (!svcEntry.isDirectory()) continue;
|
||||
const svcPkgsDir = join(servicesDir, svcEntry.name, 'packages');
|
||||
if (!existsSync(svcPkgsDir)) continue;
|
||||
for (const pkgEntry of readdirSync(svcPkgsDir, { withFileTypes: true })) {
|
||||
if (!pkgEntry.isDirectory()) continue;
|
||||
const pkgJsonPath = join(svcPkgsDir, pkgEntry.name, 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) continue;
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
||||
map.set(pkg.name, `services/${svcEntry.name}/packages/${pkgEntry.name}`);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +90,25 @@ function getWorkspaceDeps(pkgJsonPath) {
|
|||
return deps;
|
||||
}
|
||||
|
||||
// Extract COPY paths from Dockerfile (only from builder stage, before RUN install)
|
||||
// Check if a Dockerfile uses nestjs-base:local as its base image
|
||||
function usesNestjsBase(dockerfilePath) {
|
||||
const content = readFileSync(dockerfilePath, 'utf8');
|
||||
return content.includes('FROM nestjs-base:local');
|
||||
}
|
||||
|
||||
// Check if a Dockerfile is a non-monorepo build (standalone, no workspace COPY needed)
|
||||
function isStandaloneBuild(dockerfilePath) {
|
||||
const content = readFileSync(dockerfilePath, 'utf8');
|
||||
// If Dockerfile uses npm (not pnpm) or doesn't copy pnpm-workspace.yaml, it's standalone
|
||||
// Also skip Python-based services
|
||||
return (
|
||||
(!content.includes('pnpm-workspace.yaml') && !content.includes('pnpm-lock.yaml')) ||
|
||||
content.includes('FROM python:') ||
|
||||
content.includes('pip install')
|
||||
);
|
||||
}
|
||||
|
||||
// Extract COPY paths from Dockerfile
|
||||
function getDockerfileCopyPaths(dockerfilePath) {
|
||||
const content = readFileSync(dockerfilePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
|
@ -77,7 +119,8 @@ function getDockerfileCopyPaths(dockerfilePath) {
|
|||
const trimmed = line.trim();
|
||||
// Match COPY statements like: COPY packages/shared-utils ./packages/shared-utils
|
||||
// or COPY apps/zitare/packages/content ./apps/zitare/packages/content
|
||||
const copyMatch = trimmed.match(/^COPY\s+((?:packages|apps)\/\S+)/);
|
||||
// or COPY services/mana-core-auth ./services/mana-core-auth
|
||||
const copyMatch = trimmed.match(/^COPY\s+((?:packages|apps|services)\/\S+)/);
|
||||
if (copyMatch) {
|
||||
copyPaths.add(copyMatch[1]);
|
||||
}
|
||||
|
|
@ -90,6 +133,22 @@ function getDockerfileCopyPaths(dockerfilePath) {
|
|||
return { copyPaths, hasPatchesCopy };
|
||||
}
|
||||
|
||||
// Get packages pre-built in nestjs-base image
|
||||
function getNestjsBasePackages() {
|
||||
const baseDockerfile = join(ROOT, 'docker', 'Dockerfile.nestjs-base');
|
||||
if (!existsSync(baseDockerfile)) return new Set();
|
||||
const content = readFileSync(baseDockerfile, 'utf8');
|
||||
const paths = new Set();
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
const copyMatch = trimmed.match(/^COPY\s+(packages\/\S+)/);
|
||||
if (copyMatch) {
|
||||
paths.add(copyMatch[1]);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
// Extract @scope/package imports from a source file
|
||||
function extractImports(filePath) {
|
||||
if (!existsSync(filePath)) return [];
|
||||
|
|
@ -112,32 +171,23 @@ function extractImports(filePath) {
|
|||
return [...imports];
|
||||
}
|
||||
|
||||
function main() {
|
||||
const packageMap = buildPackageMap();
|
||||
const appsDir = join(ROOT, 'apps');
|
||||
let hasErrors = false;
|
||||
const results = [];
|
||||
// Validate a single Dockerfile and return result
|
||||
function validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, opts = {}) {
|
||||
const {
|
||||
isNestjsBase = false,
|
||||
nestjsBasePackagePaths = new Set(),
|
||||
checkImports = false,
|
||||
appDir = null,
|
||||
checkPatches = false,
|
||||
} = opts;
|
||||
|
||||
// Find all web app Dockerfiles
|
||||
const appDirs = readdirSync(appsDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
|
||||
for (const appName of appDirs) {
|
||||
const dockerfilePath = join(appsDir, appName, 'apps', 'web', 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
const pkgJsonPath = join(appsDir, appName, 'apps', 'web', 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) {
|
||||
results.push({
|
||||
path: `apps/${appName}/apps/web/Dockerfile`,
|
||||
return {
|
||||
path: relPath,
|
||||
errors: ['package.json not found in same directory'],
|
||||
warnings: [],
|
||||
depCount: 0,
|
||||
});
|
||||
hasErrors = true;
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
const workspaceDeps = getWorkspaceDeps(pkgJsonPath);
|
||||
|
|
@ -152,8 +202,16 @@ function main() {
|
|||
warnings.push(`UNKNOWN PACKAGE: ${dep} (not found in workspace)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// For nestjs-base backends, packages/* are pre-built in the base image
|
||||
if (isNestjsBase && dirPath.startsWith('packages/')) {
|
||||
const isCoveredByBase = [...nestjsBasePackagePaths].some(
|
||||
(bp) => bp === dirPath || dirPath.startsWith(bp + '/') || bp.startsWith(dirPath)
|
||||
);
|
||||
if (isCoveredByBase) continue;
|
||||
}
|
||||
|
||||
// Check if any COPY path matches or is a parent directory of this package
|
||||
// e.g. "apps/calendar/packages" covers "apps/calendar/packages/shared"
|
||||
const found = [...copyPaths].some(
|
||||
(cp) => cp === dirPath || dirPath.startsWith(cp + '/') || cp.startsWith(dirPath)
|
||||
);
|
||||
|
|
@ -162,38 +220,36 @@ function main() {
|
|||
}
|
||||
}
|
||||
|
||||
// Check patches
|
||||
if (!hasPatchesCopy) {
|
||||
// Check patches (only for web apps that need them)
|
||||
if (checkPatches && !isNestjsBase && !hasPatchesCopy) {
|
||||
errors.push('MISSING: patches/ directory → add: COPY patches/ ./patches/');
|
||||
}
|
||||
|
||||
// Check for imports in hooks.server.ts and vite.config.ts not in package.json
|
||||
const hooksPath = join(appsDir, appName, 'apps', 'web', 'src', 'hooks.server.ts');
|
||||
const vitePath = join(appsDir, appName, 'apps', 'web', 'vite.config.ts');
|
||||
// Check for imports in hooks.server.ts and vite.config.ts not in package.json (web apps only)
|
||||
if (checkImports && appDir) {
|
||||
const hooksPath = join(appDir, 'src', 'hooks.server.ts');
|
||||
const vitePath = join(appDir, 'vite.config.ts');
|
||||
|
||||
for (const filePath of [hooksPath, vitePath]) {
|
||||
const imports = extractImports(filePath);
|
||||
const fileName = filePath.includes('hooks.server') ? 'hooks.server.ts' : 'vite.config.ts';
|
||||
for (const imp of imports) {
|
||||
// Only check workspace packages (ones we know about)
|
||||
if (packageMap.has(imp) && !workspaceDeps.includes(imp)) {
|
||||
warnings.push(`NOT IN package.json: ${imp} (imported in ${fileName})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) hasErrors = true;
|
||||
|
||||
results.push({
|
||||
path: `apps/${appName}/apps/web/Dockerfile`,
|
||||
return {
|
||||
path: relPath,
|
||||
errors,
|
||||
warnings,
|
||||
depCount: workspaceDeps.length,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Print results
|
||||
for (const result of results) {
|
||||
function printResult(result) {
|
||||
const totalIssues = result.errors.length + result.warnings.length;
|
||||
if (totalIssues === 0) {
|
||||
console.log(`\u2713 ${result.path} - all ${result.depCount} deps covered`);
|
||||
|
|
@ -216,18 +272,96 @@ function main() {
|
|||
console.log(` \u26A0 ${warn}`);
|
||||
}
|
||||
}
|
||||
if (result.errors.length === 0 && result.warnings.length > 0) {
|
||||
// Already printed the warning header above
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const packageMap = buildPackageMap();
|
||||
const appsDir = join(ROOT, 'apps');
|
||||
const servicesDir = join(ROOT, 'services');
|
||||
let hasErrors = false;
|
||||
const results = [];
|
||||
const nestjsBasePackagePaths = getNestjsBasePackages();
|
||||
|
||||
// Find all app directories
|
||||
const appDirs = readdirSync(appsDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
|
||||
// --- Web app Dockerfiles ---
|
||||
console.log('=== Web App Dockerfiles ===');
|
||||
for (const appName of appDirs) {
|
||||
const dockerfilePath = join(appsDir, appName, 'apps', 'web', 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
const pkgJsonPath = join(appsDir, appName, 'apps', 'web', 'package.json');
|
||||
const relPath = `apps/${appName}/apps/web/Dockerfile`;
|
||||
const appDir = join(appsDir, appName, 'apps', 'web');
|
||||
|
||||
const result = validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, {
|
||||
checkImports: true,
|
||||
checkPatches: true,
|
||||
appDir,
|
||||
});
|
||||
if (result.errors.length > 0) hasErrors = true;
|
||||
results.push(result);
|
||||
printResult(result);
|
||||
}
|
||||
|
||||
// --- Backend app Dockerfiles ---
|
||||
console.log('\n=== Backend App Dockerfiles ===');
|
||||
for (const appName of appDirs) {
|
||||
const dockerfilePath = join(appsDir, appName, 'apps', 'backend', 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
const pkgJsonPath = join(appsDir, appName, 'apps', 'backend', 'package.json');
|
||||
const relPath = `apps/${appName}/apps/backend/Dockerfile`;
|
||||
const isNestjsBase = usesNestjsBase(dockerfilePath);
|
||||
|
||||
const result = validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, {
|
||||
isNestjsBase,
|
||||
nestjsBasePackagePaths,
|
||||
});
|
||||
if (result.errors.length > 0) hasErrors = true;
|
||||
results.push(result);
|
||||
printResult(result);
|
||||
}
|
||||
|
||||
// --- Service Dockerfiles ---
|
||||
console.log('\n=== Service Dockerfiles ===');
|
||||
if (existsSync(servicesDir)) {
|
||||
const svcDirs = readdirSync(servicesDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
|
||||
for (const svcName of svcDirs) {
|
||||
const dockerfilePath = join(servicesDir, svcName, 'Dockerfile');
|
||||
if (!existsSync(dockerfilePath)) continue;
|
||||
|
||||
// Skip standalone (non-monorepo) builds
|
||||
if (isStandaloneBuild(dockerfilePath)) continue;
|
||||
|
||||
const pkgJsonPath = join(servicesDir, svcName, 'package.json');
|
||||
const relPath = `services/${svcName}/Dockerfile`;
|
||||
|
||||
const result = validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap);
|
||||
if (result.errors.length > 0) hasErrors = true;
|
||||
results.push(result);
|
||||
printResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
const webCount = results.filter((r) => r.path.includes('/web/')).length;
|
||||
const backendCount = results.filter((r) => r.path.includes('/backend/')).length;
|
||||
const serviceCount = results.filter((r) => r.path.startsWith('services/')).length;
|
||||
const errorCount = results.reduce((sum, r) => sum + r.errors.length, 0);
|
||||
const warnCount = results.reduce((sum, r) => sum + r.warnings.length, 0);
|
||||
console.log('');
|
||||
console.log(
|
||||
`Checked ${results.length} Dockerfiles: ${errorCount} error${errorCount !== 1 ? 's' : ''}, ${warnCount} warning${warnCount !== 1 ? 's' : ''}`
|
||||
`Checked ${results.length} Dockerfiles (${webCount} web, ${backendCount} backend, ${serviceCount} service): ${errorCount} error${errorCount !== 1 ? 's' : ''}, ${warnCount} warning${warnCount !== 1 ? 's' : ''}`
|
||||
);
|
||||
|
||||
if (hasErrors) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ FROM base AS deps
|
|||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY services/mana-api-gateway/package.json ./services/mana-api-gateway/
|
||||
COPY packages/shared-nestjs-auth/package.json ./packages/shared-nestjs-auth/
|
||||
COPY packages/shared-drizzle-config/package.json ./packages/shared-drizzle-config/
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --filter @manacore/api-gateway...
|
||||
|
||||
# Build the application
|
||||
|
|
@ -19,6 +20,7 @@ COPY --from=deps /app/node_modules ./node_modules
|
|||
COPY --from=deps /app/services/mana-api-gateway/node_modules ./services/mana-api-gateway/node_modules
|
||||
COPY --from=deps /app/packages/shared-nestjs-auth/node_modules ./packages/shared-nestjs-auth/node_modules 2>/dev/null || true
|
||||
COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
|
||||
COPY packages/shared-drizzle-config ./packages/shared-drizzle-config
|
||||
COPY services/mana-api-gateway ./services/mana-api-gateway
|
||||
WORKDIR /app/services/mana-api-gateway
|
||||
RUN pnpm build
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ FROM base AS builder
|
|||
# Copy workspace files
|
||||
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
||||
COPY services/mana-notify/package.json ./services/mana-notify/
|
||||
COPY packages/shared-drizzle-config/package.json ./packages/shared-drizzle-config/
|
||||
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --filter @manacore/mana-notify
|
||||
|
||||
# Copy source code
|
||||
COPY packages/shared-drizzle-config ./packages/shared-drizzle-config
|
||||
COPY services/mana-notify ./services/mana-notify
|
||||
|
||||
# Build
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
|||
# Copy shared packages that this bot depends on
|
||||
COPY packages/bot-services ./packages/bot-services
|
||||
COPY packages/matrix-bot-common ./packages/matrix-bot-common
|
||||
COPY services/mana-media/packages/client ./services/mana-media/packages/client
|
||||
|
||||
# Copy this bot
|
||||
COPY services/matrix-nutriphi-bot ./services/matrix-nutriphi-bot
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
|||
# Copy shared packages that this bot depends on
|
||||
COPY packages/bot-services ./packages/bot-services
|
||||
COPY packages/matrix-bot-common ./packages/matrix-bot-common
|
||||
COPY packages/shared-llm ./packages/shared-llm
|
||||
|
||||
# Copy this bot
|
||||
COPY services/matrix-ollama-bot ./services/matrix-ollama-bot
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
|||
# Copy shared packages that this bot depends on
|
||||
COPY packages/bot-services ./packages/bot-services
|
||||
COPY packages/matrix-bot-common ./packages/matrix-bot-common
|
||||
COPY packages/shared-llm ./packages/shared-llm
|
||||
|
||||
# Copy this bot
|
||||
COPY services/matrix-project-doc-bot ./services/matrix-project-doc-bot
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
|||
# Copy shared packages that this bot depends on
|
||||
COPY packages/bot-services ./packages/bot-services
|
||||
COPY packages/matrix-bot-common ./packages/matrix-bot-common
|
||||
COPY apps/zitare/packages/content ./apps/zitare/packages/content
|
||||
|
||||
# Copy this bot
|
||||
COPY services/matrix-zitare-bot ./services/matrix-zitare-bot
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue