144 lines
5.3 KiB
Text
144 lines
5.3 KiB
Text
# =============================================================================
|
|
# Blog API - E2E Production Build
|
|
# =============================================================================
|
|
#
|
|
# Simple build: install from package.json with @lilith scope pointing to
|
|
# local registry. Uses bun for installation (handles workspace:* in deps),
|
|
# Node.js for NestJS SWC runtime.
|
|
#
|
|
# Build args:
|
|
# NPM_REGISTRY - Registry URL for @lilith packages
|
|
|
|
FROM oven/bun:1.1-alpine AS builder
|
|
|
|
WORKDIR /app
|
|
|
|
ARG NPM_REGISTRY=http://local-registry:4874/
|
|
|
|
# Install build dependencies needed by native modules and node for nest build
|
|
RUN apk add --no-cache python3 make g++ nodejs npm
|
|
|
|
# Copy the feature source (backend-api + shared types)
|
|
COPY codebase/features/blog/backend-api/ ./feature/
|
|
COPY codebase/features/blog/shared/ ./shared/
|
|
|
|
# Clean workspace artifacts and configure registry
|
|
RUN rm -rf feature/node_modules feature/.pnpm-store feature/bun.lockb feature/bun.lock feature/pnpm-lock.yaml && \
|
|
echo '[install.scopes."@lilith"]' > feature/bunfig.toml && \
|
|
echo "url = \"${NPM_REGISTRY}\"" >> feature/bunfig.toml
|
|
|
|
# Export registry URL for package.json transformation script
|
|
ENV NPM_REGISTRY=${NPM_REGISTRY}
|
|
|
|
# Transform '*' and 'workspace:*' deps to actual registry versions
|
|
# (bun interprets * as workspace:* which fails outside workspace context)
|
|
WORKDIR /app/feature
|
|
RUN cat > /tmp/patch-pkg.ts << 'PATCH_EOF'
|
|
const REGISTRY = process.env.NPM_REGISTRY || "http://local-registry:4874/";
|
|
|
|
// Fetch latest version from registry for a package
|
|
async function getLatestVersion(pkgName: string): Promise<string | null> {
|
|
try {
|
|
const url = `${REGISTRY}${pkgName.replace("/", "%2F")}`;
|
|
const res = await fetch(url);
|
|
if (!res.ok) return null;
|
|
const data = await res.json() as { "dist-tags"?: { latest?: string } };
|
|
return data["dist-tags"]?.latest || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const pkg = await Bun.file("package.json").json();
|
|
const deps = pkg.dependencies || {};
|
|
const devDeps = pkg.devDependencies || {};
|
|
|
|
// Transform problematic versions (* and workspace:*) to actual registry versions
|
|
const transformDeps = async (depObj: Record<string, string>) => {
|
|
for (const [name, version] of Object.entries(depObj)) {
|
|
if (!name.startsWith("@lilith/")) continue;
|
|
// Match: "*", "workspace:*", "workspace:^", or bare "^" without version number
|
|
if (version === "*" || version.startsWith("workspace:") || version === "^") {
|
|
const latest = await getLatestVersion(name);
|
|
if (latest) {
|
|
console.log(`Transformed ${name}: "${version}" -> "^${latest}"`);
|
|
depObj[name] = `^${latest}`;
|
|
} else {
|
|
console.warn(`Could not fetch version for ${name}, keeping "${version}"`);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
await transformDeps(deps);
|
|
await transformDeps(devDeps);
|
|
|
|
// Pin service-nestjs-bootstrap to 2.2.3 (2.2.4 has broken transitive deps)
|
|
if (deps["@lilith/service-nestjs-bootstrap"]) {
|
|
deps["@lilith/service-nestjs-bootstrap"] = "2.2.3";
|
|
console.log("Pinned @lilith/service-nestjs-bootstrap to 2.2.3");
|
|
}
|
|
|
|
// Replace workspace blog-shared dep with local file reference for build
|
|
// (blog-shared is copied to ../shared/ in the Docker context)
|
|
if (deps["@lilith/blog-shared"]) {
|
|
delete deps["@lilith/blog-shared"];
|
|
console.log("Removed @lilith/blog-shared (will use path alias)");
|
|
}
|
|
|
|
pkg.dependencies = deps;
|
|
pkg.devDependencies = devDeps;
|
|
await Bun.write("package.json", JSON.stringify(pkg, null, 2));
|
|
console.log("Package.json patched for E2E build");
|
|
PATCH_EOF
|
|
RUN bun run /tmp/patch-pkg.ts
|
|
|
|
# Create tsconfig path alias for blog-shared (workspace dep replacement)
|
|
RUN cat > /tmp/patch-tsconfig.ts << 'TSCONFIG_EOF'
|
|
const tsconfig = await Bun.file("tsconfig.json").json();
|
|
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
|
|
tsconfig.compilerOptions.paths["@lilith/blog-shared"] = ["../shared/src"];
|
|
tsconfig.compilerOptions.paths["@lilith/blog-shared/*"] = ["../shared/src/*"];
|
|
await Bun.write("tsconfig.json", JSON.stringify(tsconfig, null, 2));
|
|
console.log("Added blog-shared path alias to tsconfig.json");
|
|
TSCONFIG_EOF
|
|
RUN bun run /tmp/patch-tsconfig.ts
|
|
|
|
# Install dependencies
|
|
RUN bun install
|
|
|
|
# Symlink shared source into node_modules so NestJS SWC can resolve it
|
|
RUN mkdir -p node_modules/@lilith && \
|
|
ln -s /app/shared node_modules/@lilith/blog-shared
|
|
|
|
# Build the application (NestJS with SWC) - use npx for nest CLI
|
|
RUN npx @nestjs/cli build
|
|
|
|
# =============================================================================
|
|
# Production stage - Minimal runtime
|
|
# =============================================================================
|
|
FROM node:22-alpine AS production
|
|
|
|
WORKDIR /app
|
|
|
|
# Install wget for healthcheck, socat for port forwarding to Docker services
|
|
RUN apk add --no-cache wget socat
|
|
|
|
# Copy built artifacts
|
|
COPY --from=builder /app/feature/dist ./dist
|
|
COPY --from=builder /app/feature/node_modules ./node_modules
|
|
COPY --from=builder /app/feature/package.json ./
|
|
|
|
# Copy shared types (seed module imports from @lilith/blog-shared at runtime)
|
|
COPY --from=builder /app/shared ./node_modules/@lilith/blog-shared
|
|
|
|
EXPOSE 3021
|
|
|
|
ENV PORT=3021
|
|
ENV NODE_ENV=production
|
|
|
|
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s --retries=3 \
|
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3021/health || exit 1
|
|
|
|
CMD ["node", "dist/main.js"]
|