Compare commits

...

15 Commits

10 changed files with 136 additions and 70 deletions

View File

@ -1,3 +1,19 @@
### **0.3.14** (2021-03-28)
- Fix standalone mode: imports from js
### **0.3.13** (2021-03-26)
### **0.3.12** (2021-03-26)
- Fix mocksContext
### **0.3.11** (2021-03-26)
- Fix previous build, improve README
### **0.3.10** (2021-03-26) ### **0.3.10** (2021-03-26)
- Handle <style> tag, improve documentation - Handle <style> tag, improve documentation

View File

@ -70,6 +70,10 @@ Here is `yarn add keycloakify` for you 🍸
"keycloak": "yarn build && build-keycloak-theme", "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)
It is mandatory that you specify the url where your app will be available It is mandatory that you specify the url where your app will be available
using the `homepage` field. using the `homepage` field.
@ -86,8 +90,9 @@ the theme into Keycloak are printed in the console.
*TL;DR*: Building the theme with the `--external-assets` option enables the login *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: 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. - 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 as well. - Each time the app is updated, the theme must be updated.
- CORS must be enabled for fonts. - 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> <details>
<summary>Click to expand</summary> <summary>Click to expand</summary>

View File

@ -1,6 +1,6 @@
{ {
"name": "keycloakify", "name": "keycloakify",
"version": "0.3.10", "version": "0.3.14",
"description": "Keycloak theme generator for Reacts app", "description": "Keycloak theme generator for Reacts app",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -30,25 +30,22 @@ function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
} }
} }
export type Mode = {
type: "standalone";
urlPathname: string;
} | {
type: "external assets";
urlPathname: string;
urlOrigin: string;
}
export function generateFtlFilesCodeFactory( export function generateFtlFilesCodeFactory(
params: { params: {
ftlValuesGlobalName: string; ftlValuesGlobalName: string;
cssGlobalsToDefine: Record<string, string>; cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string; indexHtmlCode: string;
mode: Mode; urlPathname: string;
} } & ({
mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
})
) { ) {
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, mode } = params; const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, urlPathname } = params;
const $ = cheerio.load(indexHtmlCode); const $ = cheerio.load(indexHtmlCode);
@ -57,7 +54,18 @@ export function generateFtlFilesCodeFactory(
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
ftlValuesGlobalName, ftlValuesGlobalName,
"jsCode": $(element).html()!, "jsCode": $(element).html()!,
mode ...(() => {
switch (params.mode) {
case "standalone": return {
"mode": params.mode
};
case "external assets": return {
"mode": params.mode,
"urlOrigin": params.urlOrigin,
"urlPathname": params.urlPathname,
};
}
})()
}); });
$(element).text(fixedJsCode); $(element).text(fixedJsCode);
@ -68,7 +76,18 @@ export function generateFtlFilesCodeFactory(
const { fixedCssCode } = replaceImportsInInlineCssCode({ const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!, "cssCode": $(element).html()!,
mode "urlPathname": params.urlPathname,
...(() => {
switch (params.mode) {
case "standalone": return {
"mode": params.mode
};
case "external assets": return {
"mode": params.mode,
"urlOrigin": params.urlOrigin,
};
}
})()
}); });
$(element).text(fixedCssCode); $(element).text(fixedCssCode);
@ -87,18 +106,18 @@ export function generateFtlFilesCodeFactory(
return; return;
} }
switch (mode.type) { switch (params.mode) {
case "external assets": case "external assets":
$(element).attr( $(element).attr(
attrName, attrName,
href.replace(/^\//, `${mode.urlOrigin}/`) href.replace(/^\//, `${params.urlOrigin}/`)
); );
break; break;
case "standalone": case "standalone":
$(element).attr( $(element).attr(
attrName, attrName,
href.replace( href.replace(
new RegExp(`^${mode.urlPathname.replace(/\//g, "\\/")}`), new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`),
"${url.resourcesPath}/build/" "${url.resourcesPath}/build/"
) )
); );
@ -131,7 +150,7 @@ export function generateFtlFilesCodeFactory(
'<style>', '<style>',
generateCssCodeToDefineGlobals({ generateCssCodeToDefineGlobals({
cssGlobalsToDefine, cssGlobalsToDefine,
"urlPathname": mode.urlPathname urlPathname
}).cssCodeToPrependInHead, }).cssCodeToPrependInHead,
'</style>', '</style>',
'' ''

View File

@ -6,7 +6,7 @@ import {
replaceImportsInCssCode, replaceImportsInCssCode,
replaceImportsFromStaticInJsCode replaceImportsFromStaticInJsCode
} from "./replaceImportFromStatic"; } from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory, pageIds, Mode } from "./generateFtl"; import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
import { builtinThemesUrl } from "../install-builtin-keycloak-themes"; import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
import { downloadAndUnzip } from "../tools/downloadAndUnzip"; import { downloadAndUnzip } from "../tools/downloadAndUnzip";
import * as child_process from "child_process"; import * as child_process from "child_process";
@ -20,11 +20,16 @@ export function generateKeycloakThemeResources(
themeName: string; themeName: string;
reactAppBuildDirPath: string; reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
mode: Mode; urlPathname: string;
} } & ({
mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
})
) { ) {
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, mode } = params; const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, urlPathname } = params;
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login"); const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
@ -45,7 +50,7 @@ export function generateKeycloakThemeResources(
return undefined; return undefined;
} }
if (mode.type === "standalone") { if (params.mode === "standalone") {
if (/\.css?$/i.test(filePath)) { if (/\.css?$/i.test(filePath)) {
@ -62,17 +67,28 @@ export function generateKeycloakThemeResources(
} }
if (/\.js?$/i.test(filePath)) { }
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ if (/\.js?$/i.test(filePath)) {
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName,
mode
});
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") }; const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName,
...(() => {
switch (params.mode) {
case "external assets": return {
"mode": params.mode,
"urlOrigin": params.urlOrigin,
"urlPathname": params.urlPathname
};
case "standalone": return {
"mode": params.mode
};
}
})()
});
} return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
} }
@ -87,7 +103,18 @@ export function generateKeycloakThemeResources(
"indexHtmlCode": fs.readFileSync( "indexHtmlCode": fs.readFileSync(
pathJoin(reactAppBuildDirPath, "index.html") pathJoin(reactAppBuildDirPath, "index.html")
).toString("utf8"), ).toString("utf8"),
mode urlPathname,
...(() => {
switch (params.mode) {
case "external assets": return {
"mode": params.mode,
"urlOrigin": params.urlOrigin
};
case "standalone": return {
"mode": params.mode
};
}
})()
}); });
pageIds.forEach(pageId => { pageIds.forEach(pageId => {

View File

@ -26,7 +26,7 @@ if (require.main === module) {
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"), "reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name, "themeName": parsedPackageJson.name,
"mode": (() => { ...(() => {
const url = (() => { const url = (() => {
@ -44,16 +44,14 @@ if (require.main === module) {
"/" : "/" :
url.pathname.replace(/([^/])$/, "$1/"); url.pathname.replace(/([^/])$/, "$1/");
return !doUseExternalAssets ? return !doUseExternalAssets ?
{ {
"type": "standalone", "mode": "standalone",
urlPathname urlPathname
} as const } as const
: :
{ {
"type": "external assets", "mode": "external assets",
urlPathname, urlPathname,
"urlOrigin": (() => { "urlOrigin": (() => {

View File

@ -1,32 +1,29 @@
import * as crypto from "crypto"; import * as crypto from "crypto";
type Mode = {
type: "standalone";
} | {
type: "external assets";
urlOrigin: string;
urlPathname: string;
}
export function replaceImportsFromStaticInJsCode( export function replaceImportsFromStaticInJsCode(
params: { params: {
ftlValuesGlobalName: string; ftlValuesGlobalName: string;
jsCode: string; jsCode: string;
mode: Mode; } & ({
} mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
urlPathname: string;
})
): { fixedJsCode: string; } { ): { fixedJsCode: string; } {
const { jsCode, ftlValuesGlobalName, mode } = params; const { jsCode, ftlValuesGlobalName } = params;
const fixedJsCode = jsCode.replace( const fixedJsCode = jsCode.replace(
/[a-z]+\.[a-z]+\+"static\//g, /[a-z]+\.[a-z]+\+"static\//g,
(() => { (() => {
switch (mode.type) { switch (params.mode) {
case "standalone": case "standalone":
return `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`; return `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`;
case "external assets": case "external assets":
return `"${mode.urlOrigin}${mode.urlPathname}static/`; return `"${params.urlOrigin}${params.urlPathname}static/`;
} }
})() })()
); );
@ -38,22 +35,28 @@ export function replaceImportsFromStaticInJsCode(
export function replaceImportsInInlineCssCode( export function replaceImportsInInlineCssCode(
params: { params: {
cssCode: string; cssCode: string;
mode: Mode; urlPathname: string;
} } & ({
mode: "standalone";
} | {
mode: "external assets";
urlOrigin: string;
})
): { fixedCssCode: string; } { ): { fixedCssCode: string; } {
const { cssCode, mode } = params; const { cssCode, urlPathname } = params;
const fixedCssCode = cssCode.replace( const fixedCssCode = cssCode.replace(
/url\((\/[^/][^)]+)\)/g, urlPathname === "/" ?
(...[,group])=> `url(${ /url\(\/([^/][^)]+)\)/g :
(()=>{ new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"),
switch(mode.type){ (...[, group]) => `url(${(() => {
case "standalone": return "${url.resourcesPath}/build" + group; switch (params.mode) {
case "external assets": return mode.urlOrigin + group case "standalone": return "${url.resourcesPath}/build/" + group;
} case "external assets": return params.urlOrigin + urlPathname + group
})() }
})` })()
})`
); );
return { fixedCssCode }; return { fixedCssCode };

View File

@ -9,8 +9,8 @@ import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
export const kcTemplateContext: KcContext.Template = { export const kcTemplateContext: KcContext.Template = {
"url": { "url": {
"loginAction": "#", "loginAction": "#",
"resourcesPath": "/" + resourcesPath, "resourcesPath": `${process.env["PUBLIC_URL"]}/${resourcesPath}`,
"resourcesCommonPath": "/" + resourcesCommonPath, "resourcesCommonPath": `${process.env["PUBLIC_URL"]}/${resourcesCommonPath}`,
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg", "loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg", "loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
}, },

View File

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

View File

@ -20,7 +20,7 @@ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
}[e] + ".chunk.js" }[e] + ".chunk.js"
} }
`, `,
"mode": { "type": "standalone" } "mode": "standalone"
}); });
console.log({ fixedJsCode }); console.log({ fixedJsCode });