Compare commits

..

1 Commits

Author SHA1 Message Date
daf95b3dbb wip 2024-10-05 05:57:14 +02:00
39 changed files with 393 additions and 970 deletions

View File

@ -290,16 +290,6 @@
"code", "code",
"test" "test"
] ]
},
{
"login": "nima70",
"name": "Nima Shokouhfar",
"avatar_url": "https://avatars.githubusercontent.com/u/5094767?v=4",
"profile": "https://github.com/nima70",
"contributions": [
"code",
"test"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@ -43,11 +43,6 @@
Keycloakify is fully compatible with Keycloak from version 11 to 26...[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)
> 📣 **Keycloakify 26 Released**
> Themes built with Keycloakify versions **prior** to Keycloak 26 are **incompatible** with Keycloak 26.
> To ensure compatibility, simply upgrade to the latest Keycloakify version for your major release (v10 or v11) and rebuild your theme.
> No breaking changes have been introduced, but the target version ranges have been updated. For more details, see [this guide](https://docs.keycloakify.dev/targeting-specific-keycloak-versions).
## Sponsors ## Sponsors
Friends for the project, we trust and recommend their services. Friends for the project, we trust and recommend their services.
@ -141,7 +136,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<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/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> <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>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nima70"><img src="https://avatars.githubusercontent.com/u/5094767?v=4?s=100" width="100px;" alt="Nima Shokouhfar"/><br /><sub><b>Nima Shokouhfar</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=nima70" title="Code">💻</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=nima70" title="Tests">⚠️</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,6 +1,6 @@
{ {
"name": "keycloakify", "name": "keycloakify",
"version": "11.3.5", "version": "11.2.10",
"description": "Framework to create custom Keycloak UIs", "description": "Framework to create custom Keycloak UIs",
"repository": { "repository": {
"type": "git", "type": "git",
@ -38,14 +38,12 @@
"dist/", "dist/",
"!dist/tsconfig.tsbuildinfo", "!dist/tsconfig.tsbuildinfo",
"!dist/bin/", "!dist/bin/",
"dist/bin/**/*.d.ts",
"dist/bin/main.js", "dist/bin/main.js",
"dist/bin/*.index.js", "dist/bin/*.index.js",
"dist/bin/*.node", "dist/bin/*.node",
"dist/bin/shared/constants.js", "dist/bin/shared/constants.js",
"dist/bin/shared/constants.js.map", "dist/bin/shared/*.d.ts",
"dist/bin/shared/customHandler.js", "dist/bin/shared/*.js.map",
"dist/bin/shared/customHandler.js.map",
"!dist/vite-plugin/", "!dist/vite-plugin/",
"dist/vite-plugin/index.js", "dist/vite-plugin/index.js",
"dist/vite-plugin/index.d.ts", "dist/vite-plugin/index.d.ts",
@ -73,6 +71,7 @@
"@babel/preset-env": "7.24.8", "@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",

View File

@ -13,11 +13,16 @@ import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path"; import { join as pathJoin, relative as pathRelative, dirname as pathDirname } 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 type { BuildContext } from "./shared/buildContext"; import type { CliCommandOptions } from "./main";
import { getBuildContext } from "./shared/buildContext";
import chalk from "chalk"; import chalk from "chalk";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { buildContext } = params; const { cliCommandOptions } = params;
const buildContext = getBuildContext({
cliCommandOptions
});
console.log(chalk.cyan("Theme type:")); console.log(chalk.cyan("Theme type:"));

View File

@ -1,96 +1,13 @@
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate"; import { copyKeycloakResourcesToPublic } from "./shared/copyKeycloakResourcesToPublic";
import { join as pathJoin, dirname as pathDirname } from "path"; import { getBuildContext } from "./shared/buildContext";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "./shared/constants"; import type { CliCommandOptions } from "./main";
import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
import * as fs from "fs";
import { rmSync } from "./tools/fs.rmSync";
import type { BuildContext } from "./shared/buildContext";
import { transformCodebase } from "./tools/transformCodebase";
import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { buildContext } = params; const { cliCommandOptions } = params;
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ const buildContext = getBuildContext({ cliCommandOptions });
commandName: "copy-keycloak-resources-to-public",
copyKeycloakResourcesToPublic({
buildContext buildContext
}); });
if (hasBeenHandled) {
return;
}
const destDirPath = pathJoin(
buildContext.publicDirPath,
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
);
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
const keycloakifyBuildinfoRaw = JSON.stringify(
{
keycloakifyVersion: readThisNpmPackageVersion()
},
null,
2
);
skip_if_already_done: {
if (!fs.existsSync(keycloakifyBuildinfoFilePath)) {
break skip_if_already_done;
}
const keycloakifyBuildinfoRaw_previousRun = fs
.readFileSync(keycloakifyBuildinfoFilePath)
.toString("utf8");
if (keycloakifyBuildinfoRaw_previousRun !== keycloakifyBuildinfoRaw) {
break skip_if_already_done;
}
return;
}
rmSync(destDirPath, { force: true, recursive: true });
// NOTE: To remove in a while, remove the legacy keycloak-resources directory
rmSync(pathJoin(pathDirname(destDirPath), "keycloak-resources"), {
force: true,
recursive: true
});
rmSync(pathJoin(pathDirname(destDirPath), ".keycloakify"), {
force: true,
recursive: true
});
fs.mkdirSync(destDirPath, { recursive: true });
fs.writeFileSync(pathJoin(destDirPath, ".gitignore"), Buffer.from("*", "utf8"));
transformCodebase({
srcDirPath: pathJoin(
getThisCodebaseRootDirPath(),
"res",
"public",
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
),
destDirPath
});
fs.writeFileSync(
pathJoin(destDirPath, "README.txt"),
Buffer.from(
// prettier-ignore
[
"This directory is only used in dev mode by Keycloakify",
"It won't be included in your final build.",
"Do not modify anything in this directory.",
].join("\n")
)
);
fs.writeFileSync(
keycloakifyBuildinfoFilePath,
Buffer.from(keycloakifyBuildinfoRaw, "utf8")
);
} }

View File

@ -20,22 +20,17 @@ import {
} from "path"; } 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 type { BuildContext } from "./shared/buildContext"; import type { CliCommandOptions } from "./main";
import { getBuildContext } from "./shared/buildContext";
import chalk from "chalk"; import chalk from "chalk";
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { buildContext } = params; const { cliCommandOptions } = params;
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ const buildContext = getBuildContext({
commandName: "eject-page", cliCommandOptions
buildContext
}); });
if (hasBeenHandled) {
return;
}
console.log(chalk.cyan("Theme type:")); console.log(chalk.cyan("Theme type:"));
const themeType = await (async () => { const themeType = await (async () => {
@ -249,12 +244,12 @@ export async function command(params: { buildContext: BuildContext }) {
)} copy pasted from the Keycloakify source code into your project` )} copy pasted from the Keycloakify source code into your project`
); );
edit_KcPage: { edit_KcApp: {
if ( if (
pageIdOrComponent !== templateValue && pageIdOrComponent !== templateValue &&
pageIdOrComponent !== userProfileFormFieldsValue pageIdOrComponent !== userProfileFormFieldsValue
) { ) {
break edit_KcPage; break edit_KcApp;
} }
const kcAppTsxPath = pathJoin( const kcAppTsxPath = pathJoin(

View File

View File

View File

@ -1,24 +1,17 @@
import type { BuildContext } from "../shared/buildContext"; import { getBuildContext } from "../shared/buildContext";
import type { CliCommandOptions } from "../main";
import cliSelect from "cli-select"; import cliSelect from "cli-select";
import child_process from "child_process"; import child_process from "child_process";
import chalk from "chalk"; import chalk from "chalk";
import { join as pathJoin, relative as pathRelative } from "path"; import { join as pathJoin, relative as pathRelative } from "path";
import * as fs from "fs"; import * as fs from "fs";
import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig"; import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig";
import { command as updateKcGenCommand } from "../update-kc-gen"; import { generateKcGenTs } from "../shared/generateKcGenTs";
import { maybeDelegateCommandToCustomHandler } from "../shared/customHandler_delegate";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { buildContext } = params; const { cliCommandOptions } = params;
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ const buildContext = getBuildContext({ cliCommandOptions });
commandName: "initialize-account-theme",
buildContext
});
if (hasBeenHandled) {
return;
}
const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account"); const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
@ -104,7 +97,7 @@ export async function command(params: { buildContext: BuildContext }) {
updateAccountThemeImplementationInConfig({ buildContext, accountThemeType }); updateAccountThemeImplementationInConfig({ buildContext, accountThemeType });
await updateKcGenCommand({ await generateKcGenTs({
buildContext: { buildContext: {
...buildContext, ...buildContext,
implementedThemeTypes: { implementedThemeTypes: {

View File

@ -5,18 +5,15 @@ import * as fs from "fs";
import chalk from "chalk"; import chalk from "chalk";
import { z } from "zod"; import { z } from "zod";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import { is } from "tsafe/is";
export type BuildContextLike = { export type BuildContextLike = {
bundler: BuildContext["bundler"]; bundler: BuildContext["bundler"];
projectDirPath: string;
packageJsonFilePath: string;
}; };
assert<BuildContext extends BuildContextLike ? true : false>(); assert<BuildContext extends BuildContextLike ? true : false>();
export function updateAccountThemeImplementationInConfig(params: { export function updateAccountThemeImplementationInConfig(params: {
buildContext: BuildContextLike; buildContext: BuildContext;
accountThemeType: "Single-Page" | "Multi-Page"; accountThemeType: "Single-Page" | "Multi-Page";
}) { }) {
const { buildContext, accountThemeType } = params; const { buildContext, accountThemeType } = params;
@ -84,8 +81,6 @@ export function updateAccountThemeImplementationInConfig(params: {
zParsedPackageJson.parse(parsedPackageJson); zParsedPackageJson.parse(parsedPackageJson);
assert(is<ParsedPackageJson>(parsedPackageJson));
return parsedPackageJson; return parsedPackageJson;
})(); })();

View File

@ -1,22 +1,15 @@
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 "./shared/promptKeycloakVersion"; import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
import type { BuildContext } from "./shared/buildContext"; import { getBuildContext } from "./shared/buildContext";
import * as fs from "fs"; import * as fs from "fs";
import type { CliCommandOptions } from "./main";
import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive"; import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive";
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { buildContext } = params; const { cliCommandOptions } = params;
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ const buildContext = getBuildContext({ cliCommandOptions });
commandName: "initialize-email-theme",
buildContext
});
if (hasBeenHandled) {
return;
}
const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email"); const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");

View File

@ -2,16 +2,19 @@ import { generateResources } from "./generateResources";
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path"; import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
import * as child_process from "child_process"; import * as child_process from "child_process";
import * as fs from "fs"; import * as fs from "fs";
import type { BuildContext } from "../shared/buildContext"; import { getBuildContext } from "../shared/buildContext";
import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../shared/constants"; import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../shared/constants";
import { buildJars } from "./buildJars"; import { buildJars } from "./buildJars";
import type { CliCommandOptions } from "../main";
import chalk from "chalk"; import chalk from "chalk";
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion"; import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import * as os from "os"; import * as os from "os";
import { rmSync } from "../tools/fs.rmSync"; import { rmSync } from "../tools/fs.rmSync";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { buildContext } = params; const { cliCommandOptions } = params;
const buildContext = getBuildContext({ cliCommandOptions });
exit_if_maven_not_installed: { exit_if_maven_not_installed: {
let commandOutput: Buffer | undefined = undefined; let commandOutput: Buffer | undefined = undefined;

View File

@ -4,9 +4,8 @@ import { termost } from "termost";
import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion"; import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
import * as child_process from "child_process"; import * as child_process from "child_process";
import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx"; import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx";
import { getBuildContext } from "./shared/buildContext";
type CliCommandOptions = { export type CliCommandOptions = {
projectDirPath: string | undefined; projectDirPath: string | undefined;
}; };
@ -70,10 +69,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath }) => { handler: async cliCommandOptions => {
const { command } = await import("./keycloakify"); const { command } = await import("./keycloakify");
await command({ buildContext: getBuildContext({ projectDirPath }) }); await command({ cliCommandOptions });
} }
}); });
@ -131,13 +130,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath, keycloakVersion, port, realmJsonFilePath }) => { handler: async cliCommandOptions => {
const { command } = await import("./start-keycloak"); const { command } = await import("./start-keycloak");
await command({ await command({ cliCommandOptions });
buildContext: getBuildContext({ projectDirPath }),
cliCommandOptions: { keycloakVersion, port, realmJsonFilePath }
});
} }
}); });
@ -148,10 +144,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath }) => { handler: async cliCommandOptions => {
const { command } = await import("./eject-page"); const { command } = await import("./eject-page");
await command({ buildContext: getBuildContext({ projectDirPath }) }); await command({ cliCommandOptions });
} }
}); });
@ -162,10 +158,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath }) => { handler: async cliCommandOptions => {
const { command } = await import("./add-story"); const { command } = await import("./add-story");
await command({ buildContext: getBuildContext({ projectDirPath }) }); await command({ cliCommandOptions });
} }
}); });
@ -176,10 +172,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath }) => { handler: async cliCommandOptions => {
const { command } = await import("./initialize-email-theme"); const { command } = await import("./initialize-email-theme");
await command({ buildContext: getBuildContext({ projectDirPath }) }); await command({ cliCommandOptions });
} }
}); });
@ -190,10 +186,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath }) => { handler: async cliCommandOptions => {
const { command } = await import("./initialize-account-theme"); const { command } = await import("./initialize-account-theme");
await command({ buildContext: getBuildContext({ projectDirPath }) }); await command({ cliCommandOptions });
} }
}); });
@ -205,10 +201,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath }) => { handler: async cliCommandOptions => {
const { command } = await import("./copy-keycloak-resources-to-public"); const { command } = await import("./copy-keycloak-resources-to-public");
await command({ buildContext: getBuildContext({ projectDirPath }) }); await command({ cliCommandOptions });
} }
}); });
@ -220,10 +216,10 @@ program
}) })
.task({ .task({
skip, skip,
handler: async ({ projectDirPath }) => { handler: async cliCommandOptions => {
const { command } = await import("./update-kc-gen"); const { command } = await import("./update-kc-gen");
await command({ buildContext: getBuildContext({ projectDirPath }) }); await command({ cliCommandOptions });
} }
}); });

View File

@ -7,6 +7,7 @@ import {
dirname as pathDirname dirname as pathDirname
} from "path"; } from "path";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath"; import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import type { CliCommandOptions } from "../main";
import { z } from "zod"; import { z } from "zod";
import * as fs from "fs"; import * as fs from "fs";
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
@ -23,7 +24,7 @@ import { objectEntries } from "tsafe/objectEntries";
import { type ThemeType } from "./constants"; 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 FetchOptionsLike } from "../tools/fetchProxyOptions"; import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
import { is } from "tsafe/is"; import { is } from "tsafe/is";
export type BuildContext = { export type BuildContext = {
@ -42,7 +43,7 @@ export type BuildContext = {
* In this case the urlPathname will be "/my-app/" */ * In this case the urlPathname will be "/my-app/" */
urlPathname: string | undefined; urlPathname: string | undefined;
assetsDirPath: string; assetsDirPath: string;
fetchOptions: FetchOptionsLike; fetchOptions: ProxyFetchOptions;
kcContextExclusionsFtlCode: string | undefined; kcContextExclusionsFtlCode: string | undefined;
environmentVariables: { name: string; default: string }[]; environmentVariables: { name: string; default: string }[];
themeSrcDirPath: string; themeSrcDirPath: string;
@ -128,12 +129,14 @@ export type ResolvedViteConfig = {
}; };
export function getBuildContext(params: { export function getBuildContext(params: {
projectDirPath: string | undefined; cliCommandOptions: CliCommandOptions;
}): BuildContext { }): BuildContext {
const { cliCommandOptions } = params;
const projectDirPath = const projectDirPath =
params.projectDirPath !== undefined cliCommandOptions.projectDirPath !== undefined
? getAbsoluteAndInOsFormatPath({ ? getAbsoluteAndInOsFormatPath({
pathIsh: params.projectDirPath, pathIsh: cliCommandOptions.projectDirPath,
cwd: process.cwd() cwd: process.cwd()
}) })
: process.cwd(); : process.cwd();

View File

@ -71,8 +71,3 @@ export type AccountThemePageId = (typeof ACCOUNT_THEME_PAGE_IDS)[number];
export const CONTAINER_NAME = "keycloak-keycloakify"; export const CONTAINER_NAME = "keycloak-keycloakify";
export const FALLBACK_LANGUAGE_TAG = "en"; export const FALLBACK_LANGUAGE_TAG = "en";
export const CUSTOM_HANDLER_ENV_NAMES = {
COMMAND_NAME: "KEYCLOAKIFY_COMMAND_NAME",
BUILD_CONTEXT: "KEYCLOAKIFY_BUILD_CONTEXT"
};

View File

@ -0,0 +1,95 @@
import { join as pathJoin, dirname as pathDirname } from "path";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../shared/constants";
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import { assert } from "tsafe/assert";
import * as fs from "fs";
import { rmSync } from "../tools/fs.rmSync";
import type { BuildContext } from "./buildContext";
import { transformCodebase } from "../tools/transformCodebase";
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
export type BuildContextLike = {
publicDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export function copyKeycloakResourcesToPublic(params: {
buildContext: BuildContextLike;
}) {
const { buildContext } = params;
const destDirPath = pathJoin(
buildContext.publicDirPath,
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
);
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
const keycloakifyBuildinfoRaw = JSON.stringify(
{
keycloakifyVersion: readThisNpmPackageVersion()
},
null,
2
);
skip_if_already_done: {
if (!fs.existsSync(keycloakifyBuildinfoFilePath)) {
break skip_if_already_done;
}
const keycloakifyBuildinfoRaw_previousRun = fs
.readFileSync(keycloakifyBuildinfoFilePath)
.toString("utf8");
if (keycloakifyBuildinfoRaw_previousRun !== keycloakifyBuildinfoRaw) {
break skip_if_already_done;
}
return;
}
rmSync(destDirPath, { force: true, recursive: true });
// NOTE: To remove in a while, remove the legacy keycloak-resources directory
rmSync(pathJoin(pathDirname(destDirPath), "keycloak-resources"), {
force: true,
recursive: true
});
rmSync(pathJoin(pathDirname(destDirPath), ".keycloakify"), {
force: true,
recursive: true
});
fs.mkdirSync(destDirPath, { recursive: true });
fs.writeFileSync(pathJoin(destDirPath, ".gitignore"), Buffer.from("*", "utf8"));
transformCodebase({
srcDirPath: pathJoin(
getThisCodebaseRootDirPath(),
"res",
"public",
WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
),
destDirPath
});
fs.writeFileSync(
pathJoin(destDirPath, "README.txt"),
Buffer.from(
// prettier-ignore
[
"This directory is only used in dev mode by Keycloakify",
"It won't be included in your final build.",
"Do not modify anything in this directory.",
].join("\n")
)
);
fs.writeFileSync(
keycloakifyBuildinfoFilePath,
Buffer.from(keycloakifyBuildinfoRaw, "utf8")
);
}

View File

@ -1,41 +0,0 @@
import { assert } from "tsafe/assert";
import type { BuildContext } from "./buildContext";
import { CUSTOM_HANDLER_ENV_NAMES } from "./constants";
export const BIN_NAME = "_keycloakify-custom-handler";
export const NOT_IMPLEMENTED_EXIT_CODE = 78;
export type CommandName =
| "update-kc-gen"
| "eject-page"
| "add-story"
| "initialize-account-theme"
| "initialize-email-theme"
| "copy-keycloak-resources-to-public";
export type ApiVersion = "v1";
export function readParams(params: { apiVersion: ApiVersion }) {
const { apiVersion } = params;
assert(apiVersion === "v1");
const commandName = (() => {
const envValue = process.env[CUSTOM_HANDLER_ENV_NAMES.COMMAND_NAME];
assert(envValue !== undefined);
return envValue as CommandName;
})();
const buildContext = (() => {
const envValue = process.env[CUSTOM_HANDLER_ENV_NAMES.BUILD_CONTEXT];
assert(envValue !== undefined);
return JSON.parse(envValue) as BuildContext;
})();
return { commandName, buildContext };
}

View File

@ -1,46 +0,0 @@
import { assert, type Equals } from "tsafe/assert";
import type { BuildContext } from "./buildContext";
import { CUSTOM_HANDLER_ENV_NAMES } from "./constants";
import {
NOT_IMPLEMENTED_EXIT_CODE,
type CommandName,
BIN_NAME,
ApiVersion
} from "./customHandler";
import * as child_process from "child_process";
import { dirname as pathDirname } from "path";
import * as fs from "fs";
assert<Equals<ApiVersion, "v1">>();
export function maybeDelegateCommandToCustomHandler(params: {
commandName: CommandName;
buildContext: BuildContext;
}): { hasBeenHandled: boolean } {
const { commandName, buildContext } = params;
if (!fs.readdirSync(pathDirname(process.argv[1])).includes(BIN_NAME)) {
return { hasBeenHandled: false };
}
try {
child_process.execSync(`npx ${BIN_NAME}`, {
stdio: "inherit",
env: {
...process.env,
[CUSTOM_HANDLER_ENV_NAMES.COMMAND_NAME]: commandName,
[CUSTOM_HANDLER_ENV_NAMES.BUILD_CONTEXT]: JSON.stringify(buildContext)
}
});
} catch (error: any) {
const status = error.status;
if (status === NOT_IMPLEMENTED_EXIT_CODE) {
return { hasBeenHandled: false };
}
process.exit(status);
}
return { hasBeenHandled: true };
}

View File

@ -0,0 +1,175 @@
import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
import type { BuildContext } from "./buildContext";
import * as fs from "fs/promises";
import { join as pathJoin } from "path";
import { existsAsync } from "../tools/fs.existsAsync";
import { z } from "zod";
export type BuildContextLike = {
projectDirPath: string;
themeNames: string[];
environmentVariables: { name: string; default: string }[];
themeSrcDirPath: string;
implementedThemeTypes: Pick<
BuildContext["implementedThemeTypes"],
"login" | "account"
>;
packageJsonFilePath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function generateKcGenTs(params: {
buildContext: BuildContextLike;
}): Promise<void> {
const { buildContext } = params;
const isReactProject: boolean = await (async () => {
const parsedPackageJson = await (async () => {
type ParsedPackageJson = {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};
const zParsedPackageJson = (() => {
type TargetType = ParsedPackageJson;
const zTargetType = z.object({
dependencies: z.record(z.string()).optional(),
devDependencies: z.record(z.string()).optional()
});
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
return id<z.ZodType<TargetType>>(zTargetType);
})();
return zParsedPackageJson.parse(
JSON.parse(
(await fs.readFile(buildContext.packageJsonFilePath)).toString("utf8")
)
);
})();
return (
{
...parsedPackageJson.dependencies,
...parsedPackageJson.devDependencies
}.react !== undefined
);
})();
const filePath = pathJoin(
buildContext.themeSrcDirPath,
`kc.gen.ts${isReactProject ? "x" : ""}`
);
const currentContent = (await existsAsync(filePath))
? await fs.readFile(filePath)
: undefined;
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
const newContent = Buffer.from(
[
`/* prettier-ignore-start */`,
``,
`/* eslint-disable */`,
``,
`// @ts-nocheck`,
``,
`// noinspection JSUnusedGlobalSymbols`,
``,
`// This file is auto-generated by Keycloakify`,
``,
isReactProject && `import { lazy, Suspense, type ReactNode } from "react";`,
``,
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
``,
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
``,
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
``,
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
``,
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
Object.fromEntries(
buildContext.environmentVariables.map(
({ name, default: defaultValue }) => [name, defaultValue]
)
),
null,
2
)};`,
``,
`export type KcContext =`,
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
` ;`,
``,
`declare global {`,
` interface Window {`,
` kcContext?: KcContext;`,
` }`,
`}`,
``,
...(!isReactProject
? []
: [
hasLoginTheme &&
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
hasAccountTheme &&
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
``,
`export function KcPage(`,
` props: {`,
` kcContext: KcContext;`,
` fallback?: ReactNode;`,
` }`,
`) {`,
` const { kcContext, fallback } = props;`,
` return (`,
` <Suspense fallback={fallback}>`,
` {(() => {`,
` switch (kcContext.themeType) {`,
hasLoginTheme &&
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
hasAccountTheme &&
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
` }`,
` })()}`,
` </Suspense>`,
` );`,
`}`
]),
``,
`/* prettier-ignore-end */`,
``
]
.filter(item => typeof item === "string")
.join("\n"),
"utf8"
);
if (currentContent !== undefined && currentContent.equals(newContent)) {
return;
}
await fs.writeFile(filePath, newContent);
delete_legacy_file: {
if (!isReactProject) {
break delete_legacy_file;
}
const legacyFilePath = filePath.replace(/tsx$/, "ts");
if (!(await existsAsync(legacyFilePath))) {
break delete_legacy_file;
}
await fs.unlink(legacyFilePath);
}
}

View File

@ -1,5 +1,6 @@
import type { BuildContext } from "../shared/buildContext"; import { getBuildContext } from "../shared/buildContext";
import { exclude } from "tsafe/exclude"; import { exclude } from "tsafe/exclude";
import type { CliCommandOptions as CliCommandOptions_common } from "../main";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion"; import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { CONTAINER_NAME } from "../shared/constants"; import { CONTAINER_NAME } from "../shared/constants";
import { SemVer } from "../tools/SemVer"; import { SemVer } from "../tools/SemVer";
@ -28,14 +29,13 @@ import { existsAsync } from "../tools/fs.existsAsync";
import { rm } from "../tools/fs.rm"; import { rm } from "../tools/fs.rm";
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive"; import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
export async function command(params: { export type CliCommandOptions = CliCommandOptions_common & {
buildContext: BuildContext; port: number | undefined;
cliCommandOptions: { keycloakVersion: string | undefined;
port: number | undefined; realmJsonFilePath: string | undefined;
keycloakVersion: string | undefined; };
realmJsonFilePath: string | undefined;
}; export async function command(params: { cliCommandOptions: CliCommandOptions }) {
}) {
exit_if_docker_not_installed: { exit_if_docker_not_installed: {
let commandOutput: string | undefined = undefined; let commandOutput: string | undefined = undefined;
@ -88,7 +88,9 @@ export async function command(params: {
process.exit(1); process.exit(1);
} }
const { cliCommandOptions, buildContext } = params; const { cliCommandOptions } = params;
const buildContext = getBuildContext({ cliCommandOptions });
const { dockerImageTag } = await (async () => { const { dockerImageTag } = await (async () => {
if (cliCommandOptions.keycloakVersion !== undefined) { if (cliCommandOptions.keycloakVersion !== undefined) {

View File

@ -1,18 +1,16 @@
import { type FetchOptions } from "make-fetch-happen";
import * as child_process from "child_process"; import * as child_process from "child_process";
import * as fs from "fs"; import * as fs from "fs";
import { exclude } from "tsafe/exclude"; import { exclude } from "tsafe/exclude";
export type FetchOptionsLike = { export type ProxyFetchOptions = Pick<
proxy: string | undefined; FetchOptions,
noProxy: string | string[]; "proxy" | "noProxy" | "strictSSL" | "cert" | "ca"
strictSSL: boolean; >;
cert: string | string[] | undefined;
ca: string[] | undefined;
};
export function getProxyFetchOptions(params: { export function getProxyFetchOptions(params: {
npmConfigGetCwd: string; npmConfigGetCwd: string;
}): FetchOptionsLike { }): ProxyFetchOptions {
const { npmConfigGetCwd } = params; const { npmConfigGetCwd } = params;
const cfg = (() => { const cfg = (() => {

View File

@ -1,120 +1,13 @@
import type { BuildContext } from "./shared/buildContext"; import type { CliCommandOptions } from "./main";
import * as fs from "fs/promises"; import { getBuildContext } from "./shared/buildContext";
import { join as pathJoin } from "path"; import { generateKcGenTs } from "./shared/generateKcGenTs";
import { existsAsync } from "./tools/fs.existsAsync";
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { buildContext } = params; const { cliCommandOptions } = params;
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ const buildContext = getBuildContext({
commandName: "update-kc-gen", cliCommandOptions
buildContext
}); });
if (hasBeenHandled) { await generateKcGenTs({ buildContext });
return;
}
const filePath = pathJoin(buildContext.themeSrcDirPath, `kc.gen.tsx`);
const currentContent = (await existsAsync(filePath))
? await fs.readFile(filePath)
: undefined;
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
const newContent = Buffer.from(
[
`/* prettier-ignore-start */`,
``,
`/* eslint-disable */`,
``,
`// @ts-nocheck`,
``,
`// noinspection JSUnusedGlobalSymbols`,
``,
`// This file is auto-generated by Keycloakify`,
``,
`import { lazy, Suspense, type ReactNode } from "react";`,
``,
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
``,
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
``,
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
``,
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
``,
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
Object.fromEntries(
buildContext.environmentVariables.map(
({ name, default: defaultValue }) => [name, defaultValue]
)
),
null,
2
)};`,
``,
`export type KcContext =`,
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
` ;`,
``,
`declare global {`,
` interface Window {`,
` kcContext?: KcContext;`,
` }`,
`}`,
``,
hasLoginTheme &&
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
hasAccountTheme &&
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
``,
`export function KcPage(`,
` props: {`,
` kcContext: KcContext;`,
` fallback?: ReactNode;`,
` }`,
`) {`,
` const { kcContext, fallback } = props;`,
` return (`,
` <Suspense fallback={fallback}>`,
` {(() => {`,
` switch (kcContext.themeType) {`,
hasLoginTheme &&
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
hasAccountTheme &&
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
` }`,
` })()}`,
` </Suspense>`,
` );`,
`}`,
``,
`/* prettier-ignore-end */`,
``
]
.filter(item => typeof item === "string")
.join("\n"),
"utf8"
);
if (currentContent !== undefined && currentContent.equals(newContent)) {
return;
}
await fs.writeFile(filePath, newContent);
delete_legacy_file: {
const legacyFilePath = filePath.replace(/tsx$/, "ts");
if (!(await existsAsync(legacyFilePath))) {
break delete_legacy_file;
}
await fs.unlink(legacyFilePath);
}
} }

View File

@ -94,7 +94,6 @@ export declare namespace KcContext {
languageTag: string; languageTag: string;
}[]; }[];
currentLanguageTag: string; currentLanguageTag: string;
rtl?: boolean;
}; };
auth?: { auth?: {
showUsername?: boolean; showUsername?: boolean;

View File

@ -19,7 +19,6 @@ export type KcContextLike = {
locale?: { locale?: {
currentLanguageTag: string; currentLanguageTag: string;
supported: { languageTag: string; url: string; label: string }[]; supported: { languageTag: string; url: string; label: string }[];
rtl?: boolean;
}; };
"x-keycloakify": { "x-keycloakify": {
messages: Record<string, string>; messages: Record<string, string>;
@ -96,49 +95,6 @@ export function createGetI18n<
const html = document.querySelector("html"); const html = document.querySelector("html");
assert(html !== null); assert(html !== null);
html.lang = currentLanguageTag; html.lang = currentLanguageTag;
const isRtl = (() => {
const { rtl } = kcContext.locale ?? {};
if (rtl !== undefined) {
return rtl;
}
return [
/* spell-checker: disable */
// Common RTL languages
"ar", // Arabic
"fa", // Persian (Farsi)
"he", // Hebrew
"ur", // Urdu
"ps", // Pashto
"syr", // Syriac
"dv", // Divehi (Maldivian)
"ku", // Kurdish (Sorani)
"ug", // Uighur
"az", // Azerbaijani (Arabic script)
"sd", // Sindhi
// Less common RTL languages
"yi", // Yiddish
"ha", // Hausa (when written in Arabic script)
"ks", // Kashmiri (written in the Perso-Arabic script)
"bal", // Balochi (when written in Arabic script)
"khw", // Khowar (Chitrali)
"brh", // Brahui (when written in Arabic script)
"tmh", // Tamashek (some dialects use Arabic script)
"bgn", // Western Balochi
"arc", // Aramaic
"sam", // Samaritan Aramaic
"prd", // Parsi-Dari (a dialect of Persian)
"huz", // Hazaragi (a dialect of Persian)
"gbz", // Zaza (written in Arabic script in some areas)
"urj" // Urdu in Romanized script (not always RTL, but to account for edge cases)
/* spell-checker: enable */
].includes(currentLanguageTag);
})();
html.dir = isRtl ? "rtl" : "ltr";
} }
const getLanguageLabel = (languageTag: LanguageTag) => { const getLanguageLabel = (languageTag: LanguageTag) => {

View File

@ -2,7 +2,6 @@ import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps"; import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext"; import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>, I18n>) { export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@ -31,14 +30,7 @@ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code
<input id="code" className={kcClsx("kcTextareaClass")} defaultValue={code.code} /> <input id="code" className={kcClsx("kcTextareaClass")} defaultValue={code.code} />
</> </>
) : ( ) : (
code.error && ( <p id="error">{code.error}</p>
<p
id="error"
dangerouslySetInnerHTML={{
__html: kcSanitize(code.error)
}}
/>
)
)} )}
</div> </div>
</Template> </Template>

View File

@ -6,7 +6,7 @@ import {
} from "../bin/shared/constants"; } from "../bin/shared/constants";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import { rm } from "../bin/tools/fs.rm"; import { rm } from "../bin/tools/fs.rm";
import { command as copyKeycloakResourcesToPublicCommand } from "../bin/copy-keycloak-resources-to-public"; import { copyKeycloakResourcesToPublic } from "../bin/shared/copyKeycloakResourcesToPublic";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { import {
getBuildContext, getBuildContext,
@ -15,7 +15,7 @@ import {
type ResolvedViteConfig type ResolvedViteConfig
} from "../bin/shared/buildContext"; } from "../bin/shared/buildContext";
import MagicString from "magic-string"; import MagicString from "magic-string";
import { command as updateKcGenCommand } from "../bin/update-kc-gen"; import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
export namespace keycloakify { export namespace keycloakify {
export type Params = BuildOptions & { export type Params = BuildOptions & {
@ -122,12 +122,13 @@ export function keycloakify(params: keycloakify.Params) {
} }
const buildContext = getBuildContext({ const buildContext = getBuildContext({
projectDirPath cliCommandOptions: {
projectDirPath
}
}); });
await copyKeycloakResourcesToPublicCommand({ buildContext }); copyKeycloakResourcesToPublic({ buildContext }),
await generateKcGenTs({ buildContext });
await updateKcGenCommand({ buildContext });
}, },
transform: (code, id) => { transform: (code, id) => {
assert(command !== undefined); assert(command !== undefined);

View File

@ -16,42 +16,3 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
render: () => <KcPageStory /> render: () => <KcPageStory />
}; };
export const WithErrorCode: Story = {
render: () => (
<KcPageStory
kcContext={{
code: {
success: false,
error: "Failed to generate code"
}
}}
/>
)
};
export const WithFrenchLanguage: Story = {
render: () => (
<KcPageStory
kcContext={{
locale: {
currentLanguageTag: "fr"
},
code: {
success: true,
code: "XYZ789"
}
}}
/>
)
};
export const WithHtmlErrorMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
code: {
success: false,
error: "Something went wrong. <a href='https://example.com'>Try again</a>"
}
}}
/>
)
};

View File

@ -16,33 +16,3 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
render: () => <KcPageStory /> render: () => <KcPageStory />
}; };
export const WithAIAFlow: Story = {
render: () => (
<KcPageStory
kcContext={{
triggered_from_aia: true,
url: { loginAction: "/login-action" }
}}
/>
)
};
export const WithoutAIAFlow: Story = {
render: () => (
<KcPageStory
kcContext={{
triggered_from_aia: false,
url: { loginAction: "/login-action" }
}}
/>
)
};
export const WithCustomButtonStyle: Story = {
render: () => (
<KcPageStory
kcContext={{
triggered_from_aia: true,
url: { loginAction: "/login-action" }
}}
/>
)
};

View File

@ -16,13 +16,3 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
render: () => <KcPageStory /> render: () => <KcPageStory />
}; };
export const WithCustomCredentialLabel: Story = {
render: () => (
<KcPageStory
kcContext={{
credentialLabel: "Test Credential",
url: { loginAction: "/login-action" }
}}
/>
)
};

View File

@ -26,38 +26,3 @@ export const WithAnotherMessage: Story = {
/> />
) )
}; };
export const WithHtmlErrorMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
message: {
summary: "<strong>Error:</strong> Something went wrong. <a href='https://example.com'>Go back</a>"
}
}}
/>
)
};
export const FrenchError: Story = {
render: () => (
<KcPageStory
kcContext={{
locale: { currentLanguageTag: "fr" },
message: { summary: "Une erreur s'est produite" }
}}
/>
)
};
export const WithSkipLink: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "An error occurred" },
skipLink: true,
client: {
baseUrl: "https://example.com"
}
}}
/>
)
};

View File

@ -16,14 +16,3 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
render: () => <KcPageStory /> render: () => <KcPageStory />
}; };
export const WithoutRedirectUrl: Story = {
render: () => (
<KcPageStory
kcContext={{
logout: {
clients: []
}
}}
/>
)
};

View File

@ -16,47 +16,3 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
render: () => <KcPageStory /> render: () => <KcPageStory />
}; };
export const WithFormValidationErrors: Story = {
render: () => (
<KcPageStory
kcContext={{
messagesPerField: {
existsError: (fieldName: string) => ["email", "firstName"].includes(fieldName),
get: (fieldName: string) => {
if (fieldName === "email") return "Invalid email format.";
if (fieldName === "firstName") return "First name is required.";
}
}
}}
/>
)
};
export const WithReadOnlyFields: Story = {
render: () => (
<KcPageStory
kcContext={{
profile: {
attributesByName: {
email: { value: "jane.doe@example.com", readOnly: true },
firstName: { value: "Jane", readOnly: false }
}
}
}}
/>
)
};
export const WithPrefilledFormFields: Story = {
render: () => (
<KcPageStory
kcContext={{
profile: {
attributesByName: {
firstName: { value: "Jane" },
lastName: { value: "Doe" },
email: { value: "jane.doe@example.com" }
}
}
}}
/>
)
};

View File

@ -55,42 +55,3 @@ export const WithRequiredActions: Story = {
/> />
) )
}; };
export const WithPageRedirect: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "You will be redirected shortly." },
pageRedirectUri: "https://example.com"
}}
/>
)
};
export const WithoutClientBaseUrl: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "No client base URL defined." },
client: { baseUrl: undefined }
}}
/>
)
};
export const WithMessageHeader: Story = {
render: () => (
<KcPageStory
kcContext={{
messageHeader: "Important Notice",
message: { summary: "This is an important message." }
}}
/>
)
};
export const WithAdvancedMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "Please take note of this <strong>important</strong> information." }
}}
/>
)
};

View File

@ -231,131 +231,3 @@ export const WithErrorMessage: Story = {
/> />
) )
}; };
export const WithOneSocialProvider: Story = {
render: args => (
<KcPageStory
{...args}
kcContext={{
social: {
displayInfo: true,
providers: [
{
loginUrl: "google",
alias: "google",
providerId: "google",
displayName: "Google",
iconClasses: "fa fa-google"
}
]
}
}}
/>
)
};
export const WithTwoSocialProviders: Story = {
render: args => (
<KcPageStory
{...args}
kcContext={{
social: {
displayInfo: true,
providers: [
{
loginUrl: "google",
alias: "google",
providerId: "google",
displayName: "Google",
iconClasses: "fa fa-google"
},
{
loginUrl: "microsoft",
alias: "microsoft",
providerId: "microsoft",
displayName: "Microsoft",
iconClasses: "fa fa-windows"
}
]
}
}}
/>
)
};
export const WithNoSocialProviders: Story = {
render: args => (
<KcPageStory
{...args}
kcContext={{
social: {
displayInfo: true,
providers: []
}
}}
/>
)
};
export const WithMoreThanTwoSocialProviders: Story = {
render: args => (
<KcPageStory
{...args}
kcContext={{
social: {
displayInfo: true,
providers: [
{
loginUrl: "google",
alias: "google",
providerId: "google",
displayName: "Google",
iconClasses: "fa fa-google"
},
{
loginUrl: "microsoft",
alias: "microsoft",
providerId: "microsoft",
displayName: "Microsoft",
iconClasses: "fa fa-windows"
},
{
loginUrl: "facebook",
alias: "facebook",
providerId: "facebook",
displayName: "Facebook",
iconClasses: "fa fa-facebook"
},
{
loginUrl: "twitter",
alias: "twitter",
providerId: "twitter",
displayName: "Twitter",
iconClasses: "fa fa-twitter"
}
]
}
}}
/>
)
};
export const WithSocialProvidersAndWithoutRememberMe: Story = {
render: args => (
<KcPageStory
{...args}
kcContext={{
social: {
displayInfo: true,
providers: [
{
loginUrl: "google",
alias: "google",
providerId: "google",
displayName: "Google",
iconClasses: "fa fa-google"
}
]
},
realm: { rememberMe: false }
}}
/>
)
};

View File

@ -41,24 +41,3 @@ export const WithError: Story = {
/> />
) )
}; };
export const WithAppInitiatedAction: Story = {
render: () => (
<KcPageStory
kcContext={{
isAppInitiatedAction: true
}}
/>
)
};
export const WithPreFilledUserLabel: Story = {
render: () => (
<KcPageStory
kcContext={{
totp: {
otpCredentials: [{ userLabel: "MyDevice" }]
}
}}
/>
)
};

View File

@ -16,49 +16,3 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
render: () => <KcPageStory /> render: () => <KcPageStory />
}; };
export const WithIdpAlias: Story = {
render: () => (
<KcPageStory
kcContext={{
idpAlias: "Google",
brokerContext: {
username: "john.doe"
},
realm: {
displayName: "MyRealm"
}
}}
/>
)
};
export const WithoutIdpAlias: Story = {
render: () => (
<KcPageStory
kcContext={{
idpAlias: undefined,
brokerContext: {
username: "john.doe"
},
realm: {
displayName: "MyRealm"
}
}}
/>
)
};
export const WithCustomRealmDisplayName: Story = {
render: () => (
<KcPageStory
kcContext={{
idpAlias: "Facebook",
brokerContext: {
username: "jane.doe"
},
realm: {
displayName: "CustomRealm"
}
}}
/>
)
};

View File

@ -115,6 +115,7 @@ export const WithFavoritePet: Story = {
) )
}; };
export const WithNewsletter: Story = { export const WithNewsletter: Story = {
render: () => ( render: () => (
<KcPageStory <KcPageStory
@ -131,7 +132,7 @@ export const WithNewsletter: Story = {
}, },
annotations: { annotations: {
inputOptionLabels: { inputOptionLabels: {
yes: "I want my email inbox filled with spam" "yes": "I want my email inbox filled with spam"
}, },
inputType: "multiselect-checkboxes" inputType: "multiselect-checkboxes"
}, },
@ -139,12 +140,13 @@ export const WithNewsletter: Story = {
readOnly: false readOnly: false
} satisfies Attribute } satisfies Attribute
} }
} },
}} }}
/> />
) )
}; };
export const WithEmailAsUsername: Story = { export const WithEmailAsUsername: Story = {
render: () => ( render: () => (
<KcPageStory <KcPageStory
@ -215,65 +217,3 @@ export const WithTermsAcceptance: Story = {
/> />
) )
}; };
export const WithTermsNotAccepted: Story = {
render: args => (
<KcPageStory
{...args}
kcContext={{
termsAcceptanceRequired: true,
messagesPerField: {
existsError: (fieldName: string) => fieldName === "termsAccepted",
get: (fieldName: string) => (fieldName === "termsAccepted" ? "You must accept the terms." : undefined)
}
}}
/>
)
};
export const WithFieldErrors: Story = {
render: () => (
<KcPageStory
kcContext={{
profile: {
attributesByName: {
username: { value: "" },
email: { value: "invalid-email" }
}
},
messagesPerField: {
existsError: (fieldName: string) => ["username", "email"].includes(fieldName),
get: (fieldName: string) => {
if (fieldName === "username") return "Username is required.";
if (fieldName === "email") return "Invalid email format.";
}
}
}}
/>
)
};
export const WithReadOnlyFields: Story = {
render: () => (
<KcPageStory
kcContext={{
profile: {
attributesByName: {
username: { value: "johndoe", readOnly: true },
email: { value: "jhon.doe@gmail.com", readOnly: false }
}
}
}}
/>
)
};
export const WithAutoGeneratedUsername: Story = {
render: () => (
<KcPageStory
kcContext={{
profile: {
attributesByName: {
username: { value: "autogenerated_username" }
}
}
}}
/>
)
};

View File

@ -45,32 +45,3 @@ export const French: Story = {
/> />
) )
}; };
export const WithErrorMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
messagesPerField: {
existsError: () => true,
get: () => "An error occurred while processing your request."
}
}}
/>
)
};
export const Spanish: Story = {
render: () => (
<KcPageStory
kcContext={{
locale: {
currentLanguageTag: "es"
},
"x-keycloakify": {
messages: {
termsText: "<p>Mis términos en <strong>Español</strong></p>"
}
}
}}
/>
)
};

View File

@ -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"
@ -12606,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"