Compare commits
47 Commits
v11.0.1
...
angular_in
Author | SHA1 | Date | |
---|---|---|---|
daf95b3dbb | |||
e3bd7f3bc5 | |||
e14f187fc0 | |||
da495b90ae | |||
8d9b80f549 | |||
2e9da33622 | |||
6f416ad335 | |||
4e982ee898 | |||
bcb514ae9c | |||
cfdad8d71d | |||
39ad1eb8d1 | |||
3d1d2e316b | |||
dd217e8a46 | |||
1339a96ea4 | |||
616e834c90 | |||
80eaa77acc | |||
ce3135c83b | |||
09abc73068 | |||
037d623550 | |||
8c8d2fd6a8 | |||
153a99d63f | |||
939e3ca7ea | |||
a0dc7eeb7c | |||
c21d072231 | |||
2e10ec8073 | |||
1177d6770c | |||
d492a393fe | |||
77952337c5 | |||
6716fcb881 | |||
302fe8d7cd | |||
2ea5e34e81 | |||
d7103b1ad9 | |||
9f8a36fe93 | |||
47ca811878 | |||
8cacb21f1b | |||
a0c95207cf | |||
da3023cf5e | |||
94779c3476 | |||
802a6ab5ec | |||
04307c8226 | |||
ff6b91b801 | |||
c8ca598465 | |||
9444b897ee | |||
3d1951b72c | |||
acc27ae448 | |||
e6993214ff | |||
2f02a4379c |
.all-contributorsrc
.github/workflows
CONTRIBUTING.mdREADME.mdpackage.jsonscripts
src
account
bin
eject-page
keycloakify
buildJars
generateFtl
generateResources
shared
start-keycloak
login
vite-plugin
stories/login/pages
yarn.lock@ -269,6 +269,27 @@
|
|||||||
"test",
|
"test",
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "kathari00",
|
||||||
|
"name": "Katharina Eiserfey",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/42547712?v=4",
|
||||||
|
"profile": "https://github.com/kathari00",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"test",
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "luca-peruzzo",
|
||||||
|
"name": "Luca Peruzzo",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/69015314?v=4",
|
||||||
|
"profile": "https://github.com/luca-peruzzo",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -112,7 +112,7 @@ jobs:
|
|||||||
registry-url: https://registry.npmjs.org/
|
registry-url: https://registry.npmjs.org/
|
||||||
- uses: bahmutov/npm-install@v1
|
- uses: bahmutov/npm-install@v1
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npx -y -p denoify@1.6.12 enable_short_npm_import_path
|
- run: npx -y -p denoify@1.6.13 enable_short_npm_import_path
|
||||||
env:
|
env:
|
||||||
DRY_RUN: "0"
|
DRY_RUN: "0"
|
||||||
- uses: garronej/ts-ci@v2.1.2
|
- uses: garronej/ts-ci@v2.1.2
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
Looking to contribute? Thank you! PR are more than welcome.
|
Looking to contribute? Thank you! PR are more than welcome.
|
||||||
|
|
||||||
Please refers to [this documentation page](https://docs.keycloakify.dev/contributing) that will help you get started.
|
Please refers to [this documentation page](https://docs.keycloakify.dev/faq-and-help/contributing) that will help you get started.
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
<img width="400" src="https://github.com/user-attachments/assets/6bf3bef9-00b0-4460-97b9-0d2da8500798">
|
<img width="400" src="https://github.com/user-attachments/assets/6bf3bef9-00b0-4460-97b9-0d2da8500798">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Keycloakify is fully compatible with Keycloak from version 11 to 25...[and beyond](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)
|
Keycloakify is fully compatible with Keycloak from version 11 to 26...[and beyond](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
@ -134,6 +134,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/oes-rioniz/"><img src="https://avatars.githubusercontent.com/u/5172296?v=4?s=100" width="100px;" alt="Omid"/><br /><sub><b>Omid</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=uchar" title="Tests">⚠️</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=uchar" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/oes-rioniz/"><img src="https://avatars.githubusercontent.com/u/5172296?v=4?s=100" width="100px;" alt="Omid"/><br /><sub><b>Omid</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=uchar" title="Tests">⚠️</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=uchar" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kathari00"><img src="https://avatars.githubusercontent.com/u/42547712?v=4?s=100" width="100px;" alt="Katharina Eiserfey"/><br /><sub><b>Katharina Eiserfey</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=kathari00" title="Code">💻</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=kathari00" title="Tests">⚠️</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=kathari00" title="Documentation">📖</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/luca-peruzzo"><img src="https://avatars.githubusercontent.com/u/69015314?v=4?s=100" width="100px;" alt="Luca Peruzzo"/><br /><sub><b>Luca Peruzzo</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=luca-peruzzo" title="Code">💻</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=luca-peruzzo" title="Tests">⚠️</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
28
package.json
28
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "11.0.1",
|
"version": "11.2.10",
|
||||||
"description": "Framework to create custom Keycloak UIs",
|
"description": "Framework to create custom Keycloak UIs",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -62,33 +62,42 @@
|
|||||||
],
|
],
|
||||||
"homepage": "https://www.keycloakify.dev",
|
"homepage": "https://www.keycloakify.dev",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tsafe": "^1.6.6"
|
"tsafe": "^1.7.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.24.5",
|
"@babel/core": "^7.24.5",
|
||||||
"@babel/generator": "^7.24.5",
|
"@babel/generator": "^7.24.5",
|
||||||
"@babel/parser": "^7.24.5",
|
"@babel/parser": "^7.24.5",
|
||||||
|
"@babel/preset-env": "7.24.8",
|
||||||
"@babel/types": "^7.24.5",
|
"@babel/types": "^7.24.5",
|
||||||
"@emotion/react": "^11.11.4",
|
"@emotion/react": "^11.11.4",
|
||||||
|
"@keycloakify/angular": "^0.0.1-rc.19",
|
||||||
"@octokit/rest": "^20.1.1",
|
"@octokit/rest": "^20.1.1",
|
||||||
"@storybook/addon-a11y": "^6.5.16",
|
"@storybook/addon-a11y": "^6.5.16",
|
||||||
"@storybook/builder-webpack5": "^6.5.13",
|
"@storybook/builder-webpack5": "^6.5.13",
|
||||||
"@storybook/manager-webpack5": "^6.5.13",
|
"@storybook/manager-webpack5": "^6.5.13",
|
||||||
"@storybook/react": "^6.5.13",
|
"@storybook/react": "^6.5.13",
|
||||||
"eslint-plugin-storybook": "^0.6.7",
|
|
||||||
"@types/babel__generator": "^7.6.4",
|
"@types/babel__generator": "^7.6.4",
|
||||||
|
"@types/dompurify": "^2.0.0",
|
||||||
"@types/make-fetch-happen": "^10.0.1",
|
"@types/make-fetch-happen": "^10.0.1",
|
||||||
"@types/minimist": "^1.2.2",
|
"@types/minimist": "^1.2.2",
|
||||||
"@types/node": "^18.15.3",
|
"@types/node": "^18.15.3",
|
||||||
|
"@types/properties-parser": "^0.3.3",
|
||||||
"@types/react": "^18.0.35",
|
"@types/react": "^18.0.35",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@types/yauzl": "^2.10.3",
|
"@types/yauzl": "^2.10.3",
|
||||||
"@vercel/ncc": "^0.38.1",
|
"@vercel/ncc": "^0.38.1",
|
||||||
|
"babel-loader": "9.1.3",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"cheerio": "1.0.0-rc.12",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"chokidar-cli": "^3.0.0",
|
"chokidar-cli": "^3.0.0",
|
||||||
"cli-select": "^1.1.2",
|
"cli-select": "^1.1.2",
|
||||||
|
"dompurify": "^3.1.6",
|
||||||
|
"eslint-plugin-storybook": "^0.6.7",
|
||||||
|
"evt": "^2.5.7",
|
||||||
|
"html-entities": "^2.5.2",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
|
"isomorphic-dompurify": "^2.15.0",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"magic-string": "^0.30.7",
|
"magic-string": "^0.30.7",
|
||||||
"make-fetch-happen": "^11.0.3",
|
"make-fetch-happen": "^11.0.3",
|
||||||
@ -103,20 +112,13 @@
|
|||||||
"termost": "^v0.12.1",
|
"termost": "^v0.12.1",
|
||||||
"tsc-alias": "^1.8.10",
|
"tsc-alias": "^1.8.10",
|
||||||
"tss-react": "^4.9.10",
|
"tss-react": "^4.9.10",
|
||||||
|
"tsx": "^4.15.5",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^5.2.11",
|
"vite": "^5.2.11",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
"yauzl": "^2.10.0",
|
|
||||||
"zod": "^3.17.10",
|
|
||||||
"evt": "^2.5.7",
|
|
||||||
"tsx": "^4.15.5",
|
|
||||||
"html-entities": "^2.5.2",
|
|
||||||
"isomorphic-dompurify": "^2.15.0",
|
|
||||||
"dompurify": "^3.1.6",
|
|
||||||
"@types/dompurify": "^2.0.0",
|
|
||||||
"webpack": "5.93.0",
|
"webpack": "5.93.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"babel-loader": "9.1.3",
|
"yauzl": "^2.10.0",
|
||||||
"@babel/preset-env": "7.24.8"
|
"zod": "^3.17.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,9 +73,7 @@ export function vendorFrontendDependencies(params: { distDirPath: string }) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
run(
|
run(`npx webpack --config ${webpackConfigJsFilePath}`);
|
||||||
`npx webpack --config ${pathRelative(process.cwd(), webpackConfigJsFilePath)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.readdirSync(webpackOutputDirPath)
|
fs.readdirSync(webpackOutputDirPath)
|
||||||
.filter(fileBasename => !fileBasename.endsWith(".txt"))
|
.filter(fileBasename => !fileBasename.endsWith(".txt"))
|
||||||
|
@ -13,7 +13,7 @@ import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRoot
|
|||||||
import { deepAssign } from "../src/tools/deepAssign";
|
import { deepAssign } from "../src/tools/deepAssign";
|
||||||
import { THEME_TYPES } from "../src/bin/shared/constants";
|
import { THEME_TYPES } from "../src/bin/shared/constants";
|
||||||
import { transformCodebase } from "../src/bin/tools/transformCodebase";
|
import { transformCodebase } from "../src/bin/tools/transformCodebase";
|
||||||
const propertiesParser: any = require("properties-parser");
|
import propertiesParser from "properties-parser";
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
generateI18nMessages();
|
generateI18nMessages();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import type { ClassKey } from "keycloakify/account/lib/kcClsx";
|
||||||
|
|
||||||
export type TemplateProps<KcContext, I18n> = {
|
export type TemplateProps<KcContext, I18n> = {
|
||||||
kcContext: KcContext;
|
kcContext: KcContext;
|
||||||
@ -10,17 +11,4 @@ export type TemplateProps<KcContext, I18n> = {
|
|||||||
active: string;
|
active: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ClassKey =
|
export type { ClassKey };
|
||||||
| "kcHtmlClass"
|
|
||||||
| "kcBodyClass"
|
|
||||||
| "kcButtonClass"
|
|
||||||
| "kcButtonPrimaryClass"
|
|
||||||
| "kcButtonLargeClass"
|
|
||||||
| "kcButtonDefaultClass"
|
|
||||||
| "kcContentWrapperClass"
|
|
||||||
| "kcFormClass"
|
|
||||||
| "kcFormGroupClass"
|
|
||||||
| "kcInputWrapperClass"
|
|
||||||
| "kcLabelClass"
|
|
||||||
| "kcInputClass"
|
|
||||||
| "kcInputErrorMessageClass";
|
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
import { createGetKcClsx } from "keycloakify/lib/getKcClsx";
|
import { createGetKcClsx } from "keycloakify/lib/getKcClsx";
|
||||||
import type { ClassKey } from "keycloakify/account/TemplateProps";
|
|
||||||
|
export type ClassKey =
|
||||||
|
| "kcHtmlClass"
|
||||||
|
| "kcBodyClass"
|
||||||
|
| "kcButtonClass"
|
||||||
|
| "kcButtonPrimaryClass"
|
||||||
|
| "kcButtonLargeClass"
|
||||||
|
| "kcButtonDefaultClass"
|
||||||
|
| "kcContentWrapperClass"
|
||||||
|
| "kcFormClass"
|
||||||
|
| "kcFormGroupClass"
|
||||||
|
| "kcInputWrapperClass"
|
||||||
|
| "kcLabelClass"
|
||||||
|
| "kcInputClass"
|
||||||
|
| "kcInputErrorMessageClass";
|
||||||
|
|
||||||
export const { getKcClsx } = createGetKcClsx<ClassKey>({
|
export const { getKcClsx } = createGetKcClsx<ClassKey>({
|
||||||
defaultClasses: {
|
defaultClasses: {
|
||||||
@ -20,6 +34,4 @@ export const { getKcClsx } = createGetKcClsx<ClassKey>({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export type { ClassKey };
|
|
||||||
|
|
||||||
export type KcClsx = ReturnType<typeof getKcClsx>["kcClsx"];
|
export type KcClsx = ReturnType<typeof getKcClsx>["kcClsx"];
|
||||||
|
0
src/bin/eject-page/index.ts
Normal file
0
src/bin/eject-page/index.ts
Normal file
0
src/bin/eject-page/react.ts
Normal file
0
src/bin/eject-page/react.ts
Normal file
@ -197,7 +197,7 @@ export async function buildJar(params: {
|
|||||||
|
|
||||||
await new Promise<void>((resolve, reject) =>
|
await new Promise<void>((resolve, reject) =>
|
||||||
child_process.exec(
|
child_process.exec(
|
||||||
`mvn install -Dmaven.repo.local="${pathJoin(keycloakifyBuildCacheDirPath, ".m2")}"`,
|
`mvn clean install -Dmaven.repo.local="${pathJoin(keycloakifyBuildCacheDirPath, ".m2")}"`,
|
||||||
{ cwd: keycloakifyBuildCacheDirPath },
|
{ cwd: keycloakifyBuildCacheDirPath },
|
||||||
error => {
|
error => {
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
|
@ -52,9 +52,9 @@ export function getKeycloakVersionRangeForJar(params: {
|
|||||||
case "0.6":
|
case "0.6":
|
||||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||||
case null:
|
case null:
|
||||||
return undefined;
|
return "26-and-above" as const;
|
||||||
case "1.1.5":
|
case "1.1.5":
|
||||||
return "25-and-above" as const;
|
return "25" as const;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert<Equals<typeof keycloakAccountV1Version, never>>(false);
|
assert<Equals<typeof keycloakAccountV1Version, never>>(false);
|
||||||
@ -75,9 +75,9 @@ export function getKeycloakVersionRangeForJar(params: {
|
|||||||
}
|
}
|
||||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||||
case null:
|
case null:
|
||||||
return "21-and-below";
|
return "all-other-versions";
|
||||||
case "1.1.5":
|
case "1.1.5":
|
||||||
return "22-and-above";
|
return "22-to-25";
|
||||||
}
|
}
|
||||||
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(
|
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(
|
||||||
false
|
false
|
||||||
|
@ -166,7 +166,7 @@ function decodeHtmlEntities(htmlStr){
|
|||||||
areSamePath(path, []) &&
|
areSamePath(path, []) &&
|
||||||
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(xKeycloakify.pageId)
|
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(xKeycloakify.pageId)
|
||||||
) || (
|
) || (
|
||||||
["masterAdminClient", "delegateForUpdate", "defaultRole"]?seq_contains(key) &&
|
["masterAdminClient", "delegateForUpdate", "defaultRole", "smtpConfig"]?seq_contains(key) &&
|
||||||
areSamePath(path, ["realm"])
|
areSamePath(path, ["realm"])
|
||||||
) || (
|
) || (
|
||||||
xKeycloakify.pageId == "error.ftl" &&
|
xKeycloakify.pageId == "error.ftl" &&
|
||||||
@ -235,6 +235,9 @@ function decodeHtmlEntities(htmlStr){
|
|||||||
"identityFederationEnabled",
|
"identityFederationEnabled",
|
||||||
"userManagedAccessAllowed"
|
"userManagedAccessAllowed"
|
||||||
]?seq_contains(key)
|
]?seq_contains(key)
|
||||||
|
) || (
|
||||||
|
["flowContext", "session", "realm"]?seq_contains(key) &&
|
||||||
|
areSamePath(path, ["social"])
|
||||||
)
|
)
|
||||||
>
|
>
|
||||||
<#-- <#local outSeq += ["/*" + path?join(".") + "." + key + " excluded*/"]> -->
|
<#-- <#local outSeq += ["/*" + path?join(".") + "." + key + " excluded*/"]> -->
|
||||||
|
@ -1,16 +1,56 @@
|
|||||||
import type { BuildContext } from "../../shared/buildContext";
|
import type { BuildContext } from "../../shared/buildContext";
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import {
|
|
||||||
generateResourcesForMainTheme,
|
|
||||||
type BuildContextLike as BuildContextLike_generateResourcesForMainTheme
|
|
||||||
} from "./generateResourcesForMainTheme";
|
|
||||||
import { generateResourcesForThemeVariant } from "./generateResourcesForThemeVariant";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { rmSync } from "../../tools/fs.rmSync";
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
|
import {
|
||||||
|
join as pathJoin,
|
||||||
|
relative as pathRelative,
|
||||||
|
dirname as pathDirname,
|
||||||
|
extname as pathExtname,
|
||||||
|
sep as pathSep
|
||||||
|
} from "path";
|
||||||
|
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
||||||
|
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
||||||
|
import {
|
||||||
|
generateFtlFilesCodeFactory,
|
||||||
|
type BuildContextLike as BuildContextLike_kcContextExclusionsFtlCode
|
||||||
|
} from "../generateFtl";
|
||||||
|
import {
|
||||||
|
type ThemeType,
|
||||||
|
LOGIN_THEME_PAGE_IDS,
|
||||||
|
ACCOUNT_THEME_PAGE_IDS,
|
||||||
|
WELL_KNOWN_DIRECTORY_BASE_NAME
|
||||||
|
} from "../../shared/constants";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
import { readFieldNameUsage } from "./readFieldNameUsage";
|
||||||
|
import { readExtraPagesNames } from "./readExtraPageNames";
|
||||||
|
import {
|
||||||
|
generateMessageProperties,
|
||||||
|
type BuildContextLike as BuildContextLike_generateMessageProperties
|
||||||
|
} from "./generateMessageProperties";
|
||||||
|
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
|
||||||
|
import {
|
||||||
|
writeMetaInfKeycloakThemes,
|
||||||
|
type MetaInfKeycloakTheme
|
||||||
|
} from "../../shared/metaInfKeycloakThemes";
|
||||||
|
import { objectEntries } from "tsafe/objectEntries";
|
||||||
|
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
|
||||||
|
import * as child_process from "child_process";
|
||||||
|
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
||||||
|
import propertiesParser from "properties-parser";
|
||||||
|
|
||||||
export type BuildContextLike = BuildContextLike_generateResourcesForMainTheme & {
|
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
||||||
themeNames: string[];
|
BuildContextLike_generateMessageProperties & {
|
||||||
};
|
themeNames: string[];
|
||||||
|
extraThemeProperties: string[] | undefined;
|
||||||
|
projectDirPath: string;
|
||||||
|
projectBuildDirPath: string;
|
||||||
|
environmentVariables: { name: string; default: string }[];
|
||||||
|
implementedThemeTypes: BuildContext["implementedThemeTypes"];
|
||||||
|
themeSrcDirPath: string;
|
||||||
|
bundler: "vite" | "webpack";
|
||||||
|
packageJsonFilePath: string;
|
||||||
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
@ -20,25 +60,443 @@ export async function generateResources(params: {
|
|||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { resourcesDirPath, buildContext } = params;
|
const { resourcesDirPath, buildContext } = params;
|
||||||
|
|
||||||
const [themeName, ...themeVariantNames] = buildContext.themeNames;
|
const [themeName] = buildContext.themeNames;
|
||||||
|
|
||||||
if (fs.existsSync(resourcesDirPath)) {
|
if (fs.existsSync(resourcesDirPath)) {
|
||||||
rmSync(resourcesDirPath, { recursive: true });
|
rmSync(resourcesDirPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { writeMessagePropertiesFilesForThemeVariant } =
|
const getThemeTypeDirPath = (params: {
|
||||||
await generateResourcesForMainTheme({
|
themeType: ThemeType | "email";
|
||||||
resourcesDirPath,
|
themeName: string;
|
||||||
|
}) => {
|
||||||
|
const { themeType, themeName } = params;
|
||||||
|
return pathJoin(resourcesDirPath, "theme", themeName, themeType);
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeMessagePropertiesFilesByThemeType: Partial<
|
||||||
|
Record<ThemeType, (params: { messageDirPath: string; themeName: string }) => void>
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
for (const themeType of ["login", "account"] as const) {
|
||||||
|
if (!buildContext.implementedThemeTypes[themeType].isImplemented) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isForAccountSpa =
|
||||||
|
themeType === "account" &&
|
||||||
|
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
||||||
|
buildContext.implementedThemeTypes.account.type === "Single-Page");
|
||||||
|
|
||||||
|
const themeTypeDirPath = getThemeTypeDirPath({ themeName, themeType });
|
||||||
|
|
||||||
|
apply_replacers_and_move_to_theme_resources: {
|
||||||
|
const destDirPath = pathJoin(
|
||||||
|
themeTypeDirPath,
|
||||||
|
"resources",
|
||||||
|
WELL_KNOWN_DIRECTORY_BASE_NAME.DIST
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
|
||||||
|
rmSync(destDirPath, { recursive: true, force: true });
|
||||||
|
|
||||||
|
if (
|
||||||
|
themeType === "account" &&
|
||||||
|
buildContext.implementedThemeTypes.login.isImplemented
|
||||||
|
) {
|
||||||
|
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: pathJoin(
|
||||||
|
getThemeTypeDirPath({
|
||||||
|
themeName,
|
||||||
|
themeType: "login"
|
||||||
|
}),
|
||||||
|
"resources",
|
||||||
|
WELL_KNOWN_DIRECTORY_BASE_NAME.DIST
|
||||||
|
),
|
||||||
|
destDirPath
|
||||||
|
});
|
||||||
|
|
||||||
|
break apply_replacers_and_move_to_theme_resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const dirPath = pathJoin(
|
||||||
|
buildContext.projectBuildDirPath,
|
||||||
|
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fs.existsSync(dirPath)) {
|
||||||
|
assert(buildContext.bundler === "webpack");
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
[
|
||||||
|
`Keycloakify build error: The ${WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES} directory shouldn't exist in your build directory.`,
|
||||||
|
`(${pathRelative(process.cwd(), dirPath)}).\n`,
|
||||||
|
`Theses assets are only required for local development with Storybook.",
|
||||||
|
"Please remove this directory as an additional step of your command.\n`,
|
||||||
|
`For example: \`"build": "... && rimraf ${pathRelative(buildContext.projectDirPath, dirPath)}"\``
|
||||||
|
].join(" ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: buildContext.projectBuildDirPath,
|
||||||
|
destDirPath,
|
||||||
|
transformSourceCode: ({ filePath, fileRelativePath, sourceCode }) => {
|
||||||
|
if (filePath.endsWith(".css")) {
|
||||||
|
const { fixedCssCode } = replaceImportsInCssCode({
|
||||||
|
cssCode: sourceCode.toString("utf8"),
|
||||||
|
cssFileRelativeDirPath: pathDirname(fileRelativePath),
|
||||||
|
buildContext
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedSourceCode: Buffer.from(fixedCssCode, "utf8")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePath.endsWith(".js")) {
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode({
|
||||||
|
jsCode: sourceCode.toString("utf8"),
|
||||||
|
buildContext
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedSourceCode: Buffer.from(fixedJsCode, "utf8")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { modifiedSourceCode: sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
||||||
themeName,
|
themeName,
|
||||||
buildContext
|
indexHtmlCode: fs
|
||||||
|
.readFileSync(pathJoin(buildContext.projectBuildDirPath, "index.html"))
|
||||||
|
.toString("utf8"),
|
||||||
|
buildContext,
|
||||||
|
keycloakifyVersion: readThisNpmPackageVersion(),
|
||||||
|
themeType,
|
||||||
|
fieldNames: readFieldNameUsage({
|
||||||
|
themeSrcDirPath: buildContext.themeSrcDirPath,
|
||||||
|
themeType
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const themeVariantName of themeVariantNames) {
|
[
|
||||||
generateResourcesForThemeVariant({
|
...(() => {
|
||||||
resourcesDirPath,
|
switch (themeType) {
|
||||||
themeName,
|
case "login":
|
||||||
themeVariantName,
|
return LOGIN_THEME_PAGE_IDS;
|
||||||
writeMessagePropertiesFiles: writeMessagePropertiesFilesForThemeVariant
|
case "account":
|
||||||
|
return isForAccountSpa ? ["index.ftl"] : ACCOUNT_THEME_PAGE_IDS;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
...(isForAccountSpa
|
||||||
|
? []
|
||||||
|
: readExtraPagesNames({
|
||||||
|
themeType,
|
||||||
|
themeSrcDirPath: buildContext.themeSrcDirPath
|
||||||
|
}))
|
||||||
|
].forEach(pageId => {
|
||||||
|
const { ftlCode } = generateFtlFilesCode({ pageId });
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
pathJoin(themeTypeDirPath, pageId),
|
||||||
|
Buffer.from(ftlCode, "utf8")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let languageTags: string[] | undefined = undefined;
|
||||||
|
|
||||||
|
i18n_messages_generation: {
|
||||||
|
if (isForAccountSpa) {
|
||||||
|
break i18n_messages_generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrap = generateMessageProperties({
|
||||||
|
buildContext,
|
||||||
|
themeType
|
||||||
|
});
|
||||||
|
|
||||||
|
languageTags = wrap.languageTags;
|
||||||
|
const { writeMessagePropertiesFiles } = wrap;
|
||||||
|
|
||||||
|
writeMessagePropertiesFilesByThemeType[themeType] =
|
||||||
|
writeMessagePropertiesFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
bring_in_account_v3_i18n_messages: {
|
||||||
|
if (!buildContext.implementedThemeTypes.account.isImplemented) {
|
||||||
|
break bring_in_account_v3_i18n_messages;
|
||||||
|
}
|
||||||
|
if (buildContext.implementedThemeTypes.account.type !== "Single-Page") {
|
||||||
|
break bring_in_account_v3_i18n_messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountUiDirPath = child_process
|
||||||
|
.execSync("npm list @keycloakify/keycloak-account-ui --parseable", {
|
||||||
|
cwd: pathDirname(buildContext.packageJsonFilePath)
|
||||||
|
})
|
||||||
|
.toString("utf8")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const messageDirPath_defaults = pathJoin(accountUiDirPath, "messages");
|
||||||
|
|
||||||
|
if (!fs.existsSync(messageDirPath_defaults)) {
|
||||||
|
throw new Error(
|
||||||
|
`Please update @keycloakify/keycloak-account-ui to 25.0.4-rc.5 or later.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messagesDirPath_dest = pathJoin(
|
||||||
|
getThemeTypeDirPath({ themeName, themeType: "account" }),
|
||||||
|
"messages"
|
||||||
|
);
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: messageDirPath_defaults,
|
||||||
|
destDirPath: messagesDirPath_dest
|
||||||
|
});
|
||||||
|
|
||||||
|
apply_theme_changes: {
|
||||||
|
const messagesDirPath_theme = pathJoin(
|
||||||
|
buildContext.themeSrcDirPath,
|
||||||
|
"account",
|
||||||
|
"messages"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(messagesDirPath_theme)) {
|
||||||
|
break apply_theme_changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readdirSync(messagesDirPath_theme).forEach(basename => {
|
||||||
|
const filePath_src = pathJoin(messagesDirPath_theme, basename);
|
||||||
|
const filePath_dest = pathJoin(messagesDirPath_dest, basename);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath_dest)) {
|
||||||
|
fs.cpSync(filePath_src, filePath_dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages_src = propertiesParser.parse(
|
||||||
|
fs.readFileSync(filePath_src).toString("utf8")
|
||||||
|
);
|
||||||
|
const messages_dest = propertiesParser.parse(
|
||||||
|
fs.readFileSync(filePath_dest).toString("utf8")
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
...messages_dest,
|
||||||
|
...messages_src
|
||||||
|
};
|
||||||
|
|
||||||
|
const editor = propertiesParser.createEditor();
|
||||||
|
|
||||||
|
Object.entries(messages).forEach(([key, value]) => {
|
||||||
|
editor.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
filePath_dest,
|
||||||
|
Buffer.from(editor.toString(), "utf8")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
languageTags = fs
|
||||||
|
.readdirSync(messagesDirPath_dest)
|
||||||
|
.map(basename =>
|
||||||
|
basename.replace(/^messages_/, "").replace(/\.properties$/, "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
keycloak_static_resources: {
|
||||||
|
if (isForAccountSpa) {
|
||||||
|
break keycloak_static_resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: pathJoin(
|
||||||
|
getThisCodebaseRootDirPath(),
|
||||||
|
"res",
|
||||||
|
"public",
|
||||||
|
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES,
|
||||||
|
themeType
|
||||||
|
),
|
||||||
|
destDirPath: pathJoin(themeTypeDirPath, "resources")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
pathJoin(themeTypeDirPath, "theme.properties"),
|
||||||
|
Buffer.from(
|
||||||
|
[
|
||||||
|
`parent=${(() => {
|
||||||
|
switch (themeType) {
|
||||||
|
case "account":
|
||||||
|
return isForAccountSpa ? "base" : "account-v1";
|
||||||
|
case "login":
|
||||||
|
return "keycloak";
|
||||||
|
}
|
||||||
|
assert<Equals<typeof themeType, never>>(false);
|
||||||
|
})()}`,
|
||||||
|
...(isForAccountSpa ? ["deprecatedMode=false"] : []),
|
||||||
|
...(buildContext.extraThemeProperties ?? []),
|
||||||
|
...buildContext.environmentVariables.map(
|
||||||
|
({ name, default: defaultValue }) =>
|
||||||
|
`${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
|
||||||
|
),
|
||||||
|
...(languageTags === undefined
|
||||||
|
? []
|
||||||
|
: [`locales=${languageTags.join(",")}`])
|
||||||
|
].join("\n\n"),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
email: {
|
||||||
|
if (!buildContext.implementedThemeTypes.email.isImplemented) {
|
||||||
|
break email;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: emailThemeSrcDirPath,
|
||||||
|
destDirPath: getThemeTypeDirPath({ themeName, themeType: "email" })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bring_in_account_v1: {
|
||||||
|
if (!buildContext.implementedThemeTypes.account.isImplemented) {
|
||||||
|
break bring_in_account_v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildContext.implementedThemeTypes.account.type !== "Multi-Page") {
|
||||||
|
break bring_in_account_v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: pathJoin(getThisCodebaseRootDirPath(), "res", "account-v1"),
|
||||||
|
destDirPath: getThemeTypeDirPath({
|
||||||
|
themeName: "account-v1",
|
||||||
|
themeType: "account"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const metaInfKeycloakThemes: MetaInfKeycloakTheme = { themes: [] };
|
||||||
|
|
||||||
|
for (const themeName of buildContext.themeNames) {
|
||||||
|
metaInfKeycloakThemes.themes.push({
|
||||||
|
name: themeName,
|
||||||
|
types: objectEntries(buildContext.implementedThemeTypes)
|
||||||
|
.filter(([, { isImplemented }]) => isImplemented)
|
||||||
|
.map(([themeType]) => themeType)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildContext.implementedThemeTypes.account.isImplemented) {
|
||||||
|
metaInfKeycloakThemes.themes.push({
|
||||||
|
name: "account-v1",
|
||||||
|
types: ["account"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
writeMetaInfKeycloakThemes({
|
||||||
|
resourcesDirPath,
|
||||||
|
getNewMetaInfKeycloakTheme: () => metaInfKeycloakThemes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const themeVariantName of buildContext.themeNames) {
|
||||||
|
if (themeVariantName === themeName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: pathJoin(resourcesDirPath, "theme", themeName),
|
||||||
|
destDirPath: pathJoin(resourcesDirPath, "theme", themeVariantName),
|
||||||
|
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
||||||
|
if (
|
||||||
|
pathExtname(fileRelativePath) === ".ftl" &&
|
||||||
|
fileRelativePath.split(pathSep).length === 2
|
||||||
|
) {
|
||||||
|
const modifiedSourceCode = Buffer.from(
|
||||||
|
Buffer.from(sourceCode)
|
||||||
|
.toString("utf-8")
|
||||||
|
.replace(
|
||||||
|
`"themeName": "${themeName}"`,
|
||||||
|
`"themeName": "${themeVariantName}"`
|
||||||
|
),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(Buffer.compare(modifiedSourceCode, sourceCode) !== 0);
|
||||||
|
|
||||||
|
return { modifiedSourceCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { modifiedSourceCode: sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const themeName of buildContext.themeNames) {
|
||||||
|
for (const [themeType, writeMessagePropertiesFiles] of objectEntries(
|
||||||
|
writeMessagePropertiesFilesByThemeType
|
||||||
|
)) {
|
||||||
|
// NOTE: This is just a quirk of the type system: We can't really differentiate in a record
|
||||||
|
// between the case where the key isn't present and the case where the value is `undefined`.
|
||||||
|
if (writeMessagePropertiesFiles === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writeMessagePropertiesFiles({
|
||||||
|
messageDirPath: pathJoin(
|
||||||
|
getThemeTypeDirPath({ themeName, themeType }),
|
||||||
|
"messages"
|
||||||
|
),
|
||||||
|
themeName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modify_email_theme_per_variant: {
|
||||||
|
if (!buildContext.implementedThemeTypes.email.isImplemented) {
|
||||||
|
break modify_email_theme_per_variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const themeName of buildContext.themeNames) {
|
||||||
|
const emailThemeDirPath = getThemeTypeDirPath({
|
||||||
|
themeName,
|
||||||
|
themeType: "email"
|
||||||
|
});
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: emailThemeDirPath,
|
||||||
|
destDirPath: emailThemeDirPath,
|
||||||
|
transformSourceCode: ({ filePath, sourceCode }) => {
|
||||||
|
if (!filePath.endsWith(".ftl")) {
|
||||||
|
return { modifiedSourceCode: sourceCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedSourceCode: Buffer.from(
|
||||||
|
sourceCode
|
||||||
|
.toString("utf8")
|
||||||
|
.replace(/xKeycloakify\.themeName/g, `"${themeName}"`),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,373 +0,0 @@
|
|||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
|
|
||||||
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
|
||||||
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
|
||||||
import {
|
|
||||||
generateFtlFilesCodeFactory,
|
|
||||||
type BuildContextLike as BuildContextLike_kcContextExclusionsFtlCode
|
|
||||||
} from "../generateFtl";
|
|
||||||
import {
|
|
||||||
type ThemeType,
|
|
||||||
LOGIN_THEME_PAGE_IDS,
|
|
||||||
ACCOUNT_THEME_PAGE_IDS,
|
|
||||||
WELL_KNOWN_DIRECTORY_BASE_NAME
|
|
||||||
} from "../../shared/constants";
|
|
||||||
import type { BuildContext } from "../../shared/buildContext";
|
|
||||||
import { assert, type Equals } from "tsafe/assert";
|
|
||||||
import { readFieldNameUsage } from "./readFieldNameUsage";
|
|
||||||
import { readExtraPagesNames } from "./readExtraPageNames";
|
|
||||||
import {
|
|
||||||
generateMessageProperties,
|
|
||||||
type BuildContextLike as BuildContextLike_generateMessageProperties
|
|
||||||
} from "./generateMessageProperties";
|
|
||||||
import { rmSync } from "../../tools/fs.rmSync";
|
|
||||||
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
|
|
||||||
import {
|
|
||||||
writeMetaInfKeycloakThemes,
|
|
||||||
type MetaInfKeycloakTheme
|
|
||||||
} from "../../shared/metaInfKeycloakThemes";
|
|
||||||
import { objectEntries } from "tsafe/objectEntries";
|
|
||||||
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
|
|
||||||
import * as child_process from "child_process";
|
|
||||||
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
|
||||||
|
|
||||||
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
|
||||||
BuildContextLike_generateMessageProperties & {
|
|
||||||
extraThemeProperties: string[] | undefined;
|
|
||||||
projectDirPath: string;
|
|
||||||
projectBuildDirPath: string;
|
|
||||||
environmentVariables: { name: string; default: string }[];
|
|
||||||
implementedThemeTypes: BuildContext["implementedThemeTypes"];
|
|
||||||
themeSrcDirPath: string;
|
|
||||||
bundler: "vite" | "webpack";
|
|
||||||
packageJsonFilePath: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
|
||||||
|
|
||||||
export async function generateResourcesForMainTheme(params: {
|
|
||||||
buildContext: BuildContextLike;
|
|
||||||
themeName: string;
|
|
||||||
resourcesDirPath: string;
|
|
||||||
}): Promise<{
|
|
||||||
writeMessagePropertiesFilesForThemeVariant: (params: {
|
|
||||||
getMessageDirPath: (params: { themeType: ThemeType }) => string;
|
|
||||||
themeName: string;
|
|
||||||
}) => void;
|
|
||||||
}> {
|
|
||||||
const { themeName, resourcesDirPath, buildContext } = params;
|
|
||||||
|
|
||||||
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
|
|
||||||
const { themeType } = params;
|
|
||||||
return pathJoin(resourcesDirPath, "theme", themeName, themeType);
|
|
||||||
};
|
|
||||||
|
|
||||||
const writeMessagePropertiesFilesByThemeType: Partial<
|
|
||||||
Record<ThemeType, (params: { messageDirPath: string; themeName: string }) => void>
|
|
||||||
> = {};
|
|
||||||
|
|
||||||
for (const themeType of ["login", "account"] as const) {
|
|
||||||
if (!buildContext.implementedThemeTypes[themeType].isImplemented) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isForAccountSpa =
|
|
||||||
themeType === "account" &&
|
|
||||||
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
|
||||||
buildContext.implementedThemeTypes.account.type === "Single-Page");
|
|
||||||
|
|
||||||
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
|
||||||
|
|
||||||
apply_replacers_and_move_to_theme_resources: {
|
|
||||||
const destDirPath = pathJoin(
|
|
||||||
themeTypeDirPath,
|
|
||||||
"resources",
|
|
||||||
WELL_KNOWN_DIRECTORY_BASE_NAME.DIST
|
|
||||||
);
|
|
||||||
|
|
||||||
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
|
|
||||||
rmSync(destDirPath, { recursive: true, force: true });
|
|
||||||
|
|
||||||
if (
|
|
||||||
themeType === "account" &&
|
|
||||||
buildContext.implementedThemeTypes.login.isImplemented
|
|
||||||
) {
|
|
||||||
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: pathJoin(
|
|
||||||
getThemeTypeDirPath({
|
|
||||||
themeType: "login"
|
|
||||||
}),
|
|
||||||
"resources",
|
|
||||||
WELL_KNOWN_DIRECTORY_BASE_NAME.DIST
|
|
||||||
),
|
|
||||||
destDirPath
|
|
||||||
});
|
|
||||||
|
|
||||||
break apply_replacers_and_move_to_theme_resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const dirPath = pathJoin(
|
|
||||||
buildContext.projectBuildDirPath,
|
|
||||||
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fs.existsSync(dirPath)) {
|
|
||||||
assert(buildContext.bundler === "webpack");
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
[
|
|
||||||
`Keycloakify build error: The ${WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES} directory shouldn't exist in your build directory.`,
|
|
||||||
`(${pathRelative(process.cwd(), dirPath)}).\n`,
|
|
||||||
`Theses assets are only required for local development with Storybook.",
|
|
||||||
"Please remove this directory as an additional step of your command.\n`,
|
|
||||||
`For example: \`"build": "... && rimraf ${pathRelative(buildContext.projectDirPath, dirPath)}"\``
|
|
||||||
].join(" ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: buildContext.projectBuildDirPath,
|
|
||||||
destDirPath,
|
|
||||||
transformSourceCode: ({ filePath, fileRelativePath, sourceCode }) => {
|
|
||||||
if (filePath.endsWith(".css")) {
|
|
||||||
const { fixedCssCode } = replaceImportsInCssCode({
|
|
||||||
cssCode: sourceCode.toString("utf8"),
|
|
||||||
cssFileRelativeDirPath: pathDirname(fileRelativePath),
|
|
||||||
buildContext
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
modifiedSourceCode: Buffer.from(fixedCssCode, "utf8")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filePath.endsWith(".js")) {
|
|
||||||
const { fixedJsCode } = replaceImportsInJsCode({
|
|
||||||
jsCode: sourceCode.toString("utf8"),
|
|
||||||
buildContext
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
modifiedSourceCode: Buffer.from(fixedJsCode, "utf8")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { modifiedSourceCode: sourceCode };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
|
||||||
themeName,
|
|
||||||
indexHtmlCode: fs
|
|
||||||
.readFileSync(pathJoin(buildContext.projectBuildDirPath, "index.html"))
|
|
||||||
.toString("utf8"),
|
|
||||||
buildContext,
|
|
||||||
keycloakifyVersion: readThisNpmPackageVersion(),
|
|
||||||
themeType,
|
|
||||||
fieldNames: readFieldNameUsage({
|
|
||||||
themeSrcDirPath: buildContext.themeSrcDirPath,
|
|
||||||
themeType
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
|
||||||
...(() => {
|
|
||||||
switch (themeType) {
|
|
||||||
case "login":
|
|
||||||
return LOGIN_THEME_PAGE_IDS;
|
|
||||||
case "account":
|
|
||||||
return isForAccountSpa ? ["index.ftl"] : ACCOUNT_THEME_PAGE_IDS;
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
...(isForAccountSpa
|
|
||||||
? []
|
|
||||||
: readExtraPagesNames({
|
|
||||||
themeType,
|
|
||||||
themeSrcDirPath: buildContext.themeSrcDirPath
|
|
||||||
}))
|
|
||||||
].forEach(pageId => {
|
|
||||||
const { ftlCode } = generateFtlFilesCode({ pageId });
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
pathJoin(themeTypeDirPath, pageId),
|
|
||||||
Buffer.from(ftlCode, "utf8")
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let languageTags: string[] | undefined = undefined;
|
|
||||||
|
|
||||||
i18n_messages_generation: {
|
|
||||||
if (isForAccountSpa) {
|
|
||||||
break i18n_messages_generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrap = generateMessageProperties({
|
|
||||||
buildContext,
|
|
||||||
themeType
|
|
||||||
});
|
|
||||||
|
|
||||||
languageTags = wrap.languageTags;
|
|
||||||
const { writeMessagePropertiesFiles } = wrap;
|
|
||||||
|
|
||||||
writeMessagePropertiesFilesByThemeType[themeType] =
|
|
||||||
writeMessagePropertiesFiles;
|
|
||||||
|
|
||||||
writeMessagePropertiesFiles({
|
|
||||||
messageDirPath: pathJoin(themeTypeDirPath, "messages"),
|
|
||||||
themeName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bring_in_account_v3_i18n_messages: {
|
|
||||||
if (!buildContext.implementedThemeTypes.account.isImplemented) {
|
|
||||||
break bring_in_account_v3_i18n_messages;
|
|
||||||
}
|
|
||||||
if (buildContext.implementedThemeTypes.account.type !== "Single-Page") {
|
|
||||||
break bring_in_account_v3_i18n_messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountUiDirPath = child_process
|
|
||||||
.execSync("npm list @keycloakify/keycloak-account-ui --parseable", {
|
|
||||||
cwd: pathDirname(buildContext.packageJsonFilePath)
|
|
||||||
})
|
|
||||||
.toString("utf8")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const messagesDirPath = pathJoin(accountUiDirPath, "messages");
|
|
||||||
|
|
||||||
if (!fs.existsSync(messagesDirPath)) {
|
|
||||||
throw new Error(
|
|
||||||
`Please update @keycloakify/keycloak-account-ui to 25.0.4-rc.5 or later.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: messagesDirPath,
|
|
||||||
destDirPath: pathJoin(
|
|
||||||
getThemeTypeDirPath({ themeType: "account" }),
|
|
||||||
"messages"
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
keycloak_static_resources: {
|
|
||||||
if (isForAccountSpa) {
|
|
||||||
break keycloak_static_resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: pathJoin(
|
|
||||||
getThisCodebaseRootDirPath(),
|
|
||||||
"res",
|
|
||||||
"public",
|
|
||||||
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES,
|
|
||||||
themeType
|
|
||||||
),
|
|
||||||
destDirPath: pathJoin(themeTypeDirPath, "resources")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
pathJoin(themeTypeDirPath, "theme.properties"),
|
|
||||||
Buffer.from(
|
|
||||||
[
|
|
||||||
`parent=${(() => {
|
|
||||||
switch (themeType) {
|
|
||||||
case "account":
|
|
||||||
return isForAccountSpa ? "base" : "account-v1";
|
|
||||||
case "login":
|
|
||||||
return "keycloak";
|
|
||||||
}
|
|
||||||
assert<Equals<typeof themeType, never>>(false);
|
|
||||||
})()}`,
|
|
||||||
...(isForAccountSpa ? ["deprecatedMode=false"] : []),
|
|
||||||
...(buildContext.extraThemeProperties ?? []),
|
|
||||||
...buildContext.environmentVariables.map(
|
|
||||||
({ name, default: defaultValue }) =>
|
|
||||||
`${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
|
|
||||||
),
|
|
||||||
...(languageTags === undefined
|
|
||||||
? []
|
|
||||||
: [`locales=${languageTags.join(",")}`])
|
|
||||||
].join("\n\n"),
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
email: {
|
|
||||||
if (!buildContext.implementedThemeTypes.email.isImplemented) {
|
|
||||||
break email;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: emailThemeSrcDirPath,
|
|
||||||
destDirPath: getThemeTypeDirPath({ themeType: "email" })
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bring_in_account_v1: {
|
|
||||||
if (!buildContext.implementedThemeTypes.account.isImplemented) {
|
|
||||||
break bring_in_account_v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildContext.implementedThemeTypes.account.type !== "Multi-Page") {
|
|
||||||
break bring_in_account_v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: pathJoin(getThisCodebaseRootDirPath(), "res", "account-v1"),
|
|
||||||
destDirPath: pathJoin(resourcesDirPath, "theme", "account-v1", "account")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const metaInfKeycloakThemes: MetaInfKeycloakTheme = { themes: [] };
|
|
||||||
|
|
||||||
metaInfKeycloakThemes.themes.push({
|
|
||||||
name: themeName,
|
|
||||||
types: objectEntries(buildContext.implementedThemeTypes)
|
|
||||||
.filter(([, { isImplemented }]) => isImplemented)
|
|
||||||
.map(([themeType]) => themeType)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (buildContext.implementedThemeTypes.account.isImplemented) {
|
|
||||||
metaInfKeycloakThemes.themes.push({
|
|
||||||
name: "account-v1",
|
|
||||||
types: ["account"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
writeMetaInfKeycloakThemes({
|
|
||||||
resourcesDirPath,
|
|
||||||
getNewMetaInfKeycloakTheme: () => metaInfKeycloakThemes
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
writeMessagePropertiesFilesForThemeVariant: ({
|
|
||||||
getMessageDirPath,
|
|
||||||
themeName
|
|
||||||
}) => {
|
|
||||||
objectEntries(writeMessagePropertiesFilesByThemeType).forEach(
|
|
||||||
([themeType, writeMessagePropertiesFiles]) => {
|
|
||||||
if (writeMessagePropertiesFiles === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writeMessagePropertiesFiles({
|
|
||||||
messageDirPath: getMessageDirPath({ themeType }),
|
|
||||||
themeName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
import { join as pathJoin, extname as pathExtname, sep as pathSep } from "path";
|
|
||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
|
||||||
import { writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import type { ThemeType } from "../../shared/constants";
|
|
||||||
|
|
||||||
export function generateResourcesForThemeVariant(params: {
|
|
||||||
resourcesDirPath: string;
|
|
||||||
themeName: string;
|
|
||||||
themeVariantName: string;
|
|
||||||
writeMessagePropertiesFiles: (params: {
|
|
||||||
getMessageDirPath: (params: { themeType: ThemeType }) => string;
|
|
||||||
themeName: string;
|
|
||||||
}) => void;
|
|
||||||
}) {
|
|
||||||
const { resourcesDirPath, themeName, themeVariantName, writeMessagePropertiesFiles } =
|
|
||||||
params;
|
|
||||||
|
|
||||||
const mainThemeDirPath = pathJoin(resourcesDirPath, "theme", themeName);
|
|
||||||
const themeVariantDirPath = pathJoin(mainThemeDirPath, "..", themeVariantName);
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: mainThemeDirPath,
|
|
||||||
destDirPath: themeVariantDirPath,
|
|
||||||
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
|
||||||
if (
|
|
||||||
pathExtname(fileRelativePath) === ".ftl" &&
|
|
||||||
fileRelativePath.split(pathSep).length === 2
|
|
||||||
) {
|
|
||||||
const modifiedSourceCode = Buffer.from(
|
|
||||||
Buffer.from(sourceCode)
|
|
||||||
.toString("utf-8")
|
|
||||||
.replace(
|
|
||||||
`"themeName": "${themeName}"`,
|
|
||||||
`"themeName": "${themeVariantName}"`
|
|
||||||
),
|
|
||||||
"utf8"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(Buffer.compare(modifiedSourceCode, sourceCode) !== 0);
|
|
||||||
|
|
||||||
return { modifiedSourceCode };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { modifiedSourceCode: sourceCode };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
writeMetaInfKeycloakThemes({
|
|
||||||
resourcesDirPath,
|
|
||||||
getNewMetaInfKeycloakTheme: ({ metaInfKeycloakTheme }) => {
|
|
||||||
assert(metaInfKeycloakTheme !== undefined);
|
|
||||||
|
|
||||||
const newMetaInfKeycloakTheme = metaInfKeycloakTheme;
|
|
||||||
|
|
||||||
newMetaInfKeycloakTheme.themes.push({
|
|
||||||
name: themeVariantName,
|
|
||||||
types: (() => {
|
|
||||||
const theme = newMetaInfKeycloakTheme.themes.find(
|
|
||||||
({ name }) => name === themeName
|
|
||||||
);
|
|
||||||
assert(theme !== undefined);
|
|
||||||
return theme.types;
|
|
||||||
})()
|
|
||||||
});
|
|
||||||
|
|
||||||
return newMetaInfKeycloakTheme;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
writeMessagePropertiesFiles({
|
|
||||||
getMessageDirPath: ({ themeType }) =>
|
|
||||||
pathJoin(themeVariantDirPath, themeType, "messages"),
|
|
||||||
themeName: themeVariantName
|
|
||||||
});
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ export type KeycloakVersionRange =
|
|||||||
| KeycloakVersionRange.WithoutAccountV1Theme;
|
| KeycloakVersionRange.WithoutAccountV1Theme;
|
||||||
|
|
||||||
export namespace KeycloakVersionRange {
|
export namespace KeycloakVersionRange {
|
||||||
export type WithoutAccountV1Theme = "21-and-below" | "22-and-above";
|
export type WithoutAccountV1Theme = "22-to-25" | "all-other-versions";
|
||||||
|
|
||||||
export type WithAccountV1Theme = "21-and-below" | "23" | "24" | "25-and-above";
|
export type WithAccountV1Theme = "21-and-below" | "23" | "24" | "25" | "26-and-above";
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import { type ThemeType } from "./constants";
|
|||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
|
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
|
||||||
|
import { is } from "tsafe/is";
|
||||||
|
|
||||||
export type BuildContext = {
|
export type BuildContext = {
|
||||||
themeVersion: string;
|
themeVersion: string;
|
||||||
@ -240,8 +241,7 @@ export function getBuildContext(params: {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
parsedPackageJson.dependencies?.keycloakify === undefined &&
|
parsedPackageJson.dependencies?.keycloakify === undefined &&
|
||||||
parsedPackageJson.devDependencies?.keycloakify === undefined &&
|
parsedPackageJson.devDependencies?.keycloakify === undefined
|
||||||
parsedPackageJson.name !== "keycloakify" // NOTE: For local storybook build
|
|
||||||
) {
|
) {
|
||||||
break success;
|
break success;
|
||||||
}
|
}
|
||||||
@ -277,7 +277,8 @@ export function getBuildContext(params: {
|
|||||||
"21-and-below": z.union([z.boolean(), z.string()]),
|
"21-and-below": z.union([z.boolean(), z.string()]),
|
||||||
"23": z.union([z.boolean(), z.string()]),
|
"23": z.union([z.boolean(), z.string()]),
|
||||||
"24": z.union([z.boolean(), z.string()]),
|
"24": z.union([z.boolean(), z.string()]),
|
||||||
"25-and-above": z.union([z.boolean(), z.string()])
|
"25": z.union([z.boolean(), z.string()]),
|
||||||
|
"26-and-above": z.union([z.boolean(), z.string()])
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
});
|
});
|
||||||
@ -298,8 +299,8 @@ export function getBuildContext(params: {
|
|||||||
]),
|
]),
|
||||||
keycloakVersionTargets: z
|
keycloakVersionTargets: z
|
||||||
.object({
|
.object({
|
||||||
"21-and-below": z.union([z.boolean(), z.string()]),
|
"22-to-25": z.union([z.boolean(), z.string()]),
|
||||||
"22-and-above": z.union([z.boolean(), z.string()])
|
"all-other-versions": z.union([z.boolean(), z.string()])
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
});
|
});
|
||||||
@ -470,26 +471,44 @@ export function getBuildContext(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const themeNames = ((): [string, ...string[]] => {
|
const themeNames = ((): [string, ...string[]] => {
|
||||||
if (buildOptions.themeName === undefined) {
|
const themeNames = ((): [string, ...string[]] => {
|
||||||
return parsedPackageJson.name === undefined
|
if (buildOptions.themeName === undefined) {
|
||||||
? ["keycloakify"]
|
return parsedPackageJson.name === undefined
|
||||||
: [
|
? ["keycloakify"]
|
||||||
parsedPackageJson.name
|
: [
|
||||||
.replace(/^@(.*)/, "$1")
|
parsedPackageJson.name
|
||||||
.split("/")
|
.replace(/^@(.*)/, "$1")
|
||||||
.join("-")
|
.split("/")
|
||||||
];
|
.join("-")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof buildOptions.themeName === "string") {
|
||||||
|
return [buildOptions.themeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [mainThemeName, ...themeVariantNames] = buildOptions.themeName;
|
||||||
|
|
||||||
|
assert(mainThemeName !== undefined);
|
||||||
|
|
||||||
|
return [mainThemeName, ...themeVariantNames];
|
||||||
|
})();
|
||||||
|
|
||||||
|
for (const themeName of themeNames) {
|
||||||
|
if (!/^[a-zA-Z0-9_-]+$/.test(themeName)) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
[
|
||||||
|
`Invalid theme name: ${themeName}`,
|
||||||
|
`Theme names should only contain letters, numbers, and "_" or "-"`
|
||||||
|
].join(" ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof buildOptions.themeName === "string") {
|
return themeNames;
|
||||||
return [buildOptions.themeName];
|
|
||||||
}
|
|
||||||
|
|
||||||
const [mainThemeName, ...themeVariantNames] = buildOptions.themeName;
|
|
||||||
|
|
||||||
assert(mainThemeName !== undefined);
|
|
||||||
|
|
||||||
return [mainThemeName, ...themeVariantNames];
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const projectBuildDirPath = (() => {
|
const projectBuildDirPath = (() => {
|
||||||
@ -749,7 +768,11 @@ export function getBuildContext(params: {
|
|||||||
return "24" as const;
|
return "24" as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "25-and-above" as const;
|
if (buildForKeycloakMajorVersionNumber === 25) {
|
||||||
|
return "25" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "26-and-above" as const;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
assert<
|
assert<
|
||||||
@ -762,11 +785,14 @@ export function getBuildContext(params: {
|
|||||||
return keycloakVersionRange;
|
return keycloakVersionRange;
|
||||||
} else {
|
} else {
|
||||||
const keycloakVersionRange = (() => {
|
const keycloakVersionRange = (() => {
|
||||||
if (buildForKeycloakMajorVersionNumber <= 21) {
|
if (
|
||||||
return "21-and-below" as const;
|
buildForKeycloakMajorVersionNumber <= 21 ||
|
||||||
|
buildForKeycloakMajorVersionNumber >= 26
|
||||||
|
) {
|
||||||
|
return "all-other-versions" as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "22-and-above" as const;
|
return "22-to-25" as const;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
assert<
|
assert<
|
||||||
@ -784,6 +810,12 @@ export function getBuildContext(params: {
|
|||||||
use_custom_jar_basename: {
|
use_custom_jar_basename: {
|
||||||
const { keycloakVersionTargets } = buildOptions;
|
const { keycloakVersionTargets } = buildOptions;
|
||||||
|
|
||||||
|
assert(
|
||||||
|
is<Record<KeycloakVersionRange, string | boolean>>(
|
||||||
|
keycloakVersionTargets
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if (keycloakVersionTargets === undefined) {
|
if (keycloakVersionTargets === undefined) {
|
||||||
break use_custom_jar_basename;
|
break use_custom_jar_basename;
|
||||||
}
|
}
|
||||||
@ -828,7 +860,8 @@ export function getBuildContext(params: {
|
|||||||
"21-and-below",
|
"21-and-below",
|
||||||
"23",
|
"23",
|
||||||
"24",
|
"24",
|
||||||
"25-and-above"
|
"25",
|
||||||
|
"26-and-above"
|
||||||
] as const) {
|
] as const) {
|
||||||
assert<
|
assert<
|
||||||
Equals<
|
Equals<
|
||||||
@ -844,8 +877,8 @@ export function getBuildContext(params: {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const keycloakVersionRange of [
|
for (const keycloakVersionRange of [
|
||||||
"21-and-below",
|
"22-to-25",
|
||||||
"22-and-above"
|
"all-other-versions"
|
||||||
] as const) {
|
] as const) {
|
||||||
assert<
|
assert<
|
||||||
Equals<
|
Equals<
|
||||||
@ -871,7 +904,17 @@ export function getBuildContext(params: {
|
|||||||
const jarTargets: BuildContext["jarTargets"] = [];
|
const jarTargets: BuildContext["jarTargets"] = [];
|
||||||
|
|
||||||
for (const [keycloakVersionRange, jarNameOrBoolean] of objectEntries(
|
for (const [keycloakVersionRange, jarNameOrBoolean] of objectEntries(
|
||||||
buildOptions.keycloakVersionTargets
|
(() => {
|
||||||
|
const { keycloakVersionTargets } = buildOptions;
|
||||||
|
|
||||||
|
assert(
|
||||||
|
is<Record<KeycloakVersionRange, string | boolean>>(
|
||||||
|
keycloakVersionTargets
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return keycloakVersionTargets;
|
||||||
|
})()
|
||||||
)) {
|
)) {
|
||||||
if (jarNameOrBoolean === false) {
|
if (jarNameOrBoolean === false) {
|
||||||
continue;
|
continue;
|
||||||
|
2400
src/bin/start-keycloak/myrealm-realm-26.json
Normal file
2400
src/bin/start-keycloak/myrealm-realm-26.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@ import type { ThemeType, LoginThemePageId } from "keycloakify/bin/shared/constan
|
|||||||
import type { ValueOf } from "keycloakify/tools/ValueOf";
|
import type { ValueOf } from "keycloakify/tools/ValueOf";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
import type { ClassKey } from "keycloakify/login/lib/kcClsx";
|
||||||
|
|
||||||
export type ExtendKcContext<
|
export type ExtendKcContext<
|
||||||
KcContextExtension extends { properties?: Record<string, string | undefined> },
|
KcContextExtension extends { properties?: Record<string, string | undefined> },
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import type { ClassKey } from "keycloakify/login/lib/kcClsx";
|
||||||
|
|
||||||
export type TemplateProps<KcContext, I18n> = {
|
export type TemplateProps<KcContext, I18n> = {
|
||||||
kcContext: KcContext;
|
kcContext: KcContext;
|
||||||
@ -18,128 +19,4 @@ export type TemplateProps<KcContext, I18n> = {
|
|||||||
bodyClassName?: string;
|
bodyClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ClassKey =
|
export type { ClassKey };
|
||||||
| "kcBodyClass"
|
|
||||||
| "kcHeaderWrapperClass"
|
|
||||||
| "kcLocaleWrapperClass"
|
|
||||||
| "kcInfoAreaWrapperClass"
|
|
||||||
| "kcFormButtonsWrapperClass"
|
|
||||||
| "kcFormOptionsWrapperClass"
|
|
||||||
| "kcCheckboxInputClass"
|
|
||||||
| "kcLocaleDropDownClass"
|
|
||||||
| "kcLocaleListItemClass"
|
|
||||||
| "kcContentWrapperClass"
|
|
||||||
| "kcLogoIdP-facebook"
|
|
||||||
| "kcAuthenticatorOTPClass"
|
|
||||||
| "kcLogoIdP-bitbucket"
|
|
||||||
| "kcAuthenticatorWebAuthnClass"
|
|
||||||
| "kcWebAuthnDefaultIcon"
|
|
||||||
| "kcLogoIdP-stackoverflow"
|
|
||||||
| "kcSelectAuthListItemClass"
|
|
||||||
| "kcLogoIdP-microsoft"
|
|
||||||
| "kcLoginOTPListItemHeaderClass"
|
|
||||||
| "kcLocaleItemClass"
|
|
||||||
| "kcLoginOTPListItemIconBodyClass"
|
|
||||||
| "kcInputHelperTextAfterClass"
|
|
||||||
| "kcFormClass"
|
|
||||||
| "kcSelectAuthListClass"
|
|
||||||
| "kcInputClassRadioCheckboxLabelDisabled"
|
|
||||||
| "kcSelectAuthListItemIconClass"
|
|
||||||
| "kcRecoveryCodesWarning"
|
|
||||||
| "kcFormSettingClass"
|
|
||||||
| "kcWebAuthnBLE"
|
|
||||||
| "kcInputWrapperClass"
|
|
||||||
| "kcSelectAuthListItemArrowIconClass"
|
|
||||||
| "kcFeedbackAreaClass"
|
|
||||||
| "kcFormPasswordVisibilityButtonClass"
|
|
||||||
| "kcLogoIdP-google"
|
|
||||||
| "kcCheckLabelClass"
|
|
||||||
| "kcSelectAuthListItemFillClass"
|
|
||||||
| "kcAuthenticatorDefaultClass"
|
|
||||||
| "kcLogoIdP-gitlab"
|
|
||||||
| "kcFormAreaClass"
|
|
||||||
| "kcFormButtonsClass"
|
|
||||||
| "kcInputClassRadioLabel"
|
|
||||||
| "kcAuthenticatorWebAuthnPasswordlessClass"
|
|
||||||
| "kcSelectAuthListItemHeadingClass"
|
|
||||||
| "kcInfoAreaClass"
|
|
||||||
| "kcLogoLink"
|
|
||||||
| "kcContainerClass"
|
|
||||||
| "kcSelectAuthListItemTitle"
|
|
||||||
| "kcHtmlClass"
|
|
||||||
| "kcLoginOTPListItemTitleClass"
|
|
||||||
| "kcLogoIdP-openshift-v4"
|
|
||||||
| "kcWebAuthnUnknownIcon"
|
|
||||||
| "kcFormSocialAccountNameClass"
|
|
||||||
| "kcLogoIdP-openshift-v3"
|
|
||||||
| "kcLoginOTPListInputClass"
|
|
||||||
| "kcWebAuthnUSB"
|
|
||||||
| "kcInputClassRadio"
|
|
||||||
| "kcWebAuthnKeyIcon"
|
|
||||||
| "kcFeedbackInfoIcon"
|
|
||||||
| "kcCommonLogoIdP"
|
|
||||||
| "kcRecoveryCodesActions"
|
|
||||||
| "kcFormGroupHeader"
|
|
||||||
| "kcFormSocialAccountSectionClass"
|
|
||||||
| "kcLogoIdP-instagram"
|
|
||||||
| "kcAlertClass"
|
|
||||||
| "kcHeaderClass"
|
|
||||||
| "kcLabelWrapperClass"
|
|
||||||
| "kcFormPasswordVisibilityIconShow"
|
|
||||||
| "kcFormSocialAccountLinkClass"
|
|
||||||
| "kcLocaleMainClass"
|
|
||||||
| "kcInputGroup"
|
|
||||||
| "kcTextareaClass"
|
|
||||||
| "kcButtonBlockClass"
|
|
||||||
| "kcButtonClass"
|
|
||||||
| "kcWebAuthnNFC"
|
|
||||||
| "kcLocaleClass"
|
|
||||||
| "kcInputClassCheckboxInput"
|
|
||||||
| "kcFeedbackErrorIcon"
|
|
||||||
| "kcInputLargeClass"
|
|
||||||
| "kcInputErrorMessageClass"
|
|
||||||
| "kcRecoveryCodesList"
|
|
||||||
| "kcFormSocialAccountListClass"
|
|
||||||
| "kcAlertTitleClass"
|
|
||||||
| "kcAuthenticatorPasswordClass"
|
|
||||||
| "kcCheckInputClass"
|
|
||||||
| "kcLogoIdP-linkedin"
|
|
||||||
| "kcLogoIdP-twitter"
|
|
||||||
| "kcFeedbackWarningIcon"
|
|
||||||
| "kcResetFlowIcon"
|
|
||||||
| "kcSelectAuthListItemIconPropertyClass"
|
|
||||||
| "kcFeedbackSuccessIcon"
|
|
||||||
| "kcLoginOTPListClass"
|
|
||||||
| "kcSrOnlyClass"
|
|
||||||
| "kcFormSocialAccountListGridClass"
|
|
||||||
| "kcButtonDefaultClass"
|
|
||||||
| "kcFormGroupErrorClass"
|
|
||||||
| "kcSelectAuthListItemDescriptionClass"
|
|
||||||
| "kcSelectAuthListItemBodyClass"
|
|
||||||
| "kcWebAuthnInternal"
|
|
||||||
| "kcSelectAuthListItemArrowClass"
|
|
||||||
| "kcCheckClass"
|
|
||||||
| "kcContentClass"
|
|
||||||
| "kcLogoClass"
|
|
||||||
| "kcLoginOTPListItemIconClass"
|
|
||||||
| "kcLoginClass"
|
|
||||||
| "kcSignUpClass"
|
|
||||||
| "kcButtonLargeClass"
|
|
||||||
| "kcFormCardClass"
|
|
||||||
| "kcLocaleListClass"
|
|
||||||
| "kcInputClass"
|
|
||||||
| "kcFormGroupClass"
|
|
||||||
| "kcLogoIdP-paypal"
|
|
||||||
| "kcInputClassCheckbox"
|
|
||||||
| "kcRecoveryCodesConfirmation"
|
|
||||||
| "kcFormPasswordVisibilityIconHide"
|
|
||||||
| "kcInputClassRadioInput"
|
|
||||||
| "kcFormSocialAccountListButtonClass"
|
|
||||||
| "kcInputClassCheckboxLabel"
|
|
||||||
| "kcFormOptionsClass"
|
|
||||||
| "kcFormHeaderClass"
|
|
||||||
| "kcFormSocialAccountGridItem"
|
|
||||||
| "kcButtonPrimaryClass"
|
|
||||||
| "kcInputHelperTextBeforeClass"
|
|
||||||
| "kcLogoIdP-github"
|
|
||||||
| "kcLabelClass";
|
|
||||||
|
@ -434,9 +434,7 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InputTagSelects(props: InputFieldByTypeProps) {
|
function InputTagSelects(props: InputFieldByTypeProps) {
|
||||||
const { attribute, dispatchFormAction, kcClsx, valueOrValues } = props;
|
const { attribute, dispatchFormAction, kcClsx, i18n, valueOrValues } = props;
|
||||||
|
|
||||||
const { advancedMsg } = props.i18n;
|
|
||||||
|
|
||||||
const { classDiv, classInput, classLabel, inputType } = (() => {
|
const { classDiv, classInput, classLabel, inputType } = (() => {
|
||||||
const { inputType } = attribute.annotations;
|
const { inputType } = attribute.annotations;
|
||||||
@ -533,7 +531,7 @@ function InputTagSelects(props: InputFieldByTypeProps) {
|
|||||||
htmlFor={`${attribute.name}-${option}`}
|
htmlFor={`${attribute.name}-${option}`}
|
||||||
className={`${classLabel}${attribute.readOnly ? ` ${kcClsx("kcInputClassRadioCheckboxLabelDisabled")}` : ""}`}
|
className={`${classLabel}${attribute.readOnly ? ` ${kcClsx("kcInputClassRadioCheckboxLabelDisabled")}` : ""}`}
|
||||||
>
|
>
|
||||||
{advancedMsg(option)}
|
{inputLabel(i18n, attribute, option)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -580,8 +578,6 @@ function TextareaTag(props: InputFieldByTypeProps) {
|
|||||||
function SelectTag(props: InputFieldByTypeProps) {
|
function SelectTag(props: InputFieldByTypeProps) {
|
||||||
const { attribute, dispatchFormAction, kcClsx, displayableErrors, i18n, valueOrValues } = props;
|
const { attribute, dispatchFormAction, kcClsx, displayableErrors, i18n, valueOrValues } = props;
|
||||||
|
|
||||||
const { advancedMsgStr } = i18n;
|
|
||||||
|
|
||||||
const isMultiple = attribute.annotations.inputType === "multiselect";
|
const isMultiple = attribute.annotations.inputType === "multiselect";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -645,22 +641,26 @@ function SelectTag(props: InputFieldByTypeProps) {
|
|||||||
|
|
||||||
return options.map(option => (
|
return options.map(option => (
|
||||||
<option key={option} value={option}>
|
<option key={option} value={option}>
|
||||||
{(() => {
|
{inputLabel(i18n, attribute, option)}
|
||||||
if (attribute.annotations.inputOptionLabels !== undefined) {
|
|
||||||
const { inputOptionLabels } = attribute.annotations;
|
|
||||||
|
|
||||||
return advancedMsgStr(inputOptionLabels[option] ?? option);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attribute.annotations.inputOptionLabelsI18nPrefix !== undefined) {
|
|
||||||
return advancedMsgStr(`${attribute.annotations.inputOptionLabelsI18nPrefix}.${option}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return option;
|
|
||||||
})()}
|
|
||||||
</option>
|
</option>
|
||||||
));
|
));
|
||||||
})()}
|
})()}
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inputLabel(i18n: I18n, attribute: Attribute, option: string) {
|
||||||
|
const { advancedMsg } = i18n;
|
||||||
|
|
||||||
|
if (attribute.annotations.inputOptionLabels !== undefined) {
|
||||||
|
const { inputOptionLabels } = attribute.annotations;
|
||||||
|
|
||||||
|
return advancedMsg(inputOptionLabels[option] ?? option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.annotations.inputOptionLabelsI18nPrefix !== undefined) {
|
||||||
|
return advancedMsg(`${attribute.annotations.inputOptionLabelsI18nPrefix}.${option}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,130 @@
|
|||||||
import { createGetKcClsx } from "keycloakify/lib/getKcClsx";
|
import { createGetKcClsx } from "keycloakify/lib/getKcClsx";
|
||||||
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
|
||||||
|
export type ClassKey =
|
||||||
|
| "kcBodyClass"
|
||||||
|
| "kcHeaderWrapperClass"
|
||||||
|
| "kcLocaleWrapperClass"
|
||||||
|
| "kcInfoAreaWrapperClass"
|
||||||
|
| "kcFormButtonsWrapperClass"
|
||||||
|
| "kcFormOptionsWrapperClass"
|
||||||
|
| "kcCheckboxInputClass"
|
||||||
|
| "kcLocaleDropDownClass"
|
||||||
|
| "kcLocaleListItemClass"
|
||||||
|
| "kcContentWrapperClass"
|
||||||
|
| "kcLogoIdP-facebook"
|
||||||
|
| "kcAuthenticatorOTPClass"
|
||||||
|
| "kcLogoIdP-bitbucket"
|
||||||
|
| "kcAuthenticatorWebAuthnClass"
|
||||||
|
| "kcWebAuthnDefaultIcon"
|
||||||
|
| "kcLogoIdP-stackoverflow"
|
||||||
|
| "kcSelectAuthListItemClass"
|
||||||
|
| "kcLogoIdP-microsoft"
|
||||||
|
| "kcLoginOTPListItemHeaderClass"
|
||||||
|
| "kcLocaleItemClass"
|
||||||
|
| "kcLoginOTPListItemIconBodyClass"
|
||||||
|
| "kcInputHelperTextAfterClass"
|
||||||
|
| "kcFormClass"
|
||||||
|
| "kcSelectAuthListClass"
|
||||||
|
| "kcInputClassRadioCheckboxLabelDisabled"
|
||||||
|
| "kcSelectAuthListItemIconClass"
|
||||||
|
| "kcRecoveryCodesWarning"
|
||||||
|
| "kcFormSettingClass"
|
||||||
|
| "kcWebAuthnBLE"
|
||||||
|
| "kcInputWrapperClass"
|
||||||
|
| "kcSelectAuthListItemArrowIconClass"
|
||||||
|
| "kcFeedbackAreaClass"
|
||||||
|
| "kcFormPasswordVisibilityButtonClass"
|
||||||
|
| "kcLogoIdP-google"
|
||||||
|
| "kcCheckLabelClass"
|
||||||
|
| "kcSelectAuthListItemFillClass"
|
||||||
|
| "kcAuthenticatorDefaultClass"
|
||||||
|
| "kcLogoIdP-gitlab"
|
||||||
|
| "kcFormAreaClass"
|
||||||
|
| "kcFormButtonsClass"
|
||||||
|
| "kcInputClassRadioLabel"
|
||||||
|
| "kcAuthenticatorWebAuthnPasswordlessClass"
|
||||||
|
| "kcSelectAuthListItemHeadingClass"
|
||||||
|
| "kcInfoAreaClass"
|
||||||
|
| "kcLogoLink"
|
||||||
|
| "kcContainerClass"
|
||||||
|
| "kcSelectAuthListItemTitle"
|
||||||
|
| "kcHtmlClass"
|
||||||
|
| "kcLoginOTPListItemTitleClass"
|
||||||
|
| "kcLogoIdP-openshift-v4"
|
||||||
|
| "kcWebAuthnUnknownIcon"
|
||||||
|
| "kcFormSocialAccountNameClass"
|
||||||
|
| "kcLogoIdP-openshift-v3"
|
||||||
|
| "kcLoginOTPListInputClass"
|
||||||
|
| "kcWebAuthnUSB"
|
||||||
|
| "kcInputClassRadio"
|
||||||
|
| "kcWebAuthnKeyIcon"
|
||||||
|
| "kcFeedbackInfoIcon"
|
||||||
|
| "kcCommonLogoIdP"
|
||||||
|
| "kcRecoveryCodesActions"
|
||||||
|
| "kcFormGroupHeader"
|
||||||
|
| "kcFormSocialAccountSectionClass"
|
||||||
|
| "kcLogoIdP-instagram"
|
||||||
|
| "kcAlertClass"
|
||||||
|
| "kcHeaderClass"
|
||||||
|
| "kcLabelWrapperClass"
|
||||||
|
| "kcFormPasswordVisibilityIconShow"
|
||||||
|
| "kcFormSocialAccountLinkClass"
|
||||||
|
| "kcLocaleMainClass"
|
||||||
|
| "kcInputGroup"
|
||||||
|
| "kcTextareaClass"
|
||||||
|
| "kcButtonBlockClass"
|
||||||
|
| "kcButtonClass"
|
||||||
|
| "kcWebAuthnNFC"
|
||||||
|
| "kcLocaleClass"
|
||||||
|
| "kcInputClassCheckboxInput"
|
||||||
|
| "kcFeedbackErrorIcon"
|
||||||
|
| "kcInputLargeClass"
|
||||||
|
| "kcInputErrorMessageClass"
|
||||||
|
| "kcRecoveryCodesList"
|
||||||
|
| "kcFormSocialAccountListClass"
|
||||||
|
| "kcAlertTitleClass"
|
||||||
|
| "kcAuthenticatorPasswordClass"
|
||||||
|
| "kcCheckInputClass"
|
||||||
|
| "kcLogoIdP-linkedin"
|
||||||
|
| "kcLogoIdP-twitter"
|
||||||
|
| "kcFeedbackWarningIcon"
|
||||||
|
| "kcResetFlowIcon"
|
||||||
|
| "kcSelectAuthListItemIconPropertyClass"
|
||||||
|
| "kcFeedbackSuccessIcon"
|
||||||
|
| "kcLoginOTPListClass"
|
||||||
|
| "kcSrOnlyClass"
|
||||||
|
| "kcFormSocialAccountListGridClass"
|
||||||
|
| "kcButtonDefaultClass"
|
||||||
|
| "kcFormGroupErrorClass"
|
||||||
|
| "kcSelectAuthListItemDescriptionClass"
|
||||||
|
| "kcSelectAuthListItemBodyClass"
|
||||||
|
| "kcWebAuthnInternal"
|
||||||
|
| "kcSelectAuthListItemArrowClass"
|
||||||
|
| "kcCheckClass"
|
||||||
|
| "kcContentClass"
|
||||||
|
| "kcLogoClass"
|
||||||
|
| "kcLoginOTPListItemIconClass"
|
||||||
|
| "kcLoginClass"
|
||||||
|
| "kcSignUpClass"
|
||||||
|
| "kcButtonLargeClass"
|
||||||
|
| "kcFormCardClass"
|
||||||
|
| "kcLocaleListClass"
|
||||||
|
| "kcInputClass"
|
||||||
|
| "kcFormGroupClass"
|
||||||
|
| "kcLogoIdP-paypal"
|
||||||
|
| "kcInputClassCheckbox"
|
||||||
|
| "kcRecoveryCodesConfirmation"
|
||||||
|
| "kcFormPasswordVisibilityIconHide"
|
||||||
|
| "kcInputClassRadioInput"
|
||||||
|
| "kcFormSocialAccountListButtonClass"
|
||||||
|
| "kcInputClassCheckboxLabel"
|
||||||
|
| "kcFormOptionsClass"
|
||||||
|
| "kcFormHeaderClass"
|
||||||
|
| "kcFormSocialAccountGridItem"
|
||||||
|
| "kcButtonPrimaryClass"
|
||||||
|
| "kcInputHelperTextBeforeClass"
|
||||||
|
| "kcLogoIdP-github"
|
||||||
|
| "kcLabelClass";
|
||||||
|
|
||||||
export const { getKcClsx } = createGetKcClsx<ClassKey>({
|
export const { getKcClsx } = createGetKcClsx<ClassKey>({
|
||||||
defaultClasses: {
|
defaultClasses: {
|
||||||
@ -138,6 +263,4 @@ export const { getKcClsx } = createGetKcClsx<ClassKey>({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export type { ClassKey };
|
|
||||||
|
|
||||||
export type KcClsx = ReturnType<typeof getKcClsx>["kcClsx"];
|
export type KcClsx = ReturnType<typeof getKcClsx>["kcClsx"];
|
||||||
|
@ -166,7 +166,7 @@ export function keycloakify(params: keycloakify.Params) {
|
|||||||
[
|
[
|
||||||
`(`,
|
`(`,
|
||||||
`(window.kcContext === undefined || import.meta.env.MODE === "development")?`,
|
`(window.kcContext === undefined || import.meta.env.MODE === "development")?`,
|
||||||
`"${urlPathname ?? "/"}":`,
|
`import.meta.env.BASE_URL:`,
|
||||||
`(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`,
|
`(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`,
|
||||||
`)`
|
`)`
|
||||||
].join("")
|
].join("")
|
||||||
|
@ -115,6 +115,38 @@ export const WithFavoritePet: Story = {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const WithNewsletter: Story = {
|
||||||
|
render: () => (
|
||||||
|
<KcPageStory
|
||||||
|
kcContext={{
|
||||||
|
profile: {
|
||||||
|
attributesByName: {
|
||||||
|
newsletter: {
|
||||||
|
name: "newsletter",
|
||||||
|
displayName: "Sign up to the newsletter",
|
||||||
|
validators: {
|
||||||
|
options: {
|
||||||
|
options: ["yes"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
inputOptionLabels: {
|
||||||
|
"yes": "I want my email inbox filled with spam"
|
||||||
|
},
|
||||||
|
inputType: "multiselect-checkboxes"
|
||||||
|
},
|
||||||
|
required: false,
|
||||||
|
readOnly: false
|
||||||
|
} satisfies Attribute
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const WithEmailAsUsername: Story = {
|
export const WithEmailAsUsername: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<KcPageStory
|
<KcPageStory
|
||||||
|
25
yarn.lock
25
yarn.lock
@ -2545,6 +2545,14 @@
|
|||||||
"@jridgewell/resolve-uri" "^3.1.0"
|
"@jridgewell/resolve-uri" "^3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||||
|
|
||||||
|
"@keycloakify/angular@^0.0.1-rc.19":
|
||||||
|
version "0.0.1-rc.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/@keycloakify/angular/-/angular-0.0.1-rc.19.tgz#56edbfc74dc66d6ea23ff3b0d2e5ce48fad200c6"
|
||||||
|
integrity sha512-C2etRoAv2jCv+BMaIaJcEBKWEV8uM47pUpO37ZRFj78jChM+o8a0/Q+ooadkidKupvOe9K5SB91fdL3aUwTyEw==
|
||||||
|
dependencies:
|
||||||
|
tsafe "^1.7.5"
|
||||||
|
tslib "^2.3.0"
|
||||||
|
|
||||||
"@mdx-js/mdx@^1.6.22":
|
"@mdx-js/mdx@^1.6.22":
|
||||||
version "1.6.22"
|
version "1.6.22"
|
||||||
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba"
|
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba"
|
||||||
@ -3722,6 +3730,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||||
|
|
||||||
|
"@types/properties-parser@^0.3.3":
|
||||||
|
version "0.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/properties-parser/-/properties-parser-0.3.3.tgz#107eef7e637c969ae1fe6ed86f373a57309bdd2c"
|
||||||
|
integrity sha512-VZhGpE+QQ2JNbaY4B4Y2iM/jdUsq3HO75uBKLk07VT6P2Kg1aifeYL6I3RosFniSdAb4PtuH5UaY8jXU7JeIYA==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/qs@^6.9.5":
|
"@types/qs@^6.9.5":
|
||||||
version "6.9.7"
|
version "6.9.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||||
@ -12572,6 +12587,11 @@ tsafe@^1.6.6:
|
|||||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.6.tgz#fd93e64d6eb13ef83ed1650669cc24bad4f5df9f"
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.6.tgz#fd93e64d6eb13ef83ed1650669cc24bad4f5df9f"
|
||||||
integrity sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g==
|
integrity sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g==
|
||||||
|
|
||||||
|
tsafe@^1.7.5:
|
||||||
|
version "1.7.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.7.5.tgz#0d3a31202b5ef87c7ba997e66e03fd80801278ef"
|
||||||
|
integrity sha512-tbNyyBSbwfbilFfiuXkSOj82a6++ovgANwcoqBAcO9/REPoZMEQoE8kWPeO0dy5A2D/2Lajr8Ohue5T0ifIvLQ==
|
||||||
|
|
||||||
tsc-alias@^1.8.10:
|
tsc-alias@^1.8.10:
|
||||||
version "1.8.10"
|
version "1.8.10"
|
||||||
resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.10.tgz#279f9bf0dd8bc10fb27820393d4881db5a303938"
|
resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.10.tgz#279f9bf0dd8bc10fb27820393d4881db5a303938"
|
||||||
@ -12594,6 +12614,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||||
|
|
||||||
|
tslib@^2.3.0:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
|
||||||
|
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
|
||||||
|
|
||||||
tss-react@^4.9.10:
|
tss-react@^4.9.10:
|
||||||
version "4.9.10"
|
version "4.9.10"
|
||||||
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-4.9.10.tgz#287392f4459116ac83c51fc117fad138ac06f139"
|
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-4.9.10.tgz#287392f4459116ac83c51fc117fad138ac06f139"
|
||||||
|
Reference in New Issue
Block a user