Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion packages/software-factory/src/cli/serve-realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,30 @@ import { startFactoryRealmServer } from '../harness';

let log = logger('serve-realm');

function parseCliArg(name: string): string | undefined {
let prefix = `--${name}=`;
let arg = process.argv.find((a) => a.startsWith(prefix));
return arg ? arg.slice(prefix.length) : undefined;
}

function parseCliNumber(name: string): number | undefined {
let value = parseCliArg(name);
if (value == null) {
return undefined;
}
let parsed = Number(value);
if (!Number.isFinite(parsed)) {
throw new Error(`--${name} must be a valid number, received: ${value}`);
}
return parsed;
}

async function main(): Promise<void> {
// First positional arg is realmDir (skip --flags)
let positional = process.argv.slice(2).filter((a) => !a.startsWith('--'));
let realmDir = resolve(
process.cwd(),
process.argv[2] ?? 'test-fixtures/darkfactory-adopter',
positional[0] ?? 'test-fixtures/darkfactory-adopter',
);

if (!process.env.SOFTWARE_FACTORY_CONTEXT) {
Expand Down Expand Up @@ -44,6 +64,9 @@ async function main(): Promise<void> {
.SOFTWARE_FACTORY_TEMPLATE_REALM_SERVER_URL
? new URL(process.env.SOFTWARE_FACTORY_TEMPLATE_REALM_SERVER_URL)
: undefined,
realmServerPort: parseCliNumber('realmServerPort'),
compatRealmServerPort: parseCliNumber('compatRealmServerPort'),
prerenderURL: parseCliArg('prerenderURL'),
});

let payload = {
Expand Down
5 changes: 5 additions & 0 deletions packages/software-factory/src/harness/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export async function startFactoryGlobalContext(
let { realmURL, realmServerURL } = await resolveFactoryRealmLocation({
realmURL: options.realmURL,
realmServerURL: options.realmServerURL,
compatRealmServerPort: options.compatRealmServerPort,
});
let support = await startFactorySupportServices();
try {
Expand Down Expand Up @@ -125,6 +126,7 @@ export async function ensureFactoryRealmTemplate(
let { realmURL, realmServerURL } = await resolveFactoryRealmLocation({
realmURL: options.realmURL ?? contextRealmURL,
realmServerURL: options.realmServerURL ?? contextRealmServerURL,
compatRealmServerPort: options.compatRealmServerPort,
});
let permissions = options.permissions ?? DEFAULT_PERMISSIONS;
let fixtureHash = hashRealmFixture(realmDir);
Expand Down Expand Up @@ -356,6 +358,7 @@ export async function startFactoryRealmServer(
let { realmURL, realmServerURL } = await resolveFactoryRealmLocation({
realmURL: options.realmURL ?? contextRealmURL,
realmServerURL: options.realmServerURL ?? contextRealmServerURL,
compatRealmServerPort: options.compatRealmServerPort,
});
let templateDatabaseName = options.templateDatabaseName;
let databaseName = runtimeDatabaseName();
Expand Down Expand Up @@ -448,6 +451,8 @@ export async function startFactoryRealmServer(
context,
migrateDB: false,
fullIndexOnStartup: false,
realmServerPort: options.realmServerPort,
prerenderURL: options.prerenderURL,
});
} catch (error) {
let cleanupError: unknown;
Expand Down
21 changes: 14 additions & 7 deletions packages/software-factory/src/harness/isolated-realm-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ import {
baseRealmDir,
baseRealmURLFor,
captureProcessLogs,
CONFIGURED_PRERENDER_URL,
createProcessExitPromise,
DEFAULT_MATRIX_SERVER_USERNAME,
DEFAULT_PG_HOST,
DEFAULT_PG_POOL_MAX,
DEFAULT_PG_PORT,
DEFAULT_PG_USER,
DEFAULT_REALM_LOG_LEVELS,
DEFAULT_REALM_SERVER_PORT,
findAvailablePort,
FIXTURE_SOURCE_REALM_URL_PLACEHOLDER,
FULL_INDEX_REALM_STARTUP_TIMEOUT_MS,
Expand Down Expand Up @@ -277,6 +275,8 @@ export async function startIsolatedRealmStack({
fullIndexOnStartup,
additionalRealms,
workerManagerPort: explicitWorkerManagerPort,
realmServerPort: explicitRealmServerPort,
prerenderURL: explicitPrerenderURL,
}: {
realmDir: string;
realmURL: URL;
Expand All @@ -290,6 +290,13 @@ export async function startIsolatedRealmStack({
* picking one dynamically. This lets callers know the port upfront (e.g.
* for progress monitoring via /_indexing-status). */
workerManagerPort?: number;
/** When provided, the realm-server will listen on this port instead of
* picking one dynamically. */
realmServerPort?: number;
/** When provided, reuse this existing prerender server URL instead of
* starting a new one. The Playwright harness keeps prerender alive for
* the lifetime of a testWorker and passes its URL here. */
prerenderURL?: string;
}): Promise<RunningFactoryStack> {
let rootDir = mkdtempSync(join(tmpdir(), 'software-factory-realms-'));
let testRealmDir = join(rootDir, 'test');
Expand All @@ -298,9 +305,9 @@ export async function startIsolatedRealmStack({
let actualWorkerManagerPort =
explicitWorkerManagerPort ?? (await findAvailablePort());
let actualRealmServerPort =
DEFAULT_REALM_SERVER_PORT === 0
? await findAvailablePort()
: DEFAULT_REALM_SERVER_PORT;
explicitRealmServerPort && explicitRealmServerPort !== 0
? explicitRealmServerPort
: await findAvailablePort();
let actualRealmServerURL = withPort(realmServerURL, actualRealmServerPort);
let actualRealmPath = realmRelativePath(realmURL, realmServerURL);
let actualRealmURL = realmURLWithinServer(
Expand Down Expand Up @@ -360,12 +367,12 @@ export async function startIsolatedRealmStack({
// lifetime of a Playwright testWorker even though the realm stack itself is
// recreated per test. When provided, reuse that long-lived prerender URL so
// we only restart realm-server and worker-manager here.
let prerender = CONFIGURED_PRERENDER_URL
let prerender = explicitPrerenderURL
? undefined
: await startHarnessPrerenderServer({
boxelHostURL: realmServerURL.href.replace(/\/$/, ''),
});
let prerenderURL = CONFIGURED_PRERENDER_URL?.href ?? prerender?.url;
let prerenderURL = explicitPrerenderURL ?? prerender?.url;
if (!prerenderURL) {
throw new Error(
'Unable to determine prerender URL for isolated realm stack',
Expand Down
114 changes: 31 additions & 83 deletions packages/software-factory/src/harness/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ import {
import { createHash } from 'node:crypto';
import { createServer as createNetServer } from 'node:net';
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { dirname, join, relative, resolve } from 'node:path';
import { join, relative, resolve } from 'node:path';

import jwt from 'jsonwebtoken';
import '../setup-logger';
import { logger } from '../logger';

// Strip ambient env vars that could break the hermetic test seal.
// The harness always sets HOST_URL explicitly via context.hostURL when
// spawning child processes — an ambient HOST_URL (e.g. from a dev shell
// that sets HOST_URL=http://localhost:4200) would be inherited by child
// processes that don't explicitly override it, causing them to talk to
// a different Matrix/realm server than the hermetic test infrastructure.
// The harness always passes port values explicitly through CLI args or
// function parameters — ambient env vars (e.g. from a dev shell running
// mise dev-all) would be inherited by child processes, overriding the
// dynamically allocated ports and breaking test isolation.
// This module is only imported by harness code (test infrastructure),
// so it's safe to strip unconditionally — NODE_ENV may be 'test' or
// 'development' depending on how the harness is invoked.
delete process.env.HOST_URL;
delete process.env.SOFTWARE_FACTORY_REALM_PORT;
delete process.env.SOFTWARE_FACTORY_COMPAT_REALM_PORT;
delete process.env.SOFTWARE_FACTORY_PRERENDER_PORT;
delete process.env.SOFTWARE_FACTORY_PRERENDER_URL;

export type RealmAction = 'read' | 'write' | 'realm-owner' | 'assume-user';

Expand Down Expand Up @@ -49,6 +52,12 @@ export interface FactoryRealmOptions {
cacheSalt?: string;
templateDatabaseName?: string;
context?: FactoryTestContext | FactorySupportContext;
/** Explicit compat realm-server port (the public-facing proxy port). */
compatRealmServerPort?: number;
/** Explicit realm-server port (the internal realm-server listen port). */
realmServerPort?: number;
/** Explicit prerender URL to reuse instead of starting a new prerender. */
prerenderURL?: string;
}

export interface FactoryRealmTemplate {
Expand Down Expand Up @@ -145,12 +154,6 @@ export const prepareTestPgScript = resolve(
);

export const CACHE_VERSION = 8;
export const DEFAULT_REALM_SERVER_PORT = Number(
process.env.SOFTWARE_FACTORY_REALM_PORT ?? 0,
);
export const DEFAULT_COMPAT_REALM_SERVER_PORT = Number(
process.env.SOFTWARE_FACTORY_COMPAT_REALM_PORT ?? 0,
);
export const CONFIGURED_REALM_URL = process.env.SOFTWARE_FACTORY_REALM_URL
? new URL(process.env.SOFTWARE_FACTORY_REALM_URL)
: undefined;
Expand All @@ -176,13 +179,6 @@ export const DEFAULT_PG_HOST =
process.env.SOFTWARE_FACTORY_PGHOST ?? '127.0.0.1';
export const DEFAULT_PG_USER =
process.env.SOFTWARE_FACTORY_PGUSER ?? 'postgres';
export const DEFAULT_PRERENDER_PORT = Number(
process.env.SOFTWARE_FACTORY_PRERENDER_PORT ?? 0,
);
export const CONFIGURED_PRERENDER_URL = process.env
.SOFTWARE_FACTORY_PRERENDER_URL
? new URL(process.env.SOFTWARE_FACTORY_PRERENDER_URL)
: undefined;
// The seeded test Postgres used by the harness runs with max_connections=50, so
// isolated workers need a smaller per-process pool cap to keep workers=3 stable.
export const DEFAULT_PG_POOL_MAX = Number(
Expand Down Expand Up @@ -299,6 +295,7 @@ export async function findAvailablePort(): Promise<number> {

export async function resolveFactoryRealmServerURL(
realmServerURL?: URL,
compatRealmServerPort?: number,
): Promise<URL> {
if (realmServerURL) {
return new URL(realmServerURL.href);
Expand All @@ -309,15 +306,16 @@ export async function resolveFactoryRealmServerURL(
}

let port =
DEFAULT_COMPAT_REALM_SERVER_PORT === 0
? await findAvailablePort()
: DEFAULT_COMPAT_REALM_SERVER_PORT;
compatRealmServerPort && compatRealmServerPort !== 0
? compatRealmServerPort
: await findAvailablePort();
return new URL(`http://localhost:${port}/`);
}

export async function resolveFactoryRealmLocation(options: {
realmURL?: URL;
realmServerURL?: URL;
compatRealmServerPort?: number;
}): Promise<{
realmURL: URL;
realmServerURL: URL;
Expand All @@ -334,7 +332,10 @@ export async function resolveFactoryRealmLocation(options: {
: undefined;

if (!realmURL && !realmServerURL) {
realmServerURL = await resolveFactoryRealmServerURL();
realmServerURL = await resolveFactoryRealmServerURL(
undefined,
options.compatRealmServerPort,
);
realmURL = new URL('test/', realmServerURL);
} else if (!realmServerURL) {
throw new Error(
Expand Down Expand Up @@ -603,66 +604,13 @@ export function maybeRequire(specifier: string) {
return undefined;
}

export function fileExists(path: string): boolean {
try {
return statSync(path).isFile();
} catch {
return false;
}
}

export function findRootRepoCheckoutDir(): string | undefined {
let result = spawnSync(
'git',
['rev-parse', '--path-format=absolute', '--git-common-dir'],
{
cwd: workspaceRoot,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
},
);

if (result.status !== 0) {
return undefined;
}

let commonDir = result.stdout.trim();
if (!commonDir.endsWith(`${join('.git')}`)) {
return undefined;
}

return dirname(commonDir);
}

export function findHostDistPackageDir(): string | undefined {
let rootRepoCheckoutDir = findRootRepoCheckoutDir();
let rootRepoHostDir =
rootRepoCheckoutDir && rootRepoCheckoutDir !== workspaceRoot
? resolve(rootRepoCheckoutDir, 'packages', 'host')
: undefined;

let candidates = [
process.env.SOFTWARE_FACTORY_HOST_DIST_PACKAGE_DIR,
hostDir,
rootRepoHostDir,
]
.filter((value): value is string => Boolean(value))
.map((value) => resolve(value));

let seen = new Set<string>();
for (let candidate of candidates) {
if (seen.has(candidate)) {
continue;
}
seen.add(candidate);

if (fileExists(join(candidate, 'dist', 'index.html'))) {
return candidate;
}
}

return undefined;
}
// Re-export host dist utilities from the side-effect-free module so
// existing harness consumers don't need to change their imports.
export {
fileExists,
findRootRepoCheckoutDir,
findHostDistPackageDir,
} from '../host-dist';

export function browserPassword(username: string): string {
let cleanUsername = username.replace(/^@/, '').replace(/:.*$/, '');
Expand Down
8 changes: 6 additions & 2 deletions packages/software-factory/src/harness/support-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
DEFAULT_MATRIX_SERVER_USERNAME,
DEFAULT_PG_HOST,
DEFAULT_PG_PORT,
DEFAULT_PRERENDER_PORT,
CONFIGURED_HOST_URL,
findAvailablePort,
findHostDistPackageDir,
Expand Down Expand Up @@ -390,7 +389,7 @@ export async function startHarnessPrerenderServer(options: {
url: string;
stop(): Promise<void>;
}> {
let port = options.port ?? DEFAULT_PRERENDER_PORT;
let port = options.port ?? 0;
if (port === 0) {
port = await findAvailablePort();
}
Expand All @@ -409,6 +408,11 @@ export async function startHarnessPrerenderServer(options: {
LOG_LEVELS:
process.env.SOFTWARE_FACTORY_PRERENDER_LOG_LEVELS ??
process.env.LOG_LEVELS,
// Prevent test harness prerender servers from registering with
// external prerender managers (e.g. the dev-all manager on :4222).
// Port 1 is expected to be closed, so heartbeat fetches fail fast
// and are silently caught by the try/catch in prerender-app.ts.
PRERENDER_MANAGER_URL: 'http://127.0.0.1:1',
},
},
);
Expand Down
Loading
Loading