@proofkit/typegen
Version:
`@proofkit/typegen` is a tool for generating TypeScript types from FileMaker database schemas, making it easier to work with FileMaker data in modern TypeScript projects.
196 lines (195 loc) • 8.23 kB
JavaScript
import { Project, ScriptKind } from "ts-morph";
import chalk from "chalk";
import { OttoAdapter } from "@proofkit/fmdapi/adapters/otto";
import DataApi from "@proofkit/fmdapi";
import { FetchAdapter } from "@proofkit/fmdapi/adapters/fetch";
import { memoryStore } from "@proofkit/fmdapi/tokenStore/memory";
import fs from "fs-extra";
import path from "path";
import { typegenConfig } from "./types.js";
import { defaultEnvNames, commentHeader, overrideCommentHeader } from "./constants.js";
import { getLayoutMetadata } from "./getLayoutMetadata.js";
import { buildSchema, buildOverrideFile } from "./buildSchema.js";
import { buildLayoutClient } from "./buildLayoutClient.js";
import { formatAndSaveSourceFiles } from "./formatting.js";
import semver from "semver";
const generateTypedClients = async (config, options) => {
const parsedConfig = typegenConfig.safeParse({ config });
if (!parsedConfig.success) {
console.log(chalk.red("ERROR: Invalid config"));
console.log(config);
console.dir(parsedConfig.error, { depth: null });
return;
}
if (Array.isArray(parsedConfig.data.config)) {
for (const option of parsedConfig.data.config) {
await generateTypedClientsSingle(option, options);
}
} else {
await generateTypedClientsSingle(parsedConfig.data.config, options);
}
};
const generateTypedClientsSingle = async (config, options) => {
var _a;
const {
envNames,
layouts,
clientSuffix = "Client",
generateClient = true,
clearOldFiles = false,
...rest
} = config;
const { resetOverrides = false, cwd = process.cwd() } = options ?? {};
const validator = rest.validator ?? "zod/v4";
const rootDir = path.join(cwd, rest.path ?? "schema");
try {
const packageJson = JSON.parse(
fs.readFileSync(path.join(cwd, "package.json"), "utf8")
);
const fmdapiVersion = (_a = packageJson.dependencies) == null ? void 0 : _a["@proofkit/fmdapi"];
if (fmdapiVersion && semver.valid(fmdapiVersion)) {
const isAtLeast501 = semver.satisfies(fmdapiVersion, ">=5.0.1");
if (!isAtLeast501) {
console.log(
chalk.yellow(
"WARNING: @proofkit/typegen will generate types only compatible with @proofkit/fmdapi version 5.0.1 or higher. Please update to the latest version of @proofkit/fmdapi"
)
);
}
}
} catch (e) {
}
const project = new Project({});
const server = process.env[(envNames == null ? void 0 : envNames.server) ?? defaultEnvNames.server];
const db = process.env[(envNames == null ? void 0 : envNames.db) ?? defaultEnvNames.db];
const apiKey = ((envNames == null ? void 0 : envNames.auth) && "apiKey" in envNames.auth ? process.env[envNames.auth.apiKey ?? defaultEnvNames.apiKey] : void 0) ?? process.env[defaultEnvNames.apiKey];
const username = ((envNames == null ? void 0 : envNames.auth) && "username" in envNames.auth ? process.env[envNames.auth.username ?? defaultEnvNames.username] : void 0) ?? process.env[defaultEnvNames.username];
const password = ((envNames == null ? void 0 : envNames.auth) && "password" in envNames.auth ? process.env[envNames.auth.password ?? defaultEnvNames.password] : void 0) ?? process.env[defaultEnvNames.password];
const auth = apiKey ? { apiKey } : { username: username ?? "", password: password ?? "" };
if (!server || !db || !apiKey && !username) {
console.log(chalk.red("ERROR: Could not get all required config values"));
console.log("Ensure the following environment variables are set:");
if (!server) console.log(`${(envNames == null ? void 0 : envNames.server) ?? defaultEnvNames.server}`);
if (!db) console.log(`${(envNames == null ? void 0 : envNames.db) ?? defaultEnvNames.db}`);
if (!apiKey) {
const apiKeyNameToLog = (envNames == null ? void 0 : envNames.auth) && "apiKey" in envNames.auth && envNames.auth.apiKey ? envNames.auth.apiKey : defaultEnvNames.apiKey;
const usernameNameToLog = (envNames == null ? void 0 : envNames.auth) && "username" in envNames.auth && envNames.auth.username ? envNames.auth.username : defaultEnvNames.username;
const passwordNameToLog = (envNames == null ? void 0 : envNames.auth) && "password" in envNames.auth && envNames.auth.password ? envNames.auth.password : defaultEnvNames.password;
console.log(
`${apiKeyNameToLog} (or ${usernameNameToLog} and ${passwordNameToLog})`
);
}
console.log();
return;
}
await fs.ensureDir(rootDir);
if (clearOldFiles) {
fs.emptyDirSync(path.join(rootDir, "client"));
fs.emptyDirSync(path.join(rootDir, "generated"));
}
const clientIndexFilePath = path.join(rootDir, "client", "index.ts");
fs.rmSync(clientIndexFilePath, { force: true });
let successCount = 0;
let errorCount = 0;
let totalCount = 0;
for await (const item of layouts) {
totalCount++;
const client = "apiKey" in auth ? DataApi({
adapter: new OttoAdapter({ auth, server, db }),
layout: item.layoutName
}) : DataApi({
adapter: new FetchAdapter({
auth,
server,
db,
tokenStore: memoryStore()
}),
layout: item.layoutName
});
const result = await getLayoutMetadata({
client,
valueLists: item.valueLists
});
if (!result) {
errorCount++;
continue;
}
const { schema, portalSchema, valueLists } = result;
const args = {
schemaName: item.schemaName,
schema,
layoutName: item.layoutName,
portalSchema,
valueLists,
type: validator === "zod" || validator === "zod/v4" || validator === "zod/v3" ? validator : "ts",
strictNumbers: item.strictNumbers,
webviewerScriptName: config.webviewerScriptName,
envNames: {
auth: "apiKey" in auth ? {
apiKey: (envNames == null ? void 0 : envNames.auth) && "apiKey" in envNames.auth ? envNames.auth.apiKey : defaultEnvNames.apiKey
} : {
username: (envNames == null ? void 0 : envNames.auth) && "username" in envNames.auth ? envNames.auth.username : defaultEnvNames.username,
password: (envNames == null ? void 0 : envNames.auth) && "password" in envNames.auth ? envNames.auth.password : defaultEnvNames.password
},
db: (envNames == null ? void 0 : envNames.db) ?? defaultEnvNames.db,
server: (envNames == null ? void 0 : envNames.server) ?? defaultEnvNames.server
}
};
const schemaFile = project.createSourceFile(
path.join(rootDir, "generated", `${item.schemaName}.ts`),
{ leadingTrivia: commentHeader },
{
overwrite: true,
scriptKind: ScriptKind.TS
}
);
buildSchema(schemaFile, args);
const overrideFilePath = path.join(rootDir, `${item.schemaName}.ts`);
if (!fs.existsSync(overrideFilePath) || resetOverrides) {
const overrideFile = project.createSourceFile(
overrideFilePath,
{
leadingTrivia: overrideCommentHeader
},
{
overwrite: true,
scriptKind: ScriptKind.TS
}
);
buildOverrideFile(overrideFile, schemaFile, args);
}
if (item.generateClient ?? generateClient) {
await fs.ensureDir(path.join(rootDir, "client"));
const layoutClientFile = project.createSourceFile(
path.join(rootDir, "client", `${item.schemaName}.ts`),
{ leadingTrivia: commentHeader },
{
overwrite: true,
scriptKind: ScriptKind.TS
}
);
buildLayoutClient(layoutClientFile, args);
await fs.ensureFile(clientIndexFilePath);
const clientIndexFile = project.addSourceFileAtPath(clientIndexFilePath);
clientIndexFile.addExportDeclaration({
namedExports: [
{ name: "client", alias: `${item.schemaName}${clientSuffix}` }
],
moduleSpecifier: `./${item.schemaName}`
});
} else {
console.log(
chalk.yellow(
`Skipping client generation for ${item.schemaName} because generateClient is false`
)
);
}
successCount++;
}
await formatAndSaveSourceFiles(project);
return { successCount, errorCount, totalCount };
};
export {
generateTypedClients
};
//# sourceMappingURL=typegen.js.map