Compare commits
1 Commits
decouple_u
...
angular_in
Author | SHA1 | Date | |
---|---|---|---|
daf95b3dbb |
.all-contributorsrcREADME.mdpackage.json
src
bin
add-story.tscopy-keycloak-resources-to-public.ts
eject-page
initialize-account-theme
initialize-email-theme.tskeycloakify
main.tsshared
buildContext.tsconstants.tscopyKeycloakResourcesToPublic.tscustomHandler.tscustomHandler_delegate.tsgenerateKcGenTs.ts
start-keycloak
tools
update-kc-gen.tslogin
KcContext
i18n
noJsx
lib
pages
tools
vite-plugin
stories/login/pages
Code.stories.tsxDeleteAccountConfirm.stories.tsxDeleteCredential.stories.tsxError.stories.tsxFrontchannelLogout.stories.tsxIdpReviewUserProfile.stories.tsxInfo.stories.tsxLogin.stories.tsxLoginConfigTotp.stories.tsxLoginIdpLinkEmail.stories.tsxRegister.stories.tsxTerms.stories.tsx
yarn.lock@ -290,25 +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"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "marvinruder",
|
|
||||||
"name": "Marvin A. Ruder",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/18495294?v=4",
|
|
||||||
"profile": "https://mruder.dev",
|
|
||||||
"contributions": [
|
|
||||||
"bug"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
37
README.md
37
README.md
@ -43,48 +43,21 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
Project backers, we trust and recommend their services.
|
Friends for the project, we trust and recommend their services.
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<i><a href="https://phasetwo.io/?utm_source=keycloakify"><strong>Keycloak as a Service</strong></a> - Keycloak community contributors of popular <a href="https://github.com/p2-inc#our-extensions-?utm_source=keycloakify">extensions</a> providing free and dedicated <a href="https://phasetwo.io/hosting/?utm_source=keycloakify">Keycloak hosting</a> and enterprise <a href="https://phasetwo.io/support/?utm_source=keycloakify">Keycloak support</a> to businesses of all sizes.</i>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -109,7 +82,7 @@ Project backers, we trust and recommend their services.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github"><strong>Managed Keycloak Provider</strong> - With Cloud-IAM powering your Keycloak clusters, you can sleep easy knowing you've got the software and the experts you need for operational excellence. Cloud IAM is a french company. </a>
|
<a href="https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github"><strong>Managed Keycloak Provider</strong> - With Cloud-IAM powering your Keycloak clusters, you can sleep easy knowing you've got the software and the experts you need for operational excellence. </a>
|
||||||
<br/>
|
<br/>
|
||||||
Use code <code>keycloakify5</code> at checkout for a 5% discount.
|
Use code <code>keycloakify5</code> at checkout for a 5% discount.
|
||||||
</p>
|
</p>
|
||||||
@ -163,8 +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>
|
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://mruder.dev"><img src="https://avatars.githubusercontent.com/u/18495294?v=4?s=100" width="100px;" alt="Marvin A. Ruder"/><br /><sub><b>Marvin A. Ruder</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/issues?q=author%3Amarvinruder" title="Bug reports">🐛</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "11.3.9-rc.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",
|
||||||
@ -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",
|
||||||
|
@ -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:"));
|
||||||
|
|
||||||
|
@ -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")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -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(
|
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
@ -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: {
|
||||||
|
@ -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;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -1,25 +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";
|
|
||||||
import fetch from "make-fetch-happen";
|
|
||||||
import { SemVer } from "./tools/SemVer";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
@ -39,7 +29,7 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
|
|
||||||
console.log("Initialize with the base email theme from which version of Keycloak?");
|
console.log("Initialize with the base email theme from which version of Keycloak?");
|
||||||
|
|
||||||
let { keycloakVersion } = await promptKeycloakVersion({
|
const { keycloakVersion } = await promptKeycloakVersion({
|
||||||
// NOTE: This is arbitrary
|
// NOTE: This is arbitrary
|
||||||
startingFromMajor: 17,
|
startingFromMajor: 17,
|
||||||
excludeMajorVersions: [],
|
excludeMajorVersions: [],
|
||||||
@ -47,32 +37,8 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
|
|
||||||
const getUrl = (keycloakVersion: string) => {
|
|
||||||
return `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`;
|
|
||||||
};
|
|
||||||
|
|
||||||
keycloakVersion = await (async () => {
|
|
||||||
const keycloakVersionParsed = SemVer.parse(keycloakVersion);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const url = getUrl(SemVer.stringify(keycloakVersionParsed));
|
|
||||||
|
|
||||||
const response = await fetch(url, buildContext.fetchOptions);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(keycloakVersionParsed.patch !== 0);
|
|
||||||
|
|
||||||
keycloakVersionParsed.patch--;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SemVer.stringify(keycloakVersionParsed);
|
|
||||||
})();
|
|
||||||
|
|
||||||
const { extractedDirPath } = await downloadAndExtractArchive({
|
const { extractedDirPath } = await downloadAndExtractArchive({
|
||||||
url: getUrl(keycloakVersion),
|
url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
|
||||||
cacheDirPath: buildContext.cacheDirPath,
|
cacheDirPath: buildContext.cacheDirPath,
|
||||||
fetchOptions: buildContext.fetchOptions,
|
fetchOptions: buildContext.fetchOptions,
|
||||||
uniqueIdOfOnArchiveFile: "extractOnlyEmailTheme",
|
uniqueIdOfOnArchiveFile: "extractOnlyEmailTheme",
|
||||||
|
@ -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;
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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();
|
||||||
@ -508,15 +511,6 @@ export function getBuildContext(params: {
|
|||||||
return themeNames;
|
return themeNames;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const relativePathsCwd = (() => {
|
|
||||||
switch (bundler) {
|
|
||||||
case "vite":
|
|
||||||
return projectDirPath;
|
|
||||||
case "webpack":
|
|
||||||
return pathDirname(packageJsonFilePath);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
const projectBuildDirPath = (() => {
|
const projectBuildDirPath = (() => {
|
||||||
webpack: {
|
webpack: {
|
||||||
if (bundler !== "webpack") {
|
if (bundler !== "webpack") {
|
||||||
@ -528,7 +522,7 @@ export function getBuildContext(params: {
|
|||||||
if (parsedPackageJson.keycloakify.projectBuildDirPath !== undefined) {
|
if (parsedPackageJson.keycloakify.projectBuildDirPath !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: parsedPackageJson.keycloakify.projectBuildDirPath,
|
pathIsh: parsedPackageJson.keycloakify.projectBuildDirPath,
|
||||||
cwd: relativePathsCwd
|
cwd: projectDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,7 +566,7 @@ export function getBuildContext(params: {
|
|||||||
if (buildOptions.keycloakifyBuildDirPath !== undefined) {
|
if (buildOptions.keycloakifyBuildDirPath !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: buildOptions.keycloakifyBuildDirPath,
|
pathIsh: buildOptions.keycloakifyBuildDirPath,
|
||||||
cwd: relativePathsCwd
|
cwd: projectDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,7 +595,7 @@ export function getBuildContext(params: {
|
|||||||
if (parsedPackageJson.keycloakify.publicDirPath !== undefined) {
|
if (parsedPackageJson.keycloakify.publicDirPath !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: parsedPackageJson.keycloakify.publicDirPath,
|
pathIsh: parsedPackageJson.keycloakify.publicDirPath,
|
||||||
cwd: relativePathsCwd
|
cwd: projectDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,7 +667,7 @@ export function getBuildContext(params: {
|
|||||||
pathIsh:
|
pathIsh:
|
||||||
parsedPackageJson.keycloakify
|
parsedPackageJson.keycloakify
|
||||||
.staticDirPathInProjectBuildDirPath,
|
.staticDirPathInProjectBuildDirPath,
|
||||||
cwd: relativePathsCwd
|
cwd: projectBuildDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1001,7 +995,7 @@ export function getBuildContext(params: {
|
|||||||
type: "path",
|
type: "path",
|
||||||
path: getAbsoluteAndInOsFormatPath({
|
path: getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: urlOrPath,
|
pathIsh: urlOrPath,
|
||||||
cwd: relativePathsCwd
|
cwd: projectDirPath
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1011,7 +1005,7 @@ export function getBuildContext(params: {
|
|||||||
? undefined
|
? undefined
|
||||||
: getAbsoluteAndInOsFormatPath({
|
: getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: buildOptions.startKeycloakOptions.realmJsonFilePath,
|
pathIsh: buildOptions.startKeycloakOptions.realmJsonFilePath,
|
||||||
cwd: relativePathsCwd
|
cwd: projectDirPath
|
||||||
}),
|
}),
|
||||||
port: buildOptions.startKeycloakOptions?.port
|
port: buildOptions.startKeycloakOptions?.port
|
||||||
}
|
}
|
||||||
|
@ -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"
|
|
||||||
};
|
|
||||||
|
95
src/bin/shared/copyKeycloakResourcesToPublic.ts
Normal file
95
src/bin/shared/copyKeycloakResourcesToPublic.ts
Normal 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")
|
||||||
|
);
|
||||||
|
}
|
@ -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 };
|
|
||||||
}
|
|
@ -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 };
|
|
||||||
}
|
|
175
src/bin/shared/generateKcGenTs.ts
Normal file
175
src/bin/shared/generateKcGenTs.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
@ -396,12 +398,12 @@ export async function command(params: {
|
|||||||
...(realmJsonFilePath === undefined
|
...(realmJsonFilePath === undefined
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
`-v${SPACE_PLACEHOLDER}"${realmJsonFilePath}":/opt/keycloak/data/import/myrealm-realm.json`
|
`-v${SPACE_PLACEHOLDER}".${pathSep}${pathRelative(process.cwd(), realmJsonFilePath)}":/opt/keycloak/data/import/myrealm-realm.json`
|
||||||
]),
|
]),
|
||||||
`-v${SPACE_PLACEHOLDER}"${jarFilePath_cacheDir}":/opt/keycloak/providers/keycloak-theme.jar`,
|
`-v${SPACE_PLACEHOLDER}".${pathSep}${pathRelative(process.cwd(), jarFilePath_cacheDir)}":/opt/keycloak/providers/keycloak-theme.jar`,
|
||||||
...extensionJarFilePaths.map(
|
...extensionJarFilePaths.map(
|
||||||
jarFilePath =>
|
jarFilePath =>
|
||||||
`-v${SPACE_PLACEHOLDER}"${jarFilePath}":/opt/keycloak/providers/${pathBasename(jarFilePath)}`
|
`-v${SPACE_PLACEHOLDER}".${pathSep}${pathRelative(process.cwd(), jarFilePath)}":/opt/keycloak/providers/${pathBasename(jarFilePath)}`
|
||||||
),
|
),
|
||||||
...(keycloakMajorVersionNumber <= 20
|
...(keycloakMajorVersionNumber <= 20
|
||||||
? [`-e${SPACE_PLACEHOLDER}JAVA_OPTS=-Dkeycloak.profile=preview`]
|
? [`-e${SPACE_PLACEHOLDER}JAVA_OPTS=-Dkeycloak.profile=preview`]
|
||||||
@ -424,7 +426,7 @@ export async function command(params: {
|
|||||||
}))
|
}))
|
||||||
.map(
|
.map(
|
||||||
({ localDirPath, containerDirPath }) =>
|
({ localDirPath, containerDirPath }) =>
|
||||||
`-v${SPACE_PLACEHOLDER}"${localDirPath}":${containerDirPath}:rw`
|
`-v${SPACE_PLACEHOLDER}".${pathSep}${pathRelative(process.cwd(), localDirPath)}":${containerDirPath}:rw`
|
||||||
),
|
),
|
||||||
...buildContext.environmentVariables
|
...buildContext.environmentVariables
|
||||||
.map(({ name }) => ({ name, envValue: process.env[name] }))
|
.map(({ name }) => ({ name, envValue: process.env[name] }))
|
||||||
|
@ -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 = (() => {
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,6 @@ export declare namespace KcContext {
|
|||||||
languageTag: string;
|
languageTag: string;
|
||||||
}[];
|
}[];
|
||||||
currentLanguageTag: string;
|
currentLanguageTag: string;
|
||||||
rtl?: boolean;
|
|
||||||
};
|
};
|
||||||
auth?: {
|
auth?: {
|
||||||
showUsername?: boolean;
|
showUsername?: boolean;
|
||||||
|
@ -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) => {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
|||||||
export * from "./getUserProfileApi";
|
|
@ -1,109 +0,0 @@
|
|||||||
import { assert } from "keycloakify/tools/assert";
|
|
||||||
let cleanup: (() => void) | undefined;
|
|
||||||
const handledElements = new WeakSet<HTMLElement>();
|
|
||||||
const KC_NUMBER_UNFORMAT = "kcNumberUnFormat";
|
|
||||||
const SELECTOR = `input[data-${KC_NUMBER_UNFORMAT}]`;
|
|
||||||
|
|
||||||
export function unFormatNumberOnSubmit() {
|
|
||||||
cleanup?.();
|
|
||||||
|
|
||||||
const handleElement = (element: HTMLInputElement) => {
|
|
||||||
if (handledElements.has(element)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const form = element.closest("form");
|
|
||||||
|
|
||||||
if (form === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.addEventListener("submit", () => {
|
|
||||||
const rawFormat = element.getAttribute(`data-${KC_NUMBER_UNFORMAT}`);
|
|
||||||
if (rawFormat) {
|
|
||||||
element.value = formatNumber(element.value, rawFormat);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
handledElements.add(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.querySelectorAll(SELECTOR).forEach(element => {
|
|
||||||
assert(element instanceof HTMLInputElement);
|
|
||||||
handleElement(element);
|
|
||||||
});
|
|
||||||
|
|
||||||
const observer = new MutationObserver(mutationsList => {
|
|
||||||
for (const mutation of mutationsList) {
|
|
||||||
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
|
|
||||||
mutation.addedNodes.forEach(node => {
|
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
const element = (node as HTMLElement).querySelector(SELECTOR);
|
|
||||||
if (element !== null) {
|
|
||||||
assert(element instanceof HTMLInputElement);
|
|
||||||
handleElement(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
cleanup = () => observer.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Keycloak code
|
|
||||||
const formatNumber = (input: string, format: string) => {
|
|
||||||
if (!input) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// array holding the patterns for the number of expected digits in each part
|
|
||||||
const digitPattern = format.match(/{\d+}/g);
|
|
||||||
|
|
||||||
if (!digitPattern) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate the maximum size of the given pattern based on the sum of the expected digits
|
|
||||||
const maxSize = digitPattern.reduce(
|
|
||||||
(total, p) => total + parseInt(p.replace("{", "").replace("}", "")),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
// keep only digits
|
|
||||||
let rawValue = input.replace(/\D+/g, "");
|
|
||||||
|
|
||||||
// make sure the value is a number
|
|
||||||
//@ts-expect-error
|
|
||||||
if (parseInt(rawValue) != rawValue) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the number of digits does not exceed the maximum size
|
|
||||||
if (rawValue.length > maxSize) {
|
|
||||||
rawValue = rawValue.substring(0, maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the regex based based on the expected digits in each part
|
|
||||||
const formatter = digitPattern.reduce((result, p) => result + `(\\d${p})`, "^");
|
|
||||||
|
|
||||||
// if the current digits match the pattern we have each group of digits in an array
|
|
||||||
let digits = new RegExp(formatter).exec(rawValue);
|
|
||||||
|
|
||||||
// no match, return the raw value without any format
|
|
||||||
if (!digits) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = format;
|
|
||||||
|
|
||||||
// finally format the current digits accordingly to the given format
|
|
||||||
for (let i = 0; i < digitPattern.length; i++) {
|
|
||||||
result = result.replace(digitPattern[i], digits[i + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||||
|
@ -2,7 +2,6 @@ import { useEffect } from "react";
|
|||||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import { assert } from "keycloakify/tools/assert";
|
import { assert } from "keycloakify/tools/assert";
|
||||||
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
||||||
import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
|
|
||||||
|
|
||||||
type KcContextLike = {
|
type KcContextLike = {
|
||||||
url: {
|
url: {
|
||||||
@ -68,12 +67,6 @@ export function useScript(params: { authButtonId: string; kcContext: KcContextLi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
insertScriptTags();
|
||||||
await waitForElementMountedOnDom({
|
|
||||||
elementId: authButtonId
|
|
||||||
});
|
|
||||||
|
|
||||||
insertScriptTags();
|
|
||||||
})();
|
|
||||||
}, [isFetchingTranslations]);
|
}, [isFetchingTranslations]);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
|
|
||||||
|
|
||||||
type I18nLike = {
|
type I18nLike = {
|
||||||
msgStr: (key: "recovery-codes-download-file-header" | "recovery-codes-download-file-description" | "recovery-codes-download-file-date") => string;
|
msgStr: (key: "recovery-codes-download-file-header" | "recovery-codes-download-file-description" | "recovery-codes-download-file-date") => string;
|
||||||
@ -138,12 +137,6 @@ export function useScript(params: { olRecoveryCodesListId: string; i18n: I18nLik
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
insertScriptTags();
|
||||||
await waitForElementMountedOnDom({
|
|
||||||
elementId: olRecoveryCodesListId
|
|
||||||
});
|
|
||||||
|
|
||||||
insertScriptTags();
|
|
||||||
})();
|
|
||||||
}, [isFetchingTranslations]);
|
}, [isFetchingTranslations]);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { useEffect } from "react";
|
|||||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import { assert } from "keycloakify/tools/assert";
|
import { assert } from "keycloakify/tools/assert";
|
||||||
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
||||||
import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
|
|
||||||
|
|
||||||
type KcContextLike = {
|
type KcContextLike = {
|
||||||
url: {
|
url: {
|
||||||
@ -60,12 +59,6 @@ export function useScript(params: { authButtonId: string; kcContext: KcContextLi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
insertScriptTags();
|
||||||
await waitForElementMountedOnDom({
|
|
||||||
elementId: authButtonId
|
|
||||||
});
|
|
||||||
|
|
||||||
insertScriptTags();
|
|
||||||
})();
|
|
||||||
}, [isFetchingTranslations]);
|
}, [isFetchingTranslations]);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { useEffect } from "react";
|
|||||||
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import { assert } from "keycloakify/tools/assert";
|
import { assert } from "keycloakify/tools/assert";
|
||||||
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
||||||
import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
|
|
||||||
|
|
||||||
type KcContextLike = {
|
type KcContextLike = {
|
||||||
url: {
|
url: {
|
||||||
@ -89,12 +88,6 @@ export function useScript(params: { authButtonId: string; kcContext: KcContextLi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
insertScriptTags();
|
||||||
await waitForElementMountedOnDom({
|
|
||||||
elementId: authButtonId
|
|
||||||
});
|
|
||||||
|
|
||||||
insertScriptTags();
|
|
||||||
})();
|
|
||||||
}, [isFetchingTranslations]);
|
}, [isFetchingTranslations]);
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
export async function waitForElementMountedOnDom(params: {
|
|
||||||
elementId: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
const { elementId } = params;
|
|
||||||
|
|
||||||
const getElement = () => document.getElementById(elementId);
|
|
||||||
|
|
||||||
const element = getElement();
|
|
||||||
|
|
||||||
if (element === null) {
|
|
||||||
let prElementPresentInTheDom_resolve: () => void;
|
|
||||||
const prElementPresentInTheDom = new Promise<void>(
|
|
||||||
resolve => (prElementPresentInTheDom_resolve = resolve)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Observe the dom for the element to be added
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
const element = getElement();
|
|
||||||
if (element === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
observer.disconnect();
|
|
||||||
prElementPresentInTheDom_resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
await prElementPresentInTheDom;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
@ -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>"
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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" }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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" }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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: []
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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." }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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 }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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" }]
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
@ -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>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
13
yarn.lock
13
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"
|
||||||
@ -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"
|
||||||
|
Reference in New Issue
Block a user