Compare commits

...

67 Commits

Author SHA1 Message Date
60bd6621c8 Release candidate 2024-06-24 02:43:03 +02:00
b5f6262763 Support cd in running build script in webpack 2024-06-24 02:42:44 +02:00
2b8c4422de Release candidate 2024-06-23 22:48:01 +02:00
a686432c65 Shell: true for windows 2024-06-23 22:47:45 +02:00
449e625877 Fix storybook build 2024-06-23 22:39:29 +02:00
1ac07dafde Release candidate 2024-06-23 21:23:44 +02:00
3878e28b56 Improve monorepo project support, work if there only a package.json at the root (like NX) 2024-06-23 21:23:06 +02:00
cf6bc8666b Include fsevents.node in npm bundle 2024-06-23 21:10:11 +02:00
f76063eb40 Make it easier to link to another starter 2024-06-23 20:54:08 +02:00
ed52c5824d Give immediate feedback if projectDirPath is wrong 2024-06-23 16:56:24 +02:00
9333400322 Remove unused buildContext prop 2024-06-23 02:07:34 +02:00
3689cfcc0d Consistency 2024-06-23 02:06:45 +02:00
b73eceb535 Release candidate 2024-06-23 00:46:01 +02:00
5dc3453fc9 Enable user profile in default keycloak 23 configuration 2024-06-23 00:45:26 +02:00
cef1139a4b Release candidate 2024-06-23 00:37:26 +02:00
ac96959947 Add missing fieldNames from synthetic user attributes 2024-06-23 00:37:06 +02:00
4d73d877ba move used defined exclusions down 2024-06-23 00:18:03 +02:00
9f1186302e Release candidate 2024-06-22 20:12:22 +02:00
319dcc0d15 Stable i18n messages across Keycloak versions 2024-06-22 20:12:02 +02:00
e99fdb8561 Log what file have changed when linking dynamically in starter 2024-06-22 20:11:34 +02:00
f37a342a63 Release candidate 2024-06-22 17:18:52 +02:00
09a039894d Remove React as peer dpendency so that Keycloakify can be more easily used in Vue and Angular projects 2024-06-22 17:18:08 +02:00
3efbb1a9fd Release candidate 2024-06-22 17:05:37 +02:00
920ee62ee3 Implement fallback to english for messages bundle provided via Keycloakify 2024-06-22 17:05:14 +02:00
1ace44fe31 Rename extraMessages -> messageBundle 2024-06-22 17:03:59 +02:00
a60f05415b Export fallback language tag ("en") as a constant 2024-06-22 17:03:44 +02:00
42c9d39e02 Release candidate 2024-06-22 17:01:48 +02:00
a8186f1ed9 Don't use tsafe directly in ejectable components 2024-06-22 17:01:45 +02:00
c2ff515a17 Enable termsText to be extended via local message bundle 2024-06-22 14:09:11 +02:00
960c3ba558 Release candidate 2024-06-22 02:53:51 +02:00
454a9cd01c Remove useDownloadTerms see: https://docs.keycloakify.dev/terms-and-conditions, remove react-markdown 2024-06-22 02:53:30 +02:00
7d42ce1c87 Release candidate 2024-06-21 22:07:50 +02:00
57f6f980cf Update terms storybook 2024-06-21 22:07:36 +02:00
8cba3aae2c Release candidate 2024-06-21 21:25:41 +02:00
01b32f78ed Allow to override termsText 2024-06-21 21:24:04 +02:00
b6066dfd5f Release candidate 2024-06-21 20:28:32 +02:00
3ad554ed59 #569 2024-06-21 20:28:14 +02:00
6aacc6361b Release candidate 2024-06-21 02:13:48 +02:00
638e4e6410 Set the terms to empty string when building 2024-06-21 02:13:31 +02:00
aa9b7cccc7 Rework Terms 2024-06-21 02:01:55 +02:00
41739c8528 Bump version 2024-06-20 04:28:33 +02:00
89b32dc7fc Fix wrong code snippet 2024-06-20 04:28:12 +02:00
44aec23251 Release candidate 2024-06-19 22:41:42 +02:00
12fd6160c5 Fix inline CSS in html 2024-06-19 22:41:25 +02:00
8819abc418 Release candidate 2024-06-19 03:56:13 +02:00
96b627095c https://github.com/adbayb/termost/pull/31 2024-06-19 03:52:57 +02:00
dba004f924 Release candidate 2024-06-19 01:41:45 +02:00
5423a07c47 Patch CSS for Keycloak by using relative paths instead of css variables 2024-06-19 01:41:22 +02:00
aba725372e Release candidate 2024-06-18 22:41:08 +02:00
a61aa9dd5d Add missing fonts from the account theme's default assets 2024-06-18 16:41:09 +02:00
74349b20ce Adding missing font from default theme resources 2024-06-17 13:26:32 +02:00
09ab9a1c8f Fix storybook build 2024-06-17 13:03:39 +02:00
abfe5789a3 Publish new storybook 2024-06-17 12:53:06 +02:00
67ebac496d Release candidate 2024-06-17 00:07:53 +02:00
60a2bf173b Add missing base font face 2024-06-17 00:07:38 +02:00
4e03f07864 Do not includes all shared source, it's bundled already 2024-06-17 00:00:41 +02:00
aef1709d7f Release candidate 2024-06-16 18:27:37 +02:00
2f590f7be2 Add missing file in npm bundle 2024-06-16 18:27:18 +02:00
d5fa6ca89a Fix unit tests 2024-06-16 17:55:06 +02:00
8eaaffb25a Release candidate 2024-06-16 15:19:44 +02:00
28c5e2bab2 Rename use 'dist' instead of 'build' for basenameOfTheKeycloakifyResourcesDir 2024-06-16 15:19:27 +02:00
e212039f2c Release cadidate 2024-06-16 14:59:11 +02:00
99b0b67f77 Add publicDirpath option for webpack 2024-06-16 14:58:51 +02:00
6ec9ba3c01 Add version in build options 2024-06-16 14:53:18 +02:00
d7960a7dcf Release candidate 2024-06-16 14:05:38 +02:00
2a6e9af9c9 Enable to use an other directory than build/assets in webpack 2024-06-16 14:05:23 +02:00
327e4d1f90 Add doc link 2024-06-16 11:48:39 +02:00
54 changed files with 1118 additions and 1597 deletions

View File

@ -16,8 +16,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: bahmutov/npm-install@v1
- name: If this step fails run 'yarn format' then commit again.
run: yarn format:check
- name: If this step fails run 'npm run format' then commit again.
run: npm run format:check
test:
runs-on: ${{ matrix.os }}
needs: test_lint
@ -32,13 +32,12 @@ jobs:
with:
node-version: ${{ matrix.node }}
- uses: bahmutov/npm-install@v1
- run: yarn build
- run: yarn test
#- run: yarn test:keycloakify-starter
- run: npm run build
- run: npm run test
storybook:
runs-on: ubuntu-latest
if: github.event_name == 'push'
#if: github.event_name == 'push'
needs: test
steps:
- uses: actions/checkout@v4
@ -46,11 +45,11 @@ jobs:
with:
node-version: '18'
- uses: bahmutov/npm-install@v1
- run: yarn build-storybook -o ./build_storybook
- run: npm run build-storybook
- run: git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${{github.repository}}.git
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npx -y -p gh-pages@3.1.0 gh-pages -d ./build_storybook -u "github-actions-bot <actions@github.com>"
- run: npx -y -p gh-pages@3.1.0 gh-pages -d ./storybook-static -u "github-actions-bot <actions@github.com>"
check_if_version_upgraded:
name: Check if version upgrade
@ -112,7 +111,7 @@ jobs:
with:
registry-url: https://registry.npmjs.org/
- uses: bahmutov/npm-install@v1
- run: yarn build
- run: npm run build
- run: npx -y -p denoify@1.6.12 enable_short_npm_import_path
env:
DRY_RUN: "0"

View File

@ -1,13 +1,13 @@
{
"name": "keycloakify",
"version": "10.0.0-rc.67",
"version": "10.0.0-rc.93",
"description": "Create Keycloak themes using React",
"repository": {
"type": "git",
"url": "git://github.com/keycloakify/keycloakify.git"
},
"scripts": {
"prepare": "patch-package && tsx scripts/generate-i18n-messages.ts",
"prepare": "tsx scripts/generate-i18n-messages.ts",
"build": "tsx scripts/build.ts",
"storybook": "tsx scripts/start-storybook.ts",
"link-in-starter": "tsx scripts/link-in-starter.ts",
@ -41,12 +41,14 @@
"!dist/bin/",
"dist/bin/main.js",
"dist/bin/*.index.js",
"dist/bin/*.node",
"dist/bin/shared/constants.js",
"dist/bin/shared/*.d.ts",
"dist/bin/shared/*.js.map",
"!dist/vite-plugin/",
"dist/vite-plugin/index.js",
"dist/vite-plugin/index.d.ts",
"dist/vite-plugin/vite-plugin.d.ts",
"dist/vite-plugin/index.js"
"dist/vite-plugin/vite-plugin.d.ts"
],
"keywords": [
"keycloak",
@ -60,11 +62,7 @@
"bluehats"
],
"homepage": "https://www.keycloakify.dev",
"peerDependencies": {
"react": "*"
},
"dependencies": {
"react-markdown": "^5.0.3",
"tsafe": "^1.6.6"
},
"devDependencies": {
@ -100,7 +98,6 @@
"lint-staged": "^11.0.0",
"magic-string": "^0.30.7",
"make-fetch-happen": "^11.0.3",
"patch-package": "^8.0.0",
"powerhooks": "^1.0.10",
"prettier": "^3.2.5",
"properties-parser": "^0.3.1",
@ -109,7 +106,7 @@
"recast": "^0.23.3",
"run-exclusive": "^2.2.19",
"storybook-dark-mode": "^1.1.2",
"termost": "^0.12.0",
"termost": "^v0.12.1",
"tsc-alias": "^1.8.10",
"tss-react": "^4.9.10",
"typescript": "^4.9.1-beta",

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@ if (fs.existsSync(join("dist", "bin", "main.original.js"))) {
);
fs.readdirSync(join("dist", "bin")).forEach(fileBasename => {
if (/[0-9]\.index.js/.test(fileBasename)) {
if (/[0-9]\.index.js/.test(fileBasename) || fileBasename.endsWith(".node")) {
fs.rmSync(join("dist", "bin", fileBasename));
}
});
@ -111,9 +111,10 @@ run(
)}`
);
fs.readdirSync(join("dist", "ncc_out")).forEach(fileBasename =>
assert(!fileBasename.endsWith(".index.js"))
);
fs.readdirSync(join("dist", "ncc_out")).forEach(fileBasename => {
assert(!fileBasename.endsWith(".index.js"));
assert(!fileBasename.endsWith(".node"));
});
transformCodebase({
srcDirPath: join("dist", "ncc_out"),

View File

@ -65,11 +65,14 @@ async function main() {
fs
.readFileSync(pathJoin(baseThemeDirPath, filePath))
.toString("utf8")
)
).map(([key, value]: any) => [
key === "locale_pt_BR" ? "locale_pt-BR" : key,
value.replace(/''/g, "'")
])
) as Record<string, string>
)
.map(([key, value]) => [key, value.replace(/''/g, "'")])
.map(([key, value]) => [
key === "locale_pt_BR" ? "locale_pt-BR" : key,
value
])
.map(([key, value]) => [key, key === "termsText" ? "" : value])
);
});
}

View File

@ -10,14 +10,16 @@ fs.rmSync(".yarn_home", { recursive: true, force: true });
run("yarn install");
run("yarn build");
fs.rmSync(join("..", "keycloakify-starter", "node_modules"), {
const starterName = "keycloakify-starter-webpack";
fs.rmSync(join("..", starterName, "node_modules"), {
recursive: true,
force: true
});
run("yarn install", { cwd: join("..", "keycloakify-starter") });
run("yarn install", { cwd: join("..", starterName) });
run(`npx tsx ${join("scripts", "link-in-app.ts")} keycloakify-starter`);
run(`npx tsx ${join("scripts", "link-in-app.ts")} ${starterName}`);
startRebuildOnSrcChange();

View File

@ -28,9 +28,13 @@ export function startRebuildOnSrcChange() {
console.log(chalk.green("Watching for changes in src/"));
chokidar.watch(["src", "stories"], { ignoreInitial: true }).on("all", async () => {
await waitForDebounce();
chokidar
.watch(["src", "stories"], { ignoreInitial: true })
.on("all", async (event, path) => {
console.log(chalk.bold(`${event}: ${path}`));
runYarnBuild();
});
await waitForDebounce();
runYarnBuild();
});
}

View File

@ -13,7 +13,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { msg, msgStr, getChangeLocaleUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { locale, url, features, realm, message, referrer } = kcContext;
@ -79,7 +79,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
<ul>
{locale.supported.map(({ languageTag }) => (
<li key={languageTag} className="kc-dropdown-item">
<a href={getChangeLocalUrl(languageTag)}>{labelBySupportedLanguageTag[languageTag]}</a>
<a href={getChangeLocaleUrl(languageTag)}>{labelBySupportedLanguageTag[languageTag]}</a>
</li>
))}
</ul>

View File

@ -1,12 +1,9 @@
import "keycloakify/tools/Object.fromEntries";
import { useEffect, useState } from "react";
import { assert } from "tsafe/assert";
import messages_fallbackLanguage from "./baseMessages/en";
import { getMessages } from "./baseMessages";
import type { KcContext } from "../KcContext";
import { Reflect } from "tsafe/Reflect";
export const fallbackLanguageTag = "en";
import { fallbackLanguageTag } from "keycloakify/bin/shared/constants";
export type KcContextLike = {
locale?: {
@ -30,7 +27,7 @@ export type GenericI18n<MessageKey extends string> = {
* Redirect to this url to change the language.
* After reload currentLanguageTag === newLanguageTag
*/
getChangeLocalUrl: (newLanguageTag: string) => string;
getChangeLocaleUrl: (newLanguageTag: string) => string;
/**
* e.g. "en" => "English", "fr" => "Français", ...
*
@ -88,7 +85,9 @@ export type GenericI18n<MessageKey extends string> = {
isFetchingTranslations: boolean;
};
function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } }) {
export function createGetI18n<ExtraMessageKey extends string = never>(messageBundle: {
[languageTag: string]: { [key in ExtraMessageKey]: string };
}) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
type Result = { i18n: I18n; prI18n_currentLanguage: Promise<I18n> | undefined };
@ -108,9 +107,9 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
return cachedResult;
}
const partialI18n: Pick<I18n, "currentLanguageTag" | "getChangeLocalUrl" | "labelBySupportedLanguageTag"> = {
const partialI18n: Pick<I18n, "currentLanguageTag" | "getChangeLocaleUrl" | "labelBySupportedLanguageTag"> = {
currentLanguageTag: kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag,
getChangeLocalUrl: newLanguageTag => {
getChangeLocaleUrl: newLanguageTag => {
const { locale } = kcContext;
assert(locale !== undefined, "Internationalization not enabled");
@ -126,8 +125,8 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
const { createI18nTranslationFunctions } = createI18nTranslationFunctionsFactory<MessageKey, ExtraMessageKey>({
messages_fallbackLanguage,
extraMessages_fallbackLanguage: extraMessages[fallbackLanguageTag],
extraMessages: extraMessages[partialI18n.currentLanguageTag]
messageBundle_fallbackLanguage: messageBundle[fallbackLanguageTag],
messageBundle_currentLanguage: messageBundle[partialI18n.currentLanguageTag]
});
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag === fallbackLanguageTag;
@ -135,17 +134,19 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
const result: Result = {
i18n: {
...partialI18n,
...createI18nTranslationFunctions({ messages: undefined }),
...createI18nTranslationFunctions({
messages_currentLanguage: isCurrentLanguageFallbackLanguage ? messages_fallbackLanguage : undefined
}),
isFetchingTranslations: !isCurrentLanguageFallbackLanguage
},
prI18n_currentLanguage: isCurrentLanguageFallbackLanguage
? undefined
: (async () => {
const messages = await getMessages(partialI18n.currentLanguageTag);
const messages_currentLanguage = await getMessages(partialI18n.currentLanguageTag);
const i18n_currentLanguage: I18n = {
...partialI18n,
...createI18nTranslationFunctions({ messages }),
...createI18nTranslationFunctions({ messages_currentLanguage }),
isFetchingTranslations: false
};
@ -168,66 +169,30 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
return { getI18n };
}
export function createUseI18n<ExtraMessageKey extends string = never>(extraMessages: {
[languageTag: string]: { [key in ExtraMessageKey]: string };
}) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
const { getI18n } = createGetI18n(extraMessages);
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n } {
const { kcContext } = params;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(i18n);
useEffect(() => {
let isActive = true;
prI18n_currentLanguage?.then(i18n => {
if (!isActive) {
return;
}
setI18n_toReturn(i18n);
});
return () => {
isActive = false;
};
}, []);
return { i18n: i18n_toReturn };
}
return { useI18n, ofTypeI18n: Reflect<I18n>() };
}
function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraMessageKey extends string>(params: {
messages_fallbackLanguage: Record<MessageKey, string>;
extraMessages_fallbackLanguage: Record<ExtraMessageKey, string> | undefined;
extraMessages: Partial<Record<ExtraMessageKey, string>> | undefined;
messageBundle_fallbackLanguage: Record<ExtraMessageKey, string> | undefined;
messageBundle_currentLanguage: Partial<Record<ExtraMessageKey, string>> | undefined;
}) {
const { extraMessages } = params;
const { messageBundle_currentLanguage } = params;
const messages_fallbackLanguage = {
...params.messages_fallbackLanguage,
...params.extraMessages_fallbackLanguage
...params.messageBundle_fallbackLanguage
};
function createI18nTranslationFunctions(params: {
messages: Partial<Record<MessageKey, string>> | undefined;
messages_currentLanguage: Partial<Record<MessageKey, string>> | undefined;
}): Pick<GenericI18n<MessageKey | ExtraMessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
const messages = {
...params.messages,
...extraMessages
const messages_currentLanguage = {
...params.messages_currentLanguage,
...messageBundle_currentLanguage
};
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
const { key, args, doRenderAsHtml } = props;
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (messages_fallbackLanguage as any)[key];
const messageOrUndefined: string | undefined = (messages_currentLanguage as any)[key] ?? (messages_fallbackLanguage as any)[key];
if (messageOrUndefined === undefined) {
return undefined;

View File

@ -1,5 +1,4 @@
import type { GenericI18n, MessageKey, KcContextLike } from "./i18n";
export type { MessageKey, KcContextLike };
export type I18n = GenericI18n<MessageKey>;
export { createUseI18n } from "./i18n";
export { fallbackLanguageTag } from "./i18n";
export { createUseI18n } from "./useI18n";

View File

@ -0,0 +1,44 @@
import { useEffect, useState } from "react";
import {
createGetI18n,
type GenericI18n,
type MessageKey,
type KcContextLike
} from "./i18n";
import { Reflect } from "tsafe/Reflect";
export function createUseI18n<ExtraMessageKey extends string = never>(messageBundle: {
[languageTag: string]: { [key in ExtraMessageKey]: string };
}) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
const { getI18n } = createGetI18n(messageBundle);
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n } {
const { kcContext } = params;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(i18n);
useEffect(() => {
let isActive = true;
prI18n_currentLanguage?.then(i18n => {
if (!isActive) {
return;
}
setI18n_toReturn(i18n);
});
return () => {
isActive = false;
};
}, []);
return { i18n: i18n_toReturn };
}
return { useI18n, ofTypeI18n: Reflect<I18n>() };
}

View File

@ -190,6 +190,8 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const userProfileFormFieldComponentName = "UserProfileFormFields";
const componentName = componentBasename.replace(/.tsx$/, "");
console.log(
[
``,
@ -207,10 +209,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`// ...`,
``,
chalk.green(
`+const ${componentBasename.replace(
/.tsx$/,
""
)} = lazy(() => import("./pages/${componentBasename}"));`
`+const ${componentName} = lazy(() => import("./pages/${componentName}"));`
),
...[
``,
@ -224,7 +223,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
` switch (kcContext.pageId) {`,
` // ...`,
`+ case "${pageIdOrComponent}": return (`,
`+ <${componentBasename}`,
`+ <${componentName}`,
`+ {...{ kcContext, i18n, classes }}`,
`+ Template={Template}`,
`+ doUseDefaultCss={true}`,

View File

@ -1,7 +1,12 @@
import cheerio from "cheerio";
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
import { generateCssCodeToDefineGlobals } from "../replacers/replaceImportsInCssCode";
import { replaceImportsInInlineCssCode } from "../replacers/replaceImportsInInlineCssCode";
import {
replaceImportsInJsCode,
BuildContextLike as BuildContextLike_replaceImportsInJsCode
} from "../replacers/replaceImportsInJsCode";
import {
replaceImportsInCssCode,
BuildContextLike as BuildContextLike_replaceImportsInCssCode
} from "../replacers/replaceImportsInCssCode";
import * as fs from "fs";
import { join as pathJoin } from "path";
import type { BuildContext } from "../../shared/buildContext";
@ -9,26 +14,22 @@ import { assert } from "tsafe/assert";
import {
type ThemeType,
basenameOfTheKeycloakifyResourcesDir,
resources_common,
nameOfTheLocalizationRealmOverridesUserProfileProperty
resources_common
} from "../../shared/constants";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
export type BuildContextLike = {
bundler: "vite" | "webpack";
themeVersion: string;
urlPathname: string | undefined;
projectBuildDirPath: string;
assetsDirPath: string;
kcContextExclusionsFtlCode: string | undefined;
};
export type BuildContextLike = BuildContextLike_replaceImportsInJsCode &
BuildContextLike_replaceImportsInCssCode & {
urlPathname: string | undefined;
themeVersion: string;
kcContextExclusionsFtlCode: string | undefined;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export function generateFtlFilesCodeFactory(params: {
themeName: string;
indexHtmlCode: string;
cssGlobalsToDefine: Record<string, string>;
buildContext: BuildContextLike;
keycloakifyVersion: string;
themeType: ThemeType;
@ -36,7 +37,6 @@ export function generateFtlFilesCodeFactory(params: {
}) {
const {
themeName,
cssGlobalsToDefine,
indexHtmlCode,
buildContext,
keycloakifyVersion,
@ -65,8 +65,9 @@ export function generateFtlFilesCodeFactory(params: {
assert(cssCode !== null);
const { fixedCssCode } = replaceImportsInInlineCssCode({
const { fixedCssCode } = replaceImportsInCssCode({
cssCode,
cssFileRelativeDirPath: undefined,
buildContext
});
@ -97,21 +98,6 @@ export function generateFtlFilesCodeFactory(params: {
);
})
);
if (Object.keys(cssGlobalsToDefine).length !== 0) {
$("head").prepend(
[
"",
"<style>",
generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
buildContext
}).cssCodeToPrependInHead,
"</style>",
""
].join("\n")
);
}
}
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
@ -136,10 +122,6 @@ export function generateFtlFilesCodeFactory(params: {
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType)
.replace("KEYCLOAKIFY_THEME_NAME_cXxKd3xEer", themeName)
.replace("RESOURCES_COMMON_cLsLsMrtDkpVv", resources_common)
.replace(
"lOCALIZATION_REALM_OVERRIDES_USER_PROFILE_PROPERTY_KEY_aaGLsPgGIdeeX",
nameOfTheLocalizationRealmOverridesUserProfileProperty
)
.replace(
"USER_DEFINED_EXCLUSIONS_eKsaY4ZsZ4eMr2",
buildContext.kcContextExclusionsFtlCode ?? ""

View File

@ -33,8 +33,9 @@ kcContext.pageId = "${pageId}";
if( kcContext.url && kcContext.url.resourcesPath ){
kcContext.url.resourcesCommonPath = kcContext.url.resourcesPath + "/" + "RESOURCES_COMMON_cLsLsMrtDkpVv";
}
kcContext["x-keycloakify"] = {};
<#if profile?? && profile.attributes??>
kcContext.lOCALIZATION_REALM_OVERRIDES_USER_PROFILE_PROPERTY_KEY_aaGLsPgGIdeeX = {
kcContext["x-keycloakify"].realmMessageBundleUserProfile = {
<#list profile.attributes as attribute>
<#if attribute.annotations?? && attribute.displayName??>
"${attribute.displayName}": decodeHtmlEntities("${advancedMsg(attribute.displayName)?js_string}"),
@ -61,6 +62,9 @@ if( kcContext.url && kcContext.url.resourcesPath ){
</#list>
};
</#if>
<#if pageId == "terms.ftl" || termsAcceptanceRequired?? && termsAcceptanceRequired>
kcContext["x-keycloakify"].realmMessageBundleTermsText= decodeHtmlEntities("${msg("termsText")?js_string}");
</#if>
attributes_to_attributesByName: {
if( !kcContext.profile ){
break attributes_to_attributesByName;
@ -198,14 +202,15 @@ function decodeHtmlEntities(htmlStr){
) || (
key == "execution" &&
are_same_path(path, [])
) || (
key == "entity" &&
are_same_path(path, ["user"])
)
>
<#-- <#local out_seq += ["/*" + path?join(".") + "." + key + " excluded*/"]> -->
<#continue>
</#if>
USER_DEFINED_EXCLUSIONS_eKsaY4ZsZ4eMr2
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
<#if (
["register.ftl", "register-user-profile.ftl", "terms.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
@ -221,6 +226,8 @@ function decodeHtmlEntities(htmlStr){
<#local out_seq += ["/*Accessing attemptedUsername throwed an exception */"]>
</#attempt>
</#if>
USER_DEFINED_EXCLUSIONS_eKsaY4ZsZ4eMr2
<#attempt>
<#if !object[key]??>

View File

@ -7,13 +7,13 @@ import {
lastKeycloakVersionWithAccountV1,
accountV1ThemeName
} from "../../shared/constants";
import { downloadKeycloakDefaultTheme } from "../../shared/downloadKeycloakDefaultTheme";
import {
downloadKeycloakDefaultTheme,
BuildContextLike as BuildContextLike_downloadKeycloakDefaultTheme
} from "../../shared/downloadKeycloakDefaultTheme";
import { transformCodebase } from "../../tools/transformCodebase";
export type BuildContextLike = {
cacheDirPath: string;
npmWorkspaceRootDirPath: string;
};
export type BuildContextLike = BuildContextLike_downloadKeycloakDefaultTheme;
assert<BuildContext extends BuildContextLike ? true : false>();

View File

@ -1,14 +1,15 @@
import type { ThemeType } from "../../shared/constants";
import { type ThemeType, fallbackLanguageTag } from "../../shared/constants";
import { crawl } from "../../tools/crawl";
import { join as pathJoin } from "path";
import { readFileSync } from "fs";
import { symToStr } from "tsafe/symToStr";
import { removeDuplicates } from "evt/tools/reducers/removeDuplicates";
import * as recast from "recast";
import * as babelParser from "@babel/parser";
import babelGenerate from "@babel/generator";
import * as babelTypes from "@babel/types";
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
import * as fs from "fs";
import { assert } from "tsafe/assert";
export function generateMessageProperties(params: {
themeSrcDirPath: string;
@ -16,36 +17,92 @@ export function generateMessageProperties(params: {
}): { languageTag: string; propertiesFileSource: string }[] {
const { themeSrcDirPath, themeType } = params;
let files = crawl({
dirPath: pathJoin(themeSrcDirPath, themeType),
returnedPathsType: "absolute"
});
files = files.filter(file => {
const regex = /\.(js|ts|tsx)$/;
return regex.test(file);
});
files = files.sort((a, b) => {
const regex = /\.i18n\.(ts|js|tsx)$/;
const aIsI18nFile = regex.test(a);
const bIsI18nFile = regex.test(b);
return aIsI18nFile === bIsI18nFile ? 0 : aIsI18nFile ? -1 : 1;
});
files = files.sort((a, b) => a.length - b.length);
files = files.filter(file =>
readFileSync(file).toString("utf8").includes("createUseI18n")
const baseMessagesDirPath = pathJoin(
getThisCodebaseRootDirPath(),
"src",
themeType,
"i18n",
"baseMessages"
);
if (files.length === 0) {
return [];
}
const baseMessageBundle: { [languageTag: string]: Record<string, string> } =
Object.fromEntries(
fs
.readdirSync(baseMessagesDirPath)
.filter(baseName => baseName !== "index.ts")
.map(basename => ({
languageTag: basename.replace(/\.ts$/, ""),
filePath: pathJoin(baseMessagesDirPath, basename)
}))
.map(({ languageTag, filePath }) => {
const lines = fs
.readFileSync(filePath)
.toString("utf8")
.split(/\r?\n/);
const extraMessages = files
.map(file => {
const root = recast.parse(readFileSync(file).toString("utf8"), {
let messagesJson = "{";
let isInDeclaration = false;
for (const line of lines) {
if (!isInDeclaration) {
if (line.startsWith("const messages")) {
isInDeclaration = true;
}
continue;
}
if (line.startsWith("}")) {
messagesJson += "}";
break;
}
messagesJson += line;
}
const messages = JSON.parse(messagesJson) as Record<string, string>;
return [languageTag, messages];
})
);
const { i18nTsFilePath } = (() => {
let files = crawl({
dirPath: pathJoin(themeSrcDirPath, themeType),
returnedPathsType: "absolute"
});
files = files.filter(file => {
const regex = /\.(js|ts|tsx)$/;
return regex.test(file);
});
files = files.sort((a, b) => {
const regex = /\.i18n\.(ts|js|tsx)$/;
const aIsI18nFile = regex.test(a);
const bIsI18nFile = regex.test(b);
return aIsI18nFile === bIsI18nFile ? 0 : aIsI18nFile ? -1 : 1;
});
files = files.sort((a, b) => a.length - b.length);
files = files.filter(file =>
fs.readFileSync(file).toString("utf8").includes("createUseI18n(")
);
const i18nTsFilePath: string | undefined = files[0];
return { i18nTsFilePath };
})();
const messageBundle: { [languageTag: string]: Record<string, string> } | undefined =
(() => {
if (i18nTsFilePath === undefined) {
return undefined;
}
const root = recast.parse(fs.readFileSync(i18nTsFilePath).toString("utf8"), {
parser: {
parse: (code: string) =>
babelParser.parse(code, {
@ -57,7 +114,7 @@ export function generateMessageProperties(params: {
}
});
const codes: string[] = [];
let messageBundleDeclarationTsCode: string | undefined = undefined;
recast.visit(root, {
visitCallExpression: function (path) {
@ -65,103 +122,71 @@ export function generateMessageProperties(params: {
path.node.callee.type === "Identifier" &&
path.node.callee.name === "createUseI18n"
) {
codes.push(babelGenerate(path.node.arguments[0] as any).code);
messageBundleDeclarationTsCode = babelGenerate(
path.node.arguments[0] as any
).code;
return false;
}
this.traverse(path);
}
});
return codes;
})
.flat()
.map(code => {
let extraMessages: {
assert(messageBundleDeclarationTsCode !== undefined);
let messageBundle: {
[languageTag: string]: Record<string, string>;
} = {};
try {
eval(`${symToStr({ extraMessages })} = ${code}`);
eval(
`${symToStr({ messageBundle })} = ${messageBundleDeclarationTsCode}`
);
} catch {
console.warn(
[
"WARNING: Make sure that the first argument of createUseI18n can be evaluated in a javascript",
"runtime where only the node globals are available.",
"WARNING: Make sure the messageBundle your provided as argument of createUseI18n can be statically evaluated.",
"This is important because we need to put your i18n messages in messages_*.properties files",
"or they won't be available server side.",
"\n",
"The following code could not be evaluated:",
"\n",
code
messageBundleDeclarationTsCode
].join(" ")
);
}
return extraMessages;
});
return messageBundle;
})();
const languageTags = extraMessages
.map(extraMessage => Object.keys(extraMessage))
.flat()
.reduce(...removeDuplicates<string>());
const keyValueMapByLanguageTag: Record<string, Record<string, string>> = {};
for (const languageTag of languageTags) {
const keyValueMap: Record<string, string> = {};
for (const extraMessage of extraMessages) {
const keyValueMap_i = extraMessage[languageTag];
if (keyValueMap_i === undefined) {
continue;
}
for (const [key, value] of Object.entries(keyValueMap_i)) {
if (keyValueMap[key] !== undefined) {
console.warn(
[
"WARNING: The following key is defined multiple times:",
"\n",
key,
"\n",
"The following value will be ignored:",
"\n",
value,
"\n",
"The following value was already defined:",
"\n",
keyValueMap[key]
].join(" ")
);
continue;
const mergedMessageBundle: { [languageTag: string]: Record<string, string> } =
Object.fromEntries(
Object.entries(baseMessageBundle).map(([languageTag, messages]) => [
languageTag,
{
...messages,
...(messageBundle === undefined
? {}
: messageBundle[languageTag] ??
messageBundle[fallbackLanguageTag] ??
messageBundle[Object.keys(messageBundle)[0]] ??
{})
}
])
);
keyValueMap[key] = value;
}
}
keyValueMapByLanguageTag[languageTag] = keyValueMap;
}
const out: { languageTag: string; propertiesFileSource: string }[] = [];
for (const [languageTag, keyValueMap] of Object.entries(keyValueMapByLanguageTag)) {
const propertiesFileSource = Object.entries(keyValueMap)
.map(([key, value]) => `${key}=${escapeStringForPropertiesFile(value)}`)
.join("\n");
out.push({
const messageProperties: { languageTag: string; propertiesFileSource: string }[] =
Object.entries(mergedMessageBundle).map(([languageTag, messages]) => ({
languageTag,
propertiesFileSource: [
"# This file was generated by keycloakify",
"",
"parent=base",
"",
propertiesFileSource,
...(themeType !== "account" ? ["parent=base"] : []),
...Object.entries(messages).map(
([key, value]) => `${key}=${escapeStringForPropertiesFile(value)}`
),
""
].join("\n")
});
}
}));
return out;
return messageProperties;
}

View File

@ -1,6 +1,11 @@
import { transformCodebase } from "../../tools/transformCodebase";
import * as fs from "fs";
import { join as pathJoin, resolve as pathResolve, relative as pathRelative } from "path";
import {
join as pathJoin,
resolve as pathResolve,
relative as pathRelative,
dirname as pathDirname
} from "path";
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
import {
@ -48,6 +53,7 @@ export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
environmentVariables: { name: string; default: string }[];
recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"];
themeSrcDirPath: string;
bundler: { type: "vite" } | { type: "webpack" };
};
assert<BuildContext extends BuildContextLike ? true : false>();
@ -64,8 +70,6 @@ export async function generateResourcesForMainTheme(params: {
return pathJoin(resourcesDirPath, "theme", themeName, themeType);
};
const cssGlobalsToDefine: Record<string, string> = {};
for (const themeType of ["login", "account"] as const) {
if (!buildContext.recordIsImplementedByThemeType[themeType]) {
continue;
@ -110,7 +114,7 @@ export async function generateResourcesForMainTheme(params: {
);
if (fs.existsSync(dirPath)) {
assert(buildContext.bundler === "webpack");
assert(buildContext.bundler.type === "webpack");
throw new Error(
[
@ -127,21 +131,14 @@ export async function generateResourcesForMainTheme(params: {
transformCodebase({
srcDirPath: buildContext.projectBuildDirPath,
destDirPath,
transformSourceCode: ({ filePath, sourceCode }) => {
transformSourceCode: ({ filePath, fileRelativePath, sourceCode }) => {
if (filePath.endsWith(".css")) {
const {
cssGlobalsToDefine: cssGlobalsToDefineForThisFile,
fixedCssCode
} = replaceImportsInCssCode({
cssCode: sourceCode.toString("utf8")
const { fixedCssCode } = replaceImportsInCssCode({
cssCode: sourceCode.toString("utf8"),
cssFileRelativeDirPath: pathDirname(fileRelativePath),
buildContext
});
Object.entries(cssGlobalsToDefineForThisFile).forEach(
([key, value]) => {
cssGlobalsToDefine[key] = value;
}
);
return {
modifiedSourceCode: Buffer.from(fixedCssCode, "utf8")
};
@ -168,7 +165,6 @@ export async function generateResourcesForMainTheme(params: {
indexHtmlCode: fs
.readFileSync(pathJoin(buildContext.projectBuildDirPath, "index.html"))
.toString("utf8"),
cssGlobalsToDefine,
buildContext,
keycloakifyVersion: readThisNpmPackageVersion(),
themeType,

View File

@ -11,7 +11,15 @@ export function readFieldNameUsage(params: {
}): string[] {
const { themeSrcDirPath, themeType } = params;
const fieldNames = new Set<string>();
// NOTE: We pre-populate with the synthetic user attributes defined in useUserProfileForm (can't be parsed automatically)
const fieldNames = new Set<string>([
"firstName",
"lastName",
"email",
"username",
"password",
"password-confirm"
]);
for (const srcDirPath of [
pathJoin(getThisCodebaseRootDirPath(), "src", themeType),

View File

@ -85,7 +85,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
});
run_post_build_script: {
if (buildContext.bundler !== "vite") {
if (buildContext.bundler.type !== "vite") {
break run_post_build_script;
}

View File

@ -1,7 +1,7 @@
import * as crypto from "crypto";
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import { basenameOfTheKeycloakifyResourcesDir } from "../../shared/constants";
import { assert } from "tsafe/assert";
import { posix } from "path";
export type BuildContextLike = {
urlPathname: string | undefined;
@ -9,68 +9,45 @@ export type BuildContextLike = {
assert<BuildContext extends BuildContextLike ? true : false>();
export function replaceImportsInCssCode(params: { cssCode: string }): {
fixedCssCode: string;
cssGlobalsToDefine: Record<string, string>;
} {
const { cssCode } = params;
const cssGlobalsToDefine: Record<string, string> = {};
new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*?/g) ?? []).forEach(
match =>
(cssGlobalsToDefine[
"url" +
crypto
.createHash("sha256")
.update(match)
.digest("hex")
.substring(0, 15)
] = match)
);
let fixedCssCode = cssCode;
Object.keys(cssGlobalsToDefine).forEach(
cssVariableName =>
//NOTE: split/join pattern ~ replace all
(fixedCssCode = fixedCssCode
.split(cssGlobalsToDefine[cssVariableName])
.join(`var(--${cssVariableName})`))
);
return { fixedCssCode, cssGlobalsToDefine };
}
export function generateCssCodeToDefineGlobals(params: {
cssGlobalsToDefine: Record<string, string>;
export function replaceImportsInCssCode(params: {
cssCode: string;
cssFileRelativeDirPath: string | undefined;
buildContext: BuildContextLike;
}): {
cssCodeToPrependInHead: string;
fixedCssCode: string;
} {
const { cssGlobalsToDefine, buildContext } = params;
const { cssCode, cssFileRelativeDirPath, buildContext } = params;
return {
cssCodeToPrependInHead: [
":root {",
...Object.keys(cssGlobalsToDefine)
.map(cssVariableName =>
[
`--${cssVariableName}:`,
cssGlobalsToDefine[cssVariableName].replace(
new RegExp(
`url\\(${(buildContext.urlPathname ?? "/").replace(
/\//g,
"\\/"
)}`,
"g"
),
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
)
].join(" ")
)
.map(line => ` ${line};`),
"}"
].join("\n")
};
const fixedCssCode = cssCode.replace(
/url\(["']?(\/[^/][^)"']+)["']?\)/g,
(match, assetFileAbsoluteUrlPathname) => {
if (buildContext.urlPathname !== undefined) {
if (!assetFileAbsoluteUrlPathname.startsWith(buildContext.urlPathname)) {
// NOTE: Should never happen
return match;
}
assetFileAbsoluteUrlPathname = assetFileAbsoluteUrlPathname.replace(
buildContext.urlPathname,
"/"
);
}
inline_style_in_html: {
if (cssFileRelativeDirPath !== undefined) {
break inline_style_in_html;
}
return `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}${assetFileAbsoluteUrlPathname})`;
}
const assetFileRelativeUrlPathname = posix.relative(
cssFileRelativeDirPath.replace(/\\/g, "/"),
assetFileAbsoluteUrlPathname.replace(/^\//, "")
);
return `url(${assetFileRelativeUrlPathname})`;
}
);
return { fixedCssCode };
}

View File

@ -1,28 +0,0 @@
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import { basenameOfTheKeycloakifyResourcesDir } from "../../shared/constants";
export type BuildContextLike = {
urlPathname: string | undefined;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export function replaceImportsInInlineCssCode(params: {
cssCode: string;
buildContext: BuildContextLike;
}): {
fixedCssCode: string;
} {
const { cssCode, buildContext } = params;
const fixedCssCode = cssCode.replace(
buildContext.urlPathname === undefined
? /url\(["']?\/([^/][^)"']+)["']?\)/g
: new RegExp(`url\\(["']?${buildContext.urlPathname}([^)"']+)["']?\\)`, "g"),
(...[, group]) =>
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})`
);
return { fixedCssCode };
}

View File

@ -8,7 +8,7 @@ export type BuildContextLike = {
projectBuildDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
bundler: "vite" | "webpack";
bundler: { type: "vite" } | { type: "webpack" };
};
assert<BuildContext extends BuildContextLike ? true : false>();
@ -20,7 +20,7 @@ export function replaceImportsInJsCode(params: {
const { jsCode, buildContext } = params;
const { fixedJsCode } = (() => {
switch (buildContext.bundler) {
switch (buildContext.bundler.type) {
case "vite":
return replaceImportsInJsCode_vite({
jsCode,

View File

@ -1,7 +1,12 @@
import { parse as urlParse } from "url";
import { join as pathJoin } from "path";
import {
join as pathJoin,
sep as pathSep,
relative as pathRelative,
resolve as pathResolve,
dirname as pathDirname
} from "path";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import { getNpmWorkspaceRootDirPath } from "../tools/getNpmWorkspaceRootDirPath";
import type { CliCommandOptions } from "../main";
import { z } from "zod";
import * as fs from "fs";
@ -21,9 +26,9 @@ import { type ThemeType } from "./constants";
import { id } from "tsafe/id";
import { symToStr } from "tsafe/symToStr";
import chalk from "chalk";
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
export type BuildContext = {
bundler: "vite" | "webpack";
themeVersion: string;
themeNames: [string, ...string[]];
extraThemeProperties: string[] | undefined;
@ -40,7 +45,7 @@ export type BuildContext = {
* In this case the urlPathname will be "/my-app/" */
urlPathname: string | undefined;
assetsDirPath: string;
npmWorkspaceRootDirPath: string;
fetchOptions: ProxyFetchOptions;
kcContextExclusionsFtlCode: string | undefined;
environmentVariables: { name: string; default: string }[];
themeSrcDirPath: string;
@ -49,10 +54,20 @@ export type BuildContext = {
keycloakVersionRange: KeycloakVersionRange;
jarFileBasename: string;
}[];
bundler:
| {
type: "vite";
}
| {
type: "webpack";
packageJsonDirPath: string;
packageJsonScripts: Record<string, string>;
};
};
export type BuildOptions = {
themeName?: string | string[];
themeVersion?: string;
environmentVariables?: { name: string; default: string }[];
extraThemeProperties?: string[];
artifactId?: string;
@ -60,6 +75,7 @@ export type BuildOptions = {
loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string;
kcContextExclusionsFtl?: string;
/** https://docs.keycloakify.dev/v/v10/targetting-specific-keycloak-versions */
keycloakVersionTargets?: BuildOptions.KeycloakVersionTargets;
};
@ -88,15 +104,54 @@ export function getBuildContext(params: {
}): BuildContext {
const { cliCommandOptions } = params;
const projectDirPath = (() => {
if (cliCommandOptions.projectDirPath === undefined) {
return process.cwd();
const projectDirPath =
cliCommandOptions.projectDirPath !== undefined
? getAbsoluteAndInOsFormatPath({
pathIsh: cliCommandOptions.projectDirPath,
cwd: process.cwd()
})
: process.cwd();
const { themeSrcDirPath } = (() => {
const srcDirPath = pathJoin(projectDirPath, "src");
const themeSrcDirPath: string | undefined = crawl({
dirPath: srcDirPath,
returnedPathsType: "relative to dirPath"
})
.map(fileRelativePath => {
for (const themeSrcDirBasename of ["keycloak-theme", "keycloak_theme"]) {
const split = fileRelativePath.split(themeSrcDirBasename);
if (split.length === 2) {
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
}
}
return undefined;
})
.filter(exclude(undefined))[0];
if (themeSrcDirPath !== undefined) {
return { themeSrcDirPath };
}
return getAbsoluteAndInOsFormatPath({
pathIsh: cliCommandOptions.projectDirPath,
cwd: process.cwd()
});
for (const themeType of [...themeTypes, "email"]) {
if (!fs.existsSync(pathJoin(srcDirPath, themeType))) {
continue;
}
return { themeSrcDirPath: srcDirPath };
}
console.log(
chalk.red(
[
`Can't locate your Keycloak theme source directory in .${pathSep}${pathRelative(process.cwd(), srcDirPath)}`,
`Make sure to either use the Keycloakify CLI in the root of your Keycloakify project or use the --project CLI option`,
`If you are collocating your Keycloak theme with your app you must have a directory named 'keycloak-theme' or 'keycloak_theme' in your 'src' directory`
].join("\n")
)
);
process.exit(1);
})();
const { resolvedViteConfig } = (() => {
@ -133,20 +188,58 @@ export function getBuildContext(params: {
return { resolvedViteConfig };
})();
const packageJsonFilePath = (function getPackageJSonDirPath(upCount: number): string {
const dirPath = pathResolve(
pathJoin(...[projectDirPath, ...Array(upCount).fill("..")])
);
assert(dirPath !== pathSep, "Root package.json not found");
success: {
const packageJsonFilePath = pathJoin(dirPath, "package.json");
if (!fs.existsSync(packageJsonFilePath)) {
break success;
}
const parsedPackageJson = z
.object({
name: z.string().optional(),
dependencies: z.record(z.string()).optional(),
devDependencies: z.record(z.string()).optional()
})
.parse(JSON.parse(fs.readFileSync(packageJsonFilePath).toString("utf8")));
if (
parsedPackageJson.dependencies?.keycloakify === undefined &&
parsedPackageJson.devDependencies?.keycloakify === undefined &&
parsedPackageJson.name !== "keycloakify" // NOTE: For local storybook build
) {
break success;
}
return packageJsonFilePath;
}
return getPackageJSonDirPath(upCount + 1);
})(0);
const parsedPackageJson = (() => {
type BuildOptions_packageJson = BuildOptions & {
projectBuildDirPath?: string;
staticDirPathInProjectBuildDirPath?: string;
publicDirPath?: string;
};
type ParsedPackageJson = {
name: string;
name?: string;
version?: string;
homepage?: string;
keycloakify?: BuildOptions_packageJson;
};
const zParsedPackageJson = z.object({
name: z.string(),
name: z.string().optional(),
version: z.string().optional(),
homepage: z.string().optional(),
keycloakify: id<z.ZodType<BuildOptions_packageJson>>(
@ -168,6 +261,9 @@ export function getBuildContext(params: {
)
.optional(),
themeName: z.union([z.string(), z.array(z.string())]).optional(),
themeVersion: z.string().optional(),
staticDirPathInProjectBuildDirPath: z.string().optional(),
publicDirPath: z.string().optional(),
keycloakVersionTargets: id<
z.ZodType<BuildOptions.KeycloakVersionTargets>
>(
@ -221,59 +317,24 @@ export function getBuildContext(params: {
assert<Equals<Got, Expected>>();
}
const configurationPackageJsonFilePath = (() => {
const rootPackageJsonFilePath = pathJoin(projectDirPath, "package.json");
return fs.existsSync(rootPackageJsonFilePath)
? rootPackageJsonFilePath
: packageJsonFilePath;
})();
return zParsedPackageJson.parse(
JSON.parse(
fs.readFileSync(pathJoin(projectDirPath, "package.json")).toString("utf8")
)
JSON.parse(fs.readFileSync(configurationPackageJsonFilePath).toString("utf8"))
);
})();
const buildOptions: BuildOptions = {
const buildOptions = {
...parsedPackageJson.keycloakify,
...resolvedViteConfig?.buildOptions
};
const { themeSrcDirPath } = (() => {
const srcDirPath = pathJoin(projectDirPath, "src");
const themeSrcDirPath: string | undefined = crawl({
dirPath: srcDirPath,
returnedPathsType: "relative to dirPath"
})
.map(fileRelativePath => {
for (const themeSrcDirBasename of ["keycloak-theme", "keycloak_theme"]) {
const split = fileRelativePath.split(themeSrcDirBasename);
if (split.length === 2) {
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
}
}
return undefined;
})
.filter(exclude(undefined))[0];
if (themeSrcDirPath !== undefined) {
return { themeSrcDirPath };
}
for (const themeType of [...themeTypes, "email"]) {
if (!fs.existsSync(pathJoin(srcDirPath, themeType))) {
continue;
}
return { themeSrcDirPath: srcDirPath };
}
console.log(
chalk.red(
[
"Can't locate your keycloak theme source directory.",
"See: https://docs.keycloakify.dev/v/v10/keycloakify-in-my-app/collocation"
].join("\n")
)
);
process.exit(1);
})();
const recordIsImplementedByThemeType = objectFromEntries(
(["login", "account", "email"] as const).map(themeType => [
themeType,
@ -283,12 +344,14 @@ export function getBuildContext(params: {
const themeNames = ((): [string, ...string[]] => {
if (buildOptions.themeName === undefined) {
return [
parsedPackageJson.name
.replace(/^@(.*)/, "$1")
.split("/")
.join("-")
];
return parsedPackageJson.name === undefined
? ["keycloakify"]
: [
parsedPackageJson.name
.replace(/^@(.*)/, "$1")
.split("/")
.join("-")
];
}
if (typeof buildOptions.themeName === "string") {
@ -308,9 +371,9 @@ export function getBuildContext(params: {
break webpack;
}
if (parsedPackageJson.keycloakify?.projectBuildDirPath !== undefined) {
if (buildOptions.projectBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({
pathIsh: parsedPackageJson.keycloakify.projectBuildDirPath,
pathIsh: buildOptions.projectBuildDirPath,
cwd: projectDirPath
});
}
@ -321,17 +384,30 @@ export function getBuildContext(params: {
return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
})();
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({
projectDirPath,
dependencyExpected: "keycloakify"
});
const bundler = resolvedViteConfig !== undefined ? "vite" : "webpack";
return {
bundler,
themeVersion:
process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
bundler:
resolvedViteConfig !== undefined
? { type: "vite" }
: (() => {
const { scripts } = z
.object({
scripts: z.record(z.string()).optional()
})
.parse(
JSON.parse(
fs.readFileSync(packageJsonFilePath).toString("utf8")
)
);
return {
type: "webpack",
packageJsonDirPath: pathDirname(packageJsonFilePath),
packageJsonScripts: scripts ?? {}
};
})(),
themeVersion: buildOptions.themeVersion ?? parsedPackageJson.version ?? "0.0.0",
themeNames,
extraThemeProperties: buildOptions.extraThemeProperties,
groupId: (() => {
@ -373,14 +449,21 @@ export function getBuildContext(params: {
);
})(),
publicDirPath: (() => {
if (process.env.PUBLIC_DIR_PATH !== undefined) {
return getAbsoluteAndInOsFormatPath({
pathIsh: process.env.PUBLIC_DIR_PATH,
cwd: projectDirPath
});
}
webpack: {
if (resolvedViteConfig !== undefined) {
break webpack;
}
if (process.env.PUBLIC_DIR_PATH !== undefined) {
if (buildOptions.publicDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({
pathIsh: process.env.PUBLIC_DIR_PATH,
pathIsh: buildOptions.publicDirPath,
cwd: projectDirPath
});
}
@ -400,7 +483,11 @@ export function getBuildContext(params: {
});
}
return pathJoin(npmWorkspaceRootDirPath, "node_modules", ".cache");
return pathJoin(
pathDirname(packageJsonFilePath),
"node_modules",
".cache"
);
})(),
"keycloakify"
);
@ -437,12 +524,18 @@ export function getBuildContext(params: {
break webpack;
}
if (buildOptions.staticDirPathInProjectBuildDirPath !== undefined) {
getAbsoluteAndInOsFormatPath({
pathIsh: buildOptions.staticDirPathInProjectBuildDirPath,
cwd: projectBuildDirPath
});
}
return pathJoin(projectBuildDirPath, "static");
}
return pathJoin(projectBuildDirPath, resolvedViteConfig.assetsDir);
})(),
npmWorkspaceRootDirPath,
kcContextExclusionsFtlCode: (() => {
if (buildOptions.kcContextExclusionsFtl === undefined) {
return undefined;
@ -462,6 +555,33 @@ export function getBuildContext(params: {
environmentVariables: buildOptions.environmentVariables ?? [],
recordIsImplementedByThemeType,
themeSrcDirPath,
fetchOptions: getProxyFetchOptions({
npmConfigGetCwd: (function callee(upCount: number): string {
const dirPath = pathResolve(
pathJoin(...[projectDirPath, ...Array(upCount).fill("..")])
);
assert(
dirPath !== pathSep,
"Couldn't find a place to run 'npm config get'"
);
try {
child_process.execSync("npm config get", {
cwd: dirPath,
stdio: "ignore"
});
} catch (error) {
if (String(error).includes("ENOWORKSPACES")) {
return callee(upCount + 1);
}
throw error;
}
return dirPath;
})(0)
}),
jarTargets: (() => {
const getDefaultJarFileBasename = (range: string) =>
`keycloak-theme-for-kc-${range}.jar`;
@ -677,6 +797,8 @@ export function getBuildContext(params: {
null,
2
);
message +=
"\nSee: https://docs.keycloakify.dev/v/v10/targetting-specific-keycloak-versions";
return message;
})()

View File

@ -1,9 +1,7 @@
export const nameOfTheLocalizationRealmOverridesUserProfileProperty =
"__localizationRealmOverridesUserProfile";
export const keycloak_resources = "keycloak-resources";
export const resources_common = "resources-common";
export const lastKeycloakVersionWithAccountV1 = "21.1.2";
export const basenameOfTheKeycloakifyResourcesDir = "build";
export const basenameOfTheKeycloakifyResourcesDir = "dist";
export const themeTypes = ["login", "account"] as const;
export const accountV1ThemeName = "account-v1";
@ -69,3 +67,5 @@ export type LoginThemePageId = (typeof loginThemePageIds)[number];
export type AccountThemePageId = (typeof accountThemePageIds)[number];
export const containerName = "keycloak-keycloakify";
export const fallbackLanguageTag = "en";

View File

@ -37,10 +37,7 @@ export async function copyKeycloakResourcesToPublic(params: {
buildContext: {
loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(),
cacheDirPath: pathRelative(destDirPath, buildContext.cacheDirPath),
npmWorkspaceRootDirPath: pathRelative(
destDirPath,
buildContext.npmWorkspaceRootDirPath
)
fetchOptions: buildContext.fetchOptions
}
},
null,

View File

@ -6,7 +6,7 @@ import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
export type BuildContextLike = {
cacheDirPath: string;
npmWorkspaceRootDirPath: string;
fetchOptions: BuildContext["fetchOptions"];
};
assert<BuildContext extends BuildContextLike ? true : false>();
@ -23,7 +23,7 @@ export async function downloadKeycloakDefaultTheme(params: {
const { extractedDirPath } = await downloadAndExtractArchive({
url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
cacheDirPath: buildContext.cacheDirPath,
npmWorkspaceRootDirPath: buildContext.npmWorkspaceRootDirPath,
fetchOptions: buildContext.fetchOptions,
uniqueIdOfOnOnArchiveFile: "downloadKeycloakDefaultTheme",
onArchiveFile: async params => {
const fileRelativePath = pathRelative("theme", params.fileRelativePath);
@ -112,6 +112,18 @@ export async function downloadKeycloakDefaultTheme(params: {
"dist",
"fonts",
"OpenSans-Semibold-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.ttf"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.woff"
)
];
}
@ -183,12 +195,42 @@ export async function downloadKeycloakDefaultTheme(params: {
"fonts",
"OpenSans-Light-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Bold-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Bold-webfont.woff"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Bold-webfont.ttf"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"fontawesome-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.ttf"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.woff"
),
pathJoin("jquery", "dist", "jquery.min.js")
];
}

View File

@ -1,16 +1,15 @@
import * as child_process from "child_process";
import { Deferred } from "evt/tools/Deferred";
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import type { BuildContext } from "../shared/buildContext";
import * as fs from "fs";
import { join as pathJoin } from "path";
import chalk from "chalk";
import { sep as pathSep, join as pathJoin } from "path";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
export type BuildContextLike = {
projectDirPath: string;
keycloakifyBuildDirPath: string;
bundler: "vite" | "webpack";
npmWorkspaceRootDirPath: string;
bundler: BuildContext["bundler"];
projectBuildDirPath: string;
};
@ -21,95 +20,29 @@ export async function appBuild(params: {
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
const { bundler } = buildContext;
switch (buildContext.bundler.type) {
case "vite":
return appBuild_vite({ buildContext });
case "webpack":
return appBuild_webpack({ buildContext });
}
}
const { command, args, cwd } = (() => {
switch (bundler) {
case "vite":
return {
command: "npx",
args: ["vite", "build"],
cwd: buildContext.projectDirPath
};
case "webpack": {
for (const dirPath of [
buildContext.projectDirPath,
buildContext.npmWorkspaceRootDirPath
]) {
try {
const parsedPackageJson = JSON.parse(
fs
.readFileSync(pathJoin(dirPath, "package.json"))
.toString("utf8")
);
async function appBuild_vite(params: {
buildContext: BuildContextLike;
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
const [scriptName] =
Object.entries(parsedPackageJson.scripts).find(
([, scriptValue]) => {
assert(is<string>(scriptValue));
if (
scriptValue.includes("webpack") &&
scriptValue.includes("--mode production")
) {
return true;
}
assert(buildContext.bundler.type === "vite");
if (
scriptValue.includes("react-scripts") &&
scriptValue.includes("build")
) {
return true;
}
const dIsSuccess = new Deferred<boolean>();
if (
scriptValue.includes("react-app-rewired") &&
scriptValue.includes("build")
) {
return true;
}
console.log(chalk.blue("Running: 'npx vite build'"));
if (
scriptValue.includes("craco") &&
scriptValue.includes("build")
) {
return true;
}
if (
scriptValue.includes("ng") &&
scriptValue.includes("build")
) {
return true;
}
return false;
}
) ?? [];
if (scriptName === undefined) {
continue;
}
return {
command: "npm",
args: ["run", scriptName],
cwd: dirPath
};
} catch {
continue;
}
}
throw new Error(
"Keycloakify was unable to determine which script is responsible for building the app."
);
}
}
})();
const dResult = new Deferred<{ isSuccess: boolean }>();
const child = child_process.spawn(command, args, { cwd, shell: true });
const child = child_process.spawn("npx", ["vite", "build"], {
cwd: buildContext.projectDirPath,
shell: true
});
child.stdout.on("data", data => {
if (data.toString("utf8").includes("gzip:")) {
@ -121,9 +54,127 @@ export async function appBuild(params: {
child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => dResult.resolve({ isSuccess: code === 0 }));
child.on("exit", code => dIsSuccess.resolve(code === 0));
const { isSuccess } = await dResult.pr;
const isSuccess = await dIsSuccess.pr;
return { isAppBuildSuccess: isSuccess };
}
async function appBuild_webpack(params: {
buildContext: BuildContextLike;
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
assert(buildContext.bundler.type === "webpack");
const entries = Object.entries(buildContext.bundler.packageJsonScripts).filter(
([, scriptCommand]) => scriptCommand.includes("keycloakify build")
);
if (entries.length === 0) {
console.log(
chalk.red(
[
`You should have a script in your package.json at ${buildContext.bundler.packageJsonDirPath}`,
`that includes the 'keycloakify build' command`
].join(" ")
)
);
process.exit(-1);
}
const entry =
entries.length === 1
? entries[0]
: entries.find(([scriptName]) => scriptName === "build-keycloak-theme");
if (entry === undefined) {
console.log(
chalk.red(
"There's multiple candidate script for building your app, name one 'build-keycloak-theme'"
)
);
process.exit(-1);
}
const [scriptName, scriptCommand] = entry;
const { appBuildSubCommands } = (() => {
const appBuildSubCommands: string[] = [];
for (const subCmd of scriptCommand.split("&&").map(s => s.trim())) {
if (subCmd.includes("keycloakify build")) {
break;
}
appBuildSubCommands.push(subCmd);
}
return { appBuildSubCommands };
})();
if (appBuildSubCommands.length === 0) {
console.log(
chalk.red(
`Your ${scriptName} script should look like "... && keycloakify build ..."`
)
);
process.exit(-1);
}
let commandCwd = buildContext.bundler.packageJsonDirPath;
for (const subCommand of appBuildSubCommands) {
const dIsSuccess = new Deferred<boolean>();
const [command, ...args] = subCommand.split(" ");
if (command === "cd") {
const [pathIsh] = args;
commandCwd = getAbsoluteAndInOsFormatPath({
pathIsh,
cwd: commandCwd
});
continue;
}
console.log(chalk.blue(`Running: '${subCommand}'`));
const child = child_process.spawn(command, args, {
cwd: commandCwd,
env: {
...process.env,
PATH: (() => {
const separator = pathSep === "/" ? ":" : ";";
return [
pathJoin(
buildContext.bundler.packageJsonDirPath,
"node_modules",
".bin"
),
...(process.env.PATH ?? "").split(separator)
].join(separator);
})()
},
shell: true
});
child.stdout.on("data", data => process.stdout.write(data));
child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => dIsSuccess.resolve(code === 0));
const isSuccess = await dIsSuccess.pr;
if (!isSuccess) {
return { isAppBuildSuccess: false };
}
}
return { isAppBuildSuccess: true };
}

View File

@ -7,8 +7,6 @@ import type { BuildContext } from "../shared/buildContext";
export type BuildContextLike = {
projectDirPath: string;
keycloakifyBuildDirPath: string;
bundler: "vite" | "webpack";
npmWorkspaceRootDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();

View File

@ -1,6 +1,8 @@
{
"id": "34c5f904-d66e-4d8f-8876-8f00d9fa9d6c",
"realm": "myrealm",
"displayName": "",
"displayNameHtml": "",
"notBefore": 0,
"defaultSignatureAlgorithm": "RS256",
"revokeRefreshToken": false,
@ -1356,11 +1358,11 @@
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper",
"saml-user-property-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"saml-role-list-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper"
]
@ -1431,13 +1433,13 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"saml-user-property-mapper",
"saml-role-list-mapper",
"oidc-full-name-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper",
"oidc-usermodel-property-mapper"
]
}
@ -2127,17 +2129,20 @@
"dockerAuthenticationFlow": "docker auth",
"attributes": {
"cibaBackchannelTokenDeliveryMode": "poll",
"cibaExpiresIn": "120",
"cibaAuthRequestedUserHint": "login_hint",
"oauth2DeviceCodeLifespan": "600",
"clientOfflineSessionMaxLifespan": "0",
"oauth2DevicePollingInterval": "5",
"clientSessionIdleTimeout": "0",
"parRequestUriLifespan": "60",
"clientSessionMaxLifespan": "0",
"userProfileEnabled": "true",
"clientOfflineSessionIdleTimeout": "0",
"cibaInterval": "5",
"realmReusableOtpCode": "false"
"realmReusableOtpCode": "false",
"cibaExpiresIn": "120",
"oauth2DeviceCodeLifespan": "600",
"parRequestUriLifespan": "60",
"clientSessionMaxLifespan": "0",
"frontendUrl": "",
"acr.loa.map": "{}"
},
"keycloakVersion": "23.0.7",
"userManagedAccessAllowed": false,

View File

@ -121,7 +121,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
if (!isAppBuildSuccess) {
console.log(
chalk.red(
`App build failed, exiting. Try running 'npm run build' and see what's wrong.`
`App build failed, exiting. Try building your app (e.g 'npm run build') and see what's wrong.`
)
);
process.exit(1);

View File

@ -1,12 +1,12 @@
import fetch from "make-fetch-happen";
import fetch, { type FetchOptions } from "make-fetch-happen";
import { mkdir, unlink, writeFile, readdir, readFile } from "fs/promises";
import { dirname as pathDirname, join as pathJoin } from "path";
import { assert } from "tsafe/assert";
import { extractArchive } from "../extractArchive";
import { existsAsync } from "../fs.existsAsync";
import { getProxyFetchOptions } from "./fetchProxyOptions";
import { extractArchive } from "./extractArchive";
import { existsAsync } from "./fs.existsAsync";
import * as crypto from "crypto";
import { rm } from "../fs.rm";
import { rm } from "./fs.rm";
export async function downloadAndExtractArchive(params: {
url: string;
@ -20,15 +20,10 @@ export async function downloadAndExtractArchive(params: {
}) => Promise<void>;
}) => Promise<void>;
cacheDirPath: string;
npmWorkspaceRootDirPath: string;
fetchOptions: FetchOptions | undefined;
}): Promise<{ extractedDirPath: string }> {
const {
url,
uniqueIdOfOnOnArchiveFile,
onArchiveFile,
cacheDirPath,
npmWorkspaceRootDirPath
} = params;
const { url, uniqueIdOfOnOnArchiveFile, onArchiveFile, cacheDirPath, fetchOptions } =
params;
const archiveFileBasename = url.split("?")[0].split("/").reverse()[0];
@ -55,10 +50,7 @@ export async function downloadAndExtractArchive(params: {
await mkdir(pathDirname(archiveFilePath), { recursive: true });
const response = await fetch(
url,
await getProxyFetchOptions({ npmWorkspaceRootDirPath })
);
const response = await fetch(url, fetchOptions);
response.body?.setMaxListeners(Number.MAX_VALUE);
assert(typeof response.body !== "undefined" && response.body != null);

View File

@ -1 +0,0 @@
export * from "./downloadAndExtractArchive";

View File

@ -1,61 +1,40 @@
import { exec as execCallback } from "child_process";
import { readFile } from "fs/promises";
import { type FetchOptions } from "make-fetch-happen";
import { promisify } from "util";
function ensureArray<T>(arg0: T | T[]) {
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
}
function ensureSingleOrNone<T>(arg0: T | T[]) {
if (!Array.isArray(arg0)) return arg0;
if (arg0.length === 0) return undefined;
if (arg0.length === 1) return arg0[0];
throw new Error(
"Illegal configuration, expected a single value but found multiple: " +
arg0.map(String).join(", ")
);
}
type NPMConfig = Record<string, string | string[]>;
/**
* Get npm configuration as map
*/
async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
const { npmWorkspaceRootDirPath } = params;
const exec = promisify(execCallback);
const stdout = await exec("npm config get", {
encoding: "utf8",
cwd: npmWorkspaceRootDirPath
}).then(({ stdout }) => stdout);
const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) =>
key in cfg
? { ...cfg, [key]: [...ensureArray(cfg[key]), value] }
: { ...cfg, [key]: value };
return stdout
.split("\n")
.filter(line => !line.startsWith(";"))
.map(line => line.trim())
.map(line => line.split("=", 2) as [string, string])
.reduce(npmConfigReducer, {} as NPMConfig);
}
import * as child_process from "child_process";
import * as fs from "fs";
export type ProxyFetchOptions = Pick<
FetchOptions,
"proxy" | "noProxy" | "strictSSL" | "cert" | "ca"
>;
export async function getProxyFetchOptions(params: {
npmWorkspaceRootDirPath: string;
}): Promise<ProxyFetchOptions> {
const { npmWorkspaceRootDirPath } = params;
export function getProxyFetchOptions(params: {
npmConfigGetCwd: string;
}): ProxyFetchOptions {
const { npmConfigGetCwd } = params;
const cfg = await getNmpConfig({ npmWorkspaceRootDirPath });
const cfg = (() => {
const output = child_process
.execSync("npm config get", {
cwd: npmConfigGetCwd
})
.toString("utf8");
return output
.split("\n")
.filter(line => !line.startsWith(";"))
.map(line => line.trim())
.map(line => line.split("=", 2) as [string, string])
.reduce(
(
cfg: Record<string, string | string[]>,
[key, value]: [string, string]
) =>
key in cfg
? { ...cfg, [key]: [...ensureArray(cfg[key]), value] }
: { ...cfg, [key]: value },
{}
);
})();
const proxy = ensureSingleOrNone(cfg["https-proxy"] ?? cfg["proxy"]);
const noProxy = cfg["noproxy"] ?? cfg["no-proxy"];
@ -71,17 +50,16 @@ export async function getProxyFetchOptions(params: {
if (typeof cafile !== "undefined" && cafile !== "null") {
ca.push(
...(await (async () => {
function chunks<T>(arr: T[], size: number = 2) {
return arr
.map((_, i) => i % size == 0 && arr.slice(i, i + size))
.filter(Boolean) as T[][];
}
const cafileContent = await readFile(cafile, "utf-8");
...(() => {
const cafileContent = fs.readFileSync(cafile).toString("utf8");
const newLinePlaceholder = "NEW_LINE_PLACEHOLDER_xIsPsK23svt";
const chunks = <T>(arr: T[], size: number = 2) =>
arr
.map((_, i) => i % size == 0 && arr.slice(i, i + size))
.filter(Boolean) as T[][];
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(
ca =>
ca
@ -90,7 +68,7 @@ export async function getProxyFetchOptions(params: {
.replace(new RegExp(`^${newLinePlaceholder}`), "")
.replace(new RegExp(newLinePlaceholder, "g"), "\\n")
);
})())
})()
);
}
@ -102,3 +80,17 @@ export async function getProxyFetchOptions(params: {
ca: ca.length === 0 ? undefined : ca
};
}
function ensureArray<T>(arg0: T | T[]) {
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
}
function ensureSingleOrNone<T>(arg0: T | T[]) {
if (!Array.isArray(arg0)) return arg0;
if (arg0.length === 0) return undefined;
if (arg0.length === 1) return arg0[0];
throw new Error(
"Illegal configuration, expected a single value but found multiple: " +
arg0.map(String).join(", ")
);
}

View File

@ -1,73 +0,0 @@
import * as child_process from "child_process";
import { join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
import { assert } from "tsafe/assert";
import * as fs from "fs";
export function getNpmWorkspaceRootDirPath(params: {
projectDirPath: string;
dependencyExpected: string;
}) {
const { projectDirPath, dependencyExpected } = params;
const npmWorkspaceRootDirPath = (function callee(depth: number): string {
const cwd = pathResolve(
pathJoin(...[projectDirPath, ...Array(depth).fill("..")])
);
assert(cwd !== pathSep, "NPM workspace not found");
try {
child_process.execSync("npm config get", {
cwd,
stdio: "ignore"
});
} catch (error) {
if (String(error).includes("ENOWORKSPACES")) {
return callee(depth + 1);
}
throw error;
}
const packageJsonFilePath = pathJoin(cwd, "package.json");
if (!fs.existsSync(packageJsonFilePath)) {
return callee(depth + 1);
}
assert(fs.existsSync(packageJsonFilePath));
const parsedPackageJson = JSON.parse(
fs.readFileSync(packageJsonFilePath).toString("utf8")
);
let isExpectedDependencyFound = false;
for (const dependenciesOrDevDependencies of [
"dependencies",
"devDependencies"
] as const) {
const dependencies = parsedPackageJson[dependenciesOrDevDependencies];
if (dependencies === undefined) {
continue;
}
assert(dependencies instanceof Object);
if (dependencies[dependencyExpected] === undefined) {
continue;
}
isExpectedDependencyFound = true;
}
if (!isExpectedDependencyFound && parsedPackageJson.name !== dependencyExpected) {
return callee(depth + 1);
}
return cwd;
})(0);
return { npmWorkspaceRootDirPath };
}

View File

@ -1,8 +1,4 @@
import type {
ThemeType,
LoginThemePageId,
nameOfTheLocalizationRealmOverridesUserProfileProperty
} from "keycloakify/bin/shared/constants";
import type { ThemeType, LoginThemePageId } from "keycloakify/bin/shared/constants";
import type { ExtractAfterStartingWith } from "keycloakify/tools/ExtractAfterStartingWith";
import type { ValueOf } from "keycloakify/tools/ValueOf";
import { assert } from "tsafe/assert";
@ -158,7 +154,10 @@ export declare namespace KcContext {
ssoLoginInOtherTabsUrl: string;
};
properties: {};
__localizationRealmOverridesUserProfile?: Record<string, string>;
"x-keycloakify": {
realmMessageBundleUserProfile: Record<string, string> | undefined;
realmMessageBundleTermsText: string | undefined;
};
};
export type SamlPostForm = Common & {
@ -276,6 +275,7 @@ export declare namespace KcContext {
lastName?: string;
markedForEviction?: boolean;
};
__localizationRealmOverridesTermsText?: string;
};
export type LoginDeviceVerifyUserCode = Common & {
@ -772,11 +772,3 @@ export type PasswordPolicies = {
/** Whether the password can be the email address */
notEmail?: boolean;
};
assert<
KcContext.Common extends Partial<
Record<typeof nameOfTheLocalizationRealmOverridesUserProfileProperty, unknown>
>
? true
: false
>();

View File

@ -161,7 +161,10 @@ export const kcContextCommonMock: KcContext.Common = {
scripts: [],
isAppInitiatedAction: false,
properties: {},
__localizationRealmOverridesUserProfile: {}
"x-keycloakify": {
realmMessageBundleUserProfile: undefined,
realmMessageBundleTermsText: undefined
}
};
const loginUrl = {

View File

@ -29,7 +29,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { msg, msgStr, getChangeLocaleUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;
@ -153,7 +153,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
role="menuitem"
id={`language-${i + 1}`}
className={kcClsx("kcLocaleItemClass")}
href={getChangeLocalUrl(languageTag)}
href={getChangeLocaleUrl(languageTag)}
>
{labelBySupportedLanguageTag[languageTag]}
</a>

View File

@ -1,19 +1,19 @@
import "keycloakify/tools/Object.fromEntries";
import { useEffect, useState } from "react";
import { assert } from "tsafe/assert";
import messages_fallbackLanguage from "./baseMessages/en";
import { getMessages } from "./baseMessages";
import type { KcContext } from "../KcContext";
import { Reflect } from "tsafe/Reflect";
export const fallbackLanguageTag = "en";
import { fallbackLanguageTag } from "keycloakify/bin/shared/constants";
export type KcContextLike = {
locale?: {
currentLanguageTag: string;
supported: { languageTag: string; url: string; label: string }[];
};
__localizationRealmOverridesUserProfile?: Record<string, string>;
"x-keycloakify": {
realmMessageBundleUserProfile: Record<string, string> | undefined;
realmMessageBundleTermsText: string | undefined;
};
};
assert<KcContext extends KcContextLike ? true : false>();
@ -31,7 +31,7 @@ export type GenericI18n<MessageKey extends string> = {
* Redirect to this url to change the language.
* After reload currentLanguageTag === newLanguageTag
*/
getChangeLocalUrl: (newLanguageTag: string) => string;
getChangeLocaleUrl: (newLanguageTag: string) => string;
/**
* e.g. "en" => "English", "fr" => "Français", ...
*
@ -89,7 +89,9 @@ export type GenericI18n<MessageKey extends string> = {
isFetchingTranslations: boolean;
};
function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: { [languageTag: string]: { [key in ExtraMessageKey]: string } }) {
export function createGetI18n<ExtraMessageKey extends string = never>(messageBundle: {
[languageTag: string]: { [key in ExtraMessageKey]: string };
}) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
type Result = { i18n: I18n; prI18n_currentLanguage: Promise<I18n> | undefined };
@ -109,9 +111,9 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
return cachedResult;
}
const partialI18n: Pick<I18n, "currentLanguageTag" | "getChangeLocalUrl" | "labelBySupportedLanguageTag"> = {
const partialI18n: Pick<I18n, "currentLanguageTag" | "getChangeLocaleUrl" | "labelBySupportedLanguageTag"> = {
currentLanguageTag: kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag,
getChangeLocalUrl: newLanguageTag => {
getChangeLocaleUrl: newLanguageTag => {
const { locale } = kcContext;
assert(locale !== undefined, "Internationalization not enabled");
@ -127,9 +129,10 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
const { createI18nTranslationFunctions } = createI18nTranslationFunctionsFactory<MessageKey, ExtraMessageKey>({
messages_fallbackLanguage,
extraMessages_fallbackLanguage: extraMessages[fallbackLanguageTag],
extraMessages: extraMessages[partialI18n.currentLanguageTag],
__localizationRealmOverridesUserProfile: kcContext.__localizationRealmOverridesUserProfile
messageBundle_fallbackLanguage: messageBundle[fallbackLanguageTag],
messageBundle_currentLanguage: messageBundle[partialI18n.currentLanguageTag],
realmMessageBundleUserProfile: kcContext["x-keycloakify"].realmMessageBundleUserProfile,
realmMessageBundleTermsText: kcContext["x-keycloakify"].realmMessageBundleTermsText
});
const isCurrentLanguageFallbackLanguage = partialI18n.currentLanguageTag === fallbackLanguageTag;
@ -137,17 +140,19 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
const result: Result = {
i18n: {
...partialI18n,
...createI18nTranslationFunctions({ messages: undefined }),
...createI18nTranslationFunctions({
messages_currentLanguage: isCurrentLanguageFallbackLanguage ? messages_fallbackLanguage : undefined
}),
isFetchingTranslations: !isCurrentLanguageFallbackLanguage
},
prI18n_currentLanguage: isCurrentLanguageFallbackLanguage
? undefined
: (async () => {
const messages = await getMessages(partialI18n.currentLanguageTag);
const messages_currentLanguage = await getMessages(partialI18n.currentLanguageTag);
const i18n_currentLanguage: I18n = {
...partialI18n,
...createI18nTranslationFunctions({ messages }),
...createI18nTranslationFunctions({ messages_currentLanguage }),
isFetchingTranslations: false
};
@ -170,67 +175,40 @@ function createGetI18n<ExtraMessageKey extends string = never>(extraMessages: {
return { getI18n };
}
export function createUseI18n<ExtraMessageKey extends string = never>(extraMessages: {
[languageTag: string]: { [key in ExtraMessageKey]: string };
}) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
const { getI18n } = createGetI18n(extraMessages);
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n } {
const { kcContext } = params;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(i18n);
useEffect(() => {
let isActive = true;
prI18n_currentLanguage?.then(i18n => {
if (!isActive) {
return;
}
setI18n_toReturn(i18n);
});
return () => {
isActive = false;
};
}, []);
return { i18n: i18n_toReturn };
}
return { useI18n, ofTypeI18n: Reflect<I18n>() };
}
function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraMessageKey extends string>(params: {
messages_fallbackLanguage: Record<MessageKey, string>;
extraMessages_fallbackLanguage: Record<ExtraMessageKey, string> | undefined;
extraMessages: Partial<Record<ExtraMessageKey, string>> | undefined;
__localizationRealmOverridesUserProfile: Record<string, string> | undefined;
messageBundle_fallbackLanguage: Record<ExtraMessageKey, string> | undefined;
messageBundle_currentLanguage: Partial<Record<ExtraMessageKey, string>> | undefined;
realmMessageBundleUserProfile: Record<string, string> | undefined;
realmMessageBundleTermsText: string | undefined;
}) {
const { __localizationRealmOverridesUserProfile, extraMessages } = params;
const { messageBundle_currentLanguage, realmMessageBundleUserProfile, realmMessageBundleTermsText } = params;
const messages_fallbackLanguage = {
...params.messages_fallbackLanguage,
...params.extraMessages_fallbackLanguage
...params.messageBundle_fallbackLanguage
};
function createI18nTranslationFunctions(params: {
messages: Partial<Record<MessageKey, string>> | undefined;
messages_currentLanguage: Partial<Record<MessageKey, string>> | undefined;
}): Pick<GenericI18n<MessageKey | ExtraMessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
const messages = {
...params.messages,
...extraMessages
const messages_currentLanguage = {
...params.messages_currentLanguage,
...messageBundle_currentLanguage
};
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
const { key, args, doRenderAsHtml } = props;
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (messages_fallbackLanguage as any)[key];
const messageOrUndefined: string | undefined = (() => {
const messageOrUndefined = (messages_currentLanguage as any)[key] ?? (messages_fallbackLanguage as any)[key];
if (key === "termsText" && realmMessageBundleTermsText !== undefined) {
return realmMessageBundleTermsText;
}
return messageOrUndefined;
})();
if (messageOrUndefined === undefined) {
return undefined;
@ -281,8 +259,8 @@ function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraM
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): JSX.Element | string {
const { key, args, doRenderAsHtml } = props;
if (__localizationRealmOverridesUserProfile !== undefined && key in __localizationRealmOverridesUserProfile) {
const resolvedMessage = __localizationRealmOverridesUserProfile[key];
if (realmMessageBundleUserProfile !== undefined && key in realmMessageBundleUserProfile) {
const resolvedMessage = realmMessageBundleUserProfile[key];
return doRenderAsHtml ? (
<span

View File

@ -1,5 +1,4 @@
import type { GenericI18n, MessageKey, KcContextLike } from "./i18n";
export type { MessageKey, KcContextLike };
export type I18n = GenericI18n<MessageKey>;
export { createUseI18n } from "./i18n";
export { fallbackLanguageTag } from "./i18n";
export { createUseI18n } from "./useI18n";

44
src/login/i18n/useI18n.ts Normal file
View File

@ -0,0 +1,44 @@
import { useEffect, useState } from "react";
import {
createGetI18n,
type GenericI18n,
type MessageKey,
type KcContextLike
} from "./i18n";
import { Reflect } from "tsafe/Reflect";
export function createUseI18n<ExtraMessageKey extends string = never>(messageBundle: {
[languageTag: string]: { [key in ExtraMessageKey]: string };
}) {
type I18n = GenericI18n<MessageKey | ExtraMessageKey>;
const { getI18n } = createGetI18n(messageBundle);
function useI18n(params: { kcContext: KcContextLike }): { i18n: I18n } {
const { kcContext } = params;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(i18n);
useEffect(() => {
let isActive = true;
prI18n_currentLanguage?.then(i18n => {
if (!isActive) {
return;
}
setI18n_toReturn(i18n);
});
return () => {
isActive = false;
};
}, []);
return { i18n: i18n_toReturn };
}
return { useI18n, ofTypeI18n: Reflect<I18n>() };
}

View File

@ -1,4 +1,3 @@
export type { ExtendKcContext, Attribute } from "keycloakify/login/KcContext";
export type { ClassKey } from "keycloakify/login/TemplateProps";
export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
export { createUseI18n } from "keycloakify/login/i18n";

View File

@ -1,57 +0,0 @@
import { fallbackLanguageTag } from "keycloakify/login/i18n";
import { assert } from "tsafe/assert";
import {
createStatefulObservable,
useRerenderOnChange
} from "keycloakify/tools/StatefulObservable";
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
import { KcContext } from "../KcContext";
const obs = createStatefulObservable<
| {
termsMarkdown: string;
termsLanguageTag: string | undefined;
}
| undefined
>(() => undefined);
export type KcContextLike = {
pageId: string;
locale?: {
currentLanguageTag: string;
};
termsAcceptanceRequired?: boolean;
};
assert<KcContext extends KcContextLike ? true : false>();
/** Allow to avoid bundling the terms and download it on demand*/
export function useDownloadTerms(params: {
kcContext: KcContextLike;
downloadTermsMarkdown: (params: {
currentLanguageTag: string;
}) => Promise<{ termsMarkdown: string; termsLanguageTag: string | undefined }>;
}) {
const { kcContext, downloadTermsMarkdown } = params;
useOnFistMount(async () => {
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
obs.current = await downloadTermsMarkdown({
currentLanguageTag:
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
});
}
});
}
export function useTermsMarkdown() {
useRerenderOnChange(obs);
if (obs.current === undefined) {
return { isDownloadComplete: false as const };
}
const { termsMarkdown, termsLanguageTag } = obs.current;
return { isDownloadComplete: true, termsMarkdown, termsLanguageTag };
}

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useReducer } from "react";
import { assert } from "tsafe/assert";
import { assert } from "keycloakify/tools/assert";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";

View File

@ -1,6 +1,6 @@
import { useState, useEffect, useReducer } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { assert } from "tsafe/assert";
import { assert } from "keycloakify/tools/assert";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";

View File

@ -1,5 +1,5 @@
import { useEffect, useReducer } from "react";
import { assert } from "tsafe/assert";
import { assert } from "keycloakify/tools/assert";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";

View File

@ -1,7 +1,5 @@
import { useState } from "react";
import { Markdown } from "keycloakify/tools/Markdown";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
import type { PageProps } from "keycloakify/login/pages/PageProps";
@ -80,21 +78,12 @@ function TermsAcceptance(props: { i18n: I18n; kcClsx: KcClsx; messagesPerField:
const { msg } = i18n;
// NOTE: Refer to https://docs.keycloakify.dev/terms-and-conditions to load your terms and conditions.
const { termsMarkdown } = useTermsMarkdown();
if (termsMarkdown === undefined) {
return null;
}
return (
<>
<div className="form-group">
<div className={kcClsx("kcInputWrapperClass")}>
{msg("termsTitle")}
<div id="kc-registration-terms-text">
<Markdown>{termsMarkdown}</Markdown>
</div>
<div id="kc-registration-terms-text">{msg("termsText")}</div>
</div>
</div>
<div className="form-group">

View File

@ -1,6 +1,4 @@
import { Markdown } from "keycloakify/tools/Markdown";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
@ -15,13 +13,7 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
const { msg, msgStr } = i18n;
const { locale, url } = kcContext;
const { isDownloadComplete, termsMarkdown, termsLanguageTag } = useTermsMarkdown();
if (!isDownloadComplete) {
return null;
}
const { url } = kcContext;
return (
<Template
@ -32,9 +24,7 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
displayMessage={false}
headerNode={msg("termsTitle")}
>
<div id="kc-terms-text" lang={termsLanguageTag !== locale?.currentLanguageTag ? termsLanguageTag : undefined}>
<Markdown>{termsMarkdown}</Markdown>
</div>
<div id="kc-terms-text">{msg("termsText")}</div>
<form className="form-actions" action={url.loginAction} method="POST">
<input
className={kcClsx("kcButtonClass", "kcButtonClass", "kcButtonClass", "kcButtonPrimaryClass", "kcButtonLargeClass")}

View File

@ -1,5 +1,5 @@
import { useEffect, Fragment } from "react";
import { assert } from "tsafe/assert";
import { assert } from "keycloakify/tools/assert";
import { clsx } from "keycloakify/tools/clsx";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";

View File

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { assert } from "tsafe/assert";
import { assert } from "keycloakify/tools/assert";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { PageProps } from "keycloakify/login/pages/PageProps";

View File

@ -1,3 +0,0 @@
import Markdown from "react-markdown";
export { Markdown };

View File

@ -2,7 +2,6 @@ import React from "react";
import DefaultPage from "../../dist/login/DefaultPage";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
import { useDownloadTerms } from "../../dist/login/lib/useDownloadTerms";
import Template from "../../dist/login/Template";
import UserProfileFormFields from "../../dist/login/UserProfileFormFields";
@ -11,31 +10,6 @@ export default function KcPage(props: { kcContext: KcContext }) {
const { i18n } = useI18n({ kcContext });
useDownloadTerms({
kcContext,
downloadTermsMarkdown: async ({ currentLanguageTag }) => {
let termsLanguageTag = currentLanguageTag;
let termsFileName: string;
switch (currentLanguageTag) {
case "fr":
termsFileName = "fr.md";
break;
case "es":
termsFileName = "es.md";
break;
default:
termsFileName = "en.md";
termsLanguageTag = "en";
break;
}
const termsMarkdown = await fetch(`/terms/${termsFileName}`).then(response => response.text());
return { termsMarkdown, termsLanguageTag };
}
});
return (
<DefaultPage
kcContext={kcContext}

View File

@ -14,7 +14,15 @@ export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => <KcPageStory />
render: () => (
<KcPageStory
kcContext={{
"x-keycloakify": {
realmMessageBundleTermsText: "<p>My terms in <strong>English</strong></p>"
}
}}
/>
)
};
export const French: Story = {
@ -23,18 +31,11 @@ export const French: Story = {
kcContext={{
locale: {
currentLanguageTag: "fr"
}
}}
/>
)
};
export const Spanish: Story = {
render: () => (
<KcPageStory
kcContext={{
locale: {
currentLanguageTag: "es"
},
"x-keycloakify": {
// cSpell: disable
realmMessageBundleTermsText: "<p>Mes terme en <strong>Français</strong></p>"
// cSpell: enable
}
}}
/>

View File

@ -1,11 +1,6 @@
import { replaceImportsInJsCode_vite } from "keycloakify/bin/keycloakify/replacers/replaceImportsInJsCode/vite";
import { replaceImportsInJsCode_webpack } from "keycloakify/bin/keycloakify/replacers/replaceImportsInJsCode/webpack";
import {
generateCssCodeToDefineGlobals,
replaceImportsInCssCode
} from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode";
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
import { same } from "evt/tools/inDepth/same";
import { replaceImportsInCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode";
import { expect, it, describe } from "vitest";
import { basenameOfTheKeycloakifyResourcesDir } from "keycloakify/bin/shared/constants";
@ -272,13 +267,13 @@ describe("js replacer - webpack", () => {
const fixedJsCodeExpected = `
function f() {
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
return window.kcContext.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
function sameAsF() {
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
return window.kcContext.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
@ -293,7 +288,7 @@ describe("js replacer - webpack", () => {
}
return "u";
})()] = function(e) {
return "/build/static/js/" + e + "." + {
return "/${basenameOfTheKeycloakifyResourcesDir}/static/js/" + e + "." + {
147: "6c5cee76",
787: "8da10fcf",
922: "be170a73"
@ -310,7 +305,7 @@ describe("js replacer - webpack", () => {
}
return "miniCssF";
})()] = function(e) {
return "/build/static/css/" + e + "." + {
return "/${basenameOfTheKeycloakifyResourcesDir}/static/css/" + e + "." + {
164:"dcfd7749",
908:"67c9ed2c"
} [e] + ".chunk.css"
@ -325,7 +320,7 @@ describe("js replacer - webpack", () => {
});
}
return "u";
})()] = e => "/build/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
})()] = e => "/${basenameOfTheKeycloakifyResourcesDir}/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
t[(function(){
var pd = Object.getOwnPropertyDescriptor(t, "p");
@ -336,7 +331,7 @@ describe("js replacer - webpack", () => {
});
}
return "miniCssF";
})()] = e => "/build/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
})()] = e => "/${basenameOfTheKeycloakifyResourcesDir}/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
`;
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
@ -385,279 +380,156 @@ describe("js replacer - webpack", () => {
});
describe("css replacer", () => {
it("transforms absolute urls to css globals properly with no urlPathname", () => {
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
it("replaceImportsInCssCode - 1", () => {
const { fixedCssCode } = replaceImportsInCssCode({
cssCode: `
.my-div {
background: url(/logo192.png) no-repeat center center;
background: url(/background.png) no-repeat center center;
}
.my-div2 {
background: url(/logo192.png) repeat center center;
background: url(/assets/background.png) repeat center center;
}
.my-div {
background-image: url(/static/media/something.svg);
.my-div3 {
background-image: url(/assets/media/something.svg);
}
`
});
const fixedCssCodeExpected = `
.my-div {
background: var(--urla882a969fd39473) no-repeat center center;
}
.my-div2 {
background: var(--urla882a969fd39473) repeat center center;
}
.my-div {
background-image: var(--urldd75cab58377c19);
}
`;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
const cssGlobalsToDefineExpected = {
urla882a969fd39473: "url(/logo192.png)",
urldd75cab58377c19: "url(/static/media/something.svg)"
};
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
`,
cssFileRelativeDirPath: "assets/",
buildContext: {
urlPathname: undefined
}
});
const cssCodeToPrependInHeadExpected = `
:root {
--urla882a969fd39473: url(\${url.resourcesPath}/build/logo192.png);
--urldd75cab58377c19: url(\${url.resourcesPath}/build/static/media/something.svg);
}
`;
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(
true
);
});
it("transforms absolute urls to css globals properly with custom urlPathname", () => {
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
cssCode: `
.my-div {
background: url(/x/y/z/logo192.png) no-repeat center center;
}
.my-div2 {
background: url(/x/y/z/logo192.png) no-repeat center center;
}
.my-div {
background-image: url(/x/y/z/static/media/something.svg);
}
`
});
const fixedCssCodeExpected = `
.my-div {
background: var(--url749a3139386b2c8) no-repeat center center;
background: url(../background.png) no-repeat center center;
}
.my-div2 {
background: var(--url749a3139386b2c8) no-repeat center center;
background: url(background.png) repeat center center;
}
.my-div {
background-image: var(--url8bdc0887b97ac9a);
.my-div3 {
background-image: url(media/something.svg);
}
`;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
});
const cssGlobalsToDefineExpected = {
url749a3139386b2c8: "url(/x/y/z/logo192.png)",
url8bdc0887b97ac9a: "url(/x/y/z/static/media/something.svg)"
};
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
it("replaceImportsInCssCode - 2", () => {
const { fixedCssCode } = replaceImportsInCssCode({
cssCode: `
.my-div {
background: url(/a/b/background.png) no-repeat center center;
}
.my-div2 {
background: url(/a/b/assets/background.png) repeat center center;
}
.my-div3 {
background-image: url(/a/b/assets/media/something.svg);
}
`,
cssFileRelativeDirPath: "assets/",
buildContext: {
urlPathname: "/x/y/z/"
urlPathname: "/a/b/"
}
});
const cssCodeToPrependInHeadExpected = `
:root {
--url749a3139386b2c8: url(\${url.resourcesPath}/build/logo192.png);
--url8bdc0887b97ac9a: url(\${url.resourcesPath}/build/static/media/something.svg);
const fixedCssCodeExpected = `
.my-div {
background: url(../background.png) no-repeat center center;
}
.my-div2 {
background: url(background.png) repeat center center;
}
.my-div3 {
background-image: url(media/something.svg);
}
`;
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(
true
);
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
});
});
describe("inline css replacer", () => {
describe("no url pathName", () => {
const cssCode = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
}
`;
it("transforms css for standalone app properly", () => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
buildContext: {
urlPathname: undefined
it("replaceImportsInCssCode - 3", () => {
const { fixedCssCode } = replaceImportsInCssCode({
cssCode: `
.my-div {
background: url(/a/b/background.png) no-repeat center center;
}
});
.my-div2 {
background: url(/a/b/assets/background.png) repeat center center;
}
.my-div3 {
background-image: url(/a/b/assets/media/something.svg);
}
`,
cssFileRelativeDirPath: undefined,
buildContext: {
urlPathname: "/a/b/"
}
});
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
const fixedCssCodeExpected = `
.my-div {
background: url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/background.png) no-repeat center center;
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
.my-div2 {
background: url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/assets/background.png) repeat center center;
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
.my-div3 {
background-image: url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/assets/media/something.svg);
}
`;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
});
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
});
describe("with url pathName", () => {
const cssCode = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
}
`;
it("transforms css for standalone app properly", () => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
buildContext: {
urlPathname: "/x/y/z/"
it("replaceImportsInCssCode - 4", () => {
const { fixedCssCode } = replaceImportsInCssCode({
cssCode: `
.my-div {
background: url(/background.png) no-repeat center center;
}
});
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
.my-div2 {
background: url(/assets/background.png) repeat center center;
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
.my-div3 {
background-image: url(/assets/media/something.svg);
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
}
`;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
`,
cssFileRelativeDirPath: undefined,
buildContext: {
urlPathname: undefined
}
});
const fixedCssCodeExpected = `
.my-div {
background: url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/background.png) no-repeat center center;
}
.my-div2 {
background: url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/assets/background.png) repeat center center;
}
.my-div3 {
background-image: url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/assets/media/something.svg);
}
`;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
});
});

401
yarn.lock
View File

@ -3331,7 +3331,7 @@
"@types/retry" "*"
"@types/ssri" "*"
"@types/mdast@^3.0.0", "@types/mdast@^3.0.3":
"@types/mdast@^3.0.0":
version "3.0.11"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.11.tgz#dc130f7e7d9306124286f6d6cee40cf4d14a3dc0"
integrity sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==
@ -3886,11 +3886,6 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
"@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
accepts@~1.3.5, accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
@ -4040,12 +4035,10 @@ ansi-escapes@^4.3.0:
dependencies:
type-fest "^0.21.3"
ansi-escapes@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-5.0.0.tgz#b6a0caf0eef0c41af190e9a749e0c00ec04bb2a6"
integrity sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==
dependencies:
type-fest "^1.0.2"
ansi-escapes@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz#76c54ce9b081dad39acec4b5d53377913825fb0f"
integrity sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==
ansi-html-community@0.0.8, ansi-html-community@^0.0.8:
version "0.0.8"
@ -4091,7 +4084,7 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
ansi-styles@^6.0.0, ansi-styles@^6.1.0:
ansi-styles@^6.0.0, ansi-styles@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
@ -4881,17 +4874,6 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
call-bind@^1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
function-bind "^1.1.2"
get-intrinsic "^1.2.4"
set-function-length "^1.2.1"
call-me-maybe@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa"
@ -5121,11 +5103,6 @@ ci-info@^2.0.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
ci-info@^3.7.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@ -5206,13 +5183,13 @@ cli-truncate@2.1.0, cli-truncate@^2.1.0:
slice-ansi "^3.0.0"
string-width "^4.2.0"
cli-truncate@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389"
integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==
cli-truncate@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a"
integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==
dependencies:
slice-ansi "^5.0.0"
string-width "^5.0.0"
string-width "^7.0.0"
cliui@^5.0.0:
version "5.0.0"
@ -5700,7 +5677,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@ -5785,15 +5762,6 @@ default-browser-id@^1.0.4:
meow "^3.1.0"
untildify "^2.0.0"
define-data-property@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
gopd "^1.0.1"
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
@ -5971,7 +5939,7 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
dependencies:
domelementtype "^2.2.0"
domhandler@^5.0, domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
@ -6024,11 +5992,6 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -6064,6 +6027,11 @@ elliptic@^6.5.3:
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
emoji-regex@^10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23"
integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@ -6074,11 +6042,6 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
emoji-regex@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
@ -6225,18 +6188,6 @@ es-array-method-boxes-properly@^1.0.0:
resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e"
integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==
es-define-property@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
dependencies:
get-intrinsic "^1.2.4"
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-get-iterator@^1.0.2, es-get-iterator@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6"
@ -6867,13 +6818,6 @@ find-versions@^4.0.0:
dependencies:
semver-regex "^3.1.2"
find-yarn-workspace-root@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
dependencies:
micromatch "^4.0.2"
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@ -7064,11 +7008,6 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
function.prototype.name@^1.1.0, function.prototype.name@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
@ -7109,6 +7048,11 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-east-asian-width@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e"
integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
@ -7128,17 +7072,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@
has "^1.0.3"
has-symbols "^1.0.3"
get-intrinsic@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@ -7349,13 +7282,6 @@ has-property-descriptors@^1.0.0:
dependencies:
get-intrinsic "^1.1.1"
has-property-descriptors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
dependencies:
es-define-property "^1.0.0"
has-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
@ -7433,13 +7359,6 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hasown@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
hast-to-hyperscript@^9.0.0:
version "9.0.1"
resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d"
@ -7575,16 +7494,6 @@ html-tags@^3.1.0:
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
html-to-react@^1.3.4:
version "1.5.1"
resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.5.1.tgz#82ea8e5948ae15778a22888201add49e15bf8888"
integrity sha512-dFLZRBjpMk89Ukwa6Fq7oApinn3TEZD0gGFUkmI9DqNQxTjN7gF9owhyu+t8h+bpEZrX2DMxZLYjEfw0C/iL7A==
dependencies:
domhandler "^5.0"
htmlparser2 "^8.0"
lodash.camelcase "^4.3.0"
react "^18.0"
html-void-elements@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483"
@ -7626,7 +7535,7 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
htmlparser2@^8.0, htmlparser2@^8.0.1:
htmlparser2@^8.0.1:
version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
@ -8048,6 +7957,13 @@ is-fullwidth-code-point@^4.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
is-fullwidth-code-point@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704"
integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==
dependencies:
get-east-asian-width "^1.0.0"
is-function@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08"
@ -8493,16 +8409,6 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-stable-stringify@^1.0.2:
version "1.1.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454"
integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==
dependencies:
call-bind "^1.0.5"
isarray "^2.0.5"
jsonify "^0.0.1"
object-keys "^1.1.1"
json5@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
@ -8524,11 +8430,6 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
junk@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
@ -8558,13 +8459,6 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
klaw-sync@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
dependencies:
graceful-fs "^4.1.11"
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@ -8633,17 +8527,17 @@ listr2@^3.12.2:
through "^2.3.8"
wrap-ansi "^7.0.0"
listr2@^7.0.1:
version "7.0.2"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-7.0.2.tgz#3aa3e1549dfaf3c57ab5eeaba754da3b87f33063"
integrity sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==
listr2@^8.0.1:
version "8.2.1"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.1.tgz#06a1a6efe85f23c5324180d7c1ddbd96b5eefd6d"
integrity sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==
dependencies:
cli-truncate "^3.1.0"
cli-truncate "^4.0.0"
colorette "^2.0.20"
eventemitter3 "^5.0.1"
log-update "^5.0.1"
rfdc "^1.3.0"
wrap-ansi "^8.1.0"
log-update "^6.0.0"
rfdc "^1.3.1"
wrap-ansi "^9.0.0"
load-json-file@^1.0.0:
version "1.1.0"
@ -8714,11 +8608,6 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@ -8749,16 +8638,16 @@ log-update@^4.0.0:
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
log-update@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-5.0.1.tgz#9e928bf70cb183c1f0c9e91d9e6b7115d597ce09"
integrity sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==
log-update@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.0.0.tgz#0ddeb7ac6ad658c944c1de902993fce7c33f5e59"
integrity sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==
dependencies:
ansi-escapes "^5.0.0"
ansi-escapes "^6.2.0"
cli-cursor "^4.0.0"
slice-ansi "^5.0.0"
strip-ansi "^7.0.1"
wrap-ansi "^8.0.1"
slice-ansi "^7.0.0"
strip-ansi "^7.1.0"
wrap-ansi "^9.0.0"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
@ -8932,13 +8821,6 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
mdast-add-list-metadata@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf"
integrity sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==
dependencies:
unist-util-visit-parents "1.1.2"
mdast-squeeze-paragraphs@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97"
@ -8953,17 +8835,6 @@ mdast-util-definitions@^4.0.0:
dependencies:
unist-util-visit "^2.0.0"
mdast-util-from-markdown@^0.8.0:
version "0.8.5"
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz#d1ef2ca42bc377ecb0463a987910dae89bd9a28c"
integrity sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==
dependencies:
"@types/mdast" "^3.0.0"
mdast-util-to-string "^2.0.0"
micromark "~2.11.0"
parse-entities "^2.0.0"
unist-util-stringify-position "^2.0.0"
mdast-util-to-hast@10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb"
@ -8983,11 +8854,6 @@ mdast-util-to-string@^1.0.0:
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527"
integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==
mdast-util-to-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b"
integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==
mdurl@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@ -9091,14 +8957,6 @@ microevent.ts@~0.1.1:
resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0"
integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==
micromark@~2.11.0:
version "2.11.4"
resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a"
integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==
dependencies:
debug "^4.0.0"
parse-entities "^2.0.0"
micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@ -9688,7 +9546,7 @@ onetime@^6.0.0:
dependencies:
mimic-fn "^4.0.0"
open@^7.0.3, open@^7.4.2:
open@^7.0.3:
version "7.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
@ -9732,11 +9590,6 @@ os-homedir@^1.0.0:
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
p-all@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-all/-/p-all-2.1.0.tgz#91419be56b7dee8fe4c5db875d55e0da084244a0"
@ -9948,27 +9801,6 @@ pascalcase@^0.1.1:
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==
patch-package@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61"
integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
chalk "^4.1.2"
ci-info "^3.7.0"
cross-spawn "^7.0.3"
find-yarn-workspace-root "^2.0.0"
fs-extra "^9.0.0"
json-stable-stringify "^1.0.2"
klaw-sync "^6.0.0"
minimist "^1.2.6"
open "^7.4.2"
rimraf "^2.6.3"
semver "^7.5.3"
slash "^2.0.0"
tmp "^0.0.33"
yaml "^2.2.2"
path-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
@ -10651,7 +10483,7 @@ react-is@17.0.2, react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.6:
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -10661,22 +10493,6 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react-markdown@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-5.0.3.tgz#41040ea7a9324b564b328fb81dd6c04f2a5373ac"
integrity sha512-jDWOc1AvWn0WahpjW6NK64mtx6cwjM4iSsLHJPNBqoAgGOVoIdJMqaKX4++plhOtdd4JksdqzlDibgPx6B/M2w==
dependencies:
"@types/mdast" "^3.0.3"
"@types/unist" "^2.0.3"
html-to-react "^1.3.4"
mdast-add-list-metadata "1.0.1"
prop-types "^15.7.2"
react-is "^16.8.6"
remark-parse "^9.0.0"
unified "^9.0.0"
unist-util-visit "^2.0.0"
xtend "^4.0.1"
react-merge-refs@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
@ -10697,7 +10513,7 @@ react-sizeme@^3.0.1:
shallowequal "^1.1.0"
throttle-debounce "^3.0.1"
react@^18.0, react@^18.2.0:
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@ -10914,13 +10730,6 @@ remark-parse@8.0.3:
vfile-location "^3.0.0"
xtend "^4.0.1"
remark-parse@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640"
integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==
dependencies:
mdast-util-from-markdown "^0.8.0"
remark-slug@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-6.1.0.tgz#0503268d5f0c4ecb1f33315c00465ccdd97923ce"
@ -11061,6 +10870,11 @@ rfdc@^1.3.0:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
rfdc@^1.3.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca"
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
rimraf@^2.5.4, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@ -11267,11 +11081,6 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
dependencies:
lru-cache "^6.0.0"
semver@^7.5.3:
version "7.6.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
send@0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
@ -11338,18 +11147,6 @@ set-blocking@^2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
set-function-length@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
dependencies:
define-data-property "^1.1.4"
es-errors "^1.3.0"
function-bind "^1.1.2"
get-intrinsic "^1.2.4"
gopd "^1.0.1"
has-property-descriptors "^1.0.2"
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@ -11479,6 +11276,14 @@ slice-ansi@^5.0.0:
ansi-styles "^6.0.0"
is-fullwidth-code-point "^4.0.0"
slice-ansi@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9"
integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==
dependencies:
ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0"
smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
@ -11768,14 +11573,14 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^5.0.0, string-width@^5.0.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
string-width@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a"
integrity sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==
dependencies:
eastasianwidth "^0.2.0"
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
emoji-regex "^10.3.0"
get-east-asian-width "^1.0.0"
strip-ansi "^7.1.0"
string.prototype.codepointat@^0.2.0:
version "0.2.1"
@ -11885,10 +11690,10 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
dependencies:
ansi-regex "^6.0.1"
@ -12045,13 +11850,13 @@ telejson@^6.0.8:
lodash "^4.17.21"
memoizerific "^1.11.3"
termost@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/termost/-/termost-0.12.0.tgz#ca15c83fe3b2dfc83be0dcf4215aafad3bc6c018"
integrity sha512-7MbYVQvdhaZHSy/mO8TM6yXcDTS2wkeLiWIwoUL43n6wo4NSO38V3GNsBRlI4zcl8vQzm+bmqplyqOTYVZCpFw==
termost@^v0.12.1:
version "0.12.1"
resolved "https://registry.yarnpkg.com/termost/-/termost-0.12.1.tgz#c1debbaaa8ae503c6ab57d2aa94518ae71647b29"
integrity sha512-jcfEHVbbYseoc2WRlb0c9vuV65Za/duEMm+A/O9nGXkhSqNBd5GkbcS6cSu4xOTQ/NTrXYQ3muONnniRaQTKuQ==
dependencies:
enquirer "^2.4.1"
listr2 "^7.0.1"
listr2 "^8.0.1"
picocolors "^1.0.0"
terser-webpack-plugin@^1.4.3:
@ -12171,13 +11976,6 @@ tinyspy@^2.2.0:
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1"
integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
dependencies:
os-tmpdir "~1.0.2"
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@ -12355,11 +12153,6 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^1.0.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@ -12472,18 +12265,6 @@ unified@9.2.0:
trough "^1.0.0"
vfile "^4.0.0"
unified@^9.0.0:
version "9.2.2"
resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975"
integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==
dependencies:
bail "^1.0.0"
extend "^3.0.0"
is-buffer "^2.0.0"
is-plain-obj "^2.0.0"
trough "^1.0.0"
vfile "^4.0.0"
union-value@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@ -12563,11 +12344,6 @@ unist-util-stringify-position@^2.0.0:
dependencies:
"@types/unist" "^2.0.2"
unist-util-visit-parents@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06"
integrity sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==
unist-util-visit-parents@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6"
@ -13147,14 +12923,14 @@ wrap-ansi@^7.0.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
wrap-ansi@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e"
integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==
dependencies:
ansi-styles "^6.1.0"
string-width "^5.0.1"
strip-ansi "^7.0.1"
ansi-styles "^6.2.1"
string-width "^7.0.0"
strip-ansi "^7.1.0"
wrappy@1:
version "1.0.2"
@ -13213,11 +12989,6 @@ yaml@^1.10.0, yaml@^1.7.2:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yaml@^2.2.2:
version "2.4.3"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.3.tgz#0777516b8c7880bcaa0f426a5410e8d6b0be1f3d"
integrity sha512-sntgmxj8o7DE7g/Qi60cqpLBA3HG3STcDA0kO+WfB05jEKhZMbY7umNm2rBpQvsmZ16/lPXCJGW2672dgOUkrg==
yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"