176 lines
6.2 KiB
TypeScript
176 lines
6.2 KiB
TypeScript
import { assert, type Equals } from "tsafe/assert";
|
|
import { id } from "tsafe/id";
|
|
import type { BuildContext } from "./buildContext";
|
|
import * as fs from "fs/promises";
|
|
import { join as pathJoin } from "path";
|
|
import { existsAsync } from "../tools/fs.existsAsync";
|
|
import { z } from "zod";
|
|
|
|
export type BuildContextLike = {
|
|
projectDirPath: string;
|
|
themeNames: string[];
|
|
environmentVariables: { name: string; default: string }[];
|
|
themeSrcDirPath: string;
|
|
implementedThemeTypes: Pick<
|
|
BuildContext["implementedThemeTypes"],
|
|
"login" | "account"
|
|
>;
|
|
packageJsonFilePath: string;
|
|
};
|
|
|
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
|
|
|
export async function generateKcGenTs(params: {
|
|
buildContext: BuildContextLike;
|
|
}): Promise<void> {
|
|
const { buildContext } = params;
|
|
|
|
const isReactProject: boolean = await (async () => {
|
|
const parsedPackageJson = await (async () => {
|
|
type ParsedPackageJson = {
|
|
dependencies?: Record<string, string>;
|
|
devDependencies?: Record<string, string>;
|
|
};
|
|
|
|
const zParsedPackageJson = (() => {
|
|
type TargetType = ParsedPackageJson;
|
|
|
|
const zTargetType = z.object({
|
|
dependencies: z.record(z.string()).optional(),
|
|
devDependencies: z.record(z.string()).optional()
|
|
});
|
|
|
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
|
|
|
return id<z.ZodType<TargetType>>(zTargetType);
|
|
})();
|
|
|
|
return zParsedPackageJson.parse(
|
|
JSON.parse(
|
|
(await fs.readFile(buildContext.packageJsonFilePath)).toString("utf8")
|
|
)
|
|
);
|
|
})();
|
|
|
|
return (
|
|
{
|
|
...parsedPackageJson.dependencies,
|
|
...parsedPackageJson.devDependencies
|
|
}.react !== undefined
|
|
);
|
|
})();
|
|
|
|
const filePath = pathJoin(
|
|
buildContext.themeSrcDirPath,
|
|
`kc.gen.ts${isReactProject ? "x" : ""}`
|
|
);
|
|
|
|
const currentContent = (await existsAsync(filePath))
|
|
? await fs.readFile(filePath)
|
|
: undefined;
|
|
|
|
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
|
|
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
|
|
|
|
const newContent = Buffer.from(
|
|
[
|
|
`/* prettier-ignore-start */`,
|
|
``,
|
|
`/* eslint-disable */`,
|
|
``,
|
|
`// @ts-nocheck`,
|
|
``,
|
|
`// noinspection JSUnusedGlobalSymbols`,
|
|
``,
|
|
`// This file is auto-generated by Keycloakify`,
|
|
``,
|
|
isReactProject && `import { lazy, Suspense, type ReactNode } from "react";`,
|
|
``,
|
|
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
|
|
``,
|
|
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
|
|
``,
|
|
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
|
|
``,
|
|
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
|
|
``,
|
|
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
|
|
Object.fromEntries(
|
|
buildContext.environmentVariables.map(
|
|
({ name, default: defaultValue }) => [name, defaultValue]
|
|
)
|
|
),
|
|
null,
|
|
2
|
|
)};`,
|
|
``,
|
|
`export type KcContext =`,
|
|
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
|
|
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
|
|
` ;`,
|
|
``,
|
|
`declare global {`,
|
|
` interface Window {`,
|
|
` kcContext?: KcContext;`,
|
|
` }`,
|
|
`}`,
|
|
``,
|
|
...(!isReactProject
|
|
? []
|
|
: [
|
|
hasLoginTheme &&
|
|
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
|
|
hasAccountTheme &&
|
|
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
|
|
``,
|
|
`export function KcPage(`,
|
|
` props: {`,
|
|
` kcContext: KcContext;`,
|
|
` fallback?: ReactNode;`,
|
|
` }`,
|
|
`) {`,
|
|
` const { kcContext, fallback } = props;`,
|
|
` return (`,
|
|
` <Suspense fallback={fallback}>`,
|
|
` {(() => {`,
|
|
` switch (kcContext.themeType) {`,
|
|
hasLoginTheme &&
|
|
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
|
|
hasAccountTheme &&
|
|
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
|
|
` }`,
|
|
` })()}`,
|
|
` </Suspense>`,
|
|
` );`,
|
|
`}`
|
|
]),
|
|
``,
|
|
`/* prettier-ignore-end */`,
|
|
``
|
|
]
|
|
.filter(item => typeof item === "string")
|
|
.join("\n"),
|
|
"utf8"
|
|
);
|
|
|
|
if (currentContent !== undefined && currentContent.equals(newContent)) {
|
|
return;
|
|
}
|
|
|
|
await fs.writeFile(filePath, newContent);
|
|
|
|
delete_legacy_file: {
|
|
if (!isReactProject) {
|
|
break delete_legacy_file;
|
|
}
|
|
|
|
const legacyFilePath = filePath.replace(/tsx$/, "ts");
|
|
|
|
if (!(await existsAsync(legacyFilePath))) {
|
|
break delete_legacy_file;
|
|
}
|
|
|
|
await fs.unlink(legacyFilePath);
|
|
}
|
|
}
|