mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Stage generated app-server bindings before replacement
This commit is contained in:
parent
b6f6d04147
commit
5e362add5b
2 changed files with 73 additions and 21 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { spawnSync } from "node:child_process";
|
||||
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
|
|
@ -8,21 +8,54 @@ const generatedHeader = "// GENERATED CODE! DO NOT MODIFY BY HAND!";
|
|||
const normalizationNotice = "// This file was mechanically normalized after generation by scripts/generate-app-server-types.mjs.";
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
await generateAppServerTypes();
|
||||
try {
|
||||
await generateAppServerTypes();
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateAppServerTypes(options = {}) {
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
const generatedDir = path.resolve(cwd, generatedRelativeDir);
|
||||
const runCommand = options.runCommand ?? run;
|
||||
await cleanGeneratedTypes(generatedDir);
|
||||
await runCommand("codex", ["app-server", "generate-ts", "--experimental", "--out", generatedRelativeDir], { cwd });
|
||||
await normalizeGeneratedTypes(generatedDir);
|
||||
const generatedParent = path.dirname(generatedDir);
|
||||
await mkdir(generatedParent, { recursive: true });
|
||||
const stagedDir = await mkdtemp(path.join(generatedParent, ".app-server-"));
|
||||
try {
|
||||
const stagedRelativeDir = path.relative(cwd, stagedDir);
|
||||
await runCommand("codex", ["app-server", "generate-ts", "--experimental", "--out", stagedRelativeDir], { cwd });
|
||||
await normalizeGeneratedTypes(stagedDir);
|
||||
await replaceGeneratedTypes(generatedDir, stagedDir);
|
||||
} finally {
|
||||
await rm(stagedDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanGeneratedTypes(generatedDir) {
|
||||
await rm(generatedDir, { recursive: true, force: true });
|
||||
await mkdir(generatedDir, { recursive: true });
|
||||
async function replaceGeneratedTypes(generatedDir, stagedDir) {
|
||||
const backupDir = `${generatedDir}.backup-${process.pid.toString()}-${Date.now().toString()}`;
|
||||
let hasBackup = false;
|
||||
try {
|
||||
try {
|
||||
await rename(generatedDir, backupDir);
|
||||
hasBackup = true;
|
||||
} catch (error) {
|
||||
if (!isMissingPathError(error)) throw error;
|
||||
}
|
||||
await rename(stagedDir, generatedDir);
|
||||
if (hasBackup) await rm(backupDir, { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
if (hasBackup) {
|
||||
await rm(generatedDir, { recursive: true, force: true });
|
||||
await rename(backupDir, generatedDir);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function isMissingPathError(error) {
|
||||
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
||||
}
|
||||
|
||||
async function normalizeGeneratedTypes(generatedDir) {
|
||||
|
|
@ -72,8 +105,7 @@ function run(command, args, options = {}) {
|
|||
shell: false,
|
||||
});
|
||||
if (result.error) {
|
||||
console.error(`Failed to run ${command} ${args.join(" ")}: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
throw new Error(`Failed to run ${command} ${args.join(" ")}: ${result.error.message}`);
|
||||
}
|
||||
if (result.status !== 0) process.exit(result.status ?? 1);
|
||||
if (result.status !== 0) throw new Error(`${command} ${args.join(" ")} exited with status ${(result.status ?? 1).toString()}.`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { spawnSync } from "node:child_process";
|
||||
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, readdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
|
@ -26,26 +26,46 @@ describe("development scripts", () => {
|
|||
cwd,
|
||||
async runCommand(command: string, args: string[], options: { cwd: string }) {
|
||||
calls.push({ command, args, cwd: options.cwd });
|
||||
await mkdir(path.join(options.cwd, "src", "generated", "app-server", "v2"), { recursive: true });
|
||||
const outIndex = args.indexOf("--out");
|
||||
const outputDir = args[outIndex + 1];
|
||||
if (!outputDir) throw new Error("Missing generated output directory");
|
||||
await mkdir(path.join(options.cwd, outputDir, "v2"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(options.cwd, "src", "generated", "app-server", "v2", "Example.ts"),
|
||||
path.join(options.cwd, outputDir, "v2", "Example.ts"),
|
||||
"// GENERATED CODE! DO NOT MODIFY BY HAND!\nexport type Example = string | null | null;\n",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
expect(calls).toEqual([
|
||||
{
|
||||
command: "codex",
|
||||
args: ["app-server", "generate-ts", "--experimental", "--out", "src/generated/app-server"],
|
||||
cwd,
|
||||
},
|
||||
]);
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0]).toMatchObject({ command: "codex", cwd });
|
||||
expect(calls[0]?.args.slice(0, 4)).toEqual(["app-server", "generate-ts", "--experimental", "--out"]);
|
||||
expect(calls[0]?.args[4]?.replaceAll("\\", "/")).toMatch(/^src\/generated\/\.app-server-/);
|
||||
await expect(readFile(path.join(cwd, "src", "generated", "app-server", "v2", "Example.ts"), "utf8")).resolves.toContain(
|
||||
"export type Example = string | null;",
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves generated bindings when generation fails", async () => {
|
||||
const cwd = await tempWorkspace();
|
||||
const generatedDir = path.join(cwd, "src", "generated", "app-server");
|
||||
await mkdir(generatedDir, { recursive: true });
|
||||
await writeFile(path.join(generatedDir, "Existing.ts"), "export type Existing = true;\n");
|
||||
const { generateAppServerTypes } = await import(pathToFileURL(path.join(repoRoot, "scripts", "generate-app-server-types.mjs")).href);
|
||||
|
||||
await expect(
|
||||
generateAppServerTypes({
|
||||
cwd,
|
||||
runCommand: async () => {
|
||||
throw new Error("generation failed");
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow("generation failed");
|
||||
|
||||
await expect(readFile(path.join(generatedDir, "Existing.ts"), "utf8")).resolves.toBe("export type Existing = true;\n");
|
||||
await expect(readdir(path.join(cwd, "src", "generated"))).resolves.toEqual(["app-server"]);
|
||||
});
|
||||
|
||||
it("reads app-server compatibility policy from the declared baseline in API baseline checks", async () => {
|
||||
const cwd = await tempWorkspace();
|
||||
await mkdir(path.join(cwd, "scripts"), { recursive: true });
|
||||
|
|
|
|||
Loading…
Reference in a new issue