import { writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { services as SERVICE_REGISTRY } from './services/registry'; import type { ServiceName } from './services/types'; import { createN8NStack, type N8NStack } from './stack'; export interface ServiceStackOptions { services: ServiceName[]; projectName?: string; } /** * Creates a stack with only services (no n8n containers). * Useful for integration tests that need databases/services but not full n8n. * * @example * const stack = await createServiceStack({ services: ['postgres'] }); * const pgContainer = stack.serviceResults.postgres?.container; * const host = pgContainer.getHost(); * const port = pgContainer.getMappedPort(5432); * await stack.stop(); */ export async function createServiceStack(options: ServiceStackOptions): Promise { const { services, projectName } = options; return await createN8NStack({ mains: 0, workers: 0, postgres: services.includes('postgres'), services, projectName, external: true, }); } /** * Collects host-reachable env vars (mapped ports, localhost) for every service * in the stack. Used by `pnpm services` to produce the .env that `pnpm dev` * picks up. */ export function collectExternalEnv( stack: N8NStack, services: ServiceName[], ): Record { const env: Record = {}; for (const name of services) { const result = stack.serviceResults[name]; if (!result) continue; const service = SERVICE_REGISTRY[name]; Object.assign(env, service.env?.(result, true) ?? {}, service.extraEnv?.(result, true) ?? {}); } return env; } /** * Path to the .env file that `pnpm dev` / `pnpm start` reads. `pnpm start` * runs `os-normalize.mjs` which `cd`s into `packages/cli/bin` before launching * n8n, and dotenv loads from cwd. */ export function devEnvFilePath(): string { return resolve(__dirname, '../../..', 'packages/cli/bin/.env'); } /** * Writes the dev-loop .env file consumed by `pnpm dev`. Returns the env vars * that were written so callers can log a summary. */ export function writeDevEnvFile(stack: N8NStack, services: ServiceName[]): Record { const env = collectExternalEnv(stack, services); if (Object.keys(env).length === 0) return env; const lines = [ '# Generated by pnpm services — do not edit', `# Project: ${stack.projectName}`, '# Stop with: pnpm --filter n8n-containers services:clean', '', ...Object.entries(env).map(([key, value]) => `${key}=${value}`), '', ]; writeFileSync(devEnvFilePath(), lines.join('\n')); return env; }