import { execSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { expect } from "@playwright/test"; import { show, listMachineUsers, getMachineUserToken, listOAuth2Clients, getAppHealth, createWorkspace, deploy, } from "@tailor-platform/sdk/cli"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const APP_NAME = "template-app"; const STATIC_WEBSITE_NAME = "default-web"; const STATE_FILE = path.resolve(__dirname, ".e2e-state.json"); const frontendDir = path.resolve(__dirname, ".."); const backendDir = path.resolve(frontendDir, "../backend"); export interface E2eState { workspaceId: string; appUrl: string; clientId: string; graphqlEndpoint: string; token: string; frontendUrl: string; // false when CI provided the workspace, so teardown must not delete it // (other jobs share it). ownsWorkspace: boolean; } async function withCwd(dir: string, fn: () => Promise): Promise { const original = process.cwd(); process.chdir(dir); try { return await fn(); } finally { process.chdir(original); } } async function globalSetup() { // In CI, the workspace is already deployed and seeded; resolve its connection // info only. Do NOT deploy or re-seed: the integration job shares it, and // re-seeding would wipe its data. Tests create their own rows. // Locally (no env var), create, deploy, and seed a workspace. const providedWorkspaceId = process.env.TAILOR_PLATFORM_WORKSPACE_ID; if (providedWorkspaceId) { const workspaceId = providedWorkspaceId; console.log(`[global-setup] reusing workspace ${workspaceId} (no deploy/seed)`); const app = await withCwd(backendDir, () => show({ workspaceId })); const appUrl = app.url; const machineUsers = await withCwd(backendDir, () => listMachineUsers({ workspaceId })); const tokens = await withCwd(backendDir, () => getMachineUserToken({ name: machineUsers[0].name, workspaceId }), ); const oauth2Clients = await withCwd(backendDir, () => listOAuth2Clients({ workspaceId })); const clientId = oauth2Clients[0].clientId; // CI sets E2E_BASE_URL before launching Playwright; the config reads it for // use.baseURL before this setup runs, so it cannot be set here. const frontendUrl = process.env.E2E_BASE_URL ?? ""; const state: E2eState = { workspaceId, appUrl, clientId, graphqlEndpoint: `${appUrl}/query`, token: tokens.accessToken, frontendUrl, ownsWorkspace: false, }; fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); return; } const workspaceName = `${APP_NAME}-e2e-${Date.now()}`; const workspace = await withCwd(backendDir, () => createWorkspace({ name: workspaceName, region: "asia-northeast", organizationId: process.env.TAILOR_PLATFORM_ORGANIZATION_ID, folderId: process.env.TAILOR_PLATFORM_FOLDER_ID, }), ); const workspaceId = workspace.id; await withCwd(backendDir, () => deploy({ workspaceId, yes: true })); await withCwd(backendDir, async () => { await expect .poll( async () => (await getAppHealth({ name: APP_NAME, workspaceId })).status.toLowerCase(), { intervals: [500, 1_000, 2_000, 3_000], timeout: 60_000, message: "App did not become healthy after deploy", }, ) .toBe("ok"); }); const app = await withCwd(backendDir, () => show({ workspaceId })); const appUrl = app.url; const machineUsers = await withCwd(backendDir, () => listMachineUsers({ workspaceId })); const tokens = await withCwd(backendDir, () => getMachineUserToken({ name: machineUsers[0].name, workspaceId, }), ); const oauth2Clients = await withCwd(backendDir, () => listOAuth2Clients({ workspaceId })); const clientId = oauth2Clients[0].clientId; // Seed master data console.log("[global-setup] Seeding data..."); execSync(`pnpm tailor seed apply --skip-idp -m ${machineUsers[0].name}`, { cwd: backendDir, stdio: "inherit", env: { ...process.env, TAILOR_PLATFORM_WORKSPACE_ID: workspaceId }, }); // Build and deploy the frontend console.log("[global-setup] Building frontend..."); execSync("pnpm run build", { cwd: frontendDir, stdio: "inherit", env: { ...process.env, VITE_TAILOR_APP_URL: appUrl, VITE_TAILOR_CLIENT_ID: clientId, }, }); console.log("[global-setup] Deploying frontend..."); execSync( `pnpm tailor staticwebsite deploy --name ${STATIC_WEBSITE_NAME} --dir ./dist --workspace-id ${workspaceId}`, { cwd: frontendDir, stdio: "inherit", }, ); // Get the static website URL const swOutput = execSync( `pnpm tailor staticwebsite get ${STATIC_WEBSITE_NAME} --workspace-id ${workspaceId} --json`, { cwd: frontendDir, encoding: "utf-8", }, ); const swInfo = JSON.parse(swOutput) as { url: string }; const frontendUrl = swInfo.url; console.log(`[global-setup] Frontend deployed at: ${frontendUrl}`); const state: E2eState = { workspaceId, appUrl, clientId, graphqlEndpoint: `${appUrl}/query`, token: tokens.accessToken, frontendUrl, ownsWorkspace: true, }; fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); process.env.E2E_BASE_URL = frontendUrl; } export default globalSetup;