Skip to content

Commit f301e27

Browse files
authored
Merge pull request #189 from proofgeist/codex/issue-50-typegen-index
2 parents a0a2f1a + 497ad0a commit f301e27

3 files changed

Lines changed: 130 additions & 6 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@proofkit/typegen": patch
3+
---
4+
5+
Fix typegen client index generation when multiple fmdapi configs write to the same output path

packages/typegen/src/typegen.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@ export const generateTypedClients = async (
4646

4747
const { resetOverrides = false, cwd = process.cwd() } = options ?? {};
4848

49+
const clientIndexPathsToReset = new Set<string>();
50+
const rootDirsToClear = new Set<string>();
51+
for (const singleConfig of configArray) {
52+
if (singleConfig?.type !== "fmdapi") {
53+
continue;
54+
}
55+
const rootDir = path.join(cwd, singleConfig.path ?? "schema");
56+
clientIndexPathsToReset.add(path.join(rootDir, "client", "index.ts"));
57+
if (singleConfig.clearOldFiles) {
58+
rootDirsToClear.add(rootDir);
59+
}
60+
}
61+
62+
for (const rootDir of rootDirsToClear) {
63+
fs.emptyDirSync(path.join(rootDir, "client"));
64+
fs.emptyDirSync(path.join(rootDir, "generated"));
65+
}
66+
67+
for (const clientIndexPath of clientIndexPathsToReset) {
68+
fs.rmSync(clientIndexPath, { force: true });
69+
}
70+
4971
let totalSuccessCount = 0;
5072
let totalErrorCount = 0;
5173
let totalCount = 0;
@@ -98,7 +120,6 @@ const generateTypedClientsSingle = async (
98120
clientSuffix = "Client",
99121

100122
generateClient = true,
101-
clearOldFiles = false,
102123
...rest
103124
} = config;
104125

@@ -247,12 +268,7 @@ const generateTypedClientsSingle = async (
247268
}
248269

249270
await fs.ensureDir(rootDir);
250-
if (clearOldFiles) {
251-
fs.emptyDirSync(path.join(rootDir, "client"));
252-
fs.emptyDirSync(path.join(rootDir, "generated"));
253-
}
254271
const clientIndexFilePath = path.join(rootDir, "client", "index.ts");
255-
fs.rmSync(clientIndexFilePath, { force: true }); // ensure clean slate for this file
256272

257273
let successCount = 0;
258274
let errorCount = 0;

packages/typegen/tests/typegen.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,109 @@ describe("typegen unit tests", () => {
467467
expect(content).toContain("suffixSchemaLayout");
468468
});
469469

470+
it("preserves client index exports across multiple configs targeting the same path", async () => {
471+
vi.stubGlobal(
472+
"fetch",
473+
createLayoutMetadataMock({
474+
LayoutA: mockLayoutMetadata["basic-layout"],
475+
LayoutB: mockLayoutMetadata["layout-with-portal"],
476+
}),
477+
);
478+
479+
const config: Extract<z.infer<typeof typegenConfigSingle>, { type: "fmdapi" }>[] = [
480+
{
481+
type: "fmdapi",
482+
layouts: [
483+
{
484+
layoutName: "LayoutA",
485+
schemaName: "schemaA",
486+
},
487+
],
488+
path: "unit-typegen-output/multi-config",
489+
generateClient: true,
490+
clientSuffix: "Layout",
491+
clearOldFiles: false,
492+
},
493+
{
494+
type: "fmdapi",
495+
layouts: [
496+
{
497+
layoutName: "LayoutB",
498+
schemaName: "schemaB",
499+
},
500+
],
501+
path: "unit-typegen-output/multi-config",
502+
generateClient: true,
503+
clientSuffix: "Layout",
504+
clearOldFiles: false,
505+
},
506+
];
507+
508+
await generateTypedClients(config, { cwd: import.meta.dirname });
509+
510+
const indexPath = path.join(__dirname, "unit-typegen-output/multi-config/client/index.ts");
511+
const content = await fs.readFile(indexPath, "utf-8");
512+
513+
expect(content).toContain('export { client as schemaALayout } from "./schemaA";');
514+
expect(content).toContain('export { client as schemaBLayout } from "./schemaB";');
515+
});
516+
517+
it("clears shared output path once when any config enables clearOldFiles", async () => {
518+
vi.stubGlobal(
519+
"fetch",
520+
createLayoutMetadataMock({
521+
LayoutA: mockLayoutMetadata["basic-layout"],
522+
LayoutB: mockLayoutMetadata["layout-with-portal"],
523+
}),
524+
);
525+
526+
const outputDir = path.join(__dirname, "unit-typegen-output/multi-config-clear");
527+
await fs.mkdir(path.join(outputDir, "client"), { recursive: true });
528+
await fs.mkdir(path.join(outputDir, "generated"), { recursive: true });
529+
await fs.writeFile(path.join(outputDir, "client", "stale.ts"), "stale client");
530+
await fs.writeFile(path.join(outputDir, "generated", "stale.ts"), "stale generated");
531+
532+
const config: Extract<z.infer<typeof typegenConfigSingle>, { type: "fmdapi" }>[] = [
533+
{
534+
type: "fmdapi",
535+
layouts: [
536+
{
537+
layoutName: "LayoutA",
538+
schemaName: "schemaA",
539+
},
540+
],
541+
path: "unit-typegen-output/multi-config-clear",
542+
generateClient: true,
543+
clientSuffix: "Layout",
544+
clearOldFiles: true,
545+
},
546+
{
547+
type: "fmdapi",
548+
layouts: [
549+
{
550+
layoutName: "LayoutB",
551+
schemaName: "schemaB",
552+
},
553+
],
554+
path: "unit-typegen-output/multi-config-clear",
555+
generateClient: true,
556+
clientSuffix: "Layout",
557+
clearOldFiles: false,
558+
},
559+
];
560+
561+
await generateTypedClients(config, { cwd: import.meta.dirname });
562+
563+
const indexPath = path.join(outputDir, "client/index.ts");
564+
const indexContent = await fs.readFile(indexPath, "utf-8");
565+
566+
expect(indexContent).toContain('export { client as schemaALayout } from "./schemaA";');
567+
expect(indexContent).toContain('export { client as schemaBLayout } from "./schemaB";');
568+
569+
await expect(fs.access(path.join(outputDir, "client", "stale.ts"))).rejects.toThrow();
570+
await expect(fs.access(path.join(outputDir, "generated", "stale.ts"))).rejects.toThrow();
571+
});
572+
470573
it("generates client using WebViewerAdapter when fmMcp config is provided", async () => {
471574
process.env.FM_MCP_BASE_URL = "http://127.0.0.1:1365";
472575
process.env.FM_CONNECTED_FILE_NAME = "TestFile";

0 commit comments

Comments
 (0)