Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"db:dump": "bun scripts/dump-db.ts",
"db:restore": "bun scripts/restore-db.ts",
"lancedb:sync-slow": "bun scripts/sync-lancedb-slow.ts",
"measure:dev-startup": "bun scripts/measure-dev-startup.ts",
"db:studio": "drizzle-kit studio",
"lint": "biome lint ./src",
"format": "biome format --fix ./src",
Expand Down
182 changes: 5 additions & 177 deletions apps/server/scripts/e2e-server.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
import { lstat, mkdir, rm, stat, symlink, writeFile } from "node:fs/promises";
import { lstat, symlink } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { PGlite } from "@electric-sql/pglite";
import { defaultAppConfig } from "@solid-imager/core/domain/config/config-schema";
import { mediaGenerationInfo, medias, mediaSources } from "@solid-imager/db/schema";
import { drizzle } from "drizzle-orm/pglite";
import { migrate } from "drizzle-orm/pglite/migrator";
import sharp from "sharp";
import {
E2E_PRIMARY_FILE_NAME,
E2E_PRIMARY_MEDIA_ID,
E2E_SIMILAR_FILE_NAME,
E2E_SIMILAR_MEDIA_ID,
E2E_SOURCE_ID,
E2E_SOURCE_NAME,
getE2eMediaDir,
getE2eRuntimeDir,
getFixtureMediaPath,
} from "../src/tests/e2e/support/fixture";
import { assertSafeRuntimeDir, prepareIsolatedRuntime } from "./isolated-runtime";
import { getE2eRuntimeDir } from "../src/tests/e2e/support/fixture";

type E2eMode = "dev" | "production";

Expand All @@ -44,163 +29,6 @@ function getPort(): string {
return port;
}

function assertSafeRuntimeDir(runtimeDir: string): void {
const resolvedRoot = path.resolve(allowedRuntimeRoot);
const relative = path.relative(resolvedRoot, runtimeDir);
if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) {
throw new Error(
`E2E_RUNTIME_DIR must be a child of ${resolvedRoot}, received ${runtimeDir}`,
);
}
}

function createImageSvg(accentColor: string, backgroundColor: string): Buffer {
return Buffer.from(`
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<rect width="256" height="256" fill="${backgroundColor}" />
<circle cx="128" cy="128" r="84" fill="${accentColor}" />
<path d="M48 184 L104 120 L144 160 L184 96 L224 184 Z" fill="#ffffff" fill-opacity="0.85" />
</svg>
`);
}

async function seedMediaFixtures(runtimeDir: string): Promise<void> {
const mediaDir = getE2eMediaDir();
const thumbnailDir = path.join(runtimeDir, "thumbnails", E2E_SOURCE_ID);
await mkdir(mediaDir, { recursive: true });
await mkdir(thumbnailDir, { recursive: true });

const primaryPath = getFixtureMediaPath(E2E_PRIMARY_FILE_NAME);
const similarPath = getFixtureMediaPath(E2E_SIMILAR_FILE_NAME);
await Promise.all([
sharp(createImageSvg("#4f46e5", "#e0e7ff")).png().toFile(primaryPath),
sharp(createImageSvg("#6366f1", "#eef2ff")).png().toFile(similarPath),
]);

await Promise.all([
sharp(primaryPath)
.resize({ width: 512, height: 512, fit: "inside" })
.webp()
.toFile(path.join(thumbnailDir, `${E2E_PRIMARY_MEDIA_ID}.webp`)),
sharp(similarPath)
.resize({ width: 512, height: 512, fit: "inside" })
.webp()
.toFile(path.join(thumbnailDir, `${E2E_SIMILAR_MEDIA_ID}.webp`)),
]);

const [primaryStats, similarStats] = await Promise.all([
stat(primaryPath),
stat(similarPath),
]);
const pgliteDir = path.join(runtimeDir, "pglite");
const client = new PGlite(pgliteDir);
const db = drizzle(client);

try {
await migrate(db, { migrationsFolder: path.join(appRoot, "drizzle") });
const seededAt = new Date();
await db.insert(mediaSources).values({
id: E2E_SOURCE_ID,
name: E2E_SOURCE_NAME,
description: "Isolated media source for browser E2E tests",
type: "local",
connectionInfo: { path: mediaDir },
createdAt: seededAt,
updatedAt: seededAt,
});
await db.insert(medias).values([
{
id: E2E_PRIMARY_MEDIA_ID,
mediaSourceId: E2E_SOURCE_ID,
filePath: E2E_PRIMARY_FILE_NAME,
fileName: E2E_PRIMARY_FILE_NAME,
mediaType: "image",
width: 256,
height: 256,
fileSize: primaryStats.size,
description: "Primary E2E fixture media",
createdAt: primaryStats.birthtime,
modifiedAt: primaryStats.mtime,
indexedAt: seededAt,
status: "active",
},
{
id: E2E_SIMILAR_MEDIA_ID,
mediaSourceId: E2E_SOURCE_ID,
filePath: E2E_SIMILAR_FILE_NAME,
fileName: E2E_SIMILAR_FILE_NAME,
mediaType: "image",
width: 256,
height: 256,
fileSize: similarStats.size,
description: "Similarity candidate E2E fixture media",
createdAt: similarStats.birthtime,
modifiedAt: similarStats.mtime,
indexedAt: seededAt,
status: "active",
},
]);
await db.insert(mediaGenerationInfo).values([
{
mediaId: E2E_PRIMARY_MEDIA_ID,
metadata: { fixture: "e2e-primary" },
},
{
mediaId: E2E_SIMILAR_MEDIA_ID,
metadata: { fixture: "e2e-similar" },
},
]);
} finally {
await client.close();
}
}

async function createRouteTreePlaceholder(runtimeDir: string): Promise<string> {
const frozenRouteTreePath = path.join(runtimeDir, "routeTree.e2e.ts");

// The Vite router plugin must replace this before application modules are
// transformed. A placeholder makes generation failures fail this isolated
// run instead of silently falling back to a checked-in route tree.
await writeFile(
frozenRouteTreePath,
"export const __e2eRouteTreePlaceholder = true;\n",
);
return frozenRouteTreePath;
}

async function prepareE2eEnvironment(runtimeDir: string): Promise<string> {
await rm(runtimeDir, { recursive: true, force: true });
await mkdir(runtimeDir, { recursive: true });

const config = {
...defaultAppConfig,
jobs: {
...defaultAppConfig.jobs,
concurrency: 1,
aiConcurrency: 1,
pollIntervalMs: 100,
enableAutoTagging: false,
enableAutoCcipExtraction: false,
},
storage: {
...defaultAppConfig.storage,
thumbnailDir: path.join(runtimeDir, "thumbnails"),
},
lancedb: {
...defaultAppConfig.lancedb,
autoFullSync: false,
cacheDir: path.join(runtimeDir, "lancedb-cache"),
ccipVectorDir: path.join(runtimeDir, "lancedb-ccip"),
},
};
await writeFile(
path.join(runtimeDir, "config.json"),
JSON.stringify(config, null, 2),
);
await seedMediaFixtures(runtimeDir);
return await createRouteTreePlaceholder(runtimeDir);
}

function serverEnvironment(
runtimeDir: string,
routeTreePath: string,
Expand Down Expand Up @@ -315,6 +143,6 @@ async function startServer(

const mode = getMode();
const runtimeDir = getE2eRuntimeDir();
assertSafeRuntimeDir(runtimeDir);
const routeTreePath = await prepareE2eEnvironment(runtimeDir);
assertSafeRuntimeDir(runtimeDir, allowedRuntimeRoot);
const { routeTreePath } = await prepareIsolatedRuntime(runtimeDir);
await startServer(mode, serverEnvironment(runtimeDir, routeTreePath));
181 changes: 181 additions & 0 deletions apps/server/scripts/isolated-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { mkdir, rm, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { PGlite } from '@electric-sql/pglite';
import { defaultAppConfig } from '@solid-imager/core/domain/config/config-schema';
import { mediaGenerationInfo, medias, mediaSources } from '@solid-imager/db/schema';
import { drizzle } from 'drizzle-orm/pglite';
import { migrate } from 'drizzle-orm/pglite/migrator';
import sharp from 'sharp';
import {
E2E_PRIMARY_FILE_NAME,
E2E_PRIMARY_MEDIA_ID,
E2E_SIMILAR_FILE_NAME,
E2E_SIMILAR_MEDIA_ID,
E2E_SOURCE_ID,
E2E_SOURCE_NAME,
} from '../src/tests/e2e/support/fixture';

const appRoot = path.resolve(import.meta.dir, '..');

export type IsolatedRuntime = {
routeTreePath: string;
};

export function assertSafeRuntimeDir(
runtimeDir: string,
allowedRuntimeRoot: string,
): void {
const resolvedRoot = path.resolve(allowedRuntimeRoot);
const relative = path.relative(resolvedRoot, runtimeDir);
if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error(
`Runtime directory must be a child of ${resolvedRoot}, received ${runtimeDir}`,
);
}
}

function createImageSvg(accentColor: string, backgroundColor: string): Buffer {
return Buffer.from(`
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<rect width="256" height="256" fill="${backgroundColor}" />
<circle cx="128" cy="128" r="84" fill="${accentColor}" />
<path d="M48 184 L104 120 L144 160 L184 96 L224 184 Z" fill="#ffffff" fill-opacity="0.85" />
</svg>
`);
}

async function seedMediaFixtures(runtimeDir: string): Promise<void> {
const mediaDir = path.join(runtimeDir, 'media');
const thumbnailDir = path.join(runtimeDir, 'thumbnails', E2E_SOURCE_ID);
await mkdir(mediaDir, { recursive: true });
await mkdir(thumbnailDir, { recursive: true });

const primaryPath = path.join(mediaDir, E2E_PRIMARY_FILE_NAME);
const similarPath = path.join(mediaDir, E2E_SIMILAR_FILE_NAME);
await Promise.all([
sharp(createImageSvg('#4f46e5', '#e0e7ff')).png().toFile(primaryPath),
sharp(createImageSvg('#6366f1', '#eef2ff')).png().toFile(similarPath),
]);

await Promise.all([
sharp(primaryPath)
.resize({ width: 512, height: 512, fit: 'inside' })
.webp()
.toFile(path.join(thumbnailDir, `${E2E_PRIMARY_MEDIA_ID}.webp`)),
sharp(similarPath)
.resize({ width: 512, height: 512, fit: 'inside' })
.webp()
.toFile(path.join(thumbnailDir, `${E2E_SIMILAR_MEDIA_ID}.webp`)),
]);

const [primaryStats, similarStats] = await Promise.all([
stat(primaryPath),
stat(similarPath),
]);
const pgliteDir = path.join(runtimeDir, 'pglite');
const client = new PGlite(pgliteDir);
const db = drizzle(client);

try {
await migrate(db, { migrationsFolder: path.join(appRoot, 'drizzle') });
const seededAt = new Date();
await db.insert(mediaSources).values({
id: E2E_SOURCE_ID,
name: E2E_SOURCE_NAME,
description: 'Isolated media source for browser tests',
type: 'local',
connectionInfo: { path: mediaDir },
createdAt: seededAt,
updatedAt: seededAt,
});
await db.insert(medias).values([
{
id: E2E_PRIMARY_MEDIA_ID,
mediaSourceId: E2E_SOURCE_ID,
filePath: E2E_PRIMARY_FILE_NAME,
fileName: E2E_PRIMARY_FILE_NAME,
mediaType: 'image',
width: 256,
height: 256,
fileSize: primaryStats.size,
description: 'Primary browser fixture media',
createdAt: primaryStats.birthtime,
modifiedAt: primaryStats.mtime,
indexedAt: seededAt,
status: 'active',
},
{
id: E2E_SIMILAR_MEDIA_ID,
mediaSourceId: E2E_SOURCE_ID,
filePath: E2E_SIMILAR_FILE_NAME,
fileName: E2E_SIMILAR_FILE_NAME,
mediaType: 'image',
width: 256,
height: 256,
fileSize: similarStats.size,
description: 'Similarity candidate browser fixture media',
createdAt: similarStats.birthtime,
modifiedAt: similarStats.mtime,
indexedAt: seededAt,
status: 'active',
},
]);
await db.insert(mediaGenerationInfo).values([
{
mediaId: E2E_PRIMARY_MEDIA_ID,
metadata: { fixture: 'e2e-primary' },
},
{
mediaId: E2E_SIMILAR_MEDIA_ID,
metadata: { fixture: 'e2e-similar' },
},
]);
} finally {
await client.close();
}
}

async function createRouteTreePlaceholder(runtimeDir: string): Promise<string> {
const routeTreePath = path.join(runtimeDir, 'routeTree.generated.ts');
await writeFile(
routeTreePath,
'export const __isolatedRouteTreePlaceholder = true;\n',
);
return routeTreePath;
}

export async function prepareIsolatedRuntime(
runtimeDir: string,
): Promise<IsolatedRuntime> {
await rm(runtimeDir, { recursive: true, force: true });
await mkdir(runtimeDir, { recursive: true });

const config = {
...defaultAppConfig,
jobs: {
...defaultAppConfig.jobs,
concurrency: 1,
aiConcurrency: 1,
pollIntervalMs: 100,
enableAutoTagging: false,
enableAutoCcipExtraction: false,
},
storage: {
...defaultAppConfig.storage,
thumbnailDir: path.join(runtimeDir, 'thumbnails'),
},
lancedb: {
...defaultAppConfig.lancedb,
autoFullSync: false,
cacheDir: path.join(runtimeDir, 'lancedb-cache'),
ccipVectorDir: path.join(runtimeDir, 'lancedb-ccip'),
},
};
await writeFile(
path.join(runtimeDir, 'config.json'),
JSON.stringify(config, null, 2),
);
await seedMediaFixtures(runtimeDir);

return { routeTreePath: await createRouteTreePlaceholder(runtimeDir) };
}
Loading