Compare commits

...

32 Commits

Author SHA1 Message Date
70a524da46 Bump version 2023-03-25 04:56:28 +01:00
bf6c846fac Use locate theme dir in eject script 2023-03-25 04:56:17 +01:00
b83e4bef3f Bump version 2023-03-25 04:43:40 +01:00
9f7fe0d8f7 Fix error initialization email 2023-03-25 04:42:44 +01:00
741dee57e4 Bump version 2023-03-25 04:28:47 +01:00
fff4dba708 #276: Add build option to select keycloak default assets version 2023-03-25 04:28:10 +01:00
f4f7ab3e49 Make email theme initialization work with theme-only projects 2023-03-25 04:20:10 +01:00
88fe99b1b8 Bump version 2023-03-24 06:25:45 +01:00
92c1486f6a Fix previous release 2023-03-24 06:25:25 +01:00
caea64cef3 Fix build 2023-03-24 06:01:07 +01:00
90783d8ee8 Bump version 2023-03-24 05:51:01 +01:00
be57801e21 Fix email theme path 2023-03-24 05:50:40 +01:00
ff84786b4e Bump version 2023-03-24 05:44:00 +01:00
1e863672cb Find the email theme in src 2023-03-24 05:43:34 +01:00
fb98a9c383 Bump version 2023-03-24 04:14:57 +01:00
05163f22cb Rename InseeFrLab to Keycloakify 2023-03-24 04:14:41 +01:00
160f12d7d3 Rename UserProfileCommon to UserProfileFormFields 2023-03-24 04:12:52 +01:00
49e4e36184 Update README.md 2023-03-22 21:33:41 +01:00
c4f8879cda Bump version 2023-03-22 04:49:30 +01:00
8f54166653 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2023-03-22 04:48:08 +01:00
b9f020c447 Merge pull request #272 from willwill96/update-login-types
feat(login context): improve login typings
2023-03-22 04:22:13 +01:00
c357f3eb4d Mention storybook in the changelog 2023-03-22 03:48:21 +01:00
7ebbb0417a feat(login context): improve login typings 2023-03-21 19:47:05 -07:00
6e4b4173b5 Add link to storybook #274 2023-03-22 03:46:30 +01:00
87ebad7efb Bump version 2023-03-22 03:34:59 +01:00
3294aaed3b Pefrorm Keycloak theme download in paralel 2023-03-22 03:34:44 +01:00
0e21f3eab6 Add missing mock 2023-03-22 03:07:33 +01:00
9fcf692cb8 Bump version 2023-03-22 03:03:24 +01:00
da577ea3cc Add stateChecker to password context 2023-03-22 03:02:44 +01:00
6ae1d8938a Fix eslint 2023-03-22 03:00:44 +01:00
3e18a7390c Bump version: Release v7 🚀 2023-03-22 01:55:49 +01:00
5f43f1afc6 Shot a new post build tutorial video 2023-03-22 01:46:05 +01:00
24 changed files with 159 additions and 86 deletions

View File

@ -14,7 +14,7 @@
<a href="https://github.com/garronej/keycloakify/blob/main/LICENSE"> <a href="https://github.com/garronej/keycloakify/blob/main/LICENSE">
<img src="https://img.shields.io/npm/l/keycloakify"> <img src="https://img.shields.io/npm/l/keycloakify">
</a> </a>
<a href="https://github.com/InseeFrLab/keycloakify/blob/729503fe31a155a823f46dd66ad4ff34ca274e0a/tsconfig.json#L14"> <a href="https://github.com/keycloakify/keycloakify/blob/729503fe31a155a823f46dd66ad4ff34ca274e0a/tsconfig.json#L14">
<img src="https://camo.githubusercontent.com/0f9fcc0ac1b8617ad4989364f60f78b2d6b32985ad6a508f215f14d8f897b8d3/68747470733a2f2f62616467656e2e6e65742f62616467652f547970655363726970742f7374726963742532302546302539462539322541412f626c7565"> <img src="https://camo.githubusercontent.com/0f9fcc0ac1b8617ad4989364f60f78b2d6b32985ad6a508f215f14d8f897b8d3/68747470733a2f2f62616467656e2e6e65742f62616467652f547970655363726970742f7374726963742532302546302539462539322541412f626c7565">
</a> </a>
<a href="https://github.com/thomasdarimont/awesome-keycloak"> <a href="https://github.com/thomasdarimont/awesome-keycloak">
@ -25,6 +25,8 @@
- -
<a href="https://docs.keycloakify.dev">Documentation</a> <a href="https://docs.keycloakify.dev">Documentation</a>
- -
<a href="https://storybook.keycloakify.dev/storybook">Storybook</a>
-
<a href="https://github.com/codegouvfr/keycloakify-starter">Starter project</a> <a href="https://github.com/codegouvfr/keycloakify-starter">Starter project</a>
</p> </p>
</p> </p>
@ -34,15 +36,23 @@
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png"> <img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
</p> </p>
> 🗣 V6 have been released 🎉 > 🗣 V7 have been released 🎉
> [It features major improvements](https://github.com/InseeFrLab/keycloakify#600). > [It features major improvements](https://github.com/keycloakify/keycloakify#70-).
> Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6). > Checkout [the migration guide](https://docs.keycloakify.dev/migration-guides/v6-greater-than-v7).
# Changelog highlights # Changelog highlights
## 7.0 🍾
- Account theme support 🚀
- It's much easier to customize pages at the CSS level, you can now see in the browser dev tool the customizable classes.
- New interactive CLI tool `npx eject-keycloak-page`, that enables to select the page you want to customize at the component level.
- There is [a Storybook](https://storybook.keycloakify.dev)
- [Remember me is fixed](https://github.com/keycloakify/keycloakify/pull/272)
## 6.13 ## 6.13
- Build work behind corporate proxies, [see issue](https://github.com/InseeFrLab/keycloakify/issues/257). - Build work behind corporate proxies, [see issue](https://github.com/keycloakify/keycloakify/issues/257).
## 6.12 ## 6.12
@ -55,13 +65,13 @@ Massive improvement in the developer experience:
## 6.11.4 ## 6.11.4
- You no longer need to have Maven installed to build the theme. Thanks to @lordvlad, [see PR](https://github.com/InseeFrLab/keycloakify/pull/239). - You no longer need to have Maven installed to build the theme. Thanks to @lordvlad, [see PR](https://github.com/keycloakify/keycloakify/pull/239).
- Feature new build options: [`bundler`](https://docs.keycloakify.dev/build-options#keycloakify.bundler), [`groupId`](https://docs.keycloakify.dev/build-options#keycloakify.groupid), [`artifactId`](https://docs.keycloakify.dev/build-options#keycloakify.artifactid), [`version`](https://docs.keycloakify.dev/build-options#version). - Feature new build options: [`bundler`](https://docs.keycloakify.dev/build-options#keycloakify.bundler), [`groupId`](https://docs.keycloakify.dev/build-options#keycloakify.groupid), [`artifactId`](https://docs.keycloakify.dev/build-options#keycloakify.artifactid), [`version`](https://docs.keycloakify.dev/build-options#version).
Theses options can be user to customize the output name of the .jar. You can use environnement variables to overrides the values read in the package.json. Thanks to @lordvlad. Theses options can be user to customize the output name of the .jar. You can use environnement variables to overrides the values read in the package.json. Thanks to @lordvlad.
## 6.10.0 ## 6.10.0
- Widows compat (thanks to @lordvlad, [see PR](https://github.com/InseeFrLab/keycloakify/pull/226)). WSL is no longer required 🎉 - Widows compat (thanks to @lordvlad, [see PR](https://github.com/keycloakify/keycloakify/pull/226)). WSL is no longer required 🎉
## 6.8.4 ## 6.8.4
@ -71,19 +81,19 @@ Massive improvement in the developer experience:
- It is now possible to pass a custom `<Template />` component as a prop to `<KcApp />` and every - It is now possible to pass a custom `<Template />` component as a prop to `<KcApp />` and every
individual page (`<Login />`, `<RegisterUserProfile />`, ...) it enables to customize only the header and footer for individual page (`<Login />`, `<RegisterUserProfile />`, ...) it enables to customize only the header and footer for
example without having to switch to a full-component level customization. [See issue](https://github.com/InseeFrLab/keycloakify/issues/191). example without having to switch to a full-component level customization. [See issue](https://github.com/keycloakify/keycloakify/issues/191).
## 6.7.0 ## 6.7.0
- Add support for `webauthn-authenticate.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/185). - Add support for `webauthn-authenticate.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/keycloakify/keycloakify/pull/185).
## 6.6.0 ## 6.6.0
- Add support for `login-password.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/184). - Add support for `login-password.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/keycloakify/keycloakify/pull/184).
## 6.5.0 ## 6.5.0
- Add support for `login-username.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/183). - Add support for `login-username.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/keycloakify/keycloakify/pull/183).
## 6.4.0 ## 6.4.0
@ -102,11 +112,11 @@ Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6)
## 5.8.0 ## 5.8.0
- [React.lazy()](https://reactjs.org/docs/code-splitting.html#reactlazy) support 🎉. [#141](https://github.com/InseeFrLab/keycloakify/issues/141) - [React.lazy()](https://reactjs.org/docs/code-splitting.html#reactlazy) support 🎉. [#141](https://github.com/keycloakify/keycloakify/issues/141)
## 5.7.0 ## 5.7.0
- Feat `logout-confirm.ftl`. [PR](https://github.com/InseeFrLab/keycloakify/pull/120) - Feat `logout-confirm.ftl`. [PR](https://github.com/keycloakify/keycloakify/pull/120)
## 5.6.4 ## 5.6.4
@ -114,7 +124,7 @@ Fix `login-verify-email.ftl` page. [Before](https://user-images.githubuserconten
## v5.6.0 ## v5.6.0
Add support for `login-config-totp.ftl` page [#127](https://github.com/InseeFrLab/keycloakify/pull/127). Add support for `login-config-totp.ftl` page [#127](https://github.com/keycloakify/keycloakify/pull/127).
## v5.3.0 ## v5.3.0
@ -129,7 +139,7 @@ Import of terms and services have changed. [See example](https://github.com/garr
## v4.10.0 ## v4.10.0
Add `login-idp-link-email.ftl` page [See PR](https://github.com/InseeFrLab/keycloakify/pull/92). Add `login-idp-link-email.ftl` page [See PR](https://github.com/keycloakify/keycloakify/pull/92).
## v4.8.0 ## v4.8.0
@ -142,7 +152,7 @@ Add `login-idp-link-email.ftl` page [See PR](https://github.com/InseeFrLab/keycl
## v4.7.2 ## v4.7.2
> WARNING: This is broken. > WARNING: This is broken.
> Testing with local Keycloak container working with M1 Mac. Thanks to [@eduardosanzb](https://github.com/InseeFrLab/keycloakify/issues/43#issuecomment-975699658). > Testing with local Keycloak container working with M1 Mac. Thanks to [@eduardosanzb](https://github.com/keycloakify/keycloakify/issues/43#issuecomment-975699658).
> Be aware: When running M1s you are testing with Keycloak v15 else the local container spun will be a Keycloak v16.1.0. > Be aware: When running M1s you are testing with Keycloak v15 else the local container spun will be a Keycloak v16.1.0.
## v4.7.0 ## v4.7.0
@ -176,12 +186,12 @@ Change [this](https://github.com/garronej/keycloakify-demo-app/blob/df664c13c77c
No breaking changes except that `@emotion/react`, [`tss-react`](https://www.npmjs.com/package/tss-react) and [`powerhooks`](https://www.npmjs.com/package/powerhooks) are now `peerDependencies` instead of being just dependencies. No breaking changes except that `@emotion/react`, [`tss-react`](https://www.npmjs.com/package/tss-react) and [`powerhooks`](https://www.npmjs.com/package/powerhooks) are now `peerDependencies` instead of being just dependencies.
It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and
[when passing params from the app to the login page](https://github.com/InseeFrLab/keycloakify#implement-context-persistence-optional). [when passing params from the app to the login page](https://github.com/keycloakify/keycloakify#implement-context-persistence-optional).
## v2.5 ## v2.5
- Feature [Use advanced message](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/i18n/useKcMessage.tsx#L53-L66) - Feature [Use advanced message](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/i18n/useKcMessage.tsx#L53-L66)
and [`messagesPerFields`](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/getKcContext/KcContextBase.ts#L70-L75) (implementation [here](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/generateFtl/common.ftl#L130-L189)) and [`messagesPerFields`](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/getKcContext/KcContextBase.ts#L70-L75) (implementation [here](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/generateFtl/common.ftl#L130-L189))
- Test container now uses Keycloak version `15.0.2`. - Test container now uses Keycloak version `15.0.2`.
## v2 ## v2

View File

@ -1,10 +1,10 @@
{ {
"name": "keycloakify", "name": "keycloakify",
"version": "7.0.0-rc.16", "version": "7.2.2",
"description": "Create Keycloak themes using React", "description": "Create Keycloak themes using React",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/inseefrlab/keycloakify.git" "url": "git://github.com/keycloakify/keycloakify.git"
}, },
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -43,6 +43,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && ( {realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
<li> <li>
<div className="kc-dropdown" id="kc-locale-dropdown"> <div className="kc-dropdown" id="kc-locale-dropdown">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" id="kc-current-locale-link"> <a href="#" id="kc-current-locale-link">
{labelBySupportedLanguageTag[currentLanguageTag]} {labelBySupportedLanguageTag[currentLanguageTag]}
</a> </a>

View File

@ -64,6 +64,7 @@ export declare namespace KcContext {
password: { password: {
passwordSet: boolean; passwordSet: boolean;
}; };
stateChecker: string;
}; };
export type Account = Common & { export type Account = Common & {

View File

@ -154,7 +154,8 @@ export const kcContextMocks: KcContext[] = [
"pageId": "password.ftl", "pageId": "password.ftl",
"password": { "password": {
"passwordSet": true "passwordSet": true
} },
"stateChecker": "state checker"
}), }),
id<KcContext.Account>({ id<KcContext.Account>({
...kcContextCommonMock, ...kcContextCommonMock,

View File

@ -15,7 +15,7 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
} }
}); });
const { url, password, account } = kcContext; const { url, password, account, stateChecker } = kcContext;
const { msg } = i18n; const { msg } = i18n;
@ -55,7 +55,7 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
</div> </div>
)} )}
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}" /> <input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
<div className="form-group"> <div className="form-group">
<div className="col-sm-2 col-md-2"> <div className="col-sm-2 col-md-2">

View File

@ -10,15 +10,17 @@ import { getLogger } from "./tools/logger";
export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) { export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) {
const { keycloakVersion, destDirPath, isSilent } = params; const { keycloakVersion, destDirPath, isSilent } = params;
for (const ext of ["", "-community"]) { await Promise.all(
await downloadAndUnzip({ ["", "-community"].map(ext =>
"destDirPath": destDirPath, downloadAndUnzip({
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`, "destDirPath": destDirPath,
"pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`, "url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
"cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache"), "pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
isSilent "cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache"),
}); isSilent
} })
)
);
} }
if (require.main === module) { if (require.main === module) {

View File

@ -16,6 +16,7 @@ import { existsSync } from "fs";
import { join as pathJoin, relative as pathRelative } from "path"; import { join as pathJoin, relative as pathRelative } from "path";
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase"; import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
import { assert, Equals } from "tsafe/assert"; import { assert, Equals } from "tsafe/assert";
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
(async () => { (async () => {
const projectRootDir = getProjectRoot(); const projectRootDir = getProjectRoot();
@ -50,7 +51,13 @@ import { assert, Equals } from "tsafe/assert";
const pageBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(/ftl$/, "tsx"); const pageBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(/ftl$/, "tsx");
const targetFilePath = pathJoin(process.cwd(), "src", "keycloak-theme", themeType, "pages", pageBasename); const { themeSrcDirPath } = getThemeSrcDirPath();
if (themeSrcDirPath === undefined) {
throw new Error("Couldn't locate your theme sources");
}
const targetFilePath = pathJoin(themeSrcDirPath, themeType, "pages", pageBasename);
if (existsSync(targetFilePath)) { if (existsSync(targetFilePath)) {
console.log(`${pageId} is already ejected, ${pathRelative(process.cwd(), targetFilePath)} already exists`); console.log(`${pageId} is already ejected, ${pathRelative(process.cwd(), targetFilePath)} already exists`);

View File

@ -0,0 +1,33 @@
import { join as pathJoin } from "path";
import * as fs from "fs";
import { crawl } from "./tools/crawl";
import { exclude } from "tsafe/exclude";
const reactProjectDirPath = process.cwd();
const themeSrcDirBasename = "keycloak-theme";
export function getThemeSrcDirPath() {
const srcDirPath = pathJoin(reactProjectDirPath, "src");
const themeSrcDirPath: string | undefined = crawl(srcDirPath)
.map(fileRelativePath => {
const split = fileRelativePath.split(themeSrcDirBasename);
if (split.length !== 2) {
return undefined;
}
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
})
.filter(exclude(undefined))[0];
if (themeSrcDirBasename === undefined) {
if (fs.existsSync(pathJoin(srcDirPath, "login")) || fs.existsSync(pathJoin(srcDirPath, "account"))) {
return { "themeSrcDirPath": srcDirPath };
}
return { "themeSrcDirPath": undefined };
}
return { themeSrcDirPath };
}

View File

@ -1,27 +1,43 @@
#!/usr/bin/env node #!/usr/bin/env node
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme"; import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
import { keycloakThemeEmailDirPath } from "./keycloakify";
import { join as pathJoin, relative as pathRelative } from "path"; import { join as pathJoin, relative as pathRelative } from "path";
import { transformCodebase } from "./tools/transformCodebase"; import { transformCodebase } from "./tools/transformCodebase";
import { promptKeycloakVersion } from "./promptKeycloakVersion"; import { promptKeycloakVersion } from "./promptKeycloakVersion";
import * as fs from "fs"; import * as fs from "fs";
import { getCliOptions } from "./tools/cliOptions"; import { getCliOptions } from "./tools/cliOptions";
import { getLogger } from "./tools/logger"; import { getLogger } from "./tools/logger";
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
(async () => { export function getEmailThemeSrcDirPath() {
const { themeSrcDirPath } = getThemeSrcDirPath();
const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email");
return { emailThemeSrcDirPath };
}
async function main() {
const { isSilent } = getCliOptions(process.argv.slice(2)); const { isSilent } = getCliOptions(process.argv.slice(2));
const logger = getLogger({ isSilent }); const logger = getLogger({ isSilent });
if (fs.existsSync(keycloakThemeEmailDirPath)) { const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath();
logger.warn(`There is already a ${pathRelative(process.cwd(), keycloakThemeEmailDirPath)} directory in your project. Aborting.`);
if (emailThemeSrcDirPath === undefined) {
logger.warn("Couldn't locate your theme source directory");
process.exit(-1);
}
if (fs.existsSync(emailThemeSrcDirPath)) {
logger.warn(`There is already a ${pathRelative(process.cwd(), emailThemeSrcDirPath)} directory in your project. Aborting.`);
process.exit(-1); process.exit(-1);
} }
const { keycloakVersion } = await promptKeycloakVersion(); const { keycloakVersion } = await promptKeycloakVersion();
const builtinKeycloakThemeTmpDirPath = pathJoin(keycloakThemeEmailDirPath, "..", "tmp_xIdP3_builtin_keycloak_theme"); const builtinKeycloakThemeTmpDirPath = pathJoin(emailThemeSrcDirPath, "..", "tmp_xIdP3_builtin_keycloak_theme");
await downloadBuiltinKeycloakTheme({ await downloadBuiltinKeycloakTheme({
keycloakVersion, keycloakVersion,
@ -31,18 +47,20 @@ import { getLogger } from "./tools/logger";
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"), "srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"),
"destDirPath": keycloakThemeEmailDirPath "destDirPath": emailThemeSrcDirPath
}); });
{ {
const themePropertyFilePath = pathJoin(keycloakThemeEmailDirPath, "theme.properties"); const themePropertyFilePath = pathJoin(emailThemeSrcDirPath, "theme.properties");
fs.writeFileSync(themePropertyFilePath, Buffer.from(`parent=base\n${fs.readFileSync(themePropertyFilePath).toString("utf8")}`, "utf8")); fs.writeFileSync(themePropertyFilePath, Buffer.from(`parent=base\n${fs.readFileSync(themePropertyFilePath).toString("utf8")}`, "utf8"));
} }
logger.log( logger.log(`${pathRelative(process.cwd(), emailThemeSrcDirPath)} ready to be customized, feel free to remove every file you do not customize`);
`${pathRelative(process.cwd(), keycloakThemeEmailDirPath)} ready to be customized, feel free to remove every file you do not customize`
);
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true }); fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
})(); }
if (require.main === module) {
main();
}

View File

@ -22,6 +22,7 @@ type ParsedPackageJson = {
artifactId?: string; artifactId?: string;
groupId?: string; groupId?: string;
bundler?: Bundler; bundler?: Bundler;
keycloakVersionDefaultAssets?: string;
}; };
}; };
@ -38,7 +39,8 @@ const zParsedPackageJson = z.object({
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(), "areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(),
"artifactId": z.string().optional(), "artifactId": z.string().optional(),
"groupId": z.string().optional(), "groupId": z.string().optional(),
"bundler": z.enum(bundlers).optional() "bundler": z.enum(bundlers).optional(),
"keycloakVersionDefaultAssets": z.string().optional()
}) })
.optional() .optional()
}); });
@ -59,6 +61,7 @@ export namespace BuildOptions {
groupId: string; groupId: string;
artifactId: string; artifactId: string;
bundler: Bundler; bundler: Bundler;
keycloakVersionDefaultAssets: string;
}; };
export type Standalone = Common & { export type Standalone = Common & {
@ -125,7 +128,8 @@ export function readBuildOptions(params: {
const common: BuildOptions.Common = (() => { const common: BuildOptions.Common = (() => {
const { name, keycloakify = {}, version, homepage } = parsedPackageJson; const { name, keycloakify = {}, version, homepage } = parsedPackageJson;
const { extraPages, extraLoginPages, extraAccountPages, extraThemeProperties, groupId, artifactId, bundler } = keycloakify ?? {}; const { extraPages, extraLoginPages, extraAccountPages, extraThemeProperties, groupId, artifactId, bundler, keycloakVersionDefaultAssets } =
keycloakify ?? {};
const themeName = name const themeName = name
.replace(/^@(.*)/, "$1") .replace(/^@(.*)/, "$1")
@ -167,7 +171,8 @@ export function readBuildOptions(params: {
"extraLoginPages": [...(extraPages ?? []), ...(extraLoginPages ?? [])], "extraLoginPages": [...(extraPages ?? []), ...(extraLoginPages ?? [])],
extraAccountPages, extraAccountPages,
extraThemeProperties, extraThemeProperties,
isSilent isSilent,
"keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3"
}; };
})(); })();

View File

@ -164,9 +164,9 @@
key == "updateProfileCtx" && key == "updateProfileCtx" &&
are_same_path(path, []) are_same_path(path, [])
) || ( ) || (
<#-- https://github.com/InseeFrLab/keycloakify/pull/65#issuecomment-991896344 (reports with saml-post-form.ftl) --> <#-- https://github.com/keycloakify/keycloakify/pull/65#issuecomment-991896344 (reports with saml-post-form.ftl) -->
<#-- https://github.com/InseeFrLab/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) --> <#-- https://github.com/keycloakify/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
<#-- https://github.com/InseeFrLab/keycloakify/issues/109#issuecomment-1134610163 --> <#-- https://github.com/keycloakify/keycloakify/issues/109#issuecomment-1134610163 -->
key == "loginAction" && key == "loginAction" &&
are_same_path(path, ["url"]) && are_same_path(path, ["url"]) &&
["saml-post-form.ftl", "error.ftl", "info.ftl"]?seq_contains(pageId) && ["saml-post-form.ftl", "error.ftl", "info.ftl"]?seq_contains(pageId) &&

View File

@ -10,7 +10,6 @@ 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 { Reflect } from "tsafe/Reflect"; import { Reflect } from "tsafe/Reflect";
import { getLogger } from "../tools/logger";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
@ -56,13 +55,11 @@ export namespace BuildOptionsLike {
export async function generateKeycloakThemeResources(params: { export async function generateKeycloakThemeResources(params: {
reactAppBuildDirPath: string; reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
keycloakThemeEmailDirPath: string; emailThemeSrcDirPath: string | undefined;
keycloakVersion: string; keycloakVersion: string;
buildOptions: BuildOptionsLike; buildOptions: BuildOptionsLike;
}): Promise<{ doBundlesEmailTemplate: boolean }> { }): Promise<{ doBundlesEmailTemplate: boolean }> {
const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, keycloakThemeEmailDirPath, keycloakVersion, buildOptions } = params; const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, emailThemeSrcDirPath, keycloakVersion, buildOptions } = params;
const logger = getLogger({ isSilent: buildOptions.isSilent });
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);
@ -228,13 +225,7 @@ export async function generateKeycloakThemeResources(params: {
let doBundlesEmailTemplate: boolean; let doBundlesEmailTemplate: boolean;
email: { email: {
if (!fs.existsSync(keycloakThemeEmailDirPath)) { if (emailThemeSrcDirPath === undefined) {
logger.log(
[
`Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
`To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`
].join("\n")
);
doBundlesEmailTemplate = false; doBundlesEmailTemplate = false;
break email; break email;
} }
@ -242,7 +233,7 @@ export async function generateKeycloakThemeResources(params: {
doBundlesEmailTemplate = true; doBundlesEmailTemplate = true;
transformCodebase({ transformCodebase({
"srcDirPath": keycloakThemeEmailDirPath, "srcDirPath": emailThemeSrcDirPath,
"destDirPath": getThemeDirPath("email") "destDirPath": getThemeDirPath("email")
}); });
} }

View File

@ -9,12 +9,12 @@ import { getLogger } from "../tools/logger";
import { getCliOptions } from "../tools/cliOptions"; import { getCliOptions } from "../tools/cliOptions";
import jar from "../tools/jar"; import jar from "../tools/jar";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { Equals } from "tsafe"; import { Equals } from "tsafe";
import { getEmailThemeSrcDirPath } from "../initialize-email-theme";
const reactProjectDirPath = process.cwd(); const reactProjectDirPath = process.cwd();
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak"); export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
export const keycloakThemeEmailDirPath = pathJoin(reactProjectDirPath, "src", "keycloak-theme", "email");
export async function main() { export async function main() {
const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2)); const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2));
@ -38,13 +38,18 @@ export async function main() {
const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({ const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath,
keycloakThemeEmailDirPath, "emailThemeSrcDirPath": (() => {
const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath();
if (emailThemeSrcDirPath === undefined || !fs.existsSync(emailThemeSrcDirPath)) {
return;
}
return emailThemeSrcDirPath;
})(),
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"), "reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
buildOptions, buildOptions,
//We have to leave it at that otherwise we break our default theme. "keycloakVersion": buildOptions.keycloakVersionDefaultAssets
//Problem is that we can`t guarantee that the the old resources
//will still be available on the newer keycloak version.
"keycloakVersion": "11.0.3"
}); });
const { jarFilePath } = generateJavaStackFiles({ const { jarFilePath } = generateJavaStackFiles({
@ -143,6 +148,8 @@ export async function main() {
``, ``,
`- Go to 👉 https://www.keycloak.org/app/ 👈 Click "Save" then "Sign in". You should see your login page`, `- Go to 👉 https://www.keycloak.org/app/ 👈 Click "Save" then "Sign in". You should see your login page`,
`- Got to 👉 http://localhost:8080/realms/myrealm/account 👈 to see your account theme`, `- Got to 👉 http://localhost:8080/realms/myrealm/account 👈 to see your account theme`,
``,
`Video tutorial: https://youtu.be/WMyGZNHQkjU`,
`` ``
].join("\n") ].join("\n")
); );

View File

@ -101,7 +101,8 @@ export declare namespace KcContext {
registrationDisabled: boolean; registrationDisabled: boolean;
login: { login: {
username?: string; username?: string;
rememberMe?: boolean; rememberMe?: string;
password?: string;
}; };
usernameEditDisabled: boolean; usernameEditDisabled: boolean;
social: { social: {
@ -219,7 +220,7 @@ export declare namespace KcContext {
registrationDisabled: boolean; registrationDisabled: boolean;
login: { login: {
username?: string; username?: string;
rememberMe?: boolean; rememberMe?: string;
}; };
usernameHidden?: boolean; usernameHidden?: boolean;
social: { social: {

View File

@ -260,9 +260,7 @@ export const kcContextMocks: KcContext[] = [
"displayInfo": true "displayInfo": true
}, },
"usernameEditDisabled": false, "usernameEditDisabled": false,
"login": { "login": {},
"rememberMe": false
},
"registrationDisabled": false "registrationDisabled": false
}), }),
...(() => { ...(() => {
@ -376,9 +374,7 @@ export const kcContextMocks: KcContext[] = [
"displayInfo": true "displayInfo": true
}, },
"usernameHidden": false, "usernameHidden": false,
"login": { "login": {},
"rememberMe": false
},
"registrationDisabled": false "registrationDisabled": false
}), }),
id<KcContext.LoginPassword>({ id<KcContext.LoginPassword>({

View File

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx"; import { clsx } from "keycloakify/tools/clsx";
import { UserProfileFormFields } from "keycloakify/login/pages/shared/UserProfileCommons"; import { UserProfileFormFields } from "keycloakify/login/pages/shared/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps"; import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext"; import type { KcContext } from "../kcContext";

View File

@ -124,7 +124,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
id="rememberMe" id="rememberMe"
name="rememberMe" name="rememberMe"
type="checkbox" type="checkbox"
{...(login.rememberMe {...(login.rememberMe === "on"
? { ? {
"checked": true "checked": true
} }

View File

@ -109,7 +109,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
id="rememberMe" id="rememberMe"
name="rememberMe" name="rememberMe"
type="checkbox" type="checkbox"
{...(login.rememberMe {...(login.rememberMe === "on"
? { ? {
"checked": true "checked": true
} }

View File

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx"; import { clsx } from "keycloakify/tools/clsx";
import { UserProfileFormFields } from "./shared/UserProfileCommons"; import { UserProfileFormFields } from "./shared/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps"; import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext"; import type { KcContext } from "../kcContext";

View File

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx"; import { clsx } from "keycloakify/tools/clsx";
import { UserProfileFormFields } from "keycloakify/login/pages/shared/UserProfileCommons"; import { UserProfileFormFields } from "keycloakify/login/pages/shared/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps"; import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName"; import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext"; import type { KcContext } from "../kcContext";

View File

@ -7,7 +7,7 @@ setupSampleReactProject();
generateKeycloakThemeResources({ generateKeycloakThemeResources({
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"), "reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"), "keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
"keycloakThemeEmailDirPath": pathJoin(sampleReactProjectDirPath, "keycloak_email"), "emailThemeSrcDirPath": undefined,
"keycloakVersion": "11.0.3", "keycloakVersion": "11.0.3",
"buildOptions": { "buildOptions": {
"themeName": "keycloakify-demo-app", "themeName": "keycloakify-demo-app",

View File

@ -6,7 +6,7 @@ export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_reac
export async function setupSampleReactProject() { export async function setupSampleReactProject() {
await downloadAndUnzip({ await downloadAndUnzip({
"url": "https://github.com/InseeFrLab/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip", "url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
"destDirPath": sampleReactProjectDirPath, "destDirPath": sampleReactProjectDirPath,
"cacheDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak", ".cache"), "cacheDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak", ".cache"),
"isSilent": false "isSilent": false