Compare commits

...

13 Commits

15 changed files with 203 additions and 341 deletions

View File

@ -1,3 +1,19 @@
### **0.3.18** (2021-04-01)
- Fix error.ftt, Adopt best effort strategy to convert ftl values into JS
### **0.3.17** (2021-03-29)
- Use push instead of replace in keycloak-js adapter to enable going back
### **0.3.15** (2021-03-28)
- Remove all reference to --external-assets, broken feature
### **0.3.14** (2021-03-28)
- Fix standalone mode: imports from js
### **0.3.13** (2021-03-26)

View File

@ -41,7 +41,6 @@ Here is `yarn add keycloakify` for you 🍸
- [Motivations](#motivations)
- [How to use](#how-to-use)
- [Setting up the build tool](#setting-up-the-build-tool)
- [Specify from where the resources should be downloaded.](#specify-from-where-the-resources-should-be-downloaded)
- [Developing your login and register pages in your React app](#developing-your-login-and-register-pages-in-your-react-app)
- [Just changing the look](#just-changing-the-look)
- [Changing the look **and** feel](#changing-the-look-and-feel)
@ -70,10 +69,8 @@ Here is `yarn add keycloakify` for you 🍸
"keycloak": "yarn build && build-keycloak-theme",
},
```
`"homepage"` must be specified only if the theme is build using
`--external-assets`(#specify-from-where-the-resources-should-be-downloaded) or if
the url path is not `/` (only the url path will be considered so it doesn't matter if the
base url is wrong)
`"homepage"` must be specified only if the url path is not `/`
(Onl `/YOUR-APP` matters `URL.OF` don't have to be the actual domain)
It is mandatory that you specify the url where your app will be available
using the `homepage` field.
@ -85,46 +82,6 @@ dependency with `yarn install` and build the keycloak theme with
Once the build is complete instructions about how to load
the theme into Keycloak are printed in the console.
### Specify from where the resources should be downloaded.
*TL;DR*: Building the theme with the `--external-assets` option enables the login
page to load faster for first time users but it also implies that:
- If the app is down, your Keycloak login and register pages are down as well.
- Each time the app is updated, the theme must be updated.
- CORS must be enabled for fonts.
- You must know at build time what will be the url of your app (`"homepage"` in `package.json`).
<details>
<summary>Click to expand</summary>
When you run `npx build-keycloak-theme` without arguments, Keycloakify will build
a standalone version of the Keycloak theme. That is to say even if your app, the
one hosted at the url specified as `homepage`, is down the Keycloak theme will still work.
It also mean that you won't have to update your theme on your Keycloak server each time
your app is updated.
In this mode, the default, every asset are served by the keycloak server.
The drawback of this approach is that when users access the login page for the first time
they have to download the whole app again.
You probably have [long-term asset caching](https://create-react-app.dev/docs/production-build/#static-file-caching)
enabled in the server that host your app ([example](https://github.com/garronej/keycloakify-demo-app/blob/224c43383548635a463fa68e8909c147ac189f0e/nginx.conf#L14))
so it can be interesting to only serve the html from Keycloak server and everything
else, your JS bundles, your CSS ect from the server that host your app.
To enable this behavior you car run:
```bash
npx build-keycloak-theme --external-assets
```
(instead of `npx build-keycloak-theme`)
This is something you probably want to do in your CI pipeline. [Example](https://github.com/garronej/keycloakify-demo-app/blob/224c43383548635a463fa68e8909c147ac189f0e/.github/workflows/ci.yaml#L112)
Also note that there is [a same-origin policy exception for fonts](https://en.wikipedia.org/wiki/Same-origin_policy#cite_note-3) so you must enabled
CORS for fonts on the server hosting your app. Concretely this mean that your server should add a `Access-Control-Allow-Origin: *` response header to
GET request on *.woff2?. [Example with Nginx](https://github.com/garronej/keycloakify-demo-app/blob/224c43383548635a463fa68e8909c147ac189f0e/nginx.conf#L18-L20)
</details>
## Developing your login and register pages in your React app
### Just changing the look
@ -240,8 +197,6 @@ NOTE: This build tool has only be tested on MacOS.
You won't be able to [import things from your public directory in your JavaScript code](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system). (This isn't recommended anyway).
## `@font-face` importing fonts from the `src/` dir
**If you are building the theme with `--external-assets` this limitation doesn't apply.**
### Example of setup that **won't** work
- We have a `fonts/` directory in `src/`
@ -322,5 +277,5 @@ flash of the blank html before the js bundle have been evaluated
Part of the lib that runs with node, at build time.
- `npx build-keycloak-theme [--external-assets]`: Builds the theme, the CWD is assumed to be the root of your react project.
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "0.3.13",
"version": "0.3.18",
"description": "Keycloak theme generator for Reacts app",
"repository": {
"type": "git",
@ -14,8 +14,7 @@
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"test": "node dist/test",
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
"generate-messages": "node dist/bin/generate-i18n-messages.js",
"watch": "tsc -w"
"generate-messages": "node dist/bin/generate-i18n-messages.js"
},
"bin": {
"build-keycloak-theme": "dist/bin/build-keycloak-theme/index.js",

View File

@ -0,0 +1,112 @@
<script>const _=
{
"url": (function (){
<#if !url?has_content>
return undefined;
</#if>
return {
"loginAction": "${(url.loginAction!'')?no_esc}",
"resourcesPath": "${(url.resourcesPath!'')?no_esc}",
"resourcesCommonPath": "${(url.resourcesCommonPath!'')?no_esc}",
"loginRestartFlowUrl": "${(url.loginRestartFlowUrl!'')?no_esc}",
"loginUrl": "${(url.loginUrl!'')?no_esc}"
};
})(),
"realm": {
"displayName": "${realm.displayName!''}" || undefined,
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
"internationalizationEnabled": ${(realm.internationalizationEnabled!false)?c},
"registrationEmailAsUsername": ${(realm.registrationEmailAsUsername!false)?c},
},
"locale": (function (){
<#if !realm.internationalizationEnabled>
return undefined;
</#if>
return {
"supported": (function(){
<#if !realm.internationalizationEnabled>
return undefined;
</#if>
var out= [];
<#list locale.supported as lng>
out.push({
"url": "${lng.url?no_esc}",
"label": "${lng.label}",
"languageTag": "${lng.languageTag}"
});
</#list>
return out;
})(),
"current": "${locale.current}"
};
})(),
"auth": (function (){
<#if !auth?has_content>
return undefined;
</#if>
var out= {
"showUsername": ${auth.showUsername()?c},
"showResetCredentials": ${auth.showResetCredentials()?c},
"showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c},
};
<#if auth.showUsername() && !auth.showResetCredentials()>
Object.assign(
out,
{
"attemptedUsername": "${auth.attemptedUsername}"
}
);
</#if>
return out;
})(),
"scripts": (function(){
var out = [];
<#if scripts??>
<#list scripts as script>
out.push("${script}");
</#list>
</#if>
return out;
})(),
"message": (function (){
<#if !message?has_content>
return undefined;
</#if>
return { 
"type": "${message.type}",
"summary": String.htmlUnescape("${message.summary}")
};
})(),
"isAppInitiatedAction": (function (){
<#if isAppInitiatedAction??>
return true;
</#if>
return false;
})()
}
</script>

View File

@ -19,7 +19,7 @@ function loadAdjacentFile(fileBasename: string) {
.toString("utf8");
};
function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
function loadFtlFile(ftlFileBasename: PageId | "common.ftl") {
try {
return loadAdjacentFile(ftlFileBasename)
@ -37,12 +37,7 @@ export function generateFtlFilesCodeFactory(
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
urlPathname: string;
} & ({
mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
})
}
) {
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, urlPathname } = params;
@ -53,19 +48,7 @@ export function generateFtlFilesCodeFactory(
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
ftlValuesGlobalName,
"jsCode": $(element).html()!,
...(() => {
switch (params.mode) {
case "standalone": return {
"mode": params.mode
};
case "external assets": return {
"mode": params.mode,
"urlOrigin": params.urlOrigin,
"urlPathname": params.urlPathname,
};
}
})()
"jsCode": $(element).html()!
});
$(element).text(fixedJsCode);
@ -76,18 +59,7 @@ export function generateFtlFilesCodeFactory(
const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!,
"urlPathname": params.urlPathname,
...(() => {
switch (params.mode) {
case "standalone": return {
"mode": params.mode
};
case "external assets": return {
"mode": params.mode,
"urlOrigin": params.urlOrigin,
};
}
})()
"urlPathname": params.urlPathname
});
$(element).text(fixedCssCode);
@ -106,31 +78,20 @@ export function generateFtlFilesCodeFactory(
return;
}
switch (params.mode) {
case "external assets":
$(element).attr(
attrName,
href.replace(/^\//, `${params.urlOrigin}/`)
);
break;
case "standalone":
$(element).attr(
attrName,
href.replace(
new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`),
"${url.resourcesPath}/build/"
)
);
break;
}
$(element).attr(
attrName,
href.replace(
new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`),
"${url.resourcesPath}/build/"
)
);
})
);
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
const ftlCommonPlaceholders = {
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadFtlFile("template.ftl"),
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadFtlFile("common.ftl"),
'<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->':
[
'<#if scripts??>',

View File

@ -1,114 +0,0 @@
<script>const _=
{
"url": {
"loginAction": "${url.loginAction?no_esc}",
"resourcesPath": "${url.resourcesPath?no_esc}",
"resourcesCommonPath": "${url.resourcesCommonPath?no_esc}",
"loginRestartFlowUrl": "${url.loginRestartFlowUrl?no_esc}",
"loginUrl": "${url.loginUrl?no_esc}"
},
"realm": {
"displayName": "${realm.displayName!''}" || undefined,
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
"internationalizationEnabled": ${realm.internationalizationEnabled?c},
"registrationEmailAsUsername": ${realm.registrationEmailAsUsername?c},
},
"locale": (function (){
<#if realm.internationalizationEnabled>
return {
"supported": (function(){
<#if realm.internationalizationEnabled>
var out= [];
<#list locale.supported as lng>
out.push({
"url": "${lng.url?no_esc}",
"label": "${lng.label}",
"languageTag": "${lng.languageTag}"
});
</#list>
return out;
</#if>
return undefined;
})(),
"current": "${locale.current}"
};
</#if>
return undefined;
})(),
"auth": (function (){
<#if auth?has_content>
var out= {
"showUsername": ${auth.showUsername()?c},
"showResetCredentials": ${auth.showResetCredentials()?c},
"showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c},
};
<#if auth.showUsername() && !auth.showResetCredentials()>
Object.assign(
out,
{
"attemptedUsername": "${auth.attemptedUsername}"
}
);
</#if>
return out;
</#if>
return undefined;
})(),
"scripts": (function(){
var out = [];
<#if scripts??>
<#list scripts as script>
out.push("${script}");
</#list>
</#if>
return out;
})(),
"message": (function (){
<#if message?has_content>
return { 
"type": "${message.type}",
"summary": String.htmlUnescape("${message.summary}")
};
</#if>
return undefined;
})(),
"isAppInitiatedAction": (function (){
<#if isAppInitiatedAction??>
return true;
</#if>
return false;
})()
}
</script>

View File

@ -21,12 +21,7 @@ export function generateKeycloakThemeResources(
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
urlPathname: string;
} & ({
mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
})
}
) {
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, urlPathname } = params;
@ -50,34 +45,31 @@ export function generateKeycloakThemeResources(
return undefined;
}
if (params.mode === "standalone") {
if (/\.css?$/i.test(filePath)) {
if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode(
{ "cssCode": sourceCode.toString("utf8") }
);
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode(
{ "cssCode": sourceCode.toString("utf8") }
);
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine
};
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine
};
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
}
if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName,
"mode": params.mode
});
if (/\.js?$/i.test(filePath)) {
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName
});
}
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
@ -92,18 +84,7 @@ export function generateKeycloakThemeResources(
"indexHtmlCode": fs.readFileSync(
pathJoin(reactAppBuildDirPath, "index.html")
).toString("utf8"),
urlPathname,
...(() => {
switch (params.mode) {
case "external assets": return {
"mode": params.mode,
"urlOrigin": params.urlOrigin
};
case "standalone": return {
"mode": params.mode
};
}
})()
urlPathname
});
pageIds.forEach(pageId => {

View File

@ -11,8 +11,6 @@ import { URL } from "url";
const reactProjectDirPath = process.cwd();
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
@ -26,8 +24,7 @@ if (require.main === module) {
keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name,
...(() => {
"urlPathname": (() => {
const url = (() => {
@ -39,35 +36,12 @@ if (require.main === module) {
})();
const urlPathname =
url === undefined ?
"/" :
url.pathname.replace(/([^/])$/, "$1/");
return url === undefined ?
"/" :
url.pathname.replace(/([^/])$/, "$1/");
return !doUseExternalAssets ?
{
"mode": "standalone",
urlPathname
} as const
:
{
"mode": "external assets",
urlPathname,
"urlOrigin": (() => {
if (url === undefined) {
console.error("ERROR: You must specify 'homepage' in your package.json");
process.exit(-1);
}
return url.origin;
})()
} as const;
})()
});
const { jarFilePath } = generateJavaStackFiles({

View File

@ -5,27 +5,14 @@ export function replaceImportsFromStaticInJsCode(
params: {
ftlValuesGlobalName: string;
jsCode: string;
} & ({
mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
urlPathname: string;
})
}
): { fixedJsCode: string; } {
const { jsCode, ftlValuesGlobalName } = params;
const fixedJsCode = jsCode.replace(
/[a-z]+\.[a-z]+\+"static\//g,
(() => {
switch (params.mode) {
case "standalone":
return `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`;
case "external assets":
return `"${params.urlOrigin}${params.urlPathname}static/`;
}
})()
`window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
);
return { fixedJsCode };
@ -36,12 +23,7 @@ export function replaceImportsInInlineCssCode(
params: {
cssCode: string;
urlPathname: string;
} & ({
mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
})
}
): { fixedCssCode: string; } {
const { cssCode, urlPathname } = params;
@ -50,13 +32,7 @@ export function replaceImportsInInlineCssCode(
urlPathname === "/" ?
/url\(\/([^/][^)]+)\)/g :
new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"),
(...[, group]) => `url(${(() => {
switch (params.mode) {
case "standalone": return "${url.resourcesPath}/build/" + group;
case "external assets": return params.urlOrigin + urlPathname + group
}
})()
})`
(...[, group]) => `url(${"${url.resourcesPath}/build/" + group})`
);
return { fixedCssCode };

View File

@ -25,7 +25,7 @@ export type TemplateProps = {
showUsernameNode?: ReactNode;
formNode: ReactNode;
infoNode?: ReactNode;
} & { kcContext: KcContext.Template; } & KcTemplateProps;
} & { kcContext: KcContext; } & KcTemplateProps;
export const Template = memo((props: TemplateProps) => {

View File

@ -10,12 +10,17 @@ import type { LanguageLabel } from "./i18n/KcLanguageTag";
type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
StrEnum extends `${Prefix}${infer U}` ? U : never;
/** Take theses type definition with a grain of salt.
* Some values might be undefined on some pages.
* (ex: url.loginAction is undefined on error.ftl)
*/
export type KcContext =
KcContext.Login | KcContext.Register | KcContext.Info |
KcContext.Error | KcContext.LoginResetPassword | KcContext.LoginVerifyEmail;
export declare namespace KcContext {
export type Template = {
export type Common = {
url: {
loginAction: string;
resourcesPath: string;
@ -27,7 +32,7 @@ export declare namespace KcContext {
displayName?: string;
displayNameHtml?: string;
internationalizationEnabled: boolean;
registrationEmailAsUsername: boolean; //<---
registrationEmailAsUsername: boolean;
};
/** Undefined if !realm.internationalizationEnabled */
locale?: {
@ -55,7 +60,7 @@ export declare namespace KcContext {
isAppInitiatedAction: boolean;
};
export type Login = Template & {
export type Login = Common & {
pageId: "login.ftl";
url: {
loginResetCredentialsUrl: string;
@ -88,7 +93,7 @@ export declare namespace KcContext {
};
};
export type Register = Template & {
export type Register = Common & {
pageId: "register.ftl";
url: {
registrationAction: string;
@ -120,7 +125,7 @@ export declare namespace KcContext {
recaptchaSiteKey?: string;
};
export type Info = Template & {
export type Info = Common & {
pageId: "info.ftl";
messageHeader?: string;
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[];
@ -132,21 +137,21 @@ export declare namespace KcContext {
}
};
export type Error = Template & {
export type Error = Common & {
pageId: "error.ftl";
client?: {
baseUrl?: string;
}
};
export type LoginResetPassword = Template & {
export type LoginResetPassword = Common & {
pageId: "login-reset-password.ftl";
realm: {
loginWithEmailAllowed: boolean;
}
};
export type LoginVerifyEmail = Template & {
export type LoginVerifyEmail = Common & {
pageId: "login-verify-email.ftl";
};

View File

@ -6,7 +6,7 @@ import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
//NOTE: Aside because we want to be able to import them from node
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
export const kcTemplateContext: KcContext.Template = {
const kcCommonContext: KcContext.Common = {
"url": {
"loginAction": "#",
"resourcesPath": `${process.env["PUBLIC_URL"]}/${resourcesPath}`,
@ -111,7 +111,7 @@ export const kcTemplateContext: KcContext.Template = {
};
Object.defineProperty(
kcTemplateContext.locale!,
kcCommonContext.locale!,
"current",
{
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
@ -120,22 +120,22 @@ Object.defineProperty(
);
export const kcLoginContext: KcContext.Login = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "login.ftl",
"url": {
...kcTemplateContext.url,
...kcCommonContext.url,
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg"
},
"realm": {
...kcTemplateContext.realm,
...kcCommonContext.realm,
"loginWithEmailAllowed": true,
"rememberMe": true,
"password": true,
"resetPasswordAllowed": true,
"registrationAllowed": true
},
"auth": kcTemplateContext.auth!,
"auth": kcCommonContext.auth!,
"social": {
"displayInfo": true
},
@ -147,7 +147,7 @@ export const kcLoginContext: KcContext.Login = {
};
export const kcRegisterContext: KcContext.Register = {
...kcTemplateContext,
...kcCommonContext,
"url": {
...kcLoginContext.url,
"registrationAction": "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
@ -166,7 +166,7 @@ export const kcRegisterContext: KcContext.Register = {
};
export const kcInfoContext: KcContext.Info ={
...kcTemplateContext,
...kcCommonContext,
"pageId": "info.ftl",
"messageHeader": "<Message header>",
"requiredActions": undefined,
@ -178,7 +178,7 @@ export const kcInfoContext: KcContext.Info ={
};
export const kcErrorContext: KcContext.Error = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "error.ftl",
"client": {
"baseUrl": "#"
@ -186,16 +186,16 @@ export const kcErrorContext: KcContext.Error = {
};
export const kcLoginResetPasswordContext: KcContext.LoginResetPassword = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "login-reset-password.ftl",
"realm":{
...kcTemplateContext.realm,
...kcCommonContext.realm,
"loginWithEmailAllowed": false
}
};
export const kcLoginVerifyEmailContext: KcContext.LoginVerifyEmail = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "login-verify-email.ftl"
};

View File

@ -66,13 +66,12 @@ export function createKeycloakAdapter(
return {
"login": options => {
window.location.replace(
window.location.href=
transformUrlBeforeRedirect(
keycloakInstance.createLoginUrl(
options
)
)
);
);
return neverResolvingPromise;
},
"logout": options => {
@ -86,13 +85,13 @@ export function createKeycloakAdapter(
return neverResolvingPromise;
},
"register": options => {
window.location.replace(
window.location.href =
transformUrlBeforeRedirect(
keycloakInstance.createRegisterUrl(
options
)
)
);
);
return neverResolvingPromise;
},
"accountManagement": () => {

View File

@ -12,7 +12,6 @@ generateKeycloakThemeResources({
"themeName": "keycloakify-demo-app",
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
"mode": "standalone",
"urlPathname": "/keycloakify-demo-app/"
});

View File

@ -19,8 +19,7 @@ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
3: "0664cdc0"
}[e] + ".chunk.js"
}
`,
"mode": "standalone"
`
});
console.log({ fixedJsCode });