Update prettier configuration

This commit is contained in:
Joseph Garrone
2024-05-20 15:48:51 +02:00
parent 7a89888d11
commit 22e7ff1424
138 changed files with 3681 additions and 2007 deletions

View File

@ -1,11 +1,24 @@
{ {
"printWidth": 150, "printWidth": 90,
"tabWidth": 4, "tabWidth": 4,
"useTabs": false, "useTabs": false,
"semi": true, "semi": true,
"singleQuote": false, "singleQuote": false,
"quoteProps": "preserve",
"trailingComma": "none", "trailingComma": "none",
"bracketSpacing": true, "bracketSpacing": true,
"arrowParens": "avoid" "arrowParens": "avoid",
"overrides": [
{
"files": "*.tsx",
"options": {
"printWidth": 150
}
},
{
"files": "useUserProfileForm.tsx",
"options": {
"printWidth": 150
}
}
]
} }

View File

@ -107,7 +107,7 @@
"make-fetch-happen": "^11.0.3", "make-fetch-happen": "^11.0.3",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"powerhooks": "^1.0.10", "powerhooks": "^1.0.10",
"prettier": "^2.3.0", "prettier": "^3.2.5",
"properties-parser": "^0.3.1", "properties-parser": "^0.3.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -10,7 +10,10 @@ console.log(chalk.cyan("Building Keycloakify..."));
const startTime = Date.now(); const startTime = Date.now();
if (fs.existsSync(join("dist", "bin", "main.original.js"))) { if (fs.existsSync(join("dist", "bin", "main.original.js"))) {
fs.renameSync(join("dist", "bin", "main.original.js"), join("dist", "bin", "main.js")); fs.renameSync(
join("dist", "bin", "main.original.js"),
join("dist", "bin", "main.js")
);
fs.readdirSync(join("dist", "bin")).forEach(fileBasename => { fs.readdirSync(join("dist", "bin")).forEach(fileBasename => {
if (/[0-9]\.index.js/.test(fileBasename)) { if (/[0-9]\.index.js/.test(fileBasename)) {
@ -28,55 +31,74 @@ if (!fs.readFileSync(join("dist", "bin", "main.js")).toString("utf8").includes("
run(`npx ncc build ${join("dist", "bin", "main.js")} -o ${join("dist", "ncc_out")}`); run(`npx ncc build ${join("dist", "bin", "main.js")} -o ${join("dist", "ncc_out")}`);
transformCodebase({ transformCodebase({
"srcDirPath": join("dist", "ncc_out"), srcDirPath: join("dist", "ncc_out"),
"destDirPath": join("dist", "bin"), destDirPath: join("dist", "bin"),
"transformSourceCode": ({ fileRelativePath, sourceCode }) => { transformSourceCode: ({ fileRelativePath, sourceCode }) => {
if (fileRelativePath === "index.js") { if (fileRelativePath === "index.js") {
return { return {
"newFileName": "main.js", newFileName: "main.js",
"modifiedSourceCode": sourceCode modifiedSourceCode: sourceCode
}; };
} }
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
}); });
fs.rmSync(join("dist", "ncc_out"), { "recursive": true }); fs.rmSync(join("dist", "ncc_out"), { recursive: true });
patchDeprecatedBufferApiUsage(join("dist", "bin", "main.js")); patchDeprecatedBufferApiUsage(join("dist", "bin", "main.js"));
fs.chmodSync( fs.chmodSync(
join("dist", "bin", "main.js"), join("dist", "bin", "main.js"),
fs.statSync(join("dist", "bin", "main.js")).mode | fs.constants.S_IXUSR | fs.constants.S_IXGRP | fs.constants.S_IXOTH fs.statSync(join("dist", "bin", "main.js")).mode |
fs.constants.S_IXUSR |
fs.constants.S_IXGRP |
fs.constants.S_IXOTH
); );
run(`npx tsc -p ${join("src", "tsconfig.json")}`); run(`npx tsc -p ${join("src", "tsconfig.json")}`);
run(`npx tsc-alias -p ${join("src", "tsconfig.json")}`); run(`npx tsc-alias -p ${join("src", "tsconfig.json")}`);
if (fs.existsSync(join("dist", "vite-plugin", "index.original.js"))) { if (fs.existsSync(join("dist", "vite-plugin", "index.original.js"))) {
fs.renameSync(join("dist", "vite-plugin", "index.original.js"), join("dist", "vite-plugin", "index.js")); fs.renameSync(
join("dist", "vite-plugin", "index.original.js"),
join("dist", "vite-plugin", "index.js")
);
} }
run(`npx tsc -p ${join("src", "vite-plugin", "tsconfig.json")}`); run(`npx tsc -p ${join("src", "vite-plugin", "tsconfig.json")}`);
if (!fs.readFileSync(join("dist", "vite-plugin", "index.js")).toString("utf8").includes("ncc")) { if (
fs.cpSync(join("dist", "vite-plugin", "index.js"), join("dist", "vite-plugin", "index.original.js")); !fs
.readFileSync(join("dist", "vite-plugin", "index.js"))
.toString("utf8")
.includes("ncc")
) {
fs.cpSync(
join("dist", "vite-plugin", "index.js"),
join("dist", "vite-plugin", "index.original.js")
);
} }
run(`npx ncc build ${join("dist", "vite-plugin", "index.js")} -o ${join("dist", "ncc_out")}`); run(
`npx ncc build ${join("dist", "vite-plugin", "index.js")} -o ${join(
"dist",
"ncc_out"
)}`
);
transformCodebase({ transformCodebase({
"srcDirPath": join("dist", "ncc_out"), srcDirPath: join("dist", "ncc_out"),
"destDirPath": join("dist", "vite-plugin"), destDirPath: join("dist", "vite-plugin"),
"transformSourceCode": ({ fileRelativePath, sourceCode }) => { transformSourceCode: ({ fileRelativePath, sourceCode }) => {
assert(fileRelativePath === "index.js"); assert(fileRelativePath === "index.js");
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
}); });
fs.rmSync(join("dist", "ncc_out"), { "recursive": true }); fs.rmSync(join("dist", "ncc_out"), { recursive: true });
patchDeprecatedBufferApiUsage(join("dist", "vite-plugin", "index.js")); patchDeprecatedBufferApiUsage(join("dist", "vite-plugin", "index.js"));
@ -85,7 +107,7 @@ console.log(chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixe
function run(command: string) { function run(command: string) {
console.log(chalk.grey(`$ ${command}`)); console.log(chalk.grey(`$ ${command}`));
child_process.execSync(command, { "stdio": "inherit" }); child_process.execSync(command, { stdio: "inherit" });
} }
function patchDeprecatedBufferApiUsage(filePath: string) { function patchDeprecatedBufferApiUsage(filePath: string) {

View File

@ -4,15 +4,34 @@ import { SemVer } from "../src/bin/tools/SemVer";
import { join as pathJoin, relative as pathRelative } from "path"; import { join as pathJoin, relative as pathRelative } from "path";
import chalk from "chalk"; import chalk from "chalk";
run([`docker exec -it ${containerName}`, `/opt/keycloak/bin/kc.sh export`, `--dir /tmp`, `--realm myrealm`, `--users realm_file`].join(" ")); run(
[
`docker exec -it ${containerName}`,
`/opt/keycloak/bin/kc.sh export`,
`--dir /tmp`,
`--realm myrealm`,
`--users realm_file`
].join(" ")
);
const keycloakMajorVersionNumber = SemVer.parse( const keycloakMajorVersionNumber = SemVer.parse(
child_process.execSync(`docker inspect --format '{{.Config.Image}}' ${containerName}`).toString("utf8").trim().split(":")[1] child_process
.execSync(`docker inspect --format '{{.Config.Image}}' ${containerName}`)
.toString("utf8")
.trim()
.split(":")[1]
).major; ).major;
const targetFilePath = pathRelative( const targetFilePath = pathRelative(
process.cwd(), process.cwd(),
pathJoin(__dirname, "..", "src", "bin", "start-keycloak", `myrealm-realm-${keycloakMajorVersionNumber}.json`) pathJoin(
__dirname,
"..",
"src",
"bin",
"start-keycloak",
`myrealm-realm-${keycloakMajorVersionNumber}.json`
)
); );
run(`docker cp ${containerName}:/tmp/myrealm-realm.json ${targetFilePath}`); run(`docker cp ${containerName}:/tmp/myrealm-realm.json ${targetFilePath}`);
@ -22,5 +41,5 @@ console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath
function run(command: string) { function run(command: string) {
console.log(chalk.grey(`$ ${command}`)); console.log(chalk.grey(`$ ${command}`));
return child_process.execSync(command, { "stdio": "inherit" }); return child_process.execSync(command, { stdio: "inherit" });
} }

View File

@ -1,6 +1,11 @@
import "minimal-polyfills/Object.fromEntries"; import "minimal-polyfills/Object.fromEntries";
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, dirname as pathDirname, sep as pathSep } from "path"; import {
join as pathJoin,
relative as pathRelative,
dirname as pathDirname,
sep as pathSep
} from "path";
import { crawl } from "../src/bin/tools/crawl"; import { crawl } from "../src/bin/tools/crawl";
import { downloadKeycloakDefaultTheme } from "../src/bin/shared/downloadKeycloakDefaultTheme"; import { downloadKeycloakDefaultTheme } from "../src/bin/shared/downloadKeycloakDefaultTheme";
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath"; import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
@ -19,18 +24,26 @@ async function main() {
const tmpDirPath = pathJoin(thisCodebaseRootDirPath, "tmp_xImOef9dOd44"); const tmpDirPath = pathJoin(thisCodebaseRootDirPath, "tmp_xImOef9dOd44");
rmSync(tmpDirPath, { "recursive": true, "force": true }); rmSync(tmpDirPath, { recursive: true, force: true });
fs.mkdirSync(tmpDirPath); fs.mkdirSync(tmpDirPath);
fs.writeFileSync(pathJoin(tmpDirPath, ".gitignore"), Buffer.from("/*\n!.gitignore\n", "utf8")); fs.writeFileSync(
pathJoin(tmpDirPath, ".gitignore"),
Buffer.from("/*\n!.gitignore\n", "utf8")
);
await downloadKeycloakDefaultTheme({ await downloadKeycloakDefaultTheme({
keycloakVersion, keycloakVersion,
"destDirPath": tmpDirPath, destDirPath: tmpDirPath,
"buildOptions": { buildOptions: {
"cacheDirPath": pathJoin(thisCodebaseRootDirPath, "node_modules", ".cache", "keycloakify"), cacheDirPath: pathJoin(
"npmWorkspaceRootDirPath": thisCodebaseRootDirPath thisCodebaseRootDirPath,
"node_modules",
".cache",
"keycloakify"
),
npmWorkspaceRootDirPath: thisCodebaseRootDirPath
} }
}); });
@ -40,11 +53,13 @@ async function main() {
{ {
const baseThemeDirPath = pathJoin(tmpDirPath, "base"); const baseThemeDirPath = pathJoin(tmpDirPath, "base");
const re = new RegExp(`^([^\\${pathSep}]+)\\${pathSep}messages\\${pathSep}messages_([^.]+).properties$`); const re = new RegExp(
`^([^\\${pathSep}]+)\\${pathSep}messages\\${pathSep}messages_([^.]+).properties$`
);
crawl({ crawl({
"dirPath": baseThemeDirPath, dirPath: baseThemeDirPath,
"returnedPathsType": "relative to dirPath" returnedPathsType: "relative to dirPath"
}).forEach(filePath => { }).forEach(filePath => {
const match = filePath.match(re); const match = filePath.match(re);
@ -55,14 +70,21 @@ async function main() {
const [, typeOfPage, language] = match; const [, typeOfPage, language] = match;
(record[typeOfPage] ??= {})[language.replace(/_/g, "-")] = Object.fromEntries( (record[typeOfPage] ??= {})[language.replace(/_/g, "-")] = Object.fromEntries(
Object.entries(propertiesParser.parse(fs.readFileSync(pathJoin(baseThemeDirPath, filePath)).toString("utf8"))).map( Object.entries(
([key, value]: any) => [key === "locale_pt_BR" ? "locale_pt-BR" : key, value.replace(/''/g, "'")] propertiesParser.parse(
) fs
.readFileSync(pathJoin(baseThemeDirPath, filePath))
.toString("utf8")
)
).map(([key, value]: any) => [
key === "locale_pt_BR" ? "locale_pt-BR" : key,
value.replace(/''/g, "'")
])
); );
}); });
} }
rmSync(tmpDirPath, { "recursive": true }); rmSync(tmpDirPath, { recursive: true });
Object.keys(record).forEach(themeType => { Object.keys(record).forEach(themeType => {
const recordForPageType = record[themeType]; const recordForPageType = record[themeType];
@ -71,19 +93,28 @@ async function main() {
return; return;
} }
const baseMessagesDirPath = pathJoin(thisCodebaseRootDirPath, "src", themeType, "i18n", "baseMessages"); const baseMessagesDirPath = pathJoin(
thisCodebaseRootDirPath,
"src",
themeType,
"i18n",
"baseMessages"
);
const languages = Object.keys(recordForPageType); const languages = Object.keys(recordForPageType);
const generatedFileHeader = [ const generatedFileHeader = [
`//This code was automatically generated by running ${pathRelative(thisCodebaseRootDirPath, __filename)}`, `//This code was automatically generated by running ${pathRelative(
thisCodebaseRootDirPath,
__filename
)}`,
"//PLEASE DO NOT EDIT MANUALLY" "//PLEASE DO NOT EDIT MANUALLY"
].join("\n"); ].join("\n");
languages.forEach(language => { languages.forEach(language => {
const filePath = pathJoin(baseMessagesDirPath, `${language}.ts`); const filePath = pathJoin(baseMessagesDirPath, `${language}.ts`);
fs.mkdirSync(pathDirname(filePath), { "recursive": true }); fs.mkdirSync(pathDirname(filePath), { recursive: true });
fs.writeFileSync( fs.writeFileSync(
filePath, filePath,
@ -92,7 +123,11 @@ async function main() {
generatedFileHeader, generatedFileHeader,
"", "",
"/* spell-checker: disable */", "/* spell-checker: disable */",
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`, `const messages= ${JSON.stringify(
recordForPageType[language],
null,
2
)};`,
"", "",
"export default messages;", "export default messages;",
"/* spell-checker: enable */" "/* spell-checker: enable */"
@ -117,7 +152,10 @@ async function main() {
` case "en": return en;`, ` case "en": return en;`,
...languages ...languages
.filter(language => language !== "en") .filter(language => language !== "en")
.map(language => ` case "${language}": return import("./${language}");`), .map(
language =>
` case "${language}": return import("./${language}");`
),
' default: return { "default": {} };', ' default: return { "default": {} };',
" }", " }",
" })();", " })();",

View File

@ -10,7 +10,8 @@ import { chmod, stat } from "fs/promises";
const promises = Object.values<string>(bin).map(async scriptPath => { const promises = Object.values<string>(bin).map(async scriptPath => {
const fullPath = pathJoin(thisCodebaseRootDirPath, scriptPath); const fullPath = pathJoin(thisCodebaseRootDirPath, scriptPath);
const oldMode = (await stat(fullPath)).mode; const oldMode = (await stat(fullPath)).mode;
const newMode = oldMode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH; const newMode =
oldMode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH;
await chmod(fullPath, newMode); await chmod(fullPath, newMode);
}); });

View File

@ -13,20 +13,26 @@ fs.writeFileSync(
Buffer.from( Buffer.from(
JSON.stringify( JSON.stringify(
(() => { (() => {
const packageJsonParsed = JSON.parse(fs.readFileSync(pathJoin(rootDirPath, "package.json")).toString("utf8")); const packageJsonParsed = JSON.parse(
fs
.readFileSync(pathJoin(rootDirPath, "package.json"))
.toString("utf8")
);
return { return {
...packageJsonParsed, ...packageJsonParsed,
"main": packageJsonParsed["main"]?.replace(/^dist\//, ""), main: packageJsonParsed["main"]?.replace(/^dist\//, ""),
"types": packageJsonParsed["types"]?.replace(/^dist\//, ""), types: packageJsonParsed["types"]?.replace(/^dist\//, ""),
"module": packageJsonParsed["module"]?.replace(/^dist\//, ""), module: packageJsonParsed["module"]?.replace(/^dist\//, ""),
"exports": !("exports" in packageJsonParsed) exports: !("exports" in packageJsonParsed)
? undefined ? undefined
: Object.fromEntries( : Object.fromEntries(
Object.entries(packageJsonParsed["exports"]).map(([key, value]) => [ Object.entries(packageJsonParsed["exports"]).map(
key, ([key, value]) => [
(value as string).replace(/^\.\/dist\//, "./") key,
]) (value as string).replace(/^\.\/dist\//, "./")
]
)
) )
}; };
})(), })(),
@ -39,9 +45,9 @@ fs.writeFileSync(
const destSrcDirPath = pathJoin(rootDirPath, "dist", "src"); const destSrcDirPath = pathJoin(rootDirPath, "dist", "src");
fs.rmSync(destSrcDirPath, { "recursive": true, "force": true }); fs.rmSync(destSrcDirPath, { recursive: true, force: true });
fs.cpSync(pathJoin(rootDirPath, "src"), destSrcDirPath, { "recursive": true }); fs.cpSync(pathJoin(rootDirPath, "src"), destSrcDirPath, { recursive: true });
const commonThirdPartyDeps = (() => { const commonThirdPartyDeps = (() => {
// For example [ "@emotion" ] it's more convenient than // For example [ "@emotion" ] it's more convenient than
@ -53,7 +59,9 @@ const commonThirdPartyDeps = (() => {
...namespaceSingletonDependencies ...namespaceSingletonDependencies
.map(namespaceModuleName => .map(namespaceModuleName =>
fs fs
.readdirSync(pathJoin(rootDirPath, "node_modules", namespaceModuleName)) .readdirSync(
pathJoin(rootDirPath, "node_modules", namespaceModuleName)
)
.map(submoduleName => `${namespaceModuleName}/${submoduleName}`) .map(submoduleName => `${namespaceModuleName}/${submoduleName}`)
) )
.reduce((prev, curr) => [...prev, ...curr], []), .reduce((prev, curr) => [...prev, ...curr], []),
@ -63,21 +71,25 @@ const commonThirdPartyDeps = (() => {
const yarnGlobalDirPath = pathJoin(rootDirPath, ".yarn_home"); const yarnGlobalDirPath = pathJoin(rootDirPath, ".yarn_home");
fs.rmSync(yarnGlobalDirPath, { "recursive": true, "force": true }); fs.rmSync(yarnGlobalDirPath, { recursive: true, force: true });
fs.mkdirSync(yarnGlobalDirPath); fs.mkdirSync(yarnGlobalDirPath);
const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => { const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => {
const { targetModuleName, cwd } = params; const { targetModuleName, cwd } = params;
const cmd = ["yarn", "link", ...(targetModuleName !== undefined ? [targetModuleName] : ["--no-bin-links"])].join(" "); const cmd = [
"yarn",
"link",
...(targetModuleName !== undefined ? [targetModuleName] : ["--no-bin-links"])
].join(" ");
console.log(`$ cd ${pathRelative(rootDirPath, cwd) || "."} && ${cmd}`); console.log(`$ cd ${pathRelative(rootDirPath, cwd) || "."} && ${cmd}`);
execSync(cmd, { execSync(cmd, {
cwd, cwd,
"env": { env: {
...process.env, ...process.env,
"HOME": yarnGlobalDirPath HOME: yarnGlobalDirPath
} }
}); });
}; };
@ -93,7 +105,9 @@ const testAppPaths = (() => {
return testAppPath; return testAppPath;
} }
console.warn(`Skipping ${testAppName} since it cant be found here: ${testAppPath}`); console.warn(
`Skipping ${testAppName} since it cant be found here: ${testAppPath}`
);
return undefined; return undefined;
}) })
@ -105,7 +119,7 @@ if (testAppPaths.length === 0) {
process.exit(-1); process.exit(-1);
} }
testAppPaths.forEach(testAppPath => execSync("yarn install", { "cwd": testAppPath })); testAppPaths.forEach(testAppPath => execSync("yarn install", { cwd: testAppPath }));
console.log("=== Linking common dependencies ==="); console.log("=== Linking common dependencies ===");
@ -118,29 +132,37 @@ commonThirdPartyDeps.forEach(commonThirdPartyDep => {
console.log(`${current}/${total} ${commonThirdPartyDep}`); console.log(`${current}/${total} ${commonThirdPartyDep}`);
const localInstallPath = pathJoin( const localInstallPath = pathJoin(
...[rootDirPath, "node_modules", ...(commonThirdPartyDep.startsWith("@") ? commonThirdPartyDep.split("/") : [commonThirdPartyDep])] ...[
rootDirPath,
"node_modules",
...(commonThirdPartyDep.startsWith("@")
? commonThirdPartyDep.split("/")
: [commonThirdPartyDep])
]
); );
execYarnLink({ "cwd": localInstallPath }); execYarnLink({ cwd: localInstallPath });
}); });
commonThirdPartyDeps.forEach(commonThirdPartyDep => commonThirdPartyDeps.forEach(commonThirdPartyDep =>
testAppPaths.forEach(testAppPath => testAppPaths.forEach(testAppPath =>
execYarnLink({ execYarnLink({
"cwd": testAppPath, cwd: testAppPath,
"targetModuleName": commonThirdPartyDep targetModuleName: commonThirdPartyDep
}) })
) )
); );
console.log("=== Linking in house dependencies ==="); console.log("=== Linking in house dependencies ===");
execYarnLink({ "cwd": pathJoin(rootDirPath, "dist") }); execYarnLink({ cwd: pathJoin(rootDirPath, "dist") });
testAppPaths.forEach(testAppPath => testAppPaths.forEach(testAppPath =>
execYarnLink({ execYarnLink({
"cwd": testAppPath, cwd: testAppPath,
"targetModuleName": JSON.parse(fs.readFileSync(pathJoin(rootDirPath, "package.json")).toString("utf8"))["name"] targetModuleName: JSON.parse(
fs.readFileSync(pathJoin(rootDirPath, "package.json")).toString("utf8")
)["name"]
}) })
); );

View File

@ -2,16 +2,19 @@ import * as child_process from "child_process";
import * as fs from "fs"; import * as fs from "fs";
import { join } from "path"; import { join } from "path";
fs.rmSync("node_modules", { "recursive": true, "force": true }); fs.rmSync("node_modules", { recursive: true, force: true });
fs.rmSync("dist", { "recursive": true, "force": true }); fs.rmSync("dist", { recursive: true, force: true });
fs.rmSync(".yarn_home", { "recursive": true, "force": true }); fs.rmSync(".yarn_home", { recursive: true, force: true });
run("yarn install"); run("yarn install");
run("yarn build"); run("yarn build");
fs.rmSync(join("..", "keycloakify-starter", "node_modules"), { "recursive": true, "force": true }); fs.rmSync(join("..", "keycloakify-starter", "node_modules"), {
recursive: true,
force: true
});
run("yarn install", { "cwd": join("..", "keycloakify-starter") }); run("yarn install", { cwd: join("..", "keycloakify-starter") });
run(`npx ts-node --skipProject ${join("scripts", "link-in-app.ts")} keycloakify-starter`); run(`npx ts-node --skipProject ${join("scripts", "link-in-app.ts")} keycloakify-starter`);
@ -20,5 +23,5 @@ run(`npx chokidar '${join("src", "**", "*")}' -c 'yarn build'`);
function run(command: string, options?: { cwd: string }) { function run(command: string, options?: { cwd: string }) {
console.log(`$ ${command}`); console.log(`$ ${command}`);
child_process.execSync(command, { "stdio": "inherit", ...options }); child_process.execSync(command, { stdio: "inherit", ...options });
} }

View File

@ -1,4 +1,7 @@
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "keycloakify/bin/shared/constants"; import {
nameOfTheGlobal,
basenameOfTheKeycloakifyResourcesDir
} from "keycloakify/bin/shared/constants";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
/** /**

View File

@ -24,13 +24,13 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
}, []); }, []);
useSetClassName({ useSetClassName({
"qualifiedName": "html", qualifiedName: "html",
"className": getClassName("kcHtmlClass") className: getClassName("kcHtmlClass")
}); });
useSetClassName({ useSetClassName({
"qualifiedName": "body", qualifiedName: "body",
"className": clsx("admin-console", "user", getClassName("kcBodyClass")) className: clsx("admin-console", "user", getClassName("kcBodyClass"))
}); });
useEffect(() => { useEffect(() => {
@ -46,7 +46,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
}, []); }, []);
const { areAllStyleSheetsLoaded } = useInsertLinkTags({ const { areAllStyleSheetsLoaded } = useInsertLinkTags({
"hrefs": !doUseDefaultCss hrefs: !doUseDefaultCss
? [] ? []
: [ : [
`${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`, `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`,

View File

@ -2,7 +2,10 @@ import type { ReactNode } from "react";
import type { KcContext } from "./kcContext"; import type { KcContext } from "./kcContext";
import type { I18n } from "./i18n"; import type { I18n } from "./i18n";
export type TemplateProps<KcContext extends KcContext.Common, I18nExtended extends I18n> = { export type TemplateProps<
KcContext extends KcContext.Common,
I18nExtended extends I18n
> = {
kcContext: KcContext; kcContext: KcContext;
i18n: I18nExtended; i18n: I18nExtended;
doUseDefaultCss: boolean; doUseDefaultCss: boolean;

View File

@ -91,19 +91,19 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
setI18n({ setI18n({
...createI18nTranslationFunctions({ ...createI18nTranslationFunctions({
"fallbackMessages": { fallbackMessages: {
...fallbackMessages, ...fallbackMessages,
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}), ...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
...(extraMessages[fallbackLanguageTag] ?? {}) ...(extraMessages[fallbackLanguageTag] ?? {})
} as any, } as any,
"messages": { messages: {
...(await getMessages(currentLanguageTag)), ...(await getMessages(currentLanguageTag)),
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}), ...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
...(extraMessages[currentLanguageTag] ?? {}) ...(extraMessages[currentLanguageTag] ?? {})
} as any } as any
}), }),
currentLanguageTag, currentLanguageTag,
"getChangeLocalUrl": newLanguageTag => { getChangeLocalUrl: newLanguageTag => {
const { locale } = kcContext; const { locale } = kcContext;
assert(locale !== undefined, "Internationalization not enabled"); assert(locale !== undefined, "Internationalization not enabled");
@ -114,7 +114,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
return targetSupportedLocale.url; return targetSupportedLocale.url;
}, },
"labelBySupportedLanguageTag": Object.fromEntries( labelBySupportedLanguageTag: Object.fromEntries(
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label]) (kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
) )
}); });
@ -170,7 +170,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
})(); })();
return doRenderMarkdown ? ( return doRenderMarkdown ? (
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}> <Markdown allowDangerousHtml renderers={{ paragraph: "span" }}>
{messageWithArgsInjectedIfAny} {messageWithArgsInjectedIfAny}
</Markdown> </Markdown>
) : ( ) : (
@ -186,7 +186,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1]; const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
const out = resolveMsg({ const out = resolveMsg({
"key": keyUnwrappedFromCurlyBraces, key: keyUnwrappedFromCurlyBraces,
args, args,
doRenderMarkdown doRenderMarkdown
}); });
@ -195,36 +195,46 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
} }
return { return {
"msgStr": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": false }) as string, msgStr: (key, ...args) => resolveMsg({ key, args, doRenderMarkdown: false }) as string,
"msg": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": true }) as JSX.Element, msg: (key, ...args) => resolveMsg({ key, args, doRenderMarkdown: true }) as JSX.Element,
"advancedMsg": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": true }) as JSX.Element, advancedMsg: (key, ...args) =>
"advancedMsgStr": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": false }) as string resolveMsgAdvanced({
key,
args,
doRenderMarkdown: true
}) as JSX.Element,
advancedMsgStr: (key, ...args) =>
resolveMsgAdvanced({
key,
args,
doRenderMarkdown: false
}) as string
}; };
} }
const keycloakifyExtraMessages = { const keycloakifyExtraMessages = {
"en": { en: {
"shouldBeEqual": "{0} should be equal to {1}", shouldBeEqual: "{0} should be equal to {1}",
"shouldBeDifferent": "{0} should be different to {1}", shouldBeDifferent: "{0} should be different to {1}",
"shouldMatchPattern": "Pattern should match: `/{0}/`", shouldMatchPattern: "Pattern should match: `/{0}/`",
"mustBeAnInteger": "Must be an integer", mustBeAnInteger: "Must be an integer",
"notAValidOption": "Not a valid option", notAValidOption: "Not a valid option",
"newPasswordSameAsOld": "New password must be different from the old one", newPasswordSameAsOld: "New password must be different from the old one",
"passwordConfirmNotMatch": "Password confirmation does not match" passwordConfirmNotMatch: "Password confirmation does not match"
}, },
"fr": { fr: {
/* spell-checker: disable */ /* spell-checker: disable */
"shouldBeEqual": "{0} doit être égal à {1}", shouldBeEqual: "{0} doit être égal à {1}",
"shouldBeDifferent": "{0} doit être différent de {1}", shouldBeDifferent: "{0} doit être différent de {1}",
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`", shouldMatchPattern: "Dois respecter le schéma: `/{0}/`",
"mustBeAnInteger": "Doit être un nombre entier", mustBeAnInteger: "Doit être un nombre entier",
"notAValidOption": "N'est pas une option valide", notAValidOption: "N'est pas une option valide",
"logoutConfirmTitle": "Déconnexion", logoutConfirmTitle: "Déconnexion",
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?", logoutConfirmHeader: "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
"doLogout": "Se déconnecter", doLogout: "Se déconnecter",
"newPasswordSameAsOld": "Le nouveau mot de passe doit être différent de l'ancien", newPasswordSameAsOld: "Le nouveau mot de passe doit être différent de l'ancien",
"passwordConfirmNotMatch": "La confirmation du mot de passe ne correspond pas" passwordConfirmNotMatch: "La confirmation du mot de passe ne correspond pas"
/* spell-checker: enable */ /* spell-checker: enable */
} }
}; };

View File

@ -68,7 +68,10 @@ export declare namespace KcContext {
* @param text to return * @param text to return
* @return text if message exists for given field, else undefined * @return text if message exists for given field, else undefined
*/ */
printIfExists: <T extends string>(fieldName: string, text: T) => T | undefined; printIfExists: <T extends string>(
fieldName: string,
text: T
) => T | undefined;
/** /**
* Check if exists error message for given fields * Check if exists error message for given fields
* *

View File

@ -4,17 +4,28 @@ import { isStorybook } from "keycloakify/lib/isStorybook";
import type { ExtendKcContext } from "./getKcContextFromWindow"; import type { ExtendKcContext } from "./getKcContextFromWindow";
import { getKcContextFromWindow } from "./getKcContextFromWindow"; import { getKcContextFromWindow } from "./getKcContextFromWindow";
import { symToStr } from "tsafe/symToStr"; import { symToStr } from "tsafe/symToStr";
import { kcContextMocks, kcContextCommonMock } from "keycloakify/account/kcContext/kcContextMocks"; import {
kcContextMocks,
kcContextCommonMock
} from "keycloakify/account/kcContext/kcContextMocks";
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: { export function createGetKcContext<
KcContextExtension extends { pageId: string } = never
>(params?: {
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[]; mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
mockProperties?: Record<string, string>; mockProperties?: Record<string, string>;
}) { }) {
const { mockData, mockProperties } = params ?? {}; const { mockData, mockProperties } = params ?? {};
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: { function getKcContext<
PageId extends
| ExtendKcContext<KcContextExtension>["pageId"]
| undefined = undefined
>(params?: {
mockPageId?: PageId; mockPageId?: PageId;
storyPartialKcContext?: DeepPartial<Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>>; storyPartialKcContext?: DeepPartial<
Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>
>;
}): { }): {
kcContext: PageId extends undefined kcContext: PageId extends undefined
? ExtendKcContext<KcContextExtension> | undefined ? ExtendKcContext<KcContextExtension> | undefined
@ -32,34 +43,46 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
break warn_that_mock_is_enbaled; break warn_that_mock_is_enbaled;
} }
console.log(`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`, "background: red; color: yellow; font-size: medium"); console.log(
`%cKeycloakify: ${symToStr({
mockPageId
})} set to ${mockPageId}.`,
"background: red; color: yellow; font-size: medium"
);
} }
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId); const kcContextDefaultMock = kcContextMocks.find(
({ pageId }) => pageId === mockPageId
);
const partialKcContextCustomMock = (() => { const partialKcContextCustomMock = (() => {
const out: DeepPartial<ExtendKcContext<KcContextExtension>> = {}; const out: DeepPartial<ExtendKcContext<KcContextExtension>> = {};
const mockDataPick = mockData?.find(({ pageId }) => pageId === mockPageId); const mockDataPick = mockData?.find(
({ pageId }) => pageId === mockPageId
);
if (mockDataPick !== undefined) { if (mockDataPick !== undefined) {
deepAssign({ deepAssign({
"target": out, target: out,
"source": mockDataPick source: mockDataPick
}); });
} }
if (storyPartialKcContext !== undefined) { if (storyPartialKcContext !== undefined) {
deepAssign({ deepAssign({
"target": out, target: out,
"source": storyPartialKcContext source: storyPartialKcContext
}); });
} }
return Object.keys(out).length === 0 ? undefined : out; return Object.keys(out).length === 0 ? undefined : out;
})(); })();
if (kcContextDefaultMock === undefined && partialKcContextCustomMock === undefined) { if (
kcContextDefaultMock === undefined &&
partialKcContextCustomMock === undefined
) {
console.warn( console.warn(
[ [
`WARNING: You declared the non build in page ${mockPageId} but you didn't `, `WARNING: You declared the non build in page ${mockPageId} but you didn't `,
@ -72,21 +95,24 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
const kcContext: any = {}; const kcContext: any = {};
deepAssign({ deepAssign({
"target": kcContext, target: kcContext,
"source": kcContextDefaultMock !== undefined ? kcContextDefaultMock : { "pageId": mockPageId, ...kcContextCommonMock } source:
kcContextDefaultMock !== undefined
? kcContextDefaultMock
: { pageId: mockPageId, ...kcContextCommonMock }
}); });
if (partialKcContextCustomMock !== undefined) { if (partialKcContextCustomMock !== undefined) {
deepAssign({ deepAssign({
"target": kcContext, target: kcContext,
"source": partialKcContextCustomMock source: partialKcContextCustomMock
}); });
} }
if (mockProperties !== undefined) { if (mockProperties !== undefined) {
deepAssign({ deepAssign({
"target": kcContext.properties, target: kcContext.properties,
"source": mockProperties source: mockProperties
}); });
} }
@ -94,14 +120,14 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
} }
if (realKcContext === undefined) { if (realKcContext === undefined) {
return { "kcContext": undefined as any }; return { kcContext: undefined as any };
} }
if (realKcContext.themeType !== "account") { if (realKcContext.themeType !== "account") {
return { "kcContext": undefined as any }; return { kcContext: undefined as any };
} }
return { "kcContext": realKcContext as any }; return { kcContext: realKcContext as any };
} }
return { getKcContext }; return { getKcContext };

View File

@ -5,7 +5,9 @@ import { createGetKcContext } from "./createGetKcContext";
/** NOTE: We now recommend using createGetKcContext instead of this function to make storybook integration easier /** NOTE: We now recommend using createGetKcContext instead of this function to make storybook integration easier
* See: https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/account/kcContext.ts * See: https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/account/kcContext.ts
*/ */
export function getKcContext<KcContextExtension extends { pageId: string } = never>(params?: { export function getKcContext<
KcContextExtension extends { pageId: string } = never
>(params?: {
mockPageId?: ExtendKcContext<KcContextExtension>["pageId"]; mockPageId?: ExtendKcContext<KcContextExtension>["pageId"];
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[]; mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
}): { kcContext: ExtendKcContext<KcContextExtension> | undefined } { }): { kcContext: ExtendKcContext<KcContextExtension> | undefined } {

View File

@ -2,10 +2,14 @@ import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminati
import { nameOfTheGlobal } from "keycloakify/bin/shared/constants"; import { nameOfTheGlobal } from "keycloakify/bin/shared/constants";
import type { KcContext } from "./KcContext"; import type { KcContext } from "./KcContext";
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [KcContextExtension] extends [never] export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [
KcContextExtension
] extends [never]
? KcContext ? KcContext
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>; : AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
export function getKcContextFromWindow<KcContextExtension extends { pageId: string } = never>(): ExtendKcContext<KcContextExtension> | undefined { export function getKcContextFromWindow<
KcContextExtension extends { pageId: string } = never
>(): ExtendKcContext<KcContextExtension> | undefined {
return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal]; return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal];
} }

View File

@ -7,254 +7,258 @@ import { BASE_URL } from "keycloakify/lib/BASE_URL";
const resourcesPath = `${BASE_URL}${keycloak_resources}/account/resources`; const resourcesPath = `${BASE_URL}${keycloak_resources}/account/resources`;
export const kcContextCommonMock: KcContext.Common = { export const kcContextCommonMock: KcContext.Common = {
"themeVersion": "0.0.0", themeVersion: "0.0.0",
"keycloakifyVersion": "0.0.0", keycloakifyVersion: "0.0.0",
"themeType": "account", themeType: "account",
"themeName": "my-theme-name", themeName: "my-theme-name",
"url": { url: {
resourcesPath, resourcesPath,
"resourcesCommonPath": `${resourcesPath}/${resources_common}`, resourcesCommonPath: `${resourcesPath}/${resources_common}`,
"resourceUrl": "#", resourceUrl: "#",
"accountUrl": "#", accountUrl: "#",
"applicationsUrl": "#", applicationsUrl: "#",
"logoutUrl": "#", logoutUrl: "#",
"getLogoutUrl": () => "#", getLogoutUrl: () => "#",
"logUrl": "#", logUrl: "#",
"passwordUrl": "#", passwordUrl: "#",
"sessionsUrl": "#", sessionsUrl: "#",
"socialUrl": "#", socialUrl: "#",
"totpUrl": "#" totpUrl: "#"
}, },
"realm": { realm: {
"internationalizationEnabled": true, internationalizationEnabled: true,
"userManagedAccessAllowed": true userManagedAccessAllowed: true
}, },
"messagesPerField": { messagesPerField: {
"printIfExists": () => { printIfExists: () => {
return undefined; return undefined;
}, },
"existsError": () => false, existsError: () => false,
"get": key => `Fake error for ${key}`, get: key => `Fake error for ${key}`,
"exists": () => false exists: () => false
}, },
"locale": { locale: {
"supported": [ supported: [
/* spell-checker: disable */ /* spell-checker: disable */
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
"label": "Deutsch", label: "Deutsch",
"languageTag": "de" languageTag: "de"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
"label": "Norsk", label: "Norsk",
"languageTag": "no" languageTag: "no"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
"label": "Русский", label: "Русский",
"languageTag": "ru" languageTag: "ru"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
"label": "Svenska", label: "Svenska",
"languageTag": "sv" languageTag: "sv"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
"label": "Português (Brasil)", label: "Português (Brasil)",
"languageTag": "pt-BR" languageTag: "pt-BR"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
"label": "Lietuvių", label: "Lietuvių",
"languageTag": "lt" languageTag: "lt"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
"label": "English", label: "English",
"languageTag": "en" languageTag: "en"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
"label": "Italiano", label: "Italiano",
"languageTag": "it" languageTag: "it"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
"label": "Français", label: "Français",
"languageTag": "fr" languageTag: "fr"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
"label": "中文简体", label: "中文简体",
"languageTag": "zh-CN" languageTag: "zh-CN"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
"label": "Español", label: "Español",
"languageTag": "es" languageTag: "es"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
"label": "Čeština", label: "Čeština",
"languageTag": "cs" languageTag: "cs"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
"label": "日本語", label: "日本語",
"languageTag": "ja" languageTag: "ja"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
"label": "Slovenčina", label: "Slovenčina",
"languageTag": "sk" languageTag: "sk"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
"label": "Polski", label: "Polski",
"languageTag": "pl" languageTag: "pl"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
"label": "Català", label: "Català",
"languageTag": "ca" languageTag: "ca"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
"label": "Nederlands", label: "Nederlands",
"languageTag": "nl" languageTag: "nl"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
"label": "Türkçe", label: "Türkçe",
"languageTag": "tr" languageTag: "tr"
} }
/* spell-checker: enable */ /* spell-checker: enable */
], ],
"currentLanguageTag": "en" currentLanguageTag: "en"
}, },
"features": { features: {
"authorization": true, authorization: true,
"identityFederation": true, identityFederation: true,
"log": true, log: true,
"passwordUpdateSupported": true passwordUpdateSupported: true
}, },
"referrer": undefined, referrer: undefined,
"account": { account: {
"firstName": "john", firstName: "john",
"lastName": "doe", lastName: "doe",
"email": "john.doe@code.gouv.fr", email: "john.doe@code.gouv.fr",
"username": "doe_j" username: "doe_j"
}, },
"properties": { properties: {
"parent": "account-v1", parent: "account-v1",
"kcButtonLargeClass": "btn-lg", kcButtonLargeClass: "btn-lg",
"locales": "ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN", locales:
"kcButtonPrimaryClass": "btn-primary", "ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
"accountResourceProvider": "account-v1", kcButtonPrimaryClass: "btn-primary",
"styles": accountResourceProvider: "account-v1",
"css/account.css img/icon-sidebar-active.png img/logo.png resources-common/node_modules/patternfly/dist/css/patternfly.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css", styles: "css/account.css img/icon-sidebar-active.png img/logo.png resources-common/node_modules/patternfly/dist/css/patternfly.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css",
"kcButtonClass": "btn", kcButtonClass: "btn",
"kcButtonDefaultClass": "btn-default" kcButtonDefaultClass: "btn-default"
} }
}; };
export const kcContextMocks: KcContext[] = [ export const kcContextMocks: KcContext[] = [
id<KcContext.Password>({ id<KcContext.Password>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "password.ftl", pageId: "password.ftl",
"password": { password: {
"passwordSet": true passwordSet: true
}, },
"stateChecker": "state checker" stateChecker: "state checker"
}), }),
id<KcContext.Account>({ id<KcContext.Account>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "account.ftl", pageId: "account.ftl",
"url": { url: {
...kcContextCommonMock.url, ...kcContextCommonMock.url,
"referrerURI": "#", referrerURI: "#",
"accountUrl": "#" accountUrl: "#"
}, },
"realm": { realm: {
...kcContextCommonMock.realm, ...kcContextCommonMock.realm,
"registrationEmailAsUsername": true, registrationEmailAsUsername: true,
"editUsernameAllowed": true editUsernameAllowed: true
}, },
"stateChecker": "" stateChecker: ""
}), }),
id<KcContext.Sessions>({ id<KcContext.Sessions>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "sessions.ftl", pageId: "sessions.ftl",
"sessions": { sessions: {
"sessions": [ sessions: [
{ {
"ipAddress": "127.0.0.1", ipAddress: "127.0.0.1",
"started": new Date().toString(), started: new Date().toString(),
"lastAccess": new Date().toString(), lastAccess: new Date().toString(),
"expires": new Date().toString(), expires: new Date().toString(),
"clients": ["Chrome", "Firefox"], clients: ["Chrome", "Firefox"],
"id": "f8951177-817d-4a70-9c02-86d3c170fe51" id: "f8951177-817d-4a70-9c02-86d3c170fe51"
} }
] ]
}, },
"stateChecker": "g6WB1FaYnKotTkiy7ZrlxvFztSqS0U8jvHsOOOb2z4g" stateChecker: "g6WB1FaYnKotTkiy7ZrlxvFztSqS0U8jvHsOOOb2z4g"
}), }),
id<KcContext.Totp>({ id<KcContext.Totp>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "totp.ftl", pageId: "totp.ftl",
"totp": { totp: {
"enabled": true, enabled: true,
"totpSecretEncoded": "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV", totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
"qrUrl": "#", qrUrl: "#",
"totpSecretQrCode": totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=", "iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=",
"manualUrl": "#", manualUrl: "#",
"totpSecret": "G4nsI8lQagRMUchH8jEG", totpSecret: "G4nsI8lQagRMUchH8jEG",
"otpCredentials": [], otpCredentials: [],
"supportedApplications": ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"], supportedApplications: [
"policy": { "totpAppFreeOTPName",
"algorithm": "HmacSHA1", "totpAppMicrosoftAuthenticatorName",
"digits": 6, "totpAppGoogleName"
"lookAheadWindow": 1, ],
"type": "totp", policy: {
"period": 30 algorithm: "HmacSHA1",
digits: 6,
lookAheadWindow: 1,
type: "totp",
period: 30
} }
}, },
"mode": "qr", mode: "qr",
"isAppInitiatedAction": false, isAppInitiatedAction: false,
"stateChecker": "" stateChecker: ""
}), }),
id<KcContext.Log>({ id<KcContext.Log>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "log.ftl", pageId: "log.ftl",
"log": { log: {
"events": [ events: [
{ {
"date": "2/21/2024, 1:28:39 PM", date: "2/21/2024, 1:28:39 PM",
"event": "login", event: "login",
"ipAddress": "172.17.0.1", ipAddress: "172.17.0.1",
"client": "security-admin-console", client: "security-admin-console",
"details": [{ key: "openid-connect", value: "admin" }] details: [{ key: "openid-connect", value: "admin" }]
} }
] ]
} }
}), }),
id<KcContext.FederatedIdentity>({ id<KcContext.FederatedIdentity>({
...kcContextCommonMock, ...kcContextCommonMock,
"stateChecker": "", stateChecker: "",
"pageId": "federatedIdentity.ftl", pageId: "federatedIdentity.ftl",
"federatedIdentity": { federatedIdentity: {
"identities": [ identities: [
{ {
"providerId": "keycloak-oidc", providerId: "keycloak-oidc",
"displayName": "keycloak-oidc", displayName: "keycloak-oidc",
"userName": "John", userName: "John",
"connected": true connected: true
} }
], ],
"removeLinkPossible": true removeLinkPossible: true
} }
}) })
]; ];

View File

@ -2,19 +2,20 @@ import { createUseClassName } from "keycloakify/lib/useGetClassName";
import type { ClassKey } from "keycloakify/account/TemplateProps"; import type { ClassKey } from "keycloakify/account/TemplateProps";
export const { useGetClassName } = createUseClassName<ClassKey>({ export const { useGetClassName } = createUseClassName<ClassKey>({
"defaultClasses": { defaultClasses: {
"kcHtmlClass": undefined, kcHtmlClass: undefined,
"kcBodyClass": undefined, kcBodyClass: undefined,
"kcButtonClass": "btn", kcButtonClass: "btn",
"kcContentWrapperClass": "row", kcContentWrapperClass: "row",
"kcButtonPrimaryClass": "btn-primary", kcButtonPrimaryClass: "btn-primary",
"kcButtonLargeClass": "btn-lg", kcButtonLargeClass: "btn-lg",
"kcButtonDefaultClass": "btn-default", kcButtonDefaultClass: "btn-default",
"kcFormClass": "form-horizontal", kcFormClass: "form-horizontal",
"kcFormGroupClass": "form-group", kcFormGroupClass: "form-group",
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", kcInputWrapperClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcLabelClass": "control-label", kcLabelClass: "control-label",
"kcInputClass": "form-control", kcInputClass: "form-control",
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text" kcInputErrorMessageClass:
"pf-c-form__helper-text pf-m-error required kc-feedback-text"
} }
}); });

View File

@ -9,9 +9,9 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
const { getClassName } = useGetClassName({ const { getClassName } = useGetClassName({
doUseDefaultCss, doUseDefaultCss,
"classes": { classes: {
...classes, ...classes,
"kcBodyClass": clsx(classes?.kcBodyClass, "user") kcBodyClass: clsx(classes?.kcBodyClass, "user")
} }
}); });

View File

@ -10,9 +10,9 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
const { getClassName } = useGetClassName({ const { getClassName } = useGetClassName({
doUseDefaultCss, doUseDefaultCss,
"classes": { classes: {
...classes, ...classes,
"kcBodyClass": clsx(classes?.kcBodyClass, "password") kcBodyClass: clsx(classes?.kcBodyClass, "password")
} }
}); });
@ -57,18 +57,18 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
{...{ {...{
kcContext: { kcContext: {
...kcContext, ...kcContext,
"message": (() => { message: (() => {
if (newPasswordError !== "") { if (newPasswordError !== "") {
return { return {
"type": "error", type: "error",
"summary": newPasswordError summary: newPasswordError
}; };
} }
if (newPasswordConfirmError !== "") { if (newPasswordConfirmError !== "") {
return { return {
"type": "error", type: "error",
"summary": newPasswordConfirmError summary: newPasswordConfirmError
}; };
} }
@ -98,7 +98,7 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
value={account.username ?? ""} value={account.username ?? ""}
autoComplete="username" autoComplete="username"
readOnly readOnly
style={{ "display": "none" }} style={{ display: "none" }}
/> />
{password.passwordSet && ( {password.passwordSet && (

View File

@ -18,9 +18,9 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = { const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
"HmacSHA1": "SHA1", HmacSHA1: "SHA1",
"HmacSHA256": "SHA256", HmacSHA256: "SHA256",
"HmacSHA512": "SHA512" HmacSHA512: "SHA512"
}; };
return ( return (

View File

@ -8,9 +8,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const buildOptions = readBuildOptions({ cliCommandOptions }); const buildOptions = readBuildOptions({ cliCommandOptions });
await copyKeycloakResourcesToPublic({ await copyKeycloakResourcesToPublic({
"buildOptions": { buildOptions: {
...buildOptions, ...buildOptions,
"publicDirPath": buildOptions.reactAppRootDirPath publicDirPath: buildOptions.reactAppRootDirPath
} }
}); });
} }

View File

@ -12,22 +12,39 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
cliCommandOptions cliCommandOptions
}); });
console.log(chalk.cyan("Select the Keycloak version from which you want to download the builtins theme:")); console.log(
chalk.cyan(
"Select the Keycloak version from which you want to download the builtins theme:"
)
);
const { keycloakVersion } = await promptKeycloakVersion({ const { keycloakVersion } = await promptKeycloakVersion({
"startingFromMajor": undefined, startingFromMajor: undefined,
"cacheDirPath": buildOptions.cacheDirPath cacheDirPath: buildOptions.cacheDirPath
}); });
console.log(`${keycloakVersion}`); console.log(`${keycloakVersion}`);
const destDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme"); const destDirPath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"theme"
);
console.log( console.log(
[ [
`Downloading builtins theme of Keycloak ${keycloakVersion} here:`, `Downloading builtins theme of Keycloak ${keycloakVersion} here:`,
`- ${chalk.bold(`.${pathSep}${pathJoin(pathRelative(process.cwd(), destDirPath), "base")}`)}`, `- ${chalk.bold(
`- ${chalk.bold(`.${pathSep}${pathJoin(pathRelative(process.cwd(), destDirPath), "keycloak")}`)}` `.${pathSep}${pathJoin(pathRelative(process.cwd(), destDirPath), "base")}`
)}`,
`- ${chalk.bold(
`.${pathSep}${pathJoin(
pathRelative(process.cwd(), destDirPath),
"keycloak"
)}`
)}`
].join("\n") ].join("\n")
); );

View File

@ -30,7 +30,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(chalk.cyan("Theme type:")); console.log(chalk.cyan("Theme type:"));
const { value: themeType } = await cliSelect<ThemeType>({ const { value: themeType } = await cliSelect<ThemeType>({
"values": [...themeTypes] values: [...themeTypes]
}).catch(() => { }).catch(() => {
process.exit(-1); process.exit(-1);
}); });
@ -40,7 +40,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(chalk.cyan("Select the page you want to customize:")); console.log(chalk.cyan("Select the page you want to customize:"));
const { value: pageId } = await cliSelect<LoginThemePageId | AccountThemePageId>({ const { value: pageId } = await cliSelect<LoginThemePageId | AccountThemePageId>({
"values": (() => { values: (() => {
switch (themeType) { switch (themeType) {
case "login": case "login":
return [...loginThemePageIds]; return [...loginThemePageIds];
@ -55,14 +55,29 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(`${pageId}`); console.log(`${pageId}`);
const componentPageBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(/ftl$/, "tsx"); const componentPageBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(
/ftl$/,
"tsx"
);
const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath }); const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
});
const targetFilePath = pathJoin(themeSrcDirPath, themeType, "pages", componentPageBasename); const targetFilePath = pathJoin(
themeSrcDirPath,
themeType,
"pages",
componentPageBasename
);
if (fs.existsSync(targetFilePath)) { if (fs.existsSync(targetFilePath)) {
console.log(`${pageId} is already ejected, ${pathRelative(process.cwd(), targetFilePath)} already exists`); console.log(
`${pageId} is already ejected, ${pathRelative(
process.cwd(),
targetFilePath
)} already exists`
);
process.exit(-1); process.exit(-1);
} }
@ -71,12 +86,20 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const targetDirPath = pathDirname(targetFilePath); const targetDirPath = pathDirname(targetFilePath);
if (!fs.existsSync(targetDirPath)) { if (!fs.existsSync(targetDirPath)) {
fs.mkdirSync(targetDirPath, { "recursive": true }); fs.mkdirSync(targetDirPath, { recursive: true });
} }
} }
const componentPageContent = fs const componentPageContent = fs
.readFileSync(pathJoin(getThisCodebaseRootDirPath(), "src", themeType, "pages", componentPageBasename)) .readFileSync(
pathJoin(
getThisCodebaseRootDirPath(),
"src",
themeType,
"pages",
componentPageBasename
)
)
.toString("utf8"); .toString("utf8");
fs.writeFileSync(targetFilePath, Buffer.from(componentPageContent, "utf8")); fs.writeFileSync(targetFilePath, Buffer.from(componentPageContent, "utf8"));
@ -92,11 +115,23 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
``, ``,
`You now need to update your page router:`, `You now need to update your page router:`,
``, ``,
`${chalk.bold(pathJoin(".", pathRelative(process.cwd(), themeSrcDirPath), themeType, "KcApp.tsx"))}:`, `${chalk.bold(
pathJoin(
".",
pathRelative(process.cwd(), themeSrcDirPath),
themeType,
"KcApp.tsx"
)
)}:`,
chalk.grey("```"), chalk.grey("```"),
`// ...`, `// ...`,
``, ``,
chalk.green(`+const ${componentPageBasename.replace(/.tsx$/, "")} = lazy(() => import("./pages/${componentPageBasename}"));`), chalk.green(
`+const ${componentPageBasename.replace(
/.tsx$/,
""
)} = lazy(() => import("./pages/${componentPageBasename}"));`
),
...[ ...[
``, ``,
` export default function KcApp(props: { kcContext: KcContext; }) {`, ` export default function KcApp(props: { kcContext: KcContext; }) {`,
@ -114,7 +149,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`+ Template={Template}`, `+ Template={Template}`,
...(!componentPageContent.includes(userProfileFormFieldComponentName) ...(!componentPageContent.includes(userProfileFormFieldComponentName)
? [] ? []
: [`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`]), : [
`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`
]),
`+ doUseDefaultCss={true}`, `+ doUseDefaultCss={true}`,
`+ />`, `+ />`,
`+ );`, `+ );`,

View File

@ -14,13 +14,18 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const buildOptions = readBuildOptions({ cliCommandOptions }); const buildOptions = readBuildOptions({ cliCommandOptions });
const { themeSrcDirPath } = getThemeSrcDirPath({ const { themeSrcDirPath } = getThemeSrcDirPath({
"reactAppRootDirPath": buildOptions.reactAppRootDirPath reactAppRootDirPath: buildOptions.reactAppRootDirPath
}); });
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email"); const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
if (fs.existsSync(emailThemeSrcDirPath)) { if (fs.existsSync(emailThemeSrcDirPath)) {
console.warn(`There is already a ${pathRelative(process.cwd(), emailThemeSrcDirPath)} directory in your project. Aborting.`); console.warn(
`There is already a ${pathRelative(
process.cwd(),
emailThemeSrcDirPath
)} directory in your project. Aborting.`
);
process.exit(-1); process.exit(-1);
} }
@ -29,33 +34,50 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { keycloakVersion } = await promptKeycloakVersion({ const { keycloakVersion } = await promptKeycloakVersion({
// NOTE: This is arbitrary // NOTE: This is arbitrary
"startingFromMajor": 17, startingFromMajor: 17,
"cacheDirPath": buildOptions.cacheDirPath cacheDirPath: buildOptions.cacheDirPath
}); });
const builtinKeycloakThemeTmpDirPath = pathJoin(buildOptions.cacheDirPath, "initialize-email-theme_tmp"); const builtinKeycloakThemeTmpDirPath = pathJoin(
buildOptions.cacheDirPath,
"initialize-email-theme_tmp"
);
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true }); rmSync(builtinKeycloakThemeTmpDirPath, {
recursive: true,
force: true
});
await downloadKeycloakDefaultTheme({ await downloadKeycloakDefaultTheme({
keycloakVersion, keycloakVersion,
"destDirPath": builtinKeycloakThemeTmpDirPath, destDirPath: builtinKeycloakThemeTmpDirPath,
buildOptions buildOptions
}); });
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"), srcDirPath: pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"),
"destDirPath": emailThemeSrcDirPath destDirPath: emailThemeSrcDirPath
}); });
{ {
const themePropertyFilePath = pathJoin(emailThemeSrcDirPath, "theme.properties"); const themePropertyFilePath = pathJoin(emailThemeSrcDirPath, "theme.properties");
fs.writeFileSync(themePropertyFilePath, Buffer.from(`parent=base\n${fs.readFileSync(themePropertyFilePath).toString("utf8")}`, "utf8")); fs.writeFileSync(
themePropertyFilePath,
Buffer.from(
`parent=base\n${fs.readFileSync(themePropertyFilePath).toString("utf8")}`,
"utf8"
)
);
} }
console.log(`The \`${pathJoin(".", pathRelative(process.cwd(), emailThemeSrcDirPath))}\` directory have been created.`); console.log(
`The \`${pathJoin(
".",
pathRelative(process.cwd(), emailThemeSrcDirPath)
)}\` directory have been created.`
);
console.log("You can delete any file you don't modify."); console.log("You can delete any file you don't modify.");
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true }); rmSync(builtinKeycloakThemeTmpDirPath, { recursive: true });
} }

View File

@ -1,11 +1,17 @@
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import type { KeycloakAccountV1Version, KeycloakThemeAdditionalInfoExtensionVersion } from "./extensionVersions"; import type {
KeycloakAccountV1Version,
KeycloakThemeAdditionalInfoExtensionVersion
} from "./extensionVersions";
import { join as pathJoin, dirname as pathDirname } from "path"; import { join as pathJoin, dirname as pathDirname } from "path";
import { transformCodebase } from "../../tools/transformCodebase"; import { transformCodebase } from "../../tools/transformCodebase";
import type { BuildOptions } from "../../shared/buildOptions"; import type { BuildOptions } from "../../shared/buildOptions";
import * as fs from "fs/promises"; import * as fs from "fs/promises";
import { accountV1ThemeName } from "../../shared/constants"; import { accountV1ThemeName } from "../../shared/constants";
import { generatePom, BuildOptionsLike as BuildOptionsLike_generatePom } from "./generatePom"; import {
generatePom,
BuildOptionsLike as BuildOptionsLike_generatePom
} from "./generatePom";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { isInside } from "../../tools/isInside"; import { isInside } from "../../tools/isInside";
import child_process from "child_process"; import child_process from "child_process";
@ -29,26 +35,53 @@ export async function buildJar(params: {
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion; keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
buildOptions: BuildOptionsLike; buildOptions: BuildOptionsLike;
}): Promise<void> { }): Promise<void> {
const { jarFileBasename, keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, buildOptions } = params; const {
jarFileBasename,
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion,
buildOptions
} = params;
const keycloakifyBuildTmpDirPath = pathJoin(buildOptions.cacheDirPath, jarFileBasename.replace(".jar", "")); const keycloakifyBuildTmpDirPath = pathJoin(
buildOptions.cacheDirPath,
jarFileBasename.replace(".jar", "")
);
rmSync(keycloakifyBuildTmpDirPath, { "recursive": true, "force": true }); rmSync(keycloakifyBuildTmpDirPath, { recursive: true, force: true });
{ {
const metaInfKeycloakThemesJsonRelativePath = getMetaInfKeycloakThemesJsonFilePath({ "keycloakifyBuildDirPath": "" }); const metaInfKeycloakThemesJsonRelativePath =
getMetaInfKeycloakThemesJsonFilePath({
keycloakifyBuildDirPath: ""
});
const { transformCodebase_common } = (() => { const { transformCodebase_common } = (() => {
const includingAccountV1ThemeNames = [...buildOptions.themeNames, accountV1ThemeName]; const includingAccountV1ThemeNames = [
...buildOptions.themeNames,
accountV1ThemeName
];
const transformCodebase_common: Param0<typeof transformCodebase>["transformSourceCode"] = ({ fileRelativePath, sourceCode }) => { const transformCodebase_common: Param0<
typeof transformCodebase
>["transformSourceCode"] = ({ fileRelativePath, sourceCode }) => {
if (metaInfKeycloakThemesJsonRelativePath === fileRelativePath) { if (metaInfKeycloakThemesJsonRelativePath === fileRelativePath) {
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
for (const themeName of includingAccountV1ThemeNames) { for (const themeName of includingAccountV1ThemeNames) {
if (isInside({ "dirPath": pathJoin("src", "main", "resources", "theme", themeName), "filePath": fileRelativePath })) { if (
return { "modifiedSourceCode": sourceCode }; isInside({
dirPath: pathJoin(
"src",
"main",
"resources",
"theme",
themeName
),
filePath: fileRelativePath
})
) {
return { modifiedSourceCode: sourceCode };
} }
} }
@ -60,36 +93,70 @@ export async function buildJar(params: {
const { transformCodebase_patchForUsingBuiltinAccountV1 } = (() => { const { transformCodebase_patchForUsingBuiltinAccountV1 } = (() => {
if (keycloakAccountV1Version !== null) { if (keycloakAccountV1Version !== null) {
return { "transformCodebase_patchForUsingBuiltinAccountV1": undefined }; return {
transformCodebase_patchForUsingBuiltinAccountV1: undefined
};
} }
const themePropertiesFileRelativePathSet = new Set( const themePropertiesFileRelativePathSet = new Set(
...buildOptions.themeNames.map(themeName => pathJoin("src", "main", "resources", "theme", themeName, "account", "theme.properties")) ...buildOptions.themeNames.map(themeName =>
pathJoin(
"src",
"main",
"resources",
"theme",
themeName,
"account",
"theme.properties"
)
)
); );
const accountV1RelativeDirPath = pathJoin("src", "main", "resources", "theme", accountV1ThemeName); const accountV1RelativeDirPath = pathJoin(
"src",
"main",
"resources",
"theme",
accountV1ThemeName
);
const transformCodebase_patchForUsingBuiltinAccountV1: Param0<typeof transformCodebase>["transformSourceCode"] = ({ const transformCodebase_patchForUsingBuiltinAccountV1: Param0<
fileRelativePath, typeof transformCodebase
sourceCode >["transformSourceCode"] = ({ fileRelativePath, sourceCode }) => {
}) => { if (
if (isInside({ "dirPath": accountV1RelativeDirPath, "filePath": fileRelativePath })) { isInside({
dirPath: accountV1RelativeDirPath,
filePath: fileRelativePath
})
) {
return undefined; return undefined;
} }
if (fileRelativePath === metaInfKeycloakThemesJsonRelativePath) { if (fileRelativePath === metaInfKeycloakThemesJsonRelativePath) {
const keycloakThemesJsonParsed = JSON.parse(sourceCode.toString("utf8")) as { const keycloakThemesJsonParsed = JSON.parse(
sourceCode.toString("utf8")
) as {
themes: { name: string; types: string[] }[]; themes: { name: string; types: string[] }[];
}; };
keycloakThemesJsonParsed.themes = keycloakThemesJsonParsed.themes.filter(({ name }) => name !== accountV1ThemeName); keycloakThemesJsonParsed.themes =
keycloakThemesJsonParsed.themes.filter(
({ name }) => name !== accountV1ThemeName
);
return { "modifiedSourceCode": Buffer.from(JSON.stringify(keycloakThemesJsonParsed, null, 2), "utf8") }; return {
modifiedSourceCode: Buffer.from(
JSON.stringify(keycloakThemesJsonParsed, null, 2),
"utf8"
)
};
} }
if (themePropertiesFileRelativePathSet.has(fileRelativePath)) { if (themePropertiesFileRelativePathSet.has(fileRelativePath)) {
const modifiedSourceCode = Buffer.from( const modifiedSourceCode = Buffer.from(
sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"), sourceCode
.toString("utf8")
.replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
"utf8" "utf8"
); );
@ -99,16 +166,16 @@ export async function buildJar(params: {
return { modifiedSourceCode }; return { modifiedSourceCode };
} }
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
}; };
return { transformCodebase_patchForUsingBuiltinAccountV1 }; return { transformCodebase_patchForUsingBuiltinAccountV1 };
})(); })();
transformCodebase({ transformCodebase({
"srcDirPath": buildOptions.keycloakifyBuildDirPath, srcDirPath: buildOptions.keycloakifyBuildDirPath,
"destDirPath": keycloakifyBuildTmpDirPath, destDirPath: keycloakifyBuildTmpDirPath,
"transformSourceCode": params => { transformSourceCode: params => {
const resultCommon = transformCodebase_common(params); const resultCommon = transformCodebase_common(params);
if (resultCommon === undefined) { if (resultCommon === undefined) {
@ -125,7 +192,7 @@ export async function buildJar(params: {
return transformCodebase_patchForUsingBuiltinAccountV1?.({ return transformCodebase_patchForUsingBuiltinAccountV1?.({
...params, ...params,
"sourceCode": modifiedSourceCode sourceCode: modifiedSourceCode
}); });
} }
}); });
@ -152,7 +219,16 @@ export async function buildJar(params: {
(["register.ftl", "login-update-profile.ftl"] as const).forEach(pageId => (["register.ftl", "login-update-profile.ftl"] as const).forEach(pageId =>
buildOptions.themeNames.map(themeName => { buildOptions.themeNames.map(themeName => {
const ftlFilePath = pathJoin(keycloakifyBuildTmpDirPath, "src", "main", "resources", "theme", themeName, "login", pageId); const ftlFilePath = pathJoin(
keycloakifyBuildTmpDirPath,
"src",
"main",
"resources",
"theme",
themeName,
"login",
pageId
);
const ftlFileContent = readFileSync(ftlFilePath).toString("utf8"); const ftlFileContent = readFileSync(ftlFilePath).toString("utf8");
@ -173,7 +249,10 @@ export async function buildJar(params: {
assert(modifiedFtlFileContent !== ftlFileContent); assert(modifiedFtlFileContent !== ftlFileContent);
fs.writeFile(pathJoin(pathDirname(ftlFilePath), realPageId), Buffer.from(modifiedFtlFileContent, "utf8")); fs.writeFile(
pathJoin(pathDirname(ftlFilePath), realPageId),
Buffer.from(modifiedFtlFileContent, "utf8")
);
}) })
); );
} }
@ -185,35 +264,46 @@ export async function buildJar(params: {
keycloakThemeAdditionalInfoExtensionVersion keycloakThemeAdditionalInfoExtensionVersion
}); });
await fs.writeFile(pathJoin(keycloakifyBuildTmpDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8")); await fs.writeFile(
pathJoin(keycloakifyBuildTmpDirPath, "pom.xml"),
Buffer.from(pomFileCode, "utf8")
);
} }
await new Promise<void>((resolve, reject) => await new Promise<void>((resolve, reject) =>
child_process.exec("mvn clean install", { "cwd": keycloakifyBuildTmpDirPath }, error => { child_process.exec(
if (error !== null) { "mvn clean install",
console.error( { cwd: keycloakifyBuildTmpDirPath },
`Build jar failed: ${JSON.stringify( error => {
{ if (error !== null) {
jarFileBasename, console.error(
keycloakAccountV1Version, `Build jar failed: ${JSON.stringify(
keycloakThemeAdditionalInfoExtensionVersion {
}, jarFileBasename,
null, keycloakAccountV1Version,
2 keycloakThemeAdditionalInfoExtensionVersion
)}` },
); null,
2
)}`
);
reject(error); reject(error);
return; return;
}
resolve();
} }
resolve(); )
})
); );
await fs.rename( await fs.rename(
pathJoin(keycloakifyBuildTmpDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`), pathJoin(
keycloakifyBuildTmpDirPath,
"target",
`${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`
),
pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename) pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)
); );
rmSync(keycloakifyBuildTmpDirPath, { "recursive": true }); rmSync(keycloakifyBuildTmpDirPath, { recursive: true });
} }

View File

@ -1,6 +1,9 @@
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { exclude } from "tsafe/exclude"; import { exclude } from "tsafe/exclude";
import { keycloakAccountV1Versions, keycloakThemeAdditionalInfoExtensionVersions } from "./extensionVersions"; import {
keycloakAccountV1Versions,
keycloakThemeAdditionalInfoExtensionVersions
} from "./extensionVersions";
import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar"; import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar";
import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar"; import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar";
import type { BuildOptions } from "../../shared/buildOptions"; import type { BuildOptions } from "../../shared/buildOptions";
@ -14,11 +17,13 @@ export type BuildOptionsLike = BuildOptionsLike_buildJar & {
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function buildJars(params: { buildOptions: BuildOptionsLike }): Promise<void> { export async function buildJars(params: {
buildOptions: BuildOptionsLike;
}): Promise<void> {
const { buildOptions } = params; const { buildOptions } = params;
const doesImplementAccountTheme = readMetaInfKeycloakThemes({ const doesImplementAccountTheme = readMetaInfKeycloakThemes({
"keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
}).themes.some(({ name }) => name === accountV1ThemeName); }).themes.some(({ name }) => name === accountV1ThemeName);
await Promise.all( await Promise.all(
@ -36,24 +41,38 @@ export async function buildJars(params: { buildOptions: BuildOptionsLike }): Pro
return undefined; return undefined;
} }
return { keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange };
})
.filter(exclude(undefined))
.map(({ keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange }) => {
const { jarFileBasename } = getJarFileBasename({ keycloakVersionRange });
return { return {
keycloakThemeAdditionalInfoExtensionVersion, keycloakThemeAdditionalInfoExtensionVersion,
jarFileBasename keycloakVersionRange
}; };
}) })
.map(({ keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }) => .filter(exclude(undefined))
buildJar({ .map(
jarFileBasename, ({
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion, keycloakThemeAdditionalInfoExtensionVersion,
buildOptions keycloakVersionRange
}) }) => {
const { jarFileBasename } = getJarFileBasename({
keycloakVersionRange
});
return {
keycloakThemeAdditionalInfoExtensionVersion,
jarFileBasename
};
}
)
.map(
({
keycloakThemeAdditionalInfoExtensionVersion,
jarFileBasename
}) =>
buildJar({
jarFileBasename,
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion,
buildOptions
})
) )
) )
.flat() .flat()

View File

@ -13,4 +13,5 @@ export const keycloakThemeAdditionalInfoExtensionVersions = [null, "1.1.5"] as c
* https://central.sonatype.com/artifact/dev.jcputney/keycloak-theme-additional-info-extension * https://central.sonatype.com/artifact/dev.jcputney/keycloak-theme-additional-info-extension
* https://github.com/jcputney/keycloak-theme-additional-info-extension * https://github.com/jcputney/keycloak-theme-additional-info-extension
* */ * */
export type KeycloakThemeAdditionalInfoExtensionVersion = (typeof keycloakThemeAdditionalInfoExtensionVersions)[number]; export type KeycloakThemeAdditionalInfoExtensionVersion =
(typeof keycloakThemeAdditionalInfoExtensionVersions)[number];

View File

@ -1,6 +1,9 @@
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../shared/buildOptions"; import type { BuildOptions } from "../../shared/buildOptions";
import type { KeycloakAccountV1Version, KeycloakThemeAdditionalInfoExtensionVersion } from "./extensionVersions"; import type {
KeycloakAccountV1Version,
KeycloakThemeAdditionalInfoExtensionVersion
} from "./extensionVersions";
export type BuildOptionsLike = { export type BuildOptionsLike = {
groupId: string; groupId: string;
@ -15,7 +18,11 @@ export function generatePom(params: {
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion; keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
buildOptions: BuildOptionsLike; buildOptions: BuildOptionsLike;
}) { }) {
const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, buildOptions } = params; const {
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion,
buildOptions
} = params;
const { pomFileCode } = (function generatePomFileCode(): { const { pomFileCode } = (function generatePomFileCode(): {
pomFileCode: string; pomFileCode: string;
@ -35,7 +42,8 @@ export function generatePom(params: {
` <properties>`, ` <properties>`,
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`, ` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
` </properties>`, ` </properties>`,
...(keycloakAccountV1Version !== null && keycloakThemeAdditionalInfoExtensionVersion !== null ...(keycloakAccountV1Version !== null &&
keycloakThemeAdditionalInfoExtensionVersion !== null
? [ ? [
` <build>`, ` <build>`,
` <plugins>`, ` <plugins>`,

View File

@ -1,5 +1,8 @@
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import type { KeycloakAccountV1Version, KeycloakThemeAdditionalInfoExtensionVersion } from "./extensionVersions"; import type {
KeycloakAccountV1Version,
KeycloakThemeAdditionalInfoExtensionVersion
} from "./extensionVersions";
import type { KeycloakVersionRange } from "../../shared/KeycloakVersionRange"; import type { KeycloakVersionRange } from "../../shared/KeycloakVersionRange";
export function getKeycloakVersionRangeForJar(params: { export function getKeycloakVersionRangeForJar(params: {
@ -7,7 +10,11 @@ export function getKeycloakVersionRangeForJar(params: {
keycloakAccountV1Version: KeycloakAccountV1Version; keycloakAccountV1Version: KeycloakAccountV1Version;
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion; keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
}): KeycloakVersionRange | undefined { }): KeycloakVersionRange | undefined {
const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, doesImplementAccountTheme } = params; const {
keycloakAccountV1Version,
keycloakThemeAdditionalInfoExtensionVersion,
doesImplementAccountTheme
} = params;
if (doesImplementAccountTheme) { if (doesImplementAccountTheme) {
const keycloakVersionRange = (() => { const keycloakVersionRange = (() => {
@ -19,7 +26,9 @@ export function getKeycloakVersionRangeForJar(params: {
case "1.1.5": case "1.1.5":
return undefined; return undefined;
} }
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false); assert<
Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>
>(false);
case "0.3": case "0.3":
switch (keycloakThemeAdditionalInfoExtensionVersion) { switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null: case null:
@ -27,7 +36,9 @@ export function getKeycloakVersionRangeForJar(params: {
case "1.1.5": case "1.1.5":
return "23" as const; return "23" as const;
} }
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false); assert<
Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>
>(false);
case "0.4": case "0.4":
switch (keycloakThemeAdditionalInfoExtensionVersion) { switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null: case null:
@ -35,11 +46,18 @@ export function getKeycloakVersionRangeForJar(params: {
case "1.1.5": case "1.1.5":
return "24-and-above" as const; return "24-and-above" as const;
} }
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false); assert<
Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>
>(false);
} }
})(); })();
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithAccountTheme | undefined>>(); assert<
Equals<
typeof keycloakVersionRange,
KeycloakVersionRange.WithAccountTheme | undefined
>
>();
return keycloakVersionRange; return keycloakVersionRange;
} else { } else {
@ -54,10 +72,17 @@ export function getKeycloakVersionRangeForJar(params: {
case "1.1.5": case "1.1.5":
return "22-and-above"; return "22-and-above";
} }
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false); assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(
false
);
})(); })();
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithoutAccountTheme | undefined>>(); assert<
Equals<
typeof keycloakVersionRange,
KeycloakVersionRange.WithoutAccountTheme | undefined
>
>();
return keycloakVersionRange; return keycloakVersionRange;
} }

View File

@ -6,7 +6,12 @@ import * as fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import type { BuildOptions } from "../../shared/buildOptions"; import type { BuildOptions } from "../../shared/buildOptions";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { type ThemeType, nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir, resources_common } from "../../shared/constants"; import {
type ThemeType,
nameOfTheGlobal,
basenameOfTheKeycloakifyResourcesDir,
resources_common
} from "../../shared/constants";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath"; import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
export type BuildOptionsLike = { export type BuildOptionsLike = {
@ -28,7 +33,15 @@ export function generateFtlFilesCodeFactory(params: {
themeType: ThemeType; themeType: ThemeType;
fieldNames: string[]; fieldNames: string[];
}) { }) {
const { themeName, cssGlobalsToDefine, indexHtmlCode, buildOptions, keycloakifyVersion, themeType, fieldNames } = params; const {
themeName,
cssGlobalsToDefine,
indexHtmlCode,
buildOptions,
keycloakifyVersion,
themeType,
fieldNames
} = params;
const $ = cheerio.load(indexHtmlCode); const $ = cheerio.load(indexHtmlCode);
@ -38,7 +51,10 @@ export function generateFtlFilesCodeFactory(params: {
assert(jsCode !== null); assert(jsCode !== null);
const { fixedJsCode } = replaceImportsInJsCode({ jsCode, buildOptions }); const { fixedJsCode } = replaceImportsInJsCode({
jsCode,
buildOptions
});
$(element).text(fixedJsCode); $(element).text(fixedJsCode);
}); });
@ -72,7 +88,9 @@ export function generateFtlFilesCodeFactory(params: {
$(element).attr( $(element).attr(
attrName, attrName,
href.replace( href.replace(
new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), new RegExp(
`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`
),
`\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/` `\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
) )
); );
@ -98,20 +116,33 @@ export function generateFtlFilesCodeFactory(params: {
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later. //FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
const ftlObjectToJsCodeDeclaringAnObject = fs const ftlObjectToJsCodeDeclaringAnObject = fs
.readFileSync( .readFileSync(
pathJoin(getThisCodebaseRootDirPath(), "src", "bin", "keycloakify", "generateFtl", "ftl_object_to_js_code_declaring_an_object.ftl") pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"keycloakify",
"generateFtl",
"ftl_object_to_js_code_declaring_an_object.ftl"
)
) )
.toString("utf8") .toString("utf8")
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1] .match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1]
.replace("FIELD_NAMES_eKsIY4ZsZ4xeM", fieldNames.map(name => `"${name}"`).join(", ")) .replace(
"FIELD_NAMES_eKsIY4ZsZ4xeM",
fieldNames.map(name => `"${name}"`).join(", ")
)
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion) .replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion)
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion) .replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion)
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType) .replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType)
.replace("KEYCLOAKIFY_THEME_NAME_cXxKd3xEer", themeName) .replace("KEYCLOAKIFY_THEME_NAME_cXxKd3xEer", themeName)
.replace("RESOURCES_COMMON_cLsLsMrtDkpVv", resources_common); .replace("RESOURCES_COMMON_cLsLsMrtDkpVv", resources_common);
const ftlObjectToJsCodeDeclaringAnObjectPlaceholder = '{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }'; const ftlObjectToJsCodeDeclaringAnObjectPlaceholder =
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }';
$("head").prepend(`<script>\nwindow.${nameOfTheGlobal}=${ftlObjectToJsCodeDeclaringAnObjectPlaceholder}</script>`); $("head").prepend(
`<script>\nwindow.${nameOfTheGlobal}=${ftlObjectToJsCodeDeclaringAnObjectPlaceholder}</script>`
);
// Remove part of the document marked as ignored. // Remove part of the document marked as ignored.
{ {
@ -119,7 +150,9 @@ export function generateFtlFilesCodeFactory(params: {
startTags.each((...[, startTag]) => { startTags.each((...[, startTag]) => {
const $startTag = $(startTag); const $startTag = $(startTag);
const $endTag = $startTag.nextAll('meta[name="keycloakify-ignore-end"]').first(); const $endTag = $startTag
.nextAll('meta[name="keycloakify-ignore-end"]')
.first();
if ($endTag.length) { if ($endTag.length) {
let currentNode = $startTag.next(); let currentNode = $startTag.next();
@ -146,9 +179,13 @@ export function generateFtlFilesCodeFactory(params: {
let ftlCode = $.html(); let ftlCode = $.html();
Object.entries({ Object.entries({
[ftlObjectToJsCodeDeclaringAnObjectPlaceholder]: ftlObjectToJsCodeDeclaringAnObject, [ftlObjectToJsCodeDeclaringAnObjectPlaceholder]:
"PAGE_ID_xIgLsPgGId9D8e": pageId ftlObjectToJsCodeDeclaringAnObject,
}).map(([searchValue, replaceValue]) => (ftlCode = ftlCode.replace(searchValue, replaceValue))); PAGE_ID_xIgLsPgGId9D8e: pageId
}).map(
([searchValue, replaceValue]) =>
(ftlCode = ftlCode.replace(searchValue, replaceValue))
);
return { ftlCode }; return { ftlCode };
} }

View File

@ -2,7 +2,11 @@ import * as fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../shared/buildOptions"; import type { BuildOptions } from "../../shared/buildOptions";
import { resources_common, lastKeycloakVersionWithAccountV1, accountV1ThemeName } from "../../shared/constants"; import {
resources_common,
lastKeycloakVersionWithAccountV1,
accountV1ThemeName
} from "../../shared/constants";
import { downloadKeycloakDefaultTheme } from "../../shared/downloadKeycloakDefaultTheme"; import { downloadKeycloakDefaultTheme } from "../../shared/downloadKeycloakDefaultTheme";
import { transformCodebase } from "../../tools/transformCodebase"; import { transformCodebase } from "../../tools/transformCodebase";
import { rmSync } from "../../tools/fs.rmSync"; import { rmSync } from "../../tools/fs.rmSync";
@ -18,32 +22,53 @@ assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike }) { export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike }) {
const { buildOptions } = params; const { buildOptions } = params;
const builtinKeycloakThemeTmpDirPath = pathJoin(buildOptions.cacheDirPath, "bringInAccountV1_tmp"); const builtinKeycloakThemeTmpDirPath = pathJoin(
buildOptions.cacheDirPath,
"bringInAccountV1_tmp"
);
await downloadKeycloakDefaultTheme({ await downloadKeycloakDefaultTheme({
"destDirPath": builtinKeycloakThemeTmpDirPath, destDirPath: builtinKeycloakThemeTmpDirPath,
"keycloakVersion": lastKeycloakVersionWithAccountV1, keycloakVersion: lastKeycloakVersionWithAccountV1,
buildOptions buildOptions
}); });
const accountV1DirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", accountV1ThemeName, "account"); const accountV1DirPath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"theme",
accountV1ThemeName,
"account"
);
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "account"), srcDirPath: pathJoin(builtinKeycloakThemeTmpDirPath, "base", "account"),
"destDirPath": accountV1DirPath destDirPath: accountV1DirPath
}); });
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "account", "resources"), srcDirPath: pathJoin(
"destDirPath": pathJoin(accountV1DirPath, "resources") builtinKeycloakThemeTmpDirPath,
"keycloak",
"account",
"resources"
),
destDirPath: pathJoin(accountV1DirPath, "resources")
}); });
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "common", "resources"), srcDirPath: pathJoin(
"destDirPath": pathJoin(accountV1DirPath, "resources", resources_common) builtinKeycloakThemeTmpDirPath,
"keycloak",
"common",
"resources"
),
destDirPath: pathJoin(accountV1DirPath, "resources", resources_common)
}); });
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true }); rmSync(builtinKeycloakThemeTmpDirPath, { recursive: true });
fs.writeFileSync( fs.writeFileSync(
pathJoin(accountV1DirPath, "theme.properties"), pathJoin(accountV1DirPath, "theme.properties"),
@ -58,8 +83,13 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike
"css/account.css", "css/account.css",
"img/icon-sidebar-active.png", "img/icon-sidebar-active.png",
"img/logo.png", "img/logo.png",
...["patternfly.min.css", "patternfly-additions.min.css", "patternfly-additions.min.css"].map( ...[
fileBasename => `${resources_common}/node_modules/patternfly/dist/css/${fileBasename}` "patternfly.min.css",
"patternfly-additions.min.css",
"patternfly-additions.min.css"
].map(
fileBasename =>
`${resources_common}/node_modules/patternfly/dist/css/${fileBasename}`
) )
].join(" "), ].join(" "),
"", "",

View File

@ -16,8 +16,8 @@ export function generateMessageProperties(params: {
const { themeSrcDirPath, themeType } = params; const { themeSrcDirPath, themeType } = params;
let files = crawl({ let files = crawl({
"dirPath": pathJoin(themeSrcDirPath, themeType), dirPath: pathJoin(themeSrcDirPath, themeType),
"returnedPathsType": "absolute" returnedPathsType: "absolute"
}); });
files = files.filter(file => { files = files.filter(file => {
@ -34,7 +34,9 @@ export function generateMessageProperties(params: {
files = files.sort((a, b) => a.length - b.length); files = files.sort((a, b) => a.length - b.length);
files = files.filter(file => readFileSync(file).toString("utf8").includes("createUseI18n")); files = files.filter(file =>
readFileSync(file).toString("utf8").includes("createUseI18n")
);
if (files.length === 0) { if (files.length === 0) {
return []; return [];
@ -43,18 +45,25 @@ export function generateMessageProperties(params: {
const extraMessages = files const extraMessages = files
.map(file => { .map(file => {
const root = recast.parse(readFileSync(file).toString("utf8"), { const root = recast.parse(readFileSync(file).toString("utf8"), {
"parser": { parser: {
"parse": (code: string) => babelParser.parse(code, { "sourceType": "module", "plugins": ["typescript"] }), parse: (code: string) =>
"generator": babelGenerate, babelParser.parse(code, {
"types": babelTypes sourceType: "module",
plugins: ["typescript"]
}),
generator: babelGenerate,
types: babelTypes
} }
}); });
const codes: string[] = []; const codes: string[] = [];
recast.visit(root, { recast.visit(root, {
"visitCallExpression": function (path) { visitCallExpression: function (path) {
if (path.node.callee.type === "Identifier" && path.node.callee.name === "createUseI18n") { if (
path.node.callee.type === "Identifier" &&
path.node.callee.name === "createUseI18n"
) {
codes.push(babelGenerate(path.node.arguments[0] as any).code); codes.push(babelGenerate(path.node.arguments[0] as any).code);
} }
this.traverse(path); this.traverse(path);
@ -65,7 +74,9 @@ export function generateMessageProperties(params: {
}) })
.flat() .flat()
.map(code => { .map(code => {
let extraMessages: { [languageTag: string]: Record<string, string> } = {}; let extraMessages: {
[languageTag: string]: Record<string, string>;
} = {};
try { try {
eval(`${symToStr({ extraMessages })} = ${code}`); eval(`${symToStr({ extraMessages })} = ${code}`);
@ -140,7 +151,14 @@ export function generateMessageProperties(params: {
out.push({ out.push({
languageTag, languageTag,
"propertiesFileSource": ["# This file was generated by keycloakify", "", "parent=base", "", propertiesFileSource, ""].join("\n") propertiesFileSource: [
"# This file was generated by keycloakify",
"",
"parent=base",
"",
propertiesFileSource,
""
].join("\n")
}); });
} }
@ -157,7 +175,12 @@ function toUTF16(codePoint: number): string {
codePoint -= 0x10000; codePoint -= 0x10000;
let highSurrogate = (codePoint >> 10) + 0xd800; let highSurrogate = (codePoint >> 10) + 0xd800;
let lowSurrogate = (codePoint % 0x400) + 0xdc00; let lowSurrogate = (codePoint % 0x400) + 0xdc00;
return "\\u" + highSurrogate.toString(16).padStart(4, "0") + "\\u" + lowSurrogate.toString(16).padStart(4, "0"); return (
"\\u" +
highSurrogate.toString(16).padStart(4, "0") +
"\\u" +
lowSurrogate.toString(16).padStart(4, "0")
);
} }
} }

View File

@ -12,7 +12,9 @@ export type BuildOptionsLike = BuildOptionsLike_generateSrcMainResourcesForMainT
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function generateSrcMainResources(params: { buildOptions: BuildOptionsLike }): Promise<void> { export async function generateSrcMainResources(params: {
buildOptions: BuildOptionsLike;
}): Promise<void> {
const { buildOptions } = params; const { buildOptions } = params;
const [themeName, ...themeVariantNames] = buildOptions.themeNames; const [themeName, ...themeVariantNames] = buildOptions.themeNames;

View File

@ -24,7 +24,10 @@ import { bringInAccountV1 } from "./bringInAccountV1";
import { getThemeSrcDirPath } from "../../shared/getThemeSrcDirPath"; import { getThemeSrcDirPath } from "../../shared/getThemeSrcDirPath";
import { rmSync } from "../../tools/fs.rmSync"; import { rmSync } from "../../tools/fs.rmSync";
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion"; import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
import { writeMetaInfKeycloakThemes, type MetaInfKeycloakTheme } from "../../shared/metaInfKeycloakThemes"; import {
writeMetaInfKeycloakThemes,
type MetaInfKeycloakTheme
} from "../../shared/metaInfKeycloakThemes";
import { objectEntries } from "tsafe/objectEntries"; import { objectEntries } from "tsafe/objectEntries";
export type BuildOptionsLike = { export type BuildOptionsLike = {
@ -43,22 +46,35 @@ export type BuildOptionsLike = {
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function generateSrcMainResourcesForMainTheme(params: { themeName: string; buildOptions: BuildOptionsLike }): Promise<void> { export async function generateSrcMainResourcesForMainTheme(params: {
themeName: string;
buildOptions: BuildOptionsLike;
}): Promise<void> {
const { themeName, buildOptions } = params; const { themeName, buildOptions } = params;
const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath }); const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
});
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => { const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
const { themeType } = params; const { themeType } = params;
return pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", themeName, themeType); return pathJoin(
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"theme",
themeName,
themeType
);
}; };
const cssGlobalsToDefine: Record<string, string> = {}; const cssGlobalsToDefine: Record<string, string> = {};
const implementedThemeTypes: Record<ThemeType | "email", boolean> = { const implementedThemeTypes: Record<ThemeType | "email", boolean> = {
"login": false, login: false,
"account": false, account: false,
"email": false email: false
}; };
for (const themeType of ["login", "account"] as const) { for (const themeType of ["login", "account"] as const) {
@ -71,18 +87,22 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
const themeTypeDirPath = getThemeTypeDirPath({ themeType }); const themeTypeDirPath = getThemeTypeDirPath({ themeType });
apply_replacers_and_move_to_theme_resources: { apply_replacers_and_move_to_theme_resources: {
const destDirPath = pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir); const destDirPath = pathJoin(
themeTypeDirPath,
"resources",
basenameOfTheKeycloakifyResourcesDir
);
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up. // NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
rmSync(destDirPath, { "recursive": true, "force": true }); rmSync(destDirPath, { recursive: true, force: true });
if (themeType === "account" && implementedThemeTypes.login) { if (themeType === "account" && implementedThemeTypes.login) {
// NOTE: We prevent doing it twice, it has been done for the login theme. // NOTE: We prevent doing it twice, it has been done for the login theme.
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin( srcDirPath: pathJoin(
getThemeTypeDirPath({ getThemeTypeDirPath({
"themeType": "login" themeType: "login"
}), }),
"resources", "resources",
basenameOfTheKeycloakifyResourcesDir basenameOfTheKeycloakifyResourcesDir
@ -94,14 +114,17 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
} }
transformCodebase({ transformCodebase({
"srcDirPath": buildOptions.reactAppBuildDirPath, srcDirPath: buildOptions.reactAppBuildDirPath,
destDirPath, destDirPath,
"transformSourceCode": ({ filePath, sourceCode }) => { transformSourceCode: ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/ //NOTE: Prevent cycles, excludes the folder we generated for debug in public/
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility. // This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
if ( if (
isInside({ isInside({
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources), dirPath: pathJoin(
buildOptions.reactAppBuildDirPath,
keycloak_resources
),
filePath filePath
}) })
) { ) {
@ -109,39 +132,50 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
} }
if (/\.css?$/i.test(filePath)) { if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine: cssGlobalsToDefineForThisFile, fixedCssCode } = replaceImportsInCssCode({ const {
"cssCode": sourceCode.toString("utf8") cssGlobalsToDefine: cssGlobalsToDefineForThisFile,
fixedCssCode
} = replaceImportsInCssCode({
cssCode: sourceCode.toString("utf8")
}); });
Object.entries(cssGlobalsToDefineForThisFile).forEach(([key, value]) => { Object.entries(cssGlobalsToDefineForThisFile).forEach(
cssGlobalsToDefine[key] = value; ([key, value]) => {
}); cssGlobalsToDefine[key] = value;
}
);
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") }; return {
modifiedSourceCode: Buffer.from(fixedCssCode, "utf8")
};
} }
if (/\.js?$/i.test(filePath)) { if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportsInJsCode({ const { fixedJsCode } = replaceImportsInJsCode({
"jsCode": sourceCode.toString("utf8"), jsCode: sourceCode.toString("utf8"),
buildOptions buildOptions
}); });
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") }; return {
modifiedSourceCode: Buffer.from(fixedJsCode, "utf8")
};
} }
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
}); });
} }
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({ const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
themeName, themeName,
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"), indexHtmlCode: fs
.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html"))
.toString("utf8"),
cssGlobalsToDefine, cssGlobalsToDefine,
buildOptions, buildOptions,
"keycloakifyVersion": readThisNpmPackageVersion(), keycloakifyVersion: readThisNpmPackageVersion(),
themeType, themeType,
"fieldNames": readFieldNameUsage({ fieldNames: readFieldNameUsage({
themeSrcDirPath, themeSrcDirPath,
themeType themeType
}) })
@ -163,9 +197,12 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
].forEach(pageId => { ].forEach(pageId => {
const { ftlCode } = generateFtlFilesCode({ pageId }); const { ftlCode } = generateFtlFilesCode({ pageId });
fs.mkdirSync(themeTypeDirPath, { "recursive": true }); fs.mkdirSync(themeTypeDirPath, { recursive: true });
fs.writeFileSync(pathJoin(themeTypeDirPath, pageId), Buffer.from(ftlCode, "utf8")); fs.writeFileSync(
pathJoin(themeTypeDirPath, pageId),
Buffer.from(ftlCode, "utf8")
);
}); });
generateMessageProperties({ generateMessageProperties({
@ -174,15 +211,23 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
}).forEach(({ languageTag, propertiesFileSource }) => { }).forEach(({ languageTag, propertiesFileSource }) => {
const messagesDirPath = pathJoin(themeTypeDirPath, "messages"); const messagesDirPath = pathJoin(themeTypeDirPath, "messages");
fs.mkdirSync(pathJoin(themeTypeDirPath, "messages"), { "recursive": true }); fs.mkdirSync(pathJoin(themeTypeDirPath, "messages"), {
recursive: true
});
const propertiesFilePath = pathJoin(messagesDirPath, `messages_${languageTag}.properties`); const propertiesFilePath = pathJoin(
messagesDirPath,
`messages_${languageTag}.properties`
);
fs.writeFileSync(propertiesFilePath, Buffer.from(propertiesFileSource, "utf8")); fs.writeFileSync(
propertiesFilePath,
Buffer.from(propertiesFileSource, "utf8")
);
}); });
await downloadKeycloakStaticResources({ await downloadKeycloakStaticResources({
"keycloakVersion": (() => { keycloakVersion: (() => {
switch (themeType) { switch (themeType) {
case "account": case "account":
return lastKeycloakVersionWithAccountV1; return lastKeycloakVersionWithAccountV1;
@ -190,7 +235,7 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
return buildOptions.loginThemeResourcesFromKeycloakVersion; return buildOptions.loginThemeResourcesFromKeycloakVersion;
} }
})(), })(),
"themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")), themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")),
themeType, themeType,
buildOptions buildOptions
}); });
@ -225,8 +270,8 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
implementedThemeTypes.email = true; implementedThemeTypes.email = true;
transformCodebase({ transformCodebase({
"srcDirPath": emailThemeSrcDirPath, srcDirPath: emailThemeSrcDirPath,
"destDirPath": getThemeTypeDirPath({ "themeType": "email" }) destDirPath: getThemeTypeDirPath({ themeType: "email" })
}); });
} }
@ -237,24 +282,24 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
} }
{ {
const metaInfKeycloakThemes: MetaInfKeycloakTheme = { "themes": [] }; const metaInfKeycloakThemes: MetaInfKeycloakTheme = { themes: [] };
metaInfKeycloakThemes.themes.push({ metaInfKeycloakThemes.themes.push({
"name": themeName, name: themeName,
"types": objectEntries(implementedThemeTypes) types: objectEntries(implementedThemeTypes)
.filter(([, isImplemented]) => isImplemented) .filter(([, isImplemented]) => isImplemented)
.map(([themeType]) => themeType) .map(([themeType]) => themeType)
}); });
if (implementedThemeTypes.account) { if (implementedThemeTypes.account) {
metaInfKeycloakThemes.themes.push({ metaInfKeycloakThemes.themes.push({
"name": accountV1ThemeName, name: accountV1ThemeName,
"types": ["account"] types: ["account"]
}); });
} }
writeMetaInfKeycloakThemes({ writeMetaInfKeycloakThemes({
"keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath, keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath,
metaInfKeycloakThemes metaInfKeycloakThemes
}); });
} }

View File

@ -1,7 +1,10 @@
import { join as pathJoin, extname as pathExtname, sep as pathSep } from "path"; import { join as pathJoin, extname as pathExtname, sep as pathSep } from "path";
import { transformCodebase } from "../../tools/transformCodebase"; import { transformCodebase } from "../../tools/transformCodebase";
import type { BuildOptions } from "../../shared/buildOptions"; import type { BuildOptions } from "../../shared/buildOptions";
import { readMetaInfKeycloakThemes, writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes"; import {
readMetaInfKeycloakThemes,
writeMetaInfKeycloakThemes
} from "../../shared/metaInfKeycloakThemes";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
export type BuildOptionsLike = { export type BuildOptionsLike = {
@ -10,20 +13,37 @@ export type BuildOptionsLike = {
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export function generateSrcMainResourcesForThemeVariant(params: { themeName: string; themeVariantName: string; buildOptions: BuildOptionsLike }) { export function generateSrcMainResourcesForThemeVariant(params: {
themeName: string;
themeVariantName: string;
buildOptions: BuildOptionsLike;
}) {
const { themeName, themeVariantName, buildOptions } = params; const { themeName, themeVariantName, buildOptions } = params;
const mainThemeDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", themeName); const mainThemeDirPath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"theme",
themeName
);
transformCodebase({ transformCodebase({
"srcDirPath": mainThemeDirPath, srcDirPath: mainThemeDirPath,
"destDirPath": pathJoin(mainThemeDirPath, "..", themeVariantName), destDirPath: pathJoin(mainThemeDirPath, "..", themeVariantName),
"transformSourceCode": ({ fileRelativePath, sourceCode }) => { transformSourceCode: ({ fileRelativePath, sourceCode }) => {
if (pathExtname(fileRelativePath) === ".ftl" && fileRelativePath.split(pathSep).length === 2) { if (
pathExtname(fileRelativePath) === ".ftl" &&
fileRelativePath.split(pathSep).length === 2
) {
const modifiedSourceCode = Buffer.from( const modifiedSourceCode = Buffer.from(
Buffer.from(sourceCode) Buffer.from(sourceCode)
.toString("utf-8") .toString("utf-8")
.replace(`out["themeName"] = "${themeName}";`, `out["themeName"] = "${themeVariantName}";`), .replace(
`out["themeName"] = "${themeName}";`,
`out["themeName"] = "${themeVariantName}";`
),
"utf8" "utf8"
); );
@ -32,25 +52,29 @@ export function generateSrcMainResourcesForThemeVariant(params: { themeName: str
return { modifiedSourceCode }; return { modifiedSourceCode };
} }
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
}); });
{ {
const updatedMetaInfKeycloakThemes = readMetaInfKeycloakThemes({ "keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath }); const updatedMetaInfKeycloakThemes = readMetaInfKeycloakThemes({
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
});
updatedMetaInfKeycloakThemes.themes.push({ updatedMetaInfKeycloakThemes.themes.push({
"name": themeVariantName, name: themeVariantName,
"types": (() => { types: (() => {
const theme = updatedMetaInfKeycloakThemes.themes.find(({ name }) => name === themeName); const theme = updatedMetaInfKeycloakThemes.themes.find(
({ name }) => name === themeName
);
assert(theme !== undefined); assert(theme !== undefined);
return theme.types; return theme.types;
})() })()
}); });
writeMetaInfKeycloakThemes({ writeMetaInfKeycloakThemes({
"keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath, keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath,
"metaInfKeycloakThemes": updatedMetaInfKeycloakThemes metaInfKeycloakThemes: updatedMetaInfKeycloakThemes
}); });
} }
} }

View File

@ -3,17 +3,26 @@ import { id } from "tsafe/id";
import { removeDuplicates } from "evt/tools/reducers/removeDuplicates"; import { removeDuplicates } from "evt/tools/reducers/removeDuplicates";
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { type ThemeType, accountThemePageIds, loginThemePageIds } from "../../shared/constants"; import {
type ThemeType,
accountThemePageIds,
loginThemePageIds
} from "../../shared/constants";
export function readExtraPagesNames(params: { themeSrcDirPath: string; themeType: ThemeType }): string[] { export function readExtraPagesNames(params: {
themeSrcDirPath: string;
themeType: ThemeType;
}): string[] {
const { themeSrcDirPath, themeType } = params; const { themeSrcDirPath, themeType } = params;
const filePaths = crawl({ const filePaths = crawl({
"dirPath": pathJoin(themeSrcDirPath, themeType), dirPath: pathJoin(themeSrcDirPath, themeType),
"returnedPathsType": "absolute" returnedPathsType: "absolute"
}).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath)); }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
const candidateFilePaths = filePaths.filter(filePath => /kcContext\.[^.]+$/.test(filePath)); const candidateFilePaths = filePaths.filter(filePath =>
/kcContext\.[^.]+$/.test(filePath)
);
if (candidateFilePaths.length === 0) { if (candidateFilePaths.length === 0) {
candidateFilePaths.push(...filePaths); candidateFilePaths.push(...filePaths);
@ -24,7 +33,12 @@ export function readExtraPagesNames(params: { themeSrcDirPath: string; themeType
for (const candidateFilPath of candidateFilePaths) { for (const candidateFilPath of candidateFilePaths) {
const rawSourceFile = fs.readFileSync(candidateFilPath).toString("utf8"); const rawSourceFile = fs.readFileSync(candidateFilPath).toString("utf8");
extraPages.push(...Array.from(rawSourceFile.matchAll(/["']?pageId["']?\s*:\s*["']([^.]+.ftl)["']/g), m => m[1])); extraPages.push(
...Array.from(
rawSourceFile.matchAll(/["']?pageId["']?\s*:\s*["']([^.]+.ftl)["']/g),
m => m[1]
)
);
} }
return extraPages.reduce(...removeDuplicates<string>()).filter(pageId => { return extraPages.reduce(...removeDuplicates<string>()).filter(pageId => {

View File

@ -5,13 +5,22 @@ import type { ThemeType } from "../../shared/constants";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath"; import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
/** Assumes the theme type exists */ /** Assumes the theme type exists */
export function readFieldNameUsage(params: { themeSrcDirPath: string; themeType: ThemeType }): string[] { export function readFieldNameUsage(params: {
themeSrcDirPath: string;
themeType: ThemeType;
}): string[] {
const { themeSrcDirPath, themeType } = params; const { themeSrcDirPath, themeType } = params;
const fieldNames = new Set<string>(); const fieldNames = new Set<string>();
for (const srcDirPath of [pathJoin(getThisCodebaseRootDirPath(), "src", themeType), pathJoin(themeSrcDirPath, themeType)]) { for (const srcDirPath of [
const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath)); pathJoin(getThisCodebaseRootDirPath(), "src", themeType),
pathJoin(themeSrcDirPath, themeType)
]) {
const filePaths = crawl({
dirPath: srcDirPath,
returnedPathsType: "absolute"
}).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
for (const filePath of filePaths) { for (const filePath of filePaths) {
const rawSourceFile = fs.readFileSync(filePath).toString("utf8"); const rawSourceFile = fs.readFileSync(filePath).toString("utf8");
@ -20,7 +29,13 @@ export function readFieldNameUsage(params: { themeSrcDirPath: string; themeType:
continue; continue;
} }
for (const functionName of ["printIfExists", "existsError", "get", "exists", "getFirstError"] as const) { for (const functionName of [
"printIfExists",
"existsError",
"get",
"exists",
"getFirstError"
] as const) {
if (!rawSourceFile.includes(functionName)) { if (!rawSourceFile.includes(functionName)) {
continue; continue;
} }
@ -40,9 +55,21 @@ export function readFieldNameUsage(params: { themeSrcDirPath: string; themeType:
return part return part
.split(",") .split(",")
.map(a => a.trim()) .map(a => a.trim())
.filter((...[, i]) => (functionName !== "printIfExists" ? true : i === 0)) .filter((...[, i]) =>
.filter(a => a.startsWith('"') || a.startsWith("'") || a.startsWith("`")) functionName !== "printIfExists" ? true : i === 0
.filter(a => a.endsWith('"') || a.endsWith("'") || a.endsWith("`")) )
.filter(
a =>
a.startsWith('"') ||
a.startsWith("'") ||
a.startsWith("`")
)
.filter(
a =>
a.endsWith('"') ||
a.endsWith("'") ||
a.endsWith("`")
)
.map(a => a.slice(1).slice(0, -1)); .map(a => a.slice(1).slice(0, -1));
}) })
.flat() .flat()

View File

@ -1,5 +1,9 @@
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path"; import {
join as pathJoin,
relative as pathRelative,
basename as pathBasename
} from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { BuildOptions } from "../shared/buildOptions"; import type { BuildOptions } from "../shared/buildOptions";
import { accountV1ThemeName } from "../shared/constants"; import { accountV1ThemeName } from "../shared/constants";
@ -27,7 +31,10 @@ export function generateStartKeycloakTestingContainer(params: {
const themeRelativeDirPath = pathJoin("src", "main", "resources", "theme"); const themeRelativeDirPath = pathJoin("src", "main", "resources", "theme");
fs.writeFileSync( fs.writeFileSync(
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename), pathJoin(
buildOptions.keycloakifyBuildDirPath,
generateStartKeycloakTestingContainer.basename
),
Buffer.from( Buffer.from(
[ [
"#!/usr/bin/env bash", "#!/usr/bin/env bash",
@ -45,9 +52,16 @@ export function generateStartKeycloakTestingContainer(params: {
"$(pwd)", "$(pwd)",
pathRelative(buildOptions.keycloakifyBuildDirPath, jarFilePath) pathRelative(buildOptions.keycloakifyBuildDirPath, jarFilePath)
)}":"/opt/keycloak/providers/${pathBasename(jarFilePath)}" \\`, )}":"/opt/keycloak/providers/${pathBasename(jarFilePath)}" \\`,
[...(doesImplementAccountTheme ? [accountV1ThemeName] : []), ...buildOptions.themeNames].map( [
...(doesImplementAccountTheme ? [accountV1ThemeName] : []),
...buildOptions.themeNames
].map(
themeName => themeName =>
` -v "${pathJoin("$(pwd)", themeRelativeDirPath, themeName).replace(/\\/g, "/")}":"/opt/keycloak/themes/${themeName}":rw \\` ` -v "${pathJoin(
"$(pwd)",
themeRelativeDirPath,
themeName
).replace(/\\/g, "/")}":"/opt/keycloak/themes/${themeName}":rw \\`
), ),
` -it quay.io/keycloak/keycloak:${keycloakVersion} \\`, ` -it quay.io/keycloak/keycloak:${keycloakVersion} \\`,
` start-dev`, ` start-dev`,
@ -55,6 +69,6 @@ export function generateStartKeycloakTestingContainer(params: {
].join("\n"), ].join("\n"),
"utf8" "utf8"
), ),
{ "mode": 0o755 } { mode: 0o755 }
); );
} }

View File

@ -15,7 +15,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
let commandOutput: Buffer | undefined = undefined; let commandOutput: Buffer | undefined = undefined;
try { try {
commandOutput = child_process.execSync("mvn --version", { "stdio": ["ignore", "pipe", "ignore"] }); commandOutput = child_process.execSync("mvn --version", {
stdio: ["ignore", "pipe", "ignore"]
});
} catch {} } catch {}
if (commandOutput?.toString("utf8").includes("Apache Maven")) { if (commandOutput?.toString("utf8").includes("Apache Maven")) {
@ -34,7 +36,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
} }
})(); })();
console.log(`${chalk.red("Apache Maven required.")} Install it with \`${chalk.bold(installationCommand)}\` (for example)`); console.log(
`${chalk.red("Apache Maven required.")} Install it with \`${chalk.bold(
installationCommand
)}\` (for example)`
);
process.exit(1); process.exit(1);
} }
@ -46,7 +52,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log( console.log(
[ [
chalk.cyan(`keycloakify v${readThisNpmPackageVersion()}`), chalk.cyan(`keycloakify v${readThisNpmPackageVersion()}`),
chalk.green(`Building the keycloak theme in .${pathSep}${pathRelative(process.cwd(), buildOptions.keycloakifyBuildDirPath)} ...`) chalk.green(
`Building the keycloak theme in .${pathSep}${pathRelative(
process.cwd(),
buildOptions.keycloakifyBuildDirPath
)} ...`
)
].join(" ") ].join(" ")
); );
@ -54,10 +65,15 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
{ {
if (!fs.existsSync(buildOptions.keycloakifyBuildDirPath)) { if (!fs.existsSync(buildOptions.keycloakifyBuildDirPath)) {
fs.mkdirSync(buildOptions.keycloakifyBuildDirPath, { "recursive": true }); fs.mkdirSync(buildOptions.keycloakifyBuildDirPath, {
recursive: true
});
} }
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8")); fs.writeFileSync(
pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"),
Buffer.from("*", "utf8")
);
} }
await generateSrcMainResources({ buildOptions }); await generateSrcMainResources({ buildOptions });
@ -68,10 +84,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
} }
child_process.execSync("npx vite", { child_process.execSync("npx vite", {
"cwd": buildOptions.reactAppRootDirPath, cwd: buildOptions.reactAppRootDirPath,
"env": { env: {
...process.env, ...process.env,
[vitePluginSubScriptEnvNames.runPostBuildScript]: JSON.stringify(buildOptions) [vitePluginSubScriptEnvNames.runPostBuildScript]:
JSON.stringify(buildOptions)
} }
}); });
} }
@ -84,5 +101,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
await buildJars({ buildOptions }); await buildJars({ buildOptions });
} }
console.log(chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)); console.log(
chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)
);
} }

View File

@ -18,7 +18,15 @@ export function replaceImportsInCssCode(params: { cssCode: string }): {
const cssGlobalsToDefine: Record<string, string> = {}; const cssGlobalsToDefine: Record<string, string> = {};
new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*?/g) ?? []).forEach( new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*?/g) ?? []).forEach(
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match) match =>
(cssGlobalsToDefine[
"url" +
crypto
.createHash("sha256")
.update(match)
.digest("hex")
.substring(0, 15)
] = match)
); );
let fixedCssCode = cssCode; let fixedCssCode = cssCode;
@ -26,26 +34,37 @@ export function replaceImportsInCssCode(params: { cssCode: string }): {
Object.keys(cssGlobalsToDefine).forEach( Object.keys(cssGlobalsToDefine).forEach(
cssVariableName => cssVariableName =>
//NOTE: split/join pattern ~ replace all //NOTE: split/join pattern ~ replace all
(fixedCssCode = fixedCssCode.split(cssGlobalsToDefine[cssVariableName]).join(`var(--${cssVariableName})`)) (fixedCssCode = fixedCssCode
.split(cssGlobalsToDefine[cssVariableName])
.join(`var(--${cssVariableName})`))
); );
return { fixedCssCode, cssGlobalsToDefine }; return { fixedCssCode, cssGlobalsToDefine };
} }
export function generateCssCodeToDefineGlobals(params: { cssGlobalsToDefine: Record<string, string>; buildOptions: BuildOptionsLike }): { export function generateCssCodeToDefineGlobals(params: {
cssGlobalsToDefine: Record<string, string>;
buildOptions: BuildOptionsLike;
}): {
cssCodeToPrependInHead: string; cssCodeToPrependInHead: string;
} { } {
const { cssGlobalsToDefine, buildOptions } = params; const { cssGlobalsToDefine, buildOptions } = params;
return { return {
"cssCodeToPrependInHead": [ cssCodeToPrependInHead: [
":root {", ":root {",
...Object.keys(cssGlobalsToDefine) ...Object.keys(cssGlobalsToDefine)
.map(cssVariableName => .map(cssVariableName =>
[ [
`--${cssVariableName}:`, `--${cssVariableName}:`,
cssGlobalsToDefine[cssVariableName].replace( cssGlobalsToDefine[cssVariableName].replace(
new RegExp(`url\\(${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`, "g"), new RegExp(
`url\\(${(buildOptions.urlPathname ?? "/").replace(
/\//g,
"\\/"
)}`,
"g"
),
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/` `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
) )
].join(" ") ].join(" ")

View File

@ -8,7 +8,10 @@ export type BuildOptionsLike = {
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOptions: BuildOptionsLike }): { export function replaceImportsInInlineCssCode(params: {
cssCode: string;
buildOptions: BuildOptionsLike;
}): {
fixedCssCode: string; fixedCssCode: string;
} { } {
const { cssCode, buildOptions } = params; const { cssCode, buildOptions } = params;
@ -17,7 +20,8 @@ export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOp
buildOptions.urlPathname === undefined buildOptions.urlPathname === undefined
? /url\(["']?\/([^/][^)"']+)["']?\)/g ? /url\(["']?\/([^/][^)"']+)["']?\)/g
: new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"), : new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
(...[, group]) => `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})` (...[, group]) =>
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})`
); );
return { fixedCssCode }; return { fixedCssCode };

View File

@ -13,7 +13,10 @@ export type BuildOptionsLike = {
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export function replaceImportsInJsCode(params: { jsCode: string; buildOptions: BuildOptionsLike }) { export function replaceImportsInJsCode(params: {
jsCode: string;
buildOptions: BuildOptionsLike;
}) {
const { jsCode, buildOptions } = params; const { jsCode, buildOptions } = params;
const { fixedJsCode } = (() => { const { fixedJsCode } = (() => {
@ -22,8 +25,8 @@ export function replaceImportsInJsCode(params: { jsCode: string; buildOptions: B
return replaceImportsInJsCode_vite({ return replaceImportsInJsCode_vite({
jsCode, jsCode,
buildOptions, buildOptions,
"basenameOfAssetsFiles": readAssetsDirSync({ basenameOfAssetsFiles: readAssetsDirSync({
"assetsDirPath": params.buildOptions.assetsDirPath assetsDirPath: params.buildOptions.assetsDirPath
}) })
}); });
case "webpack": case "webpack":

View File

@ -1,4 +1,7 @@
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../shared/constants"; import {
nameOfTheGlobal,
basenameOfTheKeycloakifyResourcesDir
} from "../../../shared/constants";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../../shared/buildOptions"; import type { BuildOptions } from "../../../shared/buildOptions";
import * as nodePath from "path"; import * as nodePath from "path";
@ -20,7 +23,12 @@ export function replaceImportsInJsCode_vite(params: {
}): { }): {
fixedJsCode: string; fixedJsCode: string;
} { } {
const { jsCode, buildOptions, basenameOfAssetsFiles, systemType = nodePath.sep === "/" ? "posix" : "win32" } = params; const {
jsCode,
buildOptions,
basenameOfAssetsFiles,
systemType = nodePath.sep === "/" ? "posix" : "win32"
} = params;
const { relative: pathRelative, sep: pathSep } = nodePath[systemType]; const { relative: pathRelative, sep: pathSep } = nodePath[systemType];
@ -38,22 +46,32 @@ export function replaceImportsInJsCode_vite(params: {
// Replace `Hv=function(e){return"/abcde12345/"+e}` by `Hv=function(e){return"/"+e}` // Replace `Hv=function(e){return"/abcde12345/"+e}` by `Hv=function(e){return"/"+e}`
fixedJsCode = fixedJsCode.replace( fixedJsCode = fixedJsCode.replace(
new RegExp( new RegExp(
`([\\w\\$][\\w\\d\\$]*)=function\\(([\\w\\$][\\w\\d\\$]*)\\)\\{return"${replaceAll(buildOptions.urlPathname, "/", "\\/")}"\\+\\2\\}`, `([\\w\\$][\\w\\d\\$]*)=function\\(([\\w\\$][\\w\\d\\$]*)\\)\\{return"${replaceAll(
buildOptions.urlPathname,
"/",
"\\/"
)}"\\+\\2\\}`,
"g" "g"
), ),
(...[, funcName, paramName]) => `${funcName}=function(${paramName}){return"/"+${paramName}}` (...[, funcName, paramName]) =>
`${funcName}=function(${paramName}){return"/"+${paramName}}`
); );
} }
replace_javascript_relatives_import_paths: { replace_javascript_relatives_import_paths: {
// Example: "assets/ or "foo/bar/" // Example: "assets/ or "foo/bar/"
const staticDir = (() => { const staticDir = (() => {
let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath); let out = pathRelative(
buildOptions.reactAppBuildDirPath,
buildOptions.assetsDirPath
);
out = replaceAll(out, pathSep, "/") + "/"; out = replaceAll(out, pathSep, "/") + "/";
if (out === "/") { if (out === "/") {
throw new Error(`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`); throw new Error(
`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`
);
} }
return out; return out;

View File

@ -1,4 +1,7 @@
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../shared/constants"; import {
nameOfTheGlobal,
basenameOfTheKeycloakifyResourcesDir
} from "../../../shared/constants";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { BuildOptions } from "../../../shared/buildOptions"; import type { BuildOptions } from "../../../shared/buildOptions";
import * as nodePath from "path"; import * as nodePath from "path";
@ -12,10 +15,18 @@ export type BuildOptionsLike = {
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOptions: BuildOptionsLike; systemType?: "posix" | "win32" }): { export function replaceImportsInJsCode_webpack(params: {
jsCode: string;
buildOptions: BuildOptionsLike;
systemType?: "posix" | "win32";
}): {
fixedJsCode: string; fixedJsCode: string;
} { } {
const { jsCode, buildOptions, systemType = nodePath.sep === "/" ? "posix" : "win32" } = params; const {
jsCode,
buildOptions,
systemType = nodePath.sep === "/" ? "posix" : "win32"
} = params;
const { relative: pathRelative, sep: pathSep } = nodePath[systemType]; const { relative: pathRelative, sep: pathSep } = nodePath[systemType];
@ -24,29 +35,51 @@ export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOp
if (buildOptions.urlPathname !== undefined) { if (buildOptions.urlPathname !== undefined) {
// "__esModule",{value:!0})},n.p="/foo-bar/",function(){if("undefined" -> ... n.p="/" ... // "__esModule",{value:!0})},n.p="/foo-bar/",function(){if("undefined" -> ... n.p="/" ...
fixedJsCode = fixedJsCode.replace( fixedJsCode = fixedJsCode.replace(
new RegExp(`,([a-zA-Z]\\.[a-zA-Z])="${replaceAll(buildOptions.urlPathname, "/", "\\/")}",`, "g"), new RegExp(
`,([a-zA-Z]\\.[a-zA-Z])="${replaceAll(
buildOptions.urlPathname,
"/",
"\\/"
)}",`,
"g"
),
(...[, assignTo]) => `,${assignTo}="/",` (...[, assignTo]) => `,${assignTo}="/",`
); );
} }
// Example: "static/ or "foo/bar/" // Example: "static/ or "foo/bar/"
const staticDir = (() => { const staticDir = (() => {
let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath); let out = pathRelative(
buildOptions.reactAppBuildDirPath,
buildOptions.assetsDirPath
);
out = replaceAll(out, pathSep, "/") + "/"; out = replaceAll(out, pathSep, "/") + "/";
if (out === "/") { if (out === "/") {
throw new Error(`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`); throw new Error(
`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`
);
} }
return out; return out;
})(); })();
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [ const getReplaceArgs = (
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=(function\\(([a-z]+)\\){return|([a-z]+)=>)"${staticDir.replace(/\//g, "\\/")}${language}\\/"`, "g"), language: "js" | "css"
): Parameters<typeof String.prototype.replace> => [
new RegExp(
`([a-zA-Z_]+)\\.([a-zA-Z]+)=(function\\(([a-z]+)\\){return|([a-z]+)=>)"${staticDir.replace(
/\//g,
"\\/"
)}${language}\\/"`,
"g"
),
(...[, n, u, matchedFunction, eForFunction]) => { (...[, n, u, matchedFunction, eForFunction]) => {
const isArrowFunction = matchedFunction.includes("=>"); const isArrowFunction = matchedFunction.includes("=>");
const e = isArrowFunction ? matchedFunction.replace("=>", "").trim() : eForFunction; const e = isArrowFunction
? matchedFunction.replace("=>", "").trim()
: eForFunction;
return ` return `
${n}[(function(){ ${n}[(function(){
@ -58,7 +91,9 @@ export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOp
}); });
} }
return "${u}"; return "${u}";
})()] = ${isArrowFunction ? `${e} =>` : `function(${e}) { return `} "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}${language}/"` })()] = ${
isArrowFunction ? `${e} =>` : `function(${e}) { return `
} "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}${language}/"`
.replace(/\s+/g, " ") .replace(/\s+/g, " ")
.trim(); .trim();
} }
@ -68,7 +103,10 @@ export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOp
.replace(...getReplaceArgs("js")) .replace(...getReplaceArgs("js"))
.replace(...getReplaceArgs("css")) .replace(...getReplaceArgs("css"))
.replace( .replace(
new RegExp(`[a-zA-Z]+\\.[a-zA-Z]+\\+"${staticDir.replace(/\//g, "\\/")}`, "g"), new RegExp(
`[a-zA-Z]+\\.[a-zA-Z]+\\+"${staticDir.replace(/\//g, "\\/")}`,
"g"
),
`window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}` `window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}`
); );

View File

@ -10,12 +10,12 @@ export type CliCommandOptions = {
const program = termost<CliCommandOptions>( const program = termost<CliCommandOptions>(
{ {
"name": "keycloakify", name: "keycloakify",
"description": "Keycloakify CLI", description: "Keycloakify CLI",
"version": readThisNpmPackageVersion() version: readThisNpmPackageVersion()
}, },
{ {
"onException": error => { onException: error => {
console.error(error); console.error(error);
process.exit(1); process.exit(1);
} }
@ -25,8 +25,8 @@ const program = termost<CliCommandOptions>(
const optionsKeys: string[] = []; const optionsKeys: string[] = [];
program.option({ program.option({
"key": "reactAppRootDirPath", key: "reactAppRootDirPath",
"name": (() => { name: (() => {
const long = "project"; const long = "project";
const short = "p"; const short = "p";
@ -34,19 +34,25 @@ program.option({
return { long, short }; return { long, short };
})(), })(),
"description": [ description: [
`For monorepos, path to the keycloakify project.`, `For monorepos, path to the keycloakify project.`,
"Example: `npx keycloakify build --project packages/keycloak-theme`", "Example: `npx keycloakify build --project packages/keycloak-theme`",
"https://docs.keycloakify.dev/build-options#project-or-p-cli-option" "https://docs.keycloakify.dev/build-options#project-or-p-cli-option"
].join(" "), ].join(" "),
"defaultValue": undefined defaultValue: undefined
}); });
function skip(_context: any, argv: { options: Record<string, unknown> }) { function skip(_context: any, argv: { options: Record<string, unknown> }) {
const unrecognizedOptionKey = Object.keys(argv.options).find(key => !optionsKeys.includes(key)); const unrecognizedOptionKey = Object.keys(argv.options).find(
key => !optionsKeys.includes(key)
);
if (unrecognizedOptionKey !== undefined) { if (unrecognizedOptionKey !== undefined) {
console.error(`keycloakify: Unrecognized option: ${unrecognizedOptionKey.length === 1 ? "-" : "--"}${unrecognizedOptionKey}`); console.error(
`keycloakify: Unrecognized option: ${
unrecognizedOptionKey.length === 1 ? "-" : "--"
}${unrecognizedOptionKey}`
);
process.exit(1); process.exit(1);
} }
@ -55,12 +61,12 @@ function skip(_context: any, argv: { options: Record<string, unknown> }) {
program program
.command({ .command({
"name": "build", name: "build",
"description": "Build the theme (default subcommand)." description: "Build the theme (default subcommand)."
}) })
.task({ .task({
skip, skip,
"handler": async cliCommandOptions => { handler: async cliCommandOptions => {
const { command } = await import("./keycloakify"); const { command } = await import("./keycloakify");
await command({ cliCommandOptions }); await command({ cliCommandOptions });
@ -73,48 +79,55 @@ program
keycloakVersion: string | undefined; keycloakVersion: string | undefined;
realmJsonFilePath: string | undefined; realmJsonFilePath: string | undefined;
}>({ }>({
"name": "start-keycloak", name: "start-keycloak",
"description": "Spin up a pre configured Docker image of Keycloak to test your theme." description:
"Spin up a pre configured Docker image of Keycloak to test your theme."
}) })
.option({ .option({
"key": "port", key: "port",
"name": (() => { name: (() => {
const name = "port"; const name = "port";
optionsKeys.push(name); optionsKeys.push(name);
return name; return name;
})(), })(),
"description": ["Keycloak server port.", "Example `--port 8085`"].join(" "), description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
"defaultValue": 8080 defaultValue: 8080
}) })
.option({ .option({
"key": "keycloakVersion", key: "keycloakVersion",
"name": (() => { name: (() => {
const name = "keycloak-version"; const name = "keycloak-version";
optionsKeys.push(name); optionsKeys.push(name);
return name; return name;
})(), })(),
"description": ["Use a specific version of Keycloak.", "Example `--keycloak-version 21.1.1`"].join(" "), description: [
"defaultValue": undefined "Use a specific version of Keycloak.",
"Example `--keycloak-version 21.1.1`"
].join(" "),
defaultValue: undefined
}) })
.option({ .option({
"key": "realmJsonFilePath", key: "realmJsonFilePath",
"name": (() => { name: (() => {
const name = "import"; const name = "import";
optionsKeys.push(name); optionsKeys.push(name);
return name; return name;
})(), })(),
"defaultValue": undefined, defaultValue: undefined,
"description": ["Import your own realm configuration file", "Example `--import path/to/myrealm-realm.json`"].join(" ") description: [
"Import your own realm configuration file",
"Example `--import path/to/myrealm-realm.json`"
].join(" ")
}) })
.task({ .task({
skip, skip,
"handler": async cliCommandOptions => { handler: async cliCommandOptions => {
const { command } = await import("./start-keycloak"); const { command } = await import("./start-keycloak");
await command({ cliCommandOptions }); await command({ cliCommandOptions });
@ -123,12 +136,12 @@ program
program program
.command({ .command({
"name": "download-keycloak-default-theme", name: "download-keycloak-default-theme",
"description": "Download the built-in Keycloak theme." description: "Download the built-in Keycloak theme."
}) })
.task({ .task({
skip, skip,
"handler": async cliCommandOptions => { handler: async cliCommandOptions => {
const { command } = await import("./download-keycloak-default-theme"); const { command } = await import("./download-keycloak-default-theme");
await command({ cliCommandOptions }); await command({ cliCommandOptions });
@ -137,12 +150,12 @@ program
program program
.command({ .command({
"name": "eject-page", name: "eject-page",
"description": "Eject a Keycloak page." description: "Eject a Keycloak page."
}) })
.task({ .task({
skip, skip,
"handler": async cliCommandOptions => { handler: async cliCommandOptions => {
const { command } = await import("./eject-page"); const { command } = await import("./eject-page");
await command({ cliCommandOptions }); await command({ cliCommandOptions });
@ -151,12 +164,12 @@ program
program program
.command({ .command({
"name": "initialize-email-theme", name: "initialize-email-theme",
"description": "Initialize an email theme." description: "Initialize an email theme."
}) })
.task({ .task({
skip, skip,
"handler": async cliCommandOptions => { handler: async cliCommandOptions => {
const { command } = await import("./initialize-email-theme"); const { command } = await import("./initialize-email-theme");
await command({ cliCommandOptions }); await command({ cliCommandOptions });
@ -165,12 +178,13 @@ program
program program
.command({ .command({
"name": "copy-keycloak-resources-to-public", name: "copy-keycloak-resources-to-public",
"description": "(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory." description:
"(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
}) })
.task({ .task({
skip, skip,
"handler": async cliCommandOptions => { handler: async cliCommandOptions => {
const { command } = await import("./copy-keycloak-resources-to-public"); const { command } = await import("./copy-keycloak-resources-to-public");
await command({ cliCommandOptions }); await command({ cliCommandOptions });
@ -181,10 +195,17 @@ program
{ {
const [, , ...rest] = process.argv; const [, , ...rest] = process.argv;
if (rest.length === 0 || (rest[0].startsWith("-") && rest[0] !== "--help" && rest[0] !== "-h")) { if (
const { status } = child_process.spawnSync("npx", ["keycloakify", "build", ...rest], { rest.length === 0 ||
"stdio": "inherit" (rest[0].startsWith("-") && rest[0] !== "--help" && rest[0] !== "-h")
}); ) {
const { status } = child_process.spawnSync(
"npx",
["keycloakify", "build", ...rest],
{
stdio: "inherit"
}
);
process.exit(status ?? 1); process.exit(status ?? 1);
} }

View File

@ -1,4 +1,6 @@
export type KeycloakVersionRange = KeycloakVersionRange.WithAccountTheme | KeycloakVersionRange.WithoutAccountTheme; export type KeycloakVersionRange =
| KeycloakVersionRange.WithAccountTheme
| KeycloakVersionRange.WithoutAccountTheme;
export namespace KeycloakVersionRange { export namespace KeycloakVersionRange {
export type WithoutAccountTheme = "21-and-below" | "22-and-above"; export type WithoutAccountTheme = "21-and-below" | "22-and-above";

View File

@ -49,7 +49,9 @@ export type ResolvedViteConfig = {
userProvidedBuildOptions: UserProvidedBuildOptions; userProvidedBuildOptions: UserProvidedBuildOptions;
}; };
export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions }): BuildOptions { export function readBuildOptions(params: {
cliCommandOptions: CliCommandOptions;
}): BuildOptions {
const { cliCommandOptions } = params; const { cliCommandOptions } = params;
const reactAppRootDirPath = (() => { const reactAppRootDirPath = (() => {
@ -58,29 +60,39 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
} }
return getAbsoluteAndInOsFormatPath({ return getAbsoluteAndInOsFormatPath({
"pathIsh": cliCommandOptions.reactAppRootDirPath, pathIsh: cliCommandOptions.reactAppRootDirPath,
"cwd": process.cwd() cwd: process.cwd()
}); });
})(); })();
const { resolvedViteConfig } = (() => { const { resolvedViteConfig } = (() => {
if (fs.readdirSync(reactAppRootDirPath).find(fileBasename => fileBasename.startsWith("vite.config")) === undefined) { if (
return { "resolvedViteConfig": undefined }; fs
.readdirSync(reactAppRootDirPath)
.find(fileBasename => fileBasename.startsWith("vite.config")) ===
undefined
) {
return { resolvedViteConfig: undefined };
} }
const output = child_process const output = child_process
.execSync("npx vite", { .execSync("npx vite", {
"cwd": reactAppRootDirPath, cwd: reactAppRootDirPath,
"env": { env: {
...process.env, ...process.env,
[vitePluginSubScriptEnvNames.resolveViteConfig]: "true" [vitePluginSubScriptEnvNames.resolveViteConfig]: "true"
} }
}) })
.toString("utf8"); .toString("utf8");
assert(output.includes(vitePluginSubScriptEnvNames.resolveViteConfig), "Seems like the Keycloakify's Vite plugin is not installed."); assert(
output.includes(vitePluginSubScriptEnvNames.resolveViteConfig),
"Seems like the Keycloakify's Vite plugin is not installed."
);
const resolvedViteConfigStr = output.split(vitePluginSubScriptEnvNames.resolveViteConfig).reverse()[0]; const resolvedViteConfigStr = output
.split(vitePluginSubScriptEnvNames.resolveViteConfig)
.reverse()[0];
const resolvedViteConfig: ResolvedViteConfig = JSON.parse(resolvedViteConfigStr); const resolvedViteConfig: ResolvedViteConfig = JSON.parse(resolvedViteConfigStr);
@ -92,22 +104,24 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
name: string; name: string;
version?: string; version?: string;
homepage?: string; homepage?: string;
keycloakify?: UserProvidedBuildOptions & { reactAppBuildDirPath?: string }; keycloakify?: UserProvidedBuildOptions & {
reactAppBuildDirPath?: string;
};
}; };
const zParsedPackageJson = z.object({ const zParsedPackageJson = z.object({
"name": z.string(), name: z.string(),
"version": z.string().optional(), version: z.string().optional(),
"homepage": z.string().optional(), homepage: z.string().optional(),
"keycloakify": z keycloakify: z
.object({ .object({
"extraThemeProperties": z.array(z.string()).optional(), extraThemeProperties: z.array(z.string()).optional(),
"artifactId": z.string().optional(), artifactId: z.string().optional(),
"groupId": z.string().optional(), groupId: z.string().optional(),
"loginThemeResourcesFromKeycloakVersion": z.string().optional(), loginThemeResourcesFromKeycloakVersion: z.string().optional(),
"reactAppBuildDirPath": z.string().optional(), reactAppBuildDirPath: z.string().optional(),
"keycloakifyBuildDirPath": z.string().optional(), keycloakifyBuildDirPath: z.string().optional(),
"themeName": z.union([z.string(), z.array(z.string())]).optional() themeName: z.union([z.string(), z.array(z.string())]).optional()
}) })
.optional() .optional()
}); });
@ -119,7 +133,13 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
assert<Expected extends Got ? true : false>(); assert<Expected extends Got ? true : false>();
} }
return zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(reactAppRootDirPath, "package.json")).toString("utf8"))); return zParsedPackageJson.parse(
JSON.parse(
fs
.readFileSync(pathJoin(reactAppRootDirPath, "package.json"))
.toString("utf8")
)
);
})(); })();
const userProvidedBuildOptions: UserProvidedBuildOptions = { const userProvidedBuildOptions: UserProvidedBuildOptions = {
@ -152,8 +172,8 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
if (parsedPackageJson.keycloakify?.reactAppBuildDirPath !== undefined) { if (parsedPackageJson.keycloakify?.reactAppBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({ return getAbsoluteAndInOsFormatPath({
"pathIsh": parsedPackageJson.keycloakify.reactAppBuildDirPath, pathIsh: parsedPackageJson.keycloakify.reactAppBuildDirPath,
"cwd": reactAppRootDirPath cwd: reactAppRootDirPath
}); });
} }
@ -165,15 +185,16 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({ const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({
reactAppRootDirPath, reactAppRootDirPath,
"dependencyExpected": "keycloakify" dependencyExpected: "keycloakify"
}); });
return { return {
"bundler": resolvedViteConfig !== undefined ? "vite" : "webpack", bundler: resolvedViteConfig !== undefined ? "vite" : "webpack",
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0", themeVersion:
process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
themeNames, themeNames,
"extraThemeProperties": userProvidedBuildOptions.extraThemeProperties, extraThemeProperties: userProvidedBuildOptions.extraThemeProperties,
"groupId": (() => { groupId: (() => {
const fallbackGroupId = `${themeNames[0]}.keycloak`; const fallbackGroupId = `${themeNames[0]}.keycloak`;
return ( return (
@ -188,24 +209,30 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
.join(".") ?? fallbackGroupId) + ".keycloak" .join(".") ?? fallbackGroupId) + ".keycloak"
); );
})(), })(),
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? userProvidedBuildOptions.artifactId ?? `${themeNames[0]}-keycloak-theme`, artifactId:
"loginThemeResourcesFromKeycloakVersion": userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4", process.env.KEYCLOAKIFY_ARTIFACT_ID ??
userProvidedBuildOptions.artifactId ??
`${themeNames[0]}-keycloak-theme`,
loginThemeResourcesFromKeycloakVersion:
userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4",
reactAppRootDirPath, reactAppRootDirPath,
reactAppBuildDirPath, reactAppBuildDirPath,
"keycloakifyBuildDirPath": (() => { keycloakifyBuildDirPath: (() => {
if (userProvidedBuildOptions.keycloakifyBuildDirPath !== undefined) { if (userProvidedBuildOptions.keycloakifyBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({ return getAbsoluteAndInOsFormatPath({
"pathIsh": userProvidedBuildOptions.keycloakifyBuildDirPath, pathIsh: userProvidedBuildOptions.keycloakifyBuildDirPath,
"cwd": reactAppRootDirPath cwd: reactAppRootDirPath
}); });
} }
return pathJoin( return pathJoin(
reactAppRootDirPath, reactAppRootDirPath,
resolvedViteConfig?.buildDir === undefined ? "build_keycloak" : `${resolvedViteConfig.buildDir}_keycloak` resolvedViteConfig?.buildDir === undefined
? "build_keycloak"
: `${resolvedViteConfig.buildDir}_keycloak`
); );
})(), })(),
"publicDirPath": (() => { publicDirPath: (() => {
webpack: { webpack: {
if (resolvedViteConfig !== undefined) { if (resolvedViteConfig !== undefined) {
break webpack; break webpack;
@ -213,8 +240,8 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
if (process.env.PUBLIC_DIR_PATH !== undefined) { if (process.env.PUBLIC_DIR_PATH !== undefined) {
return getAbsoluteAndInOsFormatPath({ return getAbsoluteAndInOsFormatPath({
"pathIsh": process.env.PUBLIC_DIR_PATH, pathIsh: process.env.PUBLIC_DIR_PATH,
"cwd": reactAppRootDirPath cwd: reactAppRootDirPath
}); });
} }
@ -223,13 +250,13 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
return pathJoin(reactAppRootDirPath, resolvedViteConfig.publicDir); return pathJoin(reactAppRootDirPath, resolvedViteConfig.publicDir);
})(), })(),
"cacheDirPath": (() => { cacheDirPath: (() => {
const cacheDirPath = pathJoin( const cacheDirPath = pathJoin(
(() => { (() => {
if (process.env.XDG_CACHE_HOME !== undefined) { if (process.env.XDG_CACHE_HOME !== undefined) {
return getAbsoluteAndInOsFormatPath({ return getAbsoluteAndInOsFormatPath({
"pathIsh": process.env.XDG_CACHE_HOME, pathIsh: process.env.XDG_CACHE_HOME,
"cwd": process.cwd() cwd: process.cwd()
}); });
} }
@ -240,7 +267,7 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
return cacheDirPath; return cacheDirPath;
})(), })(),
"urlPathname": (() => { urlPathname: (() => {
webpack: { webpack: {
if (resolvedViteConfig !== undefined) { if (resolvedViteConfig !== undefined) {
break webpack; break webpack;
@ -264,7 +291,7 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
return resolvedViteConfig.urlPathname; return resolvedViteConfig.urlPathname;
})(), })(),
"assetsDirPath": (() => { assetsDirPath: (() => {
webpack: { webpack: {
if (resolvedViteConfig !== undefined) { if (resolvedViteConfig !== undefined) {
break webpack; break webpack;

View File

@ -10,8 +10,8 @@ export const accountV1ThemeName = "account-v1";
export type ThemeType = (typeof themeTypes)[number]; export type ThemeType = (typeof themeTypes)[number];
export const vitePluginSubScriptEnvNames = { export const vitePluginSubScriptEnvNames = {
"runPostBuildScript": "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT", runPostBuildScript: "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT",
"resolveViteConfig": "KEYCLOAKIFY_RESOLVE_VITE_CONFIG" resolveViteConfig: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG"
} as const; } as const;
export const skipBuildJarsEnvName = "KEYCLOAKIFY_SKIP_BUILD_JAR"; export const skipBuildJarsEnvName = "KEYCLOAKIFY_SKIP_BUILD_JAR";

View File

@ -3,7 +3,11 @@ import {
type BuildOptionsLike as BuildOptionsLike_downloadKeycloakStaticResources type BuildOptionsLike as BuildOptionsLike_downloadKeycloakStaticResources
} from "./downloadKeycloakStaticResources"; } from "./downloadKeycloakStaticResources";
import { join as pathJoin, relative as pathRelative } from "path"; import { join as pathJoin, relative as pathRelative } from "path";
import { themeTypes, keycloak_resources, lastKeycloakVersionWithAccountV1 } from "../shared/constants"; import {
themeTypes,
keycloak_resources,
lastKeycloakVersionWithAccountV1
} from "../shared/constants";
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion"; import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import * as fs from "fs"; import * as fs from "fs";
@ -17,7 +21,9 @@ export type BuildOptionsLike = BuildOptionsLike_downloadKeycloakStaticResources
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function copyKeycloakResourcesToPublic(params: { buildOptions: BuildOptionsLike }) { export async function copyKeycloakResourcesToPublic(params: {
buildOptions: BuildOptionsLike;
}) {
const { buildOptions } = params; const { buildOptions } = params;
const destDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources); const destDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources);
@ -27,11 +33,14 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
const keycloakifyBuildinfoRaw = JSON.stringify( const keycloakifyBuildinfoRaw = JSON.stringify(
{ {
destDirPath, destDirPath,
"keycloakifyVersion": readThisNpmPackageVersion(), keycloakifyVersion: readThisNpmPackageVersion(),
"buildOptions": { buildOptions: {
"loginThemeResourcesFromKeycloakVersion": readThisNpmPackageVersion(), loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(),
"cacheDirPath": pathRelative(destDirPath, buildOptions.cacheDirPath), cacheDirPath: pathRelative(destDirPath, buildOptions.cacheDirPath),
"npmWorkspaceRootDirPath": pathRelative(destDirPath, buildOptions.npmWorkspaceRootDirPath) npmWorkspaceRootDirPath: pathRelative(
destDirPath,
buildOptions.npmWorkspaceRootDirPath
)
} }
}, },
null, null,
@ -43,7 +52,9 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
break skip_if_already_done; break skip_if_already_done;
} }
const keycloakifyBuildinfoRaw_previousRun = fs.readFileSync(keycloakifyBuildinfoFilePath).toString("utf8"); const keycloakifyBuildinfoRaw_previousRun = fs
.readFileSync(keycloakifyBuildinfoFilePath)
.toString("utf8");
if (keycloakifyBuildinfoRaw_previousRun !== keycloakifyBuildinfoRaw) { if (keycloakifyBuildinfoRaw_previousRun !== keycloakifyBuildinfoRaw) {
break skip_if_already_done; break skip_if_already_done;
@ -52,15 +63,15 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
return; return;
} }
rmSync(destDirPath, { "force": true, "recursive": true }); rmSync(destDirPath, { force: true, recursive: true });
fs.mkdirSync(destDirPath, { "recursive": true }); fs.mkdirSync(destDirPath, { recursive: true });
fs.writeFileSync(pathJoin(destDirPath, ".gitignore"), Buffer.from("*", "utf8")); fs.writeFileSync(pathJoin(destDirPath, ".gitignore"), Buffer.from("*", "utf8"));
for (const themeType of themeTypes) { for (const themeType of themeTypes) {
await downloadKeycloakStaticResources({ await downloadKeycloakStaticResources({
"keycloakVersion": (() => { keycloakVersion: (() => {
switch (themeType) { switch (themeType) {
case "login": case "login":
return buildOptions.loginThemeResourcesFromKeycloakVersion; return buildOptions.loginThemeResourcesFromKeycloakVersion;
@ -69,7 +80,7 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
} }
})(), })(),
themeType, themeType,
"themeDirPath": destDirPath, themeDirPath: destDirPath,
buildOptions buildOptions
}); });
} }
@ -86,5 +97,8 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
) )
); );
fs.writeFileSync(keycloakifyBuildinfoFilePath, Buffer.from(keycloakifyBuildinfoRaw, "utf8")); fs.writeFileSync(
keycloakifyBuildinfoFilePath,
Buffer.from(keycloakifyBuildinfoRaw, "utf8")
);
} }

View File

@ -28,22 +28,29 @@ export async function downloadAndUnzip(params: {
}; };
buildOptions: BuildOptionsLike; buildOptions: BuildOptionsLike;
}) { }) {
const { url, destDirPath, specificDirsToExtract, preCacheTransform, buildOptions } = params; const { url, destDirPath, specificDirsToExtract, preCacheTransform, buildOptions } =
params;
const { extractDirPath, zipFilePath } = (() => { const { extractDirPath, zipFilePath } = (() => {
const zipFileBasenameWithoutExt = generateFileNameFromURL({ const zipFileBasenameWithoutExt = generateFileNameFromURL({
url, url,
"preCacheTransform": preCacheTransform:
preCacheTransform === undefined preCacheTransform === undefined
? undefined ? undefined
: { : {
"actionCacheId": preCacheTransform.actionCacheId, actionCacheId: preCacheTransform.actionCacheId,
"actionFootprint": preCacheTransform.action.toString() actionFootprint: preCacheTransform.action.toString()
} }
}); });
const zipFilePath = pathJoin(buildOptions.cacheDirPath, `${zipFileBasenameWithoutExt}.zip`); const zipFilePath = pathJoin(
const extractDirPath = pathJoin(buildOptions.cacheDirPath, `tmp_unzip_${zipFileBasenameWithoutExt}`); buildOptions.cacheDirPath,
`${zipFileBasenameWithoutExt}.zip`
);
const extractDirPath = pathJoin(
buildOptions.cacheDirPath,
`tmp_unzip_${zipFileBasenameWithoutExt}`
);
return { zipFilePath, extractDirPath }; return { zipFilePath, extractDirPath };
})(); })();
@ -55,28 +62,30 @@ export async function downloadAndUnzip(params: {
const { response, isFromRemoteCache } = await (async () => { const { response, isFromRemoteCache } = await (async () => {
const proxyFetchOptions = await getProxyFetchOptions({ const proxyFetchOptions = await getProxyFetchOptions({
"npmWorkspaceRootDirPath": buildOptions.npmWorkspaceRootDirPath npmWorkspaceRootDirPath: buildOptions.npmWorkspaceRootDirPath
}); });
const response = await fetch( const response = await fetch(
`https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/${pathBasename(zipFilePath)}`, `https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/${pathBasename(
zipFilePath
)}`,
proxyFetchOptions proxyFetchOptions
); );
if (response.status === 200) { if (response.status === 200) {
return { return {
response, response,
"isFromRemoteCache": true isFromRemoteCache: true
}; };
} }
return { return {
"response": await fetch(url, proxyFetchOptions), response: await fetch(url, proxyFetchOptions),
"isFromRemoteCache": false isFromRemoteCache: false
}; };
})(); })();
await mkdir(pathDirname(zipFilePath), { "recursive": true }); await mkdir(pathDirname(zipFilePath), { recursive: true });
/** /**
* The correct way to fix this is to upgrade node-fetch beyond 3.2.5 * The correct way to fix this is to upgrade node-fetch beyond 3.2.5
@ -102,10 +111,13 @@ export async function downloadAndUnzip(params: {
try { try {
await preCacheTransform?.action({ await preCacheTransform?.action({
"destDirPath": extractDirPath destDirPath: extractDirPath
}); });
} catch (error) { } catch (error) {
await Promise.all([rm(extractDirPath, { "recursive": true }), unlink(zipFilePath)]); await Promise.all([
rm(extractDirPath, { recursive: true }),
unlink(zipFilePath)
]);
throw error; throw error;
} }
@ -114,10 +126,11 @@ export async function downloadAndUnzip(params: {
await zip(extractDirPath, zipFilePath); await zip(extractDirPath, zipFilePath);
await rm(extractDirPath, { "recursive": true }); await rm(extractDirPath, { recursive: true });
upload_to_remote_cache_if_admin: { upload_to_remote_cache_if_admin: {
const githubToken = process.env["KEYCLOAKIFY_ADMIN_GITHUB_PERSONAL_ACCESS_TOKEN"]; const githubToken =
process.env["KEYCLOAKIFY_ADMIN_GITHUB_PERSONAL_ACCESS_TOKEN"];
if (!githubToken) { if (!githubToken) {
break upload_to_remote_cache_if_admin; break upload_to_remote_cache_if_admin;
@ -145,7 +158,9 @@ export async function downloadAndUnzip(params: {
githubToken githubToken
]); ]);
} catch { } catch {
console.log("upload failed, asset probably already exists in remote cache"); console.log(
"upload failed, asset probably already exists in remote cache"
);
} }
} }
} }
@ -153,11 +168,11 @@ export async function downloadAndUnzip(params: {
await unzip(zipFilePath, extractDirPath); await unzip(zipFilePath, extractDirPath);
transformCodebase({ transformCodebase({
"srcDirPath": extractDirPath, srcDirPath: extractDirPath,
"destDirPath": destDirPath destDirPath: destDirPath
}); });
await rm(extractDirPath, { "recursive": true }); await rm(extractDirPath, { recursive: true });
} }
function generateFileNameFromURL(params: { function generateFileNameFromURL(params: {
@ -194,9 +209,15 @@ function generateFileNameFromURL(params: {
} }
// Sanitize actionCacheId the same way as other components // Sanitize actionCacheId the same way as other components
const sanitizedActionCacheId = preCacheTransform.actionCacheId.replace(/[^a-zA-Z0-9-_]/g, "_"); const sanitizedActionCacheId = preCacheTransform.actionCacheId.replace(
/[^a-zA-Z0-9-_]/g,
"_"
);
fileName += `_${sanitizedActionCacheId}_${createHash("sha256").update(preCacheTransform.actionFootprint).digest("hex").substring(0, 5)}`; fileName += `_${sanitizedActionCacheId}_${createHash("sha256")
.update(preCacheTransform.actionFootprint)
.digest("hex")
.substring(0, 5)}`;
} }
return fileName; return fileName;

View File

@ -15,25 +15,38 @@ export type BuildOptionsLike = {
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: string; destDirPath: string; buildOptions: BuildOptionsLike }) { export async function downloadKeycloakDefaultTheme(params: {
keycloakVersion: string;
destDirPath: string;
buildOptions: BuildOptionsLike;
}) {
const { keycloakVersion, destDirPath, buildOptions } = params; const { keycloakVersion, destDirPath, buildOptions } = params;
await downloadAndUnzip({ await downloadAndUnzip({
destDirPath, destDirPath,
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`, url: `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
"specificDirsToExtract": ["", "-community"].map(ext => `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`), specificDirsToExtract: ["", "-community"].map(
ext => `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`
),
buildOptions, buildOptions,
"preCacheTransform": { preCacheTransform: {
"actionCacheId": "npm install and build", actionCacheId: "npm install and build",
"action": async ({ destDirPath }) => { action: async ({ destDirPath }) => {
install_common_node_modules: { install_common_node_modules: {
const commonResourcesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources"); const commonResourcesDirPath = pathJoin(
destDirPath,
"keycloak",
"common",
"resources"
);
if (!fs.existsSync(commonResourcesDirPath)) { if (!fs.existsSync(commonResourcesDirPath)) {
break install_common_node_modules; break install_common_node_modules;
} }
if (!fs.existsSync(pathJoin(commonResourcesDirPath, "package.json"))) { if (
!fs.existsSync(pathJoin(commonResourcesDirPath, "package.json"))
) {
break install_common_node_modules; break install_common_node_modules;
} }
@ -42,36 +55,67 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
} }
child_process.execSync("npm install --omit=dev", { child_process.execSync("npm install --omit=dev", {
"cwd": commonResourcesDirPath, cwd: commonResourcesDirPath,
"stdio": "ignore" stdio: "ignore"
}); });
} }
repatriate_common_resources_from_base_login_theme: { repatriate_common_resources_from_base_login_theme: {
const baseLoginThemeResourceDir = pathJoin(destDirPath, "base", "login", "resources"); const baseLoginThemeResourceDir = pathJoin(
destDirPath,
"base",
"login",
"resources"
);
if (!fs.existsSync(baseLoginThemeResourceDir)) { if (!fs.existsSync(baseLoginThemeResourceDir)) {
break repatriate_common_resources_from_base_login_theme; break repatriate_common_resources_from_base_login_theme;
} }
transformCodebase({ transformCodebase({
"srcDirPath": baseLoginThemeResourceDir, srcDirPath: baseLoginThemeResourceDir,
"destDirPath": pathJoin(destDirPath, "keycloak", "login", "resources") destDirPath: pathJoin(
destDirPath,
"keycloak",
"login",
"resources"
)
}); });
} }
install_and_move_to_common_resources_generated_in_keycloak_v2: { install_and_move_to_common_resources_generated_in_keycloak_v2: {
if (!fs.readFileSync(pathJoin(destDirPath, "keycloak", "login", "theme.properties")).toString("utf8").includes("web_modules")) { if (
!fs
.readFileSync(
pathJoin(
destDirPath,
"keycloak",
"login",
"theme.properties"
)
)
.toString("utf8")
.includes("web_modules")
) {
break install_and_move_to_common_resources_generated_in_keycloak_v2; break install_and_move_to_common_resources_generated_in_keycloak_v2;
} }
const accountV2DirSrcDirPath = pathJoin(destDirPath, "keycloak.v2", "account", "src"); const accountV2DirSrcDirPath = pathJoin(
destDirPath,
"keycloak.v2",
"account",
"src"
);
if (!fs.existsSync(accountV2DirSrcDirPath)) { if (!fs.existsSync(accountV2DirSrcDirPath)) {
break install_and_move_to_common_resources_generated_in_keycloak_v2; break install_and_move_to_common_resources_generated_in_keycloak_v2;
} }
const packageManager = fs.existsSync(pathJoin(accountV2DirSrcDirPath, "pnpm-lock.yaml")) ? "pnpm" : "npm"; const packageManager = fs.existsSync(
pathJoin(accountV2DirSrcDirPath, "pnpm-lock.yaml")
)
? "pnpm"
: "npm";
if (packageManager === "pnpm") { if (packageManager === "pnpm") {
try { try {
@ -82,9 +126,15 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
} }
} }
child_process.execSync(`${packageManager} install`, { "cwd": accountV2DirSrcDirPath, "stdio": "ignore" }); child_process.execSync(`${packageManager} install`, {
cwd: accountV2DirSrcDirPath,
stdio: "ignore"
});
const packageJsonFilePath = pathJoin(accountV2DirSrcDirPath, "package.json"); const packageJsonFilePath = pathJoin(
accountV2DirSrcDirPath,
"package.json"
);
const packageJsonRaw = fs.readFileSync(packageJsonFilePath); const packageJsonRaw = fs.readFileSync(packageJsonFilePath);
@ -94,13 +144,21 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
.replace(`${packageManager} run check-types`, "true") .replace(`${packageManager} run check-types`, "true")
.replace(`${packageManager} run babel`, "true"); .replace(`${packageManager} run babel`, "true");
fs.writeFileSync(packageJsonFilePath, Buffer.from(JSON.stringify(parsedPackageJson, null, 2), "utf8")); fs.writeFileSync(
packageJsonFilePath,
Buffer.from(JSON.stringify(parsedPackageJson, null, 2), "utf8")
);
child_process.execSync(`${packageManager} run build`, { "cwd": accountV2DirSrcDirPath, "stdio": "ignore" }); child_process.execSync(`${packageManager} run build`, {
cwd: accountV2DirSrcDirPath,
stdio: "ignore"
});
fs.writeFileSync(packageJsonFilePath, packageJsonRaw); fs.writeFileSync(packageJsonFilePath, packageJsonRaw);
fs.rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), { "recursive": true }); fs.rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), {
recursive: true
});
} }
remove_keycloak_v2: { remove_keycloak_v2: {
@ -110,12 +168,18 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
break remove_keycloak_v2; break remove_keycloak_v2;
} }
rmSync(keycloakV2DirPath, { "recursive": true }); rmSync(keycloakV2DirPath, { recursive: true });
} }
// Note, this is an optimization for reducing the size of the jar // Note, this is an optimization for reducing the size of the jar
remove_unused_node_modules: { remove_unused_node_modules: {
const nodeModuleDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "node_modules"); const nodeModuleDirPath = pathJoin(
destDirPath,
"keycloak",
"common",
"resources",
"node_modules"
);
if (!fs.existsSync(nodeModuleDirPath)) { if (!fs.existsSync(nodeModuleDirPath)) {
break remove_unused_node_modules; break remove_unused_node_modules;
@ -167,18 +231,26 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
]; ];
transformCodebase({ transformCodebase({
"srcDirPath": nodeModuleDirPath, srcDirPath: nodeModuleDirPath,
"destDirPath": nodeModuleDirPath, destDirPath: nodeModuleDirPath,
"transformSourceCode": ({ sourceCode, fileRelativePath }) => { transformSourceCode: ({ sourceCode, fileRelativePath }) => {
if (fileRelativePath.endsWith(".map")) { if (fileRelativePath.endsWith(".map")) {
return undefined; return undefined;
} }
if (toDeletePerfixes.find(prefix => fileRelativePath.startsWith(prefix)) !== undefined) { if (
toDeletePerfixes.find(prefix =>
fileRelativePath.startsWith(prefix)
) !== undefined
) {
return undefined; return undefined;
} }
if (fileRelativePath.startsWith(pathJoin("patternfly", "dist", "fonts"))) { if (
fileRelativePath.startsWith(
pathJoin("patternfly", "dist", "fonts")
)
) {
if ( if (
!fileRelativePath.endsWith(".woff2") && !fileRelativePath.endsWith(".woff2") &&
!fileRelativePath.endsWith(".woff") && !fileRelativePath.endsWith(".woff") &&
@ -188,34 +260,50 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
} }
} }
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
}); });
} }
// Just like node_modules // Just like node_modules
remove_unused_lib: { remove_unused_lib: {
const libDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "lib"); const libDirPath = pathJoin(
destDirPath,
"keycloak",
"common",
"resources",
"lib"
);
if (!fs.existsSync(libDirPath)) { if (!fs.existsSync(libDirPath)) {
break remove_unused_lib; break remove_unused_lib;
} }
const toDeletePerfixes = ["ui-ace", "filesaver", "fileupload", "angular", "ui-ace"]; const toDeletePerfixes = [
"ui-ace",
"filesaver",
"fileupload",
"angular",
"ui-ace"
];
transformCodebase({ transformCodebase({
"srcDirPath": libDirPath, srcDirPath: libDirPath,
"destDirPath": libDirPath, destDirPath: libDirPath,
"transformSourceCode": ({ sourceCode, fileRelativePath }) => { transformSourceCode: ({ sourceCode, fileRelativePath }) => {
if (fileRelativePath.endsWith(".map")) { if (fileRelativePath.endsWith(".map")) {
return undefined; return undefined;
} }
if (toDeletePerfixes.find(prefix => fileRelativePath.startsWith(prefix)) !== undefined) { if (
toDeletePerfixes.find(prefix =>
fileRelativePath.startsWith(prefix)
) !== undefined
) {
return undefined; return undefined;
} }
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
}); });
} }
@ -226,34 +314,61 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
} }
{ {
const accountCssFilePath = pathJoin(destDirPath, "keycloak", "account", "resources", "css", "account.css"); const accountCssFilePath = pathJoin(
destDirPath,
"keycloak",
"account",
"resources",
"css",
"account.css"
);
fs.writeFileSync( fs.writeFileSync(
accountCssFilePath, accountCssFilePath,
Buffer.from(fs.readFileSync(accountCssFilePath).toString("utf8").replace("top: -34px;", "top: -34px !important;"), "utf8") Buffer.from(
fs
.readFileSync(accountCssFilePath)
.toString("utf8")
.replace("top: -34px;", "top: -34px !important;"),
"utf8"
)
); );
} }
// Note, this is an optimization for reducing the size of the jar, // Note, this is an optimization for reducing the size of the jar,
// For this version we know exactly which resources are used. // For this version we know exactly which resources are used.
{ {
const nodeModulesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "node_modules"); const nodeModulesDirPath = pathJoin(
destDirPath,
"keycloak",
"common",
"resources",
"node_modules"
);
const toKeepPrefixes = [ const toKeepPrefixes = [
...["patternfly.min.css", "patternfly-additions.min.css", "patternfly-additions.min.css"].map(fileBasename => ...[
"patternfly.min.css",
"patternfly-additions.min.css",
"patternfly-additions.min.css"
].map(fileBasename =>
pathJoin("patternfly", "dist", "css", fileBasename) pathJoin("patternfly", "dist", "css", fileBasename)
), ),
pathJoin("patternfly", "dist", "fonts") pathJoin("patternfly", "dist", "fonts")
]; ];
transformCodebase({ transformCodebase({
"srcDirPath": nodeModulesDirPath, srcDirPath: nodeModulesDirPath,
"destDirPath": nodeModulesDirPath, destDirPath: nodeModulesDirPath,
"transformSourceCode": ({ sourceCode, fileRelativePath }) => { transformSourceCode: ({ sourceCode, fileRelativePath }) => {
if (toKeepPrefixes.find(prefix => fileRelativePath.startsWith(prefix)) === undefined) { if (
toKeepPrefixes.find(prefix =>
fileRelativePath.startsWith(prefix)
) === undefined
) {
return undefined; return undefined;
} }
return { "modifiedSourceCode": sourceCode }; return { modifiedSourceCode: sourceCode };
} }
}); });
} }

View File

@ -24,26 +24,30 @@ export async function downloadKeycloakStaticResources(params: {
const tmpDirPath = pathJoin( const tmpDirPath = pathJoin(
buildOptions.cacheDirPath, buildOptions.cacheDirPath,
`downloadKeycloakStaticResources_tmp_${crypto.createHash("sha256").update(`${themeType}-${keycloakVersion}`).digest("hex").slice(0, 8)}` `downloadKeycloakStaticResources_tmp_${crypto
.createHash("sha256")
.update(`${themeType}-${keycloakVersion}`)
.digest("hex")
.slice(0, 8)}`
); );
await downloadKeycloakDefaultTheme({ await downloadKeycloakDefaultTheme({
keycloakVersion, keycloakVersion,
"destDirPath": tmpDirPath, destDirPath: tmpDirPath,
buildOptions buildOptions
}); });
const resourcesPath = pathJoin(themeDirPath, themeType, "resources"); const resourcesPath = pathJoin(themeDirPath, themeType, "resources");
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", themeType, "resources"), srcDirPath: pathJoin(tmpDirPath, "keycloak", themeType, "resources"),
"destDirPath": resourcesPath destDirPath: resourcesPath
}); });
transformCodebase({ transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"), srcDirPath: pathJoin(tmpDirPath, "keycloak", "common", "resources"),
"destDirPath": pathJoin(resourcesPath, resources_common) destDirPath: pathJoin(resourcesPath, resources_common)
}); });
rmSync(tmpDirPath, { "recursive": true }); rmSync(tmpDirPath, { recursive: true });
} }

View File

@ -1,6 +1,8 @@
import type { KeycloakVersionRange } from "./KeycloakVersionRange"; import type { KeycloakVersionRange } from "./KeycloakVersionRange";
export function getJarFileBasename(params: { keycloakVersionRange: KeycloakVersionRange }) { export function getJarFileBasename(params: {
keycloakVersionRange: KeycloakVersionRange;
}) {
const { keycloakVersionRange } = params; const { keycloakVersionRange } = params;
const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`; const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`;

View File

@ -12,7 +12,10 @@ export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
const srcDirPath = pathJoin(reactAppRootDirPath, "src"); const srcDirPath = pathJoin(reactAppRootDirPath, "src");
const themeSrcDirPath: string | undefined = crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" }) const themeSrcDirPath: string | undefined = crawl({
dirPath: srcDirPath,
returnedPathsType: "relative to dirPath"
})
.map(fileRelativePath => { .map(fileRelativePath => {
for (const themeSrcDirBasename of themeSrcDirBasenames) { for (const themeSrcDirBasename of themeSrcDirBasenames) {
const split = fileRelativePath.split(themeSrcDirBasename); const split = fileRelativePath.split(themeSrcDirBasename);
@ -32,7 +35,7 @@ export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
if (!fs.existsSync(pathJoin(srcDirPath, themeType))) { if (!fs.existsSync(pathJoin(srcDirPath, themeType))) {
continue; continue;
} }
return { "themeSrcDirPath": srcDirPath }; return { themeSrcDirPath: srcDirPath };
} }
console.error( console.error(

View File

@ -6,29 +6,56 @@ export type MetaInfKeycloakTheme = {
themes: { name: string; types: (ThemeType | "email")[] }[]; themes: { name: string; types: (ThemeType | "email")[] }[];
}; };
export function getMetaInfKeycloakThemesJsonFilePath(params: { keycloakifyBuildDirPath: string }) { export function getMetaInfKeycloakThemesJsonFilePath(params: {
keycloakifyBuildDirPath: string;
}) {
const { keycloakifyBuildDirPath } = params; const { keycloakifyBuildDirPath } = params;
return pathJoin(keycloakifyBuildDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json"); return pathJoin(
keycloakifyBuildDirPath,
"src",
"main",
"resources",
"META-INF",
"keycloak-themes.json"
);
} }
export function readMetaInfKeycloakThemes(params: { keycloakifyBuildDirPath: string }): MetaInfKeycloakTheme { export function readMetaInfKeycloakThemes(params: {
keycloakifyBuildDirPath: string;
}): MetaInfKeycloakTheme {
const { keycloakifyBuildDirPath } = params; const { keycloakifyBuildDirPath } = params;
return JSON.parse(fs.readFileSync(getMetaInfKeycloakThemesJsonFilePath({ keycloakifyBuildDirPath })).toString("utf8")) as MetaInfKeycloakTheme; return JSON.parse(
fs
.readFileSync(
getMetaInfKeycloakThemesJsonFilePath({
keycloakifyBuildDirPath
})
)
.toString("utf8")
) as MetaInfKeycloakTheme;
} }
export function writeMetaInfKeycloakThemes(params: { keycloakifyBuildDirPath: string; metaInfKeycloakThemes: MetaInfKeycloakTheme }) { export function writeMetaInfKeycloakThemes(params: {
keycloakifyBuildDirPath: string;
metaInfKeycloakThemes: MetaInfKeycloakTheme;
}) {
const { keycloakifyBuildDirPath, metaInfKeycloakThemes } = params; const { keycloakifyBuildDirPath, metaInfKeycloakThemes } = params;
const metaInfKeycloakThemesJsonPath = getMetaInfKeycloakThemesJsonFilePath({ keycloakifyBuildDirPath }); const metaInfKeycloakThemesJsonPath = getMetaInfKeycloakThemesJsonFilePath({
keycloakifyBuildDirPath
});
{ {
const dirPath = pathDirname(metaInfKeycloakThemesJsonPath); const dirPath = pathDirname(metaInfKeycloakThemesJsonPath);
if (!fs.existsSync(dirPath)) { if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { "recursive": true }); fs.mkdirSync(dirPath, { recursive: true });
} }
} }
fs.writeFileSync(metaInfKeycloakThemesJsonPath, Buffer.from(JSON.stringify(metaInfKeycloakThemes, null, 2), "utf8")); fs.writeFileSync(
metaInfKeycloakThemesJsonPath,
Buffer.from(JSON.stringify(metaInfKeycloakThemes, null, 2), "utf8")
);
} }

View File

@ -7,19 +7,26 @@ import * as fs from "fs";
import type { ReturnType } from "tsafe"; import type { ReturnType } from "tsafe";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
export async function promptKeycloakVersion(params: { startingFromMajor: number | undefined; cacheDirPath: string }) { export async function promptKeycloakVersion(params: {
startingFromMajor: number | undefined;
cacheDirPath: string;
}) {
const { startingFromMajor, cacheDirPath } = params; const { startingFromMajor, cacheDirPath } = params;
const { getLatestsSemVersionedTag } = (() => { const { getLatestsSemVersionedTag } = (() => {
const { octokit } = (() => { const { octokit } = (() => {
const githubToken = process.env.GITHUB_TOKEN; const githubToken = process.env.GITHUB_TOKEN;
const octokit = new Octokit(githubToken === undefined ? undefined : { "auth": githubToken }); const octokit = new Octokit(
githubToken === undefined ? undefined : { auth: githubToken }
);
return { octokit }; return { octokit };
})(); })();
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({ octokit }); const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({
octokit
});
return { getLatestsSemVersionedTag }; return { getLatestsSemVersionedTag };
})(); })();
@ -39,7 +46,9 @@ export async function promptKeycloakVersion(params: { startingFromMajor: number
break use_cache; break use_cache;
} }
const cache: Cache = JSON.parse(fs.readFileSync(cacheFilePath).toString("utf8")); const cache: Cache = JSON.parse(
fs.readFileSync(cacheFilePath).toString("utf8")
);
if (Date.now() - cache.time > 3_600_000) { if (Date.now() - cache.time > 3_600_000) {
fs.unlinkSync(cacheFilePath); fs.unlinkSync(cacheFilePath);
@ -50,16 +59,16 @@ export async function promptKeycloakVersion(params: { startingFromMajor: number
} }
const semVersionedTags = await getLatestsSemVersionedTag({ const semVersionedTags = await getLatestsSemVersionedTag({
"count": 50, count: 50,
"owner": "keycloak", owner: "keycloak",
"repo": "keycloak" repo: "keycloak"
}); });
{ {
const dirPath = pathDirname(cacheFilePath); const dirPath = pathDirname(cacheFilePath);
if (!fs.existsSync(dirPath)) { if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { "recursive": true }); fs.mkdirSync(dirPath, { recursive: true });
} }
} }
@ -67,7 +76,7 @@ export async function promptKeycloakVersion(params: { startingFromMajor: number
cacheFilePath, cacheFilePath,
JSON.stringify( JSON.stringify(
id<Cache>({ id<Cache>({
"time": Date.now(), time: Date.now(),
semVersionedTags semVersionedTags
}), }),
null, null,
@ -79,23 +88,33 @@ export async function promptKeycloakVersion(params: { startingFromMajor: number
})(); })();
semVersionedTags.forEach(semVersionedTag => { semVersionedTags.forEach(semVersionedTag => {
if (startingFromMajor !== undefined && semVersionedTag.version.major < startingFromMajor) { if (
startingFromMajor !== undefined &&
semVersionedTag.version.major < startingFromMajor
) {
return; return;
} }
const currentSemVersionedTag = semVersionedTagByMajor.get(semVersionedTag.version.major); const currentSemVersionedTag = semVersionedTagByMajor.get(
semVersionedTag.version.major
);
if (currentSemVersionedTag !== undefined && SemVer.compare(semVersionedTag.version, currentSemVersionedTag.version) === -1) { if (
currentSemVersionedTag !== undefined &&
SemVer.compare(semVersionedTag.version, currentSemVersionedTag.version) === -1
) {
return; return;
} }
semVersionedTagByMajor.set(semVersionedTag.version.major, semVersionedTag); semVersionedTagByMajor.set(semVersionedTag.version.major, semVersionedTag);
}); });
const lastMajorVersions = Array.from(semVersionedTagByMajor.values()).map(({ tag }) => tag); const lastMajorVersions = Array.from(semVersionedTagByMajor.values()).map(
({ tag }) => tag
);
const { value } = await cliSelect<string>({ const { value } = await cliSelect<string>({
"values": lastMajorVersions values: lastMajorVersions
}).catch(() => { }).catch(() => {
process.exit(-1); process.exit(-1);
}); });

View File

@ -407,7 +407,11 @@
"otpPolicyLookAheadWindow": 1, "otpPolicyLookAheadWindow": 1,
"otpPolicyPeriod": 30, "otpPolicyPeriod": 30,
"otpPolicyCodeReusable": false, "otpPolicyCodeReusable": false,
"otpSupportedApplications": ["totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName"], "otpSupportedApplications": [
"totpAppFreeOTPName",
"totpAppGoogleName",
"totpAppMicrosoftAuthenticatorName"
],
"localizationTexts": {}, "localizationTexts": {},
"webAuthnPolicyRpEntityName": "keycloak", "webAuthnPolicyRpEntityName": "keycloak",
"webAuthnPolicySignatureAlgorithms": ["ES256"], "webAuthnPolicySignatureAlgorithms": ["ES256"],
@ -520,7 +524,12 @@
"fullScopeAllowed": false, "fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] "optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}, },
{ {
"id": "bb874a19-9a92-4504-a7c0-d32ef6f3d266", "id": "bb874a19-9a92-4504-a7c0-d32ef6f3d266",
@ -562,7 +571,12 @@
} }
], ],
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] "optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}, },
{ {
"id": "e19ed660-ca30-4266-be25-7ac226994f0f", "id": "e19ed660-ca30-4266-be25-7ac226994f0f",
@ -589,7 +603,12 @@
"fullScopeAllowed": false, "fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] "optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}, },
{ {
"id": "2c000747-ea5b-4226-82a3-1fc32bc8a079", "id": "2c000747-ea5b-4226-82a3-1fc32bc8a079",
@ -616,7 +635,12 @@
"fullScopeAllowed": false, "fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] "optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}, },
{ {
"id": "5dfb0d1a-2ab1-480f-8bab-81502f762ac0", "id": "5dfb0d1a-2ab1-480f-8bab-81502f762ac0",
@ -761,7 +785,12 @@
} }
], ],
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] "optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}, },
{ {
"id": "8efd01a0-40cd-487d-818b-fa2b096170e0", "id": "8efd01a0-40cd-487d-818b-fa2b096170e0",
@ -788,7 +817,12 @@
"fullScopeAllowed": false, "fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] "optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
}, },
{ {
"id": "199b2e1b-2cdd-4d8b-8816-4a5c79741658", "id": "199b2e1b-2cdd-4d8b-8816-4a5c79741658",
@ -838,7 +872,12 @@
} }
], ],
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] "optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
]
} }
], ],
"clientScopes": [ "clientScopes": [
@ -1379,8 +1418,20 @@
] ]
} }
], ],
"defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins", "acr"], "defaultDefaultClientScopes": [
"defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"], "role_list",
"profile",
"email",
"roles",
"web-origins",
"acr"
],
"defaultOptionalClientScopes": [
"offline_access",
"address",
"phone",
"microprofile-jwt"
],
"browserSecurityHeaders": { "browserSecurityHeaders": {
"contentSecurityPolicyReportOnly": "", "contentSecurityPolicyReportOnly": "",
"xContentTypeOptions": "nosniff", "xContentTypeOptions": "nosniff",
@ -1575,7 +1626,19 @@
] ]
}, },
"internationalizationEnabled": true, "internationalizationEnabled": true,
"supportedLocales": ["ar", "de", "fi", "el", "ja", "en", "fa", "it", "fr", "ca", "es"], "supportedLocales": [
"ar",
"de",
"fi",
"el",
"ja",
"en",
"fa",
"it",
"fr",
"ca",
"es"
],
"defaultLocale": "en", "defaultLocale": "en",
"authenticationFlows": [ "authenticationFlows": [
{ {

View File

@ -2,13 +2,22 @@ import { readBuildOptions } from "../shared/buildOptions";
import type { CliCommandOptions as CliCommandOptions_common } from "../main"; import type { CliCommandOptions as CliCommandOptions_common } from "../main";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion"; import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes"; import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes";
import { accountV1ThemeName, skipBuildJarsEnvName, containerName } from "../shared/constants"; import {
accountV1ThemeName,
skipBuildJarsEnvName,
containerName
} from "../shared/constants";
import { SemVer } from "../tools/SemVer"; import { SemVer } from "../tools/SemVer";
import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange"; import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange";
import { getJarFileBasename } from "../shared/getJarFileBasename"; import { getJarFileBasename } from "../shared/getJarFileBasename";
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, sep as pathSep, posix as pathPosix } from "path"; import {
join as pathJoin,
relative as pathRelative,
sep as pathSep,
posix as pathPosix
} from "path";
import * as child_process from "child_process"; import * as child_process from "child_process";
import chalk from "chalk"; import chalk from "chalk";
import chokidar from "chokidar"; import chokidar from "chokidar";
@ -30,7 +39,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
let commandOutput: Buffer | undefined = undefined; let commandOutput: Buffer | undefined = undefined;
try { try {
commandOutput = child_process.execSync("docker --version", { "stdio": ["ignore", "pipe", "ignore"] }); commandOutput = child_process.execSync("docker --version", {
stdio: ["ignore", "pipe", "ignore"]
});
} catch {} } catch {}
if (commandOutput?.toString("utf8").includes("Docker")) { if (commandOutput?.toString("utf8").includes("Docker")) {
@ -40,7 +51,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log( console.log(
[ [
`${chalk.red("Docker required.")}`, `${chalk.red("Docker required.")}`,
`Install it with Docker Desktop: ${chalk.bold.underline("https://www.docker.com/products/docker-desktop/")}`, `Install it with Docker Desktop: ${chalk.bold.underline(
"https://www.docker.com/products/docker-desktop/"
)}`,
`(or any other way)` `(or any other way)`
].join(" ") ].join(" ")
); );
@ -52,7 +65,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
let isDockerRunning: boolean; let isDockerRunning: boolean;
try { try {
child_process.execSync("docker info", { "stdio": "ignore" }); child_process.execSync("docker info", { stdio: "ignore" });
isDockerRunning = true; isDockerRunning = true;
} catch { } catch {
isDockerRunning = false; isDockerRunning = false;
@ -62,7 +75,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
break exit_if_docker_not_running; break exit_if_docker_not_running;
} }
console.log([`${chalk.red("Docker daemon is not running.")}`, `Please start Docker Desktop and try again.`].join(" ")); console.log(
[
`${chalk.red("Docker daemon is not running.")}`,
`Please start Docker Desktop and try again.`
].join(" ")
);
process.exit(1); process.exit(1);
} }
@ -77,51 +95,60 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
} }
console.log( console.log(
[`${chalk.red("The theme has not been built.")}`, `Please run ${chalk.bold("npx vite && npx keycloakify build")} first.`].join(" ") [
`${chalk.red("The theme has not been built.")}`,
`Please run ${chalk.bold("npx vite && npx keycloakify build")} first.`
].join(" ")
); );
process.exit(1); process.exit(1);
} }
const metaInfKeycloakThemes = readMetaInfKeycloakThemes({ const metaInfKeycloakThemes = readMetaInfKeycloakThemes({
"keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
}); });
const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(({ name }) => name === accountV1ThemeName); const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(
({ name }) => name === accountV1ThemeName
);
const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } = await (async function getKeycloakMajor(): Promise<{ const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } =
keycloakVersion: string; await (async function getKeycloakMajor(): Promise<{
keycloakMajorNumber: number; keycloakVersion: string;
}> { keycloakMajorNumber: number;
if (cliCommandOptions.keycloakVersion !== undefined) { }> {
return { if (cliCommandOptions.keycloakVersion !== undefined) {
"keycloakVersion": cliCommandOptions.keycloakVersion, return {
"keycloakMajorNumber": SemVer.parse(cliCommandOptions.keycloakVersion).major keycloakVersion: cliCommandOptions.keycloakVersion,
}; keycloakMajorNumber: SemVer.parse(cliCommandOptions.keycloakVersion)
} .major
};
}
console.log(chalk.cyan("On which version of Keycloak do you want to test your theme?"));
const { keycloakVersion } = await promptKeycloakVersion({
"startingFromMajor": 17,
"cacheDirPath": buildOptions.cacheDirPath
});
console.log(`${keycloakVersion}`);
const keycloakMajorNumber = SemVer.parse(keycloakVersion).major;
if (doesImplementAccountTheme && keycloakMajorNumber === 22) {
console.log( console.log(
[ chalk.cyan("On which version of Keycloak do you want to test your theme?")
"Unfortunately, Keycloakify themes that implements an account theme do not work on Keycloak 22",
"Please select any other Keycloak version"
].join(" ")
); );
return getKeycloakMajor();
}
return { keycloakVersion, keycloakMajorNumber }; const { keycloakVersion } = await promptKeycloakVersion({
})(); startingFromMajor: 17,
cacheDirPath: buildOptions.cacheDirPath
});
console.log(`${keycloakVersion}`);
const keycloakMajorNumber = SemVer.parse(keycloakVersion).major;
if (doesImplementAccountTheme && keycloakMajorNumber === 22) {
console.log(
[
"Unfortunately, Keycloakify themes that implements an account theme do not work on Keycloak 22",
"Please select any other Keycloak version"
].join(" ")
);
return getKeycloakMajor();
}
return { keycloakVersion, keycloakMajorNumber };
})();
const keycloakVersionRange: KeycloakVersionRange = (() => { const keycloakVersionRange: KeycloakVersionRange = (() => {
if (doesImplementAccountTheme) { if (doesImplementAccountTheme) {
@ -139,7 +166,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
return "24-and-above" as const; return "24-and-above" as const;
})(); })();
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithAccountTheme>>(); assert<
Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithAccountTheme>
>();
return keycloakVersionRange; return keycloakVersionRange;
} else { } else {
@ -151,7 +180,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
return "22-and-above" as const; return "22-and-above" as const;
})(); })();
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithoutAccountTheme>>(); assert<
Equals<
typeof keycloakVersionRange,
KeycloakVersionRange.WithoutAccountTheme
>
>();
return keycloakVersionRange; return keycloakVersionRange;
} }
@ -163,7 +197,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const mountTargets = buildOptions.themeNames const mountTargets = buildOptions.themeNames
.map(themeName => { .map(themeName => {
const themeEntry = metaInfKeycloakThemes.themes.find(({ name }) => name === themeName); const themeEntry = metaInfKeycloakThemes.themes.find(
({ name }) => name === themeName
);
assert(themeEntry !== undefined); assert(themeEntry !== undefined);
@ -181,10 +217,24 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
return fs return fs
.readdirSync(localPathDirname) .readdirSync(localPathDirname)
.filter(fileOrDirectoryBasename => !fileOrDirectoryBasename.endsWith(".properties")) .filter(
fileOrDirectoryBasename =>
!fileOrDirectoryBasename.endsWith(".properties")
)
.map(fileOrDirectoryBasename => ({ .map(fileOrDirectoryBasename => ({
"localPath": pathJoin(localPathDirname, fileOrDirectoryBasename), localPath: pathJoin(
"containerPath": pathPosix.join("/", "opt", "keycloak", "themes", themeName, themeType, fileOrDirectoryBasename) localPathDirname,
fileOrDirectoryBasename
),
containerPath: pathPosix.join(
"/",
"opt",
"keycloak",
"themes",
themeName,
themeType,
fileOrDirectoryBasename
)
})); }));
}) })
.flat(); .flat();
@ -192,33 +242,56 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
.flat(); .flat();
try { try {
child_process.execSync(`docker rm --force ${containerName}`, { "stdio": "ignore" }); child_process.execSync(`docker rm --force ${containerName}`, {
stdio: "ignore"
});
} catch {} } catch {}
const realmJsonFilePath = await (async () => { const realmJsonFilePath = await (async () => {
if (cliCommandOptions.realmJsonFilePath !== undefined) { if (cliCommandOptions.realmJsonFilePath !== undefined) {
console.log(chalk.green(`Using realm json file: ${cliCommandOptions.realmJsonFilePath}`)); console.log(
chalk.green(
`Using realm json file: ${cliCommandOptions.realmJsonFilePath}`
)
);
return getAbsoluteAndInOsFormatPath({ return getAbsoluteAndInOsFormatPath({
"pathIsh": cliCommandOptions.realmJsonFilePath, pathIsh: cliCommandOptions.realmJsonFilePath,
"cwd": process.cwd() cwd: process.cwd()
}); });
} }
const dirPath = pathJoin(getThisCodebaseRootDirPath(), "src", "bin", "start-keycloak"); const dirPath = pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"start-keycloak"
);
const filePath = pathJoin(dirPath, `myrealm-realm-${keycloakMajorVersionNumber}.json`); const filePath = pathJoin(
dirPath,
`myrealm-realm-${keycloakMajorVersionNumber}.json`
);
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
return filePath; return filePath;
} }
console.log(`${chalk.yellow(`Keycloakify do not have a realm configuration for Keycloak ${keycloakMajorVersionNumber} yet.`)}`); console.log(
`${chalk.yellow(
`Keycloakify do not have a realm configuration for Keycloak ${keycloakMajorVersionNumber} yet.`
)}`
);
console.log(chalk.cyan("Select what configuration to use:")); console.log(chalk.cyan("Select what configuration to use:"));
const { value } = await cliSelect<string>({ const { value } = await cliSelect<string>({
"values": [...fs.readdirSync(dirPath).filter(fileBasename => fileBasename.endsWith(".json")), "none"] values: [
...fs
.readdirSync(dirPath)
.filter(fileBasename => fileBasename.endsWith(".json")),
"none"
]
}).catch(() => { }).catch(() => {
process.exit(-1); process.exit(-1);
}); });
@ -238,17 +311,37 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
...["--name", containerName], ...["--name", containerName],
...["-e", "KEYCLOAK_ADMIN=admin"], ...["-e", "KEYCLOAK_ADMIN=admin"],
...["-e", "KEYCLOAK_ADMIN_PASSWORD=admin"], ...["-e", "KEYCLOAK_ADMIN_PASSWORD=admin"],
...(realmJsonFilePath === undefined ? [] : ["-v", `${realmJsonFilePath}:/opt/keycloak/data/import/myrealm-realm.json`]), ...(realmJsonFilePath === undefined
...["-v", `${pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)}:/opt/keycloak/providers/keycloak-theme.jar`], ? []
...(keycloakMajorVersionNumber <= 20 ? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"] : []), : [
...mountTargets.map(({ localPath, containerPath }) => ["-v", `${localPath}:${containerPath}:rw`]).flat(), "-v",
`${realmJsonFilePath}:/opt/keycloak/data/import/myrealm-realm.json`
]),
...[
"-v",
`${pathJoin(
buildOptions.keycloakifyBuildDirPath,
jarFileBasename
)}:/opt/keycloak/providers/keycloak-theme.jar`
],
...(keycloakMajorVersionNumber <= 20
? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"]
: []),
...mountTargets
.map(({ localPath, containerPath }) => [
"-v",
`${localPath}:${containerPath}:rw`
])
.flat(),
`quay.io/keycloak/keycloak:${keycloakVersion}`, `quay.io/keycloak/keycloak:${keycloakVersion}`,
"start-dev", "start-dev",
...(21 <= keycloakMajorVersionNumber && keycloakMajorVersionNumber < 24 ? ["--features=declarative-user-profile"] : []), ...(21 <= keycloakMajorVersionNumber && keycloakMajorVersionNumber < 24
? ["--features=declarative-user-profile"]
: []),
...(realmJsonFilePath === undefined ? [] : ["--import-realm"]) ...(realmJsonFilePath === undefined ? [] : ["--import-realm"])
], ],
{ {
"cwd": buildOptions.keycloakifyBuildDirPath cwd: buildOptions.keycloakifyBuildDirPath
} }
] as const; ] as const;
@ -278,17 +371,23 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
[ [
"", "",
`${chalk.green("Your theme is accessible at:")}`, `${chalk.green("Your theme is accessible at:")}`,
`${chalk.green("➜")} ${chalk.cyan.bold("https://my-theme.keycloakify.dev/")}`, `${chalk.green("➜")} ${chalk.cyan.bold(
"https://my-theme.keycloakify.dev/"
)}`,
"", "",
"You can login with the following credentials:", "You can login with the following credentials:",
`- username: ${chalk.cyan.bold("testuser")}`, `- username: ${chalk.cyan.bold("testuser")}`,
`- password: ${chalk.cyan.bold("password123")}`, `- password: ${chalk.cyan.bold("password123")}`,
"", "",
`Keycloak Admin console: ${chalk.cyan.bold(`http://localhost:${cliCommandOptions.port}`)}`, `Keycloak Admin console: ${chalk.cyan.bold(
`http://localhost:${cliCommandOptions.port}`
)}`,
`- user: ${chalk.cyan.bold("admin")}`, `- user: ${chalk.cyan.bold("admin")}`,
`- password: ${chalk.cyan.bold("admin")}`, `- password: ${chalk.cyan.bold("admin")}`,
"", "",
`Watching for changes in ${chalk.bold(`.${pathSep}${pathRelative(process.cwd(), srcDirPath)}`)}` `Watching for changes in ${chalk.bold(
`.${pathSep}${pathRelative(process.cwd(), srcDirPath)}`
)}`
].join("\n") ].join("\n")
); );
}; };
@ -297,75 +396,79 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
} }
{ {
const { waitForDebounce } = waitForDebounceFactory({ "delay": 400 }); const { waitForDebounce } = waitForDebounceFactory({ delay: 400 });
chokidar.watch([srcDirPath, getThisCodebaseRootDirPath()], { "ignoreInitial": true }).on("all", async (...[, filePath]) => { chokidar
if ( .watch([srcDirPath, getThisCodebaseRootDirPath()], {
isInside({ ignoreInitial: true
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "src", "bin"), })
filePath .on("all", async (...[, filePath]) => {
}) || if (
isInside({ isInside({
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "bin"), dirPath: pathJoin(getThisCodebaseRootDirPath(), "src", "bin"),
filePath filePath
}) }) ||
) { isInside({
return; dirPath: pathJoin(getThisCodebaseRootDirPath(), "bin"),
} filePath
})
) {
return;
}
await waitForDebounce(); await waitForDebounce();
console.log(chalk.cyan("Detected changes in the theme. Rebuilding ...")); console.log(chalk.cyan("Detected changes in the theme. Rebuilding ..."));
const dViteBuildDone = new Deferred<void>(); const dViteBuildDone = new Deferred<void>();
{ {
const child = child_process.spawn("npx", ["vite", "build"], { const child = child_process.spawn("npx", ["vite", "build"], {
"cwd": buildOptions.reactAppRootDirPath, cwd: buildOptions.reactAppRootDirPath,
"env": process.env env: process.env
}); });
child.stdout.on("data", data => { child.stdout.on("data", data => {
if (data.toString("utf8").includes("gzip:")) { if (data.toString("utf8").includes("gzip:")) {
return; return;
} }
process.stdout.write(data); process.stdout.write(data);
}); });
child.stderr.on("data", data => process.stderr.write(data)); child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => { child.on("exit", code => {
if (code === 0) { if (code === 0) {
dViteBuildDone.resolve(); dViteBuildDone.resolve();
} }
}); });
} }
await dViteBuildDone.pr; await dViteBuildDone.pr;
{ {
const child = child_process.spawn("npx", ["keycloakify", "build"], { const child = child_process.spawn("npx", ["keycloakify", "build"], {
"cwd": buildOptions.reactAppRootDirPath, cwd: buildOptions.reactAppRootDirPath,
"env": { env: {
...process.env, ...process.env,
[skipBuildJarsEnvName]: "true" [skipBuildJarsEnvName]: "true"
} }
}); });
child.stdout.on("data", data => process.stdout.write(data)); child.stdout.on("data", data => process.stdout.write(data));
child.stderr.on("data", data => process.stderr.write(data)); child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => { child.on("exit", code => {
if (code !== 0) { if (code !== 0) {
console.log(chalk.yellow("Theme not updated, build failed")); console.log(chalk.yellow("Theme not updated, build failed"));
return; return;
} }
console.log(chalk.green("Rebuild done")); console.log(chalk.green("Rebuild done"));
}); });
} }
}); });
} }
} }

View File

@ -12,35 +12,39 @@ export namespace SemVer {
export type BumpType = (typeof bumpTypes)[number]; export type BumpType = (typeof bumpTypes)[number];
export function parse(versionStr: string): SemVer { export function parse(versionStr: string): SemVer {
const match = versionStr.match(/^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-rc.([0-9]+))?$/); const match = versionStr.match(
/^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-rc.([0-9]+))?$/
);
if (!match) { if (!match) {
throw new Error(`${versionStr} is not a valid semantic version`); throw new Error(`${versionStr} is not a valid semantic version`);
} }
const semVer: Omit<SemVer, "parsedFrom"> = { const semVer: Omit<SemVer, "parsedFrom"> = {
"major": parseInt(match[1]), major: parseInt(match[1]),
"minor": parseInt(match[2]), minor: parseInt(match[2]),
"patch": (() => { patch: (() => {
const str = match[3]; const str = match[3];
return str === undefined ? 0 : parseInt(str); return str === undefined ? 0 : parseInt(str);
})(), })(),
...(() => { ...(() => {
const str = match[4]; const str = match[4];
return str === undefined ? {} : { "rc": parseInt(str) }; return str === undefined ? {} : { rc: parseInt(str) };
})() })()
}; };
const initialStr = stringify(semVer); const initialStr = stringify(semVer);
Object.defineProperty(semVer, "parsedFrom", { Object.defineProperty(semVer, "parsedFrom", {
"enumerable": true, enumerable: true,
"get": function () { get: function () {
const currentStr = stringify(this); const currentStr = stringify(this);
if (currentStr !== initialStr) { if (currentStr !== initialStr) {
throw new Error(`SemVer.parsedFrom can't be read anymore, the version have been modified from ${initialStr} to ${currentStr}`); throw new Error(
`SemVer.parsedFrom can't be read anymore, the version have been modified from ${initialStr} to ${currentStr}`
);
} }
return versionStr; return versionStr;
@ -51,7 +55,9 @@ export namespace SemVer {
} }
export function stringify(v: Omit<SemVer, "parsedFrom">): string { export function stringify(v: Omit<SemVer, "parsedFrom">): string {
return `${v.major}.${v.minor}.${v.patch}${v.rc === undefined ? "" : `-rc.${v.rc}`}`; return `${v.major}.${v.minor}.${v.patch}${
v.rc === undefined ? "" : `-rc.${v.rc}`
}`;
} }
/** /**
@ -80,12 +86,25 @@ export namespace SemVer {
console.log(compare(parse("3.0.0-rc.3"), parse("4.0.0")) === -1 ) console.log(compare(parse("3.0.0-rc.3"), parse("4.0.0")) === -1 )
*/ */
export function bumpType(params: { versionBehind: string | SemVer; versionAhead: string | SemVer }): BumpType | "no bump" { export function bumpType(params: {
const versionAhead = typeof params.versionAhead === "string" ? parse(params.versionAhead) : params.versionAhead; versionBehind: string | SemVer;
const versionBehind = typeof params.versionBehind === "string" ? parse(params.versionBehind) : params.versionBehind; versionAhead: string | SemVer;
}): BumpType | "no bump" {
const versionAhead =
typeof params.versionAhead === "string"
? parse(params.versionAhead)
: params.versionAhead;
const versionBehind =
typeof params.versionBehind === "string"
? parse(params.versionBehind)
: params.versionBehind;
if (compare(versionBehind, versionAhead) === 1) { if (compare(versionBehind, versionAhead) === 1) {
throw new Error(`Version regression ${stringify(versionBehind)} -> ${stringify(versionAhead)}`); throw new Error(
`Version regression ${stringify(versionBehind)} -> ${stringify(
versionAhead
)}`
);
} }
for (const level of ["major", "minor", "patch", "rc"] as const) { for (const level of ["major", "minor", "patch", "rc"] as const) {

View File

@ -1,4 +1,8 @@
export function replaceAll(string: string, searchValue: string | RegExp, replaceValue: string): string { export function replaceAll(
string: string,
searchValue: string | RegExp,
replaceValue: string
): string {
if ((string as any).replaceAll !== undefined) { if ((string as any).replaceAll !== undefined) {
return (string as any).replaceAll(searchValue, replaceValue); return (string as any).replaceAll(searchValue, replaceValue);
} }
@ -24,7 +28,10 @@ export function replaceAll(string: string, searchValue: string | RegExp, replace
// Convert searchValue to string if it's not a string or RegExp // Convert searchValue to string if it's not a string or RegExp
var searchString = String(searchValue); var searchString = String(searchValue);
var regexFromString = new RegExp(searchString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"); var regexFromString = new RegExp(
searchString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
"g"
);
return string.replace(regexFromString, replaceValue); return string.replace(regexFromString, replaceValue);
} }

View File

@ -16,7 +16,10 @@ const crawlRec = (dirPath: string, filePaths: string[]) => {
}; };
/** List all files in a given directory return paths relative to the dir_path */ /** List all files in a given directory return paths relative to the dir_path */
export function crawl(params: { dirPath: string; returnedPathsType: "absolute" | "relative to dirPath" }): string[] { export function crawl(params: {
dirPath: string;
returnedPathsType: "absolute" | "relative to dirPath";
}): string[] {
const { dirPath, returnedPathsType } = params; const { dirPath, returnedPathsType } = params;
const filePaths: string[] = []; const filePaths: string[] = [];

View File

@ -1,27 +1,42 @@
import { Readable } from "stream"; import { Readable } from "stream";
const crc32tab = [ const crc32tab = [
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
]; ];
@ -33,11 +48,13 @@ const crc32tab = [
export function crc32(input: Readable | String | Buffer): Promise<number> { export function crc32(input: Readable | String | Buffer): Promise<number> {
if (typeof input === "string") { if (typeof input === "string") {
let crc = ~0; let crc = ~0;
for (let i = 0; i < input.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ input.charCodeAt(i)) & 0xff]; for (let i = 0; i < input.length; i++)
crc = (crc >>> 8) ^ crc32tab[(crc ^ input.charCodeAt(i)) & 0xff];
return Promise.resolve((crc ^ -1) >>> 0); return Promise.resolve((crc ^ -1) >>> 0);
} else if (input instanceof Buffer) { } else if (input instanceof Buffer) {
let crc = ~0; let crc = ~0;
for (let i = 0; i < input.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ input[i]) & 0xff]; for (let i = 0; i < input.length; i++)
crc = (crc >>> 8) ^ crc32tab[(crc ^ input[i]) & 0xff];
return Promise.resolve((crc ^ -1) >>> 0); return Promise.resolve((crc ^ -1) >>> 0);
} else if (input instanceof Readable) { } else if (input instanceof Readable) {
return new Promise<number>((resolve, reject) => { return new Promise<number>((resolve, reject) => {
@ -46,7 +63,8 @@ export function crc32(input: Readable | String | Buffer): Promise<number> {
input.on("end", () => resolve((crc ^ -1) >>> 0)); input.on("end", () => resolve((crc ^ -1) >>> 0));
input.on("error", e => reject(e)); input.on("error", e => reject(e));
input.on("data", (chunk: Buffer) => { input.on("data", (chunk: Buffer) => {
for (let i = 0; i < chunk.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ chunk[i]) & 0xff]; for (let i = 0; i < chunk.length; i++)
crc = (crc >>> 8) ^ crc32tab[(crc ^ chunk[i]) & 0xff];
}); });
}); });
} else { } else {

View File

@ -11,7 +11,10 @@ function ensureSingleOrNone<T>(arg0: T | T[]) {
if (!Array.isArray(arg0)) return arg0; if (!Array.isArray(arg0)) return arg0;
if (arg0.length === 0) return undefined; if (arg0.length === 0) return undefined;
if (arg0.length === 1) return arg0[0]; if (arg0.length === 1) return arg0[0];
throw new Error("Illegal configuration, expected a single value but found multiple: " + arg0.map(String).join(", ")); throw new Error(
"Illegal configuration, expected a single value but found multiple: " +
arg0.map(String).join(", ")
);
} }
type NPMConfig = Record<string, string | string[]>; type NPMConfig = Record<string, string | string[]>;
@ -24,10 +27,15 @@ async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
const exec = promisify(execCallback); const exec = promisify(execCallback);
const stdout = await exec("npm config get", { "encoding": "utf8", "cwd": npmWorkspaceRootDirPath }).then(({ stdout }) => stdout); const stdout = await exec("npm config get", {
encoding: "utf8",
cwd: npmWorkspaceRootDirPath
}).then(({ stdout }) => stdout);
const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) => const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) =>
key in cfg ? { ...cfg, [key]: [...ensureArray(cfg[key]), value] } : { ...cfg, [key]: value }; key in cfg
? { ...cfg, [key]: [...ensureArray(cfg[key]), value] }
: { ...cfg, [key]: value };
return stdout return stdout
.split("\n") .split("\n")
@ -37,9 +45,14 @@ async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
.reduce(npmConfigReducer, {} as NPMConfig); .reduce(npmConfigReducer, {} as NPMConfig);
} }
export type ProxyFetchOptions = Pick<FetchOptions, "proxy" | "noProxy" | "strictSSL" | "cert" | "ca">; export type ProxyFetchOptions = Pick<
FetchOptions,
"proxy" | "noProxy" | "strictSSL" | "cert" | "ca"
>;
export async function getProxyFetchOptions(params: { npmWorkspaceRootDirPath: string }): Promise<ProxyFetchOptions> { export async function getProxyFetchOptions(params: {
npmWorkspaceRootDirPath: string;
}): Promise<ProxyFetchOptions> {
const { npmWorkspaceRootDirPath } = params; const { npmWorkspaceRootDirPath } = params;
const cfg = await getNmpConfig({ npmWorkspaceRootDirPath }); const cfg = await getNmpConfig({ npmWorkspaceRootDirPath });
@ -60,14 +73,24 @@ export async function getProxyFetchOptions(params: { npmWorkspaceRootDirPath: st
ca.push( ca.push(
...(await (async () => { ...(await (async () => {
function chunks<T>(arr: T[], size: number = 2) { function chunks<T>(arr: T[], size: number = 2) {
return arr.map((_, i) => i % size == 0 && arr.slice(i, i + size)).filter(Boolean) as T[][]; 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 = await readFile(cafile, "utf-8");
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(ca => ca.join("").replace(/^\n/, "").replace(/\n/g, "\\n")); return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(
ca => ca.join("").replace(/^\n/, "").replace(/\n/g, "\\n")
);
})()) })())
); );
} }
return { proxy, noProxy, strictSSL, cert, "ca": ca.length === 0 ? undefined : ca }; return {
proxy,
noProxy,
strictSSL,
cert,
ca: ca.length === 0 ? undefined : ca
};
} }

View File

@ -1,7 +1,15 @@
import { isAbsolute as pathIsAbsolute, sep as pathSep, join as pathJoin, resolve as pathResolve } from "path"; import {
isAbsolute as pathIsAbsolute,
sep as pathSep,
join as pathJoin,
resolve as pathResolve
} from "path";
import * as os from "os"; import * as os from "os";
export function getAbsoluteAndInOsFormatPath(params: { pathIsh: string; cwd: string }): string { export function getAbsoluteAndInOsFormatPath(params: {
pathIsh: string;
cwd: string;
}): string {
const { pathIsh, cwd } = params; const { pathIsh, cwd } = params;
let pathOut = pathIsh; let pathOut = pathIsh;

View File

@ -3,14 +3,22 @@ import { join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import * as fs from "fs"; import * as fs from "fs";
export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string; dependencyExpected: string }) { export function getNpmWorkspaceRootDirPath(params: {
reactAppRootDirPath: string;
dependencyExpected: string;
}) {
const { reactAppRootDirPath, dependencyExpected } = params; const { reactAppRootDirPath, dependencyExpected } = params;
const npmWorkspaceRootDirPath = (function callee(depth: number): string { const npmWorkspaceRootDirPath = (function callee(depth: number): string {
const cwd = pathResolve(pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")])); const cwd = pathResolve(
pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")])
);
try { try {
child_process.execSync("npm config get", { cwd, "stdio": "ignore" }); child_process.execSync("npm config get", {
cwd,
stdio: "ignore"
});
} catch (error) { } catch (error) {
if (String(error).includes("ENOWORKSPACES")) { if (String(error).includes("ENOWORKSPACES")) {
assert(cwd !== pathSep, "NPM workspace not found"); assert(cwd !== pathSep, "NPM workspace not found");
@ -26,11 +34,16 @@ export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string
assert(fs.existsSync(packageJsonFilePath)); assert(fs.existsSync(packageJsonFilePath));
const parsedPackageJson = JSON.parse(fs.readFileSync(packageJsonFilePath).toString("utf8")); const parsedPackageJson = JSON.parse(
fs.readFileSync(packageJsonFilePath).toString("utf8")
);
let isExpectedDependencyFound = false; let isExpectedDependencyFound = false;
for (const dependenciesOrDevDependencies of ["dependencies", "devDependencies"] as const) { for (const dependenciesOrDevDependencies of [
"dependencies",
"devDependencies"
] as const) {
const dependencies = parsedPackageJson[dependenciesOrDevDependencies]; const dependencies = parsedPackageJson[dependenciesOrDevDependencies];
if (dependencies === undefined) { if (dependencies === undefined) {

View File

@ -5,7 +5,11 @@ import { SemVer } from "../SemVer";
export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) { export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
const { octokit } = params; const { octokit } = params;
async function getLatestsSemVersionedTag(params: { owner: string; repo: string; count: number }): Promise< async function getLatestsSemVersionedTag(params: {
owner: string;
repo: string;
count: number;
}): Promise<
{ {
tag: string; tag: string;
version: SemVer; version: SemVer;
@ -33,7 +37,9 @@ export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
semVersionedTags.push({ tag, version }); semVersionedTags.push({ tag, version });
} }
return semVersionedTags.sort(({ version: vX }, { version: vY }) => SemVer.compare(vY, vX)).slice(0, count); return semVersionedTags
.sort(({ version: vX }, { version: vY }) => SemVer.compare(vY, vX))
.slice(0, count);
} }
return { getLatestsSemVersionedTag }; return { getLatestsSemVersionedTag };

View File

@ -5,11 +5,19 @@ const per_page = 99;
export function listTagsFactory(params: { octokit: Octokit }) { export function listTagsFactory(params: { octokit: Octokit }) {
const { octokit } = params; const { octokit } = params;
const octokit_repo_listTags = async (params: { owner: string; repo: string; per_page: number; page: number }) => { const octokit_repo_listTags = async (params: {
owner: string;
repo: string;
per_page: number;
page: number;
}) => {
return octokit.repos.listTags(params); return octokit.repos.listTags(params);
}; };
async function* listTags(params: { owner: string; repo: string }): AsyncGenerator<string> { async function* listTags(params: {
owner: string;
repo: string;
}): AsyncGenerator<string> {
const { owner, repo } = params; const { owner, repo } = params;
let page = 1; let page = 1;
@ -19,7 +27,7 @@ export function listTagsFactory(params: { octokit: Octokit }) {
owner, owner,
repo, repo,
per_page, per_page,
"page": page++ page: page++
}); });
for (const branch of resp.data.map(({ name }) => name)) { for (const branch of resp.data.map(({ name }) => name)) {
@ -33,7 +41,10 @@ export function listTagsFactory(params: { octokit: Octokit }) {
} }
/** Returns the same "latest" tag as deno.land/x, not actually the latest though */ /** Returns the same "latest" tag as deno.land/x, not actually the latest though */
async function getLatestTag(params: { owner: string; repo: string }): Promise<string | undefined> { async function getLatestTag(params: {
owner: string;
repo: string;
}): Promise<string | undefined> {
const { owner, repo } = params; const { owner, repo } = params;
const itRes = await listTags({ owner, repo }).next(); const itRes = await listTags({ owner, repo }).next();

View File

@ -2,10 +2,19 @@ export type PromiseSettledAndPartitioned<T> = [T[], any[]];
export function partitionPromiseSettledResults<T>() { export function partitionPromiseSettledResults<T>() {
return [ return [
([successes, failures]: PromiseSettledAndPartitioned<T>, item: PromiseSettledResult<T>) => (
[successes, failures]: PromiseSettledAndPartitioned<T>,
item: PromiseSettledResult<T>
) =>
item.status === "rejected" item.status === "rejected"
? ([successes, [item.reason, ...failures]] as PromiseSettledAndPartitioned<T>) ? ([
: ([[item.value, ...successes], failures] as PromiseSettledAndPartitioned<T>), successes,
[item.reason, ...failures]
] as PromiseSettledAndPartitioned<T>)
: ([
[item.value, ...successes],
failures
] as PromiseSettledAndPartitioned<T>),
[[], []] as PromiseSettledAndPartitioned<T> [[], []] as PromiseSettledAndPartitioned<T>
] as const; ] as const;
} }

View File

@ -4,7 +4,11 @@ import * as fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
export function readThisNpmPackageVersion(): string { export function readThisNpmPackageVersion(): string {
const version = JSON.parse(fs.readFileSync(pathJoin(getThisCodebaseRootDirPath(), "package.json")).toString("utf8"))["version"]; const version = JSON.parse(
fs
.readFileSync(pathJoin(getThisCodebaseRootDirPath(), "package.json"))
.toString("utf8")
)["version"];
assert(typeof version === "string"); assert(typeof version === "string");

View File

@ -3,7 +3,11 @@ import * as path from "path";
import { crawl } from "./crawl"; import { crawl } from "./crawl";
import { rmSync } from "../tools/fs.rmSync"; import { rmSync } from "../tools/fs.rmSync";
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) => type TransformSourceCode = (params: {
sourceCode: Buffer;
filePath: string;
fileRelativePath: string;
}) =>
| { | {
modifiedSourceCode: Buffer; modifiedSourceCode: Buffer;
newFileName?: string; newFileName?: string;
@ -15,18 +19,27 @@ type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; file
* If source and destination are the same this function can be used to apply the transformation in place * If source and destination are the same this function can be used to apply the transformation in place
* like filtering out some files or modifying them. * like filtering out some files or modifying them.
* */ * */
export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) { export function transformCodebase(params: {
srcDirPath: string;
destDirPath: string;
transformSourceCode?: TransformSourceCode;
}) {
const { srcDirPath, transformSourceCode } = params; const { srcDirPath, transformSourceCode } = params;
const isTargetSameAsSource = path.relative(srcDirPath, params.destDirPath) === ""; const isTargetSameAsSource = path.relative(srcDirPath, params.destDirPath) === "";
const destDirPath = isTargetSameAsSource ? path.join(srcDirPath, "..", "tmp_xOsPdkPsTdzPs34sOkHs") : params.destDirPath; const destDirPath = isTargetSameAsSource
? path.join(srcDirPath, "..", "tmp_xOsPdkPsTdzPs34sOkHs")
: params.destDirPath;
fs.mkdirSync(destDirPath, { fs.mkdirSync(destDirPath, {
"recursive": true recursive: true
}); });
for (const fileRelativePath of crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })) { for (const fileRelativePath of crawl({
dirPath: srcDirPath,
returnedPathsType: "relative to dirPath"
})) {
const filePath = path.join(srcDirPath, fileRelativePath); const filePath = path.join(srcDirPath, fileRelativePath);
const destFilePath = path.join(destDirPath, fileRelativePath); const destFilePath = path.join(destDirPath, fileRelativePath);
@ -34,7 +47,7 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
// it using the lower level implementation. // it using the lower level implementation.
if (transformSourceCode === undefined) { if (transformSourceCode === undefined) {
fs.mkdirSync(path.dirname(destFilePath), { fs.mkdirSync(path.dirname(destFilePath), {
"recursive": true recursive: true
}); });
fs.copyFileSync(filePath, destFilePath); fs.copyFileSync(filePath, destFilePath);
@ -43,7 +56,7 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
} }
const transformSourceCodeResult = transformSourceCode({ const transformSourceCodeResult = transformSourceCode({
"sourceCode": fs.readFileSync(filePath), sourceCode: fs.readFileSync(filePath),
filePath, filePath,
fileRelativePath fileRelativePath
}); });
@ -53,16 +66,22 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
} }
fs.mkdirSync(path.dirname(destFilePath), { fs.mkdirSync(path.dirname(destFilePath), {
"recursive": true recursive: true
}); });
const { newFileName, modifiedSourceCode } = transformSourceCodeResult; const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
fs.writeFileSync(path.join(path.dirname(destFilePath), newFileName ?? path.basename(destFilePath)), modifiedSourceCode); fs.writeFileSync(
path.join(
path.dirname(destFilePath),
newFileName ?? path.basename(destFilePath)
),
modifiedSourceCode
);
} }
if (isTargetSameAsSource) { if (isTargetSameAsSource) {
rmSync(srcDirPath, { "recursive": true }); rmSync(srcDirPath, { recursive: true });
fs.renameSync(destDirPath, srcDirPath); fs.renameSync(destDirPath, srcDirPath);
} }

View File

@ -14,7 +14,10 @@ function populateTemplate(strings: TemplateStringsArray, ...args: unknown[]) {
if (args[i]) { if (args[i]) {
// if the interpolation value has newlines, indent the interpolation values // if the interpolation value has newlines, indent the interpolation values
// using the last known string indent // using the last known string indent
const chunk = String(args[i]).replace(/([\r?\n])/g, "$1" + " ".repeat(lastStringLineLength)); const chunk = String(args[i]).replace(
/([\r?\n])/g,
"$1" + " ".repeat(lastStringLineLength)
);
chunks.push(chunk); chunks.push(chunk);
} }
} }

View File

@ -22,7 +22,11 @@ async function pathExists(path: string) {
// Handlings of non posix path is not implemented correctly // Handlings of non posix path is not implemented correctly
// it work by coincidence. Don't have the time to fix but it should be fixed. // it work by coincidence. Don't have the time to fix but it should be fixed.
export async function unzip(file: string, targetFolder: string, specificDirsToExtract?: string[]) { export async function unzip(
file: string,
targetFolder: string,
specificDirsToExtract?: string[]
) {
specificDirsToExtract = specificDirsToExtract?.map(dirPath => { specificDirsToExtract = specificDirsToExtract?.map(dirPath => {
if (!dirPath.endsWith("/") || !dirPath.endsWith("\\")) { if (!dirPath.endsWith("/") || !dirPath.endsWith("\\")) {
dirPath += "/"; dirPath += "/";
@ -49,7 +53,9 @@ export async function unzip(file: string, targetFolder: string, specificDirsToEx
zipfile.on("entry", async entry => { zipfile.on("entry", async entry => {
if (specificDirsToExtract !== undefined) { if (specificDirsToExtract !== undefined) {
const dirPath = specificDirsToExtract.find(dirPath => entry.fileName.startsWith(dirPath)); const dirPath = specificDirsToExtract.find(dirPath =>
entry.fileName.startsWith(dirPath)
);
// Skip files outside of the unzipSubPath // Skip files outside of the unzipSubPath
if (dirPath === undefined) { if (dirPath === undefined) {
@ -85,7 +91,9 @@ export async function unzip(file: string, targetFolder: string, specificDirsToEx
return; return;
} }
await fsp.mkdir(path.dirname(target), { "recursive": true }); await fsp.mkdir(path.dirname(target), {
recursive: true
});
await pipeline(readStream, fs.createWriteStream(target)); await pipeline(readStream, fs.createWriteStream(target));

View File

@ -1 +1,3 @@
export const isStorybook = typeof window === "object" && Object.keys(window).find(key => key.startsWith("__STORYBOOK")) !== undefined; export const isStorybook =
typeof window === "object" &&
Object.keys(window).find(key => key.startsWith("__STORYBOOK")) !== undefined;

View File

@ -1,14 +1,23 @@
import { clsx } from "keycloakify/tools/clsx"; import { clsx } from "keycloakify/tools/clsx";
import { useConstCallback } from "keycloakify/tools/useConstCallback"; import { useConstCallback } from "keycloakify/tools/useConstCallback";
export function createUseClassName<ClassKey extends string>(params: { defaultClasses: Record<ClassKey, string | undefined> }) { export function createUseClassName<ClassKey extends string>(params: {
defaultClasses: Record<ClassKey, string | undefined>;
}) {
const { defaultClasses } = params; const { defaultClasses } = params;
function useGetClassName(params: { doUseDefaultCss: boolean; classes: Partial<Record<ClassKey, string>> | undefined }) { function useGetClassName(params: {
doUseDefaultCss: boolean;
classes: Partial<Record<ClassKey, string>> | undefined;
}) {
const { classes, doUseDefaultCss } = params; const { classes, doUseDefaultCss } = params;
const getClassName = useConstCallback((classKey: ClassKey): string => { const getClassName = useConstCallback((classKey: ClassKey): string => {
return clsx(classKey, doUseDefaultCss ? defaultClasses[classKey] : undefined, classes?.[classKey]); return clsx(
classKey,
doUseDefaultCss ? defaultClasses[classKey] : undefined,
classes?.[classKey]
);
}); });
return { getClassName }; return { getClassName };

View File

@ -41,13 +41,13 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
}, []); }, []);
useSetClassName({ useSetClassName({
"qualifiedName": "html", qualifiedName: "html",
"className": getClassName("kcHtmlClass") className: getClassName("kcHtmlClass")
}); });
useSetClassName({ useSetClassName({
"qualifiedName": "body", qualifiedName: "body",
"className": bodyClassName ?? getClassName("kcBodyClass") className: bodyClassName ?? getClassName("kcBodyClass")
}); });
useEffect(() => { useEffect(() => {
@ -63,7 +63,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
}, []); }, []);
const { areAllStyleSheetsLoaded } = useInsertLinkTags({ const { areAllStyleSheetsLoaded } = useInsertLinkTags({
"hrefs": !doUseDefaultCss hrefs: !doUseDefaultCss
? [] ? []
: [ : [
`${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`, `${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`,
@ -75,17 +75,17 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
}); });
const { insertScriptTags } = useInsertScriptTags({ const { insertScriptTags } = useInsertScriptTags({
"scriptTags": [ scriptTags: [
{ {
"type": "module", type: "module",
"src": `${url.resourcesPath}/js/menu-button-links.js` src: `${url.resourcesPath}/js/menu-button-links.js`
}, },
...(authenticationSession === undefined ...(authenticationSession === undefined
? [] ? []
: [ : [
{ {
"type": "module", type: "module",
"textContent": [ textContent: [
`import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";`, `import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";`,
``, ``,
`checkCookiesAndSetTimer(`, `checkCookiesAndSetTimer(`,
@ -99,8 +99,8 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
...scripts.map( ...scripts.map(
script => script =>
({ ({
"type": "text/javascript", type: "text/javascript",
"src": script src: script
} as const) } as const)
) )
] ]
@ -237,7 +237,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
<span <span
className={getClassName("kcAlertTitleClass")} className={getClassName("kcAlertTitleClass")}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
"__html": message.summary __html: message.summary
}} }}
/> />
</div> </div>

View File

@ -2,7 +2,10 @@ import type { ReactNode } from "react";
import type { KcContext } from "./kcContext"; import type { KcContext } from "./kcContext";
import type { I18n } from "./i18n"; import type { I18n } from "./i18n";
export type TemplateProps<KcContext extends KcContext.Common, I18nExtended extends I18n> = { export type TemplateProps<
KcContext extends KcContext.Common,
I18nExtended extends I18n
> = {
kcContext: KcContext; kcContext: KcContext;
i18n: I18nExtended; i18n: I18nExtended;
doUseDefaultCss: boolean; doUseDefaultCss: boolean;

View File

@ -43,7 +43,7 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps)
onIsFormSubmittableValueChange(isFormSubmittable); onIsFormSubmittableValueChange(isFormSubmittable);
}, [isFormSubmittable]); }, [isFormSubmittable]);
const groupNameRef = { "current": "" }; const groupNameRef = { current: "" };
return ( return (
<> <>
@ -62,7 +62,9 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps)
)} )}
<div <div
className={getClassName("kcFormGroupClass")} className={getClassName("kcFormGroupClass")}
style={{ "display": attribute.name === "password-confirm" && !doMakeUserConfirmPassword ? "none" : undefined }} style={{
display: attribute.name === "password-confirm" && !doMakeUserConfirmPassword ? "none" : undefined
}}
> >
<div className={getClassName("kcLabelWrapperClass")}> <div className={getClassName("kcLabelWrapperClass")}>
<label htmlFor={attribute.name} className={getClassName("kcLabelClass")}> <label htmlFor={attribute.name} className={getClassName("kcLabelClass")}>
@ -342,9 +344,9 @@ function InputTag(props: InputFiledByTypeProps & { fieldIndex: number | undefine
{...Object.fromEntries(Object.entries(attribute.html5DataAnnotations ?? {}).map(([key, value]) => [`data-${key}`, value]))} {...Object.fromEntries(Object.entries(attribute.html5DataAnnotations ?? {}).map(([key, value]) => [`data-${key}`, value]))}
onChange={event => onChange={event =>
formValidationDispatch({ formValidationDispatch({
"action": "update", action: "update",
"name": attribute.name, name: attribute.name,
"valueOrValues": (() => { valueOrValues: (() => {
if (fieldIndex !== undefined) { if (fieldIndex !== undefined) {
assert(valueOrValues instanceof Array); assert(valueOrValues instanceof Array);
@ -363,9 +365,9 @@ function InputTag(props: InputFiledByTypeProps & { fieldIndex: number | undefine
} }
onBlur={() => onBlur={() =>
props.formValidationDispatch({ props.formValidationDispatch({
"action": "focus lost", action: "focus lost",
"name": attribute.name, name: attribute.name,
"fieldIndex": fieldIndex fieldIndex: fieldIndex
}) })
} }
/> />
@ -484,9 +486,9 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
className="pf-c-button pf-m-inline pf-m-link" className="pf-c-button pf-m-inline pf-m-link"
onClick={() => onClick={() =>
dispatchFormAction({ dispatchFormAction({
"action": "update", action: "update",
"name": attribute.name, name: attribute.name,
"valueOrValues": values.filter((_, i) => i !== fieldIndex) valueOrValues: values.filter((_, i) => i !== fieldIndex)
}) })
} }
> >
@ -501,9 +503,9 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
className="pf-c-button pf-m-inline pf-m-link" className="pf-c-button pf-m-inline pf-m-link"
onClick={() => onClick={() =>
dispatchFormAction({ dispatchFormAction({
"action": "update", action: "update",
"name": attribute.name, name: attribute.name,
"valueOrValues": [...values, ""] valueOrValues: [...values, ""]
}) })
} }
> >
@ -527,17 +529,17 @@ function InputTagSelects(props: InputFiledByTypeProps) {
switch (inputType) { switch (inputType) {
case "select-radiobuttons": case "select-radiobuttons":
return { return {
"inputType": "radio", inputType: "radio",
"classDiv": getClassName("kcInputClassRadio"), classDiv: getClassName("kcInputClassRadio"),
"classInput": getClassName("kcInputClassRadioInput"), classInput: getClassName("kcInputClassRadioInput"),
"classLabel": getClassName("kcInputClassRadioLabel") classLabel: getClassName("kcInputClassRadioLabel")
}; };
case "multiselect-checkboxes": case "multiselect-checkboxes":
return { return {
"inputType": "checkbox", inputType: "checkbox",
"classDiv": getClassName("kcInputClassCheckbox"), classDiv: getClassName("kcInputClassCheckbox"),
"classInput": getClassName("kcInputClassCheckboxInput"), classInput: getClassName("kcInputClassCheckboxInput"),
"classLabel": getClassName("kcInputClassCheckboxLabel") classLabel: getClassName("kcInputClassCheckboxLabel")
}; };
} }
})(); })();
@ -581,9 +583,9 @@ function InputTagSelects(props: InputFiledByTypeProps) {
checked={valueOrValues.includes(option)} checked={valueOrValues.includes(option)}
onChange={event => onChange={event =>
formValidationDispatch({ formValidationDispatch({
"action": "update", action: "update",
"name": attribute.name, name: attribute.name,
"valueOrValues": (() => { valueOrValues: (() => {
const isChecked = event.target.checked; const isChecked = event.target.checked;
if (valueOrValues instanceof Array) { if (valueOrValues instanceof Array) {
@ -604,9 +606,9 @@ function InputTagSelects(props: InputFiledByTypeProps) {
} }
onBlur={() => onBlur={() =>
formValidationDispatch({ formValidationDispatch({
"action": "focus lost", action: "focus lost",
"name": attribute.name, name: attribute.name,
"fieldIndex": undefined fieldIndex: undefined
}) })
} }
/> />
@ -642,16 +644,16 @@ function TextareaTag(props: InputFiledByTypeProps) {
value={value} value={value}
onChange={event => onChange={event =>
formValidationDispatch({ formValidationDispatch({
"action": "update", action: "update",
"name": attribute.name, name: attribute.name,
"valueOrValues": event.target.value valueOrValues: event.target.value
}) })
} }
onBlur={() => onBlur={() =>
formValidationDispatch({ formValidationDispatch({
"action": "focus lost", action: "focus lost",
"name": attribute.name, name: attribute.name,
"fieldIndex": undefined fieldIndex: undefined
}) })
} }
/> />
@ -677,9 +679,9 @@ function SelectTag(props: InputFiledByTypeProps) {
value={valueOrValues} value={valueOrValues}
onChange={event => onChange={event =>
formValidationDispatch({ formValidationDispatch({
"action": "update", action: "update",
"name": attribute.name, name: attribute.name,
"valueOrValues": (() => { valueOrValues: (() => {
if (isMultiple) { if (isMultiple) {
return Array.from(event.target.selectedOptions).map(option => option.value); return Array.from(event.target.selectedOptions).map(option => option.value);
} }
@ -690,9 +692,9 @@ function SelectTag(props: InputFiledByTypeProps) {
} }
onBlur={() => onBlur={() =>
formValidationDispatch({ formValidationDispatch({
"action": "focus lost", action: "focus lost",
"name": attribute.name, name: attribute.name,
"fieldIndex": undefined fieldIndex: undefined
}) })
} }
> >

View File

@ -91,19 +91,19 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
setI18n({ setI18n({
...createI18nTranslationFunctions({ ...createI18nTranslationFunctions({
"fallbackMessages": { fallbackMessages: {
...fallbackMessages, ...fallbackMessages,
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}), ...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
...(extraMessages[fallbackLanguageTag] ?? {}) ...(extraMessages[fallbackLanguageTag] ?? {})
} as any, } as any,
"messages": { messages: {
...(await getMessages(currentLanguageTag)), ...(await getMessages(currentLanguageTag)),
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}), ...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
...(extraMessages[currentLanguageTag] ?? {}) ...(extraMessages[currentLanguageTag] ?? {})
} as any } as any
}), }),
currentLanguageTag, currentLanguageTag,
"getChangeLocalUrl": newLanguageTag => { getChangeLocalUrl: newLanguageTag => {
const { locale } = kcContext; const { locale } = kcContext;
assert(locale !== undefined, "Internationalization not enabled"); assert(locale !== undefined, "Internationalization not enabled");
@ -114,7 +114,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
return targetSupportedLocale.url; return targetSupportedLocale.url;
}, },
"labelBySupportedLanguageTag": Object.fromEntries( labelBySupportedLanguageTag: Object.fromEntries(
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label]) (kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
) )
}); });
@ -170,7 +170,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
})(); })();
return doRenderMarkdown ? ( return doRenderMarkdown ? (
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}> <Markdown allowDangerousHtml renderers={{ paragraph: "span" }}>
{messageWithArgsInjectedIfAny} {messageWithArgsInjectedIfAny}
</Markdown> </Markdown>
) : ( ) : (
@ -186,7 +186,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1]; const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
const out = resolveMsg({ const out = resolveMsg({
"key": keyUnwrappedFromCurlyBraces, key: keyUnwrappedFromCurlyBraces,
args, args,
doRenderMarkdown doRenderMarkdown
}); });
@ -195,38 +195,48 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
} }
return { return {
"msgStr": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": false }) as string, msgStr: (key, ...args) => resolveMsg({ key, args, doRenderMarkdown: false }) as string,
"msg": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": true }) as JSX.Element, msg: (key, ...args) => resolveMsg({ key, args, doRenderMarkdown: true }) as JSX.Element,
"advancedMsg": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": true }) as JSX.Element, advancedMsg: (key, ...args) =>
"advancedMsgStr": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": false }) as string resolveMsgAdvanced({
key,
args,
doRenderMarkdown: true
}) as JSX.Element,
advancedMsgStr: (key, ...args) =>
resolveMsgAdvanced({
key,
args,
doRenderMarkdown: false
}) as string
}; };
} }
const keycloakifyExtraMessages = { const keycloakifyExtraMessages = {
"en": { en: {
"shouldBeEqual": "{0} should be equal to {1}", shouldBeEqual: "{0} should be equal to {1}",
"shouldBeDifferent": "{0} should be different to {1}", shouldBeDifferent: "{0} should be different to {1}",
"shouldMatchPattern": "Pattern should match: `/{0}/`", shouldMatchPattern: "Pattern should match: `/{0}/`",
"mustBeAnInteger": "Must be an integer", mustBeAnInteger: "Must be an integer",
"notAValidOption": "Not a valid option", notAValidOption: "Not a valid option",
"selectAnOption": "Select an option", selectAnOption: "Select an option",
"remove": "Remove", remove: "Remove",
"addValue": "Add value" addValue: "Add value"
}, },
"fr": { fr: {
/* spell-checker: disable */ /* spell-checker: disable */
"shouldBeEqual": "{0} doit être égal à {1}", shouldBeEqual: "{0} doit être égal à {1}",
"shouldBeDifferent": "{0} doit être différent de {1}", shouldBeDifferent: "{0} doit être différent de {1}",
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`", shouldMatchPattern: "Dois respecter le schéma: `/{0}/`",
"mustBeAnInteger": "Doit être un nombre entier", mustBeAnInteger: "Doit être un nombre entier",
"notAValidOption": "N'est pas une option valide", notAValidOption: "N'est pas une option valide",
"logoutConfirmTitle": "Déconnexion", logoutConfirmTitle: "Déconnexion",
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?", logoutConfirmHeader: "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
"doLogout": "Se déconnecter", doLogout: "Se déconnecter",
"selectAnOption": "Sélectionner une option", selectAnOption: "Sélectionner une option",
"remove": "Supprimer", remove: "Supprimer",
"addValue": "Ajouter une valeur" addValue: "Ajouter une valeur"
/* spell-checker: enable */ /* spell-checker: enable */
} }
}; };

View File

@ -3,7 +3,10 @@ import { assert } from "tsafe/assert";
import type { Equals } from "tsafe"; import type { Equals } from "tsafe";
import type { MessageKey } from "../i18n/i18n"; import type { MessageKey } from "../i18n/i18n";
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never; type ExtractAfterStartingWith<
Prefix extends string,
StrEnum
> = StrEnum extends `${Prefix}${infer U}` ? U : never;
/** Take theses type definition with a grain of salt. /** Take theses type definition with a grain of salt.
* Some values might be undefined on some pages. * Some values might be undefined on some pages.
@ -103,7 +106,10 @@ export declare namespace KcContext {
* @param text to return * @param text to return
* @return text if message exists for given field, else undefined * @return text if message exists for given field, else undefined
*/ */
printIfExists: <T extends string>(fieldName: string, text: T) => T | undefined; printIfExists: <T extends string>(
fieldName: string,
text: T
) => T | undefined;
/** /**
* Check if exists error message for given fields * Check if exists error message for given fields
* *

View File

@ -10,15 +10,23 @@ import type { ExtendKcContext } from "./getKcContextFromWindow";
import { getKcContextFromWindow } from "./getKcContextFromWindow"; import { getKcContextFromWindow } from "./getKcContextFromWindow";
import { symToStr } from "tsafe/symToStr"; import { symToStr } from "tsafe/symToStr";
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: { export function createGetKcContext<
KcContextExtension extends { pageId: string } = never
>(params?: {
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[]; mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
mockProperties?: Record<string, string>; mockProperties?: Record<string, string>;
}) { }) {
const { mockData, mockProperties } = params ?? {}; const { mockData, mockProperties } = params ?? {};
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: { function getKcContext<
PageId extends
| ExtendKcContext<KcContextExtension>["pageId"]
| undefined = undefined
>(params?: {
mockPageId?: PageId; mockPageId?: PageId;
storyPartialKcContext?: DeepPartial<Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>>; storyPartialKcContext?: DeepPartial<
Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>
>;
}): { }): {
kcContext: PageId extends undefined kcContext: PageId extends undefined
? ExtendKcContext<KcContextExtension> | undefined ? ExtendKcContext<KcContextExtension> | undefined
@ -36,34 +44,46 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
break warn_that_mock_is_enbaled; break warn_that_mock_is_enbaled;
} }
console.log(`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`, "background: red; color: yellow; font-size: medium"); console.log(
`%cKeycloakify: ${symToStr({
mockPageId
})} set to ${mockPageId}.`,
"background: red; color: yellow; font-size: medium"
);
} }
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId); const kcContextDefaultMock = kcContextMocks.find(
({ pageId }) => pageId === mockPageId
);
const partialKcContextCustomMock = (() => { const partialKcContextCustomMock = (() => {
const out: DeepPartial<ExtendKcContext<KcContextExtension>> = {}; const out: DeepPartial<ExtendKcContext<KcContextExtension>> = {};
const mockDataPick = mockData?.find(({ pageId }) => pageId === mockPageId); const mockDataPick = mockData?.find(
({ pageId }) => pageId === mockPageId
);
if (mockDataPick !== undefined) { if (mockDataPick !== undefined) {
deepAssign({ deepAssign({
"target": out, target: out,
"source": mockDataPick source: mockDataPick
}); });
} }
if (storyPartialKcContext !== undefined) { if (storyPartialKcContext !== undefined) {
deepAssign({ deepAssign({
"target": out, target: out,
"source": storyPartialKcContext source: storyPartialKcContext
}); });
} }
return Object.keys(out).length === 0 ? undefined : out; return Object.keys(out).length === 0 ? undefined : out;
})(); })();
if (kcContextDefaultMock === undefined && partialKcContextCustomMock === undefined) { if (
kcContextDefaultMock === undefined &&
partialKcContextCustomMock === undefined
) {
console.warn( console.warn(
[ [
`WARNING: You declared the non build in page ${mockPageId} but you didn't `, `WARNING: You declared the non build in page ${mockPageId} but you didn't `,
@ -76,68 +96,95 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
const kcContext: any = {}; const kcContext: any = {};
deepAssign({ deepAssign({
"target": kcContext, target: kcContext,
"source": kcContextDefaultMock !== undefined ? kcContextDefaultMock : { "pageId": mockPageId, ...kcContextCommonMock } source:
kcContextDefaultMock !== undefined
? kcContextDefaultMock
: { pageId: mockPageId, ...kcContextCommonMock }
}); });
if (partialKcContextCustomMock !== undefined) { if (partialKcContextCustomMock !== undefined) {
deepAssign({ deepAssign({
"target": kcContext, target: kcContext,
"source": partialKcContextCustomMock source: partialKcContextCustomMock
}); });
if ("profile" in partialKcContextCustomMock) { if ("profile" in partialKcContextCustomMock) {
assert(kcContextDefaultMock !== undefined && "profile" in kcContextDefaultMock); assert(
kcContextDefaultMock !== undefined &&
"profile" in kcContextDefaultMock
);
const { attributes } = kcContextDefaultMock.profile; const { attributes } = kcContextDefaultMock.profile;
id<KcContext.Register>(kcContext).profile.attributes = []; id<KcContext.Register>(kcContext).profile.attributes = [];
id<KcContext.Register>(kcContext).profile.attributesByName = {}; id<KcContext.Register>(kcContext).profile.attributesByName = {};
const partialAttributes = [...((partialKcContextCustomMock as DeepPartial<KcContext.Register>).profile?.attributes ?? [])].filter( const partialAttributes = [
exclude(undefined) ...((
); partialKcContextCustomMock as DeepPartial<KcContext.Register>
).profile?.attributes ?? [])
].filter(exclude(undefined));
attributes.forEach(attribute => { attributes.forEach(attribute => {
const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name); const partialAttribute = partialAttributes.find(
({ name }) => name === attribute.name
);
const augmentedAttribute: Attribute = {} as any; const augmentedAttribute: Attribute = {} as any;
deepAssign({ deepAssign({
"target": augmentedAttribute, target: augmentedAttribute,
"source": attribute source: attribute
}); });
if (partialAttribute !== undefined) { if (partialAttribute !== undefined) {
partialAttributes.splice(partialAttributes.indexOf(partialAttribute), 1); partialAttributes.splice(
partialAttributes.indexOf(partialAttribute),
1
);
deepAssign({ deepAssign({
"target": augmentedAttribute, target: augmentedAttribute,
"source": partialAttribute source: partialAttribute
}); });
} }
id<KcContext.Register>(kcContext).profile.attributes.push(augmentedAttribute); id<KcContext.Register>(kcContext).profile.attributes.push(
id<KcContext.Register>(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute; augmentedAttribute
);
id<KcContext.Register>(kcContext).profile.attributesByName[
augmentedAttribute.name
] = augmentedAttribute;
}); });
partialAttributes partialAttributes
.map(partialAttribute => ({ "validators": {}, ...partialAttribute })) .map(partialAttribute => ({
validators: {},
...partialAttribute
}))
.forEach(partialAttribute => { .forEach(partialAttribute => {
const { name } = partialAttribute; const { name } = partialAttribute;
assert(name !== undefined, "If you define a mock attribute it must have at least a name"); assert(
name !== undefined,
"If you define a mock attribute it must have at least a name"
);
id<KcContext.Register>(kcContext).profile.attributes.push(partialAttribute as any); id<KcContext.Register>(kcContext).profile.attributes.push(
id<KcContext.Register>(kcContext).profile.attributesByName[name] = partialAttribute as any; partialAttribute as any
);
id<KcContext.Register>(kcContext).profile.attributesByName[
name
] = partialAttribute as any;
}); });
} }
} }
if (mockProperties !== undefined) { if (mockProperties !== undefined) {
deepAssign({ deepAssign({
"target": kcContext.properties, target: kcContext.properties,
"source": mockProperties source: mockProperties
}); });
} }
@ -145,14 +192,14 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
} }
if (realKcContext === undefined) { if (realKcContext === undefined) {
return { "kcContext": undefined as any }; return { kcContext: undefined as any };
} }
if (realKcContext.themeType !== "login") { if (realKcContext.themeType !== "login") {
return { "kcContext": undefined as any }; return { kcContext: undefined as any };
} }
return { "kcContext": realKcContext as any }; return { kcContext: realKcContext as any };
} }
return { getKcContext }; return { getKcContext };

View File

@ -5,7 +5,9 @@ import { createGetKcContext } from "./createGetKcContext";
/** NOTE: We now recommend using createGetKcContext instead of this function to make storybook integration easier /** NOTE: We now recommend using createGetKcContext instead of this function to make storybook integration easier
* See: https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/account/kcContext.ts * See: https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/account/kcContext.ts
*/ */
export function getKcContext<KcContextExtension extends { pageId: string } = never>(params?: { export function getKcContext<
KcContextExtension extends { pageId: string } = never
>(params?: {
mockPageId?: ExtendKcContext<KcContextExtension>["pageId"]; mockPageId?: ExtendKcContext<KcContextExtension>["pageId"];
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[]; mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
}): { kcContext: ExtendKcContext<KcContextExtension> | undefined } { }): { kcContext: ExtendKcContext<KcContextExtension> | undefined } {

View File

@ -2,10 +2,14 @@ import type { KcContext } from "./KcContext";
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey"; import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
import { nameOfTheGlobal } from "keycloakify/bin/shared/constants"; import { nameOfTheGlobal } from "keycloakify/bin/shared/constants";
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [KcContextExtension] extends [never] export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [
KcContextExtension
] extends [never]
? KcContext ? KcContext
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>; : AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
export function getKcContextFromWindow<KcContextExtension extends { pageId: string } = never>(): ExtendKcContext<KcContextExtension> | undefined { export function getKcContextFromWindow<
KcContextExtension extends { pageId: string } = never
>(): ExtendKcContext<KcContextExtension> | undefined {
return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal]; return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal];
} }

View File

@ -1,324 +1,336 @@
import "minimal-polyfills/Object.fromEntries"; import "minimal-polyfills/Object.fromEntries";
import type { KcContext, Attribute } from "./KcContext"; import type { KcContext, Attribute } from "./KcContext";
import { resources_common, keycloak_resources, type LoginThemePageId } from "keycloakify/bin/shared/constants"; import {
resources_common,
keycloak_resources,
type LoginThemePageId
} from "keycloakify/bin/shared/constants";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import { BASE_URL } from "keycloakify/lib/BASE_URL"; import { BASE_URL } from "keycloakify/lib/BASE_URL";
const attributes: Attribute[] = [ const attributes: Attribute[] = [
{ {
"validators": { validators: {
"length": { length: {
"ignore.empty.value": true, "ignore.empty.value": true,
"min": "3", min: "3",
"max": "255" max: "255"
} }
}, },
"displayName": "${username}", displayName: "${username}",
"annotations": {}, annotations: {},
"required": true, required: true,
"autocomplete": "username", autocomplete: "username",
"readOnly": false, readOnly: false,
"name": "username", name: "username",
"value": "xxxx" value: "xxxx"
}, },
{ {
"validators": { validators: {
"length": { length: {
"max": "255", max: "255",
"ignore.empty.value": true "ignore.empty.value": true
}, },
"email": { email: {
"ignore.empty.value": true "ignore.empty.value": true
}, },
"pattern": { pattern: {
"ignore.empty.value": true, "ignore.empty.value": true,
"pattern": "gmail\\.com$" pattern: "gmail\\.com$"
} }
}, },
"displayName": "${email}", displayName: "${email}",
"annotations": {}, annotations: {},
"required": true, required: true,
"autocomplete": "email", autocomplete: "email",
"readOnly": false, readOnly: false,
"name": "email" name: "email"
}, },
{ {
"validators": { validators: {
"length": { length: {
"max": "255", max: "255",
"ignore.empty.value": true "ignore.empty.value": true
} }
}, },
"displayName": "${firstName}", displayName: "${firstName}",
"annotations": {}, annotations: {},
"required": true, required: true,
"readOnly": false, readOnly: false,
"name": "firstName" name: "firstName"
}, },
{ {
"validators": { validators: {
"length": { length: {
"max": "255", max: "255",
"ignore.empty.value": true "ignore.empty.value": true
} }
}, },
"displayName": "${lastName}", displayName: "${lastName}",
"annotations": {}, annotations: {},
"required": true, required: true,
"readOnly": false, readOnly: false,
"name": "lastName" name: "lastName"
} }
]; ];
const attributesByName = Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any; const attributesByName = Object.fromEntries(
attributes.map(attribute => [attribute.name, attribute])
) as any;
const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`; const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`;
export const kcContextCommonMock: KcContext.Common = { export const kcContextCommonMock: KcContext.Common = {
"themeVersion": "0.0.0", themeVersion: "0.0.0",
"keycloakifyVersion": "0.0.0", keycloakifyVersion: "0.0.0",
"themeType": "login", themeType: "login",
"themeName": "my-theme-name", themeName: "my-theme-name",
"url": { url: {
"loginAction": "#", loginAction: "#",
resourcesPath, resourcesPath,
"resourcesCommonPath": `${resourcesPath}/${resources_common}`, resourcesCommonPath: `${resourcesPath}/${resources_common}`,
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg", loginRestartFlowUrl:
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg", "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
"ssoLoginInOtherTabsUrl": "/auth/realms/myrealm/login-actions/switch?client_id=account&tab_id=HoAx28ja4xg" loginUrl:
"/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
ssoLoginInOtherTabsUrl:
"/auth/realms/myrealm/login-actions/switch?client_id=account&tab_id=HoAx28ja4xg"
}, },
"realm": { realm: {
"name": "myrealm", name: "myrealm",
"displayName": "myrealm", displayName: "myrealm",
"displayNameHtml": "myrealm", displayNameHtml: "myrealm",
"internationalizationEnabled": true, internationalizationEnabled: true,
"registrationEmailAsUsername": false registrationEmailAsUsername: false
}, },
"messagesPerField": { messagesPerField: {
"printIfExists": () => { printIfExists: () => {
return undefined; return undefined;
}, },
"existsError": () => false, existsError: () => false,
"get": fieldName => `Fake error for ${fieldName}`, get: fieldName => `Fake error for ${fieldName}`,
"exists": () => false, exists: () => false,
"getFirstError": fieldName => `Fake error for ${fieldName}` getFirstError: fieldName => `Fake error for ${fieldName}`
}, },
"locale": { locale: {
"supported": [ supported: [
/* spell-checker: disable */ /* spell-checker: disable */
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
"label": "Deutsch", label: "Deutsch",
"languageTag": "de" languageTag: "de"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
"label": "Norsk", label: "Norsk",
"languageTag": "no" languageTag: "no"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
"label": "Русский", label: "Русский",
"languageTag": "ru" languageTag: "ru"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
"label": "Svenska", label: "Svenska",
"languageTag": "sv" languageTag: "sv"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
"label": "Português (Brasil)", label: "Português (Brasil)",
"languageTag": "pt-BR" languageTag: "pt-BR"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
"label": "Lietuvių", label: "Lietuvių",
"languageTag": "lt" languageTag: "lt"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
"label": "English", label: "English",
"languageTag": "en" languageTag: "en"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
"label": "Italiano", label: "Italiano",
"languageTag": "it" languageTag: "it"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
"label": "Français", label: "Français",
"languageTag": "fr" languageTag: "fr"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
"label": "中文简体", label: "中文简体",
"languageTag": "zh-CN" languageTag: "zh-CN"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
"label": "Español", label: "Español",
"languageTag": "es" languageTag: "es"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
"label": "Čeština", label: "Čeština",
"languageTag": "cs" languageTag: "cs"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
"label": "日本語", label: "日本語",
"languageTag": "ja" languageTag: "ja"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
"label": "Slovenčina", label: "Slovenčina",
"languageTag": "sk" languageTag: "sk"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
"label": "Polski", label: "Polski",
"languageTag": "pl" languageTag: "pl"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
"label": "Català", label: "Català",
"languageTag": "ca" languageTag: "ca"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
"label": "Nederlands", label: "Nederlands",
"languageTag": "nl" languageTag: "nl"
}, },
{ {
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr", url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
"label": "Türkçe", label: "Türkçe",
"languageTag": "tr" languageTag: "tr"
} }
/* spell-checker: enable */ /* spell-checker: enable */
], ],
"currentLanguageTag": "en" currentLanguageTag: "en"
}, },
"auth": { auth: {
"showUsername": false, showUsername: false,
"showResetCredentials": false, showResetCredentials: false,
"showTryAnotherWayLink": false showTryAnotherWayLink: false
}, },
"client": { client: {
"clientId": "myApp", clientId: "myApp",
"attributes": {} attributes: {}
}, },
"scripts": [], scripts: [],
"isAppInitiatedAction": false, isAppInitiatedAction: false,
"properties": {} properties: {}
}; };
const loginUrl = { const loginUrl = {
...kcContextCommonMock.url, ...kcContextCommonMock.url,
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg", loginResetCredentialsUrl:
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg", "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
"oauth2DeviceVerificationAction": "/auth/realms/myrealm/device", registrationUrl:
"oauthAction": "/auth/realms/myrealm/login-actions/consent?client_id=account&tab_id=HoAx28ja4xg" "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg",
oauth2DeviceVerificationAction: "/auth/realms/myrealm/device",
oauthAction:
"/auth/realms/myrealm/login-actions/consent?client_id=account&tab_id=HoAx28ja4xg"
}; };
export const kcContextMocks = [ export const kcContextMocks = [
id<KcContext.Login>({ id<KcContext.Login>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login.ftl", pageId: "login.ftl",
"url": loginUrl, url: loginUrl,
"realm": { realm: {
...kcContextCommonMock.realm, ...kcContextCommonMock.realm,
"loginWithEmailAllowed": true, loginWithEmailAllowed: true,
"rememberMe": true, rememberMe: true,
"password": true, password: true,
"resetPasswordAllowed": true, resetPasswordAllowed: true,
"registrationAllowed": true registrationAllowed: true
}, },
"auth": kcContextCommonMock.auth!, auth: kcContextCommonMock.auth!,
"social": { social: {
"displayInfo": true displayInfo: true
}, },
"usernameHidden": false, usernameHidden: false,
"login": {}, login: {},
"registrationDisabled": false registrationDisabled: false
}), }),
id<KcContext.Register>({ id<KcContext.Register>({
...kcContextCommonMock, ...kcContextCommonMock,
"url": { url: {
...loginUrl, ...loginUrl,
"registrationAction": registrationAction:
"http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0" "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
}, },
"isAppInitiatedAction": false, isAppInitiatedAction: false,
"passwordRequired": true, passwordRequired: true,
"recaptchaRequired": false, recaptchaRequired: false,
"pageId": "register.ftl", pageId: "register.ftl",
"profile": { profile: {
attributes, attributes,
attributesByName attributesByName
}, },
"scripts": [ scripts: [
//"https://www.google.com/recaptcha/api.js" //"https://www.google.com/recaptcha/api.js"
] ]
}), }),
id<KcContext.Info>({ id<KcContext.Info>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "info.ftl", pageId: "info.ftl",
"messageHeader": "<Message header>", messageHeader: "<Message header>",
"requiredActions": undefined, requiredActions: undefined,
"skipLink": false, skipLink: false,
"actionUri": "#", actionUri: "#",
"client": { client: {
"clientId": "myApp", clientId: "myApp",
"baseUrl": "#", baseUrl: "#",
"attributes": {} attributes: {}
} }
}), }),
id<KcContext.Error>({ id<KcContext.Error>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "error.ftl", pageId: "error.ftl",
"client": { client: {
"clientId": "myApp", clientId: "myApp",
"baseUrl": "#", baseUrl: "#",
"attributes": {} attributes: {}
}, },
"message": { message: {
"type": "error", type: "error",
"summary": "This is the error message" summary: "This is the error message"
} }
}), }),
id<KcContext.LoginResetPassword>({ id<KcContext.LoginResetPassword>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-reset-password.ftl", pageId: "login-reset-password.ftl",
"realm": { realm: {
...kcContextCommonMock.realm, ...kcContextCommonMock.realm,
"loginWithEmailAllowed": false, loginWithEmailAllowed: false,
"duplicateEmailsAllowed": false duplicateEmailsAllowed: false
}, },
"url": loginUrl, url: loginUrl,
"auth": {} auth: {}
}), }),
id<KcContext.LoginVerifyEmail>({ id<KcContext.LoginVerifyEmail>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-verify-email.ftl", pageId: "login-verify-email.ftl",
"user": { user: {
"email": "john.doe@gmail.com" email: "john.doe@gmail.com"
} }
}), }),
id<KcContext.Terms>({ id<KcContext.Terms>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "terms.ftl" pageId: "terms.ftl"
}), }),
id<KcContext.LoginDeviceVerifyUserCode>({ id<KcContext.LoginDeviceVerifyUserCode>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-oauth2-device-verify-user-code.ftl", pageId: "login-oauth2-device-verify-user-code.ftl",
url: loginUrl url: loginUrl
}), }),
id<KcContext.LoginOauthGrant>({ id<KcContext.LoginOauthGrant>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-oauth-grant.ftl", pageId: "login-oauth-grant.ftl",
oauth: { oauth: {
code: "5-1N4CIzfi1aprIQjmylI-9e3spLCWW9i5d-GDcs-Sw", code: "5-1N4CIzfi1aprIQjmylI-9e3spLCWW9i5d-GDcs-Sw",
clientScopesRequested: [ clientScopesRequested: [
@ -332,150 +344,152 @@ export const kcContextMocks = [
}), }),
id<KcContext.LoginOtp>({ id<KcContext.LoginOtp>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-otp.ftl", pageId: "login-otp.ftl",
"otpLogin": { otpLogin: {
"userOtpCredentials": [ userOtpCredentials: [
{ {
"id": "id1", id: "id1",
"userLabel": "label1" userLabel: "label1"
}, },
{ {
"id": "id2", id: "id2",
"userLabel": "label2" userLabel: "label2"
} }
] ]
} }
}), }),
id<KcContext.LoginUsername>({ id<KcContext.LoginUsername>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-username.ftl", pageId: "login-username.ftl",
"url": loginUrl, url: loginUrl,
"realm": { realm: {
...kcContextCommonMock.realm, ...kcContextCommonMock.realm,
"loginWithEmailAllowed": true, loginWithEmailAllowed: true,
"rememberMe": true, rememberMe: true,
"password": true, password: true,
"resetPasswordAllowed": true, resetPasswordAllowed: true,
"registrationAllowed": true registrationAllowed: true
}, },
"social": { social: {
"displayInfo": true displayInfo: true
}, },
"usernameHidden": false, usernameHidden: false,
"login": {}, login: {},
"registrationDisabled": false registrationDisabled: false
}), }),
id<KcContext.LoginPassword>({ id<KcContext.LoginPassword>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-password.ftl", pageId: "login-password.ftl",
"url": loginUrl, url: loginUrl,
"realm": { realm: {
...kcContextCommonMock.realm, ...kcContextCommonMock.realm,
"resetPasswordAllowed": true resetPasswordAllowed: true
}, },
"social": { social: {
"displayInfo": false displayInfo: false
} }
}), }),
id<KcContext.WebauthnAuthenticate>({ id<KcContext.WebauthnAuthenticate>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "webauthn-authenticate.ftl", pageId: "webauthn-authenticate.ftl",
"url": loginUrl, url: loginUrl,
"authenticators": { authenticators: {
"authenticators": [] authenticators: []
}, },
"realm": { realm: {
...kcContextCommonMock.realm, ...kcContextCommonMock.realm,
"password": true, password: true,
"registrationAllowed": true registrationAllowed: true
}, },
"challenge": "", challenge: "",
"userVerification": "not specified", userVerification: "not specified",
"rpId": "", rpId: "",
"createTimeout": "0", createTimeout: "0",
"isUserIdentified": "false", isUserIdentified: "false",
"shouldDisplayAuthenticators": false, shouldDisplayAuthenticators: false,
"social": { social: {
"displayInfo": false displayInfo: false
}, },
"login": {} login: {}
}), }),
id<KcContext.LoginUpdatePassword>({ id<KcContext.LoginUpdatePassword>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-update-password.ftl" pageId: "login-update-password.ftl"
}), }),
id<KcContext.LoginUpdateProfile>({ id<KcContext.LoginUpdateProfile>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-update-profile.ftl", pageId: "login-update-profile.ftl",
"profile": { profile: {
attributes, attributes,
attributesByName attributesByName
} }
}), }),
id<KcContext.LoginIdpLinkConfirm>({ id<KcContext.LoginIdpLinkConfirm>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-idp-link-confirm.ftl", pageId: "login-idp-link-confirm.ftl",
"idpAlias": "FranceConnect" idpAlias: "FranceConnect"
}), }),
id<KcContext.LoginIdpLinkEmail>({ id<KcContext.LoginIdpLinkEmail>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-idp-link-email.ftl", pageId: "login-idp-link-email.ftl",
"idpAlias": "FranceConnect", idpAlias: "FranceConnect",
"brokerContext": { brokerContext: {
"username": "anUsername" username: "anUsername"
} }
}), }),
id<KcContext.LoginConfigTotp>({ id<KcContext.LoginConfigTotp>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "login-config-totp.ftl", pageId: "login-config-totp.ftl",
"totp": { totp: {
"totpSecretEncoded": "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV", totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
"qrUrl": "#", qrUrl: "#",
"totpSecretQrCode": totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=", "iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=",
"manualUrl": "#", manualUrl: "#",
"totpSecret": "G4nsI8lQagRMUchH8jEG", totpSecret: "G4nsI8lQagRMUchH8jEG",
"otpCredentials": [], otpCredentials: [],
"supportedApplications": ["FreeOTP", "Google Authenticator"], supportedApplications: ["FreeOTP", "Google Authenticator"],
"policy": { policy: {
"algorithm": "HmacSHA1", algorithm: "HmacSHA1",
"digits": 6, digits: 6,
"lookAheadWindow": 1, lookAheadWindow: 1,
"type": "totp", type: "totp",
"period": 30, period: 30,
"getAlgorithmKey": () => "SHA1" getAlgorithmKey: () => "SHA1"
} }
} }
}), }),
id<KcContext.LogoutConfirm>({ id<KcContext.LogoutConfirm>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "logout-confirm.ftl", pageId: "logout-confirm.ftl",
"url": { url: {
...kcContextCommonMock.url, ...kcContextCommonMock.url,
"logoutConfirmAction": "Continuer?" logoutConfirmAction: "Continuer?"
}, },
"client": { client: {
"clientId": "myApp", clientId: "myApp",
"baseUrl": "#", baseUrl: "#",
"attributes": {} attributes: {}
}, },
"logoutConfirm": { "code": "123", skipLink: false } logoutConfirm: { code: "123", skipLink: false }
}), }),
id<KcContext.IdpReviewUserProfile>({ id<KcContext.IdpReviewUserProfile>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "idp-review-user-profile.ftl", pageId: "idp-review-user-profile.ftl",
"profile": { profile: {
attributes, attributes,
attributesByName attributesByName
} }
}), }),
id<KcContext.UpdateEmail>({ id<KcContext.UpdateEmail>({
...kcContextCommonMock, ...kcContextCommonMock,
"pageId": "update-email.ftl", pageId: "update-email.ftl",
"profile": { profile: {
"attributes": attributes.filter(attribute => attribute.name === "email"), attributes: attributes.filter(attribute => attribute.name === "email"),
"attributesByName": Object.fromEntries( attributesByName: Object.fromEntries(
attributes.filter(attribute => attribute.name === "email").map(attribute => [attribute.name, attribute]) attributes
.filter(attribute => attribute.name === "email")
.map(attribute => [attribute.name, attribute])
) )
} }
}), }),
@ -502,8 +516,8 @@ export const kcContextMocks = [
id<KcContext.SamlPostForm>({ id<KcContext.SamlPostForm>({
...kcContextCommonMock, ...kcContextCommonMock,
pageId: "saml-post-form.ftl", pageId: "saml-post-form.ftl",
"samlPost": { samlPost: {
"url": "" url: ""
} }
}), }),
id<KcContext.LoginPageExpired>({ id<KcContext.LoginPageExpired>({
@ -514,107 +528,107 @@ export const kcContextMocks = [
id<KcContext.FrontchannelLogout>({ id<KcContext.FrontchannelLogout>({
...kcContextCommonMock, ...kcContextCommonMock,
pageId: "frontchannel-logout.ftl", pageId: "frontchannel-logout.ftl",
"logout": { logout: {
"clients": [ clients: [
{ {
"name": "myApp", name: "myApp",
"frontChannelLogoutUrl": "#" frontChannelLogoutUrl: "#"
}, },
{ {
"name": "myApp2", name: "myApp2",
"frontChannelLogoutUrl": "#" frontChannelLogoutUrl: "#"
} }
] ]
} }
}), }),
id<KcContext.WebauthnRegister>({ id<KcContext.WebauthnRegister>({
"pageId": "webauthn-register.ftl", pageId: "webauthn-register.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"challenge": "random-challenge-string", challenge: "random-challenge-string",
"userid": "user123", userid: "user123",
"username": "johndoe", username: "johndoe",
"signatureAlgorithms": ["ES256", "RS256"], signatureAlgorithms: ["ES256", "RS256"],
"rpEntityName": "Example Corp", rpEntityName: "Example Corp",
"rpId": "example.com", rpId: "example.com",
"attestationConveyancePreference": "direct", attestationConveyancePreference: "direct",
"authenticatorAttachment": "platform", authenticatorAttachment: "platform",
"requireResidentKey": "required", requireResidentKey: "required",
"userVerificationRequirement": "preferred", userVerificationRequirement: "preferred",
"createTimeout": 60000, createTimeout: 60000,
"excludeCredentialIds": "credId123,credId456", excludeCredentialIds: "credId123,credId456",
"isSetRetry": false, isSetRetry: false,
"isAppInitiatedAction": true isAppInitiatedAction: true
}), }),
id<KcContext.DeleteCredential>({ id<KcContext.DeleteCredential>({
"pageId": "delete-credential.ftl", pageId: "delete-credential.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"credentialLabel": "myCredential" credentialLabel: "myCredential"
}), }),
id<KcContext.Code>({ id<KcContext.Code>({
"pageId": "code.ftl", pageId: "code.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"code": { code: {
"success": true, success: true,
"code": "123456" code: "123456"
} }
}), }),
id<KcContext.DeleteAccountConfirm>({ id<KcContext.DeleteAccountConfirm>({
"pageId": "delete-account-confirm.ftl", pageId: "delete-account-confirm.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"triggered_from_aia": true triggered_from_aia: true
}), }),
id<KcContext.LoginRecoveryAuthnCodeConfig>({ id<KcContext.LoginRecoveryAuthnCodeConfig>({
"pageId": "login-recovery-authn-code-config.ftl", pageId: "login-recovery-authn-code-config.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"recoveryAuthnCodesConfigBean": { recoveryAuthnCodesConfigBean: {
"generatedRecoveryAuthnCodesList": ["code123", "code456", "code789"], generatedRecoveryAuthnCodesList: ["code123", "code456", "code789"],
"generatedRecoveryAuthnCodesAsString": "code123, code456, code789", generatedRecoveryAuthnCodesAsString: "code123, code456, code789",
"generatedAt": new Date().getTime() generatedAt: new Date().getTime()
} }
}), }),
id<KcContext.LoginRecoveryAuthnCodeInput>({ id<KcContext.LoginRecoveryAuthnCodeInput>({
"pageId": "login-recovery-authn-code-input.ftl", pageId: "login-recovery-authn-code-input.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"recoveryAuthnCodesInputBean": { recoveryAuthnCodesInputBean: {
"codeNumber": 1234 codeNumber: 1234
} }
}), }),
id<KcContext.LoginResetOtp>({ id<KcContext.LoginResetOtp>({
"pageId": "login-reset-otp.ftl", pageId: "login-reset-otp.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"configuredOtpCredentials": { configuredOtpCredentials: {
"userOtpCredentials": [ userOtpCredentials: [
{ {
"id": "otpId1", id: "otpId1",
"userLabel": "OTP Device 1" userLabel: "OTP Device 1"
}, },
{ {
"id": "otpId2", id: "otpId2",
"userLabel": "OTP Device 2" userLabel: "OTP Device 2"
}, },
{ {
"id": "otpId3", id: "otpId3",
"userLabel": "Backup OTP" userLabel: "Backup OTP"
} }
], ],
"selectedCredentialId": "otpId2" selectedCredentialId: "otpId2"
} }
}), }),
id<KcContext.LoginX509Info>({ id<KcContext.LoginX509Info>({
"pageId": "login-x509-info.ftl", pageId: "login-x509-info.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"x509": { x509: {
"formData": { formData: {
"subjectDN": "CN=John Doe, O=Example Corp, C=US", subjectDN: "CN=John Doe, O=Example Corp, C=US",
"isUserEnabled": true, isUserEnabled: true,
"username": "johndoe" username: "johndoe"
} }
} }
}), }),
id<KcContext.WebauthnError>({ id<KcContext.WebauthnError>({
"pageId": "webauthn-error.ftl", pageId: "webauthn-error.ftl",
...kcContextCommonMock, ...kcContextCommonMock,
"isAppInitiatedAction": true isAppInitiatedAction: true
}) })
]; ];

View File

@ -33,7 +33,9 @@ export function useDownloadTerms(params: {
const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown); const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown);
const downloadTermMarkdownMemoized = useConst(() => const downloadTermMarkdownMemoized = useConst(() =>
memoize((currentLanguageTag: string) => downloadTermMarkdownConst({ currentLanguageTag })) memoize((currentLanguageTag: string) =>
downloadTermMarkdownConst({ currentLanguageTag })
)
); );
return { downloadTermMarkdownMemoized }; return { downloadTermMarkdownMemoized };
@ -41,9 +43,9 @@ export function useDownloadTerms(params: {
useEffect(() => { useEffect(() => {
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) { if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
downloadTermMarkdownMemoized(kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag).then( downloadTermMarkdownMemoized(
thermMarkdown => (evtTermsMarkdown.state = thermMarkdown) kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
); ).then(thermMarkdown => (evtTermsMarkdown.state = thermMarkdown));
} }
}, []); }, []);
} }

View File

@ -2,131 +2,138 @@ import { createUseClassName } from "keycloakify/lib/useGetClassName";
import type { ClassKey } from "keycloakify/login/TemplateProps"; import type { ClassKey } from "keycloakify/login/TemplateProps";
export const { useGetClassName } = createUseClassName<ClassKey>({ export const { useGetClassName } = createUseClassName<ClassKey>({
"defaultClasses": { defaultClasses: {
"kcBodyClass": undefined, kcBodyClass: undefined,
"kcHeaderWrapperClass": undefined, kcHeaderWrapperClass: undefined,
"kcLocaleWrapperClass": undefined, kcLocaleWrapperClass: undefined,
"kcInfoAreaWrapperClass": undefined, kcInfoAreaWrapperClass: undefined,
"kcFormButtonsWrapperClass": undefined, kcFormButtonsWrapperClass: undefined,
"kcFormOptionsWrapperClass": undefined, kcFormOptionsWrapperClass: undefined,
"kcLocaleDropDownClass": undefined, kcLocaleDropDownClass: undefined,
"kcLocaleListItemClass": undefined, kcLocaleListItemClass: undefined,
"kcContentWrapperClass": undefined, kcContentWrapperClass: undefined,
"kcCheckboxInputClass": undefined, kcCheckboxInputClass: undefined,
"kcLogoIdP-facebook": "fa fa-facebook", "kcLogoIdP-facebook": "fa fa-facebook",
"kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg", kcAuthenticatorOTPClass: "fa fa-mobile list-view-pf-icon-lg",
"kcLogoIdP-bitbucket": "fa fa-bitbucket", "kcLogoIdP-bitbucket": "fa fa-bitbucket",
"kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg", kcAuthenticatorWebAuthnClass: "fa fa-key list-view-pf-icon-lg",
"kcWebAuthnDefaultIcon": "pficon pficon-key", kcWebAuthnDefaultIcon: "pficon pficon-key",
"kcLogoIdP-stackoverflow": "fa fa-stack-overflow", "kcLogoIdP-stackoverflow": "fa fa-stack-overflow",
"kcSelectAuthListItemClass": "pf-l-stack__item select-auth-box-parent pf-l-split", kcSelectAuthListItemClass: "pf-l-stack__item select-auth-box-parent pf-l-split",
"kcLogoIdP-microsoft": "fa fa-windows", "kcLogoIdP-microsoft": "fa fa-windows",
"kcLoginOTPListItemHeaderClass": "pf-c-tile__header", kcLoginOTPListItemHeaderClass: "pf-c-tile__header",
"kcLocaleItemClass": "pf-c-dropdown__menu-item", kcLocaleItemClass: "pf-c-dropdown__menu-item",
"kcLoginOTPListItemIconBodyClass": "pf-c-tile__icon", kcLoginOTPListItemIconBodyClass: "pf-c-tile__icon",
"kcInputHelperTextAfterClass": "pf-c-form__helper-text pf-c-form__helper-text-after", kcInputHelperTextAfterClass:
"kcFormClass": "form-horizontal", "pf-c-form__helper-text pf-c-form__helper-text-after",
"kcSelectAuthListClass": "pf-l-stack select-auth-container", kcFormClass: "form-horizontal",
"kcInputClassRadioCheckboxLabelDisabled": "pf-m-disabled", kcSelectAuthListClass: "pf-l-stack select-auth-container",
"kcSelectAuthListItemIconClass": "pf-l-split__item select-auth-box-icon", kcInputClassRadioCheckboxLabelDisabled: "pf-m-disabled",
"kcRecoveryCodesWarning": "kc-recovery-codes-warning", kcSelectAuthListItemIconClass: "pf-l-split__item select-auth-box-icon",
"kcFormSettingClass": "login-pf-settings", kcRecoveryCodesWarning: "kc-recovery-codes-warning",
"kcWebAuthnBLE": "fa fa-bluetooth-b", kcFormSettingClass: "login-pf-settings",
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", kcWebAuthnBLE: "fa fa-bluetooth-b",
"kcSelectAuthListItemArrowIconClass": "fa fa-angle-right fa-lg", kcInputWrapperClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFeedbackAreaClass": "col-md-12", kcSelectAuthListItemArrowIconClass: "fa fa-angle-right fa-lg",
"kcFormPasswordVisibilityButtonClass": "pf-c-button pf-m-control", kcFeedbackAreaClass: "col-md-12",
kcFormPasswordVisibilityButtonClass: "pf-c-button pf-m-control",
"kcLogoIdP-google": "fa fa-google", "kcLogoIdP-google": "fa fa-google",
"kcCheckLabelClass": "pf-c-check__label", kcCheckLabelClass: "pf-c-check__label",
"kcSelectAuthListItemFillClass": "pf-l-split__item pf-m-fill", kcSelectAuthListItemFillClass: "pf-l-split__item pf-m-fill",
"kcAuthenticatorDefaultClass": "fa fa-list list-view-pf-icon-lg", kcAuthenticatorDefaultClass: "fa fa-list list-view-pf-icon-lg",
"kcLogoIdP-gitlab": "fa fa-gitlab", "kcLogoIdP-gitlab": "fa fa-gitlab",
"kcFormAreaClass": "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2", kcFormAreaClass:
"kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2",
"kcInputClassRadioLabel": "pf-c-radio__label", kcFormButtonsClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg", kcInputClassRadioLabel: "pf-c-radio__label",
"kcSelectAuthListItemHeadingClass": "pf-l-stack__item select-auth-box-headline pf-c-title", kcAuthenticatorWebAuthnPasswordlessClass: "fa fa-key list-view-pf-icon-lg",
"kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details", kcSelectAuthListItemHeadingClass:
"kcLogoLink": "http://www.keycloak.org", "pf-l-stack__item select-auth-box-headline pf-c-title",
"kcContainerClass": "container-fluid", kcInfoAreaClass: "col-xs-12 col-sm-4 col-md-4 col-lg-5 details",
"kcSelectAuthListItemTitle": "select-auth-box-paragraph", kcLogoLink: "http://www.keycloak.org",
"kcHtmlClass": "login-pf", kcContainerClass: "container-fluid",
"kcLoginOTPListItemTitleClass": "pf-c-tile__title", kcSelectAuthListItemTitle: "select-auth-box-paragraph",
kcHtmlClass: "login-pf",
kcLoginOTPListItemTitleClass: "pf-c-tile__title",
"kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift", "kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift",
"kcWebAuthnUnknownIcon": "pficon pficon-key unknown-transport-class", kcWebAuthnUnknownIcon: "pficon pficon-key unknown-transport-class",
"kcFormSocialAccountNameClass": "kc-social-provider-name", kcFormSocialAccountNameClass: "kc-social-provider-name",
"kcLogoIdP-openshift-v3": "pf-icon pf-icon-openshift", "kcLogoIdP-openshift-v3": "pf-icon pf-icon-openshift",
"kcLoginOTPListInputClass": "pf-c-tile__input", kcLoginOTPListInputClass: "pf-c-tile__input",
"kcWebAuthnUSB": "fa fa-usb", kcWebAuthnUSB: "fa fa-usb",
"kcInputClassRadio": "pf-c-radio", kcInputClassRadio: "pf-c-radio",
"kcWebAuthnKeyIcon": "pficon pficon-key", kcWebAuthnKeyIcon: "pficon pficon-key",
"kcFeedbackInfoIcon": "fa fa-fw fa-info-circle", kcFeedbackInfoIcon: "fa fa-fw fa-info-circle",
"kcCommonLogoIdP": "kc-social-provider-logo kc-social-gray", kcCommonLogoIdP: "kc-social-provider-logo kc-social-gray",
"kcRecoveryCodesActions": "kc-recovery-codes-actions", kcRecoveryCodesActions: "kc-recovery-codes-actions",
"kcFormGroupHeader": "pf-c-form__group", kcFormGroupHeader: "pf-c-form__group",
"kcFormSocialAccountSectionClass": "kc-social-section kc-social-gray", kcFormSocialAccountSectionClass: "kc-social-section kc-social-gray",
"kcLogoIdP-instagram": "fa fa-instagram", "kcLogoIdP-instagram": "fa fa-instagram",
"kcAlertClass": "pf-c-alert pf-m-inline", kcAlertClass: "pf-c-alert pf-m-inline",
"kcHeaderClass": "login-pf-page-header", kcHeaderClass: "login-pf-page-header",
"kcLabelWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", kcLabelWrapperClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFormPasswordVisibilityIconShow": "fa fa-eye", kcFormPasswordVisibilityIconShow: "fa fa-eye",
"kcFormSocialAccountLinkClass": "pf-c-login__main-footer-links-item-link", kcFormSocialAccountLinkClass: "pf-c-login__main-footer-links-item-link",
"kcLocaleMainClass": "pf-c-dropdown", kcLocaleMainClass: "pf-c-dropdown",
"kcInputGroup": "pf-c-input-group", kcInputGroup: "pf-c-input-group",
"kcTextareaClass": "form-control", kcTextareaClass: "form-control",
"kcButtonBlockClass": "pf-m-block", kcButtonBlockClass: "pf-m-block",
"kcButtonClass": "pf-c-button", kcButtonClass: "pf-c-button",
"kcWebAuthnNFC": "fa fa-wifi", kcWebAuthnNFC: "fa fa-wifi",
"kcLocaleClass": "col-xs-12 col-sm-1", kcLocaleClass: "col-xs-12 col-sm-1",
"kcInputClassCheckboxInput": "pf-c-check__input", kcInputClassCheckboxInput: "pf-c-check__input",
"kcFeedbackErrorIcon": "fa fa-fw fa-exclamation-circle", kcFeedbackErrorIcon: "fa fa-fw fa-exclamation-circle",
"kcInputLargeClass": "input-lg", kcInputLargeClass: "input-lg",
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text", kcInputErrorMessageClass:
"kcRecoveryCodesList": "kc-recovery-codes-list", "pf-c-form__helper-text pf-m-error required kc-feedback-text",
"kcFormSocialAccountListClass": "pf-c-login__main-footer-links kc-social-links", kcRecoveryCodesList: "kc-recovery-codes-list",
"kcAlertTitleClass": "pf-c-alert__title kc-feedback-text", kcFormSocialAccountListClass: "pf-c-login__main-footer-links kc-social-links",
"kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg", kcAlertTitleClass: "pf-c-alert__title kc-feedback-text",
"kcCheckInputClass": "pf-c-check__input", kcAuthenticatorPasswordClass: "fa fa-unlock list-view-pf-icon-lg",
kcCheckInputClass: "pf-c-check__input",
"kcLogoIdP-linkedin": "fa fa-linkedin", "kcLogoIdP-linkedin": "fa fa-linkedin",
"kcLogoIdP-twitter": "fa fa-twitter", "kcLogoIdP-twitter": "fa fa-twitter",
"kcFeedbackWarningIcon": "fa fa-fw fa-exclamation-triangle", kcFeedbackWarningIcon: "fa fa-fw fa-exclamation-triangle",
"kcResetFlowIcon": "pficon pficon-arrow fa", kcResetFlowIcon: "pficon pficon-arrow fa",
"kcSelectAuthListItemIconPropertyClass": "fa-2x select-auth-box-icon-properties", kcSelectAuthListItemIconPropertyClass: "fa-2x select-auth-box-icon-properties",
"kcFeedbackSuccessIcon": "fa fa-fw fa-check-circle", kcFeedbackSuccessIcon: "fa fa-fw fa-check-circle",
"kcLoginOTPListClass": "pf-c-tile", kcLoginOTPListClass: "pf-c-tile",
"kcSrOnlyClass": "sr-only", kcSrOnlyClass: "sr-only",
"kcFormSocialAccountListGridClass": "pf-l-grid kc-social-grid", kcFormSocialAccountListGridClass: "pf-l-grid kc-social-grid",
"kcButtonDefaultClass": "btn-default", kcButtonDefaultClass: "btn-default",
"kcFormGroupErrorClass": "has-error", kcFormGroupErrorClass: "has-error",
"kcSelectAuthListItemDescriptionClass": "pf-l-stack__item select-auth-box-desc", kcSelectAuthListItemDescriptionClass: "pf-l-stack__item select-auth-box-desc",
"kcSelectAuthListItemBodyClass": "pf-l-split__item pf-l-stack", kcSelectAuthListItemBodyClass: "pf-l-split__item pf-l-stack",
"kcWebAuthnInternal": "pficon pficon-key", kcWebAuthnInternal: "pficon pficon-key",
"kcSelectAuthListItemArrowClass": "pf-l-split__item select-auth-box-arrow", kcSelectAuthListItemArrowClass: "pf-l-split__item select-auth-box-arrow",
"kcCheckClass": "pf-c-check", kcCheckClass: "pf-c-check",
"kcContentClass": "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3", kcContentClass:
"kcLogoClass": "login-pf-brand", "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3",
"kcLoginOTPListItemIconClass": "fa fa-mobile", kcLogoClass: "login-pf-brand",
"kcLoginClass": "login-pf-page", kcLoginOTPListItemIconClass: "fa fa-mobile",
"kcSignUpClass": "login-pf-signup", kcLoginClass: "login-pf-page",
"kcButtonLargeClass": "btn-lg", kcSignUpClass: "login-pf-signup",
"kcFormCardClass": "card-pf", kcButtonLargeClass: "btn-lg",
"kcLocaleListClass": "pf-c-dropdown__menu pf-m-align-right", kcFormCardClass: "card-pf",
"kcInputClass": "pf-c-form-control", kcLocaleListClass: "pf-c-dropdown__menu pf-m-align-right",
"kcFormGroupClass": "form-group", kcInputClass: "pf-c-form-control",
kcFormGroupClass: "form-group",
"kcLogoIdP-paypal": "fa fa-paypal", "kcLogoIdP-paypal": "fa fa-paypal",
"kcInputClassCheckbox": "pf-c-check", kcInputClassCheckbox: "pf-c-check",
"kcRecoveryCodesConfirmation": "kc-recovery-codes-confirmation", kcRecoveryCodesConfirmation: "kc-recovery-codes-confirmation",
"kcFormPasswordVisibilityIconHide": "fa fa-eye-slash", kcFormPasswordVisibilityIconHide: "fa fa-eye-slash",
"kcInputClassRadioInput": "pf-c-radio__input", kcInputClassRadioInput: "pf-c-radio__input",
"kcFormSocialAccountListButtonClass": "pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray", kcFormSocialAccountListButtonClass:
"kcInputClassCheckboxLabel": "pf-c-check__label", "pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray",
"kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12", kcInputClassCheckboxLabel: "pf-c-check__label",
"kcFormHeaderClass": "login-pf-header", kcFormOptionsClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
"kcFormSocialAccountGridItem": "pf-l-grid__item", kcFormHeaderClass: "login-pf-header",
"kcButtonPrimaryClass": "pf-m-primary", kcFormSocialAccountGridItem: "pf-l-grid__item",
"kcInputHelperTextBeforeClass": "pf-c-form__helper-text pf-c-form__helper-text-before", kcButtonPrimaryClass: "pf-m-primary",
kcInputHelperTextBeforeClass:
"pf-c-form__helper-text pf-c-form__helper-text-before",
"kcLogoIdP-github": "fa fa-github", "kcLogoIdP-github": "fa fa-github",
"kcLabelClass": "pf-c-form__label pf-c-form__label-text" kcLabelClass: "pf-c-form__label pf-c-form__label-text"
} }
}); });

View File

@ -108,11 +108,11 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
const { kcContext, i18n, doMakeUserConfirmPassword } = params; const { kcContext, i18n, doMakeUserConfirmPassword } = params;
const { insertScriptTags } = useInsertScriptTags({ const { insertScriptTags } = useInsertScriptTags({
"scriptTags": Object.keys(kcContext.profile?.html5DataAnnotations ?? {}) scriptTags: Object.keys(kcContext.profile?.html5DataAnnotations ?? {})
.filter(key => key !== "kcMultivalued" && key !== "kcNumberFormat") // NOTE: Keycloakify handles it. .filter(key => key !== "kcMultivalued" && key !== "kcNumberFormat") // NOTE: Keycloakify handles it.
.map(key => ({ .map(key => ({
"type": "module", type: "module",
"src": `${kcContext.url.resourcesPath}/js/${key}.js` src: `${kcContext.url.resourcesPath}/js/${key}.js`
})) }))
}); });
@ -146,15 +146,15 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
.filter(name => (name !== "username" ? true : !kcContext.realm.registrationEmailAsUsername)) .filter(name => (name !== "username" ? true : !kcContext.realm.registrationEmailAsUsername))
.map(name => .map(name =>
id<Attribute>({ id<Attribute>({
"name": name, name: name,
"displayName": id<`\${${MessageKey}}`>(`\${${name}}`), displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
"required": true, required: true,
"value": (kcContext.register as any).formData[name] ?? "", value: (kcContext.register as any).formData[name] ?? "",
"html5DataAnnotations": {}, html5DataAnnotations: {},
"readOnly": false, readOnly: false,
"validators": {}, validators: {},
"annotations": {}, annotations: {},
"autocomplete": (() => { autocomplete: (() => {
switch (name) { switch (name) {
case "email": case "email":
return "email"; return "email";
@ -174,15 +174,15 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
.filter(name => (name !== "username" ? true : (kcContext.user as any).editUsernameAllowed)) .filter(name => (name !== "username" ? true : (kcContext.user as any).editUsernameAllowed))
.map(name => .map(name =>
id<Attribute>({ id<Attribute>({
"name": name, name: name,
"displayName": id<`\${${MessageKey}}`>(`\${${name}}`), displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
"required": true, required: true,
"value": (kcContext as any).user[name] ?? "", value: (kcContext as any).user[name] ?? "",
"html5DataAnnotations": {}, html5DataAnnotations: {},
"readOnly": false, readOnly: false,
"validators": {}, validators: {},
"annotations": {}, annotations: {},
"autocomplete": (() => { autocomplete: (() => {
switch (name) { switch (name) {
case "email": case "email":
return "email"; return "email";
@ -200,15 +200,15 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
//NOTE: Handle legacy update-email.ftl //NOTE: Handle legacy update-email.ftl
return [ return [
id<Attribute>({ id<Attribute>({
"name": "email", name: "email",
"displayName": id<`\${${MessageKey}}`>(`\${email}`), displayName: id<`\${${MessageKey}}`>(`\${email}`),
"required": true, required: true,
"value": (kcContext.email as any).value ?? "", value: (kcContext.email as any).value ?? "",
"html5DataAnnotations": {}, html5DataAnnotations: {},
"readOnly": false, readOnly: false,
"validators": {}, validators: {},
"annotations": {}, annotations: {},
"autocomplete": "email" autocomplete: "email"
}) })
]; ];
} }
@ -228,11 +228,11 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
return id<Attribute>({ return id<Attribute>({
...rest, ...rest,
"group": { group: {
"name": group, name: group,
"displayHeader": groupDisplayHeader, displayHeader: groupDisplayHeader,
"displayDescription": groupDisplayDescription, displayDescription: groupDisplayDescription,
"html5DataAnnotations": {} html5DataAnnotations: {}
} }
}); });
} }
@ -257,28 +257,28 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
syntheticAttributes.push( syntheticAttributes.push(
{ {
"name": "password", name: "password",
"displayName": id<`\${${MessageKey}}`>("${password}"), displayName: id<`\${${MessageKey}}`>("${password}"),
"required": true, required: true,
"readOnly": false, readOnly: false,
"validators": {}, validators: {},
"annotations": {}, annotations: {},
"autocomplete": "new-password", autocomplete: "new-password",
"html5DataAnnotations": {}, html5DataAnnotations: {},
// NOTE: Compat with Keycloak version prior to 24 // NOTE: Compat with Keycloak version prior to 24
...({ "groupAnnotations": {} } as {}) ...({ groupAnnotations: {} } as {})
}, },
{ {
"name": "password-confirm", name: "password-confirm",
"displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"), displayName: id<`\${${MessageKey}}`>("${passwordConfirm}"),
"required": true, required: true,
"readOnly": false, readOnly: false,
"validators": {}, validators: {},
"annotations": {}, annotations: {},
"html5DataAnnotations": {}, html5DataAnnotations: {},
"autocomplete": "new-password", autocomplete: "new-password",
// NOTE: Compat with Keycloak version prior to 24 // NOTE: Compat with Keycloak version prior to 24
...({ "groupAnnotations": {} } as {}) ...({ groupAnnotations: {} } as {})
} }
); );
} }
@ -288,7 +288,10 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
})(); })();
const initialFormFieldState = (() => { const initialFormFieldState = (() => {
const out: { attribute: Attribute; valueOrValues: string | string[] }[] = []; const out: {
attribute: Attribute;
valueOrValues: string | string[];
}[] = [];
for (const attribute of syntheticAttributes) { for (const attribute of syntheticAttributes) {
handle_multi_valued_attribute: { handle_multi_valued_attribute: {
@ -324,7 +327,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
out.push({ out.push({
attribute, attribute,
"valueOrValues": values valueOrValues: values
}); });
continue; continue;
@ -332,7 +335,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
out.push({ out.push({
attribute, attribute,
"valueOrValues": attribute.value ?? "" valueOrValues: attribute.value ?? ""
}); });
} }
@ -340,14 +343,14 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
})(); })();
const initialState: internal.State = { const initialState: internal.State = {
"formFieldStates": initialFormFieldState.map(({ attribute, valueOrValues }) => ({ formFieldStates: initialFormFieldState.map(({ attribute, valueOrValues }) => ({
attribute, attribute,
"errors": getErrors({ errors: getErrors({
"attributeName": attribute.name, attributeName: attribute.name,
"formFieldStates": initialFormFieldState formFieldStates: initialFormFieldState
}), }),
"hasLostFocusAtLeastOnce": valueOrValues instanceof Array ? valueOrValues.map(() => false) : false, hasLostFocusAtLeastOnce: valueOrValues instanceof Array ? valueOrValues.map(() => false) : false,
"valueOrValues": valueOrValues valueOrValues: valueOrValues
})) }))
}; };
@ -381,8 +384,8 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
} }
formFieldState.errors = getErrors({ formFieldState.errors = getErrors({
"attributeName": formAction.name, attributeName: formAction.name,
"formFieldStates": state.formFieldStates formFieldStates: state.formFieldStates
}); });
update_password_confirm: { update_password_confirm: {
@ -395,9 +398,9 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
} }
state = reducer(state, { state = reducer(state, {
"action": "update", action: "update",
"name": "password-confirm", name: "password-confirm",
"valueOrValues": formAction.valueOrValues valueOrValues: formAction.valueOrValues
}); });
} }
@ -421,9 +424,9 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
const formState: FormState = useMemo( const formState: FormState = useMemo(
() => ({ () => ({
"formFieldStates": state.formFieldStates.map( formFieldStates: state.formFieldStates.map(
({ errors, hasLostFocusAtLeastOnce: hasLostFocusAtLeastOnceOrArr, attribute, ...valueOrValuesWrap }) => ({ ({ errors, hasLostFocusAtLeastOnce: hasLostFocusAtLeastOnceOrArr, attribute, ...valueOrValuesWrap }) => ({
"displayableErrors": errors.filter(error => { displayableErrors: errors.filter(error => {
const hasLostFocusAtLeastOnce = const hasLostFocusAtLeastOnce =
typeof hasLostFocusAtLeastOnceOrArr === "boolean" typeof hasLostFocusAtLeastOnceOrArr === "boolean"
? hasLostFocusAtLeastOnceOrArr ? hasLostFocusAtLeastOnceOrArr
@ -482,7 +485,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
...valueOrValuesWrap ...valueOrValuesWrap
}) })
), ),
"isFormSubmittable": state.formFieldStates.every(({ errors }) => errors.length === 0) isFormSubmittable: state.formFieldStates.every(({ errors }) => errors.length === 0)
}), }),
[state] [state]
); );
@ -501,7 +504,13 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n; const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
const getErrors = useConstCallback( const getErrors = useConstCallback(
(params: { attributeName: string; formFieldStates: { attribute: Attribute; valueOrValues: string | string[] }[] }): FormFieldError[] => { (params: {
attributeName: string;
formFieldStates: {
attribute: Attribute;
valueOrValues: string | string[];
}[];
}): FormFieldError[] => {
const { attributeName, formFieldStates } = params; const { attributeName, formFieldStates } = params;
const formFieldState = formFieldStates.find(({ attribute }) => attribute.name === attributeName); const formFieldState = formFieldStates.find(({ attribute }) => attribute.name === attributeName);
@ -572,10 +581,10 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
return [ return [
{ {
errorMessageStr, errorMessageStr,
"errorMessage": <span key={0}>{errorMessageStr}</span>, errorMessage: <span key={0}>{errorMessageStr}</span>,
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "server" type: "server"
} }
} }
]; ];
@ -598,19 +607,19 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
.map((...[, index]) => { .map((...[, index]) => {
const specificValueErrors = getErrors({ const specificValueErrors = getErrors({
attributeName, attributeName,
"formFieldStates": formFieldStates.map(formFieldState => { formFieldStates: formFieldStates.map(formFieldState => {
if (formFieldState.attribute.name === attributeName) { if (formFieldState.attribute.name === attributeName) {
assert(formFieldState.valueOrValues instanceof Array); assert(formFieldState.valueOrValues instanceof Array);
return { return {
"attribute": { attribute: {
...attribute, ...attribute,
"annotations": { annotations: {
...attribute.annotations, ...attribute.annotations,
"inputType": undefined inputType: undefined
}, },
"multivalued": false multivalued: false
}, },
"valueOrValues": formFieldState.valueOrValues[index] valueOrValues: formFieldState.valueOrValues[index]
}; };
} }
@ -626,7 +635,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
return true; return true;
}) })
.map((error): FormFieldError => ({ ...error, "fieldIndex": index })); .map(
(error): FormFieldError => ({
...error,
fieldIndex: index
})
);
}) })
.reduce((acc, errors) => [...acc, ...errors], []); .reduce((acc, errors) => [...acc, ...errors], []);
@ -642,12 +656,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["error-user-attribute-required"] as const; const msgArgs = ["error-user-attribute-required"] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "other", type: "other",
"rule": "requiredField" rule: "requiredField"
} }
}); });
} }
@ -696,12 +710,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
return [ return [
{ {
"errorMessage": <Fragment key={0}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={0}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "validator", type: "validator",
"name": validatorName name: validatorName
} }
} }
]; ];
@ -740,12 +754,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordMinLengthMessage", `${minLength}`] as const; const msgArgs = ["invalidPasswordMinLengthMessage", `${minLength}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "passwordPolicy", type: "passwordPolicy",
"name": policyName name: policyName
} }
}); });
} }
@ -768,12 +782,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordMinDigitsMessage", `${minNumberOfDigits}`] as const; const msgArgs = ["invalidPasswordMinDigitsMessage", `${minNumberOfDigits}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "passwordPolicy", type: "passwordPolicy",
"name": policyName name: policyName
} }
}); });
} }
@ -798,12 +812,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordMinLowerCaseCharsMessage", `${minNumberOfLowerCaseChar}`] as const; const msgArgs = ["invalidPasswordMinLowerCaseCharsMessage", `${minNumberOfLowerCaseChar}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "passwordPolicy", type: "passwordPolicy",
"name": policyName name: policyName
} }
}); });
} }
@ -828,12 +842,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordMinUpperCaseCharsMessage", `${minNumberOfUpperCaseChar}`] as const; const msgArgs = ["invalidPasswordMinUpperCaseCharsMessage", `${minNumberOfUpperCaseChar}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "passwordPolicy", type: "passwordPolicy",
"name": policyName name: policyName
} }
}); });
} }
@ -856,12 +870,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordMinSpecialCharsMessage", `${minNumberOfSpecialChar}`] as const; const msgArgs = ["invalidPasswordMinSpecialCharsMessage", `${minNumberOfSpecialChar}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "passwordPolicy", type: "passwordPolicy",
"name": policyName name: policyName
} }
}); });
} }
@ -906,12 +920,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordNotUsernameMessage"] as const; const msgArgs = ["invalidPasswordNotUsernameMessage"] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "passwordPolicy", type: "passwordPolicy",
"name": policyName name: policyName
} }
}); });
} }
@ -944,12 +958,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordNotEmailMessage"] as const; const msgArgs = ["invalidPasswordNotEmailMessage"] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "passwordPolicy", type: "passwordPolicy",
"name": policyName name: policyName
} }
}); });
} }
@ -977,12 +991,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["invalidPasswordConfirmMessage"] as const; const msgArgs = ["invalidPasswordConfirmMessage"] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "other", type: "other",
"rule": "passwordConfirmMatchesPassword" rule: "passwordConfirmMatchesPassword"
} }
}); });
} }
@ -1001,12 +1015,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["error-user-attribute-required"] as const; const msgArgs = ["error-user-attribute-required"] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "other", type: "other",
"rule": "requiredField" rule: "requiredField"
} }
}); });
} }
@ -1027,17 +1041,17 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
} }
const source: FormFieldError.Source = { const source: FormFieldError.Source = {
"type": "validator", type: "validator",
"name": validatorName name: validatorName
}; };
if (max && value.length > parseInt(`${max}`)) { if (max && value.length > parseInt(`${max}`)) {
const msgArgs = ["error-invalid-length-too-long", `${max}`] as const; const msgArgs = ["error-invalid-length-too-long", `${max}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
source source
}); });
} }
@ -1046,9 +1060,9 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["error-invalid-length-too-short", `${min}`] as const; const msgArgs = ["error-invalid-length-too-short", `${min}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
source source
}); });
} }
@ -1076,12 +1090,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = [errorMessageKey ?? id<MessageKey>("shouldMatchPattern"), pattern] as const; const msgArgs = [errorMessageKey ?? id<MessageKey>("shouldMatchPattern"), pattern] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArgs), errorMessageStr: advancedMsgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "validator", type: "validator",
"name": validatorName name: validatorName
} }
}); });
} }
@ -1115,12 +1129,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = [id<MessageKey>("invalidEmailMessage")] as const; const msgArgs = [id<MessageKey>("invalidEmailMessage")] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "validator", type: "validator",
"name": validatorName name: validatorName
} }
}); });
} }
@ -1143,17 +1157,17 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const intValue = parseInt(value); const intValue = parseInt(value);
const source: FormFieldError.Source = { const source: FormFieldError.Source = {
"type": "validator", type: "validator",
"name": validatorName name: validatorName
}; };
if (isNaN(intValue)) { if (isNaN(intValue)) {
const msgArgs = ["mustBeAnInteger"] as const; const msgArgs = ["mustBeAnInteger"] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
source source
}); });
@ -1164,9 +1178,9 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["error-number-out-of-range-too-big", `${max}`] as const; const msgArgs = ["error-number-out-of-range-too-big", `${max}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
source source
}); });
@ -1177,9 +1191,9 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = ["error-number-out-of-range-too-small", `${min}`] as const; const msgArgs = ["error-number-out-of-range-too-small", `${min}`] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs), errorMessageStr: msgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
source source
}); });
@ -1207,12 +1221,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
const msgArgs = [id<MessageKey>("notAValidOption")] as const; const msgArgs = [id<MessageKey>("notAValidOption")] as const;
errors.push({ errors.push({
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>, errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArgs), errorMessageStr: advancedMsgStr(...msgArgs),
"fieldIndex": undefined, fieldIndex: undefined,
"source": { source: {
"type": "validator", type: "validator",
"name": validatorName name: validatorName
} }
}); });
} }

View File

@ -19,12 +19,18 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
return ( return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("deleteAccountConfirm")}> <Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("deleteAccountConfirm")}>
<form action={url.loginAction} className="form-vertical" method="post"> <form action={url.loginAction} className="form-vertical" method="post">
<div className="alert alert-warning" style={{ "marginTop": "0", "marginBottom": "30px" }}> <div className="alert alert-warning" style={{ marginTop: "0", marginBottom: "30px" }}>
<span className="pficon pficon-warning-triangle-o"></span> <span className="pficon pficon-warning-triangle-o"></span>
{msg("irreversibleAction")} {msg("irreversibleAction")}
</div> </div>
<p>{msg("deletingImplies")}</p> <p>{msg("deletingImplies")}</p>
<ul style={{ "color": "#72767b", "listStyle": "disc", "listStylePosition": "inside" }}> <ul
style={{
color: "#72767b",
listStyle: "disc",
listStylePosition: "inside"
}}
>
<li>{msg("loggingOutImmediately")}</li> <li>{msg("loggingOutImmediately")}</li>
<li>{msg("errasingData")}</li> <li>{msg("errasingData")}</li>
</ul> </ul>
@ -38,7 +44,7 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
{triggered_from_aia && ( {triggered_from_aia && (
<button <button
className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))} className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))}
style={{ "marginLeft": "calc(100% - 220px)" }} style={{ marginLeft: "calc(100% - 220px)" }}
type="submit" type="submit"
name="cancel-aia" name="cancel-aia"
value="true" value="true"

View File

@ -27,7 +27,7 @@ export default function FrontchannelLogout(props: PageProps<Extract<KcContext, {
{logout.clients.map(client => ( {logout.clients.map(client => (
<li key={client.name}> <li key={client.name}>
{client.name} {client.name}
<iframe src={client.frontChannelLogoutUrl} style={{ "display": "none" }} /> <iframe src={client.frontChannelLogoutUrl} style={{ display: "none" }} />
</li> </li>
))} ))}
</ul> </ul>

View File

@ -21,10 +21,10 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { insertScriptTags } = useInsertScriptTags({ const { insertScriptTags } = useInsertScriptTags({
"scriptTags": [ scriptTags: [
{ {
"type": "text/javascript", type: "text/javascript",
"textContent": ` textContent: `
/* copy recovery codes */ /* copy recovery codes */
function copyRecoveryCodes() { function copyRecoveryCodes() {

View File

@ -32,18 +32,18 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { insertScriptTags } = useInsertScriptTags({ const { insertScriptTags } = useInsertScriptTags({
"scriptTags": [ scriptTags: [
{ {
"type": "text/javascript", type: "text/javascript",
"src": `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js` src: `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js`
}, },
{ {
"type": "text/javascript", type: "text/javascript",
"src": `${url.resourcesPath}/js/base64url.js` src: `${url.resourcesPath}/js/base64url.js`
}, },
{ {
"type": "text/javascript", type: "text/javascript",
"textContent": ` textContent: `
function webAuthnAuthenticate() { function webAuthnAuthenticate() {
let isUserIdentified = ${isUserIdentified}; let isUserIdentified = ${isUserIdentified};
@ -206,7 +206,10 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
className={getClassName("kcSelectAuthListItemDescriptionClass")} className={getClassName("kcSelectAuthListItemDescriptionClass")}
> >
{authenticator.transports.displayNameProperties {authenticator.transports.displayNameProperties
.map((nameProperty, i, arr) => ({ nameProperty, "hasNext": i !== arr.length - 1 })) .map((nameProperty, i, arr) => ({
nameProperty,
hasNext: i !== arr.length - 1
}))
.map(({ nameProperty, hasNext }) => ( .map(({ nameProperty, hasNext }) => (
<Fragment key={nameProperty}> <Fragment key={nameProperty}>
<span>{msg(nameProperty)}</span> <span>{msg(nameProperty)}</span>

View File

@ -35,18 +35,18 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
const { msg, msgStr } = i18n; const { msg, msgStr } = i18n;
const { insertScriptTags } = useInsertScriptTags({ const { insertScriptTags } = useInsertScriptTags({
"scriptTags": [ scriptTags: [
{ {
"type": "text/javascript", type: "text/javascript",
"src": `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js` src: `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js`
}, },
{ {
"type": "text/javascript", type: "text/javascript",
"src": `${url.resourcesPath}/js/base64url.js` src: `${url.resourcesPath}/js/base64url.js`
}, },
{ {
"type": "text/javascript", type: "text/javascript",
"textContent": ` textContent: `
function registerSecurityKey() { function registerSecurityKey() {
// Check if WebAuthn is supported by this browser // Check if WebAuthn is supported by this browser

View File

@ -21,5 +21,9 @@ export declare namespace AndByDiscriminatingKey {
SingletonU1 extends Record<DiscriminatingKey, string>, SingletonU1 extends Record<DiscriminatingKey, string>,
U2, U2,
U1 extends Record<DiscriminatingKey, string> U1 extends Record<DiscriminatingKey, string>
> = U2 extends Pick<SingletonU1, DiscriminatingKey> ? U2 & SingletonU1 : U2 extends Pick<U1, DiscriminatingKey> ? never : U2; > = U2 extends Pick<SingletonU1, DiscriminatingKey>
? U2 & SingletonU1
: U2 extends Pick<U1, DiscriminatingKey>
? never
: U2;
} }

View File

@ -17,7 +17,10 @@ if (!Array.prototype.every) {
var len = O.length >>> 0; var len = O.length >>> 0;
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
if (typeof callbackfn !== "function" && Object.prototype.toString.call(callbackfn) !== "[object Function]") { if (
typeof callbackfn !== "function" &&
Object.prototype.toString.call(callbackfn) !== "[object Function]"
) {
throw new TypeError(); throw new TypeError();
} }

View File

@ -1,3 +1,5 @@
import type { LazyExoticComponent, ComponentType } from "react"; import type { LazyExoticComponent, ComponentType } from "react";
export type LazyOrNot<Component extends ComponentType<any>> = LazyExoticComponent<Component> | Component; export type LazyOrNot<Component extends ComponentType<any>> =
| LazyExoticComponent<Component>
| Component;

View File

@ -1,7 +1,13 @@
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { typeGuard } from "tsafe/typeGuard"; import { typeGuard } from "tsafe/typeGuard";
export type CxArg = undefined | null | string | boolean | Partial<Record<string, boolean | null | undefined>> | readonly CxArg[]; export type CxArg =
| undefined
| null
| string
| boolean
| Partial<Record<string, boolean | null | undefined>>
| readonly CxArg[];
export const clsx = (...args: CxArg[]): string => { export const clsx = (...args: CxArg[]): string => {
const len = args.length; const len = args.length;

View File

@ -3,7 +3,10 @@ import { is } from "tsafe/is";
import { deepClone } from "./deepClone"; import { deepClone } from "./deepClone";
//Warning: Be mindful that because of array this is not idempotent. //Warning: Be mindful that because of array this is not idempotent.
export function deepAssign(params: { target: Record<string, unknown>; source: Record<string, unknown> }) { export function deepAssign(params: {
target: Record<string, unknown>;
source: Record<string, unknown>;
}) {
const { target } = params; const { target } = params;
const source = deepClone(params.source); const source = deepClone(params.source);
@ -11,12 +14,16 @@ export function deepAssign(params: { target: Record<string, unknown>; source: Re
Object.keys(source).forEach(key => { Object.keys(source).forEach(key => {
var dereferencedSource = source[key]; var dereferencedSource = source[key];
if (target[key] === undefined || dereferencedSource instanceof Function || !(dereferencedSource instanceof Object)) { if (
target[key] === undefined ||
dereferencedSource instanceof Function ||
!(dereferencedSource instanceof Object)
) {
Object.defineProperty(target, key, { Object.defineProperty(target, key, {
"enumerable": true, enumerable: true,
"writable": true, writable: true,
"configurable": true, configurable: true,
"value": dereferencedSource value: dereferencedSource
}); });
return; return;
@ -37,8 +44,8 @@ export function deepAssign(params: { target: Record<string, unknown>; source: Re
assert(is<Record<string, unknown>>(dereferencedSource)); assert(is<Record<string, unknown>>(dereferencedSource));
deepAssign({ deepAssign({
"target": dereferencedTarget, target: dereferencedTarget,
"source": dereferencedSource source: dereferencedSource
}); });
}); });
} }

View File

@ -13,5 +13,7 @@ export function deepClone<T>(o: T): T {
return o.map(deepClone) as any; return o.map(deepClone) as any;
} }
return Object.fromEntries(Object.entries(o).map(([key, value]) => [key, deepClone(value)])) as any; return Object.fromEntries(
Object.entries(o).map(([key, value]) => [key, deepClone(value)])
) as any;
} }

View File

@ -11,7 +11,10 @@ export const formatNumber = (input: string, format: string): string => {
} }
// calculate the maximum size of the given pattern based on the sum of the expected digits // calculate the maximum size of the given pattern based on the sum of the expected digits
const maxSize = digitPattern.reduce((total, p) => total + parseInt(p.replace("{", "").replace("}", "")), 0); const maxSize = digitPattern.reduce(
(total, p) => total + parseInt(p.replace("{", "").replace("}", "")),
0
);
// keep only digits // keep only digits
let rawValue = input.replace(/\D+/g, ""); let rawValue = input.replace(/\D+/g, "");

View File

@ -2,7 +2,9 @@ import { useRef, useState } from "react";
import { Parameters } from "tsafe/Parameters"; import { Parameters } from "tsafe/Parameters";
/** https://stackoverflow.com/questions/65890278/why-cant-usecallback-always-return-the-same-ref */ /** https://stackoverflow.com/questions/65890278/why-cant-usecallback-always-return-the-same-ref */
export function useConstCallback<T extends ((...args: any[]) => unknown) | undefined | null>(callback: NonNullable<T>): T { export function useConstCallback<
T extends ((...args: any[]) => unknown) | undefined | null
>(callback: NonNullable<T>): T {
const callbackRef = useRef<typeof callback>(null as any); const callbackRef = useRef<typeof callback>(null as any);
callbackRef.current = callback; callbackRef.current = callback;

Some files were not shown because too many files have changed in this diff Show More