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
1 change: 1 addition & 0 deletions packages/software-factory/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
db-snapshots/template.pgdump binary
Empty file.
7 changes: 7 additions & 0 deletions packages/software-factory/db-snapshots/fingerprint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"fingerprint": "d4bb1aae0dbe17d2c7794f9983649c6590554abaf6370d0569300a977d46416d",
"cacheVersion": 8,
"realmServerURL": "http://localhost:41761/",
"generatedAt": "2026-04-10T16:03:26.733Z",
"pgDumpVersion": "pg_dump (PostgreSQL) 16.13 (Ubuntu 16.13-0ubuntu0.24.04.1)"
}
Binary file not shown.
1 change: 1 addition & 0 deletions packages/software-factory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"lint:js": "eslint . --report-unused-disable-directives --cache",
"lint:js:fix": "eslint . --report-unused-disable-directives --fix",
"lint:format": "prettier --check .",
"lint:snapshot-freshness": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/lint-snapshot-freshness.ts",
"lint:format:fix": "prettier --write .",
"lint:types": "ember-tsc --noEmit",
"serve:realm": "NODE_NO_WARNINGS=1 ts-node --transpileOnly src/cli/serve-realm.ts",
Expand Down
13 changes: 1 addition & 12 deletions packages/software-factory/playwright.global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ const fallbackRealmDir = resolve(
packageRoot,
'test-fixtures/darkfactory-adopter',
);
const testSourceRealmDir = resolve(
packageRoot,
'test-fixtures/public-software-factory-source',
);
const bootstrapTargetRealmDir = resolve(
packageRoot,
'test-fixtures/bootstrap-target',
Expand Down Expand Up @@ -200,7 +196,6 @@ async function prepareTemplatesForRealms(
...process.env,
SOFTWARE_FACTORY_CONTEXT: JSON.stringify(context),
SOFTWARE_FACTORY_METADATA_FILE: metadataFile,
SOFTWARE_FACTORY_SOURCE_REALM_DIR: testSourceRealmDir,
},
});

Expand Down Expand Up @@ -274,7 +269,6 @@ export default async function globalSetup() {
...process.env,
NODE_NO_WARNINGS: '1',
SOFTWARE_FACTORY_SUPPORT_METADATA_FILE: metadataFile,
SOFTWARE_FACTORY_SOURCE_REALM_DIR: testSourceRealmDir,
},
},
);
Expand All @@ -300,12 +294,7 @@ export default async function globalSetup() {
);

let preparedRealmDirs = [
...new Set([
realmDir,
bootstrapTargetRealmDir,
testRealmRunnerDir,
testSourceRealmDir,
]),
...new Set([realmDir, bootstrapTargetRealmDir, testRealmRunnerDir]),
];
let preparedTemplates = await prepareTemplatesForRealms(
preparedRealmDirs,
Expand Down
95 changes: 95 additions & 0 deletions packages/software-factory/scripts/lint-snapshot-freshness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import '../src/setup-logger';
import { existsSync } from 'node:fs';
import {
computeSnapshotFingerprint,
readSnapshotFingerprint,
DEFAULT_SNAPSHOT_FIXTURES,
DUMP_FILE,
FINGERPRINT_FILE,
} from '../src/harness/db-snapshot';

function printError(headline: string, detail: string): void {
console.error('');
console.error(`lint:snapshot-freshness — ERROR`);
console.error('');
console.error(` ${headline}`);
if (detail) {
console.error('');
for (let line of detail.split('\n')) {
console.error(` ${line}`);
}
}
console.error('');
console.error(' To fix this, run the following from the repo root:');
console.error('');
console.error(
' cd packages/software-factory && pnpm cache:prepare --update-snapshot',
);
console.error('');
console.error(
' This rebuilds the Playwright test database snapshot (~10 min on first run).',
);
console.error(
' Then commit the updated files in packages/software-factory/db-snapshots/.',
);
console.error('');
console.error(
' NOTE: This requires a running PostgreSQL instance on the port configured',
);
console.error(
' for software-factory tests (default: 127.0.0.1:55436). If you do not have',
);
console.error(
' the test database infrastructure set up, ask someone on the team who works',
);
console.error(
' on the software-factory package to regenerate the snapshot.',
);
console.error('');
}

function main(): void {
if (!existsSync(DUMP_FILE) || !existsSync(FINGERPRINT_FILE)) {
printError(
'The software-factory database snapshot files are missing.',
'This usually means the snapshot has not been generated yet.',
);
process.exitCode = 1;
return;
}

let committed = readSnapshotFingerprint();
if (!committed) {
printError(
'The software-factory snapshot fingerprint file is corrupt or unreadable.',
'The file exists but could not be parsed as JSON.',
);
process.exitCode = 1;
return;
}

let currentFingerprint = computeSnapshotFingerprint(
DEFAULT_SNAPSHOT_FIXTURES,
);

if (committed.fingerprint !== currentFingerprint) {
printError(
'The software-factory database snapshot is out of date.',
[
'Files in one of these directories have changed since the snapshot was last built:',
' - packages/base/ (base realm)',
' - packages/software-factory/realm/ (source realm)',
' - packages/software-factory/test-fixtures/ (test fixtures)',
'',
` Current source fingerprint: ${currentFingerprint}`,
` Committed fingerprint: ${committed.fingerprint}`,
].join('\n'),
);
process.exitCode = 1;
return;
}

console.log('snapshot-freshness: OK');
}

main();
21 changes: 15 additions & 6 deletions packages/software-factory/src/cli/cache-realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,29 @@ import {
ensureFactoryRealmTemplate,
} from '../harness';
import { isFactorySupportContext } from '../harness/shared';
import { DEFAULT_SNAPSHOT_FIXTURES } from '../harness/db-snapshot';
import { readSupportContext } from '../runtime-metadata';
import { logger } from '../logger';

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

async function main(): Promise<void> {
let flags = process.argv.slice(2).filter((arg) => arg.startsWith('--'));
let args = process.argv.slice(2).filter((arg) => !arg.startsWith('--'));
let realmDirs = [
...new Set(
(args.length > 0 ? args : ['test-fixtures/darkfactory-adopter']).map(
(realmDir) => resolve(process.cwd(), realmDir),
let useSnapshotFixtures = flags.includes('--update-snapshot');

let realmDirs: string[];
if (useSnapshotFixtures) {
realmDirs = DEFAULT_SNAPSHOT_FIXTURES.map((f) => f.realmDir);
} else {
realmDirs = [
...new Set(
(args.length > 0 ? args : ['test-fixtures/darkfactory-adopter']).map(
(realmDir) => resolve(process.cwd(), realmDir),
),
),
),
];
];
}
let serializedSupportContext = process.env.SOFTWARE_FACTORY_CONTEXT;

let parsedEnvContext = serializedSupportContext
Expand Down
140 changes: 138 additions & 2 deletions packages/software-factory/src/harness/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ import {
seedRealmPermissions,
warnIfSnapshotLooksCold,
} from './database';
import {
checkCommittedSnapshot,
isCanonicalFixtureSet,
restoreTemplateFromDisk,
saveSnapshot,
} from './db-snapshot';
import { startFactorySupportServices } from './support-services';
import {
startIsolatedRealmStack,
Expand Down Expand Up @@ -165,6 +171,58 @@ export async function ensureFactoryRealmTemplate(
: 'template database has not been prepared yet'
: 'template database is missing';

// Tier 2: Try restoring from committed pg_dump snapshot.
// Only attempt when the DB is actually missing (not when metadata is missing
// but the DB exists — CREATE DATABASE would fail in that case).
let snapshotFixtures: CombinedRealmFixture[] = [
{ realmDir, realmPath: realmRelativePath(realmURL, realmServerURL) },
];
if (!hasTemplateDatabase) {
let snapshotData = checkCommittedSnapshot(snapshotFixtures);
if (snapshotData) {
harnessLog.info(
'Restoring template from committed snapshot (fast path)',
);
let snapshotServerURL = new URL(snapshotData.realmServerURL);
let snapshotRealmURL = new URL(
realmRelativePath(realmURL, realmServerURL),
snapshotServerURL,
);
try {
await restoreTemplateFromDisk(templateDatabaseName);
writePreparedTemplateMetadata({
realmDir,
templateDatabaseName,
templateRealmURL: snapshotRealmURL.href,
templateRealmServerURL: snapshotData.realmServerURL,
});
return {
cacheKey,
templateDatabaseName,
fixtureHash,
cacheHit: false,
cacheMissReason: 'restored from snapshot',
realmURL: snapshotRealmURL,
realmServerURL: snapshotServerURL,
};
} catch (error) {
harnessLog.warn(
`Snapshot restore failed, falling back to full build: ${error}`,
);
try {
await dropDatabase(templateDatabaseName);
} catch {
/* best effort */
}
}
} else {
harnessLog.info(
'Snapshot not available or stale, proceeding with full build',
);
}
}

// Tier 3: Full build from scratch.
let ownedSupport:
| {
context: FactorySupportContext;
Expand Down Expand Up @@ -194,6 +252,19 @@ export async function ensureFactoryRealmTemplate(
templateRealmServerURL: realmServerURL.href,
});

// Save snapshot for future fast restores (only for canonical fixtures).
if (isCanonicalFixtureSet(snapshotFixtures)) {
try {
await saveSnapshot(
templateDatabaseName,
realmServerURL.href,
snapshotFixtures,
);
} catch (error) {
harnessLog.warn(`Failed to save snapshot: ${error}`);
}
}

return {
cacheKey,
templateDatabaseName,
Expand Down Expand Up @@ -289,6 +360,58 @@ export async function ensureCombinedFactoryRealmTemplate(
: 'template database has not been prepared yet'
: 'template database is missing';

// Resolve fixtures with absolute paths for snapshot operations.
let resolvedFixtures: CombinedRealmFixture[] = fixtures.map((f) => ({
realmDir: resolve(f.realmDir),
realmPath: f.realmPath,
}));

// Tier 2: Try restoring from committed pg_dump snapshot.
// Only attempt when the DB is actually missing (not when metadata is missing
// but the DB exists — CREATE DATABASE would fail in that case).
if (!hasTemplateDatabase) {
let snapshotData = checkCommittedSnapshot(resolvedFixtures);
if (snapshotData) {
harnessLog.info(
'Restoring template from committed snapshot (fast path)',
);
try {
await restoreTemplateFromDisk(templateDatabaseName);
writePreparedTemplateMetadata({
realmDir: resolvedFixtures[0].realmDir,
templateDatabaseName,
templateRealmURL:
snapshotData.realmServerURL + resolvedFixtures[0].realmPath,
templateRealmServerURL: snapshotData.realmServerURL,
coveredRealmDirs: resolvedFixtures.map((f) => f.realmDir),
});
return {
cacheKey,
templateDatabaseName,
combinedFixtureHash,
cacheHit: false,
cacheMissReason: 'restored from snapshot',
coveredRealmDirs: resolvedFixtures.map((f) => f.realmDir),
realmServerURL: new URL(snapshotData.realmServerURL),
};
} catch (error) {
harnessLog.warn(
`Snapshot restore failed, falling back to full build: ${error}`,
);
try {
await dropDatabase(templateDatabaseName);
} catch {
/* best effort */
}
}
} else {
harnessLog.info(
'Snapshot not available or stale, proceeding with full build',
);
}
}

// Tier 3: Full build from scratch.
let ownedSupport:
| { context: FactorySupportContext; stop(): Promise<void> }
| undefined;
Expand All @@ -300,10 +423,10 @@ export async function ensureCombinedFactoryRealmTemplate(

try {
// Resolve realm URLs for each fixture.
let realmFixtures = fixtures.map((f) => {
let realmFixtures = resolvedFixtures.map((f) => {
let realmURL = new URL(f.realmPath, realmServerURL);
return {
realmDir: resolve(f.realmDir),
realmDir: f.realmDir,
realmURL,
};
});
Expand All @@ -325,6 +448,19 @@ export async function ensureCombinedFactoryRealmTemplate(
templateRealmServerURL: realmServerURL.href,
});

// Save snapshot for future fast restores (only for canonical fixtures).
if (isCanonicalFixtureSet(resolvedFixtures)) {
try {
await saveSnapshot(
templateDatabaseName,
realmServerURL.href,
resolvedFixtures,
);
} catch (error) {
harnessLog.warn(`Failed to save snapshot: ${error}`);
}
}

return {
cacheKey,
templateDatabaseName,
Expand Down
Loading
Loading