Update prettier configuration
This commit is contained in:
parent
7a89888d11
commit
22e7ff1424
@ -1,11 +1,24 @@
|
||||
{
|
||||
"printWidth": 150,
|
||||
"printWidth": 90,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"quoteProps": "preserve",
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid"
|
||||
"arrowParens": "avoid",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.tsx",
|
||||
"options": {
|
||||
"printWidth": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "useUserProfileForm.tsx",
|
||||
"options": {
|
||||
"printWidth": 150
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -107,7 +107,7 @@
|
||||
"make-fetch-happen": "^11.0.3",
|
||||
"patch-package": "^8.0.0",
|
||||
"powerhooks": "^1.0.10",
|
||||
"prettier": "^2.3.0",
|
||||
"prettier": "^3.2.5",
|
||||
"properties-parser": "^0.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
@ -10,7 +10,10 @@ console.log(chalk.cyan("Building Keycloakify..."));
|
||||
const startTime = Date.now();
|
||||
|
||||
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 => {
|
||||
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")}`);
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": join("dist", "ncc_out"),
|
||||
"destDirPath": join("dist", "bin"),
|
||||
"transformSourceCode": ({ fileRelativePath, sourceCode }) => {
|
||||
srcDirPath: join("dist", "ncc_out"),
|
||||
destDirPath: join("dist", "bin"),
|
||||
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
||||
if (fileRelativePath === "index.js") {
|
||||
return {
|
||||
"newFileName": "main.js",
|
||||
"modifiedSourceCode": sourceCode
|
||||
newFileName: "main.js",
|
||||
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"));
|
||||
|
||||
fs.chmodSync(
|
||||
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-alias -p ${join("src", "tsconfig.json")}`);
|
||||
|
||||
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")}`);
|
||||
|
||||
if (!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"));
|
||||
if (
|
||||
!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({
|
||||
"srcDirPath": join("dist", "ncc_out"),
|
||||
"destDirPath": join("dist", "vite-plugin"),
|
||||
"transformSourceCode": ({ fileRelativePath, sourceCode }) => {
|
||||
srcDirPath: join("dist", "ncc_out"),
|
||||
destDirPath: join("dist", "vite-plugin"),
|
||||
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
||||
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"));
|
||||
|
||||
@ -85,7 +107,7 @@ console.log(chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixe
|
||||
function run(command: string) {
|
||||
console.log(chalk.grey(`$ ${command}`));
|
||||
|
||||
child_process.execSync(command, { "stdio": "inherit" });
|
||||
child_process.execSync(command, { stdio: "inherit" });
|
||||
}
|
||||
|
||||
function patchDeprecatedBufferApiUsage(filePath: string) {
|
||||
|
@ -4,15 +4,34 @@ import { SemVer } from "../src/bin/tools/SemVer";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
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(
|
||||
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;
|
||||
|
||||
const targetFilePath = pathRelative(
|
||||
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}`);
|
||||
@ -22,5 +41,5 @@ console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath
|
||||
function run(command: string) {
|
||||
console.log(chalk.grey(`$ ${command}`));
|
||||
|
||||
return child_process.execSync(command, { "stdio": "inherit" });
|
||||
return child_process.execSync(command, { stdio: "inherit" });
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
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 { downloadKeycloakDefaultTheme } from "../src/bin/shared/downloadKeycloakDefaultTheme";
|
||||
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
|
||||
@ -19,18 +24,26 @@ async function main() {
|
||||
|
||||
const tmpDirPath = pathJoin(thisCodebaseRootDirPath, "tmp_xImOef9dOd44");
|
||||
|
||||
rmSync(tmpDirPath, { "recursive": true, "force": true });
|
||||
rmSync(tmpDirPath, { recursive: true, force: true });
|
||||
|
||||
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({
|
||||
keycloakVersion,
|
||||
"destDirPath": tmpDirPath,
|
||||
"buildOptions": {
|
||||
"cacheDirPath": pathJoin(thisCodebaseRootDirPath, "node_modules", ".cache", "keycloakify"),
|
||||
"npmWorkspaceRootDirPath": thisCodebaseRootDirPath
|
||||
destDirPath: tmpDirPath,
|
||||
buildOptions: {
|
||||
cacheDirPath: pathJoin(
|
||||
thisCodebaseRootDirPath,
|
||||
"node_modules",
|
||||
".cache",
|
||||
"keycloakify"
|
||||
),
|
||||
npmWorkspaceRootDirPath: thisCodebaseRootDirPath
|
||||
}
|
||||
});
|
||||
|
||||
@ -40,11 +53,13 @@ async function main() {
|
||||
|
||||
{
|
||||
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({
|
||||
"dirPath": baseThemeDirPath,
|
||||
"returnedPathsType": "relative to dirPath"
|
||||
dirPath: baseThemeDirPath,
|
||||
returnedPathsType: "relative to dirPath"
|
||||
}).forEach(filePath => {
|
||||
const match = filePath.match(re);
|
||||
|
||||
@ -55,14 +70,21 @@ async function main() {
|
||||
const [, typeOfPage, language] = match;
|
||||
|
||||
(record[typeOfPage] ??= {})[language.replace(/_/g, "-")] = Object.fromEntries(
|
||||
Object.entries(propertiesParser.parse(fs.readFileSync(pathJoin(baseThemeDirPath, filePath)).toString("utf8"))).map(
|
||||
([key, value]: any) => [key === "locale_pt_BR" ? "locale_pt-BR" : key, value.replace(/''/g, "'")]
|
||||
)
|
||||
Object.entries(
|
||||
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 => {
|
||||
const recordForPageType = record[themeType];
|
||||
@ -71,19 +93,28 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseMessagesDirPath = pathJoin(thisCodebaseRootDirPath, "src", themeType, "i18n", "baseMessages");
|
||||
const baseMessagesDirPath = pathJoin(
|
||||
thisCodebaseRootDirPath,
|
||||
"src",
|
||||
themeType,
|
||||
"i18n",
|
||||
"baseMessages"
|
||||
);
|
||||
|
||||
const languages = Object.keys(recordForPageType);
|
||||
|
||||
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"
|
||||
].join("\n");
|
||||
|
||||
languages.forEach(language => {
|
||||
const filePath = pathJoin(baseMessagesDirPath, `${language}.ts`);
|
||||
|
||||
fs.mkdirSync(pathDirname(filePath), { "recursive": true });
|
||||
fs.mkdirSync(pathDirname(filePath), { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
@ -92,7 +123,11 @@ async function main() {
|
||||
generatedFileHeader,
|
||||
"",
|
||||
"/* spell-checker: disable */",
|
||||
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`,
|
||||
`const messages= ${JSON.stringify(
|
||||
recordForPageType[language],
|
||||
null,
|
||||
2
|
||||
)};`,
|
||||
"",
|
||||
"export default messages;",
|
||||
"/* spell-checker: enable */"
|
||||
@ -117,7 +152,10 @@ async function main() {
|
||||
` case "en": return en;`,
|
||||
...languages
|
||||
.filter(language => language !== "en")
|
||||
.map(language => ` case "${language}": return import("./${language}");`),
|
||||
.map(
|
||||
language =>
|
||||
` case "${language}": return import("./${language}");`
|
||||
),
|
||||
' default: return { "default": {} };',
|
||||
" }",
|
||||
" })();",
|
||||
|
@ -10,7 +10,8 @@ import { chmod, stat } from "fs/promises";
|
||||
const promises = Object.values<string>(bin).map(async scriptPath => {
|
||||
const fullPath = pathJoin(thisCodebaseRootDirPath, scriptPath);
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -13,20 +13,26 @@ fs.writeFileSync(
|
||||
Buffer.from(
|
||||
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 {
|
||||
...packageJsonParsed,
|
||||
"main": packageJsonParsed["main"]?.replace(/^dist\//, ""),
|
||||
"types": packageJsonParsed["types"]?.replace(/^dist\//, ""),
|
||||
"module": packageJsonParsed["module"]?.replace(/^dist\//, ""),
|
||||
"exports": !("exports" in packageJsonParsed)
|
||||
main: packageJsonParsed["main"]?.replace(/^dist\//, ""),
|
||||
types: packageJsonParsed["types"]?.replace(/^dist\//, ""),
|
||||
module: packageJsonParsed["module"]?.replace(/^dist\//, ""),
|
||||
exports: !("exports" in packageJsonParsed)
|
||||
? undefined
|
||||
: Object.fromEntries(
|
||||
Object.entries(packageJsonParsed["exports"]).map(([key, value]) => [
|
||||
key,
|
||||
(value as string).replace(/^\.\/dist\//, "./")
|
||||
])
|
||||
Object.entries(packageJsonParsed["exports"]).map(
|
||||
([key, value]) => [
|
||||
key,
|
||||
(value as string).replace(/^\.\/dist\//, "./")
|
||||
]
|
||||
)
|
||||
)
|
||||
};
|
||||
})(),
|
||||
@ -39,9 +45,9 @@ fs.writeFileSync(
|
||||
|
||||
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 = (() => {
|
||||
// For example [ "@emotion" ] it's more convenient than
|
||||
@ -53,7 +59,9 @@ const commonThirdPartyDeps = (() => {
|
||||
...namespaceSingletonDependencies
|
||||
.map(namespaceModuleName =>
|
||||
fs
|
||||
.readdirSync(pathJoin(rootDirPath, "node_modules", namespaceModuleName))
|
||||
.readdirSync(
|
||||
pathJoin(rootDirPath, "node_modules", namespaceModuleName)
|
||||
)
|
||||
.map(submoduleName => `${namespaceModuleName}/${submoduleName}`)
|
||||
)
|
||||
.reduce((prev, curr) => [...prev, ...curr], []),
|
||||
@ -63,21 +71,25 @@ const commonThirdPartyDeps = (() => {
|
||||
|
||||
const yarnGlobalDirPath = pathJoin(rootDirPath, ".yarn_home");
|
||||
|
||||
fs.rmSync(yarnGlobalDirPath, { "recursive": true, "force": true });
|
||||
fs.rmSync(yarnGlobalDirPath, { recursive: true, force: true });
|
||||
fs.mkdirSync(yarnGlobalDirPath);
|
||||
|
||||
const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => {
|
||||
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}`);
|
||||
|
||||
execSync(cmd, {
|
||||
cwd,
|
||||
"env": {
|
||||
env: {
|
||||
...process.env,
|
||||
"HOME": yarnGlobalDirPath
|
||||
HOME: yarnGlobalDirPath
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -93,7 +105,9 @@ const testAppPaths = (() => {
|
||||
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;
|
||||
})
|
||||
@ -105,7 +119,7 @@ if (testAppPaths.length === 0) {
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
testAppPaths.forEach(testAppPath => execSync("yarn install", { "cwd": testAppPath }));
|
||||
testAppPaths.forEach(testAppPath => execSync("yarn install", { cwd: testAppPath }));
|
||||
|
||||
console.log("=== Linking common dependencies ===");
|
||||
|
||||
@ -118,29 +132,37 @@ commonThirdPartyDeps.forEach(commonThirdPartyDep => {
|
||||
console.log(`${current}/${total} ${commonThirdPartyDep}`);
|
||||
|
||||
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 =>
|
||||
testAppPaths.forEach(testAppPath =>
|
||||
execYarnLink({
|
||||
"cwd": testAppPath,
|
||||
"targetModuleName": commonThirdPartyDep
|
||||
cwd: testAppPath,
|
||||
targetModuleName: commonThirdPartyDep
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
console.log("=== Linking in house dependencies ===");
|
||||
|
||||
execYarnLink({ "cwd": pathJoin(rootDirPath, "dist") });
|
||||
execYarnLink({ cwd: pathJoin(rootDirPath, "dist") });
|
||||
|
||||
testAppPaths.forEach(testAppPath =>
|
||||
execYarnLink({
|
||||
"cwd": testAppPath,
|
||||
"targetModuleName": JSON.parse(fs.readFileSync(pathJoin(rootDirPath, "package.json")).toString("utf8"))["name"]
|
||||
cwd: testAppPath,
|
||||
targetModuleName: JSON.parse(
|
||||
fs.readFileSync(pathJoin(rootDirPath, "package.json")).toString("utf8")
|
||||
)["name"]
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -2,16 +2,19 @@ import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
fs.rmSync("node_modules", { "recursive": true, "force": true });
|
||||
fs.rmSync("dist", { "recursive": true, "force": true });
|
||||
fs.rmSync(".yarn_home", { "recursive": true, "force": true });
|
||||
fs.rmSync("node_modules", { recursive: true, force: true });
|
||||
fs.rmSync("dist", { recursive: true, force: true });
|
||||
fs.rmSync(".yarn_home", { recursive: true, force: true });
|
||||
|
||||
run("yarn install");
|
||||
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`);
|
||||
|
||||
@ -20,5 +23,5 @@ run(`npx chokidar '${join("src", "**", "*")}' -c 'yarn build'`);
|
||||
function run(command: string, options?: { cwd: string }) {
|
||||
console.log(`$ ${command}`);
|
||||
|
||||
child_process.execSync(command, { "stdio": "inherit", ...options });
|
||||
child_process.execSync(command, { stdio: "inherit", ...options });
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
/**
|
||||
|
@ -24,13 +24,13 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
}, []);
|
||||
|
||||
useSetClassName({
|
||||
"qualifiedName": "html",
|
||||
"className": getClassName("kcHtmlClass")
|
||||
qualifiedName: "html",
|
||||
className: getClassName("kcHtmlClass")
|
||||
});
|
||||
|
||||
useSetClassName({
|
||||
"qualifiedName": "body",
|
||||
"className": clsx("admin-console", "user", getClassName("kcBodyClass"))
|
||||
qualifiedName: "body",
|
||||
className: clsx("admin-console", "user", getClassName("kcBodyClass"))
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@ -46,7 +46,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
}, []);
|
||||
|
||||
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
||||
"hrefs": !doUseDefaultCss
|
||||
hrefs: !doUseDefaultCss
|
||||
? []
|
||||
: [
|
||||
`${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`,
|
||||
|
@ -2,7 +2,10 @@ import type { ReactNode } from "react";
|
||||
import type { KcContext } from "./kcContext";
|
||||
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;
|
||||
i18n: I18nExtended;
|
||||
doUseDefaultCss: boolean;
|
||||
|
@ -91,19 +91,19 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
|
||||
setI18n({
|
||||
...createI18nTranslationFunctions({
|
||||
"fallbackMessages": {
|
||||
fallbackMessages: {
|
||||
...fallbackMessages,
|
||||
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
|
||||
...(extraMessages[fallbackLanguageTag] ?? {})
|
||||
} as any,
|
||||
"messages": {
|
||||
messages: {
|
||||
...(await getMessages(currentLanguageTag)),
|
||||
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
|
||||
...(extraMessages[currentLanguageTag] ?? {})
|
||||
} as any
|
||||
}),
|
||||
currentLanguageTag,
|
||||
"getChangeLocalUrl": newLanguageTag => {
|
||||
getChangeLocalUrl: newLanguageTag => {
|
||||
const { locale } = kcContext;
|
||||
|
||||
assert(locale !== undefined, "Internationalization not enabled");
|
||||
@ -114,7 +114,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
|
||||
return targetSupportedLocale.url;
|
||||
},
|
||||
"labelBySupportedLanguageTag": Object.fromEntries(
|
||||
labelBySupportedLanguageTag: Object.fromEntries(
|
||||
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
|
||||
)
|
||||
});
|
||||
@ -170,7 +170,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
})();
|
||||
|
||||
return doRenderMarkdown ? (
|
||||
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}>
|
||||
<Markdown allowDangerousHtml renderers={{ paragraph: "span" }}>
|
||||
{messageWithArgsInjectedIfAny}
|
||||
</Markdown>
|
||||
) : (
|
||||
@ -186,7 +186,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||
|
||||
const out = resolveMsg({
|
||||
"key": keyUnwrappedFromCurlyBraces,
|
||||
key: keyUnwrappedFromCurlyBraces,
|
||||
args,
|
||||
doRenderMarkdown
|
||||
});
|
||||
@ -195,36 +195,46 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
}
|
||||
|
||||
return {
|
||||
"msgStr": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": false }) as string,
|
||||
"msg": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsg": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsgStr": (key, ...args) => resolveMsgAdvanced({ 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,
|
||||
advancedMsg: (key, ...args) =>
|
||||
resolveMsgAdvanced({
|
||||
key,
|
||||
args,
|
||||
doRenderMarkdown: true
|
||||
}) as JSX.Element,
|
||||
advancedMsgStr: (key, ...args) =>
|
||||
resolveMsgAdvanced({
|
||||
key,
|
||||
args,
|
||||
doRenderMarkdown: false
|
||||
}) as string
|
||||
};
|
||||
}
|
||||
|
||||
const keycloakifyExtraMessages = {
|
||||
"en": {
|
||||
"shouldBeEqual": "{0} should be equal to {1}",
|
||||
"shouldBeDifferent": "{0} should be different to {1}",
|
||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||
"mustBeAnInteger": "Must be an integer",
|
||||
"notAValidOption": "Not a valid option",
|
||||
"newPasswordSameAsOld": "New password must be different from the old one",
|
||||
"passwordConfirmNotMatch": "Password confirmation does not match"
|
||||
en: {
|
||||
shouldBeEqual: "{0} should be equal to {1}",
|
||||
shouldBeDifferent: "{0} should be different to {1}",
|
||||
shouldMatchPattern: "Pattern should match: `/{0}/`",
|
||||
mustBeAnInteger: "Must be an integer",
|
||||
notAValidOption: "Not a valid option",
|
||||
newPasswordSameAsOld: "New password must be different from the old one",
|
||||
passwordConfirmNotMatch: "Password confirmation does not match"
|
||||
},
|
||||
"fr": {
|
||||
fr: {
|
||||
/* spell-checker: disable */
|
||||
"shouldBeEqual": "{0} doit être égal à {1}",
|
||||
"shouldBeDifferent": "{0} doit être différent de {1}",
|
||||
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
||||
"mustBeAnInteger": "Doit être un nombre entier",
|
||||
"notAValidOption": "N'est pas une option valide",
|
||||
shouldBeEqual: "{0} doit être égal à {1}",
|
||||
shouldBeDifferent: "{0} doit être différent de {1}",
|
||||
shouldMatchPattern: "Dois respecter le schéma: `/{0}/`",
|
||||
mustBeAnInteger: "Doit être un nombre entier",
|
||||
notAValidOption: "N'est pas une option valide",
|
||||
|
||||
"logoutConfirmTitle": "Déconnexion",
|
||||
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
||||
"doLogout": "Se déconnecter",
|
||||
"newPasswordSameAsOld": "Le nouveau mot de passe doit être différent de l'ancien",
|
||||
"passwordConfirmNotMatch": "La confirmation du mot de passe ne correspond pas"
|
||||
logoutConfirmTitle: "Déconnexion",
|
||||
logoutConfirmHeader: "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
||||
doLogout: "Se déconnecter",
|
||||
newPasswordSameAsOld: "Le nouveau mot de passe doit être différent de l'ancien",
|
||||
passwordConfirmNotMatch: "La confirmation du mot de passe ne correspond pas"
|
||||
/* spell-checker: enable */
|
||||
}
|
||||
};
|
||||
|
@ -68,7 +68,10 @@ export declare namespace KcContext {
|
||||
* @param text to return
|
||||
* @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
|
||||
*
|
||||
|
@ -4,17 +4,28 @@ import { isStorybook } from "keycloakify/lib/isStorybook";
|
||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||
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>>[];
|
||||
mockProperties?: Record<string, string>;
|
||||
}) {
|
||||
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;
|
||||
storyPartialKcContext?: DeepPartial<Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>>;
|
||||
storyPartialKcContext?: DeepPartial<
|
||||
Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>
|
||||
>;
|
||||
}): {
|
||||
kcContext: PageId extends undefined
|
||||
? ExtendKcContext<KcContextExtension> | undefined
|
||||
@ -32,34 +43,46 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
||||
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 out: DeepPartial<ExtendKcContext<KcContextExtension>> = {};
|
||||
|
||||
const mockDataPick = mockData?.find(({ pageId }) => pageId === mockPageId);
|
||||
const mockDataPick = mockData?.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
if (mockDataPick !== undefined) {
|
||||
deepAssign({
|
||||
"target": out,
|
||||
"source": mockDataPick
|
||||
target: out,
|
||||
source: mockDataPick
|
||||
});
|
||||
}
|
||||
|
||||
if (storyPartialKcContext !== undefined) {
|
||||
deepAssign({
|
||||
"target": out,
|
||||
"source": storyPartialKcContext
|
||||
target: out,
|
||||
source: storyPartialKcContext
|
||||
});
|
||||
}
|
||||
|
||||
return Object.keys(out).length === 0 ? undefined : out;
|
||||
})();
|
||||
|
||||
if (kcContextDefaultMock === undefined && partialKcContextCustomMock === undefined) {
|
||||
if (
|
||||
kcContextDefaultMock === undefined &&
|
||||
partialKcContextCustomMock === undefined
|
||||
) {
|
||||
console.warn(
|
||||
[
|
||||
`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 = {};
|
||||
|
||||
deepAssign({
|
||||
"target": kcContext,
|
||||
"source": kcContextDefaultMock !== undefined ? kcContextDefaultMock : { "pageId": mockPageId, ...kcContextCommonMock }
|
||||
target: kcContext,
|
||||
source:
|
||||
kcContextDefaultMock !== undefined
|
||||
? kcContextDefaultMock
|
||||
: { pageId: mockPageId, ...kcContextCommonMock }
|
||||
});
|
||||
|
||||
if (partialKcContextCustomMock !== undefined) {
|
||||
deepAssign({
|
||||
"target": kcContext,
|
||||
"source": partialKcContextCustomMock
|
||||
target: kcContext,
|
||||
source: partialKcContextCustomMock
|
||||
});
|
||||
}
|
||||
|
||||
if (mockProperties !== undefined) {
|
||||
deepAssign({
|
||||
"target": kcContext.properties,
|
||||
"source": mockProperties
|
||||
target: kcContext.properties,
|
||||
source: mockProperties
|
||||
});
|
||||
}
|
||||
|
||||
@ -94,14 +120,14 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
||||
}
|
||||
|
||||
if (realKcContext === undefined) {
|
||||
return { "kcContext": undefined as any };
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
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 };
|
||||
|
@ -5,7 +5,9 @@ import { createGetKcContext } from "./createGetKcContext";
|
||||
/** 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
|
||||
*/
|
||||
export function getKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||
export function getKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockPageId?: ExtendKcContext<KcContextExtension>["pageId"];
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
}): { kcContext: ExtendKcContext<KcContextExtension> | undefined } {
|
||||
|
@ -2,10 +2,14 @@ import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminati
|
||||
import { nameOfTheGlobal } from "keycloakify/bin/shared/constants";
|
||||
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
|
||||
: 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];
|
||||
}
|
||||
|
@ -7,254 +7,258 @@ import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
||||
const resourcesPath = `${BASE_URL}${keycloak_resources}/account/resources`;
|
||||
|
||||
export const kcContextCommonMock: KcContext.Common = {
|
||||
"themeVersion": "0.0.0",
|
||||
"keycloakifyVersion": "0.0.0",
|
||||
"themeType": "account",
|
||||
"themeName": "my-theme-name",
|
||||
"url": {
|
||||
themeVersion: "0.0.0",
|
||||
keycloakifyVersion: "0.0.0",
|
||||
themeType: "account",
|
||||
themeName: "my-theme-name",
|
||||
url: {
|
||||
resourcesPath,
|
||||
"resourcesCommonPath": `${resourcesPath}/${resources_common}`,
|
||||
"resourceUrl": "#",
|
||||
"accountUrl": "#",
|
||||
"applicationsUrl": "#",
|
||||
"logoutUrl": "#",
|
||||
"getLogoutUrl": () => "#",
|
||||
"logUrl": "#",
|
||||
"passwordUrl": "#",
|
||||
"sessionsUrl": "#",
|
||||
"socialUrl": "#",
|
||||
"totpUrl": "#"
|
||||
resourcesCommonPath: `${resourcesPath}/${resources_common}`,
|
||||
resourceUrl: "#",
|
||||
accountUrl: "#",
|
||||
applicationsUrl: "#",
|
||||
logoutUrl: "#",
|
||||
getLogoutUrl: () => "#",
|
||||
logUrl: "#",
|
||||
passwordUrl: "#",
|
||||
sessionsUrl: "#",
|
||||
socialUrl: "#",
|
||||
totpUrl: "#"
|
||||
},
|
||||
"realm": {
|
||||
"internationalizationEnabled": true,
|
||||
"userManagedAccessAllowed": true
|
||||
realm: {
|
||||
internationalizationEnabled: true,
|
||||
userManagedAccessAllowed: true
|
||||
},
|
||||
"messagesPerField": {
|
||||
"printIfExists": () => {
|
||||
messagesPerField: {
|
||||
printIfExists: () => {
|
||||
return undefined;
|
||||
},
|
||||
"existsError": () => false,
|
||||
"get": key => `Fake error for ${key}`,
|
||||
"exists": () => false
|
||||
existsError: () => false,
|
||||
get: key => `Fake error for ${key}`,
|
||||
exists: () => false
|
||||
},
|
||||
"locale": {
|
||||
"supported": [
|
||||
locale: {
|
||||
supported: [
|
||||
/* 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",
|
||||
"label": "Deutsch",
|
||||
"languageTag": "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",
|
||||
languageTag: "de"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||
"label": "Norsk",
|
||||
"languageTag": "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",
|
||||
languageTag: "no"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
"label": "Русский",
|
||||
"languageTag": "ru"
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
label: "Русский",
|
||||
languageTag: "ru"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||
"label": "Svenska",
|
||||
"languageTag": "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",
|
||||
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",
|
||||
"label": "Português (Brasil)",
|
||||
"languageTag": "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)",
|
||||
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",
|
||||
"label": "Lietuvių",
|
||||
"languageTag": "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ų",
|
||||
languageTag: "lt"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||
"label": "English",
|
||||
"languageTag": "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",
|
||||
languageTag: "en"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||
"label": "Italiano",
|
||||
"languageTag": "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",
|
||||
languageTag: "it"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
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",
|
||||
"label": "中文简体",
|
||||
"languageTag": "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: "中文简体",
|
||||
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",
|
||||
"label": "Español",
|
||||
"languageTag": "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",
|
||||
languageTag: "es"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
languageTag: "cs"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
"label": "日本語",
|
||||
"languageTag": "ja"
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
label: "日本語",
|
||||
languageTag: "ja"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
languageTag: "sk"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||
"label": "Polski",
|
||||
"languageTag": "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",
|
||||
languageTag: "pl"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||
"label": "Català",
|
||||
"languageTag": "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à",
|
||||
languageTag: "ca"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||
"label": "Nederlands",
|
||||
"languageTag": "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",
|
||||
languageTag: "nl"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
languageTag: "tr"
|
||||
}
|
||||
/* spell-checker: enable */
|
||||
],
|
||||
"currentLanguageTag": "en"
|
||||
currentLanguageTag: "en"
|
||||
},
|
||||
"features": {
|
||||
"authorization": true,
|
||||
"identityFederation": true,
|
||||
"log": true,
|
||||
"passwordUpdateSupported": true
|
||||
features: {
|
||||
authorization: true,
|
||||
identityFederation: true,
|
||||
log: true,
|
||||
passwordUpdateSupported: true
|
||||
},
|
||||
"referrer": undefined,
|
||||
"account": {
|
||||
"firstName": "john",
|
||||
"lastName": "doe",
|
||||
"email": "john.doe@code.gouv.fr",
|
||||
"username": "doe_j"
|
||||
referrer: undefined,
|
||||
account: {
|
||||
firstName: "john",
|
||||
lastName: "doe",
|
||||
email: "john.doe@code.gouv.fr",
|
||||
username: "doe_j"
|
||||
},
|
||||
"properties": {
|
||||
"parent": "account-v1",
|
||||
"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",
|
||||
"kcButtonPrimaryClass": "btn-primary",
|
||||
"accountResourceProvider": "account-v1",
|
||||
"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",
|
||||
"kcButtonDefaultClass": "btn-default"
|
||||
properties: {
|
||||
parent: "account-v1",
|
||||
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",
|
||||
kcButtonPrimaryClass: "btn-primary",
|
||||
accountResourceProvider: "account-v1",
|
||||
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",
|
||||
kcButtonDefaultClass: "btn-default"
|
||||
}
|
||||
};
|
||||
|
||||
export const kcContextMocks: KcContext[] = [
|
||||
id<KcContext.Password>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "password.ftl",
|
||||
"password": {
|
||||
"passwordSet": true
|
||||
pageId: "password.ftl",
|
||||
password: {
|
||||
passwordSet: true
|
||||
},
|
||||
"stateChecker": "state checker"
|
||||
stateChecker: "state checker"
|
||||
}),
|
||||
id<KcContext.Account>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "account.ftl",
|
||||
"url": {
|
||||
pageId: "account.ftl",
|
||||
url: {
|
||||
...kcContextCommonMock.url,
|
||||
"referrerURI": "#",
|
||||
"accountUrl": "#"
|
||||
referrerURI: "#",
|
||||
accountUrl: "#"
|
||||
},
|
||||
"realm": {
|
||||
realm: {
|
||||
...kcContextCommonMock.realm,
|
||||
"registrationEmailAsUsername": true,
|
||||
"editUsernameAllowed": true
|
||||
registrationEmailAsUsername: true,
|
||||
editUsernameAllowed: true
|
||||
},
|
||||
"stateChecker": ""
|
||||
stateChecker: ""
|
||||
}),
|
||||
id<KcContext.Sessions>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "sessions.ftl",
|
||||
"sessions": {
|
||||
"sessions": [
|
||||
pageId: "sessions.ftl",
|
||||
sessions: {
|
||||
sessions: [
|
||||
{
|
||||
"ipAddress": "127.0.0.1",
|
||||
"started": new Date().toString(),
|
||||
"lastAccess": new Date().toString(),
|
||||
"expires": new Date().toString(),
|
||||
"clients": ["Chrome", "Firefox"],
|
||||
"id": "f8951177-817d-4a70-9c02-86d3c170fe51"
|
||||
ipAddress: "127.0.0.1",
|
||||
started: new Date().toString(),
|
||||
lastAccess: new Date().toString(),
|
||||
expires: new Date().toString(),
|
||||
clients: ["Chrome", "Firefox"],
|
||||
id: "f8951177-817d-4a70-9c02-86d3c170fe51"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stateChecker": "g6WB1FaYnKotTkiy7ZrlxvFztSqS0U8jvHsOOOb2z4g"
|
||||
stateChecker: "g6WB1FaYnKotTkiy7ZrlxvFztSqS0U8jvHsOOOb2z4g"
|
||||
}),
|
||||
id<KcContext.Totp>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "totp.ftl",
|
||||
"totp": {
|
||||
"enabled": true,
|
||||
"totpSecretEncoded": "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
|
||||
"qrUrl": "#",
|
||||
"totpSecretQrCode":
|
||||
pageId: "totp.ftl",
|
||||
totp: {
|
||||
enabled: true,
|
||||
totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
|
||||
qrUrl: "#",
|
||||
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=",
|
||||
"manualUrl": "#",
|
||||
"totpSecret": "G4nsI8lQagRMUchH8jEG",
|
||||
"otpCredentials": [],
|
||||
"supportedApplications": ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
||||
"policy": {
|
||||
"algorithm": "HmacSHA1",
|
||||
"digits": 6,
|
||||
"lookAheadWindow": 1,
|
||||
"type": "totp",
|
||||
"period": 30
|
||||
manualUrl: "#",
|
||||
totpSecret: "G4nsI8lQagRMUchH8jEG",
|
||||
otpCredentials: [],
|
||||
supportedApplications: [
|
||||
"totpAppFreeOTPName",
|
||||
"totpAppMicrosoftAuthenticatorName",
|
||||
"totpAppGoogleName"
|
||||
],
|
||||
policy: {
|
||||
algorithm: "HmacSHA1",
|
||||
digits: 6,
|
||||
lookAheadWindow: 1,
|
||||
type: "totp",
|
||||
period: 30
|
||||
}
|
||||
},
|
||||
"mode": "qr",
|
||||
"isAppInitiatedAction": false,
|
||||
"stateChecker": ""
|
||||
mode: "qr",
|
||||
isAppInitiatedAction: false,
|
||||
stateChecker: ""
|
||||
}),
|
||||
id<KcContext.Log>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "log.ftl",
|
||||
"log": {
|
||||
"events": [
|
||||
pageId: "log.ftl",
|
||||
log: {
|
||||
events: [
|
||||
{
|
||||
"date": "2/21/2024, 1:28:39 PM",
|
||||
"event": "login",
|
||||
"ipAddress": "172.17.0.1",
|
||||
"client": "security-admin-console",
|
||||
"details": [{ key: "openid-connect", value: "admin" }]
|
||||
date: "2/21/2024, 1:28:39 PM",
|
||||
event: "login",
|
||||
ipAddress: "172.17.0.1",
|
||||
client: "security-admin-console",
|
||||
details: [{ key: "openid-connect", value: "admin" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
id<KcContext.FederatedIdentity>({
|
||||
...kcContextCommonMock,
|
||||
"stateChecker": "",
|
||||
"pageId": "federatedIdentity.ftl",
|
||||
"federatedIdentity": {
|
||||
"identities": [
|
||||
stateChecker: "",
|
||||
pageId: "federatedIdentity.ftl",
|
||||
federatedIdentity: {
|
||||
identities: [
|
||||
{
|
||||
"providerId": "keycloak-oidc",
|
||||
"displayName": "keycloak-oidc",
|
||||
"userName": "John",
|
||||
"connected": true
|
||||
providerId: "keycloak-oidc",
|
||||
displayName: "keycloak-oidc",
|
||||
userName: "John",
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
"removeLinkPossible": true
|
||||
removeLinkPossible: true
|
||||
}
|
||||
})
|
||||
];
|
||||
|
@ -2,19 +2,20 @@ import { createUseClassName } from "keycloakify/lib/useGetClassName";
|
||||
import type { ClassKey } from "keycloakify/account/TemplateProps";
|
||||
|
||||
export const { useGetClassName } = createUseClassName<ClassKey>({
|
||||
"defaultClasses": {
|
||||
"kcHtmlClass": undefined,
|
||||
"kcBodyClass": undefined,
|
||||
"kcButtonClass": "btn",
|
||||
"kcContentWrapperClass": "row",
|
||||
"kcButtonPrimaryClass": "btn-primary",
|
||||
"kcButtonLargeClass": "btn-lg",
|
||||
"kcButtonDefaultClass": "btn-default",
|
||||
"kcFormClass": "form-horizontal",
|
||||
"kcFormGroupClass": "form-group",
|
||||
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
"kcLabelClass": "control-label",
|
||||
"kcInputClass": "form-control",
|
||||
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text"
|
||||
defaultClasses: {
|
||||
kcHtmlClass: undefined,
|
||||
kcBodyClass: undefined,
|
||||
kcButtonClass: "btn",
|
||||
kcContentWrapperClass: "row",
|
||||
kcButtonPrimaryClass: "btn-primary",
|
||||
kcButtonLargeClass: "btn-lg",
|
||||
kcButtonDefaultClass: "btn-default",
|
||||
kcFormClass: "form-horizontal",
|
||||
kcFormGroupClass: "form-group",
|
||||
kcInputWrapperClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
kcLabelClass: "control-label",
|
||||
kcInputClass: "form-control",
|
||||
kcInputErrorMessageClass:
|
||||
"pf-c-form__helper-text pf-m-error required kc-feedback-text"
|
||||
}
|
||||
});
|
||||
|
@ -9,9 +9,9 @@ export default function Account(props: PageProps<Extract<KcContext, { pageId: "a
|
||||
|
||||
const { getClassName } = useGetClassName({
|
||||
doUseDefaultCss,
|
||||
"classes": {
|
||||
classes: {
|
||||
...classes,
|
||||
"kcBodyClass": clsx(classes?.kcBodyClass, "user")
|
||||
kcBodyClass: clsx(classes?.kcBodyClass, "user")
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -10,9 +10,9 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
|
||||
|
||||
const { getClassName } = useGetClassName({
|
||||
doUseDefaultCss,
|
||||
"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,
|
||||
"message": (() => {
|
||||
message: (() => {
|
||||
if (newPasswordError !== "") {
|
||||
return {
|
||||
"type": "error",
|
||||
"summary": newPasswordError
|
||||
type: "error",
|
||||
summary: newPasswordError
|
||||
};
|
||||
}
|
||||
|
||||
if (newPasswordConfirmError !== "") {
|
||||
return {
|
||||
"type": "error",
|
||||
"summary": newPasswordConfirmError
|
||||
type: "error",
|
||||
summary: newPasswordConfirmError
|
||||
};
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ export default function Password(props: PageProps<Extract<KcContext, { pageId: "
|
||||
value={account.username ?? ""}
|
||||
autoComplete="username"
|
||||
readOnly
|
||||
style={{ "display": "none" }}
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
|
||||
{password.passwordSet && (
|
||||
|
@ -18,9 +18,9 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
|
||||
"HmacSHA1": "SHA1",
|
||||
"HmacSHA256": "SHA256",
|
||||
"HmacSHA512": "SHA512"
|
||||
HmacSHA1: "SHA1",
|
||||
HmacSHA256: "SHA256",
|
||||
HmacSHA512: "SHA512"
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -8,9 +8,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
const buildOptions = readBuildOptions({ cliCommandOptions });
|
||||
|
||||
await copyKeycloakResourcesToPublic({
|
||||
"buildOptions": {
|
||||
buildOptions: {
|
||||
...buildOptions,
|
||||
"publicDirPath": buildOptions.reactAppRootDirPath
|
||||
publicDirPath: buildOptions.reactAppRootDirPath
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -12,22 +12,39 @@ export async function command(params: { 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({
|
||||
"startingFromMajor": undefined,
|
||||
"cacheDirPath": buildOptions.cacheDirPath
|
||||
startingFromMajor: undefined,
|
||||
cacheDirPath: buildOptions.cacheDirPath
|
||||
});
|
||||
|
||||
console.log(`→ ${keycloakVersion}`);
|
||||
|
||||
const destDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme");
|
||||
const destDirPath = pathJoin(
|
||||
buildOptions.keycloakifyBuildDirPath,
|
||||
"src",
|
||||
"main",
|
||||
"resources",
|
||||
"theme"
|
||||
);
|
||||
|
||||
console.log(
|
||||
[
|
||||
`Downloading builtins theme of Keycloak ${keycloakVersion} here:`,
|
||||
`- ${chalk.bold(`.${pathSep}${pathJoin(pathRelative(process.cwd(), destDirPath), "base")}`)}`,
|
||||
`- ${chalk.bold(`.${pathSep}${pathJoin(pathRelative(process.cwd(), destDirPath), "keycloak")}`)}`
|
||||
`- ${chalk.bold(
|
||||
`.${pathSep}${pathJoin(pathRelative(process.cwd(), destDirPath), "base")}`
|
||||
)}`,
|
||||
`- ${chalk.bold(
|
||||
`.${pathSep}${pathJoin(
|
||||
pathRelative(process.cwd(), destDirPath),
|
||||
"keycloak"
|
||||
)}`
|
||||
)}`
|
||||
].join("\n")
|
||||
);
|
||||
|
||||
|
@ -30,7 +30,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
console.log(chalk.cyan("Theme type:"));
|
||||
|
||||
const { value: themeType } = await cliSelect<ThemeType>({
|
||||
"values": [...themeTypes]
|
||||
values: [...themeTypes]
|
||||
}).catch(() => {
|
||||
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:"));
|
||||
|
||||
const { value: pageId } = await cliSelect<LoginThemePageId | AccountThemePageId>({
|
||||
"values": (() => {
|
||||
values: (() => {
|
||||
switch (themeType) {
|
||||
case "login":
|
||||
return [...loginThemePageIds];
|
||||
@ -55,14 +55,29 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
@ -71,12 +86,20 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
const targetDirPath = pathDirname(targetFilePath);
|
||||
|
||||
if (!fs.existsSync(targetDirPath)) {
|
||||
fs.mkdirSync(targetDirPath, { "recursive": true });
|
||||
fs.mkdirSync(targetDirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
const componentPageContent = fs
|
||||
.readFileSync(pathJoin(getThisCodebaseRootDirPath(), "src", themeType, "pages", componentPageBasename))
|
||||
.readFileSync(
|
||||
pathJoin(
|
||||
getThisCodebaseRootDirPath(),
|
||||
"src",
|
||||
themeType,
|
||||
"pages",
|
||||
componentPageBasename
|
||||
)
|
||||
)
|
||||
.toString("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:`,
|
||||
``,
|
||||
`${chalk.bold(pathJoin(".", pathRelative(process.cwd(), themeSrcDirPath), themeType, "KcApp.tsx"))}:`,
|
||||
`${chalk.bold(
|
||||
pathJoin(
|
||||
".",
|
||||
pathRelative(process.cwd(), themeSrcDirPath),
|
||||
themeType,
|
||||
"KcApp.tsx"
|
||||
)
|
||||
)}:`,
|
||||
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; }) {`,
|
||||
@ -114,7 +149,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
`+ Template={Template}`,
|
||||
...(!componentPageContent.includes(userProfileFormFieldComponentName)
|
||||
? []
|
||||
: [`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`]),
|
||||
: [
|
||||
`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`
|
||||
]),
|
||||
`+ doUseDefaultCss={true}`,
|
||||
`+ />`,
|
||||
`+ );`,
|
||||
|
@ -14,13 +14,18 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
const buildOptions = readBuildOptions({ cliCommandOptions });
|
||||
|
||||
const { themeSrcDirPath } = getThemeSrcDirPath({
|
||||
"reactAppRootDirPath": buildOptions.reactAppRootDirPath
|
||||
reactAppRootDirPath: buildOptions.reactAppRootDirPath
|
||||
});
|
||||
|
||||
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
|
||||
|
||||
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);
|
||||
}
|
||||
@ -29,33 +34,50 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
|
||||
const { keycloakVersion } = await promptKeycloakVersion({
|
||||
// NOTE: This is arbitrary
|
||||
"startingFromMajor": 17,
|
||||
"cacheDirPath": buildOptions.cacheDirPath
|
||||
startingFromMajor: 17,
|
||||
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({
|
||||
keycloakVersion,
|
||||
"destDirPath": builtinKeycloakThemeTmpDirPath,
|
||||
destDirPath: builtinKeycloakThemeTmpDirPath,
|
||||
buildOptions
|
||||
});
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"),
|
||||
"destDirPath": emailThemeSrcDirPath
|
||||
srcDirPath: pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"),
|
||||
destDirPath: emailThemeSrcDirPath
|
||||
});
|
||||
|
||||
{
|
||||
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.");
|
||||
|
||||
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true });
|
||||
rmSync(builtinKeycloakThemeTmpDirPath, { recursive: true });
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
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 { transformCodebase } from "../../tools/transformCodebase";
|
||||
import type { BuildOptions } from "../../shared/buildOptions";
|
||||
import * as fs from "fs/promises";
|
||||
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 { isInside } from "../../tools/isInside";
|
||||
import child_process from "child_process";
|
||||
@ -29,26 +35,53 @@ export async function buildJar(params: {
|
||||
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
|
||||
buildOptions: BuildOptionsLike;
|
||||
}): 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 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) {
|
||||
return { "modifiedSourceCode": sourceCode };
|
||||
return { modifiedSourceCode: sourceCode };
|
||||
}
|
||||
|
||||
for (const themeName of includingAccountV1ThemeNames) {
|
||||
if (isInside({ "dirPath": pathJoin("src", "main", "resources", "theme", themeName), "filePath": fileRelativePath })) {
|
||||
return { "modifiedSourceCode": sourceCode };
|
||||
if (
|
||||
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 } = (() => {
|
||||
if (keycloakAccountV1Version !== null) {
|
||||
return { "transformCodebase_patchForUsingBuiltinAccountV1": undefined };
|
||||
return {
|
||||
transformCodebase_patchForUsingBuiltinAccountV1: undefined
|
||||
};
|
||||
}
|
||||
|
||||
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"] = ({
|
||||
fileRelativePath,
|
||||
sourceCode
|
||||
}) => {
|
||||
if (isInside({ "dirPath": accountV1RelativeDirPath, "filePath": fileRelativePath })) {
|
||||
const transformCodebase_patchForUsingBuiltinAccountV1: Param0<
|
||||
typeof transformCodebase
|
||||
>["transformSourceCode"] = ({ fileRelativePath, sourceCode }) => {
|
||||
if (
|
||||
isInside({
|
||||
dirPath: accountV1RelativeDirPath,
|
||||
filePath: fileRelativePath
|
||||
})
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (fileRelativePath === metaInfKeycloakThemesJsonRelativePath) {
|
||||
const keycloakThemesJsonParsed = JSON.parse(sourceCode.toString("utf8")) as {
|
||||
const keycloakThemesJsonParsed = JSON.parse(
|
||||
sourceCode.toString("utf8")
|
||||
) as {
|
||||
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)) {
|
||||
const modifiedSourceCode = Buffer.from(
|
||||
sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
|
||||
sourceCode
|
||||
.toString("utf8")
|
||||
.replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
@ -99,16 +166,16 @@ export async function buildJar(params: {
|
||||
return { modifiedSourceCode };
|
||||
}
|
||||
|
||||
return { "modifiedSourceCode": sourceCode };
|
||||
return { modifiedSourceCode: sourceCode };
|
||||
};
|
||||
|
||||
return { transformCodebase_patchForUsingBuiltinAccountV1 };
|
||||
})();
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": buildOptions.keycloakifyBuildDirPath,
|
||||
"destDirPath": keycloakifyBuildTmpDirPath,
|
||||
"transformSourceCode": params => {
|
||||
srcDirPath: buildOptions.keycloakifyBuildDirPath,
|
||||
destDirPath: keycloakifyBuildTmpDirPath,
|
||||
transformSourceCode: params => {
|
||||
const resultCommon = transformCodebase_common(params);
|
||||
|
||||
if (resultCommon === undefined) {
|
||||
@ -125,7 +192,7 @@ export async function buildJar(params: {
|
||||
|
||||
return transformCodebase_patchForUsingBuiltinAccountV1?.({
|
||||
...params,
|
||||
"sourceCode": modifiedSourceCode
|
||||
sourceCode: modifiedSourceCode
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -152,7 +219,16 @@ export async function buildJar(params: {
|
||||
|
||||
(["register.ftl", "login-update-profile.ftl"] as const).forEach(pageId =>
|
||||
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");
|
||||
|
||||
@ -173,7 +249,10 @@ export async function buildJar(params: {
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
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) =>
|
||||
child_process.exec("mvn clean install", { "cwd": keycloakifyBuildTmpDirPath }, error => {
|
||||
if (error !== null) {
|
||||
console.error(
|
||||
`Build jar failed: ${JSON.stringify(
|
||||
{
|
||||
jarFileBasename,
|
||||
keycloakAccountV1Version,
|
||||
keycloakThemeAdditionalInfoExtensionVersion
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
child_process.exec(
|
||||
"mvn clean install",
|
||||
{ cwd: keycloakifyBuildTmpDirPath },
|
||||
error => {
|
||||
if (error !== null) {
|
||||
console.error(
|
||||
`Build jar failed: ${JSON.stringify(
|
||||
{
|
||||
jarFileBasename,
|
||||
keycloakAccountV1Version,
|
||||
keycloakThemeAdditionalInfoExtensionVersion
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
|
||||
reject(error);
|
||||
return;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
await fs.rename(
|
||||
pathJoin(keycloakifyBuildTmpDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`),
|
||||
pathJoin(
|
||||
keycloakifyBuildTmpDirPath,
|
||||
"target",
|
||||
`${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`
|
||||
),
|
||||
pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)
|
||||
);
|
||||
|
||||
rmSync(keycloakifyBuildTmpDirPath, { "recursive": true });
|
||||
rmSync(keycloakifyBuildTmpDirPath, { recursive: true });
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { assert } from "tsafe/assert";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import { keycloakAccountV1Versions, keycloakThemeAdditionalInfoExtensionVersions } from "./extensionVersions";
|
||||
import {
|
||||
keycloakAccountV1Versions,
|
||||
keycloakThemeAdditionalInfoExtensionVersions
|
||||
} from "./extensionVersions";
|
||||
import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar";
|
||||
import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar";
|
||||
import type { BuildOptions } from "../../shared/buildOptions";
|
||||
@ -14,11 +17,13 @@ export type BuildOptionsLike = BuildOptionsLike_buildJar & {
|
||||
|
||||
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 doesImplementAccountTheme = readMetaInfKeycloakThemes({
|
||||
"keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath
|
||||
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
|
||||
}).themes.some(({ name }) => name === accountV1ThemeName);
|
||||
|
||||
await Promise.all(
|
||||
@ -36,24 +41,38 @@ export async function buildJars(params: { buildOptions: BuildOptionsLike }): Pro
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange };
|
||||
})
|
||||
.filter(exclude(undefined))
|
||||
.map(({ keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange }) => {
|
||||
const { jarFileBasename } = getJarFileBasename({ keycloakVersionRange });
|
||||
|
||||
return {
|
||||
keycloakThemeAdditionalInfoExtensionVersion,
|
||||
jarFileBasename
|
||||
keycloakVersionRange
|
||||
};
|
||||
})
|
||||
.map(({ keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }) =>
|
||||
buildJar({
|
||||
jarFileBasename,
|
||||
keycloakAccountV1Version,
|
||||
.filter(exclude(undefined))
|
||||
.map(
|
||||
({
|
||||
keycloakThemeAdditionalInfoExtensionVersion,
|
||||
buildOptions
|
||||
})
|
||||
keycloakVersionRange
|
||||
}) => {
|
||||
const { jarFileBasename } = getJarFileBasename({
|
||||
keycloakVersionRange
|
||||
});
|
||||
|
||||
return {
|
||||
keycloakThemeAdditionalInfoExtensionVersion,
|
||||
jarFileBasename
|
||||
};
|
||||
}
|
||||
)
|
||||
.map(
|
||||
({
|
||||
keycloakThemeAdditionalInfoExtensionVersion,
|
||||
jarFileBasename
|
||||
}) =>
|
||||
buildJar({
|
||||
jarFileBasename,
|
||||
keycloakAccountV1Version,
|
||||
keycloakThemeAdditionalInfoExtensionVersion,
|
||||
buildOptions
|
||||
})
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
|
@ -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://github.com/jcputney/keycloak-theme-additional-info-extension
|
||||
* */
|
||||
export type KeycloakThemeAdditionalInfoExtensionVersion = (typeof keycloakThemeAdditionalInfoExtensionVersions)[number];
|
||||
export type KeycloakThemeAdditionalInfoExtensionVersion =
|
||||
(typeof keycloakThemeAdditionalInfoExtensionVersions)[number];
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { BuildOptions } from "../../shared/buildOptions";
|
||||
import type { KeycloakAccountV1Version, KeycloakThemeAdditionalInfoExtensionVersion } from "./extensionVersions";
|
||||
import type {
|
||||
KeycloakAccountV1Version,
|
||||
KeycloakThemeAdditionalInfoExtensionVersion
|
||||
} from "./extensionVersions";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
groupId: string;
|
||||
@ -15,7 +18,11 @@ export function generatePom(params: {
|
||||
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
|
||||
buildOptions: BuildOptionsLike;
|
||||
}) {
|
||||
const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, buildOptions } = params;
|
||||
const {
|
||||
keycloakAccountV1Version,
|
||||
keycloakThemeAdditionalInfoExtensionVersion,
|
||||
buildOptions
|
||||
} = params;
|
||||
|
||||
const { pomFileCode } = (function generatePomFileCode(): {
|
||||
pomFileCode: string;
|
||||
@ -35,7 +42,8 @@ export function generatePom(params: {
|
||||
` <properties>`,
|
||||
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
||||
` </properties>`,
|
||||
...(keycloakAccountV1Version !== null && keycloakThemeAdditionalInfoExtensionVersion !== null
|
||||
...(keycloakAccountV1Version !== null &&
|
||||
keycloakThemeAdditionalInfoExtensionVersion !== null
|
||||
? [
|
||||
` <build>`,
|
||||
` <plugins>`,
|
||||
|
@ -1,5 +1,8 @@
|
||||
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";
|
||||
|
||||
export function getKeycloakVersionRangeForJar(params: {
|
||||
@ -7,7 +10,11 @@ export function getKeycloakVersionRangeForJar(params: {
|
||||
keycloakAccountV1Version: KeycloakAccountV1Version;
|
||||
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
|
||||
}): KeycloakVersionRange | undefined {
|
||||
const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, doesImplementAccountTheme } = params;
|
||||
const {
|
||||
keycloakAccountV1Version,
|
||||
keycloakThemeAdditionalInfoExtensionVersion,
|
||||
doesImplementAccountTheme
|
||||
} = params;
|
||||
|
||||
if (doesImplementAccountTheme) {
|
||||
const keycloakVersionRange = (() => {
|
||||
@ -19,7 +26,9 @@ export function getKeycloakVersionRangeForJar(params: {
|
||||
case "1.1.5":
|
||||
return undefined;
|
||||
}
|
||||
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
||||
assert<
|
||||
Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>
|
||||
>(false);
|
||||
case "0.3":
|
||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||
case null:
|
||||
@ -27,7 +36,9 @@ export function getKeycloakVersionRangeForJar(params: {
|
||||
case "1.1.5":
|
||||
return "23" as const;
|
||||
}
|
||||
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
||||
assert<
|
||||
Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>
|
||||
>(false);
|
||||
case "0.4":
|
||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||
case null:
|
||||
@ -35,11 +46,18 @@ export function getKeycloakVersionRangeForJar(params: {
|
||||
case "1.1.5":
|
||||
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;
|
||||
} else {
|
||||
@ -54,10 +72,17 @@ export function getKeycloakVersionRangeForJar(params: {
|
||||
case "1.1.5":
|
||||
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;
|
||||
}
|
||||
|
@ -6,7 +6,12 @@ import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import type { BuildOptions } from "../../shared/buildOptions";
|
||||
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";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
@ -28,7 +33,15 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
themeType: ThemeType;
|
||||
fieldNames: string[];
|
||||
}) {
|
||||
const { themeName, cssGlobalsToDefine, indexHtmlCode, buildOptions, keycloakifyVersion, themeType, fieldNames } = params;
|
||||
const {
|
||||
themeName,
|
||||
cssGlobalsToDefine,
|
||||
indexHtmlCode,
|
||||
buildOptions,
|
||||
keycloakifyVersion,
|
||||
themeType,
|
||||
fieldNames
|
||||
} = params;
|
||||
|
||||
const $ = cheerio.load(indexHtmlCode);
|
||||
|
||||
@ -38,7 +51,10 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
|
||||
assert(jsCode !== null);
|
||||
|
||||
const { fixedJsCode } = replaceImportsInJsCode({ jsCode, buildOptions });
|
||||
const { fixedJsCode } = replaceImportsInJsCode({
|
||||
jsCode,
|
||||
buildOptions
|
||||
});
|
||||
|
||||
$(element).text(fixedJsCode);
|
||||
});
|
||||
@ -72,7 +88,9 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
$(element).attr(
|
||||
attrName,
|
||||
href.replace(
|
||||
new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`),
|
||||
new RegExp(
|
||||
`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`
|
||||
),
|
||||
`\${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.
|
||||
const ftlObjectToJsCodeDeclaringAnObject = fs
|
||||
.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")
|
||||
.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_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion)
|
||||
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType)
|
||||
.replace("KEYCLOAKIFY_THEME_NAME_cXxKd3xEer", themeName)
|
||||
.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.
|
||||
{
|
||||
@ -119,7 +150,9 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
|
||||
startTags.each((...[, 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) {
|
||||
let currentNode = $startTag.next();
|
||||
@ -146,9 +179,13 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
let ftlCode = $.html();
|
||||
|
||||
Object.entries({
|
||||
[ftlObjectToJsCodeDeclaringAnObjectPlaceholder]: ftlObjectToJsCodeDeclaringAnObject,
|
||||
"PAGE_ID_xIgLsPgGId9D8e": pageId
|
||||
}).map(([searchValue, replaceValue]) => (ftlCode = ftlCode.replace(searchValue, replaceValue)));
|
||||
[ftlObjectToJsCodeDeclaringAnObjectPlaceholder]:
|
||||
ftlObjectToJsCodeDeclaringAnObject,
|
||||
PAGE_ID_xIgLsPgGId9D8e: pageId
|
||||
}).map(
|
||||
([searchValue, replaceValue]) =>
|
||||
(ftlCode = ftlCode.replace(searchValue, replaceValue))
|
||||
);
|
||||
|
||||
return { ftlCode };
|
||||
}
|
||||
|
@ -2,7 +2,11 @@ import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import { assert } from "tsafe/assert";
|
||||
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 { transformCodebase } from "../../tools/transformCodebase";
|
||||
import { rmSync } from "../../tools/fs.rmSync";
|
||||
@ -18,32 +22,53 @@ assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike }) {
|
||||
const { buildOptions } = params;
|
||||
|
||||
const builtinKeycloakThemeTmpDirPath = pathJoin(buildOptions.cacheDirPath, "bringInAccountV1_tmp");
|
||||
const builtinKeycloakThemeTmpDirPath = pathJoin(
|
||||
buildOptions.cacheDirPath,
|
||||
"bringInAccountV1_tmp"
|
||||
);
|
||||
|
||||
await downloadKeycloakDefaultTheme({
|
||||
"destDirPath": builtinKeycloakThemeTmpDirPath,
|
||||
"keycloakVersion": lastKeycloakVersionWithAccountV1,
|
||||
destDirPath: builtinKeycloakThemeTmpDirPath,
|
||||
keycloakVersion: lastKeycloakVersionWithAccountV1,
|
||||
buildOptions
|
||||
});
|
||||
|
||||
const accountV1DirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", accountV1ThemeName, "account");
|
||||
const accountV1DirPath = pathJoin(
|
||||
buildOptions.keycloakifyBuildDirPath,
|
||||
"src",
|
||||
"main",
|
||||
"resources",
|
||||
"theme",
|
||||
accountV1ThemeName,
|
||||
"account"
|
||||
);
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "account"),
|
||||
"destDirPath": accountV1DirPath
|
||||
srcDirPath: pathJoin(builtinKeycloakThemeTmpDirPath, "base", "account"),
|
||||
destDirPath: accountV1DirPath
|
||||
});
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "account", "resources"),
|
||||
"destDirPath": pathJoin(accountV1DirPath, "resources")
|
||||
srcDirPath: pathJoin(
|
||||
builtinKeycloakThemeTmpDirPath,
|
||||
"keycloak",
|
||||
"account",
|
||||
"resources"
|
||||
),
|
||||
destDirPath: pathJoin(accountV1DirPath, "resources")
|
||||
});
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "common", "resources"),
|
||||
"destDirPath": pathJoin(accountV1DirPath, "resources", resources_common)
|
||||
srcDirPath: pathJoin(
|
||||
builtinKeycloakThemeTmpDirPath,
|
||||
"keycloak",
|
||||
"common",
|
||||
"resources"
|
||||
),
|
||||
destDirPath: pathJoin(accountV1DirPath, "resources", resources_common)
|
||||
});
|
||||
|
||||
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true });
|
||||
rmSync(builtinKeycloakThemeTmpDirPath, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(accountV1DirPath, "theme.properties"),
|
||||
@ -58,8 +83,13 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike
|
||||
"css/account.css",
|
||||
"img/icon-sidebar-active.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(" "),
|
||||
"",
|
||||
|
@ -16,8 +16,8 @@ export function generateMessageProperties(params: {
|
||||
const { themeSrcDirPath, themeType } = params;
|
||||
|
||||
let files = crawl({
|
||||
"dirPath": pathJoin(themeSrcDirPath, themeType),
|
||||
"returnedPathsType": "absolute"
|
||||
dirPath: pathJoin(themeSrcDirPath, themeType),
|
||||
returnedPathsType: "absolute"
|
||||
});
|
||||
|
||||
files = files.filter(file => {
|
||||
@ -34,7 +34,9 @@ export function generateMessageProperties(params: {
|
||||
|
||||
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) {
|
||||
return [];
|
||||
@ -43,18 +45,25 @@ export function generateMessageProperties(params: {
|
||||
const extraMessages = files
|
||||
.map(file => {
|
||||
const root = recast.parse(readFileSync(file).toString("utf8"), {
|
||||
"parser": {
|
||||
"parse": (code: string) => babelParser.parse(code, { "sourceType": "module", "plugins": ["typescript"] }),
|
||||
"generator": babelGenerate,
|
||||
"types": babelTypes
|
||||
parser: {
|
||||
parse: (code: string) =>
|
||||
babelParser.parse(code, {
|
||||
sourceType: "module",
|
||||
plugins: ["typescript"]
|
||||
}),
|
||||
generator: babelGenerate,
|
||||
types: babelTypes
|
||||
}
|
||||
});
|
||||
|
||||
const codes: string[] = [];
|
||||
|
||||
recast.visit(root, {
|
||||
"visitCallExpression": function (path) {
|
||||
if (path.node.callee.type === "Identifier" && path.node.callee.name === "createUseI18n") {
|
||||
visitCallExpression: function (path) {
|
||||
if (
|
||||
path.node.callee.type === "Identifier" &&
|
||||
path.node.callee.name === "createUseI18n"
|
||||
) {
|
||||
codes.push(babelGenerate(path.node.arguments[0] as any).code);
|
||||
}
|
||||
this.traverse(path);
|
||||
@ -65,7 +74,9 @@ export function generateMessageProperties(params: {
|
||||
})
|
||||
.flat()
|
||||
.map(code => {
|
||||
let extraMessages: { [languageTag: string]: Record<string, string> } = {};
|
||||
let extraMessages: {
|
||||
[languageTag: string]: Record<string, string>;
|
||||
} = {};
|
||||
|
||||
try {
|
||||
eval(`${symToStr({ extraMessages })} = ${code}`);
|
||||
@ -140,7 +151,14 @@ export function generateMessageProperties(params: {
|
||||
|
||||
out.push({
|
||||
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;
|
||||
let highSurrogate = (codePoint >> 10) + 0xd800;
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,9 @@ export type BuildOptionsLike = BuildOptionsLike_generateSrcMainResourcesForMainT
|
||||
|
||||
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 [themeName, ...themeVariantNames] = buildOptions.themeNames;
|
||||
|
@ -24,7 +24,10 @@ import { bringInAccountV1 } from "./bringInAccountV1";
|
||||
import { getThemeSrcDirPath } from "../../shared/getThemeSrcDirPath";
|
||||
import { rmSync } from "../../tools/fs.rmSync";
|
||||
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
|
||||
import { writeMetaInfKeycloakThemes, type MetaInfKeycloakTheme } from "../../shared/metaInfKeycloakThemes";
|
||||
import {
|
||||
writeMetaInfKeycloakThemes,
|
||||
type MetaInfKeycloakTheme
|
||||
} from "../../shared/metaInfKeycloakThemes";
|
||||
import { objectEntries } from "tsafe/objectEntries";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
@ -43,22 +46,35 @@ export type BuildOptionsLike = {
|
||||
|
||||
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 { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath });
|
||||
const { themeSrcDirPath } = getThemeSrcDirPath({
|
||||
reactAppRootDirPath: buildOptions.reactAppRootDirPath
|
||||
});
|
||||
|
||||
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
|
||||
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 implementedThemeTypes: Record<ThemeType | "email", boolean> = {
|
||||
"login": false,
|
||||
"account": false,
|
||||
"email": false
|
||||
login: false,
|
||||
account: false,
|
||||
email: false
|
||||
};
|
||||
|
||||
for (const themeType of ["login", "account"] as const) {
|
||||
@ -71,18 +87,22 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
|
||||
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
||||
|
||||
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.
|
||||
rmSync(destDirPath, { "recursive": true, "force": true });
|
||||
rmSync(destDirPath, { recursive: true, force: true });
|
||||
|
||||
if (themeType === "account" && implementedThemeTypes.login) {
|
||||
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(
|
||||
srcDirPath: pathJoin(
|
||||
getThemeTypeDirPath({
|
||||
"themeType": "login"
|
||||
themeType: "login"
|
||||
}),
|
||||
"resources",
|
||||
basenameOfTheKeycloakifyResourcesDir
|
||||
@ -94,14 +114,17 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
|
||||
}
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
||||
srcDirPath: buildOptions.reactAppBuildDirPath,
|
||||
destDirPath,
|
||||
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||
transformSourceCode: ({ filePath, sourceCode }) => {
|
||||
//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.
|
||||
if (
|
||||
isInside({
|
||||
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
|
||||
dirPath: pathJoin(
|
||||
buildOptions.reactAppBuildDirPath,
|
||||
keycloak_resources
|
||||
),
|
||||
filePath
|
||||
})
|
||||
) {
|
||||
@ -109,39 +132,50 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
|
||||
}
|
||||
|
||||
if (/\.css?$/i.test(filePath)) {
|
||||
const { cssGlobalsToDefine: cssGlobalsToDefineForThisFile, fixedCssCode } = replaceImportsInCssCode({
|
||||
"cssCode": sourceCode.toString("utf8")
|
||||
const {
|
||||
cssGlobalsToDefine: cssGlobalsToDefineForThisFile,
|
||||
fixedCssCode
|
||||
} = replaceImportsInCssCode({
|
||||
cssCode: sourceCode.toString("utf8")
|
||||
});
|
||||
|
||||
Object.entries(cssGlobalsToDefineForThisFile).forEach(([key, value]) => {
|
||||
cssGlobalsToDefine[key] = value;
|
||||
});
|
||||
Object.entries(cssGlobalsToDefineForThisFile).forEach(
|
||||
([key, value]) => {
|
||||
cssGlobalsToDefine[key] = value;
|
||||
}
|
||||
);
|
||||
|
||||
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
||||
return {
|
||||
modifiedSourceCode: Buffer.from(fixedCssCode, "utf8")
|
||||
};
|
||||
}
|
||||
|
||||
if (/\.js?$/i.test(filePath)) {
|
||||
const { fixedJsCode } = replaceImportsInJsCode({
|
||||
"jsCode": sourceCode.toString("utf8"),
|
||||
jsCode: sourceCode.toString("utf8"),
|
||||
buildOptions
|
||||
});
|
||||
|
||||
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
|
||||
return {
|
||||
modifiedSourceCode: Buffer.from(fixedJsCode, "utf8")
|
||||
};
|
||||
}
|
||||
|
||||
return { "modifiedSourceCode": sourceCode };
|
||||
return { modifiedSourceCode: sourceCode };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
||||
themeName,
|
||||
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
|
||||
indexHtmlCode: fs
|
||||
.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html"))
|
||||
.toString("utf8"),
|
||||
cssGlobalsToDefine,
|
||||
buildOptions,
|
||||
"keycloakifyVersion": readThisNpmPackageVersion(),
|
||||
keycloakifyVersion: readThisNpmPackageVersion(),
|
||||
themeType,
|
||||
"fieldNames": readFieldNameUsage({
|
||||
fieldNames: readFieldNameUsage({
|
||||
themeSrcDirPath,
|
||||
themeType
|
||||
})
|
||||
@ -163,9 +197,12 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
|
||||
].forEach(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({
|
||||
@ -174,15 +211,23 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
|
||||
}).forEach(({ languageTag, propertiesFileSource }) => {
|
||||
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({
|
||||
"keycloakVersion": (() => {
|
||||
keycloakVersion: (() => {
|
||||
switch (themeType) {
|
||||
case "account":
|
||||
return lastKeycloakVersionWithAccountV1;
|
||||
@ -190,7 +235,7 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
|
||||
return buildOptions.loginThemeResourcesFromKeycloakVersion;
|
||||
}
|
||||
})(),
|
||||
"themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")),
|
||||
themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")),
|
||||
themeType,
|
||||
buildOptions
|
||||
});
|
||||
@ -225,8 +270,8 @@ export async function generateSrcMainResourcesForMainTheme(params: { themeName:
|
||||
implementedThemeTypes.email = true;
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": emailThemeSrcDirPath,
|
||||
"destDirPath": getThemeTypeDirPath({ "themeType": "email" })
|
||||
srcDirPath: emailThemeSrcDirPath,
|
||||
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({
|
||||
"name": themeName,
|
||||
"types": objectEntries(implementedThemeTypes)
|
||||
name: themeName,
|
||||
types: objectEntries(implementedThemeTypes)
|
||||
.filter(([, isImplemented]) => isImplemented)
|
||||
.map(([themeType]) => themeType)
|
||||
});
|
||||
|
||||
if (implementedThemeTypes.account) {
|
||||
metaInfKeycloakThemes.themes.push({
|
||||
"name": accountV1ThemeName,
|
||||
"types": ["account"]
|
||||
name: accountV1ThemeName,
|
||||
types: ["account"]
|
||||
});
|
||||
}
|
||||
|
||||
writeMetaInfKeycloakThemes({
|
||||
"keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath,
|
||||
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath,
|
||||
metaInfKeycloakThemes
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { join as pathJoin, extname as pathExtname, sep as pathSep } from "path";
|
||||
import { transformCodebase } from "../../tools/transformCodebase";
|
||||
import type { BuildOptions } from "../../shared/buildOptions";
|
||||
import { readMetaInfKeycloakThemes, writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes";
|
||||
import {
|
||||
readMetaInfKeycloakThemes,
|
||||
writeMetaInfKeycloakThemes
|
||||
} from "../../shared/metaInfKeycloakThemes";
|
||||
import { assert } from "tsafe/assert";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
@ -10,20 +13,37 @@ export type BuildOptionsLike = {
|
||||
|
||||
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 mainThemeDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", themeName);
|
||||
const mainThemeDirPath = pathJoin(
|
||||
buildOptions.keycloakifyBuildDirPath,
|
||||
"src",
|
||||
"main",
|
||||
"resources",
|
||||
"theme",
|
||||
themeName
|
||||
);
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": mainThemeDirPath,
|
||||
"destDirPath": pathJoin(mainThemeDirPath, "..", themeVariantName),
|
||||
"transformSourceCode": ({ fileRelativePath, sourceCode }) => {
|
||||
if (pathExtname(fileRelativePath) === ".ftl" && fileRelativePath.split(pathSep).length === 2) {
|
||||
srcDirPath: mainThemeDirPath,
|
||||
destDirPath: pathJoin(mainThemeDirPath, "..", themeVariantName),
|
||||
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
||||
if (
|
||||
pathExtname(fileRelativePath) === ".ftl" &&
|
||||
fileRelativePath.split(pathSep).length === 2
|
||||
) {
|
||||
const modifiedSourceCode = Buffer.from(
|
||||
Buffer.from(sourceCode)
|
||||
.toString("utf-8")
|
||||
.replace(`out["themeName"] = "${themeName}";`, `out["themeName"] = "${themeVariantName}";`),
|
||||
.replace(
|
||||
`out["themeName"] = "${themeName}";`,
|
||||
`out["themeName"] = "${themeVariantName}";`
|
||||
),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
@ -32,25 +52,29 @@ export function generateSrcMainResourcesForThemeVariant(params: { themeName: str
|
||||
return { modifiedSourceCode };
|
||||
}
|
||||
|
||||
return { "modifiedSourceCode": sourceCode };
|
||||
return { modifiedSourceCode: sourceCode };
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
const updatedMetaInfKeycloakThemes = readMetaInfKeycloakThemes({ "keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath });
|
||||
const updatedMetaInfKeycloakThemes = readMetaInfKeycloakThemes({
|
||||
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
|
||||
});
|
||||
|
||||
updatedMetaInfKeycloakThemes.themes.push({
|
||||
"name": themeVariantName,
|
||||
"types": (() => {
|
||||
const theme = updatedMetaInfKeycloakThemes.themes.find(({ name }) => name === themeName);
|
||||
name: themeVariantName,
|
||||
types: (() => {
|
||||
const theme = updatedMetaInfKeycloakThemes.themes.find(
|
||||
({ name }) => name === themeName
|
||||
);
|
||||
assert(theme !== undefined);
|
||||
return theme.types;
|
||||
})()
|
||||
});
|
||||
|
||||
writeMetaInfKeycloakThemes({
|
||||
"keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath,
|
||||
"metaInfKeycloakThemes": updatedMetaInfKeycloakThemes
|
||||
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath,
|
||||
metaInfKeycloakThemes: updatedMetaInfKeycloakThemes
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,26 @@ import { id } from "tsafe/id";
|
||||
import { removeDuplicates } from "evt/tools/reducers/removeDuplicates";
|
||||
import * as fs from "fs";
|
||||
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 filePaths = crawl({
|
||||
"dirPath": pathJoin(themeSrcDirPath, themeType),
|
||||
"returnedPathsType": "absolute"
|
||||
dirPath: pathJoin(themeSrcDirPath, themeType),
|
||||
returnedPathsType: "absolute"
|
||||
}).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) {
|
||||
candidateFilePaths.push(...filePaths);
|
||||
@ -24,7 +33,12 @@ export function readExtraPagesNames(params: { themeSrcDirPath: string; themeType
|
||||
for (const candidateFilPath of candidateFilePaths) {
|
||||
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 => {
|
||||
|
@ -5,13 +5,22 @@ import type { ThemeType } from "../../shared/constants";
|
||||
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
||||
|
||||
/** 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 fieldNames = new Set<string>();
|
||||
|
||||
for (const srcDirPath of [pathJoin(getThisCodebaseRootDirPath(), "src", themeType), pathJoin(themeSrcDirPath, themeType)]) {
|
||||
const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
|
||||
for (const srcDirPath of [
|
||||
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) {
|
||||
const rawSourceFile = fs.readFileSync(filePath).toString("utf8");
|
||||
@ -20,7 +29,13 @@ export function readFieldNameUsage(params: { themeSrcDirPath: string; themeType:
|
||||
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)) {
|
||||
continue;
|
||||
}
|
||||
@ -40,9 +55,21 @@ export function readFieldNameUsage(params: { themeSrcDirPath: string; themeType:
|
||||
return part
|
||||
.split(",")
|
||||
.map(a => a.trim())
|
||||
.filter((...[, i]) => (functionName !== "printIfExists" ? true : i === 0))
|
||||
.filter(a => a.startsWith('"') || a.startsWith("'") || a.startsWith("`"))
|
||||
.filter(a => a.endsWith('"') || a.endsWith("'") || a.endsWith("`"))
|
||||
.filter((...[, i]) =>
|
||||
functionName !== "printIfExists" ? true : i === 0
|
||||
)
|
||||
.filter(
|
||||
a =>
|
||||
a.startsWith('"') ||
|
||||
a.startsWith("'") ||
|
||||
a.startsWith("`")
|
||||
)
|
||||
.filter(
|
||||
a =>
|
||||
a.endsWith('"') ||
|
||||
a.endsWith("'") ||
|
||||
a.endsWith("`")
|
||||
)
|
||||
.map(a => a.slice(1).slice(0, -1));
|
||||
})
|
||||
.flat()
|
||||
|
@ -1,5 +1,9 @@
|
||||
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 type { BuildOptions } from "../shared/buildOptions";
|
||||
import { accountV1ThemeName } from "../shared/constants";
|
||||
@ -27,7 +31,10 @@ export function generateStartKeycloakTestingContainer(params: {
|
||||
const themeRelativeDirPath = pathJoin("src", "main", "resources", "theme");
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename),
|
||||
pathJoin(
|
||||
buildOptions.keycloakifyBuildDirPath,
|
||||
generateStartKeycloakTestingContainer.basename
|
||||
),
|
||||
Buffer.from(
|
||||
[
|
||||
"#!/usr/bin/env bash",
|
||||
@ -45,9 +52,16 @@ export function generateStartKeycloakTestingContainer(params: {
|
||||
"$(pwd)",
|
||||
pathRelative(buildOptions.keycloakifyBuildDirPath, jarFilePath)
|
||||
)}":"/opt/keycloak/providers/${pathBasename(jarFilePath)}" \\`,
|
||||
[...(doesImplementAccountTheme ? [accountV1ThemeName] : []), ...buildOptions.themeNames].map(
|
||||
[
|
||||
...(doesImplementAccountTheme ? [accountV1ThemeName] : []),
|
||||
...buildOptions.themeNames
|
||||
].map(
|
||||
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} \\`,
|
||||
` start-dev`,
|
||||
@ -55,6 +69,6 @@ export function generateStartKeycloakTestingContainer(params: {
|
||||
].join("\n"),
|
||||
"utf8"
|
||||
),
|
||||
{ "mode": 0o755 }
|
||||
{ mode: 0o755 }
|
||||
);
|
||||
}
|
||||
|
@ -15,7 +15,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
let commandOutput: Buffer | undefined = undefined;
|
||||
|
||||
try {
|
||||
commandOutput = child_process.execSync("mvn --version", { "stdio": ["ignore", "pipe", "ignore"] });
|
||||
commandOutput = child_process.execSync("mvn --version", {
|
||||
stdio: ["ignore", "pipe", "ignore"]
|
||||
});
|
||||
} catch {}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -46,7 +52,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
console.log(
|
||||
[
|
||||
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(" ")
|
||||
);
|
||||
|
||||
@ -54,10 +65,15 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
|
||||
{
|
||||
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 });
|
||||
@ -68,10 +84,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
}
|
||||
|
||||
child_process.execSync("npx vite", {
|
||||
"cwd": buildOptions.reactAppRootDirPath,
|
||||
"env": {
|
||||
cwd: buildOptions.reactAppRootDirPath,
|
||||
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 });
|
||||
}
|
||||
|
||||
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`)
|
||||
);
|
||||
}
|
||||
|
@ -18,7 +18,15 @@ export function replaceImportsInCssCode(params: { cssCode: string }): {
|
||||
const cssGlobalsToDefine: Record<string, string> = {};
|
||||
|
||||
new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*?/g) ?? []).forEach(
|
||||
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match)
|
||||
match =>
|
||||
(cssGlobalsToDefine[
|
||||
"url" +
|
||||
crypto
|
||||
.createHash("sha256")
|
||||
.update(match)
|
||||
.digest("hex")
|
||||
.substring(0, 15)
|
||||
] = match)
|
||||
);
|
||||
|
||||
let fixedCssCode = cssCode;
|
||||
@ -26,26 +34,37 @@ export function replaceImportsInCssCode(params: { cssCode: string }): {
|
||||
Object.keys(cssGlobalsToDefine).forEach(
|
||||
cssVariableName =>
|
||||
//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 };
|
||||
}
|
||||
|
||||
export function generateCssCodeToDefineGlobals(params: { cssGlobalsToDefine: Record<string, string>; buildOptions: BuildOptionsLike }): {
|
||||
export function generateCssCodeToDefineGlobals(params: {
|
||||
cssGlobalsToDefine: Record<string, string>;
|
||||
buildOptions: BuildOptionsLike;
|
||||
}): {
|
||||
cssCodeToPrependInHead: string;
|
||||
} {
|
||||
const { cssGlobalsToDefine, buildOptions } = params;
|
||||
|
||||
return {
|
||||
"cssCodeToPrependInHead": [
|
||||
cssCodeToPrependInHead: [
|
||||
":root {",
|
||||
...Object.keys(cssGlobalsToDefine)
|
||||
.map(cssVariableName =>
|
||||
[
|
||||
`--${cssVariableName}:`,
|
||||
cssGlobalsToDefine[cssVariableName].replace(
|
||||
new RegExp(`url\\(${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`, "g"),
|
||||
new RegExp(
|
||||
`url\\(${(buildOptions.urlPathname ?? "/").replace(
|
||||
/\//g,
|
||||
"\\/"
|
||||
)}`,
|
||||
"g"
|
||||
),
|
||||
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
|
||||
)
|
||||
].join(" ")
|
||||
|
@ -8,7 +8,10 @@ export type BuildOptionsLike = {
|
||||
|
||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
|
||||
export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOptions: BuildOptionsLike }): {
|
||||
export function replaceImportsInInlineCssCode(params: {
|
||||
cssCode: string;
|
||||
buildOptions: BuildOptionsLike;
|
||||
}): {
|
||||
fixedCssCode: string;
|
||||
} {
|
||||
const { cssCode, buildOptions } = params;
|
||||
@ -17,7 +20,8 @@ export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOp
|
||||
buildOptions.urlPathname === undefined
|
||||
? /url\(["']?\/([^/][^)"']+)["']?\)/g
|
||||
: new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
|
||||
(...[, group]) => `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})`
|
||||
(...[, group]) =>
|
||||
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})`
|
||||
);
|
||||
|
||||
return { fixedCssCode };
|
||||
|
@ -13,7 +13,10 @@ export type BuildOptionsLike = {
|
||||
|
||||
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 { fixedJsCode } = (() => {
|
||||
@ -22,8 +25,8 @@ export function replaceImportsInJsCode(params: { jsCode: string; buildOptions: B
|
||||
return replaceImportsInJsCode_vite({
|
||||
jsCode,
|
||||
buildOptions,
|
||||
"basenameOfAssetsFiles": readAssetsDirSync({
|
||||
"assetsDirPath": params.buildOptions.assetsDirPath
|
||||
basenameOfAssetsFiles: readAssetsDirSync({
|
||||
assetsDirPath: params.buildOptions.assetsDirPath
|
||||
})
|
||||
});
|
||||
case "webpack":
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../shared/constants";
|
||||
import {
|
||||
nameOfTheGlobal,
|
||||
basenameOfTheKeycloakifyResourcesDir
|
||||
} from "../../../shared/constants";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { BuildOptions } from "../../../shared/buildOptions";
|
||||
import * as nodePath from "path";
|
||||
@ -20,7 +23,12 @@ export function replaceImportsInJsCode_vite(params: {
|
||||
}): {
|
||||
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];
|
||||
|
||||
@ -38,22 +46,32 @@ export function replaceImportsInJsCode_vite(params: {
|
||||
// Replace `Hv=function(e){return"/abcde12345/"+e}` by `Hv=function(e){return"/"+e}`
|
||||
fixedJsCode = fixedJsCode.replace(
|
||||
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"
|
||||
),
|
||||
(...[, funcName, paramName]) => `${funcName}=function(${paramName}){return"/"+${paramName}}`
|
||||
(...[, funcName, paramName]) =>
|
||||
`${funcName}=function(${paramName}){return"/"+${paramName}}`
|
||||
);
|
||||
}
|
||||
|
||||
replace_javascript_relatives_import_paths: {
|
||||
// Example: "assets/ or "foo/bar/"
|
||||
const staticDir = (() => {
|
||||
let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath);
|
||||
let out = pathRelative(
|
||||
buildOptions.reactAppBuildDirPath,
|
||||
buildOptions.assetsDirPath
|
||||
);
|
||||
|
||||
out = replaceAll(out, pathSep, "/") + "/";
|
||||
|
||||
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;
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../shared/constants";
|
||||
import {
|
||||
nameOfTheGlobal,
|
||||
basenameOfTheKeycloakifyResourcesDir
|
||||
} from "../../../shared/constants";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { BuildOptions } from "../../../shared/buildOptions";
|
||||
import * as nodePath from "path";
|
||||
@ -12,10 +15,18 @@ export type BuildOptionsLike = {
|
||||
|
||||
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;
|
||||
} {
|
||||
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];
|
||||
|
||||
@ -24,29 +35,51 @@ export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOp
|
||||
if (buildOptions.urlPathname !== undefined) {
|
||||
// "__esModule",{value:!0})},n.p="/foo-bar/",function(){if("undefined" -> ... n.p="/" ...
|
||||
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}="/",`
|
||||
);
|
||||
}
|
||||
|
||||
// Example: "static/ or "foo/bar/"
|
||||
const staticDir = (() => {
|
||||
let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath);
|
||||
let out = pathRelative(
|
||||
buildOptions.reactAppBuildDirPath,
|
||||
buildOptions.assetsDirPath
|
||||
);
|
||||
|
||||
out = replaceAll(out, pathSep, "/") + "/";
|
||||
|
||||
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;
|
||||
})();
|
||||
|
||||
const getReplaceArgs = (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"),
|
||||
const getReplaceArgs = (
|
||||
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]) => {
|
||||
const isArrowFunction = matchedFunction.includes("=>");
|
||||
const e = isArrowFunction ? matchedFunction.replace("=>", "").trim() : eForFunction;
|
||||
const e = isArrowFunction
|
||||
? matchedFunction.replace("=>", "").trim()
|
||||
: eForFunction;
|
||||
|
||||
return `
|
||||
${n}[(function(){
|
||||
@ -58,7 +91,9 @@ export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOp
|
||||
});
|
||||
}
|
||||
return "${u}";
|
||||
})()] = ${isArrowFunction ? `${e} =>` : `function(${e}) { return `} "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}${language}/"`
|
||||
})()] = ${
|
||||
isArrowFunction ? `${e} =>` : `function(${e}) { return `
|
||||
} "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}${language}/"`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
@ -68,7 +103,10 @@ export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOp
|
||||
.replace(...getReplaceArgs("js"))
|
||||
.replace(...getReplaceArgs("css"))
|
||||
.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}`
|
||||
);
|
||||
|
||||
|
109
src/bin/main.ts
109
src/bin/main.ts
@ -10,12 +10,12 @@ export type CliCommandOptions = {
|
||||
|
||||
const program = termost<CliCommandOptions>(
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"description": "Keycloakify CLI",
|
||||
"version": readThisNpmPackageVersion()
|
||||
name: "keycloakify",
|
||||
description: "Keycloakify CLI",
|
||||
version: readThisNpmPackageVersion()
|
||||
},
|
||||
{
|
||||
"onException": error => {
|
||||
onException: error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
@ -25,8 +25,8 @@ const program = termost<CliCommandOptions>(
|
||||
const optionsKeys: string[] = [];
|
||||
|
||||
program.option({
|
||||
"key": "reactAppRootDirPath",
|
||||
"name": (() => {
|
||||
key: "reactAppRootDirPath",
|
||||
name: (() => {
|
||||
const long = "project";
|
||||
const short = "p";
|
||||
|
||||
@ -34,19 +34,25 @@ program.option({
|
||||
|
||||
return { long, short };
|
||||
})(),
|
||||
"description": [
|
||||
description: [
|
||||
`For monorepos, path to the keycloakify project.`,
|
||||
"Example: `npx keycloakify build --project packages/keycloak-theme`",
|
||||
"https://docs.keycloakify.dev/build-options#project-or-p-cli-option"
|
||||
].join(" "),
|
||||
"defaultValue": undefined
|
||||
defaultValue: undefined
|
||||
});
|
||||
|
||||
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) {
|
||||
console.error(`keycloakify: Unrecognized option: ${unrecognizedOptionKey.length === 1 ? "-" : "--"}${unrecognizedOptionKey}`);
|
||||
console.error(
|
||||
`keycloakify: Unrecognized option: ${
|
||||
unrecognizedOptionKey.length === 1 ? "-" : "--"
|
||||
}${unrecognizedOptionKey}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -55,12 +61,12 @@ function skip(_context: any, argv: { options: Record<string, unknown> }) {
|
||||
|
||||
program
|
||||
.command({
|
||||
"name": "build",
|
||||
"description": "Build the theme (default subcommand)."
|
||||
name: "build",
|
||||
description: "Build the theme (default subcommand)."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
"handler": async cliCommandOptions => {
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./keycloakify");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
@ -73,48 +79,55 @@ program
|
||||
keycloakVersion: string | undefined;
|
||||
realmJsonFilePath: string | undefined;
|
||||
}>({
|
||||
"name": "start-keycloak",
|
||||
"description": "Spin up a pre configured Docker image of Keycloak to test your theme."
|
||||
name: "start-keycloak",
|
||||
description:
|
||||
"Spin up a pre configured Docker image of Keycloak to test your theme."
|
||||
})
|
||||
.option({
|
||||
"key": "port",
|
||||
"name": (() => {
|
||||
key: "port",
|
||||
name: (() => {
|
||||
const name = "port";
|
||||
|
||||
optionsKeys.push(name);
|
||||
|
||||
return name;
|
||||
})(),
|
||||
"description": ["Keycloak server port.", "Example `--port 8085`"].join(" "),
|
||||
"defaultValue": 8080
|
||||
description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
|
||||
defaultValue: 8080
|
||||
})
|
||||
.option({
|
||||
"key": "keycloakVersion",
|
||||
"name": (() => {
|
||||
key: "keycloakVersion",
|
||||
name: (() => {
|
||||
const name = "keycloak-version";
|
||||
|
||||
optionsKeys.push(name);
|
||||
|
||||
return name;
|
||||
})(),
|
||||
"description": ["Use a specific version of Keycloak.", "Example `--keycloak-version 21.1.1`"].join(" "),
|
||||
"defaultValue": undefined
|
||||
description: [
|
||||
"Use a specific version of Keycloak.",
|
||||
"Example `--keycloak-version 21.1.1`"
|
||||
].join(" "),
|
||||
defaultValue: undefined
|
||||
})
|
||||
.option({
|
||||
"key": "realmJsonFilePath",
|
||||
"name": (() => {
|
||||
key: "realmJsonFilePath",
|
||||
name: (() => {
|
||||
const name = "import";
|
||||
|
||||
optionsKeys.push(name);
|
||||
|
||||
return name;
|
||||
})(),
|
||||
"defaultValue": undefined,
|
||||
"description": ["Import your own realm configuration file", "Example `--import path/to/myrealm-realm.json`"].join(" ")
|
||||
defaultValue: undefined,
|
||||
description: [
|
||||
"Import your own realm configuration file",
|
||||
"Example `--import path/to/myrealm-realm.json`"
|
||||
].join(" ")
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
"handler": async cliCommandOptions => {
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./start-keycloak");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
@ -123,12 +136,12 @@ program
|
||||
|
||||
program
|
||||
.command({
|
||||
"name": "download-keycloak-default-theme",
|
||||
"description": "Download the built-in Keycloak theme."
|
||||
name: "download-keycloak-default-theme",
|
||||
description: "Download the built-in Keycloak theme."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
"handler": async cliCommandOptions => {
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./download-keycloak-default-theme");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
@ -137,12 +150,12 @@ program
|
||||
|
||||
program
|
||||
.command({
|
||||
"name": "eject-page",
|
||||
"description": "Eject a Keycloak page."
|
||||
name: "eject-page",
|
||||
description: "Eject a Keycloak page."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
"handler": async cliCommandOptions => {
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./eject-page");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
@ -151,12 +164,12 @@ program
|
||||
|
||||
program
|
||||
.command({
|
||||
"name": "initialize-email-theme",
|
||||
"description": "Initialize an email theme."
|
||||
name: "initialize-email-theme",
|
||||
description: "Initialize an email theme."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
"handler": async cliCommandOptions => {
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./initialize-email-theme");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
@ -165,12 +178,13 @@ program
|
||||
|
||||
program
|
||||
.command({
|
||||
"name": "copy-keycloak-resources-to-public",
|
||||
"description": "(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
|
||||
name: "copy-keycloak-resources-to-public",
|
||||
description:
|
||||
"(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
"handler": async cliCommandOptions => {
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./copy-keycloak-resources-to-public");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
@ -181,10 +195,17 @@ program
|
||||
{
|
||||
const [, , ...rest] = process.argv;
|
||||
|
||||
if (rest.length === 0 || (rest[0].startsWith("-") && rest[0] !== "--help" && rest[0] !== "-h")) {
|
||||
const { status } = child_process.spawnSync("npx", ["keycloakify", "build", ...rest], {
|
||||
"stdio": "inherit"
|
||||
});
|
||||
if (
|
||||
rest.length === 0 ||
|
||||
(rest[0].startsWith("-") && rest[0] !== "--help" && rest[0] !== "-h")
|
||||
) {
|
||||
const { status } = child_process.spawnSync(
|
||||
"npx",
|
||||
["keycloakify", "build", ...rest],
|
||||
{
|
||||
stdio: "inherit"
|
||||
}
|
||||
);
|
||||
|
||||
process.exit(status ?? 1);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
export type KeycloakVersionRange = KeycloakVersionRange.WithAccountTheme | KeycloakVersionRange.WithoutAccountTheme;
|
||||
export type KeycloakVersionRange =
|
||||
| KeycloakVersionRange.WithAccountTheme
|
||||
| KeycloakVersionRange.WithoutAccountTheme;
|
||||
|
||||
export namespace KeycloakVersionRange {
|
||||
export type WithoutAccountTheme = "21-and-below" | "22-and-above";
|
||||
|
@ -49,7 +49,9 @@ export type ResolvedViteConfig = {
|
||||
userProvidedBuildOptions: UserProvidedBuildOptions;
|
||||
};
|
||||
|
||||
export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions }): BuildOptions {
|
||||
export function readBuildOptions(params: {
|
||||
cliCommandOptions: CliCommandOptions;
|
||||
}): BuildOptions {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const reactAppRootDirPath = (() => {
|
||||
@ -58,29 +60,39 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
}
|
||||
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": cliCommandOptions.reactAppRootDirPath,
|
||||
"cwd": process.cwd()
|
||||
pathIsh: cliCommandOptions.reactAppRootDirPath,
|
||||
cwd: process.cwd()
|
||||
});
|
||||
})();
|
||||
|
||||
const { resolvedViteConfig } = (() => {
|
||||
if (fs.readdirSync(reactAppRootDirPath).find(fileBasename => fileBasename.startsWith("vite.config")) === undefined) {
|
||||
return { "resolvedViteConfig": undefined };
|
||||
if (
|
||||
fs
|
||||
.readdirSync(reactAppRootDirPath)
|
||||
.find(fileBasename => fileBasename.startsWith("vite.config")) ===
|
||||
undefined
|
||||
) {
|
||||
return { resolvedViteConfig: undefined };
|
||||
}
|
||||
|
||||
const output = child_process
|
||||
.execSync("npx vite", {
|
||||
"cwd": reactAppRootDirPath,
|
||||
"env": {
|
||||
cwd: reactAppRootDirPath,
|
||||
env: {
|
||||
...process.env,
|
||||
[vitePluginSubScriptEnvNames.resolveViteConfig]: "true"
|
||||
}
|
||||
})
|
||||
.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);
|
||||
|
||||
@ -92,22 +104,24 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
name: string;
|
||||
version?: string;
|
||||
homepage?: string;
|
||||
keycloakify?: UserProvidedBuildOptions & { reactAppBuildDirPath?: string };
|
||||
keycloakify?: UserProvidedBuildOptions & {
|
||||
reactAppBuildDirPath?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const zParsedPackageJson = z.object({
|
||||
"name": z.string(),
|
||||
"version": z.string().optional(),
|
||||
"homepage": z.string().optional(),
|
||||
"keycloakify": z
|
||||
name: z.string(),
|
||||
version: z.string().optional(),
|
||||
homepage: z.string().optional(),
|
||||
keycloakify: z
|
||||
.object({
|
||||
"extraThemeProperties": z.array(z.string()).optional(),
|
||||
"artifactId": z.string().optional(),
|
||||
"groupId": z.string().optional(),
|
||||
"loginThemeResourcesFromKeycloakVersion": z.string().optional(),
|
||||
"reactAppBuildDirPath": z.string().optional(),
|
||||
"keycloakifyBuildDirPath": z.string().optional(),
|
||||
"themeName": z.union([z.string(), z.array(z.string())]).optional()
|
||||
extraThemeProperties: z.array(z.string()).optional(),
|
||||
artifactId: z.string().optional(),
|
||||
groupId: z.string().optional(),
|
||||
loginThemeResourcesFromKeycloakVersion: z.string().optional(),
|
||||
reactAppBuildDirPath: z.string().optional(),
|
||||
keycloakifyBuildDirPath: z.string().optional(),
|
||||
themeName: z.union([z.string(), z.array(z.string())]).optional()
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
@ -119,7 +133,13 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
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 = {
|
||||
@ -152,8 +172,8 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
|
||||
if (parsedPackageJson.keycloakify?.reactAppBuildDirPath !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": parsedPackageJson.keycloakify.reactAppBuildDirPath,
|
||||
"cwd": reactAppRootDirPath
|
||||
pathIsh: parsedPackageJson.keycloakify.reactAppBuildDirPath,
|
||||
cwd: reactAppRootDirPath
|
||||
});
|
||||
}
|
||||
|
||||
@ -165,15 +185,16 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
|
||||
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({
|
||||
reactAppRootDirPath,
|
||||
"dependencyExpected": "keycloakify"
|
||||
dependencyExpected: "keycloakify"
|
||||
});
|
||||
|
||||
return {
|
||||
"bundler": resolvedViteConfig !== undefined ? "vite" : "webpack",
|
||||
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
|
||||
bundler: resolvedViteConfig !== undefined ? "vite" : "webpack",
|
||||
themeVersion:
|
||||
process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
|
||||
themeNames,
|
||||
"extraThemeProperties": userProvidedBuildOptions.extraThemeProperties,
|
||||
"groupId": (() => {
|
||||
extraThemeProperties: userProvidedBuildOptions.extraThemeProperties,
|
||||
groupId: (() => {
|
||||
const fallbackGroupId = `${themeNames[0]}.keycloak`;
|
||||
|
||||
return (
|
||||
@ -188,24 +209,30 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
.join(".") ?? fallbackGroupId) + ".keycloak"
|
||||
);
|
||||
})(),
|
||||
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? userProvidedBuildOptions.artifactId ?? `${themeNames[0]}-keycloak-theme`,
|
||||
"loginThemeResourcesFromKeycloakVersion": userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4",
|
||||
artifactId:
|
||||
process.env.KEYCLOAKIFY_ARTIFACT_ID ??
|
||||
userProvidedBuildOptions.artifactId ??
|
||||
`${themeNames[0]}-keycloak-theme`,
|
||||
loginThemeResourcesFromKeycloakVersion:
|
||||
userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4",
|
||||
reactAppRootDirPath,
|
||||
reactAppBuildDirPath,
|
||||
"keycloakifyBuildDirPath": (() => {
|
||||
keycloakifyBuildDirPath: (() => {
|
||||
if (userProvidedBuildOptions.keycloakifyBuildDirPath !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": userProvidedBuildOptions.keycloakifyBuildDirPath,
|
||||
"cwd": reactAppRootDirPath
|
||||
pathIsh: userProvidedBuildOptions.keycloakifyBuildDirPath,
|
||||
cwd: reactAppRootDirPath
|
||||
});
|
||||
}
|
||||
|
||||
return pathJoin(
|
||||
reactAppRootDirPath,
|
||||
resolvedViteConfig?.buildDir === undefined ? "build_keycloak" : `${resolvedViteConfig.buildDir}_keycloak`
|
||||
resolvedViteConfig?.buildDir === undefined
|
||||
? "build_keycloak"
|
||||
: `${resolvedViteConfig.buildDir}_keycloak`
|
||||
);
|
||||
})(),
|
||||
"publicDirPath": (() => {
|
||||
publicDirPath: (() => {
|
||||
webpack: {
|
||||
if (resolvedViteConfig !== undefined) {
|
||||
break webpack;
|
||||
@ -213,8 +240,8 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
|
||||
if (process.env.PUBLIC_DIR_PATH !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": process.env.PUBLIC_DIR_PATH,
|
||||
"cwd": reactAppRootDirPath
|
||||
pathIsh: process.env.PUBLIC_DIR_PATH,
|
||||
cwd: reactAppRootDirPath
|
||||
});
|
||||
}
|
||||
|
||||
@ -223,13 +250,13 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
|
||||
return pathJoin(reactAppRootDirPath, resolvedViteConfig.publicDir);
|
||||
})(),
|
||||
"cacheDirPath": (() => {
|
||||
cacheDirPath: (() => {
|
||||
const cacheDirPath = pathJoin(
|
||||
(() => {
|
||||
if (process.env.XDG_CACHE_HOME !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": process.env.XDG_CACHE_HOME,
|
||||
"cwd": process.cwd()
|
||||
pathIsh: process.env.XDG_CACHE_HOME,
|
||||
cwd: process.cwd()
|
||||
});
|
||||
}
|
||||
|
||||
@ -240,7 +267,7 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
|
||||
return cacheDirPath;
|
||||
})(),
|
||||
"urlPathname": (() => {
|
||||
urlPathname: (() => {
|
||||
webpack: {
|
||||
if (resolvedViteConfig !== undefined) {
|
||||
break webpack;
|
||||
@ -264,7 +291,7 @@ export function readBuildOptions(params: { cliCommandOptions: CliCommandOptions
|
||||
|
||||
return resolvedViteConfig.urlPathname;
|
||||
})(),
|
||||
"assetsDirPath": (() => {
|
||||
assetsDirPath: (() => {
|
||||
webpack: {
|
||||
if (resolvedViteConfig !== undefined) {
|
||||
break webpack;
|
||||
|
@ -10,8 +10,8 @@ export const accountV1ThemeName = "account-v1";
|
||||
export type ThemeType = (typeof themeTypes)[number];
|
||||
|
||||
export const vitePluginSubScriptEnvNames = {
|
||||
"runPostBuildScript": "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT",
|
||||
"resolveViteConfig": "KEYCLOAKIFY_RESOLVE_VITE_CONFIG"
|
||||
runPostBuildScript: "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT",
|
||||
resolveViteConfig: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG"
|
||||
} as const;
|
||||
|
||||
export const skipBuildJarsEnvName = "KEYCLOAKIFY_SKIP_BUILD_JAR";
|
||||
|
@ -3,7 +3,11 @@ import {
|
||||
type BuildOptionsLike as BuildOptionsLike_downloadKeycloakStaticResources
|
||||
} from "./downloadKeycloakStaticResources";
|
||||
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 { assert } from "tsafe/assert";
|
||||
import * as fs from "fs";
|
||||
@ -17,7 +21,9 @@ export type BuildOptionsLike = BuildOptionsLike_downloadKeycloakStaticResources
|
||||
|
||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
|
||||
export async function copyKeycloakResourcesToPublic(params: { buildOptions: BuildOptionsLike }) {
|
||||
export async function copyKeycloakResourcesToPublic(params: {
|
||||
buildOptions: BuildOptionsLike;
|
||||
}) {
|
||||
const { buildOptions } = params;
|
||||
|
||||
const destDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources);
|
||||
@ -27,11 +33,14 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
|
||||
const keycloakifyBuildinfoRaw = JSON.stringify(
|
||||
{
|
||||
destDirPath,
|
||||
"keycloakifyVersion": readThisNpmPackageVersion(),
|
||||
"buildOptions": {
|
||||
"loginThemeResourcesFromKeycloakVersion": readThisNpmPackageVersion(),
|
||||
"cacheDirPath": pathRelative(destDirPath, buildOptions.cacheDirPath),
|
||||
"npmWorkspaceRootDirPath": pathRelative(destDirPath, buildOptions.npmWorkspaceRootDirPath)
|
||||
keycloakifyVersion: readThisNpmPackageVersion(),
|
||||
buildOptions: {
|
||||
loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(),
|
||||
cacheDirPath: pathRelative(destDirPath, buildOptions.cacheDirPath),
|
||||
npmWorkspaceRootDirPath: pathRelative(
|
||||
destDirPath,
|
||||
buildOptions.npmWorkspaceRootDirPath
|
||||
)
|
||||
}
|
||||
},
|
||||
null,
|
||||
@ -43,7 +52,9 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
|
||||
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) {
|
||||
break skip_if_already_done;
|
||||
@ -52,15 +63,15 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
|
||||
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"));
|
||||
|
||||
for (const themeType of themeTypes) {
|
||||
await downloadKeycloakStaticResources({
|
||||
"keycloakVersion": (() => {
|
||||
keycloakVersion: (() => {
|
||||
switch (themeType) {
|
||||
case "login":
|
||||
return buildOptions.loginThemeResourcesFromKeycloakVersion;
|
||||
@ -69,7 +80,7 @@ export async function copyKeycloakResourcesToPublic(params: { buildOptions: Buil
|
||||
}
|
||||
})(),
|
||||
themeType,
|
||||
"themeDirPath": destDirPath,
|
||||
themeDirPath: destDirPath,
|
||||
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")
|
||||
);
|
||||
}
|
||||
|
@ -28,22 +28,29 @@ export async function downloadAndUnzip(params: {
|
||||
};
|
||||
buildOptions: BuildOptionsLike;
|
||||
}) {
|
||||
const { url, destDirPath, specificDirsToExtract, preCacheTransform, buildOptions } = params;
|
||||
const { url, destDirPath, specificDirsToExtract, preCacheTransform, buildOptions } =
|
||||
params;
|
||||
|
||||
const { extractDirPath, zipFilePath } = (() => {
|
||||
const zipFileBasenameWithoutExt = generateFileNameFromURL({
|
||||
url,
|
||||
"preCacheTransform":
|
||||
preCacheTransform:
|
||||
preCacheTransform === undefined
|
||||
? undefined
|
||||
: {
|
||||
"actionCacheId": preCacheTransform.actionCacheId,
|
||||
"actionFootprint": preCacheTransform.action.toString()
|
||||
actionCacheId: preCacheTransform.actionCacheId,
|
||||
actionFootprint: preCacheTransform.action.toString()
|
||||
}
|
||||
});
|
||||
|
||||
const zipFilePath = pathJoin(buildOptions.cacheDirPath, `${zipFileBasenameWithoutExt}.zip`);
|
||||
const extractDirPath = pathJoin(buildOptions.cacheDirPath, `tmp_unzip_${zipFileBasenameWithoutExt}`);
|
||||
const zipFilePath = pathJoin(
|
||||
buildOptions.cacheDirPath,
|
||||
`${zipFileBasenameWithoutExt}.zip`
|
||||
);
|
||||
const extractDirPath = pathJoin(
|
||||
buildOptions.cacheDirPath,
|
||||
`tmp_unzip_${zipFileBasenameWithoutExt}`
|
||||
);
|
||||
|
||||
return { zipFilePath, extractDirPath };
|
||||
})();
|
||||
@ -55,28 +62,30 @@ export async function downloadAndUnzip(params: {
|
||||
|
||||
const { response, isFromRemoteCache } = await (async () => {
|
||||
const proxyFetchOptions = await getProxyFetchOptions({
|
||||
"npmWorkspaceRootDirPath": buildOptions.npmWorkspaceRootDirPath
|
||||
npmWorkspaceRootDirPath: buildOptions.npmWorkspaceRootDirPath
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
response,
|
||||
"isFromRemoteCache": true
|
||||
isFromRemoteCache: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
"response": await fetch(url, proxyFetchOptions),
|
||||
"isFromRemoteCache": false
|
||||
response: await fetch(url, proxyFetchOptions),
|
||||
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
|
||||
@ -102,10 +111,13 @@ export async function downloadAndUnzip(params: {
|
||||
|
||||
try {
|
||||
await preCacheTransform?.action({
|
||||
"destDirPath": extractDirPath
|
||||
destDirPath: extractDirPath
|
||||
});
|
||||
} catch (error) {
|
||||
await Promise.all([rm(extractDirPath, { "recursive": true }), unlink(zipFilePath)]);
|
||||
await Promise.all([
|
||||
rm(extractDirPath, { recursive: true }),
|
||||
unlink(zipFilePath)
|
||||
]);
|
||||
|
||||
throw error;
|
||||
}
|
||||
@ -114,10 +126,11 @@ export async function downloadAndUnzip(params: {
|
||||
|
||||
await zip(extractDirPath, zipFilePath);
|
||||
|
||||
await rm(extractDirPath, { "recursive": true });
|
||||
await rm(extractDirPath, { recursive: true });
|
||||
|
||||
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) {
|
||||
break upload_to_remote_cache_if_admin;
|
||||
@ -145,7 +158,9 @@ export async function downloadAndUnzip(params: {
|
||||
githubToken
|
||||
]);
|
||||
} 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);
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": extractDirPath,
|
||||
"destDirPath": destDirPath
|
||||
srcDirPath: extractDirPath,
|
||||
destDirPath: destDirPath
|
||||
});
|
||||
|
||||
await rm(extractDirPath, { "recursive": true });
|
||||
await rm(extractDirPath, { recursive: true });
|
||||
}
|
||||
|
||||
function generateFileNameFromURL(params: {
|
||||
@ -194,9 +209,15 @@ function generateFileNameFromURL(params: {
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
@ -15,25 +15,38 @@ export type BuildOptionsLike = {
|
||||
|
||||
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;
|
||||
|
||||
await downloadAndUnzip({
|
||||
destDirPath,
|
||||
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
||||
"specificDirsToExtract": ["", "-community"].map(ext => `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`),
|
||||
url: `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
||||
specificDirsToExtract: ["", "-community"].map(
|
||||
ext => `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`
|
||||
),
|
||||
buildOptions,
|
||||
"preCacheTransform": {
|
||||
"actionCacheId": "npm install and build",
|
||||
"action": async ({ destDirPath }) => {
|
||||
preCacheTransform: {
|
||||
actionCacheId: "npm install and build",
|
||||
action: async ({ destDirPath }) => {
|
||||
install_common_node_modules: {
|
||||
const commonResourcesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources");
|
||||
const commonResourcesDirPath = pathJoin(
|
||||
destDirPath,
|
||||
"keycloak",
|
||||
"common",
|
||||
"resources"
|
||||
);
|
||||
|
||||
if (!fs.existsSync(commonResourcesDirPath)) {
|
||||
break install_common_node_modules;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(pathJoin(commonResourcesDirPath, "package.json"))) {
|
||||
if (
|
||||
!fs.existsSync(pathJoin(commonResourcesDirPath, "package.json"))
|
||||
) {
|
||||
break install_common_node_modules;
|
||||
}
|
||||
|
||||
@ -42,36 +55,67 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
|
||||
}
|
||||
|
||||
child_process.execSync("npm install --omit=dev", {
|
||||
"cwd": commonResourcesDirPath,
|
||||
"stdio": "ignore"
|
||||
cwd: commonResourcesDirPath,
|
||||
stdio: "ignore"
|
||||
});
|
||||
}
|
||||
|
||||
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)) {
|
||||
break repatriate_common_resources_from_base_login_theme;
|
||||
}
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": baseLoginThemeResourceDir,
|
||||
"destDirPath": pathJoin(destDirPath, "keycloak", "login", "resources")
|
||||
srcDirPath: baseLoginThemeResourceDir,
|
||||
destDirPath: pathJoin(
|
||||
destDirPath,
|
||||
"keycloak",
|
||||
"login",
|
||||
"resources"
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const accountV2DirSrcDirPath = pathJoin(destDirPath, "keycloak.v2", "account", "src");
|
||||
const accountV2DirSrcDirPath = pathJoin(
|
||||
destDirPath,
|
||||
"keycloak.v2",
|
||||
"account",
|
||||
"src"
|
||||
);
|
||||
|
||||
if (!fs.existsSync(accountV2DirSrcDirPath)) {
|
||||
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") {
|
||||
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);
|
||||
|
||||
@ -94,13 +144,21 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
|
||||
.replace(`${packageManager} run check-types`, "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.rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), { "recursive": true });
|
||||
fs.rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), {
|
||||
recursive: true
|
||||
});
|
||||
}
|
||||
|
||||
remove_keycloak_v2: {
|
||||
@ -110,12 +168,18 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
|
||||
break remove_keycloak_v2;
|
||||
}
|
||||
|
||||
rmSync(keycloakV2DirPath, { "recursive": true });
|
||||
rmSync(keycloakV2DirPath, { recursive: true });
|
||||
}
|
||||
|
||||
// Note, this is an optimization for reducing the size of the jar
|
||||
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)) {
|
||||
break remove_unused_node_modules;
|
||||
@ -167,18 +231,26 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
|
||||
];
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": nodeModuleDirPath,
|
||||
"destDirPath": nodeModuleDirPath,
|
||||
"transformSourceCode": ({ sourceCode, fileRelativePath }) => {
|
||||
srcDirPath: nodeModuleDirPath,
|
||||
destDirPath: nodeModuleDirPath,
|
||||
transformSourceCode: ({ sourceCode, fileRelativePath }) => {
|
||||
if (fileRelativePath.endsWith(".map")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (toDeletePerfixes.find(prefix => fileRelativePath.startsWith(prefix)) !== undefined) {
|
||||
if (
|
||||
toDeletePerfixes.find(prefix =>
|
||||
fileRelativePath.startsWith(prefix)
|
||||
) !== undefined
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (fileRelativePath.startsWith(pathJoin("patternfly", "dist", "fonts"))) {
|
||||
if (
|
||||
fileRelativePath.startsWith(
|
||||
pathJoin("patternfly", "dist", "fonts")
|
||||
)
|
||||
) {
|
||||
if (
|
||||
!fileRelativePath.endsWith(".woff2") &&
|
||||
!fileRelativePath.endsWith(".woff") &&
|
||||
@ -188,34 +260,50 @@ export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: st
|
||||
}
|
||||
}
|
||||
|
||||
return { "modifiedSourceCode": sourceCode };
|
||||
return { modifiedSourceCode: sourceCode };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Just like node_modules
|
||||
remove_unused_lib: {
|
||||
const libDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "lib");
|
||||
const libDirPath = pathJoin(
|
||||
destDirPath,
|
||||
"keycloak",
|
||||
"common",
|
||||
"resources",
|
||||
"lib"
|
||||
);
|
||||
|
||||
if (!fs.existsSync(libDirPath)) {
|
||||
break remove_unused_lib;
|
||||
}
|
||||
|
||||
const toDeletePerfixes = ["ui-ace", "filesaver", "fileupload", "angular", "ui-ace"];
|
||||
const toDeletePerfixes = [
|
||||
"ui-ace",
|
||||
"filesaver",
|
||||
"fileupload",
|
||||
"angular",
|
||||
"ui-ace"
|
||||
];
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": libDirPath,
|
||||
"destDirPath": libDirPath,
|
||||
"transformSourceCode": ({ sourceCode, fileRelativePath }) => {
|
||||
srcDirPath: libDirPath,
|
||||
destDirPath: libDirPath,
|
||||
transformSourceCode: ({ sourceCode, fileRelativePath }) => {
|
||||
if (fileRelativePath.endsWith(".map")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (toDeletePerfixes.find(prefix => fileRelativePath.startsWith(prefix)) !== undefined) {
|
||||
if (
|
||||
toDeletePerfixes.find(prefix =>
|
||||
fileRelativePath.startsWith(prefix)
|
||||
) !== 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(
|
||||
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,
|
||||
// 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 = [
|
||||
...["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", "fonts")
|
||||
];
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": nodeModulesDirPath,
|
||||
"destDirPath": nodeModulesDirPath,
|
||||
"transformSourceCode": ({ sourceCode, fileRelativePath }) => {
|
||||
if (toKeepPrefixes.find(prefix => fileRelativePath.startsWith(prefix)) === undefined) {
|
||||
srcDirPath: nodeModulesDirPath,
|
||||
destDirPath: nodeModulesDirPath,
|
||||
transformSourceCode: ({ sourceCode, fileRelativePath }) => {
|
||||
if (
|
||||
toKeepPrefixes.find(prefix =>
|
||||
fileRelativePath.startsWith(prefix)
|
||||
) === undefined
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return { "modifiedSourceCode": sourceCode };
|
||||
return { modifiedSourceCode: sourceCode };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -24,26 +24,30 @@ export async function downloadKeycloakStaticResources(params: {
|
||||
|
||||
const tmpDirPath = pathJoin(
|
||||
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({
|
||||
keycloakVersion,
|
||||
"destDirPath": tmpDirPath,
|
||||
destDirPath: tmpDirPath,
|
||||
buildOptions
|
||||
});
|
||||
|
||||
const resourcesPath = pathJoin(themeDirPath, themeType, "resources");
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(tmpDirPath, "keycloak", themeType, "resources"),
|
||||
"destDirPath": resourcesPath
|
||||
srcDirPath: pathJoin(tmpDirPath, "keycloak", themeType, "resources"),
|
||||
destDirPath: resourcesPath
|
||||
});
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
|
||||
"destDirPath": pathJoin(resourcesPath, resources_common)
|
||||
srcDirPath: pathJoin(tmpDirPath, "keycloak", "common", "resources"),
|
||||
destDirPath: pathJoin(resourcesPath, resources_common)
|
||||
});
|
||||
|
||||
rmSync(tmpDirPath, { "recursive": true });
|
||||
rmSync(tmpDirPath, { recursive: true });
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import type { KeycloakVersionRange } from "./KeycloakVersionRange";
|
||||
|
||||
export function getJarFileBasename(params: { keycloakVersionRange: KeycloakVersionRange }) {
|
||||
export function getJarFileBasename(params: {
|
||||
keycloakVersionRange: KeycloakVersionRange;
|
||||
}) {
|
||||
const { keycloakVersionRange } = params;
|
||||
|
||||
const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`;
|
||||
|
@ -12,7 +12,10 @@ export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
|
||||
|
||||
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 => {
|
||||
for (const themeSrcDirBasename of themeSrcDirBasenames) {
|
||||
const split = fileRelativePath.split(themeSrcDirBasename);
|
||||
@ -32,7 +35,7 @@ export function getThemeSrcDirPath(params: { reactAppRootDirPath: string }) {
|
||||
if (!fs.existsSync(pathJoin(srcDirPath, themeType))) {
|
||||
continue;
|
||||
}
|
||||
return { "themeSrcDirPath": srcDirPath };
|
||||
return { themeSrcDirPath: srcDirPath };
|
||||
}
|
||||
|
||||
console.error(
|
||||
|
@ -6,29 +6,56 @@ export type MetaInfKeycloakTheme = {
|
||||
themes: { name: string; types: (ThemeType | "email")[] }[];
|
||||
};
|
||||
|
||||
export function getMetaInfKeycloakThemesJsonFilePath(params: { keycloakifyBuildDirPath: string }) {
|
||||
export function getMetaInfKeycloakThemesJsonFilePath(params: {
|
||||
keycloakifyBuildDirPath: string;
|
||||
}) {
|
||||
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;
|
||||
|
||||
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 metaInfKeycloakThemesJsonPath = getMetaInfKeycloakThemesJsonFilePath({ keycloakifyBuildDirPath });
|
||||
const metaInfKeycloakThemesJsonPath = getMetaInfKeycloakThemesJsonFilePath({
|
||||
keycloakifyBuildDirPath
|
||||
});
|
||||
|
||||
{
|
||||
const dirPath = pathDirname(metaInfKeycloakThemesJsonPath);
|
||||
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")
|
||||
);
|
||||
}
|
||||
|
@ -7,19 +7,26 @@ import * as fs from "fs";
|
||||
import type { ReturnType } from "tsafe";
|
||||
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 { getLatestsSemVersionedTag } = (() => {
|
||||
const { octokit } = (() => {
|
||||
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 };
|
||||
})();
|
||||
|
||||
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({ octokit });
|
||||
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({
|
||||
octokit
|
||||
});
|
||||
|
||||
return { getLatestsSemVersionedTag };
|
||||
})();
|
||||
@ -39,7 +46,9 @@ export async function promptKeycloakVersion(params: { startingFromMajor: number
|
||||
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) {
|
||||
fs.unlinkSync(cacheFilePath);
|
||||
@ -50,16 +59,16 @@ export async function promptKeycloakVersion(params: { startingFromMajor: number
|
||||
}
|
||||
|
||||
const semVersionedTags = await getLatestsSemVersionedTag({
|
||||
"count": 50,
|
||||
"owner": "keycloak",
|
||||
"repo": "keycloak"
|
||||
count: 50,
|
||||
owner: "keycloak",
|
||||
repo: "keycloak"
|
||||
});
|
||||
|
||||
{
|
||||
const dirPath = pathDirname(cacheFilePath);
|
||||
|
||||
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,
|
||||
JSON.stringify(
|
||||
id<Cache>({
|
||||
"time": Date.now(),
|
||||
time: Date.now(),
|
||||
semVersionedTags
|
||||
}),
|
||||
null,
|
||||
@ -79,23 +88,33 @@ export async function promptKeycloakVersion(params: { startingFromMajor: number
|
||||
})();
|
||||
|
||||
semVersionedTags.forEach(semVersionedTag => {
|
||||
if (startingFromMajor !== undefined && semVersionedTag.version.major < startingFromMajor) {
|
||||
if (
|
||||
startingFromMajor !== undefined &&
|
||||
semVersionedTag.version.major < startingFromMajor
|
||||
) {
|
||||
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;
|
||||
}
|
||||
|
||||
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>({
|
||||
"values": lastMajorVersions
|
||||
values: lastMajorVersions
|
||||
}).catch(() => {
|
||||
process.exit(-1);
|
||||
});
|
||||
|
@ -407,7 +407,11 @@
|
||||
"otpPolicyLookAheadWindow": 1,
|
||||
"otpPolicyPeriod": 30,
|
||||
"otpPolicyCodeReusable": false,
|
||||
"otpSupportedApplications": ["totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName"],
|
||||
"otpSupportedApplications": [
|
||||
"totpAppFreeOTPName",
|
||||
"totpAppGoogleName",
|
||||
"totpAppMicrosoftAuthenticatorName"
|
||||
],
|
||||
"localizationTexts": {},
|
||||
"webAuthnPolicyRpEntityName": "keycloak",
|
||||
"webAuthnPolicySignatureAlgorithms": ["ES256"],
|
||||
@ -520,7 +524,12 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"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",
|
||||
@ -562,7 +571,12 @@
|
||||
}
|
||||
],
|
||||
"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",
|
||||
@ -589,7 +603,12 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"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",
|
||||
@ -616,7 +635,12 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"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",
|
||||
@ -761,7 +785,12 @@
|
||||
}
|
||||
],
|
||||
"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",
|
||||
@ -788,7 +817,12 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"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",
|
||||
@ -838,7 +872,12 @@
|
||||
}
|
||||
],
|
||||
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
|
||||
"optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"]
|
||||
"optionalClientScopes": [
|
||||
"address",
|
||||
"phone",
|
||||
"offline_access",
|
||||
"microprofile-jwt"
|
||||
]
|
||||
}
|
||||
],
|
||||
"clientScopes": [
|
||||
@ -1379,8 +1418,20 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins", "acr"],
|
||||
"defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"],
|
||||
"defaultDefaultClientScopes": [
|
||||
"role_list",
|
||||
"profile",
|
||||
"email",
|
||||
"roles",
|
||||
"web-origins",
|
||||
"acr"
|
||||
],
|
||||
"defaultOptionalClientScopes": [
|
||||
"offline_access",
|
||||
"address",
|
||||
"phone",
|
||||
"microprofile-jwt"
|
||||
],
|
||||
"browserSecurityHeaders": {
|
||||
"contentSecurityPolicyReportOnly": "",
|
||||
"xContentTypeOptions": "nosniff",
|
||||
@ -1575,7 +1626,19 @@
|
||||
]
|
||||
},
|
||||
"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",
|
||||
"authenticationFlows": [
|
||||
{
|
||||
|
@ -2,13 +2,22 @@ import { readBuildOptions } from "../shared/buildOptions";
|
||||
import type { CliCommandOptions as CliCommandOptions_common } from "../main";
|
||||
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
|
||||
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 type { KeycloakVersionRange } from "../shared/KeycloakVersionRange";
|
||||
import { getJarFileBasename } from "../shared/getJarFileBasename";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
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 chalk from "chalk";
|
||||
import chokidar from "chokidar";
|
||||
@ -30,7 +39,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
let commandOutput: Buffer | undefined = undefined;
|
||||
|
||||
try {
|
||||
commandOutput = child_process.execSync("docker --version", { "stdio": ["ignore", "pipe", "ignore"] });
|
||||
commandOutput = child_process.execSync("docker --version", {
|
||||
stdio: ["ignore", "pipe", "ignore"]
|
||||
});
|
||||
} catch {}
|
||||
|
||||
if (commandOutput?.toString("utf8").includes("Docker")) {
|
||||
@ -40,7 +51,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
console.log(
|
||||
[
|
||||
`${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)`
|
||||
].join(" ")
|
||||
);
|
||||
@ -52,7 +65,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
let isDockerRunning: boolean;
|
||||
|
||||
try {
|
||||
child_process.execSync("docker info", { "stdio": "ignore" });
|
||||
child_process.execSync("docker info", { stdio: "ignore" });
|
||||
isDockerRunning = true;
|
||||
} catch {
|
||||
isDockerRunning = false;
|
||||
@ -62,7 +75,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
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);
|
||||
}
|
||||
@ -77,51 +95,60 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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<{
|
||||
keycloakVersion: string;
|
||||
keycloakMajorNumber: number;
|
||||
}> {
|
||||
if (cliCommandOptions.keycloakVersion !== undefined) {
|
||||
return {
|
||||
"keycloakVersion": cliCommandOptions.keycloakVersion,
|
||||
"keycloakMajorNumber": SemVer.parse(cliCommandOptions.keycloakVersion).major
|
||||
};
|
||||
}
|
||||
const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } =
|
||||
await (async function getKeycloakMajor(): Promise<{
|
||||
keycloakVersion: string;
|
||||
keycloakMajorNumber: number;
|
||||
}> {
|
||||
if (cliCommandOptions.keycloakVersion !== undefined) {
|
||||
return {
|
||||
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(
|
||||
[
|
||||
"Unfortunately, Keycloakify themes that implements an account theme do not work on Keycloak 22",
|
||||
"Please select any other Keycloak version"
|
||||
].join(" ")
|
||||
chalk.cyan("On which version of Keycloak do you want to test your theme?")
|
||||
);
|
||||
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 = (() => {
|
||||
if (doesImplementAccountTheme) {
|
||||
@ -139,7 +166,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
return "24-and-above" as const;
|
||||
})();
|
||||
|
||||
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithAccountTheme>>();
|
||||
assert<
|
||||
Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithAccountTheme>
|
||||
>();
|
||||
|
||||
return keycloakVersionRange;
|
||||
} else {
|
||||
@ -151,7 +180,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
return "22-and-above" as const;
|
||||
})();
|
||||
|
||||
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithoutAccountTheme>>();
|
||||
assert<
|
||||
Equals<
|
||||
typeof keycloakVersionRange,
|
||||
KeycloakVersionRange.WithoutAccountTheme
|
||||
>
|
||||
>();
|
||||
|
||||
return keycloakVersionRange;
|
||||
}
|
||||
@ -163,7 +197,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
|
||||
const mountTargets = buildOptions.themeNames
|
||||
.map(themeName => {
|
||||
const themeEntry = metaInfKeycloakThemes.themes.find(({ name }) => name === themeName);
|
||||
const themeEntry = metaInfKeycloakThemes.themes.find(
|
||||
({ name }) => name === themeName
|
||||
);
|
||||
|
||||
assert(themeEntry !== undefined);
|
||||
|
||||
@ -181,10 +217,24 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
|
||||
return fs
|
||||
.readdirSync(localPathDirname)
|
||||
.filter(fileOrDirectoryBasename => !fileOrDirectoryBasename.endsWith(".properties"))
|
||||
.filter(
|
||||
fileOrDirectoryBasename =>
|
||||
!fileOrDirectoryBasename.endsWith(".properties")
|
||||
)
|
||||
.map(fileOrDirectoryBasename => ({
|
||||
"localPath": pathJoin(localPathDirname, fileOrDirectoryBasename),
|
||||
"containerPath": pathPosix.join("/", "opt", "keycloak", "themes", themeName, themeType, fileOrDirectoryBasename)
|
||||
localPath: pathJoin(
|
||||
localPathDirname,
|
||||
fileOrDirectoryBasename
|
||||
),
|
||||
containerPath: pathPosix.join(
|
||||
"/",
|
||||
"opt",
|
||||
"keycloak",
|
||||
"themes",
|
||||
themeName,
|
||||
themeType,
|
||||
fileOrDirectoryBasename
|
||||
)
|
||||
}));
|
||||
})
|
||||
.flat();
|
||||
@ -192,33 +242,56 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
.flat();
|
||||
|
||||
try {
|
||||
child_process.execSync(`docker rm --force ${containerName}`, { "stdio": "ignore" });
|
||||
child_process.execSync(`docker rm --force ${containerName}`, {
|
||||
stdio: "ignore"
|
||||
});
|
||||
} catch {}
|
||||
|
||||
const realmJsonFilePath = await (async () => {
|
||||
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({
|
||||
"pathIsh": cliCommandOptions.realmJsonFilePath,
|
||||
"cwd": process.cwd()
|
||||
pathIsh: cliCommandOptions.realmJsonFilePath,
|
||||
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)) {
|
||||
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:"));
|
||||
|
||||
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(() => {
|
||||
process.exit(-1);
|
||||
});
|
||||
@ -238,17 +311,37 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
...["--name", containerName],
|
||||
...["-e", "KEYCLOAK_ADMIN=admin"],
|
||||
...["-e", "KEYCLOAK_ADMIN_PASSWORD=admin"],
|
||||
...(realmJsonFilePath === undefined ? [] : ["-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(),
|
||||
...(realmJsonFilePath === undefined
|
||||
? []
|
||||
: [
|
||||
"-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}`,
|
||||
"start-dev",
|
||||
...(21 <= keycloakMajorVersionNumber && keycloakMajorVersionNumber < 24 ? ["--features=declarative-user-profile"] : []),
|
||||
...(21 <= keycloakMajorVersionNumber && keycloakMajorVersionNumber < 24
|
||||
? ["--features=declarative-user-profile"]
|
||||
: []),
|
||||
...(realmJsonFilePath === undefined ? [] : ["--import-realm"])
|
||||
],
|
||||
{
|
||||
"cwd": buildOptions.keycloakifyBuildDirPath
|
||||
cwd: buildOptions.keycloakifyBuildDirPath
|
||||
}
|
||||
] as const;
|
||||
|
||||
@ -278,17 +371,23 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
[
|
||||
"",
|
||||
`${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:",
|
||||
`- username: ${chalk.cyan.bold("testuser")}`,
|
||||
`- 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")}`,
|
||||
`- 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")
|
||||
);
|
||||
};
|
||||
@ -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]) => {
|
||||
if (
|
||||
isInside({
|
||||
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "src", "bin"),
|
||||
filePath
|
||||
}) ||
|
||||
isInside({
|
||||
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "bin"),
|
||||
filePath
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
chokidar
|
||||
.watch([srcDirPath, getThisCodebaseRootDirPath()], {
|
||||
ignoreInitial: true
|
||||
})
|
||||
.on("all", async (...[, filePath]) => {
|
||||
if (
|
||||
isInside({
|
||||
dirPath: pathJoin(getThisCodebaseRootDirPath(), "src", "bin"),
|
||||
filePath
|
||||
}) ||
|
||||
isInside({
|
||||
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"], {
|
||||
"cwd": buildOptions.reactAppRootDirPath,
|
||||
"env": process.env
|
||||
});
|
||||
{
|
||||
const child = child_process.spawn("npx", ["vite", "build"], {
|
||||
cwd: buildOptions.reactAppRootDirPath,
|
||||
env: process.env
|
||||
});
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
if (data.toString("utf8").includes("gzip:")) {
|
||||
return;
|
||||
}
|
||||
child.stdout.on("data", data => {
|
||||
if (data.toString("utf8").includes("gzip:")) {
|
||||
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 => {
|
||||
if (code === 0) {
|
||||
dViteBuildDone.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
child.on("exit", code => {
|
||||
if (code === 0) {
|
||||
dViteBuildDone.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await dViteBuildDone.pr;
|
||||
await dViteBuildDone.pr;
|
||||
|
||||
{
|
||||
const child = child_process.spawn("npx", ["keycloakify", "build"], {
|
||||
"cwd": buildOptions.reactAppRootDirPath,
|
||||
"env": {
|
||||
...process.env,
|
||||
[skipBuildJarsEnvName]: "true"
|
||||
}
|
||||
});
|
||||
{
|
||||
const child = child_process.spawn("npx", ["keycloakify", "build"], {
|
||||
cwd: buildOptions.reactAppRootDirPath,
|
||||
env: {
|
||||
...process.env,
|
||||
[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 => {
|
||||
if (code !== 0) {
|
||||
console.log(chalk.yellow("Theme not updated, build failed"));
|
||||
return;
|
||||
}
|
||||
child.on("exit", code => {
|
||||
if (code !== 0) {
|
||||
console.log(chalk.yellow("Theme not updated, build failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.green("Rebuild done"));
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log(chalk.green("Rebuild done"));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,35 +12,39 @@ export namespace SemVer {
|
||||
export type BumpType = (typeof bumpTypes)[number];
|
||||
|
||||
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) {
|
||||
throw new Error(`${versionStr} is not a valid semantic version`);
|
||||
}
|
||||
|
||||
const semVer: Omit<SemVer, "parsedFrom"> = {
|
||||
"major": parseInt(match[1]),
|
||||
"minor": parseInt(match[2]),
|
||||
"patch": (() => {
|
||||
major: parseInt(match[1]),
|
||||
minor: parseInt(match[2]),
|
||||
patch: (() => {
|
||||
const str = match[3];
|
||||
|
||||
return str === undefined ? 0 : parseInt(str);
|
||||
})(),
|
||||
...(() => {
|
||||
const str = match[4];
|
||||
return str === undefined ? {} : { "rc": parseInt(str) };
|
||||
return str === undefined ? {} : { rc: parseInt(str) };
|
||||
})()
|
||||
};
|
||||
|
||||
const initialStr = stringify(semVer);
|
||||
|
||||
Object.defineProperty(semVer, "parsedFrom", {
|
||||
"enumerable": true,
|
||||
"get": function () {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
const currentStr = stringify(this);
|
||||
|
||||
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;
|
||||
@ -51,7 +55,9 @@ export namespace SemVer {
|
||||
}
|
||||
|
||||
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 )
|
||||
*/
|
||||
|
||||
export function bumpType(params: { versionBehind: string | SemVer; 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;
|
||||
export function bumpType(params: {
|
||||
versionBehind: string | SemVer;
|
||||
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) {
|
||||
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) {
|
||||
|
@ -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) {
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ const crawlRec = (dirPath: string, filePaths: string[]) => {
|
||||
};
|
||||
|
||||
/** 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 filePaths: string[] = [];
|
||||
|
@ -1,27 +1,42 @@
|
||||
import { Readable } from "stream";
|
||||
|
||||
const crc32tab = [
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 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,
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
|
||||
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
|
||||
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
|
||||
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
||||
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
|
||||
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
||||
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
||||
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
|
||||
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
|
||||
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
|
||||
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
||||
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
|
||||
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
|
||||
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
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
|
||||
];
|
||||
|
||||
@ -33,11 +48,13 @@ const crc32tab = [
|
||||
export function crc32(input: Readable | String | Buffer): Promise<number> {
|
||||
if (typeof input === "string") {
|
||||
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);
|
||||
} else if (input instanceof Buffer) {
|
||||
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);
|
||||
} else if (input instanceof Readable) {
|
||||
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("error", e => reject(e));
|
||||
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 {
|
||||
|
@ -11,7 +11,10 @@ function ensureSingleOrNone<T>(arg0: T | T[]) {
|
||||
if (!Array.isArray(arg0)) return arg0;
|
||||
if (arg0.length === 0) return undefined;
|
||||
if (arg0.length === 1) return arg0[0];
|
||||
throw new Error("Illegal configuration, expected a single value but found multiple: " + arg0.map(String).join(", "));
|
||||
throw new Error(
|
||||
"Illegal configuration, expected a single value but found multiple: " +
|
||||
arg0.map(String).join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
type NPMConfig = Record<string, string | string[]>;
|
||||
@ -24,10 +27,15 @@ async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
|
||||
|
||||
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]) =>
|
||||
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
|
||||
.split("\n")
|
||||
@ -37,9 +45,14 @@ async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
|
||||
.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 cfg = await getNmpConfig({ npmWorkspaceRootDirPath });
|
||||
@ -60,14 +73,24 @@ export async function getProxyFetchOptions(params: { npmWorkspaceRootDirPath: st
|
||||
ca.push(
|
||||
...(await (async () => {
|
||||
function chunks<T>(arr: T[], size: number = 2) {
|
||||
return arr.map((_, i) => i % size == 0 && arr.slice(i, i + size)).filter(Boolean) as T[][];
|
||||
return arr
|
||||
.map((_, i) => i % size == 0 && arr.slice(i, i + size))
|
||||
.filter(Boolean) as T[][];
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
export function getAbsoluteAndInOsFormatPath(params: { pathIsh: string; cwd: string }): string {
|
||||
export function getAbsoluteAndInOsFormatPath(params: {
|
||||
pathIsh: string;
|
||||
cwd: string;
|
||||
}): string {
|
||||
const { pathIsh, cwd } = params;
|
||||
|
||||
let pathOut = pathIsh;
|
||||
|
@ -3,14 +3,22 @@ import { join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
|
||||
import { assert } from "tsafe/assert";
|
||||
import * as fs from "fs";
|
||||
|
||||
export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string; dependencyExpected: string }) {
|
||||
export function getNpmWorkspaceRootDirPath(params: {
|
||||
reactAppRootDirPath: string;
|
||||
dependencyExpected: string;
|
||||
}) {
|
||||
const { reactAppRootDirPath, dependencyExpected } = params;
|
||||
|
||||
const npmWorkspaceRootDirPath = (function callee(depth: number): string {
|
||||
const cwd = pathResolve(pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")]));
|
||||
const cwd = pathResolve(
|
||||
pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")])
|
||||
);
|
||||
|
||||
try {
|
||||
child_process.execSync("npm config get", { cwd, "stdio": "ignore" });
|
||||
child_process.execSync("npm config get", {
|
||||
cwd,
|
||||
stdio: "ignore"
|
||||
});
|
||||
} catch (error) {
|
||||
if (String(error).includes("ENOWORKSPACES")) {
|
||||
assert(cwd !== pathSep, "NPM workspace not found");
|
||||
@ -26,11 +34,16 @@ export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string
|
||||
|
||||
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;
|
||||
|
||||
for (const dependenciesOrDevDependencies of ["dependencies", "devDependencies"] as const) {
|
||||
for (const dependenciesOrDevDependencies of [
|
||||
"dependencies",
|
||||
"devDependencies"
|
||||
] as const) {
|
||||
const dependencies = parsedPackageJson[dependenciesOrDevDependencies];
|
||||
|
||||
if (dependencies === undefined) {
|
||||
|
@ -5,7 +5,11 @@ import { SemVer } from "../SemVer";
|
||||
export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
|
||||
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;
|
||||
version: SemVer;
|
||||
@ -33,7 +37,9 @@ export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
|
||||
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 };
|
||||
|
@ -5,11 +5,19 @@ const per_page = 99;
|
||||
export function listTagsFactory(params: { octokit: Octokit }) {
|
||||
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);
|
||||
};
|
||||
|
||||
async function* listTags(params: { owner: string; repo: string }): AsyncGenerator<string> {
|
||||
async function* listTags(params: {
|
||||
owner: string;
|
||||
repo: string;
|
||||
}): AsyncGenerator<string> {
|
||||
const { owner, repo } = params;
|
||||
|
||||
let page = 1;
|
||||
@ -19,7 +27,7 @@ export function listTagsFactory(params: { octokit: Octokit }) {
|
||||
owner,
|
||||
repo,
|
||||
per_page,
|
||||
"page": page++
|
||||
page: page++
|
||||
});
|
||||
|
||||
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 */
|
||||
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 itRes = await listTags({ owner, repo }).next();
|
||||
|
@ -2,10 +2,19 @@ export type PromiseSettledAndPartitioned<T> = [T[], any[]];
|
||||
|
||||
export function partitionPromiseSettledResults<T>() {
|
||||
return [
|
||||
([successes, failures]: PromiseSettledAndPartitioned<T>, item: PromiseSettledResult<T>) =>
|
||||
(
|
||||
[successes, failures]: PromiseSettledAndPartitioned<T>,
|
||||
item: PromiseSettledResult<T>
|
||||
) =>
|
||||
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 const;
|
||||
}
|
||||
|
@ -4,7 +4,11 @@ import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
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");
|
||||
|
||||
|
@ -3,7 +3,11 @@ import * as path from "path";
|
||||
import { crawl } from "./crawl";
|
||||
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;
|
||||
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
|
||||
* 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 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, {
|
||||
"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 destFilePath = path.join(destDirPath, fileRelativePath);
|
||||
|
||||
@ -34,7 +47,7 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
|
||||
// it using the lower level implementation.
|
||||
if (transformSourceCode === undefined) {
|
||||
fs.mkdirSync(path.dirname(destFilePath), {
|
||||
"recursive": true
|
||||
recursive: true
|
||||
});
|
||||
|
||||
fs.copyFileSync(filePath, destFilePath);
|
||||
@ -43,7 +56,7 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
|
||||
}
|
||||
|
||||
const transformSourceCodeResult = transformSourceCode({
|
||||
"sourceCode": fs.readFileSync(filePath),
|
||||
sourceCode: fs.readFileSync(filePath),
|
||||
filePath,
|
||||
fileRelativePath
|
||||
});
|
||||
@ -53,16 +66,22 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(destFilePath), {
|
||||
"recursive": true
|
||||
recursive: true
|
||||
});
|
||||
|
||||
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) {
|
||||
rmSync(srcDirPath, { "recursive": true });
|
||||
rmSync(srcDirPath, { recursive: true });
|
||||
|
||||
fs.renameSync(destDirPath, srcDirPath);
|
||||
}
|
||||
|
@ -14,7 +14,10 @@ function populateTemplate(strings: TemplateStringsArray, ...args: unknown[]) {
|
||||
if (args[i]) {
|
||||
// if the interpolation value has newlines, indent the interpolation values
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,11 @@ async function pathExists(path: string) {
|
||||
|
||||
// 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.
|
||||
export async function unzip(file: string, targetFolder: string, specificDirsToExtract?: string[]) {
|
||||
export async function unzip(
|
||||
file: string,
|
||||
targetFolder: string,
|
||||
specificDirsToExtract?: string[]
|
||||
) {
|
||||
specificDirsToExtract = specificDirsToExtract?.map(dirPath => {
|
||||
if (!dirPath.endsWith("/") || !dirPath.endsWith("\\")) {
|
||||
dirPath += "/";
|
||||
@ -49,7 +53,9 @@ export async function unzip(file: string, targetFolder: string, specificDirsToEx
|
||||
|
||||
zipfile.on("entry", async entry => {
|
||||
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
|
||||
if (dirPath === undefined) {
|
||||
@ -85,7 +91,9 @@ export async function unzip(file: string, targetFolder: string, specificDirsToEx
|
||||
return;
|
||||
}
|
||||
|
||||
await fsp.mkdir(path.dirname(target), { "recursive": true });
|
||||
await fsp.mkdir(path.dirname(target), {
|
||||
recursive: true
|
||||
});
|
||||
|
||||
await pipeline(readStream, fs.createWriteStream(target));
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,14 +1,23 @@
|
||||
import { clsx } from "keycloakify/tools/clsx";
|
||||
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;
|
||||
|
||||
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 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 };
|
||||
|
@ -41,13 +41,13 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
}, []);
|
||||
|
||||
useSetClassName({
|
||||
"qualifiedName": "html",
|
||||
"className": getClassName("kcHtmlClass")
|
||||
qualifiedName: "html",
|
||||
className: getClassName("kcHtmlClass")
|
||||
});
|
||||
|
||||
useSetClassName({
|
||||
"qualifiedName": "body",
|
||||
"className": bodyClassName ?? getClassName("kcBodyClass")
|
||||
qualifiedName: "body",
|
||||
className: bodyClassName ?? getClassName("kcBodyClass")
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@ -63,7 +63,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
}, []);
|
||||
|
||||
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
||||
"hrefs": !doUseDefaultCss
|
||||
hrefs: !doUseDefaultCss
|
||||
? []
|
||||
: [
|
||||
`${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`,
|
||||
@ -75,17 +75,17 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
});
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
"scriptTags": [
|
||||
scriptTags: [
|
||||
{
|
||||
"type": "module",
|
||||
"src": `${url.resourcesPath}/js/menu-button-links.js`
|
||||
type: "module",
|
||||
src: `${url.resourcesPath}/js/menu-button-links.js`
|
||||
},
|
||||
...(authenticationSession === undefined
|
||||
? []
|
||||
: [
|
||||
{
|
||||
"type": "module",
|
||||
"textContent": [
|
||||
type: "module",
|
||||
textContent: [
|
||||
`import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";`,
|
||||
``,
|
||||
`checkCookiesAndSetTimer(`,
|
||||
@ -99,8 +99,8 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
...scripts.map(
|
||||
script =>
|
||||
({
|
||||
"type": "text/javascript",
|
||||
"src": script
|
||||
type: "text/javascript",
|
||||
src: script
|
||||
} as const)
|
||||
)
|
||||
]
|
||||
@ -237,7 +237,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
<span
|
||||
className={getClassName("kcAlertTitleClass")}
|
||||
dangerouslySetInnerHTML={{
|
||||
"__html": message.summary
|
||||
__html: message.summary
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,7 +2,10 @@ import type { ReactNode } from "react";
|
||||
import type { KcContext } from "./kcContext";
|
||||
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;
|
||||
i18n: I18nExtended;
|
||||
doUseDefaultCss: boolean;
|
||||
|
@ -43,7 +43,7 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps)
|
||||
onIsFormSubmittableValueChange(isFormSubmittable);
|
||||
}, [isFormSubmittable]);
|
||||
|
||||
const groupNameRef = { "current": "" };
|
||||
const groupNameRef = { current: "" };
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -62,7 +62,9 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps)
|
||||
)}
|
||||
<div
|
||||
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")}>
|
||||
<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]))}
|
||||
onChange={event =>
|
||||
formValidationDispatch({
|
||||
"action": "update",
|
||||
"name": attribute.name,
|
||||
"valueOrValues": (() => {
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: (() => {
|
||||
if (fieldIndex !== undefined) {
|
||||
assert(valueOrValues instanceof Array);
|
||||
|
||||
@ -363,9 +365,9 @@ function InputTag(props: InputFiledByTypeProps & { fieldIndex: number | undefine
|
||||
}
|
||||
onBlur={() =>
|
||||
props.formValidationDispatch({
|
||||
"action": "focus lost",
|
||||
"name": attribute.name,
|
||||
"fieldIndex": fieldIndex
|
||||
action: "focus lost",
|
||||
name: attribute.name,
|
||||
fieldIndex: fieldIndex
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -484,9 +486,9 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
|
||||
className="pf-c-button pf-m-inline pf-m-link"
|
||||
onClick={() =>
|
||||
dispatchFormAction({
|
||||
"action": "update",
|
||||
"name": attribute.name,
|
||||
"valueOrValues": values.filter((_, i) => i !== fieldIndex)
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: values.filter((_, i) => i !== fieldIndex)
|
||||
})
|
||||
}
|
||||
>
|
||||
@ -501,9 +503,9 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
|
||||
className="pf-c-button pf-m-inline pf-m-link"
|
||||
onClick={() =>
|
||||
dispatchFormAction({
|
||||
"action": "update",
|
||||
"name": attribute.name,
|
||||
"valueOrValues": [...values, ""]
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: [...values, ""]
|
||||
})
|
||||
}
|
||||
>
|
||||
@ -527,17 +529,17 @@ function InputTagSelects(props: InputFiledByTypeProps) {
|
||||
switch (inputType) {
|
||||
case "select-radiobuttons":
|
||||
return {
|
||||
"inputType": "radio",
|
||||
"classDiv": getClassName("kcInputClassRadio"),
|
||||
"classInput": getClassName("kcInputClassRadioInput"),
|
||||
"classLabel": getClassName("kcInputClassRadioLabel")
|
||||
inputType: "radio",
|
||||
classDiv: getClassName("kcInputClassRadio"),
|
||||
classInput: getClassName("kcInputClassRadioInput"),
|
||||
classLabel: getClassName("kcInputClassRadioLabel")
|
||||
};
|
||||
case "multiselect-checkboxes":
|
||||
return {
|
||||
"inputType": "checkbox",
|
||||
"classDiv": getClassName("kcInputClassCheckbox"),
|
||||
"classInput": getClassName("kcInputClassCheckboxInput"),
|
||||
"classLabel": getClassName("kcInputClassCheckboxLabel")
|
||||
inputType: "checkbox",
|
||||
classDiv: getClassName("kcInputClassCheckbox"),
|
||||
classInput: getClassName("kcInputClassCheckboxInput"),
|
||||
classLabel: getClassName("kcInputClassCheckboxLabel")
|
||||
};
|
||||
}
|
||||
})();
|
||||
@ -581,9 +583,9 @@ function InputTagSelects(props: InputFiledByTypeProps) {
|
||||
checked={valueOrValues.includes(option)}
|
||||
onChange={event =>
|
||||
formValidationDispatch({
|
||||
"action": "update",
|
||||
"name": attribute.name,
|
||||
"valueOrValues": (() => {
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: (() => {
|
||||
const isChecked = event.target.checked;
|
||||
|
||||
if (valueOrValues instanceof Array) {
|
||||
@ -604,9 +606,9 @@ function InputTagSelects(props: InputFiledByTypeProps) {
|
||||
}
|
||||
onBlur={() =>
|
||||
formValidationDispatch({
|
||||
"action": "focus lost",
|
||||
"name": attribute.name,
|
||||
"fieldIndex": undefined
|
||||
action: "focus lost",
|
||||
name: attribute.name,
|
||||
fieldIndex: undefined
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -642,16 +644,16 @@ function TextareaTag(props: InputFiledByTypeProps) {
|
||||
value={value}
|
||||
onChange={event =>
|
||||
formValidationDispatch({
|
||||
"action": "update",
|
||||
"name": attribute.name,
|
||||
"valueOrValues": event.target.value
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: event.target.value
|
||||
})
|
||||
}
|
||||
onBlur={() =>
|
||||
formValidationDispatch({
|
||||
"action": "focus lost",
|
||||
"name": attribute.name,
|
||||
"fieldIndex": undefined
|
||||
action: "focus lost",
|
||||
name: attribute.name,
|
||||
fieldIndex: undefined
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -677,9 +679,9 @@ function SelectTag(props: InputFiledByTypeProps) {
|
||||
value={valueOrValues}
|
||||
onChange={event =>
|
||||
formValidationDispatch({
|
||||
"action": "update",
|
||||
"name": attribute.name,
|
||||
"valueOrValues": (() => {
|
||||
action: "update",
|
||||
name: attribute.name,
|
||||
valueOrValues: (() => {
|
||||
if (isMultiple) {
|
||||
return Array.from(event.target.selectedOptions).map(option => option.value);
|
||||
}
|
||||
@ -690,9 +692,9 @@ function SelectTag(props: InputFiledByTypeProps) {
|
||||
}
|
||||
onBlur={() =>
|
||||
formValidationDispatch({
|
||||
"action": "focus lost",
|
||||
"name": attribute.name,
|
||||
"fieldIndex": undefined
|
||||
action: "focus lost",
|
||||
name: attribute.name,
|
||||
fieldIndex: undefined
|
||||
})
|
||||
}
|
||||
>
|
||||
|
@ -91,19 +91,19 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
|
||||
setI18n({
|
||||
...createI18nTranslationFunctions({
|
||||
"fallbackMessages": {
|
||||
fallbackMessages: {
|
||||
...fallbackMessages,
|
||||
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
|
||||
...(extraMessages[fallbackLanguageTag] ?? {})
|
||||
} as any,
|
||||
"messages": {
|
||||
messages: {
|
||||
...(await getMessages(currentLanguageTag)),
|
||||
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
|
||||
...(extraMessages[currentLanguageTag] ?? {})
|
||||
} as any
|
||||
}),
|
||||
currentLanguageTag,
|
||||
"getChangeLocalUrl": newLanguageTag => {
|
||||
getChangeLocalUrl: newLanguageTag => {
|
||||
const { locale } = kcContext;
|
||||
|
||||
assert(locale !== undefined, "Internationalization not enabled");
|
||||
@ -114,7 +114,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
|
||||
return targetSupportedLocale.url;
|
||||
},
|
||||
"labelBySupportedLanguageTag": Object.fromEntries(
|
||||
labelBySupportedLanguageTag: Object.fromEntries(
|
||||
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
|
||||
)
|
||||
});
|
||||
@ -170,7 +170,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
})();
|
||||
|
||||
return doRenderMarkdown ? (
|
||||
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}>
|
||||
<Markdown allowDangerousHtml renderers={{ paragraph: "span" }}>
|
||||
{messageWithArgsInjectedIfAny}
|
||||
</Markdown>
|
||||
) : (
|
||||
@ -186,7 +186,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||
|
||||
const out = resolveMsg({
|
||||
"key": keyUnwrappedFromCurlyBraces,
|
||||
key: keyUnwrappedFromCurlyBraces,
|
||||
args,
|
||||
doRenderMarkdown
|
||||
});
|
||||
@ -195,38 +195,48 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
}
|
||||
|
||||
return {
|
||||
"msgStr": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": false }) as string,
|
||||
"msg": (key, ...args) => resolveMsg({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsg": (key, ...args) => resolveMsgAdvanced({ key, args, "doRenderMarkdown": true }) as JSX.Element,
|
||||
"advancedMsgStr": (key, ...args) => resolveMsgAdvanced({ 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,
|
||||
advancedMsg: (key, ...args) =>
|
||||
resolveMsgAdvanced({
|
||||
key,
|
||||
args,
|
||||
doRenderMarkdown: true
|
||||
}) as JSX.Element,
|
||||
advancedMsgStr: (key, ...args) =>
|
||||
resolveMsgAdvanced({
|
||||
key,
|
||||
args,
|
||||
doRenderMarkdown: false
|
||||
}) as string
|
||||
};
|
||||
}
|
||||
|
||||
const keycloakifyExtraMessages = {
|
||||
"en": {
|
||||
"shouldBeEqual": "{0} should be equal to {1}",
|
||||
"shouldBeDifferent": "{0} should be different to {1}",
|
||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||
"mustBeAnInteger": "Must be an integer",
|
||||
"notAValidOption": "Not a valid option",
|
||||
"selectAnOption": "Select an option",
|
||||
"remove": "Remove",
|
||||
"addValue": "Add value"
|
||||
en: {
|
||||
shouldBeEqual: "{0} should be equal to {1}",
|
||||
shouldBeDifferent: "{0} should be different to {1}",
|
||||
shouldMatchPattern: "Pattern should match: `/{0}/`",
|
||||
mustBeAnInteger: "Must be an integer",
|
||||
notAValidOption: "Not a valid option",
|
||||
selectAnOption: "Select an option",
|
||||
remove: "Remove",
|
||||
addValue: "Add value"
|
||||
},
|
||||
"fr": {
|
||||
fr: {
|
||||
/* spell-checker: disable */
|
||||
"shouldBeEqual": "{0} doit être égal à {1}",
|
||||
"shouldBeDifferent": "{0} doit être différent de {1}",
|
||||
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
||||
"mustBeAnInteger": "Doit être un nombre entier",
|
||||
"notAValidOption": "N'est pas une option valide",
|
||||
shouldBeEqual: "{0} doit être égal à {1}",
|
||||
shouldBeDifferent: "{0} doit être différent de {1}",
|
||||
shouldMatchPattern: "Dois respecter le schéma: `/{0}/`",
|
||||
mustBeAnInteger: "Doit être un nombre entier",
|
||||
notAValidOption: "N'est pas une option valide",
|
||||
|
||||
"logoutConfirmTitle": "Déconnexion",
|
||||
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
||||
"doLogout": "Se déconnecter",
|
||||
"selectAnOption": "Sélectionner une option",
|
||||
"remove": "Supprimer",
|
||||
"addValue": "Ajouter une valeur"
|
||||
logoutConfirmTitle: "Déconnexion",
|
||||
logoutConfirmHeader: "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
||||
doLogout: "Se déconnecter",
|
||||
selectAnOption: "Sélectionner une option",
|
||||
remove: "Supprimer",
|
||||
addValue: "Ajouter une valeur"
|
||||
/* spell-checker: enable */
|
||||
}
|
||||
};
|
||||
|
@ -3,7 +3,10 @@ import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
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.
|
||||
* Some values might be undefined on some pages.
|
||||
@ -103,7 +106,10 @@ export declare namespace KcContext {
|
||||
* @param text to return
|
||||
* @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
|
||||
*
|
||||
|
@ -10,15 +10,23 @@ import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||
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>>[];
|
||||
mockProperties?: Record<string, string>;
|
||||
}) {
|
||||
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;
|
||||
storyPartialKcContext?: DeepPartial<Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>>;
|
||||
storyPartialKcContext?: DeepPartial<
|
||||
Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>
|
||||
>;
|
||||
}): {
|
||||
kcContext: PageId extends undefined
|
||||
? ExtendKcContext<KcContextExtension> | undefined
|
||||
@ -36,34 +44,46 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
||||
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 out: DeepPartial<ExtendKcContext<KcContextExtension>> = {};
|
||||
|
||||
const mockDataPick = mockData?.find(({ pageId }) => pageId === mockPageId);
|
||||
const mockDataPick = mockData?.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
if (mockDataPick !== undefined) {
|
||||
deepAssign({
|
||||
"target": out,
|
||||
"source": mockDataPick
|
||||
target: out,
|
||||
source: mockDataPick
|
||||
});
|
||||
}
|
||||
|
||||
if (storyPartialKcContext !== undefined) {
|
||||
deepAssign({
|
||||
"target": out,
|
||||
"source": storyPartialKcContext
|
||||
target: out,
|
||||
source: storyPartialKcContext
|
||||
});
|
||||
}
|
||||
|
||||
return Object.keys(out).length === 0 ? undefined : out;
|
||||
})();
|
||||
|
||||
if (kcContextDefaultMock === undefined && partialKcContextCustomMock === undefined) {
|
||||
if (
|
||||
kcContextDefaultMock === undefined &&
|
||||
partialKcContextCustomMock === undefined
|
||||
) {
|
||||
console.warn(
|
||||
[
|
||||
`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 = {};
|
||||
|
||||
deepAssign({
|
||||
"target": kcContext,
|
||||
"source": kcContextDefaultMock !== undefined ? kcContextDefaultMock : { "pageId": mockPageId, ...kcContextCommonMock }
|
||||
target: kcContext,
|
||||
source:
|
||||
kcContextDefaultMock !== undefined
|
||||
? kcContextDefaultMock
|
||||
: { pageId: mockPageId, ...kcContextCommonMock }
|
||||
});
|
||||
|
||||
if (partialKcContextCustomMock !== undefined) {
|
||||
deepAssign({
|
||||
"target": kcContext,
|
||||
"source": partialKcContextCustomMock
|
||||
target: kcContext,
|
||||
source: partialKcContextCustomMock
|
||||
});
|
||||
|
||||
if ("profile" in partialKcContextCustomMock) {
|
||||
assert(kcContextDefaultMock !== undefined && "profile" in kcContextDefaultMock);
|
||||
assert(
|
||||
kcContextDefaultMock !== undefined &&
|
||||
"profile" in kcContextDefaultMock
|
||||
);
|
||||
|
||||
const { attributes } = kcContextDefaultMock.profile;
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes = [];
|
||||
id<KcContext.Register>(kcContext).profile.attributesByName = {};
|
||||
|
||||
const partialAttributes = [...((partialKcContextCustomMock as DeepPartial<KcContext.Register>).profile?.attributes ?? [])].filter(
|
||||
exclude(undefined)
|
||||
);
|
||||
const partialAttributes = [
|
||||
...((
|
||||
partialKcContextCustomMock as DeepPartial<KcContext.Register>
|
||||
).profile?.attributes ?? [])
|
||||
].filter(exclude(undefined));
|
||||
|
||||
attributes.forEach(attribute => {
|
||||
const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name);
|
||||
const partialAttribute = partialAttributes.find(
|
||||
({ name }) => name === attribute.name
|
||||
);
|
||||
|
||||
const augmentedAttribute: Attribute = {} as any;
|
||||
|
||||
deepAssign({
|
||||
"target": augmentedAttribute,
|
||||
"source": attribute
|
||||
target: augmentedAttribute,
|
||||
source: attribute
|
||||
});
|
||||
|
||||
if (partialAttribute !== undefined) {
|
||||
partialAttributes.splice(partialAttributes.indexOf(partialAttribute), 1);
|
||||
partialAttributes.splice(
|
||||
partialAttributes.indexOf(partialAttribute),
|
||||
1
|
||||
);
|
||||
|
||||
deepAssign({
|
||||
"target": augmentedAttribute,
|
||||
"source": partialAttribute
|
||||
target: augmentedAttribute,
|
||||
source: partialAttribute
|
||||
});
|
||||
}
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes.push(augmentedAttribute);
|
||||
id<KcContext.Register>(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute;
|
||||
id<KcContext.Register>(kcContext).profile.attributes.push(
|
||||
augmentedAttribute
|
||||
);
|
||||
id<KcContext.Register>(kcContext).profile.attributesByName[
|
||||
augmentedAttribute.name
|
||||
] = augmentedAttribute;
|
||||
});
|
||||
|
||||
partialAttributes
|
||||
.map(partialAttribute => ({ "validators": {}, ...partialAttribute }))
|
||||
.map(partialAttribute => ({
|
||||
validators: {},
|
||||
...partialAttribute
|
||||
}))
|
||||
.forEach(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.attributesByName[name] = partialAttribute as any;
|
||||
id<KcContext.Register>(kcContext).profile.attributes.push(
|
||||
partialAttribute as any
|
||||
);
|
||||
id<KcContext.Register>(kcContext).profile.attributesByName[
|
||||
name
|
||||
] = partialAttribute as any;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (mockProperties !== undefined) {
|
||||
deepAssign({
|
||||
"target": kcContext.properties,
|
||||
"source": mockProperties
|
||||
target: kcContext.properties,
|
||||
source: mockProperties
|
||||
});
|
||||
}
|
||||
|
||||
@ -145,14 +192,14 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
||||
}
|
||||
|
||||
if (realKcContext === undefined) {
|
||||
return { "kcContext": undefined as any };
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
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 };
|
||||
|
@ -5,7 +5,9 @@ import { createGetKcContext } from "./createGetKcContext";
|
||||
/** 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
|
||||
*/
|
||||
export function getKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||
export function getKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockPageId?: ExtendKcContext<KcContextExtension>["pageId"];
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
}): { kcContext: ExtendKcContext<KcContextExtension> | undefined } {
|
||||
|
@ -2,10 +2,14 @@ import type { KcContext } from "./KcContext";
|
||||
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
||||
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
|
||||
: 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];
|
||||
}
|
||||
|
@ -1,324 +1,336 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
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 { assert, type Equals } from "tsafe/assert";
|
||||
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
||||
|
||||
const attributes: Attribute[] = [
|
||||
{
|
||||
"validators": {
|
||||
"length": {
|
||||
validators: {
|
||||
length: {
|
||||
"ignore.empty.value": true,
|
||||
"min": "3",
|
||||
"max": "255"
|
||||
min: "3",
|
||||
max: "255"
|
||||
}
|
||||
},
|
||||
"displayName": "${username}",
|
||||
"annotations": {},
|
||||
"required": true,
|
||||
"autocomplete": "username",
|
||||
"readOnly": false,
|
||||
"name": "username",
|
||||
"value": "xxxx"
|
||||
displayName: "${username}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "username",
|
||||
readOnly: false,
|
||||
name: "username",
|
||||
value: "xxxx"
|
||||
},
|
||||
{
|
||||
"validators": {
|
||||
"length": {
|
||||
"max": "255",
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
},
|
||||
"email": {
|
||||
email: {
|
||||
"ignore.empty.value": true
|
||||
},
|
||||
"pattern": {
|
||||
pattern: {
|
||||
"ignore.empty.value": true,
|
||||
"pattern": "gmail\\.com$"
|
||||
pattern: "gmail\\.com$"
|
||||
}
|
||||
},
|
||||
"displayName": "${email}",
|
||||
"annotations": {},
|
||||
"required": true,
|
||||
"autocomplete": "email",
|
||||
"readOnly": false,
|
||||
"name": "email"
|
||||
displayName: "${email}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "email",
|
||||
readOnly: false,
|
||||
name: "email"
|
||||
},
|
||||
{
|
||||
"validators": {
|
||||
"length": {
|
||||
"max": "255",
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
"displayName": "${firstName}",
|
||||
"annotations": {},
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"name": "firstName"
|
||||
displayName: "${firstName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "firstName"
|
||||
},
|
||||
{
|
||||
"validators": {
|
||||
"length": {
|
||||
"max": "255",
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
"displayName": "${lastName}",
|
||||
"annotations": {},
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"name": "lastName"
|
||||
displayName: "${lastName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
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`;
|
||||
|
||||
export const kcContextCommonMock: KcContext.Common = {
|
||||
"themeVersion": "0.0.0",
|
||||
"keycloakifyVersion": "0.0.0",
|
||||
"themeType": "login",
|
||||
"themeName": "my-theme-name",
|
||||
"url": {
|
||||
"loginAction": "#",
|
||||
themeVersion: "0.0.0",
|
||||
keycloakifyVersion: "0.0.0",
|
||||
themeType: "login",
|
||||
themeName: "my-theme-name",
|
||||
url: {
|
||||
loginAction: "#",
|
||||
resourcesPath,
|
||||
"resourcesCommonPath": `${resourcesPath}/${resources_common}`,
|
||||
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?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"
|
||||
resourcesCommonPath: `${resourcesPath}/${resources_common}`,
|
||||
loginRestartFlowUrl:
|
||||
"/auth/realms/myrealm/login-actions/restart?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": {
|
||||
"name": "myrealm",
|
||||
"displayName": "myrealm",
|
||||
"displayNameHtml": "myrealm",
|
||||
"internationalizationEnabled": true,
|
||||
"registrationEmailAsUsername": false
|
||||
realm: {
|
||||
name: "myrealm",
|
||||
displayName: "myrealm",
|
||||
displayNameHtml: "myrealm",
|
||||
internationalizationEnabled: true,
|
||||
registrationEmailAsUsername: false
|
||||
},
|
||||
"messagesPerField": {
|
||||
"printIfExists": () => {
|
||||
messagesPerField: {
|
||||
printIfExists: () => {
|
||||
return undefined;
|
||||
},
|
||||
"existsError": () => false,
|
||||
"get": fieldName => `Fake error for ${fieldName}`,
|
||||
"exists": () => false,
|
||||
"getFirstError": fieldName => `Fake error for ${fieldName}`
|
||||
existsError: () => false,
|
||||
get: fieldName => `Fake error for ${fieldName}`,
|
||||
exists: () => false,
|
||||
getFirstError: fieldName => `Fake error for ${fieldName}`
|
||||
},
|
||||
"locale": {
|
||||
"supported": [
|
||||
locale: {
|
||||
supported: [
|
||||
/* 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",
|
||||
"label": "Deutsch",
|
||||
"languageTag": "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",
|
||||
languageTag: "de"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||
"label": "Norsk",
|
||||
"languageTag": "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",
|
||||
languageTag: "no"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
"label": "Русский",
|
||||
"languageTag": "ru"
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
label: "Русский",
|
||||
languageTag: "ru"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||
"label": "Svenska",
|
||||
"languageTag": "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",
|
||||
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",
|
||||
"label": "Português (Brasil)",
|
||||
"languageTag": "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)",
|
||||
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",
|
||||
"label": "Lietuvių",
|
||||
"languageTag": "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ų",
|
||||
languageTag: "lt"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||
"label": "English",
|
||||
"languageTag": "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",
|
||||
languageTag: "en"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||
"label": "Italiano",
|
||||
"languageTag": "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",
|
||||
languageTag: "it"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
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",
|
||||
"label": "中文简体",
|
||||
"languageTag": "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: "中文简体",
|
||||
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",
|
||||
"label": "Español",
|
||||
"languageTag": "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",
|
||||
languageTag: "es"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
languageTag: "cs"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
"label": "日本語",
|
||||
"languageTag": "ja"
|
||||
url: "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
label: "日本語",
|
||||
languageTag: "ja"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
languageTag: "sk"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||
"label": "Polski",
|
||||
"languageTag": "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",
|
||||
languageTag: "pl"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||
"label": "Català",
|
||||
"languageTag": "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à",
|
||||
languageTag: "ca"
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||
"label": "Nederlands",
|
||||
"languageTag": "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",
|
||||
languageTag: "nl"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"languageTag": "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",
|
||||
languageTag: "tr"
|
||||
}
|
||||
/* spell-checker: enable */
|
||||
],
|
||||
"currentLanguageTag": "en"
|
||||
currentLanguageTag: "en"
|
||||
},
|
||||
"auth": {
|
||||
"showUsername": false,
|
||||
"showResetCredentials": false,
|
||||
"showTryAnotherWayLink": false
|
||||
auth: {
|
||||
showUsername: false,
|
||||
showResetCredentials: false,
|
||||
showTryAnotherWayLink: false
|
||||
},
|
||||
"client": {
|
||||
"clientId": "myApp",
|
||||
"attributes": {}
|
||||
client: {
|
||||
clientId: "myApp",
|
||||
attributes: {}
|
||||
},
|
||||
"scripts": [],
|
||||
"isAppInitiatedAction": false,
|
||||
"properties": {}
|
||||
scripts: [],
|
||||
isAppInitiatedAction: false,
|
||||
properties: {}
|
||||
};
|
||||
|
||||
const loginUrl = {
|
||||
...kcContextCommonMock.url,
|
||||
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
||||
"registrationUrl": "/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"
|
||||
loginResetCredentialsUrl:
|
||||
"/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
||||
registrationUrl:
|
||||
"/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 = [
|
||||
id<KcContext.Login>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login.ftl",
|
||||
"url": loginUrl,
|
||||
"realm": {
|
||||
pageId: "login.ftl",
|
||||
url: loginUrl,
|
||||
realm: {
|
||||
...kcContextCommonMock.realm,
|
||||
"loginWithEmailAllowed": true,
|
||||
"rememberMe": true,
|
||||
"password": true,
|
||||
"resetPasswordAllowed": true,
|
||||
"registrationAllowed": true
|
||||
loginWithEmailAllowed: true,
|
||||
rememberMe: true,
|
||||
password: true,
|
||||
resetPasswordAllowed: true,
|
||||
registrationAllowed: true
|
||||
},
|
||||
"auth": kcContextCommonMock.auth!,
|
||||
"social": {
|
||||
"displayInfo": true
|
||||
auth: kcContextCommonMock.auth!,
|
||||
social: {
|
||||
displayInfo: true
|
||||
},
|
||||
"usernameHidden": false,
|
||||
"login": {},
|
||||
"registrationDisabled": false
|
||||
usernameHidden: false,
|
||||
login: {},
|
||||
registrationDisabled: false
|
||||
}),
|
||||
id<KcContext.Register>({
|
||||
...kcContextCommonMock,
|
||||
"url": {
|
||||
url: {
|
||||
...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"
|
||||
},
|
||||
"isAppInitiatedAction": false,
|
||||
"passwordRequired": true,
|
||||
"recaptchaRequired": false,
|
||||
"pageId": "register.ftl",
|
||||
"profile": {
|
||||
isAppInitiatedAction: false,
|
||||
passwordRequired: true,
|
||||
recaptchaRequired: false,
|
||||
pageId: "register.ftl",
|
||||
profile: {
|
||||
attributes,
|
||||
attributesByName
|
||||
},
|
||||
"scripts": [
|
||||
scripts: [
|
||||
//"https://www.google.com/recaptcha/api.js"
|
||||
]
|
||||
}),
|
||||
id<KcContext.Info>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "info.ftl",
|
||||
"messageHeader": "<Message header>",
|
||||
"requiredActions": undefined,
|
||||
"skipLink": false,
|
||||
"actionUri": "#",
|
||||
"client": {
|
||||
"clientId": "myApp",
|
||||
"baseUrl": "#",
|
||||
"attributes": {}
|
||||
pageId: "info.ftl",
|
||||
messageHeader: "<Message header>",
|
||||
requiredActions: undefined,
|
||||
skipLink: false,
|
||||
actionUri: "#",
|
||||
client: {
|
||||
clientId: "myApp",
|
||||
baseUrl: "#",
|
||||
attributes: {}
|
||||
}
|
||||
}),
|
||||
id<KcContext.Error>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "error.ftl",
|
||||
"client": {
|
||||
"clientId": "myApp",
|
||||
"baseUrl": "#",
|
||||
"attributes": {}
|
||||
pageId: "error.ftl",
|
||||
client: {
|
||||
clientId: "myApp",
|
||||
baseUrl: "#",
|
||||
attributes: {}
|
||||
},
|
||||
"message": {
|
||||
"type": "error",
|
||||
"summary": "This is the error message"
|
||||
message: {
|
||||
type: "error",
|
||||
summary: "This is the error message"
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginResetPassword>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-reset-password.ftl",
|
||||
"realm": {
|
||||
pageId: "login-reset-password.ftl",
|
||||
realm: {
|
||||
...kcContextCommonMock.realm,
|
||||
"loginWithEmailAllowed": false,
|
||||
"duplicateEmailsAllowed": false
|
||||
loginWithEmailAllowed: false,
|
||||
duplicateEmailsAllowed: false
|
||||
},
|
||||
"url": loginUrl,
|
||||
"auth": {}
|
||||
url: loginUrl,
|
||||
auth: {}
|
||||
}),
|
||||
id<KcContext.LoginVerifyEmail>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-verify-email.ftl",
|
||||
"user": {
|
||||
"email": "john.doe@gmail.com"
|
||||
pageId: "login-verify-email.ftl",
|
||||
user: {
|
||||
email: "john.doe@gmail.com"
|
||||
}
|
||||
}),
|
||||
id<KcContext.Terms>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "terms.ftl"
|
||||
pageId: "terms.ftl"
|
||||
}),
|
||||
id<KcContext.LoginDeviceVerifyUserCode>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-oauth2-device-verify-user-code.ftl",
|
||||
pageId: "login-oauth2-device-verify-user-code.ftl",
|
||||
url: loginUrl
|
||||
}),
|
||||
id<KcContext.LoginOauthGrant>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-oauth-grant.ftl",
|
||||
pageId: "login-oauth-grant.ftl",
|
||||
oauth: {
|
||||
code: "5-1N4CIzfi1aprIQjmylI-9e3spLCWW9i5d-GDcs-Sw",
|
||||
clientScopesRequested: [
|
||||
@ -332,150 +344,152 @@ export const kcContextMocks = [
|
||||
}),
|
||||
id<KcContext.LoginOtp>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-otp.ftl",
|
||||
"otpLogin": {
|
||||
"userOtpCredentials": [
|
||||
pageId: "login-otp.ftl",
|
||||
otpLogin: {
|
||||
userOtpCredentials: [
|
||||
{
|
||||
"id": "id1",
|
||||
"userLabel": "label1"
|
||||
id: "id1",
|
||||
userLabel: "label1"
|
||||
},
|
||||
{
|
||||
"id": "id2",
|
||||
"userLabel": "label2"
|
||||
id: "id2",
|
||||
userLabel: "label2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginUsername>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-username.ftl",
|
||||
"url": loginUrl,
|
||||
"realm": {
|
||||
pageId: "login-username.ftl",
|
||||
url: loginUrl,
|
||||
realm: {
|
||||
...kcContextCommonMock.realm,
|
||||
"loginWithEmailAllowed": true,
|
||||
"rememberMe": true,
|
||||
"password": true,
|
||||
"resetPasswordAllowed": true,
|
||||
"registrationAllowed": true
|
||||
loginWithEmailAllowed: true,
|
||||
rememberMe: true,
|
||||
password: true,
|
||||
resetPasswordAllowed: true,
|
||||
registrationAllowed: true
|
||||
},
|
||||
"social": {
|
||||
"displayInfo": true
|
||||
social: {
|
||||
displayInfo: true
|
||||
},
|
||||
"usernameHidden": false,
|
||||
"login": {},
|
||||
"registrationDisabled": false
|
||||
usernameHidden: false,
|
||||
login: {},
|
||||
registrationDisabled: false
|
||||
}),
|
||||
id<KcContext.LoginPassword>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-password.ftl",
|
||||
"url": loginUrl,
|
||||
"realm": {
|
||||
pageId: "login-password.ftl",
|
||||
url: loginUrl,
|
||||
realm: {
|
||||
...kcContextCommonMock.realm,
|
||||
"resetPasswordAllowed": true
|
||||
resetPasswordAllowed: true
|
||||
},
|
||||
"social": {
|
||||
"displayInfo": false
|
||||
social: {
|
||||
displayInfo: false
|
||||
}
|
||||
}),
|
||||
id<KcContext.WebauthnAuthenticate>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "webauthn-authenticate.ftl",
|
||||
"url": loginUrl,
|
||||
"authenticators": {
|
||||
"authenticators": []
|
||||
pageId: "webauthn-authenticate.ftl",
|
||||
url: loginUrl,
|
||||
authenticators: {
|
||||
authenticators: []
|
||||
},
|
||||
"realm": {
|
||||
realm: {
|
||||
...kcContextCommonMock.realm,
|
||||
"password": true,
|
||||
"registrationAllowed": true
|
||||
password: true,
|
||||
registrationAllowed: true
|
||||
},
|
||||
"challenge": "",
|
||||
"userVerification": "not specified",
|
||||
"rpId": "",
|
||||
"createTimeout": "0",
|
||||
"isUserIdentified": "false",
|
||||
"shouldDisplayAuthenticators": false,
|
||||
"social": {
|
||||
"displayInfo": false
|
||||
challenge: "",
|
||||
userVerification: "not specified",
|
||||
rpId: "",
|
||||
createTimeout: "0",
|
||||
isUserIdentified: "false",
|
||||
shouldDisplayAuthenticators: false,
|
||||
social: {
|
||||
displayInfo: false
|
||||
},
|
||||
"login": {}
|
||||
login: {}
|
||||
}),
|
||||
id<KcContext.LoginUpdatePassword>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-update-password.ftl"
|
||||
pageId: "login-update-password.ftl"
|
||||
}),
|
||||
id<KcContext.LoginUpdateProfile>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-update-profile.ftl",
|
||||
"profile": {
|
||||
pageId: "login-update-profile.ftl",
|
||||
profile: {
|
||||
attributes,
|
||||
attributesByName
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginIdpLinkConfirm>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-idp-link-confirm.ftl",
|
||||
"idpAlias": "FranceConnect"
|
||||
pageId: "login-idp-link-confirm.ftl",
|
||||
idpAlias: "FranceConnect"
|
||||
}),
|
||||
id<KcContext.LoginIdpLinkEmail>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-idp-link-email.ftl",
|
||||
"idpAlias": "FranceConnect",
|
||||
"brokerContext": {
|
||||
"username": "anUsername"
|
||||
pageId: "login-idp-link-email.ftl",
|
||||
idpAlias: "FranceConnect",
|
||||
brokerContext: {
|
||||
username: "anUsername"
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginConfigTotp>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-config-totp.ftl",
|
||||
"totp": {
|
||||
"totpSecretEncoded": "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
|
||||
"qrUrl": "#",
|
||||
"totpSecretQrCode":
|
||||
pageId: "login-config-totp.ftl",
|
||||
totp: {
|
||||
totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
|
||||
qrUrl: "#",
|
||||
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=",
|
||||
"manualUrl": "#",
|
||||
"totpSecret": "G4nsI8lQagRMUchH8jEG",
|
||||
"otpCredentials": [],
|
||||
"supportedApplications": ["FreeOTP", "Google Authenticator"],
|
||||
"policy": {
|
||||
"algorithm": "HmacSHA1",
|
||||
"digits": 6,
|
||||
"lookAheadWindow": 1,
|
||||
"type": "totp",
|
||||
"period": 30,
|
||||
"getAlgorithmKey": () => "SHA1"
|
||||
manualUrl: "#",
|
||||
totpSecret: "G4nsI8lQagRMUchH8jEG",
|
||||
otpCredentials: [],
|
||||
supportedApplications: ["FreeOTP", "Google Authenticator"],
|
||||
policy: {
|
||||
algorithm: "HmacSHA1",
|
||||
digits: 6,
|
||||
lookAheadWindow: 1,
|
||||
type: "totp",
|
||||
period: 30,
|
||||
getAlgorithmKey: () => "SHA1"
|
||||
}
|
||||
}
|
||||
}),
|
||||
id<KcContext.LogoutConfirm>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "logout-confirm.ftl",
|
||||
"url": {
|
||||
pageId: "logout-confirm.ftl",
|
||||
url: {
|
||||
...kcContextCommonMock.url,
|
||||
"logoutConfirmAction": "Continuer?"
|
||||
logoutConfirmAction: "Continuer?"
|
||||
},
|
||||
"client": {
|
||||
"clientId": "myApp",
|
||||
"baseUrl": "#",
|
||||
"attributes": {}
|
||||
client: {
|
||||
clientId: "myApp",
|
||||
baseUrl: "#",
|
||||
attributes: {}
|
||||
},
|
||||
"logoutConfirm": { "code": "123", skipLink: false }
|
||||
logoutConfirm: { code: "123", skipLink: false }
|
||||
}),
|
||||
id<KcContext.IdpReviewUserProfile>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "idp-review-user-profile.ftl",
|
||||
"profile": {
|
||||
pageId: "idp-review-user-profile.ftl",
|
||||
profile: {
|
||||
attributes,
|
||||
attributesByName
|
||||
}
|
||||
}),
|
||||
id<KcContext.UpdateEmail>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "update-email.ftl",
|
||||
"profile": {
|
||||
"attributes": attributes.filter(attribute => attribute.name === "email"),
|
||||
"attributesByName": Object.fromEntries(
|
||||
attributes.filter(attribute => attribute.name === "email").map(attribute => [attribute.name, attribute])
|
||||
pageId: "update-email.ftl",
|
||||
profile: {
|
||||
attributes: attributes.filter(attribute => attribute.name === "email"),
|
||||
attributesByName: Object.fromEntries(
|
||||
attributes
|
||||
.filter(attribute => attribute.name === "email")
|
||||
.map(attribute => [attribute.name, attribute])
|
||||
)
|
||||
}
|
||||
}),
|
||||
@ -502,8 +516,8 @@ export const kcContextMocks = [
|
||||
id<KcContext.SamlPostForm>({
|
||||
...kcContextCommonMock,
|
||||
pageId: "saml-post-form.ftl",
|
||||
"samlPost": {
|
||||
"url": ""
|
||||
samlPost: {
|
||||
url: ""
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginPageExpired>({
|
||||
@ -514,107 +528,107 @@ export const kcContextMocks = [
|
||||
id<KcContext.FrontchannelLogout>({
|
||||
...kcContextCommonMock,
|
||||
pageId: "frontchannel-logout.ftl",
|
||||
"logout": {
|
||||
"clients": [
|
||||
logout: {
|
||||
clients: [
|
||||
{
|
||||
"name": "myApp",
|
||||
"frontChannelLogoutUrl": "#"
|
||||
name: "myApp",
|
||||
frontChannelLogoutUrl: "#"
|
||||
},
|
||||
{
|
||||
"name": "myApp2",
|
||||
"frontChannelLogoutUrl": "#"
|
||||
name: "myApp2",
|
||||
frontChannelLogoutUrl: "#"
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
id<KcContext.WebauthnRegister>({
|
||||
"pageId": "webauthn-register.ftl",
|
||||
pageId: "webauthn-register.ftl",
|
||||
...kcContextCommonMock,
|
||||
"challenge": "random-challenge-string",
|
||||
"userid": "user123",
|
||||
"username": "johndoe",
|
||||
"signatureAlgorithms": ["ES256", "RS256"],
|
||||
"rpEntityName": "Example Corp",
|
||||
"rpId": "example.com",
|
||||
"attestationConveyancePreference": "direct",
|
||||
"authenticatorAttachment": "platform",
|
||||
"requireResidentKey": "required",
|
||||
"userVerificationRequirement": "preferred",
|
||||
"createTimeout": 60000,
|
||||
"excludeCredentialIds": "credId123,credId456",
|
||||
"isSetRetry": false,
|
||||
"isAppInitiatedAction": true
|
||||
challenge: "random-challenge-string",
|
||||
userid: "user123",
|
||||
username: "johndoe",
|
||||
signatureAlgorithms: ["ES256", "RS256"],
|
||||
rpEntityName: "Example Corp",
|
||||
rpId: "example.com",
|
||||
attestationConveyancePreference: "direct",
|
||||
authenticatorAttachment: "platform",
|
||||
requireResidentKey: "required",
|
||||
userVerificationRequirement: "preferred",
|
||||
createTimeout: 60000,
|
||||
excludeCredentialIds: "credId123,credId456",
|
||||
isSetRetry: false,
|
||||
isAppInitiatedAction: true
|
||||
}),
|
||||
id<KcContext.DeleteCredential>({
|
||||
"pageId": "delete-credential.ftl",
|
||||
pageId: "delete-credential.ftl",
|
||||
...kcContextCommonMock,
|
||||
"credentialLabel": "myCredential"
|
||||
credentialLabel: "myCredential"
|
||||
}),
|
||||
id<KcContext.Code>({
|
||||
"pageId": "code.ftl",
|
||||
pageId: "code.ftl",
|
||||
...kcContextCommonMock,
|
||||
"code": {
|
||||
"success": true,
|
||||
"code": "123456"
|
||||
code: {
|
||||
success: true,
|
||||
code: "123456"
|
||||
}
|
||||
}),
|
||||
id<KcContext.DeleteAccountConfirm>({
|
||||
"pageId": "delete-account-confirm.ftl",
|
||||
pageId: "delete-account-confirm.ftl",
|
||||
...kcContextCommonMock,
|
||||
"triggered_from_aia": true
|
||||
triggered_from_aia: true
|
||||
}),
|
||||
id<KcContext.LoginRecoveryAuthnCodeConfig>({
|
||||
"pageId": "login-recovery-authn-code-config.ftl",
|
||||
pageId: "login-recovery-authn-code-config.ftl",
|
||||
...kcContextCommonMock,
|
||||
"recoveryAuthnCodesConfigBean": {
|
||||
"generatedRecoveryAuthnCodesList": ["code123", "code456", "code789"],
|
||||
"generatedRecoveryAuthnCodesAsString": "code123, code456, code789",
|
||||
"generatedAt": new Date().getTime()
|
||||
recoveryAuthnCodesConfigBean: {
|
||||
generatedRecoveryAuthnCodesList: ["code123", "code456", "code789"],
|
||||
generatedRecoveryAuthnCodesAsString: "code123, code456, code789",
|
||||
generatedAt: new Date().getTime()
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginRecoveryAuthnCodeInput>({
|
||||
"pageId": "login-recovery-authn-code-input.ftl",
|
||||
pageId: "login-recovery-authn-code-input.ftl",
|
||||
...kcContextCommonMock,
|
||||
"recoveryAuthnCodesInputBean": {
|
||||
"codeNumber": 1234
|
||||
recoveryAuthnCodesInputBean: {
|
||||
codeNumber: 1234
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginResetOtp>({
|
||||
"pageId": "login-reset-otp.ftl",
|
||||
pageId: "login-reset-otp.ftl",
|
||||
...kcContextCommonMock,
|
||||
"configuredOtpCredentials": {
|
||||
"userOtpCredentials": [
|
||||
configuredOtpCredentials: {
|
||||
userOtpCredentials: [
|
||||
{
|
||||
"id": "otpId1",
|
||||
"userLabel": "OTP Device 1"
|
||||
id: "otpId1",
|
||||
userLabel: "OTP Device 1"
|
||||
},
|
||||
{
|
||||
"id": "otpId2",
|
||||
"userLabel": "OTP Device 2"
|
||||
id: "otpId2",
|
||||
userLabel: "OTP Device 2"
|
||||
},
|
||||
{
|
||||
"id": "otpId3",
|
||||
"userLabel": "Backup OTP"
|
||||
id: "otpId3",
|
||||
userLabel: "Backup OTP"
|
||||
}
|
||||
],
|
||||
"selectedCredentialId": "otpId2"
|
||||
selectedCredentialId: "otpId2"
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginX509Info>({
|
||||
"pageId": "login-x509-info.ftl",
|
||||
pageId: "login-x509-info.ftl",
|
||||
...kcContextCommonMock,
|
||||
"x509": {
|
||||
"formData": {
|
||||
"subjectDN": "CN=John Doe, O=Example Corp, C=US",
|
||||
"isUserEnabled": true,
|
||||
"username": "johndoe"
|
||||
x509: {
|
||||
formData: {
|
||||
subjectDN: "CN=John Doe, O=Example Corp, C=US",
|
||||
isUserEnabled: true,
|
||||
username: "johndoe"
|
||||
}
|
||||
}
|
||||
}),
|
||||
id<KcContext.WebauthnError>({
|
||||
"pageId": "webauthn-error.ftl",
|
||||
pageId: "webauthn-error.ftl",
|
||||
...kcContextCommonMock,
|
||||
"isAppInitiatedAction": true
|
||||
isAppInitiatedAction: true
|
||||
})
|
||||
];
|
||||
|
||||
|
@ -33,7 +33,9 @@ export function useDownloadTerms(params: {
|
||||
const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown);
|
||||
|
||||
const downloadTermMarkdownMemoized = useConst(() =>
|
||||
memoize((currentLanguageTag: string) => downloadTermMarkdownConst({ currentLanguageTag }))
|
||||
memoize((currentLanguageTag: string) =>
|
||||
downloadTermMarkdownConst({ currentLanguageTag })
|
||||
)
|
||||
);
|
||||
|
||||
return { downloadTermMarkdownMemoized };
|
||||
@ -41,9 +43,9 @@ export function useDownloadTerms(params: {
|
||||
|
||||
useEffect(() => {
|
||||
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
||||
downloadTermMarkdownMemoized(kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag).then(
|
||||
thermMarkdown => (evtTermsMarkdown.state = thermMarkdown)
|
||||
);
|
||||
downloadTermMarkdownMemoized(
|
||||
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
|
||||
).then(thermMarkdown => (evtTermsMarkdown.state = thermMarkdown));
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
@ -2,131 +2,138 @@ import { createUseClassName } from "keycloakify/lib/useGetClassName";
|
||||
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
||||
|
||||
export const { useGetClassName } = createUseClassName<ClassKey>({
|
||||
"defaultClasses": {
|
||||
"kcBodyClass": undefined,
|
||||
"kcHeaderWrapperClass": undefined,
|
||||
"kcLocaleWrapperClass": undefined,
|
||||
"kcInfoAreaWrapperClass": undefined,
|
||||
"kcFormButtonsWrapperClass": undefined,
|
||||
"kcFormOptionsWrapperClass": undefined,
|
||||
"kcLocaleDropDownClass": undefined,
|
||||
"kcLocaleListItemClass": undefined,
|
||||
"kcContentWrapperClass": undefined,
|
||||
"kcCheckboxInputClass": undefined,
|
||||
defaultClasses: {
|
||||
kcBodyClass: undefined,
|
||||
kcHeaderWrapperClass: undefined,
|
||||
kcLocaleWrapperClass: undefined,
|
||||
kcInfoAreaWrapperClass: undefined,
|
||||
kcFormButtonsWrapperClass: undefined,
|
||||
kcFormOptionsWrapperClass: undefined,
|
||||
kcLocaleDropDownClass: undefined,
|
||||
kcLocaleListItemClass: undefined,
|
||||
kcContentWrapperClass: undefined,
|
||||
kcCheckboxInputClass: undefined,
|
||||
|
||||
"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",
|
||||
"kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg",
|
||||
"kcWebAuthnDefaultIcon": "pficon pficon-key",
|
||||
kcAuthenticatorWebAuthnClass: "fa fa-key list-view-pf-icon-lg",
|
||||
kcWebAuthnDefaultIcon: "pficon pficon-key",
|
||||
"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",
|
||||
"kcLoginOTPListItemHeaderClass": "pf-c-tile__header",
|
||||
"kcLocaleItemClass": "pf-c-dropdown__menu-item",
|
||||
"kcLoginOTPListItemIconBodyClass": "pf-c-tile__icon",
|
||||
"kcInputHelperTextAfterClass": "pf-c-form__helper-text pf-c-form__helper-text-after",
|
||||
"kcFormClass": "form-horizontal",
|
||||
"kcSelectAuthListClass": "pf-l-stack select-auth-container",
|
||||
"kcInputClassRadioCheckboxLabelDisabled": "pf-m-disabled",
|
||||
"kcSelectAuthListItemIconClass": "pf-l-split__item select-auth-box-icon",
|
||||
"kcRecoveryCodesWarning": "kc-recovery-codes-warning",
|
||||
"kcFormSettingClass": "login-pf-settings",
|
||||
"kcWebAuthnBLE": "fa fa-bluetooth-b",
|
||||
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
"kcSelectAuthListItemArrowIconClass": "fa fa-angle-right fa-lg",
|
||||
"kcFeedbackAreaClass": "col-md-12",
|
||||
"kcFormPasswordVisibilityButtonClass": "pf-c-button pf-m-control",
|
||||
kcLoginOTPListItemHeaderClass: "pf-c-tile__header",
|
||||
kcLocaleItemClass: "pf-c-dropdown__menu-item",
|
||||
kcLoginOTPListItemIconBodyClass: "pf-c-tile__icon",
|
||||
kcInputHelperTextAfterClass:
|
||||
"pf-c-form__helper-text pf-c-form__helper-text-after",
|
||||
kcFormClass: "form-horizontal",
|
||||
kcSelectAuthListClass: "pf-l-stack select-auth-container",
|
||||
kcInputClassRadioCheckboxLabelDisabled: "pf-m-disabled",
|
||||
kcSelectAuthListItemIconClass: "pf-l-split__item select-auth-box-icon",
|
||||
kcRecoveryCodesWarning: "kc-recovery-codes-warning",
|
||||
kcFormSettingClass: "login-pf-settings",
|
||||
kcWebAuthnBLE: "fa fa-bluetooth-b",
|
||||
kcInputWrapperClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
kcSelectAuthListItemArrowIconClass: "fa fa-angle-right fa-lg",
|
||||
kcFeedbackAreaClass: "col-md-12",
|
||||
kcFormPasswordVisibilityButtonClass: "pf-c-button pf-m-control",
|
||||
"kcLogoIdP-google": "fa fa-google",
|
||||
"kcCheckLabelClass": "pf-c-check__label",
|
||||
"kcSelectAuthListItemFillClass": "pf-l-split__item pf-m-fill",
|
||||
"kcAuthenticatorDefaultClass": "fa fa-list list-view-pf-icon-lg",
|
||||
kcCheckLabelClass: "pf-c-check__label",
|
||||
kcSelectAuthListItemFillClass: "pf-l-split__item pf-m-fill",
|
||||
kcAuthenticatorDefaultClass: "fa fa-list list-view-pf-icon-lg",
|
||||
"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",
|
||||
"kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
"kcInputClassRadioLabel": "pf-c-radio__label",
|
||||
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg",
|
||||
"kcSelectAuthListItemHeadingClass": "pf-l-stack__item select-auth-box-headline pf-c-title",
|
||||
"kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details",
|
||||
"kcLogoLink": "http://www.keycloak.org",
|
||||
"kcContainerClass": "container-fluid",
|
||||
"kcSelectAuthListItemTitle": "select-auth-box-paragraph",
|
||||
"kcHtmlClass": "login-pf",
|
||||
"kcLoginOTPListItemTitleClass": "pf-c-tile__title",
|
||||
kcFormAreaClass:
|
||||
"col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2",
|
||||
kcFormButtonsClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
kcInputClassRadioLabel: "pf-c-radio__label",
|
||||
kcAuthenticatorWebAuthnPasswordlessClass: "fa fa-key list-view-pf-icon-lg",
|
||||
kcSelectAuthListItemHeadingClass:
|
||||
"pf-l-stack__item select-auth-box-headline pf-c-title",
|
||||
kcInfoAreaClass: "col-xs-12 col-sm-4 col-md-4 col-lg-5 details",
|
||||
kcLogoLink: "http://www.keycloak.org",
|
||||
kcContainerClass: "container-fluid",
|
||||
kcSelectAuthListItemTitle: "select-auth-box-paragraph",
|
||||
kcHtmlClass: "login-pf",
|
||||
kcLoginOTPListItemTitleClass: "pf-c-tile__title",
|
||||
"kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift",
|
||||
"kcWebAuthnUnknownIcon": "pficon pficon-key unknown-transport-class",
|
||||
"kcFormSocialAccountNameClass": "kc-social-provider-name",
|
||||
kcWebAuthnUnknownIcon: "pficon pficon-key unknown-transport-class",
|
||||
kcFormSocialAccountNameClass: "kc-social-provider-name",
|
||||
"kcLogoIdP-openshift-v3": "pf-icon pf-icon-openshift",
|
||||
"kcLoginOTPListInputClass": "pf-c-tile__input",
|
||||
"kcWebAuthnUSB": "fa fa-usb",
|
||||
"kcInputClassRadio": "pf-c-radio",
|
||||
"kcWebAuthnKeyIcon": "pficon pficon-key",
|
||||
"kcFeedbackInfoIcon": "fa fa-fw fa-info-circle",
|
||||
"kcCommonLogoIdP": "kc-social-provider-logo kc-social-gray",
|
||||
"kcRecoveryCodesActions": "kc-recovery-codes-actions",
|
||||
"kcFormGroupHeader": "pf-c-form__group",
|
||||
"kcFormSocialAccountSectionClass": "kc-social-section kc-social-gray",
|
||||
kcLoginOTPListInputClass: "pf-c-tile__input",
|
||||
kcWebAuthnUSB: "fa fa-usb",
|
||||
kcInputClassRadio: "pf-c-radio",
|
||||
kcWebAuthnKeyIcon: "pficon pficon-key",
|
||||
kcFeedbackInfoIcon: "fa fa-fw fa-info-circle",
|
||||
kcCommonLogoIdP: "kc-social-provider-logo kc-social-gray",
|
||||
kcRecoveryCodesActions: "kc-recovery-codes-actions",
|
||||
kcFormGroupHeader: "pf-c-form__group",
|
||||
kcFormSocialAccountSectionClass: "kc-social-section kc-social-gray",
|
||||
"kcLogoIdP-instagram": "fa fa-instagram",
|
||||
"kcAlertClass": "pf-c-alert pf-m-inline",
|
||||
"kcHeaderClass": "login-pf-page-header",
|
||||
"kcLabelWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
"kcFormPasswordVisibilityIconShow": "fa fa-eye",
|
||||
"kcFormSocialAccountLinkClass": "pf-c-login__main-footer-links-item-link",
|
||||
"kcLocaleMainClass": "pf-c-dropdown",
|
||||
"kcInputGroup": "pf-c-input-group",
|
||||
"kcTextareaClass": "form-control",
|
||||
"kcButtonBlockClass": "pf-m-block",
|
||||
"kcButtonClass": "pf-c-button",
|
||||
"kcWebAuthnNFC": "fa fa-wifi",
|
||||
"kcLocaleClass": "col-xs-12 col-sm-1",
|
||||
"kcInputClassCheckboxInput": "pf-c-check__input",
|
||||
"kcFeedbackErrorIcon": "fa fa-fw fa-exclamation-circle",
|
||||
"kcInputLargeClass": "input-lg",
|
||||
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text",
|
||||
"kcRecoveryCodesList": "kc-recovery-codes-list",
|
||||
"kcFormSocialAccountListClass": "pf-c-login__main-footer-links kc-social-links",
|
||||
"kcAlertTitleClass": "pf-c-alert__title kc-feedback-text",
|
||||
"kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg",
|
||||
"kcCheckInputClass": "pf-c-check__input",
|
||||
kcAlertClass: "pf-c-alert pf-m-inline",
|
||||
kcHeaderClass: "login-pf-page-header",
|
||||
kcLabelWrapperClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
kcFormPasswordVisibilityIconShow: "fa fa-eye",
|
||||
kcFormSocialAccountLinkClass: "pf-c-login__main-footer-links-item-link",
|
||||
kcLocaleMainClass: "pf-c-dropdown",
|
||||
kcInputGroup: "pf-c-input-group",
|
||||
kcTextareaClass: "form-control",
|
||||
kcButtonBlockClass: "pf-m-block",
|
||||
kcButtonClass: "pf-c-button",
|
||||
kcWebAuthnNFC: "fa fa-wifi",
|
||||
kcLocaleClass: "col-xs-12 col-sm-1",
|
||||
kcInputClassCheckboxInput: "pf-c-check__input",
|
||||
kcFeedbackErrorIcon: "fa fa-fw fa-exclamation-circle",
|
||||
kcInputLargeClass: "input-lg",
|
||||
kcInputErrorMessageClass:
|
||||
"pf-c-form__helper-text pf-m-error required kc-feedback-text",
|
||||
kcRecoveryCodesList: "kc-recovery-codes-list",
|
||||
kcFormSocialAccountListClass: "pf-c-login__main-footer-links kc-social-links",
|
||||
kcAlertTitleClass: "pf-c-alert__title kc-feedback-text",
|
||||
kcAuthenticatorPasswordClass: "fa fa-unlock list-view-pf-icon-lg",
|
||||
kcCheckInputClass: "pf-c-check__input",
|
||||
"kcLogoIdP-linkedin": "fa fa-linkedin",
|
||||
"kcLogoIdP-twitter": "fa fa-twitter",
|
||||
"kcFeedbackWarningIcon": "fa fa-fw fa-exclamation-triangle",
|
||||
"kcResetFlowIcon": "pficon pficon-arrow fa",
|
||||
"kcSelectAuthListItemIconPropertyClass": "fa-2x select-auth-box-icon-properties",
|
||||
"kcFeedbackSuccessIcon": "fa fa-fw fa-check-circle",
|
||||
"kcLoginOTPListClass": "pf-c-tile",
|
||||
"kcSrOnlyClass": "sr-only",
|
||||
"kcFormSocialAccountListGridClass": "pf-l-grid kc-social-grid",
|
||||
"kcButtonDefaultClass": "btn-default",
|
||||
"kcFormGroupErrorClass": "has-error",
|
||||
"kcSelectAuthListItemDescriptionClass": "pf-l-stack__item select-auth-box-desc",
|
||||
"kcSelectAuthListItemBodyClass": "pf-l-split__item pf-l-stack",
|
||||
"kcWebAuthnInternal": "pficon pficon-key",
|
||||
"kcSelectAuthListItemArrowClass": "pf-l-split__item select-auth-box-arrow",
|
||||
"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",
|
||||
"kcLogoClass": "login-pf-brand",
|
||||
"kcLoginOTPListItemIconClass": "fa fa-mobile",
|
||||
"kcLoginClass": "login-pf-page",
|
||||
"kcSignUpClass": "login-pf-signup",
|
||||
"kcButtonLargeClass": "btn-lg",
|
||||
"kcFormCardClass": "card-pf",
|
||||
"kcLocaleListClass": "pf-c-dropdown__menu pf-m-align-right",
|
||||
"kcInputClass": "pf-c-form-control",
|
||||
"kcFormGroupClass": "form-group",
|
||||
kcFeedbackWarningIcon: "fa fa-fw fa-exclamation-triangle",
|
||||
kcResetFlowIcon: "pficon pficon-arrow fa",
|
||||
kcSelectAuthListItemIconPropertyClass: "fa-2x select-auth-box-icon-properties",
|
||||
kcFeedbackSuccessIcon: "fa fa-fw fa-check-circle",
|
||||
kcLoginOTPListClass: "pf-c-tile",
|
||||
kcSrOnlyClass: "sr-only",
|
||||
kcFormSocialAccountListGridClass: "pf-l-grid kc-social-grid",
|
||||
kcButtonDefaultClass: "btn-default",
|
||||
kcFormGroupErrorClass: "has-error",
|
||||
kcSelectAuthListItemDescriptionClass: "pf-l-stack__item select-auth-box-desc",
|
||||
kcSelectAuthListItemBodyClass: "pf-l-split__item pf-l-stack",
|
||||
kcWebAuthnInternal: "pficon pficon-key",
|
||||
kcSelectAuthListItemArrowClass: "pf-l-split__item select-auth-box-arrow",
|
||||
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",
|
||||
kcLogoClass: "login-pf-brand",
|
||||
kcLoginOTPListItemIconClass: "fa fa-mobile",
|
||||
kcLoginClass: "login-pf-page",
|
||||
kcSignUpClass: "login-pf-signup",
|
||||
kcButtonLargeClass: "btn-lg",
|
||||
kcFormCardClass: "card-pf",
|
||||
kcLocaleListClass: "pf-c-dropdown__menu pf-m-align-right",
|
||||
kcInputClass: "pf-c-form-control",
|
||||
kcFormGroupClass: "form-group",
|
||||
"kcLogoIdP-paypal": "fa fa-paypal",
|
||||
"kcInputClassCheckbox": "pf-c-check",
|
||||
"kcRecoveryCodesConfirmation": "kc-recovery-codes-confirmation",
|
||||
"kcFormPasswordVisibilityIconHide": "fa fa-eye-slash",
|
||||
"kcInputClassRadioInput": "pf-c-radio__input",
|
||||
"kcFormSocialAccountListButtonClass": "pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray",
|
||||
"kcInputClassCheckboxLabel": "pf-c-check__label",
|
||||
"kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
"kcFormHeaderClass": "login-pf-header",
|
||||
"kcFormSocialAccountGridItem": "pf-l-grid__item",
|
||||
"kcButtonPrimaryClass": "pf-m-primary",
|
||||
"kcInputHelperTextBeforeClass": "pf-c-form__helper-text pf-c-form__helper-text-before",
|
||||
kcInputClassCheckbox: "pf-c-check",
|
||||
kcRecoveryCodesConfirmation: "kc-recovery-codes-confirmation",
|
||||
kcFormPasswordVisibilityIconHide: "fa fa-eye-slash",
|
||||
kcInputClassRadioInput: "pf-c-radio__input",
|
||||
kcFormSocialAccountListButtonClass:
|
||||
"pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray",
|
||||
kcInputClassCheckboxLabel: "pf-c-check__label",
|
||||
kcFormOptionsClass: "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||
kcFormHeaderClass: "login-pf-header",
|
||||
kcFormSocialAccountGridItem: "pf-l-grid__item",
|
||||
kcButtonPrimaryClass: "pf-m-primary",
|
||||
kcInputHelperTextBeforeClass:
|
||||
"pf-c-form__helper-text pf-c-form__helper-text-before",
|
||||
"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"
|
||||
}
|
||||
});
|
||||
|
@ -108,11 +108,11 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
|
||||
|
||||
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.
|
||||
.map(key => ({
|
||||
"type": "module",
|
||||
"src": `${kcContext.url.resourcesPath}/js/${key}.js`
|
||||
type: "module",
|
||||
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))
|
||||
.map(name =>
|
||||
id<Attribute>({
|
||||
"name": name,
|
||||
"displayName": id<`\${${MessageKey}}`>(`\${${name}}`),
|
||||
"required": true,
|
||||
"value": (kcContext.register as any).formData[name] ?? "",
|
||||
"html5DataAnnotations": {},
|
||||
"readOnly": false,
|
||||
"validators": {},
|
||||
"annotations": {},
|
||||
"autocomplete": (() => {
|
||||
name: name,
|
||||
displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
|
||||
required: true,
|
||||
value: (kcContext.register as any).formData[name] ?? "",
|
||||
html5DataAnnotations: {},
|
||||
readOnly: false,
|
||||
validators: {},
|
||||
annotations: {},
|
||||
autocomplete: (() => {
|
||||
switch (name) {
|
||||
case "email":
|
||||
return "email";
|
||||
@ -174,15 +174,15 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
.filter(name => (name !== "username" ? true : (kcContext.user as any).editUsernameAllowed))
|
||||
.map(name =>
|
||||
id<Attribute>({
|
||||
"name": name,
|
||||
"displayName": id<`\${${MessageKey}}`>(`\${${name}}`),
|
||||
"required": true,
|
||||
"value": (kcContext as any).user[name] ?? "",
|
||||
"html5DataAnnotations": {},
|
||||
"readOnly": false,
|
||||
"validators": {},
|
||||
"annotations": {},
|
||||
"autocomplete": (() => {
|
||||
name: name,
|
||||
displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
|
||||
required: true,
|
||||
value: (kcContext as any).user[name] ?? "",
|
||||
html5DataAnnotations: {},
|
||||
readOnly: false,
|
||||
validators: {},
|
||||
annotations: {},
|
||||
autocomplete: (() => {
|
||||
switch (name) {
|
||||
case "email":
|
||||
return "email";
|
||||
@ -200,15 +200,15 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
//NOTE: Handle legacy update-email.ftl
|
||||
return [
|
||||
id<Attribute>({
|
||||
"name": "email",
|
||||
"displayName": id<`\${${MessageKey}}`>(`\${email}`),
|
||||
"required": true,
|
||||
"value": (kcContext.email as any).value ?? "",
|
||||
"html5DataAnnotations": {},
|
||||
"readOnly": false,
|
||||
"validators": {},
|
||||
"annotations": {},
|
||||
"autocomplete": "email"
|
||||
name: "email",
|
||||
displayName: id<`\${${MessageKey}}`>(`\${email}`),
|
||||
required: true,
|
||||
value: (kcContext.email as any).value ?? "",
|
||||
html5DataAnnotations: {},
|
||||
readOnly: false,
|
||||
validators: {},
|
||||
annotations: {},
|
||||
autocomplete: "email"
|
||||
})
|
||||
];
|
||||
}
|
||||
@ -228,11 +228,11 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
|
||||
return id<Attribute>({
|
||||
...rest,
|
||||
"group": {
|
||||
"name": group,
|
||||
"displayHeader": groupDisplayHeader,
|
||||
"displayDescription": groupDisplayDescription,
|
||||
"html5DataAnnotations": {}
|
||||
group: {
|
||||
name: group,
|
||||
displayHeader: groupDisplayHeader,
|
||||
displayDescription: groupDisplayDescription,
|
||||
html5DataAnnotations: {}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -257,28 +257,28 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
|
||||
syntheticAttributes.push(
|
||||
{
|
||||
"name": "password",
|
||||
"displayName": id<`\${${MessageKey}}`>("${password}"),
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"validators": {},
|
||||
"annotations": {},
|
||||
"autocomplete": "new-password",
|
||||
"html5DataAnnotations": {},
|
||||
name: "password",
|
||||
displayName: id<`\${${MessageKey}}`>("${password}"),
|
||||
required: true,
|
||||
readOnly: false,
|
||||
validators: {},
|
||||
annotations: {},
|
||||
autocomplete: "new-password",
|
||||
html5DataAnnotations: {},
|
||||
// NOTE: Compat with Keycloak version prior to 24
|
||||
...({ "groupAnnotations": {} } as {})
|
||||
...({ groupAnnotations: {} } as {})
|
||||
},
|
||||
{
|
||||
"name": "password-confirm",
|
||||
"displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
||||
"required": true,
|
||||
"readOnly": false,
|
||||
"validators": {},
|
||||
"annotations": {},
|
||||
"html5DataAnnotations": {},
|
||||
"autocomplete": "new-password",
|
||||
name: "password-confirm",
|
||||
displayName: id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
||||
required: true,
|
||||
readOnly: false,
|
||||
validators: {},
|
||||
annotations: {},
|
||||
html5DataAnnotations: {},
|
||||
autocomplete: "new-password",
|
||||
// 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 out: { attribute: Attribute; valueOrValues: string | string[] }[] = [];
|
||||
const out: {
|
||||
attribute: Attribute;
|
||||
valueOrValues: string | string[];
|
||||
}[] = [];
|
||||
|
||||
for (const attribute of syntheticAttributes) {
|
||||
handle_multi_valued_attribute: {
|
||||
@ -324,7 +327,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
|
||||
out.push({
|
||||
attribute,
|
||||
"valueOrValues": values
|
||||
valueOrValues: values
|
||||
});
|
||||
|
||||
continue;
|
||||
@ -332,7 +335,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
|
||||
out.push({
|
||||
attribute,
|
||||
"valueOrValues": attribute.value ?? ""
|
||||
valueOrValues: attribute.value ?? ""
|
||||
});
|
||||
}
|
||||
|
||||
@ -340,14 +343,14 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
})();
|
||||
|
||||
const initialState: internal.State = {
|
||||
"formFieldStates": initialFormFieldState.map(({ attribute, valueOrValues }) => ({
|
||||
formFieldStates: initialFormFieldState.map(({ attribute, valueOrValues }) => ({
|
||||
attribute,
|
||||
"errors": getErrors({
|
||||
"attributeName": attribute.name,
|
||||
"formFieldStates": initialFormFieldState
|
||||
errors: getErrors({
|
||||
attributeName: attribute.name,
|
||||
formFieldStates: initialFormFieldState
|
||||
}),
|
||||
"hasLostFocusAtLeastOnce": valueOrValues instanceof Array ? valueOrValues.map(() => false) : false,
|
||||
"valueOrValues": valueOrValues
|
||||
hasLostFocusAtLeastOnce: valueOrValues instanceof Array ? valueOrValues.map(() => false) : false,
|
||||
valueOrValues: valueOrValues
|
||||
}))
|
||||
};
|
||||
|
||||
@ -381,8 +384,8 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
}
|
||||
|
||||
formFieldState.errors = getErrors({
|
||||
"attributeName": formAction.name,
|
||||
"formFieldStates": state.formFieldStates
|
||||
attributeName: formAction.name,
|
||||
formFieldStates: state.formFieldStates
|
||||
});
|
||||
|
||||
update_password_confirm: {
|
||||
@ -395,9 +398,9 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
}
|
||||
|
||||
state = reducer(state, {
|
||||
"action": "update",
|
||||
"name": "password-confirm",
|
||||
"valueOrValues": formAction.valueOrValues
|
||||
action: "update",
|
||||
name: "password-confirm",
|
||||
valueOrValues: formAction.valueOrValues
|
||||
});
|
||||
}
|
||||
|
||||
@ -421,9 +424,9 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
|
||||
const formState: FormState = useMemo(
|
||||
() => ({
|
||||
"formFieldStates": state.formFieldStates.map(
|
||||
formFieldStates: state.formFieldStates.map(
|
||||
({ errors, hasLostFocusAtLeastOnce: hasLostFocusAtLeastOnceOrArr, attribute, ...valueOrValuesWrap }) => ({
|
||||
"displayableErrors": errors.filter(error => {
|
||||
displayableErrors: errors.filter(error => {
|
||||
const hasLostFocusAtLeastOnce =
|
||||
typeof hasLostFocusAtLeastOnceOrArr === "boolean"
|
||||
? hasLostFocusAtLeastOnceOrArr
|
||||
@ -482,7 +485,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
...valueOrValuesWrap
|
||||
})
|
||||
),
|
||||
"isFormSubmittable": state.formFieldStates.every(({ errors }) => errors.length === 0)
|
||||
isFormSubmittable: state.formFieldStates.every(({ errors }) => errors.length === 0)
|
||||
}),
|
||||
[state]
|
||||
);
|
||||
@ -501,7 +504,13 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
|
||||
|
||||
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 formFieldState = formFieldStates.find(({ attribute }) => attribute.name === attributeName);
|
||||
@ -572,10 +581,10 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
return [
|
||||
{
|
||||
errorMessageStr,
|
||||
"errorMessage": <span key={0}>{errorMessageStr}</span>,
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "server"
|
||||
errorMessage: <span key={0}>{errorMessageStr}</span>,
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "server"
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -598,19 +607,19 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
.map((...[, index]) => {
|
||||
const specificValueErrors = getErrors({
|
||||
attributeName,
|
||||
"formFieldStates": formFieldStates.map(formFieldState => {
|
||||
formFieldStates: formFieldStates.map(formFieldState => {
|
||||
if (formFieldState.attribute.name === attributeName) {
|
||||
assert(formFieldState.valueOrValues instanceof Array);
|
||||
return {
|
||||
"attribute": {
|
||||
attribute: {
|
||||
...attribute,
|
||||
"annotations": {
|
||||
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;
|
||||
})
|
||||
.map((error): FormFieldError => ({ ...error, "fieldIndex": index }));
|
||||
.map(
|
||||
(error): FormFieldError => ({
|
||||
...error,
|
||||
fieldIndex: index
|
||||
})
|
||||
);
|
||||
})
|
||||
.reduce((acc, errors) => [...acc, ...errors], []);
|
||||
|
||||
@ -642,12 +656,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["error-user-attribute-required"] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "other",
|
||||
"rule": "requiredField"
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "other",
|
||||
rule: "requiredField"
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -696,12 +710,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
|
||||
return [
|
||||
{
|
||||
"errorMessage": <Fragment key={0}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "validator",
|
||||
"name": validatorName
|
||||
errorMessage: <Fragment key={0}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "validator",
|
||||
name: validatorName
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -740,12 +754,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordMinLengthMessage", `${minLength}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "passwordPolicy",
|
||||
"name": policyName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "passwordPolicy",
|
||||
name: policyName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -768,12 +782,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordMinDigitsMessage", `${minNumberOfDigits}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "passwordPolicy",
|
||||
"name": policyName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "passwordPolicy",
|
||||
name: policyName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -798,12 +812,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordMinLowerCaseCharsMessage", `${minNumberOfLowerCaseChar}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "passwordPolicy",
|
||||
"name": policyName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "passwordPolicy",
|
||||
name: policyName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -828,12 +842,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordMinUpperCaseCharsMessage", `${minNumberOfUpperCaseChar}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "passwordPolicy",
|
||||
"name": policyName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "passwordPolicy",
|
||||
name: policyName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -856,12 +870,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordMinSpecialCharsMessage", `${minNumberOfSpecialChar}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "passwordPolicy",
|
||||
"name": policyName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "passwordPolicy",
|
||||
name: policyName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -906,12 +920,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordNotUsernameMessage"] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "passwordPolicy",
|
||||
"name": policyName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "passwordPolicy",
|
||||
name: policyName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -944,12 +958,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordNotEmailMessage"] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "passwordPolicy",
|
||||
"name": policyName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "passwordPolicy",
|
||||
name: policyName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -977,12 +991,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["invalidPasswordConfirmMessage"] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "other",
|
||||
"rule": "passwordConfirmMatchesPassword"
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "other",
|
||||
rule: "passwordConfirmMatchesPassword"
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1001,12 +1015,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["error-user-attribute-required"] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "other",
|
||||
"rule": "requiredField"
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "other",
|
||||
rule: "requiredField"
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1027,17 +1041,17 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
}
|
||||
|
||||
const source: FormFieldError.Source = {
|
||||
"type": "validator",
|
||||
"name": validatorName
|
||||
type: "validator",
|
||||
name: validatorName
|
||||
};
|
||||
|
||||
if (max && value.length > parseInt(`${max}`)) {
|
||||
const msgArgs = ["error-invalid-length-too-long", `${max}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source
|
||||
});
|
||||
}
|
||||
@ -1046,9 +1060,9 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["error-invalid-length-too-short", `${min}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source
|
||||
});
|
||||
}
|
||||
@ -1076,12 +1090,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = [errorMessageKey ?? id<MessageKey>("shouldMatchPattern"), pattern] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": advancedMsgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "validator",
|
||||
"name": validatorName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: advancedMsgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "validator",
|
||||
name: validatorName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1115,12 +1129,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = [id<MessageKey>("invalidEmailMessage")] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "validator",
|
||||
"name": validatorName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "validator",
|
||||
name: validatorName
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1143,17 +1157,17 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const intValue = parseInt(value);
|
||||
|
||||
const source: FormFieldError.Source = {
|
||||
"type": "validator",
|
||||
"name": validatorName
|
||||
type: "validator",
|
||||
name: validatorName
|
||||
};
|
||||
|
||||
if (isNaN(intValue)) {
|
||||
const msgArgs = ["mustBeAnInteger"] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source
|
||||
});
|
||||
|
||||
@ -1164,9 +1178,9 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["error-number-out-of-range-too-big", `${max}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source
|
||||
});
|
||||
|
||||
@ -1177,9 +1191,9 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = ["error-number-out-of-range-too-small", `${min}`] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": msgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{msg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: msgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source
|
||||
});
|
||||
|
||||
@ -1207,12 +1221,12 @@ function useGetErrors(params: { kcContext: Pick<KcContextLike, "messagesPerField
|
||||
const msgArgs = [id<MessageKey>("notAValidOption")] as const;
|
||||
|
||||
errors.push({
|
||||
"errorMessage": <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": advancedMsgStr(...msgArgs),
|
||||
"fieldIndex": undefined,
|
||||
"source": {
|
||||
"type": "validator",
|
||||
"name": validatorName
|
||||
errorMessage: <Fragment key={`${attributeName}-${errors.length}`}>{advancedMsg(...msgArgs)}</Fragment>,
|
||||
errorMessageStr: advancedMsgStr(...msgArgs),
|
||||
fieldIndex: undefined,
|
||||
source: {
|
||||
type: "validator",
|
||||
name: validatorName
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -19,12 +19,18 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
|
||||
return (
|
||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("deleteAccountConfirm")}>
|
||||
<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>
|
||||
{msg("irreversibleAction")}
|
||||
</div>
|
||||
<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("errasingData")}</li>
|
||||
</ul>
|
||||
@ -38,7 +44,7 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
|
||||
{triggered_from_aia && (
|
||||
<button
|
||||
className={clsx(getClassName("kcButtonClass"), getClassName("kcButtonDefaultClass"), getClassName("kcButtonLargeClass"))}
|
||||
style={{ "marginLeft": "calc(100% - 220px)" }}
|
||||
style={{ marginLeft: "calc(100% - 220px)" }}
|
||||
type="submit"
|
||||
name="cancel-aia"
|
||||
value="true"
|
||||
|
@ -27,7 +27,7 @@ export default function FrontchannelLogout(props: PageProps<Extract<KcContext, {
|
||||
{logout.clients.map(client => (
|
||||
<li key={client.name}>
|
||||
{client.name}
|
||||
<iframe src={client.frontChannelLogoutUrl} style={{ "display": "none" }} />
|
||||
<iframe src={client.frontChannelLogoutUrl} style={{ display: "none" }} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -21,10 +21,10 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
"scriptTags": [
|
||||
scriptTags: [
|
||||
{
|
||||
"type": "text/javascript",
|
||||
"textContent": `
|
||||
type: "text/javascript",
|
||||
textContent: `
|
||||
|
||||
/* copy recovery codes */
|
||||
function copyRecoveryCodes() {
|
||||
|
@ -32,18 +32,18 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
"scriptTags": [
|
||||
scriptTags: [
|
||||
{
|
||||
"type": "text/javascript",
|
||||
"src": `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js`
|
||||
type: "text/javascript",
|
||||
src: `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js`
|
||||
},
|
||||
{
|
||||
"type": "text/javascript",
|
||||
"src": `${url.resourcesPath}/js/base64url.js`
|
||||
type: "text/javascript",
|
||||
src: `${url.resourcesPath}/js/base64url.js`
|
||||
},
|
||||
{
|
||||
"type": "text/javascript",
|
||||
"textContent": `
|
||||
type: "text/javascript",
|
||||
textContent: `
|
||||
|
||||
function webAuthnAuthenticate() {
|
||||
let isUserIdentified = ${isUserIdentified};
|
||||
@ -206,7 +206,10 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
||||
className={getClassName("kcSelectAuthListItemDescriptionClass")}
|
||||
>
|
||||
{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 }) => (
|
||||
<Fragment key={nameProperty}>
|
||||
<span>{msg(nameProperty)}</span>
|
||||
|
@ -35,18 +35,18 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { insertScriptTags } = useInsertScriptTags({
|
||||
"scriptTags": [
|
||||
scriptTags: [
|
||||
{
|
||||
"type": "text/javascript",
|
||||
"src": `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js`
|
||||
type: "text/javascript",
|
||||
src: `${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js`
|
||||
},
|
||||
{
|
||||
"type": "text/javascript",
|
||||
"src": `${url.resourcesPath}/js/base64url.js`
|
||||
type: "text/javascript",
|
||||
src: `${url.resourcesPath}/js/base64url.js`
|
||||
},
|
||||
{
|
||||
"type": "text/javascript",
|
||||
"textContent": `
|
||||
type: "text/javascript",
|
||||
textContent: `
|
||||
function registerSecurityKey() {
|
||||
|
||||
// Check if WebAuthn is supported by this browser
|
||||
|
@ -21,5 +21,9 @@ export declare namespace AndByDiscriminatingKey {
|
||||
SingletonU1 extends Record<DiscriminatingKey, string>,
|
||||
U2,
|
||||
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;
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ if (!Array.prototype.every) {
|
||||
var len = O.length >>> 0;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
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;
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { assert } from "tsafe/assert";
|
||||
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 => {
|
||||
const len = args.length;
|
||||
|
@ -3,7 +3,10 @@ import { is } from "tsafe/is";
|
||||
import { deepClone } from "./deepClone";
|
||||
|
||||
//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 source = deepClone(params.source);
|
||||
@ -11,12 +14,16 @@ export function deepAssign(params: { target: Record<string, unknown>; source: Re
|
||||
Object.keys(source).forEach(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, {
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"configurable": true,
|
||||
"value": dereferencedSource
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: dereferencedSource
|
||||
});
|
||||
|
||||
return;
|
||||
@ -37,8 +44,8 @@ export function deepAssign(params: { target: Record<string, unknown>; source: Re
|
||||
assert(is<Record<string, unknown>>(dereferencedSource));
|
||||
|
||||
deepAssign({
|
||||
"target": dereferencedTarget,
|
||||
"source": dereferencedSource
|
||||
target: dereferencedTarget,
|
||||
source: dereferencedSource
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -13,5 +13,7 @@ export function deepClone<T>(o: T): T {
|
||||
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;
|
||||
}
|
||||
|
@ -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
|
||||
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
|
||||
let rawValue = input.replace(/\D+/g, "");
|
||||
|
@ -2,7 +2,9 @@ import { useRef, useState } from "react";
|
||||
import { Parameters } from "tsafe/Parameters";
|
||||
|
||||
/** 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);
|
||||
|
||||
callbackRef.current = callback;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user