Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/typegen-proofkit-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/typegen": patch
---

Support transient ProofKit token passthrough.
6 changes: 5 additions & 1 deletion packages/typegen/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const oldConfigPaths = ["fmschema.config.mjs", "fmschema.config.js"];
interface ConfigArgs {
configLocation: string;
resetOverrides?: boolean;
proofkitToken?: string;
}

function init({ configLocation }: ConfigArgs) {
Expand All @@ -34,7 +35,7 @@ function init({ configLocation }: ConfigArgs) {
}
}

async function runCodegen({ configLocation, resetOverrides = false }: ConfigArgs) {
async function runCodegen({ configLocation, resetOverrides = false, proofkitToken }: ConfigArgs) {
if (!fs.existsSync(configLocation)) {
// but check if they have the old config and just need to upgrade...
let hasOldConfig = false;
Expand Down Expand Up @@ -91,6 +92,7 @@ async function runCodegen({ configLocation, resetOverrides = false }: ConfigArgs
resetOverrides,
postGenerateCommand: configParsed.data.postGenerateCommand,
configPath: configLocation,
proofkitToken,
}).catch((err: unknown) => {
const friendlyError = getFriendlyTypegenError(err);
if (friendlyError) {
Expand All @@ -117,6 +119,7 @@ program
.command("generate", { isDefault: true })
.option("--config <filename>", "optional config file name")
.option("--env-path <path>", "optional path to your .env file")
.option("--proofkit-token <token>", "transient ProofKit token for FM MCP authorization")
.option(
"--reset-overrides",
"Recreate the overrides file(s), even if they already exist. Most useful when upgrading from @proofgeist/fmdapi",
Expand All @@ -136,6 +139,7 @@ program
await runCodegen({
configLocation,
resetOverrides: options.resetOverrides,
proofkitToken: options.proofkitToken,
});
});

Expand Down
4 changes: 3 additions & 1 deletion packages/typegen/src/server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ export function createApiApp(context: ApiContext) {

// Flatten the nested layout/folder structure into a flat list with full paths
const flatLayouts = flattenLayouts(layouts);
logServer(`GET /layouts config=${configIndex} returned ${flatLayouts.length} layout${flatLayouts.length === 1 ? "" : "s"}`);
logServer(
`GET /layouts config=${configIndex} returned ${flatLayouts.length} layout${flatLayouts.length === 1 ? "" : "s"}`,
);

return c.json({ layouts: flatLayouts });
} catch (err) {
Expand Down
11 changes: 10 additions & 1 deletion packages/typegen/src/typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const generateTypedClients = async (
resetOverrides?: boolean;
cwd?: string;
configPath?: string;
proofkitToken?: string;
fmMcpClientIdentity?: FmMcpClientIdentity;
fmMcpIdleTimeoutSeconds?: number;
},
Expand Down Expand Up @@ -129,6 +130,7 @@ export const generateTypedClients = async (
cwd,
configPath: options?.configPath,
configIndex: isConfigArray ? configIndex : undefined,
proofkitToken: options?.proofkitToken,
fmMcpClientIdentity: options?.fmMcpClientIdentity,
fmMcpIdleTimeoutSeconds: options?.fmMcpIdleTimeoutSeconds,
});
Expand Down Expand Up @@ -166,6 +168,7 @@ const generateTypedClientsSingle = async (
cwd?: string;
configPath?: string;
configIndex?: number;
proofkitToken?: string;
fmMcpClientIdentity?: FmMcpClientIdentity;
fmMcpIdleTimeoutSeconds?: number;
},
Expand Down Expand Up @@ -243,6 +246,7 @@ const generateTypedClientsSingle = async (
let fmMcpConnectedFileName: string | undefined;
let fmMcpPersistentToken: string | undefined;
let fmMcpSessionId: string | undefined;
const proofkitToken = options?.proofkitToken ?? process.env.FM_MCP_SESSION_ID;

if (validationResult.mode === "fmMcp") {
fmMcpBaseUrl = normalizeFmMcpBaseUrl(validationResult.baseUrl);
Expand All @@ -254,7 +258,12 @@ const generateTypedClientsSingle = async (
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), connectedFilesTimeoutMs);
try {
const headers = new Headers();
if (proofkitToken) {
headers.set("X-ProofKit-Session", proofkitToken);
}
const res = await fetch(`${fmMcpBaseUrl}/connectedFiles`, {
headers,
signal: controller.signal,
});
clearTimeout(timeout);
Expand Down Expand Up @@ -345,7 +354,7 @@ const generateTypedClientsSingle = async (
connectedFileName: fmMcpConnectedFileName,
clientName: fmMcpObj?.clientName ?? sessionClientIdentity.clientName,
},
fmMcpPersistentToken ?? fmMcpObj?.sessionId,
proofkitToken ?? fmMcpPersistentToken ?? fmMcpObj?.sessionId,
);
} else {
server = validationResult.server;
Expand Down
61 changes: 61 additions & 0 deletions packages/typegen/tests/typegen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe("typegen unit tests", () => {
originalEnv.FM_PASSWORD = process.env.FM_PASSWORD;
originalEnv.FM_HTTP_BASE_URL = process.env.FM_HTTP_BASE_URL;
originalEnv.FM_CONNECTED_FILE_NAME = process.env.FM_CONNECTED_FILE_NAME;
originalEnv.FM_MCP_SESSION_ID = process.env.FM_MCP_SESSION_ID;
// Set mock env values for tests
// Use valid Otto API key format (KEY_ prefix for Otto v3)
process.env.OTTO_API_KEY = "KEY_test_api_key_12345";
Expand Down Expand Up @@ -713,4 +714,64 @@ describe("typegen unit tests", () => {
expect(headers.get("X-ProofKit-Session")).toBe("registered-persistent-token");
expect(fetchMock).toHaveBeenCalledTimes(1);
});

it("uses FM_MCP_SESSION_ID as proofkit token", async () => {
process.env.FM_MCP_BASE_URL = "http://127.0.0.1:1365";
process.env.FM_CONNECTED_FILE_NAME = "TestFile";
process.env.FM_MCP_SESSION_ID = "env-proofkit-token";

const fetchMock = vi.fn(
createLayoutMetadataMock({
FmMcpLayout: mockLayoutMetadata["basic-layout"],
}),
);
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);

const config: Extract<z.infer<typeof typegenConfigSingle>, { type: "fmdapi" }> = {
type: "fmdapi",
layouts: [{ layoutName: "FmMcpLayout", schemaName: "fmMcpEnvTokenSchema" }],
path: "unit-typegen-output/fm-mcp-env-token",
generateClient: true,
validator: false,
fmMcp: { enabled: true },
};

await generateTypedClients(config, { cwd: import.meta.dirname });

const headers = fetchMock.mock.calls[0]?.[1]?.headers as Headers;
expect(headers.get("X-ProofKit-Session")).toBe("env-proofkit-token");

const clientPath = path.join(__dirname, "unit-typegen-output/fm-mcp-env-token/client/fmMcpEnvTokenSchema.ts");
const content = await fs.readFile(clientPath, "utf-8");
expect(content).not.toContain("env-proofkit-token");
});

it("uses proofkit token option before FM_MCP_SESSION_ID", async () => {
process.env.FM_MCP_BASE_URL = "http://127.0.0.1:1365";
process.env.FM_CONNECTED_FILE_NAME = "TestFile";
process.env.FM_MCP_SESSION_ID = "env-proofkit-token";

const fetchMock = vi.fn(
createLayoutMetadataMock({
FmMcpLayout: mockLayoutMetadata["basic-layout"],
}),
);
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);

const config: Extract<z.infer<typeof typegenConfigSingle>, { type: "fmdapi" }> = {
type: "fmdapi",
layouts: [{ layoutName: "FmMcpLayout", schemaName: "fmMcpFlagTokenSchema" }],
path: "unit-typegen-output/fm-mcp-flag-token",
validator: false,
fmMcp: { enabled: true },
};

await generateTypedClients(config, {
cwd: import.meta.dirname,
proofkitToken: "flag-proofkit-token",
});

const headers = fetchMock.mock.calls[0]?.[1]?.headers as Headers;
expect(headers.get("X-ProofKit-Session")).toBe("flag-proofkit-token");
});
});
Loading