Analyze the code to see what field names are acutally used. Deprecates the customUserAttributes option, it's no longer needed

This commit is contained in:
garronej 2023-06-19 00:09:21 +02:00
parent 58301e0844
commit 8c8540de5d
12 changed files with 162 additions and 69 deletions

View File

@ -11,7 +11,6 @@
"scripts": { "scripts": {
"prepare": "yarn generate-i18n-messages", "prepare": "yarn generate-i18n-messages",
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/", "build": "rimraf dist/ && tsc -p src/bin && tsc -p src && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/",
"watch-in-starter": "yarn build && yarn link-in-starter && (concurrently \"tsc -p src -w\" \"tsc-alias -p src/tsconfig.json\" \"tsc -p src/bin -w\")",
"generate:json-schema": "ts-node scripts/generate-json-schema.ts", "generate:json-schema": "ts-node scripts/generate-json-schema.ts",
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js", "grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"copy-files": "copyfiles -u 1 src/**/*.ftl", "copy-files": "copyfiles -u 1 src/**/*.ftl",
@ -24,6 +23,7 @@
"generate-i18n-messages": "ts-node --skipProject scripts/generate-i18n-messages.ts", "generate-i18n-messages": "ts-node --skipProject scripts/generate-i18n-messages.ts",
"link-in-app": "ts-node --skipProject scripts/link-in-app.ts", "link-in-app": "ts-node --skipProject scripts/link-in-app.ts",
"link-in-starter": "yarn link-in-app keycloakify-starter", "link-in-starter": "yarn link-in-app keycloakify-starter",
"watch-in-starter": "yarn build && yarn link-in-starter && (concurrently \"tsc -p src -w\" \"tsc-alias -p src/tsconfig.json\" \"tsc -p src/bin -w\")",
"copy-keycloak-resources-to-storybook-static": "PUBLIC_DIR_PATH=.storybook/static node dist/bin/copy-keycloak-resources-to-public.js", "copy-keycloak-resources-to-storybook-static": "PUBLIC_DIR_PATH=.storybook/static node dist/bin/copy-keycloak-resources-to-public.js",
"storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006", "storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006",
"build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook" "build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook"

View File

@ -31,13 +31,3 @@ export function getThemeSrcDirPath(params: { projectDirPath: string }) {
return { themeSrcDirPath }; return { themeSrcDirPath };
} }
export function getEmailThemeSrcDirPath(params: { projectDirPath: string }) {
const { projectDirPath } = params;
const { themeSrcDirPath } = getThemeSrcDirPath({ projectDirPath });
const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email");
return { emailThemeSrcDirPath };
}

View File

@ -7,7 +7,7 @@ import { promptKeycloakVersion } from "./promptKeycloakVersion";
import { readBuildOptions } from "./keycloakify/BuildOptions"; import { readBuildOptions } from "./keycloakify/BuildOptions";
import * as fs from "fs"; import * as fs from "fs";
import { getLogger } from "./tools/logger"; import { getLogger } from "./tools/logger";
import { getEmailThemeSrcDirPath } from "./getSrcDirPath"; import { getThemeSrcDirPath } from "./getSrcDirPath";
export async function main() { export async function main() {
const { isSilent } = readBuildOptions({ const { isSilent } = readBuildOptions({
@ -17,16 +17,18 @@ export async function main() {
const logger = getLogger({ isSilent }); const logger = getLogger({ isSilent });
const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath({ const { themeSrcDirPath } = getThemeSrcDirPath({
"projectDirPath": process.cwd() "projectDirPath": process.cwd()
}); });
if (emailThemeSrcDirPath === undefined) { if (themeSrcDirPath === undefined) {
logger.warn("Couldn't locate your theme source directory"); logger.warn("Couldn't locate your theme source directory");
process.exit(-1); process.exit(-1);
} }
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
if (fs.existsSync(emailThemeSrcDirPath)) { if (fs.existsSync(emailThemeSrcDirPath)) {
logger.warn(`There is already a ${pathRelative(process.cwd(), emailThemeSrcDirPath)} directory in your project. Aborting.`); logger.warn(`There is already a ${pathRelative(process.cwd(), emailThemeSrcDirPath)} directory in your project. Aborting.`);

View File

@ -28,7 +28,6 @@ export namespace BuildOptions {
reactAppBuildDirPath: string; reactAppBuildDirPath: string;
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */ /** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
keycloakifyBuildDirPath: string; keycloakifyBuildDirPath: string;
customUserAttributes: string[];
}; };
export type Standalone = Common & { export type Standalone = Common & {
@ -199,8 +198,7 @@ export function readBuildOptions(params: { projectDirPath: string; processArgv:
} }
return keycloakifyBuildDirPath; return keycloakifyBuildDirPath;
})(), })()
"customUserAttributes": keycloakify.customUserAttributes ?? []
}; };
})(); })();

View File

@ -8,13 +8,7 @@
out["advancedMsg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); }; out["advancedMsg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
out["messagesPerField"]= { out["messagesPerField"]= {
<#assign fieldNames = [ <#assign fieldNames = [ FIELD_NAMES_eKsIY4ZsZ4xeM ]>
"global", "userLabel", "username", "email", "firstName", "lastName", "password", "password-confirm",
"totp", "totpSecret", "SAMLRequest", "SAMLResponse", "relayState", "device_user_code", "code",
"password-new", "rememberMe", "login", "authenticationExecution", "cancel-aia", "clientDataJSON",
"authenticatorData", "signature", "credentialId", "userHandle", "error", "authn_use_chk", "authenticationExecution",
"isSetRetry", "try-again", "attestationObject", "publicKeyCredentialId", "authenticatorLabel"CUSTOM_USER_ATTRIBUTES_eKsIY4ZsZ4xeM
]>
<#attempt> <#attempt>
<#if profile?? && profile.attributes?? && profile.attributes?is_enumerable> <#if profile?? && profile.attributes?? && profile.attributes?is_enumerable>

View File

@ -18,7 +18,6 @@ export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.Ex
export namespace BuildOptionsLike { export namespace BuildOptionsLike {
export type Common = { export type Common = {
themeName: string; themeName: string;
customUserAttributes: string[];
themeVersion: string; themeVersion: string;
}; };
@ -57,8 +56,9 @@ export function generateFtlFilesCodeFactory(params: {
buildOptions: BuildOptionsLike; buildOptions: BuildOptionsLike;
keycloakifyVersion: string; keycloakifyVersion: string;
themeType: ThemeType; themeType: ThemeType;
fieldNames: string[];
}) { }) {
const { cssGlobalsToDefine, indexHtmlCode, buildOptions, keycloakifyVersion, themeType } = params; const { cssGlobalsToDefine, indexHtmlCode, buildOptions, keycloakifyVersion, themeType, fieldNames } = params;
const $ = cheerio.load(indexHtmlCode); const $ = cheerio.load(indexHtmlCode);
@ -129,10 +129,7 @@ export function generateFtlFilesCodeFactory(params: {
.readFileSync(pathJoin(__dirname, "ftl_object_to_js_code_declaring_an_object.ftl")) .readFileSync(pathJoin(__dirname, "ftl_object_to_js_code_declaring_an_object.ftl"))
.toString("utf8") .toString("utf8")
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1] .match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1]
.replace( .replace("FIELD_NAMES_eKsIY4ZsZ4xeM", fieldNames.map(name => `"${name}"`).join(", "))
"CUSTOM_USER_ATTRIBUTES_eKsIY4ZsZ4xeM",
buildOptions.customUserAttributes.length === 0 ? "" : ", " + buildOptions.customUserAttributes.map(name => `"${name}"`).join(", ")
)
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion) .replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion)
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion) .replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion)
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType) .replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType)

View File

@ -1,9 +1,9 @@
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path"; import { join as pathJoin, dirname as pathDirname } from "path";
import { themeTypes } from "./generateFtl/generateFtl";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect"; import { Reflect } from "tsafe/Reflect";
import type { BuildOptions } from "./BuildOptions"; import type { BuildOptions } from "./BuildOptions";
import type { ThemeType } from "./generateFtl";
export type BuildOptionsLike = { export type BuildOptionsLike = {
themeName: string; themeName: string;
@ -21,7 +21,7 @@ export type BuildOptionsLike = {
export function generateJavaStackFiles(params: { export function generateJavaStackFiles(params: {
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
doBundlesEmailTemplate: boolean; implementedThemeTypes: Record<ThemeType | "email", boolean>;
buildOptions: BuildOptionsLike; buildOptions: BuildOptionsLike;
}): { }): {
jarFilePath: string; jarFilePath: string;
@ -29,7 +29,7 @@ export function generateJavaStackFiles(params: {
const { const {
buildOptions: { groupId, themeName, extraThemeNames, themeVersion, artifactId }, buildOptions: { groupId, themeName, extraThemeNames, themeVersion, artifactId },
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath,
doBundlesEmailTemplate implementedThemeTypes
} = params; } = params;
{ {
@ -70,7 +70,9 @@ export function generateJavaStackFiles(params: {
{ {
"themes": [themeName, ...extraThemeNames].map(themeName => ({ "themes": [themeName, ...extraThemeNames].map(themeName => ({
"name": themeName, "name": themeName,
"types": [...themeTypes, ...(doBundlesEmailTemplate ? ["email"] : [])] "types": Object.entries(implementedThemeTypes)
.filter(([, isImplemented]) => isImplemented)
.map(([themeType]) => themeType)
})) }))
}, },
null, null,

View File

@ -33,7 +33,6 @@ export function generateStartKeycloakTestingContainer(params: {
fs.writeFileSync( fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename), pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename),
Buffer.from( Buffer.from(
[ [
"#!/usr/bin/env bash", "#!/usr/bin/env bash",

View File

@ -9,6 +9,7 @@ import { isInside } from "../../tools/isInside";
import type { BuildOptions } from "../BuildOptions"; import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { downloadKeycloakStaticResources } from "./downloadKeycloakStaticResources"; import { downloadKeycloakStaticResources } from "./downloadKeycloakStaticResources";
import { readFieldNameUsage } from "./readFieldNameUsage";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
@ -19,7 +20,6 @@ export namespace BuildOptionsLike {
extraAccountPages?: string[]; extraAccountPages?: string[];
extraThemeProperties?: string[]; extraThemeProperties?: string[];
isSilent: boolean; isSilent: boolean;
customUserAttributes: string[];
themeVersion: string; themeVersion: string;
keycloakVersionDefaultAssets: string; keycloakVersionDefaultAssets: string;
}; };
@ -53,11 +53,12 @@ assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function generateTheme(params: { export async function generateTheme(params: {
reactAppBuildDirPath: string; reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
emailThemeSrcDirPath: string | undefined; themeSrcDirPath: string | undefined;
keycloakifySrcDirPath: string;
buildOptions: BuildOptionsLike; buildOptions: BuildOptionsLike;
keycloakifyVersion: string; keycloakifyVersion: string;
}): Promise<{ doBundlesEmailTemplate: boolean }> { }): Promise<void> {
const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, emailThemeSrcDirPath, buildOptions, keycloakifyVersion } = params; const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params;
const getThemeDirPath = (themeType: ThemeType | "email") => const getThemeDirPath = (themeType: ThemeType | "email") =>
pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, themeType); pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, themeType);
@ -142,7 +143,12 @@ export async function generateTheme(params: {
"cssGlobalsToDefine": allCssGlobalsToDefine, "cssGlobalsToDefine": allCssGlobalsToDefine,
buildOptions, buildOptions,
keycloakifyVersion, keycloakifyVersion,
themeType themeType,
"fieldNames": readFieldNameUsage({
keycloakifySrcDirPath,
themeSrcDirPath,
themeType
})
}); });
return generateFtlFilesCode; return generateFtlFilesCode;
@ -220,21 +226,20 @@ export async function generateTheme(params: {
); );
} }
let doBundlesEmailTemplate: boolean;
email: { email: {
if (emailThemeSrcDirPath === undefined) { if (themeSrcDirPath === undefined) {
doBundlesEmailTemplate = false;
break email; break email;
} }
doBundlesEmailTemplate = true; const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
if (!fs.existsSync(emailThemeSrcDirPath)) {
break email;
}
transformCodebase({ transformCodebase({
"srcDirPath": emailThemeSrcDirPath, "srcDirPath": emailThemeSrcDirPath,
"destDirPath": getThemeDirPath("email") "destDirPath": getThemeDirPath("email")
}); });
} }
return { doBundlesEmailTemplate };
} }

View File

@ -0,0 +1,95 @@
import { crawl } from "../../tools/crawl";
import { removeDuplicates } from "evt/tools/reducers/removeDuplicates";
import { join as pathJoin } from "path";
import * as fs from "fs";
import type { ThemeType } from "../generateFtl";
import { exclude } from "tsafe/exclude";
export function readFieldNameUsage(params: {
keycloakifySrcDirPath: string;
themeSrcDirPath: string | undefined;
themeType: ThemeType | "email";
}): string[] {
const { keycloakifySrcDirPath, themeSrcDirPath, themeType } = params;
const fieldNames: string[] = [];
if (themeSrcDirPath === undefined) {
//If we can't detect the user theme directory we restore the fieldNames we had previously to prevent errors.
fieldNames.push(
...[
"global",
"userLabel",
"username",
"email",
"firstName",
"lastName",
"password",
"password-confirm",
"totp",
"totpSecret",
"SAMLRequest",
"SAMLResponse",
"relayState",
"device_user_code",
"code",
"password-new",
"rememberMe",
"login",
"authenticationExecution",
"cancel-aia",
"clientDataJSON",
"authenticatorData",
"signature",
"credentialId",
"userHandle",
"error",
"authn_use_chk",
"authenticationExecution",
"isSetRetry",
"try-again",
"attestationObject",
"publicKeyCredentialId",
"authenticatorLabel"
]
);
}
for (const srcDirPath of (
[
pathJoin(keycloakifySrcDirPath, themeType),
(() => {
if (themeSrcDirPath === undefined) {
return undefined;
}
const srcDirPath = pathJoin(themeSrcDirPath, themeType);
if (!fs.existsSync(srcDirPath)) {
return undefined;
}
return srcDirPath;
})()
] as const
).filter(exclude(undefined))) {
const filePaths = crawl(srcDirPath)
.filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath))
.map(filePath => pathJoin(srcDirPath, filePath));
for (const filePath of filePaths) {
const rawSourceFile = fs.readFileSync(filePath).toString("utf8");
fieldNames.push(
...Array.from(
rawSourceFile.matchAll(/messagesPerField\.(?:(?:printIfExists)|(?:existsError)|(?:get)|(?:exists))\(["']([^"']+)["']/g),
m => m[1]
)
);
}
}
const out = fieldNames.reduce(...removeDuplicates<string>());
return out;
}

View File

@ -9,8 +9,9 @@ import { getLogger } from "../tools/logger";
import jar from "../tools/jar"; import jar from "../tools/jar";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { Equals } from "tsafe"; import { Equals } from "tsafe";
import { getEmailThemeSrcDirPath } from "../getSrcDirPath"; import { getThemeSrcDirPath } from "../getSrcDirPath";
import { getProjectRoot } from "../tools/getProjectRoot"; import { getProjectRoot } from "../tools/getProjectRoot";
import { objectKeys } from "tsafe/objectKeys";
export async function main() { export async function main() {
const projectDirPath = process.cwd(); const projectDirPath = process.cwd();
@ -23,42 +24,54 @@ export async function main() {
const logger = getLogger({ "isSilent": buildOptions.isSilent }); const logger = getLogger({ "isSilent": buildOptions.isSilent });
logger.log("🔏 Building the keycloak theme...⌚"); logger.log("🔏 Building the keycloak theme...⌚");
let doBundlesEmailTemplate: boolean | undefined; const keycloakifyDirPath = getProjectRoot();
const { themeSrcDirPath } = getThemeSrcDirPath({ projectDirPath });
for (const themeName of [buildOptions.themeName, ...buildOptions.extraThemeNames]) { for (const themeName of [buildOptions.themeName, ...buildOptions.extraThemeNames]) {
const { doBundlesEmailTemplate: doBundlesEmailTemplate_ } = await generateTheme({ await generateTheme({
keycloakThemeBuildingDirPath: buildOptions.keycloakifyBuildDirPath, "keycloakThemeBuildingDirPath": buildOptions.keycloakifyBuildDirPath,
"emailThemeSrcDirPath": (() => { themeSrcDirPath,
const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath({ projectDirPath }); "keycloakifySrcDirPath": pathJoin(keycloakifyDirPath, "src"),
if (emailThemeSrcDirPath === undefined || !fs.existsSync(emailThemeSrcDirPath)) {
return;
}
return emailThemeSrcDirPath;
})(),
"reactAppBuildDirPath": buildOptions.reactAppBuildDirPath, "reactAppBuildDirPath": buildOptions.reactAppBuildDirPath,
"buildOptions": { "buildOptions": {
...buildOptions, ...buildOptions,
"themeName": themeName "themeName": themeName
}, },
"keycloakifyVersion": (() => { "keycloakifyVersion": (() => {
const version = JSON.parse(fs.readFileSync(pathJoin(getProjectRoot(), "package.json")).toString("utf8"))["version"]; const version = JSON.parse(fs.readFileSync(pathJoin(keycloakifyDirPath, "package.json")).toString("utf8"))["version"];
assert(typeof version === "string"); assert(typeof version === "string");
return version; return version;
})() })()
}); });
doBundlesEmailTemplate ??= doBundlesEmailTemplate_;
} }
assert(doBundlesEmailTemplate !== undefined);
const { jarFilePath } = generateJavaStackFiles({ const { jarFilePath } = generateJavaStackFiles({
keycloakThemeBuildingDirPath: buildOptions.keycloakifyBuildDirPath, "keycloakThemeBuildingDirPath": buildOptions.keycloakifyBuildDirPath,
doBundlesEmailTemplate, "implementedThemeTypes": (() => {
const implementedThemeTypes = {
"login": false,
"account": false,
"email": false
};
if (themeSrcDirPath === undefined) {
implementedThemeTypes["login"] = true;
implementedThemeTypes["account"] = true;
return implementedThemeTypes;
}
for (const themeType of objectKeys(implementedThemeTypes)) {
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
continue;
}
implementedThemeTypes[themeType] = true;
}
return implementedThemeTypes;
})(),
buildOptions buildOptions
}); });

View File

@ -23,7 +23,6 @@ export type ParsedPackageJson = {
keycloakVersionDefaultAssets?: string; keycloakVersionDefaultAssets?: string;
reactAppBuildDirPath?: string; reactAppBuildDirPath?: string;
keycloakifyBuildDirPath?: string; keycloakifyBuildDirPath?: string;
customUserAttributes?: string[];
themeName?: string; themeName?: string;
extraThemeNames?: string[]; extraThemeNames?: string[];
}; };
@ -46,7 +45,6 @@ export const zParsedPackageJson = z.object({
"keycloakVersionDefaultAssets": z.string().optional(), "keycloakVersionDefaultAssets": z.string().optional(),
"reactAppBuildDirPath": z.string().optional(), "reactAppBuildDirPath": z.string().optional(),
"keycloakifyBuildDirPath": z.string().optional(), "keycloakifyBuildDirPath": z.string().optional(),
"customUserAttributes": z.array(z.string()).optional(),
"themeName": z.string().optional(), "themeName": z.string().optional(),
"extraThemeNames": z.array(z.string()).optional() "extraThemeNames": z.array(z.string()).optional()
}) })