n8n/packages/testing/containers/service-stack.ts
Declan Carroll 87cfbbbc6e
docs: Document pnpm services + pnpm dev developer loop (#30515)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:16:07 +00:00

84 lines
2.5 KiB
TypeScript

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<N8NStack> {
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<string, string> {
const env: Record<string, string> = {};
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<string, string> {
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;
}