2024-05-12 20:47:03 +02:00
|
|
|
import { transformCodebase } from "../../tools/transformCodebase";
|
|
|
|
import * as fs from "fs";
|
2024-05-17 02:05:14 +02:00
|
|
|
import { join as pathJoin, resolve as pathResolve } from "path";
|
2024-05-12 20:47:03 +02:00
|
|
|
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
|
|
|
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
2024-05-15 05:14:01 +02:00
|
|
|
import { generateFtlFilesCodeFactory } from "../generateFtl";
|
2024-05-12 20:47:03 +02:00
|
|
|
import {
|
|
|
|
type ThemeType,
|
|
|
|
lastKeycloakVersionWithAccountV1,
|
|
|
|
keycloak_resources,
|
|
|
|
accountV1ThemeName,
|
2024-05-19 04:02:36 +02:00
|
|
|
basenameOfTheKeycloakifyResourcesDir,
|
|
|
|
loginThemePageIds,
|
|
|
|
accountThemePageIds
|
2024-05-15 05:14:01 +02:00
|
|
|
} from "../../shared/constants";
|
2024-05-12 20:47:03 +02:00
|
|
|
import { isInside } from "../../tools/isInside";
|
2024-05-15 05:14:01 +02:00
|
|
|
import type { BuildOptions } from "../../shared/buildOptions";
|
2024-05-12 20:47:03 +02:00
|
|
|
import { assert, type Equals } from "tsafe/assert";
|
2024-05-15 05:14:01 +02:00
|
|
|
import { downloadKeycloakStaticResources } from "../../shared/downloadKeycloakStaticResources";
|
2024-05-12 20:47:03 +02:00
|
|
|
import { readFieldNameUsage } from "./readFieldNameUsage";
|
|
|
|
import { readExtraPagesNames } from "./readExtraPageNames";
|
|
|
|
import { generateMessageProperties } from "./generateMessageProperties";
|
|
|
|
import { bringInAccountV1 } from "./bringInAccountV1";
|
2024-05-16 09:35:31 +02:00
|
|
|
import { getThemeSrcDirPath } from "../../shared/getThemeSrcDirPath";
|
2024-05-12 20:47:03 +02:00
|
|
|
import { rmSync } from "../../tools/fs.rmSync";
|
2024-05-18 10:24:55 +02:00
|
|
|
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
|
2024-05-20 15:48:51 +02:00
|
|
|
import {
|
|
|
|
writeMetaInfKeycloakThemes,
|
|
|
|
type MetaInfKeycloakTheme
|
|
|
|
} from "../../shared/metaInfKeycloakThemes";
|
2024-05-17 02:05:14 +02:00
|
|
|
import { objectEntries } from "tsafe/objectEntries";
|
2024-05-12 20:47:03 +02:00
|
|
|
|
|
|
|
export type BuildOptionsLike = {
|
|
|
|
bundler: "vite" | "webpack";
|
|
|
|
extraThemeProperties: string[] | undefined;
|
|
|
|
themeVersion: string;
|
|
|
|
loginThemeResourcesFromKeycloakVersion: string;
|
|
|
|
reactAppBuildDirPath: string;
|
|
|
|
cacheDirPath: string;
|
|
|
|
assetsDirPath: string;
|
|
|
|
urlPathname: string | undefined;
|
|
|
|
npmWorkspaceRootDirPath: string;
|
2024-05-16 09:35:31 +02:00
|
|
|
reactAppRootDirPath: string;
|
2024-05-17 00:39:05 +02:00
|
|
|
keycloakifyBuildDirPath: string;
|
2024-05-12 20:47:03 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
export async function generateSrcMainResourcesForMainTheme(params: {
|
|
|
|
themeName: string;
|
|
|
|
buildOptions: BuildOptionsLike;
|
|
|
|
}): Promise<void> {
|
2024-05-17 00:39:05 +02:00
|
|
|
const { themeName, buildOptions } = params;
|
2024-05-16 09:35:31 +02:00
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
const { themeSrcDirPath } = getThemeSrcDirPath({
|
|
|
|
reactAppRootDirPath: buildOptions.reactAppRootDirPath
|
|
|
|
});
|
2024-05-12 20:47:03 +02:00
|
|
|
|
|
|
|
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
|
|
|
|
const { themeType } = params;
|
2024-05-20 15:48:51 +02:00
|
|
|
return pathJoin(
|
|
|
|
buildOptions.keycloakifyBuildDirPath,
|
|
|
|
"src",
|
|
|
|
"main",
|
|
|
|
"resources",
|
|
|
|
"theme",
|
|
|
|
themeName,
|
|
|
|
themeType
|
|
|
|
);
|
2024-05-12 20:47:03 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const cssGlobalsToDefine: Record<string, string> = {};
|
|
|
|
|
|
|
|
const implementedThemeTypes: Record<ThemeType | "email", boolean> = {
|
2024-05-20 15:48:51 +02:00
|
|
|
login: false,
|
|
|
|
account: false,
|
|
|
|
email: false
|
2024-05-12 20:47:03 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
for (const themeType of ["login", "account"] as const) {
|
|
|
|
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
implementedThemeTypes[themeType] = true;
|
|
|
|
|
|
|
|
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
|
|
|
|
|
|
|
apply_replacers_and_move_to_theme_resources: {
|
2024-05-20 15:48:51 +02:00
|
|
|
const destDirPath = pathJoin(
|
|
|
|
themeTypeDirPath,
|
|
|
|
"resources",
|
|
|
|
basenameOfTheKeycloakifyResourcesDir
|
|
|
|
);
|
2024-05-12 20:47:03 +02:00
|
|
|
|
|
|
|
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
|
2024-05-20 15:48:51 +02:00
|
|
|
rmSync(destDirPath, { recursive: true, force: true });
|
2024-05-12 20:47:03 +02:00
|
|
|
|
|
|
|
if (themeType === "account" && implementedThemeTypes.login) {
|
2024-05-16 09:20:37 +02:00
|
|
|
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
2024-05-12 20:47:03 +02:00
|
|
|
|
|
|
|
transformCodebase({
|
2024-05-20 15:48:51 +02:00
|
|
|
srcDirPath: pathJoin(
|
2024-05-12 20:47:03 +02:00
|
|
|
getThemeTypeDirPath({
|
2024-05-20 15:48:51 +02:00
|
|
|
themeType: "login"
|
2024-05-12 20:47:03 +02:00
|
|
|
}),
|
|
|
|
"resources",
|
|
|
|
basenameOfTheKeycloakifyResourcesDir
|
|
|
|
),
|
|
|
|
destDirPath
|
|
|
|
});
|
|
|
|
|
|
|
|
break apply_replacers_and_move_to_theme_resources;
|
|
|
|
}
|
|
|
|
|
|
|
|
transformCodebase({
|
2024-05-20 15:48:51 +02:00
|
|
|
srcDirPath: buildOptions.reactAppBuildDirPath,
|
2024-05-12 20:47:03 +02:00
|
|
|
destDirPath,
|
2024-05-20 15:48:51 +02:00
|
|
|
transformSourceCode: ({ filePath, sourceCode }) => {
|
2024-05-12 20:47:03 +02:00
|
|
|
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
|
|
|
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
|
|
|
|
if (
|
|
|
|
isInside({
|
2024-05-20 15:48:51 +02:00
|
|
|
dirPath: pathJoin(
|
|
|
|
buildOptions.reactAppBuildDirPath,
|
|
|
|
keycloak_resources
|
|
|
|
),
|
2024-05-12 20:47:03 +02:00
|
|
|
filePath
|
|
|
|
})
|
|
|
|
) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (/\.css?$/i.test(filePath)) {
|
2024-05-20 15:48:51 +02:00
|
|
|
const {
|
|
|
|
cssGlobalsToDefine: cssGlobalsToDefineForThisFile,
|
|
|
|
fixedCssCode
|
|
|
|
} = replaceImportsInCssCode({
|
|
|
|
cssCode: sourceCode.toString("utf8")
|
2024-05-12 20:47:03 +02:00
|
|
|
});
|
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
Object.entries(cssGlobalsToDefineForThisFile).forEach(
|
|
|
|
([key, value]) => {
|
|
|
|
cssGlobalsToDefine[key] = value;
|
|
|
|
}
|
|
|
|
);
|
2024-05-12 20:47:03 +02:00
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
return {
|
|
|
|
modifiedSourceCode: Buffer.from(fixedCssCode, "utf8")
|
|
|
|
};
|
2024-05-12 20:47:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (/\.js?$/i.test(filePath)) {
|
|
|
|
const { fixedJsCode } = replaceImportsInJsCode({
|
2024-05-20 15:48:51 +02:00
|
|
|
jsCode: sourceCode.toString("utf8"),
|
2024-05-12 20:47:03 +02:00
|
|
|
buildOptions
|
|
|
|
});
|
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
return {
|
|
|
|
modifiedSourceCode: Buffer.from(fixedJsCode, "utf8")
|
|
|
|
};
|
2024-05-12 20:47:03 +02:00
|
|
|
}
|
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
return { modifiedSourceCode: sourceCode };
|
2024-05-12 20:47:03 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
|
|
|
themeName,
|
2024-05-20 15:48:51 +02:00
|
|
|
indexHtmlCode: fs
|
|
|
|
.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html"))
|
|
|
|
.toString("utf8"),
|
2024-05-12 20:47:03 +02:00
|
|
|
cssGlobalsToDefine,
|
|
|
|
buildOptions,
|
2024-05-20 15:48:51 +02:00
|
|
|
keycloakifyVersion: readThisNpmPackageVersion(),
|
2024-05-12 20:47:03 +02:00
|
|
|
themeType,
|
2024-05-20 15:48:51 +02:00
|
|
|
fieldNames: readFieldNameUsage({
|
2024-05-12 20:47:03 +02:00
|
|
|
themeSrcDirPath,
|
|
|
|
themeType
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
[
|
|
|
|
...(() => {
|
|
|
|
switch (themeType) {
|
|
|
|
case "login":
|
|
|
|
return loginThemePageIds;
|
|
|
|
case "account":
|
|
|
|
return accountThemePageIds;
|
|
|
|
}
|
|
|
|
})(),
|
|
|
|
...readExtraPagesNames({
|
|
|
|
themeType,
|
|
|
|
themeSrcDirPath
|
|
|
|
})
|
|
|
|
].forEach(pageId => {
|
|
|
|
const { ftlCode } = generateFtlFilesCode({ pageId });
|
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
fs.mkdirSync(themeTypeDirPath, { recursive: true });
|
2024-05-12 20:47:03 +02:00
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
fs.writeFileSync(
|
|
|
|
pathJoin(themeTypeDirPath, pageId),
|
|
|
|
Buffer.from(ftlCode, "utf8")
|
|
|
|
);
|
2024-05-12 20:47:03 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
generateMessageProperties({
|
|
|
|
themeSrcDirPath,
|
|
|
|
themeType
|
|
|
|
}).forEach(({ languageTag, propertiesFileSource }) => {
|
|
|
|
const messagesDirPath = pathJoin(themeTypeDirPath, "messages");
|
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
fs.mkdirSync(pathJoin(themeTypeDirPath, "messages"), {
|
|
|
|
recursive: true
|
|
|
|
});
|
2024-05-12 20:47:03 +02:00
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
const propertiesFilePath = pathJoin(
|
|
|
|
messagesDirPath,
|
|
|
|
`messages_${languageTag}.properties`
|
|
|
|
);
|
2024-05-12 20:47:03 +02:00
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
fs.writeFileSync(
|
|
|
|
propertiesFilePath,
|
|
|
|
Buffer.from(propertiesFileSource, "utf8")
|
|
|
|
);
|
2024-05-12 20:47:03 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
await downloadKeycloakStaticResources({
|
2024-05-20 15:48:51 +02:00
|
|
|
keycloakVersion: (() => {
|
2024-05-12 20:47:03 +02:00
|
|
|
switch (themeType) {
|
|
|
|
case "account":
|
|
|
|
return lastKeycloakVersionWithAccountV1;
|
|
|
|
case "login":
|
|
|
|
return buildOptions.loginThemeResourcesFromKeycloakVersion;
|
|
|
|
}
|
|
|
|
})(),
|
2024-05-20 15:48:51 +02:00
|
|
|
themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")),
|
2024-05-12 20:47:03 +02:00
|
|
|
themeType,
|
|
|
|
buildOptions
|
|
|
|
});
|
|
|
|
|
|
|
|
fs.writeFileSync(
|
|
|
|
pathJoin(themeTypeDirPath, "theme.properties"),
|
|
|
|
Buffer.from(
|
|
|
|
[
|
|
|
|
`parent=${(() => {
|
|
|
|
switch (themeType) {
|
|
|
|
case "account":
|
|
|
|
return accountV1ThemeName;
|
|
|
|
case "login":
|
|
|
|
return "keycloak";
|
|
|
|
}
|
|
|
|
assert<Equals<typeof themeType, never>>(false);
|
|
|
|
})()}`,
|
|
|
|
...(buildOptions.extraThemeProperties ?? [])
|
|
|
|
].join("\n\n"),
|
|
|
|
"utf8"
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
email: {
|
|
|
|
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
|
|
|
|
|
|
|
|
if (!fs.existsSync(emailThemeSrcDirPath)) {
|
|
|
|
break email;
|
|
|
|
}
|
|
|
|
|
|
|
|
implementedThemeTypes.email = true;
|
|
|
|
|
|
|
|
transformCodebase({
|
2024-05-20 15:48:51 +02:00
|
|
|
srcDirPath: emailThemeSrcDirPath,
|
|
|
|
destDirPath: getThemeTypeDirPath({ themeType: "email" })
|
2024-05-12 20:47:03 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-05-17 02:05:14 +02:00
|
|
|
if (implementedThemeTypes.account) {
|
2024-05-12 20:47:03 +02:00
|
|
|
await bringInAccountV1({
|
|
|
|
buildOptions
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2024-05-20 15:48:51 +02:00
|
|
|
const metaInfKeycloakThemes: MetaInfKeycloakTheme = { themes: [] };
|
2024-05-17 02:05:14 +02:00
|
|
|
|
|
|
|
metaInfKeycloakThemes.themes.push({
|
2024-05-20 15:48:51 +02:00
|
|
|
name: themeName,
|
|
|
|
types: objectEntries(implementedThemeTypes)
|
2024-05-17 02:05:14 +02:00
|
|
|
.filter(([, isImplemented]) => isImplemented)
|
|
|
|
.map(([themeType]) => themeType)
|
|
|
|
});
|
2024-05-12 20:47:03 +02:00
|
|
|
|
2024-05-17 02:05:14 +02:00
|
|
|
if (implementedThemeTypes.account) {
|
|
|
|
metaInfKeycloakThemes.themes.push({
|
2024-05-20 15:48:51 +02:00
|
|
|
name: accountV1ThemeName,
|
|
|
|
types: ["account"]
|
2024-05-17 02:05:14 +02:00
|
|
|
});
|
|
|
|
}
|
2024-05-12 20:47:03 +02:00
|
|
|
|
2024-05-17 02:05:14 +02:00
|
|
|
writeMetaInfKeycloakThemes({
|
2024-05-20 15:48:51 +02:00
|
|
|
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath,
|
2024-05-17 02:05:14 +02:00
|
|
|
metaInfKeycloakThemes
|
|
|
|
});
|
2024-05-12 20:47:03 +02:00
|
|
|
}
|
|
|
|
}
|