326 lines
12 KiB
TypeScript
Raw Normal View History

import { transformCodebase } from "../../tools/transformCodebase";
2021-02-21 18:55:06 +01:00
import * as fs from "fs";
2024-02-05 08:52:58 +01:00
import { join as pathJoin, basename as pathBasename, resolve as pathResolve, dirname as pathDirname } from "path";
2024-01-30 07:10:53 +01:00
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl";
2024-01-30 00:06:17 +01:00
import {
type ThemeType,
lastKeycloakVersionWithAccountV1,
keycloak_resources,
retrocompatPostfix,
2024-01-30 00:06:17 +01:00
accountV1ThemeName,
basenameOfTheKeycloakifyResourcesDir
} from "../../constants";
import { isInside } from "../../tools/isInside";
2024-01-30 06:38:26 +01:00
import type { BuildOptions } from "../buildOptions";
import { assert, type Equals } from "tsafe/assert";
import { downloadKeycloakStaticResources } from "./downloadKeycloakStaticResources";
import { readFieldNameUsage } from "./readFieldNameUsage";
import { readExtraPagesNames } from "./readExtraPageNames";
2023-07-24 00:49:12 +02:00
import { generateMessageProperties } from "./generateMessageProperties";
2024-02-05 08:52:58 +01:00
import { bringInAccountV1 } from "./bringInAccountV1";
2022-08-16 14:41:06 +07:00
2023-08-21 05:54:17 +02:00
export type BuildOptionsLike = {
2024-01-30 07:10:53 +01:00
bundler: "vite" | "webpack";
2023-08-21 05:54:17 +02:00
extraThemeProperties: string[] | undefined;
themeVersion: string;
2023-09-03 21:02:51 +02:00
loginThemeResourcesFromKeycloakVersion: string;
2023-09-03 23:26:34 +02:00
keycloakifyBuildDirPath: string;
reactAppBuildDirPath: string;
cacheDirPath: string;
2024-01-30 07:10:53 +01:00
assetsDirPath: string;
urlPathname: string | undefined;
doBuildRetrocompatAccountTheme: boolean;
2024-02-05 08:52:58 +01:00
themeNames: string[];
2024-02-11 18:28:58 +01:00
npmWorkspaceRootDirPath: string;
2023-08-21 05:54:17 +02:00
};
2022-08-16 14:41:06 +07:00
2023-04-01 13:31:35 +02:00
assert<BuildOptions extends BuildOptionsLike ? true : false>();
2021-03-22 20:54:28 +01:00
export async function generateTheme(params: {
themeName: string;
2023-06-21 18:06:12 +02:00
themeSrcDirPath: string;
keycloakifySrcDirPath: string;
2022-08-16 14:41:06 +07:00
buildOptions: BuildOptionsLike;
keycloakifyVersion: string;
}): Promise<void> {
const { themeName, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params;
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email"; isRetrocompat?: true }) => {
const { themeType, isRetrocompat = false } = params;
return pathJoin(
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"theme",
`${themeName}${isRetrocompat ? retrocompatPostfix : ""}`,
themeType
);
2023-09-04 02:16:55 +02:00
};
2021-02-21 18:55:06 +01:00
2024-02-05 08:52:58 +01:00
const cssGlobalsToDefine: Record<string, string> = {};
2021-02-21 18:55:06 +01:00
2024-02-05 08:52:58 +01:00
const implementedThemeTypes: Record<ThemeType | "email", boolean> = {
"login": false,
"account": false,
"email": false
};
for (const themeType of ["login", "account"] as const) {
2023-06-21 18:06:12 +02:00
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
continue;
}
2024-02-05 08:52:58 +01:00
implementedThemeTypes[themeType] = true;
2024-02-05 08:52:58 +01:00
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
2024-02-05 08:52:58 +01:00
apply_replacers_and_move_to_theme_resources: {
if (themeType === "account" && implementedThemeTypes.login) {
// NOTE: We prevend doing it twice, it has been done for the login theme.
transformCodebase({
"srcDirPath": pathJoin(
getThemeTypeDirPath({
"themeType": "login"
}),
"resources",
basenameOfTheKeycloakifyResourcesDir
),
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir)
});
break apply_replacers_and_move_to_theme_resources;
}
transformCodebase({
2023-09-03 23:26:34 +02:00
"srcDirPath": buildOptions.reactAppBuildDirPath,
2024-02-05 08:52:58 +01:00
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir),
"transformSourceCode": ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
2024-02-05 08:52:58 +01:00
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
if (
isInside({
2023-09-03 23:26:34 +02:00
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
filePath
})
) {
return undefined;
}
if (/\.css?$/i.test(filePath)) {
2024-02-05 08:52:58 +01:00
const { cssGlobalsToDefine: cssGlobalsToDefineForThisFile, fixedCssCode } = replaceImportsInCssCode({
"cssCode": sourceCode.toString("utf8")
});
2024-02-05 08:52:58 +01:00
Object.entries(cssGlobalsToDefineForThisFile).forEach(([key, value]) => {
cssGlobalsToDefine[key] = value;
});
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
if (/\.js?$/i.test(filePath)) {
2024-01-30 07:10:53 +01:00
const { fixedJsCode } = replaceImportsInJsCode({
2024-01-27 18:49:29 +01:00
"jsCode": sourceCode.toString("utf8"),
2024-01-30 07:10:53 +01:00
buildOptions
});
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
2023-08-21 05:54:17 +02:00
return { "modifiedSourceCode": sourceCode };
2022-08-16 14:41:06 +07:00
}
});
}
2022-08-16 14:41:06 +07:00
2024-01-31 21:56:46 +01:00
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
themeName,
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
2024-02-05 08:52:58 +01:00
cssGlobalsToDefine,
2024-01-31 21:56:46 +01:00
buildOptions,
keycloakifyVersion,
themeType,
"fieldNames": readFieldNameUsage({
keycloakifySrcDirPath,
themeSrcDirPath,
themeType
})
});
2021-02-21 18:55:06 +01:00
2023-03-20 01:48:03 +01:00
[
...(() => {
switch (themeType) {
case "login":
return loginThemePageIds;
case "account":
return accountThemePageIds;
}
})(),
2023-06-21 18:06:12 +02:00
...readExtraPagesNames({
themeType,
themeSrcDirPath
})
2023-03-20 01:48:03 +01:00
].forEach(pageId => {
const { ftlCode } = generateFtlFilesCode({ pageId });
2022-08-16 14:41:06 +07:00
2023-09-04 02:34:10 +02:00
fs.mkdirSync(themeTypeDirPath, { "recursive": true });
2021-03-28 13:37:02 +02:00
2023-09-04 02:34:10 +02:00
fs.writeFileSync(pathJoin(themeTypeDirPath, pageId), Buffer.from(ftlCode, "utf8"));
});
2023-07-24 00:49:12 +02:00
generateMessageProperties({
themeSrcDirPath,
themeType
}).forEach(({ languageTag, propertiesFileSource }) => {
2023-09-04 02:34:10 +02:00
const messagesDirPath = pathJoin(themeTypeDirPath, "messages");
2023-07-24 00:49:12 +02:00
2023-09-04 02:34:10 +02:00
fs.mkdirSync(pathJoin(themeTypeDirPath, "messages"), { "recursive": true });
2023-07-24 00:49:12 +02:00
const propertiesFilePath = pathJoin(messagesDirPath, `messages_${languageTag}.properties`);
fs.writeFileSync(propertiesFilePath, Buffer.from(propertiesFileSource, "utf8"));
});
await downloadKeycloakStaticResources({
"keycloakVersion": (() => {
switch (themeType) {
case "account":
return lastKeycloakVersionWithAccountV1;
case "login":
2023-09-03 21:02:51 +02:00
return buildOptions.loginThemeResourcesFromKeycloakVersion;
}
})(),
2023-09-04 02:34:10 +02:00
"themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")),
2023-08-24 08:58:00 +02:00
themeType,
2023-09-03 23:26:34 +02:00
buildOptions
});
fs.writeFileSync(
2023-09-04 02:34:10 +02:00
pathJoin(themeTypeDirPath, "theme.properties"),
Buffer.from(
[
`parent=${(() => {
switch (themeType) {
case "account":
2024-01-30 00:06:17 +01:00
return accountV1ThemeName;
case "login":
return "keycloak";
}
assert<Equals<typeof themeType, never>>(false);
})()}`,
...(buildOptions.extraThemeProperties ?? [])
].join("\n\n"),
"utf8"
)
);
if (themeType === "account" && buildOptions.doBuildRetrocompatAccountTheme) {
transformCodebase({
"srcDirPath": themeTypeDirPath,
"destDirPath": getThemeTypeDirPath({ themeType, "isRetrocompat": true }),
"transformSourceCode": ({ filePath, sourceCode }) => {
if (pathBasename(filePath) === "theme.properties") {
return {
"modifiedSourceCode": Buffer.from(
sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
"utf8"
)
};
}
return { "modifiedSourceCode": sourceCode };
}
});
}
}
2021-02-21 18:55:06 +01:00
2022-04-20 00:39:40 +02:00
email: {
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
if (!fs.existsSync(emailThemeSrcDirPath)) {
break email;
}
2022-04-20 00:39:40 +02:00
2024-02-05 08:52:58 +01:00
implementedThemeTypes.email = true;
2022-04-20 00:39:40 +02:00
transformCodebase({
2023-03-24 05:43:34 +01:00
"srcDirPath": emailThemeSrcDirPath,
2023-09-04 02:34:10 +02:00
"destDirPath": getThemeTypeDirPath({ "themeType": "email" })
2022-04-20 00:39:40 +02:00
});
}
2024-02-05 08:52:58 +01:00
const parsedKeycloakThemeJson: { themes: { name: string; types: string[] }[] } = { "themes": [] };
buildOptions.themeNames.forEach(themeName =>
parsedKeycloakThemeJson.themes.push({
"name": themeName,
"types": Object.entries(implementedThemeTypes)
.filter(([, isImplemented]) => isImplemented)
.map(([themeType]) => themeType)
})
);
account_specific_extra_work: {
if (!implementedThemeTypes.account) {
break account_specific_extra_work;
}
await bringInAccountV1({ buildOptions });
parsedKeycloakThemeJson.themes.push({
"name": accountV1ThemeName,
"types": ["account"]
});
add_retrocompat_account_theme: {
if (!buildOptions.doBuildRetrocompatAccountTheme) {
break add_retrocompat_account_theme;
}
transformCodebase({
"srcDirPath": getThemeTypeDirPath({ "themeType": "account" }),
"destDirPath": getThemeTypeDirPath({ "themeType": "account", "isRetrocompat": true }),
"transformSourceCode": ({ filePath, sourceCode }) => {
if (pathBasename(filePath) === "theme.properties") {
return {
"modifiedSourceCode": Buffer.from(
sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
"utf8"
)
};
}
return { "modifiedSourceCode": sourceCode };
}
});
buildOptions.themeNames.forEach(themeName =>
parsedKeycloakThemeJson.themes.push({
"name": `${themeName}${retrocompatPostfix}`,
"types": ["account"]
})
);
}
}
{
const keycloakThemeJsonFilePath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"META-INF",
"keycloak-themes.json"
);
try {
fs.mkdirSync(pathDirname(keycloakThemeJsonFilePath));
} catch {}
fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8"));
}
2021-02-21 18:55:06 +01:00
}