mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
132 lines
4.5 KiB
TypeScript
132 lines
4.5 KiB
TypeScript
import { spawn } from "child_process";
|
|
import path from "path";
|
|
import { setTimeout as sleep } from "timers/promises";
|
|
|
|
// Root `pnpm run dev` starts eager generators and package watch builds in
|
|
// parallel. `generate-openapi-docs:watch` intentionally runs `codegen-docs`
|
|
// once before starting chokidar, because chokidar only responds to future file
|
|
// changes. Without that initial run, dev docs could serve stale OpenAPI JSON
|
|
// from a previous branch, or no generated JSON at all after a clean checkout,
|
|
// until someone edits an API route.
|
|
//
|
|
// That eager OpenAPI generation imports backend modules, and some of those
|
|
// backend modules resolve workspace packages through their built `dist`
|
|
// entrypoints. Package watch scripts update those entrypoints with
|
|
// `tsdown --watch`, but on a cold checkout, after `pnpm clean`, or during the
|
|
// first package watcher build, the entrypoints may not exist yet even though
|
|
// `tsdown --watch` is about to create them.
|
|
//
|
|
// We keep this wait scoped to the eager generator rather than putting it in
|
|
// front of backend `dev`: the long-running Next dev server can tolerate package
|
|
// watchers warming up, while a one-shot generator exits immediately on a missing
|
|
// import and `concurrently -k` then tears down the whole dev command. Package
|
|
// watch scripts also avoid deleting `dist` in dev mode, which removes the
|
|
// common restart race; this probe covers the remaining cold-start case.
|
|
//
|
|
// This probe waits only for the package imports that the backend-side generator
|
|
// needs. It does not hide real runtime errors: we retry missing-module failures
|
|
// while package builds warm up, and fail immediately for other import failures.
|
|
//
|
|
// In addition to workspace packages, the probe checks that the generated Prisma
|
|
// client exists. When `turbo run dev` starts the backend, `codegen-prisma:watch`
|
|
// (`prisma generate --watch`) performs an initial generation that briefly removes
|
|
// and recreates `src/generated/prisma/`. If `codegen-docs` runs during that
|
|
// window it fails with ERR_MODULE_NOT_FOUND for `@/generated/prisma/client`.
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
const backendDir = path.join(repoRoot, "apps/backend");
|
|
const timeoutMs = 60_000;
|
|
const retryDelayMs = 1_000;
|
|
|
|
const probeScript = `
|
|
(async () => {
|
|
await import('@hexclave/next');
|
|
await import('@hexclave/shared/dist/utils/env');
|
|
const { existsSync, readdirSync } = await import('node:fs');
|
|
const { join } = await import('node:path');
|
|
const generatedDir = join(process.cwd(), 'src', 'generated', 'prisma');
|
|
if (!existsSync(generatedDir) || readdirSync(generatedDir).length === 0) {
|
|
const err = new Error('ERR_MODULE_NOT_FOUND: Generated Prisma client not yet available at ' + generatedDir);
|
|
throw err;
|
|
}
|
|
})().then(
|
|
() => undefined,
|
|
(error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
},
|
|
);
|
|
`;
|
|
|
|
type ProbeResult = {
|
|
exitCode: number | null,
|
|
output: string,
|
|
};
|
|
|
|
function runProbe(): Promise<ProbeResult> {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn("pnpm", ["exec", "tsx", "-e", probeScript], {
|
|
cwd: backendDir,
|
|
env: process.env,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
|
|
let output = "";
|
|
|
|
child.stdout.setEncoding("utf8");
|
|
child.stderr.setEncoding("utf8");
|
|
child.stdout.on("data", (chunk: string) => {
|
|
output += chunk;
|
|
});
|
|
child.stderr.on("data", (chunk: string) => {
|
|
output += chunk;
|
|
});
|
|
|
|
child.on("error", reject);
|
|
child.on("close", (exitCode) => {
|
|
resolve({ exitCode, output });
|
|
});
|
|
});
|
|
}
|
|
|
|
function isMissingModuleError(output: string) {
|
|
return output.includes("ERR_MODULE_NOT_FOUND") || output.includes("MODULE_NOT_FOUND");
|
|
}
|
|
|
|
async function main() {
|
|
const start = performance.now();
|
|
let lastOutput = "";
|
|
let hasLoggedWait = false;
|
|
let isReady = false;
|
|
|
|
while (performance.now() - start < timeoutMs) {
|
|
const result = await runProbe();
|
|
if (result.exitCode === 0) {
|
|
isReady = true;
|
|
break;
|
|
}
|
|
|
|
lastOutput = result.output;
|
|
if (!isMissingModuleError(result.output)) {
|
|
throw new Error(`Dev package import probe failed with a non-retryable error:\n${result.output}`);
|
|
}
|
|
|
|
if (!hasLoggedWait) {
|
|
console.log("Waiting for dev package entrypoints to be generated...");
|
|
hasLoggedWait = true;
|
|
}
|
|
await sleep(retryDelayMs);
|
|
}
|
|
|
|
if (!isReady) {
|
|
throw new Error(`Timed out waiting for dev package imports to become available. Last probe output:\n${lastOutput}`);
|
|
}
|
|
}
|
|
|
|
main().then(
|
|
() => undefined,
|
|
(error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
},
|
|
);
|