Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
a7c0e5bdaa | |||
3b051cbbea | |||
f7edfd1c29 | |||
b182c43965 | |||
4639e7ad2e | |||
56241203a0 | |||
8c8540de5d | |||
b45af78322 | |||
98bcf3bf7e | |||
e28bcfced3 | |||
a5bd990245 | |||
58301e0844 | |||
c9213fb6cd | |||
641819a364 | |||
3ee3a8b41d | |||
5600403088 | |||
3b00bace23 | |||
fcba470aad | |||
206e602d73 |
@ -117,6 +117,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
|
|
||||||
# Changelog highlights
|
# Changelog highlights
|
||||||
|
|
||||||
|
## 7.13
|
||||||
|
|
||||||
|
- Deprecate `customUserAttribute`, Keycloakify now analyze your code to predict field name usage. [See doc](https://docs.keycloakify.dev/build-options#customuserattributes).
|
||||||
|
|
||||||
## 7.12
|
## 7.12
|
||||||
|
|
||||||
- You can now pack multiple themes variant in a single `.jar` bundle. In vanilla Keycloak themes you have the ability to extend a base theme.
|
- You can now pack multiple themes variant in a single `.jar` bundle. In vanilla Keycloak themes you have the ability to extend a base theme.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "7.12.2",
|
"version": "7.13.0",
|
||||||
"description": "Create Keycloak themes using React",
|
"description": "Create Keycloak themes using React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -10,8 +10,7 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"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/ && cp -r src 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"
|
||||||
|
@ -52,9 +52,34 @@ export declare namespace KcContext {
|
|||||||
name: string; // Client id
|
name: string; // Client id
|
||||||
};
|
};
|
||||||
messagesPerField: {
|
messagesPerField: {
|
||||||
printIfExists: <T>(fieldName: string, x: T) => T | undefined;
|
/**
|
||||||
|
* Return text if message for given field exists. Useful eg. to add css styles for fields with message.
|
||||||
|
*
|
||||||
|
* @param fieldName to check for
|
||||||
|
* @param text to return
|
||||||
|
* @return text if message exists for given field, else undefined
|
||||||
|
*/
|
||||||
|
printIfExists: <T extends string>(fieldName: string, text: T) => T | undefined;
|
||||||
|
/**
|
||||||
|
* Check if exists error message for given fields
|
||||||
|
*
|
||||||
|
* @param fields
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
existsError: (fieldName: string) => boolean;
|
existsError: (fieldName: string) => boolean;
|
||||||
|
/**
|
||||||
|
* Get message for given field.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* @return message text or empty string
|
||||||
|
*/
|
||||||
get: (fieldName: string) => string;
|
get: (fieldName: string) => string;
|
||||||
|
/**
|
||||||
|
* Check if message for given field exists
|
||||||
|
*
|
||||||
|
* @param field
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
exists: (fieldName: string) => boolean;
|
exists: (fieldName: string) => boolean;
|
||||||
};
|
};
|
||||||
account: {
|
account: {
|
||||||
|
@ -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 };
|
|
||||||
}
|
|
||||||
|
@ -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.`);
|
||||||
|
|
||||||
|
@ -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 ?? []
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -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>
|
||||||
@ -28,85 +22,371 @@
|
|||||||
<#recover>
|
<#recover>
|
||||||
</#attempt>
|
</#attempt>
|
||||||
|
|
||||||
"printIfExists": function (fieldName, x) {
|
"printIfExists": function (fieldName, text) {
|
||||||
<#if !messagesPerField?? >
|
|
||||||
return undefined;
|
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||||
<#else>
|
throw new Error("You're not supposed to use messagesPerField.printIfExists in this page");
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#list fieldNames as fieldName>
|
<#list fieldNames as fieldName>
|
||||||
if(fieldName === "${fieldName}" ){
|
if(fieldName === "${fieldName}" ){
|
||||||
<#attempt>
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 -->
|
||||||
|
<#if !messagesPerField.existsError??>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
return <#if messagesPerField.existsError('username', 'password')>x<#else>undefined</#if>;
|
|
||||||
<#else>
|
<#assign doExistMessageForUsernameOrPassword = "">
|
||||||
return <#if messagesPerField.existsError('${fieldName}')>x<#else>undefined</#if>;
|
|
||||||
</#if>
|
<#attempt>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')>
|
||||||
<#recover>
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
</#attempt>
|
</#attempt>
|
||||||
|
|
||||||
|
<#if !doExistMessageForUsernameOrPassword>
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
return <#if doExistMessageForUsernameOrPassword>text<#else>undefined</#if>;
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#assign doExistMessageForField = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForField = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistMessageForField>text<#else>undefined</#if>;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
|
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if doExistErrorOnUsernameOrPassword>
|
||||||
|
return text;
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#assign doExistMessageForField = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForField = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistMessageForField>text<#else>undefined</#if>;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#assign doExistMessageForField = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForField = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistMessageForField>text<#else>undefined</#if>;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
}
|
}
|
||||||
</#list>
|
</#list>
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
</#if>
|
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||||
},
|
},
|
||||||
"existsError": function (fieldName) {
|
"existsError": function (fieldName) {
|
||||||
<#if !messagesPerField?? >
|
|
||||||
return false;
|
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||||
<#else>
|
throw new Error("You're not supposed to use messagesPerField.printIfExists in this page");
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#list fieldNames as fieldName>
|
<#list fieldNames as fieldName>
|
||||||
if(fieldName === "${fieldName}" ){
|
if(fieldName === "${fieldName}" ){
|
||||||
<#attempt>
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 -->
|
||||||
|
<#if !messagesPerField.existsError??>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
return <#if messagesPerField.existsError('username', 'password')>true<#else>false</#if>;
|
|
||||||
<#else>
|
<#assign doExistMessageForUsernameOrPassword = "">
|
||||||
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
|
|
||||||
</#if>
|
<#attempt>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')>
|
||||||
<#recover>
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
</#attempt>
|
</#attempt>
|
||||||
|
|
||||||
|
<#if !doExistMessageForUsernameOrPassword>
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
return <#if doExistMessageForUsernameOrPassword>true<#else>false</#if>;
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#assign doExistMessageForField = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForField = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistMessageForField>true<#else>false</#if>;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
|
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistErrorOnUsernameOrPassword>true<#else>false</#if>;
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#assign doExistErrorMessageForField = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistErrorMessageForField = messagesPerField.existsError('${fieldName}')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistErrorMessageForField = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistErrorMessageForField>true<#else>false</#if>;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
}
|
}
|
||||||
</#list>
|
</#list>
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
</#if>
|
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||||
|
|
||||||
},
|
},
|
||||||
"get": function (fieldName) {
|
"get": function (fieldName) {
|
||||||
<#if !messagesPerField?? >
|
|
||||||
return '';
|
|
||||||
<#else>
|
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||||
|
throw new Error("You're not supposed to use messagesPerField.get in this page");
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#list fieldNames as fieldName>
|
<#list fieldNames as fieldName>
|
||||||
if(fieldName === "${fieldName}" ){
|
if(fieldName === "${fieldName}" ){
|
||||||
<#attempt>
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 -->
|
||||||
|
<#if !messagesPerField.existsError??>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
<#if messagesPerField.existsError('username', 'password')>
|
|
||||||
return 'Invalid username or password.';
|
<#assign doExistMessageForUsernameOrPassword = "">
|
||||||
</#if>
|
|
||||||
<#else>
|
<#attempt>
|
||||||
<#if messagesPerField.existsError('${fieldName}')>
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')>
|
||||||
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
|
||||||
</#if>
|
|
||||||
</#if>
|
|
||||||
<#recover>
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
</#attempt>
|
</#attempt>
|
||||||
|
|
||||||
|
<#if !doExistMessageForUsernameOrPassword>
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if !doExistMessageForUsernameOrPassword>
|
||||||
|
return "";
|
||||||
|
<#else>
|
||||||
|
<#attempt>
|
||||||
|
return "${kcSanitize(msg('invalidUserMessage'))?no_esc}";
|
||||||
|
<#recover>
|
||||||
|
return "Invalid username or password.";
|
||||||
|
</#attempt>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||||
|
<#recover>
|
||||||
|
return "invalid field";
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
|
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if doExistErrorOnUsernameOrPassword>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
return "${kcSanitize(msg('invalidUserMessage'))?no_esc}";
|
||||||
|
<#recover>
|
||||||
|
return "Invalid username or password.";
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||||
|
<#recover>
|
||||||
|
return "";
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||||
|
<#recover>
|
||||||
|
return "invalid field";
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
}
|
}
|
||||||
</#list>
|
</#list>
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
</#if>
|
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||||
|
|
||||||
},
|
},
|
||||||
"exists": function (fieldName) {
|
"exists": function (fieldName) {
|
||||||
<#if !messagesPerField?? >
|
|
||||||
return false;
|
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||||
<#else>
|
throw new Error("You're not supposed to use messagesPerField.exists in this page");
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#list fieldNames as fieldName>
|
<#list fieldNames as fieldName>
|
||||||
if(fieldName === "${fieldName}" ){
|
if(fieldName === "${fieldName}" ){
|
||||||
<#attempt>
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/359 Compat with Keycloak prior v12 -->
|
||||||
|
<#if !messagesPerField.existsError??>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
return <#if messagesPerField.exists('username') || messagesPerField.exists('password')>true<#else>false</#if>;
|
|
||||||
<#else>
|
<#assign doExistMessageForUsernameOrPassword = "">
|
||||||
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
|
|
||||||
</#if>
|
<#attempt>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('username')>
|
||||||
<#recover>
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
</#attempt>
|
</#attempt>
|
||||||
|
|
||||||
|
<#if !doExistMessageForUsernameOrPassword>
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = messagesPerField.exists('password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
return <#if doExistMessageForUsernameOrPassword>true<#else>false</#if>;
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#assign doExistMessageForField = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistMessageForField = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistMessageForField>true<#else>false</#if>;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||||
|
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
|
||||||
|
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistErrorOnUsernameOrPassword>true<#else>false</#if>;
|
||||||
|
|
||||||
|
<#else>
|
||||||
|
|
||||||
|
<#assign doExistErrorMessageForField = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#assign doExistErrorMessageForField = messagesPerField.exists('${fieldName}')>
|
||||||
|
<#recover>
|
||||||
|
<#assign doExistErrorMessageForField = true>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
return <#if doExistErrorMessageForField>true<#else>false</#if>;
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
}
|
}
|
||||||
</#list>
|
</#list>
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
</#if>
|
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -176,6 +456,10 @@
|
|||||||
are_same_path(path, ["url"]) &&
|
are_same_path(path, ["url"]) &&
|
||||||
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl"]?seq_contains(pageId) &&
|
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl"]?seq_contains(pageId) &&
|
||||||
!(auth?has_content && auth.showTryAnotherWayLink())
|
!(auth?has_content && auth.showTryAnotherWayLink())
|
||||||
|
) || (
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/issues/362 -->
|
||||||
|
["secretData", "value"]?seq_contains(key) &&
|
||||||
|
are_same_path(path, [ "totp", "otpCredentials", "*" ])
|
||||||
) || (
|
) || (
|
||||||
["contextData", "idpConfig", "idp", "authenticationSession"]?seq_contains(key) &&
|
["contextData", "idpConfig", "idp", "authenticationSession"]?seq_contains(key) &&
|
||||||
are_same_path(path, ["brokerContext"]) &&
|
are_same_path(path, ["brokerContext"]) &&
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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,
|
||||||
|
"fieldNames": readFieldNameUsage({
|
||||||
|
keycloakifySrcDirPath,
|
||||||
|
themeSrcDirPath,
|
||||||
themeType
|
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 };
|
|
||||||
}
|
}
|
||||||
|
96
src/bin/keycloakify/generateTheme/readFieldNameUsage.ts
Normal file
96
src/bin/keycloakify/generateTheme/readFieldNameUsage.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
if (!rawSourceFile.includes("messagesPerField")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldNames.push(
|
||||||
|
...Array.from(rawSourceFile.matchAll(/(?:(?:printIfExists)|(?:existsError)|(?:get)|(?:exists))\(["']([^"']+)["']/g), m => m[1])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = fieldNames.reduce(...removeDuplicates<string>());
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
|
@ -81,9 +81,34 @@ export declare namespace KcContext {
|
|||||||
};
|
};
|
||||||
isAppInitiatedAction: boolean;
|
isAppInitiatedAction: boolean;
|
||||||
messagesPerField: {
|
messagesPerField: {
|
||||||
printIfExists: <T>(fieldName: string, x: T) => T | undefined;
|
/**
|
||||||
|
* Return text if message for given field exists. Useful eg. to add css styles for fields with message.
|
||||||
|
*
|
||||||
|
* @param fieldName to check for
|
||||||
|
* @param text to return
|
||||||
|
* @return text if message exists for given field, else undefined
|
||||||
|
*/
|
||||||
|
printIfExists: <T extends string>(fieldName: string, text: T) => T | undefined;
|
||||||
|
/**
|
||||||
|
* Check if exists error message for given fields
|
||||||
|
*
|
||||||
|
* @param fields
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
existsError: (fieldName: string) => boolean;
|
existsError: (fieldName: string) => boolean;
|
||||||
|
/**
|
||||||
|
* Get message for given field.
|
||||||
|
*
|
||||||
|
* @param fieldName
|
||||||
|
* @return message text or empty string
|
||||||
|
*/
|
||||||
get: (fieldName: string) => string;
|
get: (fieldName: string) => string;
|
||||||
|
/**
|
||||||
|
* Check if message for given field exists
|
||||||
|
*
|
||||||
|
* @param field
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
exists: (fieldName: string) => boolean;
|
exists: (fieldName: string) => boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user