Compare commits
44 Commits
v10.0.0-rc
...
v10.0.0-rc
Author | SHA1 | Date | |
---|---|---|---|
da1dc0309b | |||
30f4e7d833 | |||
cf3a86fb9b | |||
e1633f43f4 | |||
5b64cfc23c | |||
19709cf085 | |||
b8bb6c4f02 | |||
b7a543f8cb | |||
04b4e19563 | |||
ffb27fc66d | |||
8b5f7eefda | |||
c750bf4ee8 | |||
aa74019ef6 | |||
9be6d9f75f | |||
81ebb9b552 | |||
5e13b8c41f | |||
dd1ed948ec | |||
8b93f701cf | |||
2f0084de5b | |||
2ef9828625 | |||
89db8983a7 | |||
287dd9bd31 | |||
9a92054c1a | |||
4189036213 | |||
2c0a427ba5 | |||
77b488d624 | |||
5249e05746 | |||
1e7a0dd7a6 | |||
fd67f2402a | |||
60a65ede2f | |||
1fa659ce61 | |||
0ab903dbc7 | |||
70b0a04793 | |||
c0df9aa939 | |||
60a1886942 | |||
1ebf97871b | |||
72e321aa32 | |||
b0f602b565 | |||
84c774503d | |||
9bbc7cc651 | |||
458083fb6d | |||
8dcfc840b4 | |||
9d06a3a6ad | |||
86cd08b954 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "10.0.0-rc.42",
|
"version": "10.0.0-rc.54",
|
||||||
"description": "Create Keycloak themes using React",
|
"description": "Create Keycloak themes using React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -44,6 +44,7 @@
|
|||||||
"dist/bin/shared/constants.js",
|
"dist/bin/shared/constants.js",
|
||||||
"dist/bin/shared/constants.d.ts",
|
"dist/bin/shared/constants.d.ts",
|
||||||
"dist/bin/shared/constants.js.map",
|
"dist/bin/shared/constants.js.map",
|
||||||
|
"dist/bin/shared/buildContext.d.ts",
|
||||||
"!dist/vite-plugin/",
|
"!dist/vite-plugin/",
|
||||||
"dist/vite-plugin/index.d.ts",
|
"dist/vite-plugin/index.d.ts",
|
||||||
"dist/vite-plugin/vite-plugin.d.ts",
|
"dist/vite-plugin/vite-plugin.d.ts",
|
||||||
@ -109,7 +110,6 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"recast": "^0.23.3",
|
"recast": "^0.23.3",
|
||||||
"run-exclusive": "^2.2.19",
|
"run-exclusive": "^2.2.19",
|
||||||
"scripting-tools": "^0.19.13",
|
|
||||||
"storybook-dark-mode": "^1.1.2",
|
"storybook-dark-mode": "^1.1.2",
|
||||||
"termost": "^0.12.0",
|
"termost": "^0.12.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
@ -3,40 +3,83 @@ import child_process from "child_process";
|
|||||||
import { SemVer } from "../src/bin/tools/SemVer";
|
import { SemVer } from "../src/bin/tools/SemVer";
|
||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
import { Deferred } from "evt/tools/Deferred";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { is } from "tsafe/is";
|
||||||
|
|
||||||
run(
|
(async () => {
|
||||||
[
|
{
|
||||||
`docker exec -it ${containerName}`,
|
const dCompleted = new Deferred<void>();
|
||||||
`/opt/keycloak/bin/kc.sh export`,
|
|
||||||
`--dir /tmp`,
|
|
||||||
`--realm myrealm`,
|
|
||||||
`--users realm_file`
|
|
||||||
].join(" ")
|
|
||||||
);
|
|
||||||
|
|
||||||
const keycloakMajorVersionNumber = SemVer.parse(
|
const child = child_process.spawn("docker", [
|
||||||
child_process
|
...["exec", containerName],
|
||||||
.execSync(`docker inspect --format '{{.Config.Image}}' ${containerName}`)
|
...["/opt/keycloak/bin/kc.sh", "export"],
|
||||||
.toString("utf8")
|
...["--dir", "/tmp"],
|
||||||
.trim()
|
...["--realm", "myrealm"],
|
||||||
.split(":")[1]
|
...["--users", "realm_file"]
|
||||||
).major;
|
]);
|
||||||
|
|
||||||
const targetFilePath = pathRelative(
|
let output = "";
|
||||||
process.cwd(),
|
|
||||||
pathJoin(
|
|
||||||
__dirname,
|
|
||||||
"..",
|
|
||||||
"src",
|
|
||||||
"bin",
|
|
||||||
"start-keycloak",
|
|
||||||
`myrealm-realm-${keycloakMajorVersionNumber}.json`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
run(`docker cp ${containerName}:/tmp/myrealm-realm.json ${targetFilePath}`);
|
const onExit = (code: number | null) => {
|
||||||
|
dCompleted.reject(new Error(`Exited with code ${code}`));
|
||||||
|
};
|
||||||
|
|
||||||
console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath)}`);
|
child.on("exit", onExit);
|
||||||
|
|
||||||
|
child.stdout.on("data", data => {
|
||||||
|
const outputStr = data.toString("utf8");
|
||||||
|
|
||||||
|
if (outputStr.includes("Export finished successfully")) {
|
||||||
|
child.removeListener("exit", onExit);
|
||||||
|
|
||||||
|
child.kill();
|
||||||
|
|
||||||
|
dCompleted.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
output += outputStr;
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on("data", data => (output += chalk.red(data.toString("utf8"))));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await dCompleted.pr;
|
||||||
|
} catch (error) {
|
||||||
|
assert(is<Error>(error));
|
||||||
|
|
||||||
|
console.log(chalk.red(error.message));
|
||||||
|
|
||||||
|
console.log(output);
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keycloakMajorVersionNumber = SemVer.parse(
|
||||||
|
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`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
run(`docker cp ${containerName}:/tmp/myrealm-realm.json ${targetFilePath}`);
|
||||||
|
|
||||||
|
console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath)}`);
|
||||||
|
})();
|
||||||
|
|
||||||
function run(command: string) {
|
function run(command: string) {
|
||||||
console.log(chalk.grey(`$ ${command}`));
|
console.log(chalk.grey(`$ ${command}`));
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
|
|
||||||
import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
|
|
||||||
import { getBuildContext } from "./shared/buildContext";
|
|
||||||
import { downloadKeycloakDefaultTheme } from "./shared/downloadKeycloakDefaultTheme";
|
|
||||||
import { transformCodebase } from "./tools/transformCodebase";
|
|
||||||
import type { CliCommandOptions } from "./main";
|
|
||||||
import chalk from "chalk";
|
|
||||||
|
|
||||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
|
||||||
const { cliCommandOptions } = params;
|
|
||||||
|
|
||||||
const buildContext = getBuildContext({
|
|
||||||
cliCommandOptions
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
chalk.cyan(
|
|
||||||
"Select the Keycloak version from which you want to download the builtins theme:"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const { keycloakVersion } = await promptKeycloakVersion({
|
|
||||||
startingFromMajor: undefined,
|
|
||||||
cacheDirPath: buildContext.cacheDirPath
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`→ ${keycloakVersion}`);
|
|
||||||
|
|
||||||
const destDirPath = pathJoin(
|
|
||||||
buildContext.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"
|
|
||||||
)}`
|
|
||||||
)}`
|
|
||||||
].join("\n")
|
|
||||||
);
|
|
||||||
|
|
||||||
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
|
|
||||||
keycloakVersion,
|
|
||||||
buildContext
|
|
||||||
});
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: defaultThemeDirPath,
|
|
||||||
destDirPath
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(chalk.green(`✓ done`));
|
|
||||||
}
|
|
@ -34,6 +34,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
const { keycloakVersion } = await promptKeycloakVersion({
|
const { keycloakVersion } = await promptKeycloakVersion({
|
||||||
// NOTE: This is arbitrary
|
// NOTE: This is arbitrary
|
||||||
startingFromMajor: 17,
|
startingFromMajor: 17,
|
||||||
|
excludeMajorVersions: [],
|
||||||
cacheDirPath: buildContext.cacheDirPath
|
cacheDirPath: buildContext.cacheDirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,12 +32,14 @@ export async function buildJar(params: {
|
|||||||
jarFileBasename: string;
|
jarFileBasename: string;
|
||||||
keycloakAccountV1Version: KeycloakAccountV1Version;
|
keycloakAccountV1Version: KeycloakAccountV1Version;
|
||||||
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
|
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
|
||||||
|
resourcesDirPath: string;
|
||||||
buildContext: BuildContextLike;
|
buildContext: BuildContextLike;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const {
|
const {
|
||||||
jarFileBasename,
|
jarFileBasename,
|
||||||
keycloakAccountV1Version,
|
keycloakAccountV1Version,
|
||||||
keycloakThemeAdditionalInfoExtensionVersion,
|
keycloakThemeAdditionalInfoExtensionVersion,
|
||||||
|
resourcesDirPath,
|
||||||
buildContext
|
buildContext
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
@ -48,35 +50,10 @@ export async function buildJar(params: {
|
|||||||
|
|
||||||
rmSync(keycloakifyBuildTmpDirPath, { recursive: true, force: true });
|
rmSync(keycloakifyBuildTmpDirPath, { recursive: true, force: true });
|
||||||
|
|
||||||
{
|
transformCodebase({
|
||||||
const transformCodebase_common = (params: {
|
srcDirPath: resourcesDirPath,
|
||||||
fileRelativePath: string;
|
destDirPath: pathJoin(keycloakifyBuildTmpDirPath, "src", "main", "resources"),
|
||||||
sourceCode: Buffer;
|
transformSourceCode:
|
||||||
}): { modifiedSourceCode: Buffer } | undefined => {
|
|
||||||
const { fileRelativePath, sourceCode } = params;
|
|
||||||
|
|
||||||
if (
|
|
||||||
fileRelativePath ===
|
|
||||||
getMetaInfKeycloakThemesJsonFilePath({ keycloakifyBuildDirPath: "." })
|
|
||||||
) {
|
|
||||||
return { modifiedSourceCode: sourceCode };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const themeName of [...buildContext.themeNames, accountV1ThemeName]) {
|
|
||||||
if (
|
|
||||||
isInside({
|
|
||||||
dirPath: pathJoin("src", "main", "resources", "theme", themeName),
|
|
||||||
filePath: fileRelativePath
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return { modifiedSourceCode: sourceCode };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const transformCodebase_patchForUsingBuiltinAccountV1 =
|
|
||||||
keycloakAccountV1Version !== null
|
keycloakAccountV1Version !== null
|
||||||
? undefined
|
? undefined
|
||||||
: (params: {
|
: (params: {
|
||||||
@ -87,13 +64,7 @@ export async function buildJar(params: {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
isInside({
|
isInside({
|
||||||
dirPath: pathJoin(
|
dirPath: pathJoin("theme", accountV1ThemeName),
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"theme",
|
|
||||||
accountV1ThemeName
|
|
||||||
),
|
|
||||||
filePath: fileRelativePath
|
filePath: fileRelativePath
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
@ -103,7 +74,7 @@ export async function buildJar(params: {
|
|||||||
if (
|
if (
|
||||||
fileRelativePath ===
|
fileRelativePath ===
|
||||||
getMetaInfKeycloakThemesJsonFilePath({
|
getMetaInfKeycloakThemesJsonFilePath({
|
||||||
keycloakifyBuildDirPath: "."
|
resourcesDirPath: "."
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
const keycloakThemesJsonParsed = JSON.parse(
|
const keycloakThemesJsonParsed = JSON.parse(
|
||||||
@ -128,15 +99,7 @@ export async function buildJar(params: {
|
|||||||
for (const themeName of buildContext.themeNames) {
|
for (const themeName of buildContext.themeNames) {
|
||||||
if (
|
if (
|
||||||
fileRelativePath ===
|
fileRelativePath ===
|
||||||
pathJoin(
|
pathJoin("theme", themeName, "account", "theme.properties")
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"theme",
|
|
||||||
themeName,
|
|
||||||
"account",
|
|
||||||
"theme.properties"
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
const modifiedSourceCode = Buffer.from(
|
const modifiedSourceCode = Buffer.from(
|
||||||
sourceCode
|
sourceCode
|
||||||
@ -157,31 +120,8 @@ export async function buildJar(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { modifiedSourceCode: sourceCode };
|
return { modifiedSourceCode: sourceCode };
|
||||||
};
|
}
|
||||||
|
});
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: buildContext.keycloakifyBuildDirPath,
|
|
||||||
destDirPath: keycloakifyBuildTmpDirPath,
|
|
||||||
transformSourceCode: params => {
|
|
||||||
const resultCommon = transformCodebase_common(params);
|
|
||||||
|
|
||||||
if (transformCodebase_patchForUsingBuiltinAccountV1 === undefined) {
|
|
||||||
return resultCommon;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resultCommon === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { modifiedSourceCode } = resultCommon;
|
|
||||||
|
|
||||||
return transformCodebase_patchForUsingBuiltinAccountV1({
|
|
||||||
...params,
|
|
||||||
sourceCode: modifiedSourceCode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
route_legacy_pages: {
|
route_legacy_pages: {
|
||||||
// NOTE: If there's no account theme there is no special target for keycloak 24 and up so we create
|
// NOTE: If there's no account theme there is no special target for keycloak 24 and up so we create
|
||||||
|
@ -8,7 +8,7 @@ import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar";
|
|||||||
import { buildJar, BuildContextLike as BuildContextLike_buildJar } from "./buildJar";
|
import { buildJar, BuildContextLike as BuildContextLike_buildJar } from "./buildJar";
|
||||||
import type { BuildContext } from "../../shared/buildContext";
|
import type { BuildContext } from "../../shared/buildContext";
|
||||||
import { getJarFileBasename } from "../../shared/getJarFileBasename";
|
import { getJarFileBasename } from "../../shared/getJarFileBasename";
|
||||||
import { readMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes";
|
import { readMetaInfKeycloakThemes_fromResourcesDirPath } from "../../shared/metaInfKeycloakThemes";
|
||||||
import { accountV1ThemeName } from "../../shared/constants";
|
import { accountV1ThemeName } from "../../shared/constants";
|
||||||
|
|
||||||
export type BuildContextLike = BuildContextLike_buildJar & {
|
export type BuildContextLike = BuildContextLike_buildJar & {
|
||||||
@ -18,12 +18,14 @@ export type BuildContextLike = BuildContextLike_buildJar & {
|
|||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
export async function buildJars(params: {
|
export async function buildJars(params: {
|
||||||
|
resourcesDirPath: string;
|
||||||
|
onlyBuildJarFileBasename: string | undefined;
|
||||||
buildContext: BuildContextLike;
|
buildContext: BuildContextLike;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { buildContext } = params;
|
const { onlyBuildJarFileBasename, resourcesDirPath, buildContext } = params;
|
||||||
|
|
||||||
const doesImplementAccountTheme = readMetaInfKeycloakThemes({
|
const doesImplementAccountTheme = readMetaInfKeycloakThemes_fromResourcesDirPath({
|
||||||
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath
|
resourcesDirPath
|
||||||
}).themes.some(({ name }) => name === accountV1ThemeName);
|
}).themes.some(({ name }) => name === accountV1ThemeName);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -56,12 +58,20 @@ export async function buildJars(params: {
|
|||||||
keycloakVersionRange
|
keycloakVersionRange
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
onlyBuildJarFileBasename !== undefined &&
|
||||||
|
onlyBuildJarFileBasename !== jarFileBasename
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
keycloakThemeAdditionalInfoExtensionVersion,
|
keycloakThemeAdditionalInfoExtensionVersion,
|
||||||
jarFileBasename
|
jarFileBasename
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.filter(exclude(undefined))
|
||||||
.map(
|
.map(
|
||||||
({
|
({
|
||||||
keycloakThemeAdditionalInfoExtensionVersion,
|
keycloakThemeAdditionalInfoExtensionVersion,
|
||||||
@ -71,6 +81,7 @@ export async function buildJars(params: {
|
|||||||
jarFileBasename,
|
jarFileBasename,
|
||||||
keycloakAccountV1Version,
|
keycloakAccountV1Version,
|
||||||
keycloakThemeAdditionalInfoExtensionVersion,
|
keycloakThemeAdditionalInfoExtensionVersion,
|
||||||
|
resourcesDirPath,
|
||||||
buildContext
|
buildContext
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// NOTE: v0.5 is a dummy version.
|
// NOTE: v0.5 is a dummy version.
|
||||||
export const keycloakAccountV1Versions = [null, "0.3", "0.4"] as const;
|
export const keycloakAccountV1Versions = [null, "0.3", "0.4", "0.6"] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://central.sonatype.com/artifact/io.phasetwo.keycloak/keycloak-account-v1
|
* https://central.sonatype.com/artifact/io.phasetwo.keycloak/keycloak-account-v1
|
||||||
|
@ -44,12 +44,20 @@ export function getKeycloakVersionRangeForJar(params: {
|
|||||||
case null:
|
case null:
|
||||||
return undefined;
|
return undefined;
|
||||||
case "1.1.5":
|
case "1.1.5":
|
||||||
return "24-and-above" as const;
|
return "24" as const;
|
||||||
}
|
}
|
||||||
assert<
|
assert<
|
||||||
Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>
|
Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>
|
||||||
>(false);
|
>(false);
|
||||||
|
case "0.6":
|
||||||
|
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||||
|
case null:
|
||||||
|
return undefined;
|
||||||
|
case "1.1.5":
|
||||||
|
return "25-and-above" as const;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert<Equals<typeof keycloakAccountV1Version, never>>(false);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
assert<
|
assert<
|
||||||
@ -65,7 +73,6 @@ export function getKeycloakVersionRangeForJar(params: {
|
|||||||
if (keycloakAccountV1Version !== null) {
|
if (keycloakAccountV1Version !== null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||||
case null:
|
case null:
|
||||||
return "21-and-below";
|
return "21-and-below";
|
||||||
|
@ -180,10 +180,42 @@ try {
|
|||||||
<#if attribute.annotations.inputTypePlaceholder??>
|
<#if attribute.annotations.inputTypePlaceholder??>
|
||||||
"${attribute.annotations.inputTypePlaceholder}": decodeHtmlEntities("${advancedMsg(attribute.annotations.inputTypePlaceholder)?js_string}"),
|
"${attribute.annotations.inputTypePlaceholder}": decodeHtmlEntities("${advancedMsg(attribute.annotations.inputTypePlaceholder)?js_string}"),
|
||||||
</#if>
|
</#if>
|
||||||
|
<!-- Loop through the options that are in attribute.validators.options.options -->
|
||||||
|
<#if (
|
||||||
|
attribute.annotations.inputOptionLabelsI18nPrefix?? &&
|
||||||
|
attribute.validators?? &&
|
||||||
|
attribute.validators.options??
|
||||||
|
)>
|
||||||
|
<#list attribute.validators.options.options as option>
|
||||||
|
"${attribute.annotations.inputOptionLabelsI18nPrefix}.${option}": decodeHtmlEntities("${msg(attribute.annotations.inputOptionLabelsI18nPrefix + "." + option)?js_string}"),
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
};
|
};
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
|
attributes_to_attributesByName: {
|
||||||
|
|
||||||
|
if( !out["profile"] ){
|
||||||
|
break attributes_to_attributesByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !out["profile"]["attributes"] ){
|
||||||
|
break attributes_to_attributesByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = out["profile"]["attributes"];
|
||||||
|
|
||||||
|
delete out["profile"]["attributes"];
|
||||||
|
|
||||||
|
out["profile"]["attributesByName"] = {};
|
||||||
|
|
||||||
|
attributes.forEach(function(attribute){
|
||||||
|
out["profile"]["attributesByName"][attribute.name] = attribute;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|
||||||
function decodeHtmlEntities(htmlStr){
|
function decodeHtmlEntities(htmlStr){
|
||||||
@ -287,8 +319,8 @@ function decodeHtmlEntities(htmlStr){
|
|||||||
key == "realmAttributes" &&
|
key == "realmAttributes" &&
|
||||||
are_same_path(path, [])
|
are_same_path(path, [])
|
||||||
) || (
|
) || (
|
||||||
<#-- attributesByName adds a lot of noise to the output and is not needed -->
|
<#-- attributesByName adds a lot of noise to the output and is not needed, we already have profile.attributes -->
|
||||||
key == "attributes" &&
|
key == "attributesByName" &&
|
||||||
are_same_path(path, ["profile"])
|
are_same_path(path, ["profile"])
|
||||||
) || (
|
) || (
|
||||||
<#-- We already have the attributes in profile speedup the rendering by filtering it out from the register object -->
|
<#-- We already have the attributes in profile speedup the rendering by filtering it out from the register object -->
|
||||||
@ -304,7 +336,7 @@ function decodeHtmlEntities(htmlStr){
|
|||||||
|
|
||||||
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
||||||
<#if (
|
<#if (
|
||||||
["register.ftl", "register-user-profile.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
["register.ftl", "register-user-profile.ftl", "terms.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||||
key == "attemptedUsername" && are_same_path(path, ["auth"])
|
key == "attemptedUsername" && are_same_path(path, ["auth"])
|
||||||
)>
|
)>
|
||||||
<#attempt>
|
<#attempt>
|
||||||
|
@ -13,13 +13,15 @@ import { transformCodebase } from "../../tools/transformCodebase";
|
|||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
npmWorkspaceRootDirPath: string;
|
npmWorkspaceRootDirPath: string;
|
||||||
keycloakifyBuildDirPath: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
export async function bringInAccountV1(params: { buildContext: BuildContextLike }) {
|
export async function bringInAccountV1(params: {
|
||||||
const { buildContext } = params;
|
resourcesDirPath: string;
|
||||||
|
buildContext: BuildContextLike;
|
||||||
|
}) {
|
||||||
|
const { resourcesDirPath, buildContext } = params;
|
||||||
|
|
||||||
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
|
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
|
||||||
keycloakVersion: lastKeycloakVersionWithAccountV1,
|
keycloakVersion: lastKeycloakVersionWithAccountV1,
|
||||||
@ -27,10 +29,7 @@ export async function bringInAccountV1(params: { buildContext: BuildContextLike
|
|||||||
});
|
});
|
||||||
|
|
||||||
const accountV1DirPath = pathJoin(
|
const accountV1DirPath = pathJoin(
|
||||||
buildContext.keycloakifyBuildDirPath,
|
resourcesDirPath,
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"theme",
|
"theme",
|
||||||
accountV1ThemeName,
|
accountV1ThemeName,
|
||||||
"account"
|
"account"
|
||||||
|
@ -5,6 +5,8 @@ import {
|
|||||||
type BuildContextLike as BuildContextLike_generateSrcMainResourcesForMainTheme
|
type BuildContextLike as BuildContextLike_generateSrcMainResourcesForMainTheme
|
||||||
} from "./generateSrcMainResourcesForMainTheme";
|
} from "./generateSrcMainResourcesForMainTheme";
|
||||||
import { generateSrcMainResourcesForThemeVariant } from "./generateSrcMainResourcesForThemeVariant";
|
import { generateSrcMainResourcesForThemeVariant } from "./generateSrcMainResourcesForThemeVariant";
|
||||||
|
import fs from "fs";
|
||||||
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
|
||||||
export type BuildContextLike = BuildContextLike_generateSrcMainResourcesForMainTheme & {
|
export type BuildContextLike = BuildContextLike_generateSrcMainResourcesForMainTheme & {
|
||||||
themeNames: string[];
|
themeNames: string[];
|
||||||
@ -14,21 +16,27 @@ assert<BuildContext extends BuildContextLike ? true : false>();
|
|||||||
|
|
||||||
export async function generateSrcMainResources(params: {
|
export async function generateSrcMainResources(params: {
|
||||||
buildContext: BuildContextLike;
|
buildContext: BuildContextLike;
|
||||||
|
resourcesDirPath: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { buildContext } = params;
|
const { resourcesDirPath, buildContext } = params;
|
||||||
|
|
||||||
const [themeName, ...themeVariantNames] = buildContext.themeNames;
|
const [themeName, ...themeVariantNames] = buildContext.themeNames;
|
||||||
|
|
||||||
|
if (fs.existsSync(resourcesDirPath)) {
|
||||||
|
rmSync(resourcesDirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
await generateSrcMainResourcesForMainTheme({
|
await generateSrcMainResourcesForMainTheme({
|
||||||
|
resourcesDirPath,
|
||||||
themeName,
|
themeName,
|
||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const themeVariantName of themeVariantNames) {
|
for (const themeVariantName of themeVariantNames) {
|
||||||
generateSrcMainResourcesForThemeVariant({
|
generateSrcMainResourcesForThemeVariant({
|
||||||
|
resourcesDirPath,
|
||||||
themeName,
|
themeName,
|
||||||
themeVariantName,
|
themeVariantName
|
||||||
buildContext
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,14 +43,10 @@ import { escapeStringForPropertiesFile } from "../../tools/escapeStringForProper
|
|||||||
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
||||||
BuildContextLike_downloadKeycloakStaticResources &
|
BuildContextLike_downloadKeycloakStaticResources &
|
||||||
BuildContextLike_bringInAccountV1 & {
|
BuildContextLike_bringInAccountV1 & {
|
||||||
bundler: "vite" | "webpack";
|
|
||||||
extraThemeProperties: string[] | undefined;
|
extraThemeProperties: string[] | undefined;
|
||||||
loginThemeResourcesFromKeycloakVersion: string;
|
loginThemeResourcesFromKeycloakVersion: string;
|
||||||
projectBuildDirPath: string;
|
|
||||||
assetsDirPath: string;
|
|
||||||
urlPathname: string | undefined;
|
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
||||||
keycloakifyBuildDirPath: string;
|
projectBuildDirPath: string;
|
||||||
environmentVariables: { name: string; default: string }[];
|
environmentVariables: { name: string; default: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,9 +54,10 @@ assert<BuildContext extends BuildContextLike ? true : false>();
|
|||||||
|
|
||||||
export async function generateSrcMainResourcesForMainTheme(params: {
|
export async function generateSrcMainResourcesForMainTheme(params: {
|
||||||
themeName: string;
|
themeName: string;
|
||||||
|
resourcesDirPath: string;
|
||||||
buildContext: BuildContextLike;
|
buildContext: BuildContextLike;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { themeName, buildContext } = params;
|
const { themeName, resourcesDirPath, buildContext } = params;
|
||||||
|
|
||||||
const { themeSrcDirPath } = getThemeSrcDirPath({
|
const { themeSrcDirPath } = getThemeSrcDirPath({
|
||||||
projectDirPath: buildContext.projectDirPath
|
projectDirPath: buildContext.projectDirPath
|
||||||
@ -68,15 +65,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
|
|||||||
|
|
||||||
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
|
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
|
||||||
const { themeType } = params;
|
const { themeType } = params;
|
||||||
return pathJoin(
|
return pathJoin(resourcesDirPath, "theme", themeName, themeType);
|
||||||
buildContext.keycloakifyBuildDirPath,
|
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"theme",
|
|
||||||
themeName,
|
|
||||||
themeType
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cssGlobalsToDefine: Record<string, string> = {};
|
const cssGlobalsToDefine: Record<string, string> = {};
|
||||||
@ -207,8 +196,6 @@ export async function generateSrcMainResourcesForMainTheme(params: {
|
|||||||
].forEach(pageId => {
|
].forEach(pageId => {
|
||||||
const { ftlCode } = generateFtlFilesCode({ pageId });
|
const { ftlCode } = generateFtlFilesCode({ pageId });
|
||||||
|
|
||||||
fs.mkdirSync(themeTypeDirPath, { recursive: true });
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
pathJoin(themeTypeDirPath, pageId),
|
pathJoin(themeTypeDirPath, pageId),
|
||||||
Buffer.from(ftlCode, "utf8")
|
Buffer.from(ftlCode, "utf8")
|
||||||
@ -291,6 +278,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
|
|||||||
|
|
||||||
if (implementedThemeTypes.account) {
|
if (implementedThemeTypes.account) {
|
||||||
await bringInAccountV1({
|
await bringInAccountV1({
|
||||||
|
resourcesDirPath,
|
||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -313,7 +301,7 @@ export async function generateSrcMainResourcesForMainTheme(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeMetaInfKeycloakThemes({
|
writeMetaInfKeycloakThemes({
|
||||||
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath,
|
resourcesDirPath,
|
||||||
metaInfKeycloakThemes
|
metaInfKeycloakThemes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { join as pathJoin, extname as pathExtname, sep as pathSep } from "path";
|
|||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
import type { BuildContext } from "../../shared/buildContext";
|
import type { BuildContext } from "../../shared/buildContext";
|
||||||
import {
|
import {
|
||||||
readMetaInfKeycloakThemes,
|
readMetaInfKeycloakThemes_fromResourcesDirPath,
|
||||||
writeMetaInfKeycloakThemes
|
writeMetaInfKeycloakThemes
|
||||||
} from "../../shared/metaInfKeycloakThemes";
|
} from "../../shared/metaInfKeycloakThemes";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
@ -14,20 +14,13 @@ export type BuildContextLike = {
|
|||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
export function generateSrcMainResourcesForThemeVariant(params: {
|
export function generateSrcMainResourcesForThemeVariant(params: {
|
||||||
|
resourcesDirPath: string;
|
||||||
themeName: string;
|
themeName: string;
|
||||||
themeVariantName: string;
|
themeVariantName: string;
|
||||||
buildContext: BuildContextLike;
|
|
||||||
}) {
|
}) {
|
||||||
const { themeName, themeVariantName, buildContext } = params;
|
const { resourcesDirPath, themeName, themeVariantName } = params;
|
||||||
|
|
||||||
const mainThemeDirPath = pathJoin(
|
const mainThemeDirPath = pathJoin(resourcesDirPath, "theme", themeName);
|
||||||
buildContext.keycloakifyBuildDirPath,
|
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"theme",
|
|
||||||
themeName
|
|
||||||
);
|
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
srcDirPath: mainThemeDirPath,
|
srcDirPath: mainThemeDirPath,
|
||||||
@ -57,9 +50,10 @@ export function generateSrcMainResourcesForThemeVariant(params: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
const updatedMetaInfKeycloakThemes = readMetaInfKeycloakThemes({
|
const updatedMetaInfKeycloakThemes =
|
||||||
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath
|
readMetaInfKeycloakThemes_fromResourcesDirPath({
|
||||||
});
|
resourcesDirPath
|
||||||
|
});
|
||||||
|
|
||||||
updatedMetaInfKeycloakThemes.themes.push({
|
updatedMetaInfKeycloakThemes.themes.push({
|
||||||
name: themeVariantName,
|
name: themeVariantName,
|
||||||
@ -73,7 +67,7 @@ export function generateSrcMainResourcesForThemeVariant(params: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
writeMetaInfKeycloakThemes({
|
writeMetaInfKeycloakThemes({
|
||||||
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath,
|
resourcesDirPath,
|
||||||
metaInfKeycloakThemes: updatedMetaInfKeycloakThemes
|
metaInfKeycloakThemes: updatedMetaInfKeycloakThemes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
import * as fs from "fs";
|
|
||||||
import {
|
|
||||||
join as pathJoin,
|
|
||||||
relative as pathRelative,
|
|
||||||
basename as pathBasename
|
|
||||||
} from "path";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import type { BuildContext } from "../shared/buildContext";
|
|
||||||
import { accountV1ThemeName } from "../shared/constants";
|
|
||||||
|
|
||||||
export type BuildContextLike = {
|
|
||||||
keycloakifyBuildDirPath: string;
|
|
||||||
themeNames: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
|
||||||
|
|
||||||
generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh";
|
|
||||||
|
|
||||||
const containerName = "keycloak-testing-container";
|
|
||||||
const keycloakVersion = "24.0.4";
|
|
||||||
|
|
||||||
/** Files for being able to run a hot reload keycloak container */
|
|
||||||
export function generateStartKeycloakTestingContainer(params: {
|
|
||||||
jarFilePath: string;
|
|
||||||
doesImplementAccountTheme: boolean;
|
|
||||||
buildContext: BuildContextLike;
|
|
||||||
}) {
|
|
||||||
const { jarFilePath, doesImplementAccountTheme, buildContext } = params;
|
|
||||||
|
|
||||||
const themeRelativeDirPath = pathJoin("src", "main", "resources", "theme");
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
pathJoin(
|
|
||||||
buildContext.keycloakifyBuildDirPath,
|
|
||||||
generateStartKeycloakTestingContainer.basename
|
|
||||||
),
|
|
||||||
Buffer.from(
|
|
||||||
[
|
|
||||||
"#!/usr/bin/env bash",
|
|
||||||
"",
|
|
||||||
`docker rm ${containerName} || true`,
|
|
||||||
"",
|
|
||||||
`cd "${buildContext.keycloakifyBuildDirPath}"`,
|
|
||||||
"",
|
|
||||||
"docker run \\",
|
|
||||||
" -p 8080:8080 \\",
|
|
||||||
` --name ${containerName} \\`,
|
|
||||||
" -e KEYCLOAK_ADMIN=admin \\",
|
|
||||||
" -e KEYCLOAK_ADMIN_PASSWORD=admin \\",
|
|
||||||
` -v "${pathJoin(
|
|
||||||
"$(pwd)",
|
|
||||||
pathRelative(buildContext.keycloakifyBuildDirPath, jarFilePath)
|
|
||||||
)}":"/opt/keycloak/providers/${pathBasename(jarFilePath)}" \\`,
|
|
||||||
[
|
|
||||||
...(doesImplementAccountTheme ? [accountV1ThemeName] : []),
|
|
||||||
...buildContext.themeNames
|
|
||||||
].map(
|
|
||||||
themeName =>
|
|
||||||
` -v "${pathJoin(
|
|
||||||
"$(pwd)",
|
|
||||||
themeRelativeDirPath,
|
|
||||||
themeName
|
|
||||||
).replace(/\\/g, "/")}":"/opt/keycloak/themes/${themeName}":rw \\`
|
|
||||||
),
|
|
||||||
` -it quay.io/keycloak/keycloak:${keycloakVersion} \\`,
|
|
||||||
` start-dev`,
|
|
||||||
""
|
|
||||||
].join("\n"),
|
|
||||||
"utf8"
|
|
||||||
),
|
|
||||||
{ mode: 0o755 }
|
|
||||||
);
|
|
||||||
}
|
|
@ -3,12 +3,16 @@ import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path
|
|||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { getBuildContext } from "../shared/buildContext";
|
import { getBuildContext } from "../shared/buildContext";
|
||||||
import { vitePluginSubScriptEnvNames, skipBuildJarsEnvName } from "../shared/constants";
|
import {
|
||||||
|
vitePluginSubScriptEnvNames,
|
||||||
|
onlyBuildJarFileBasenameEnvName
|
||||||
|
} from "../shared/constants";
|
||||||
import { buildJars } from "./buildJars";
|
import { buildJars } from "./buildJars";
|
||||||
import type { CliCommandOptions } from "../main";
|
import type { CliCommandOptions } from "../main";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
|
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
|
import { rmSync } from "../tools/fs.rmSync";
|
||||||
|
|
||||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||||
exit_if_maven_not_installed: {
|
exit_if_maven_not_installed: {
|
||||||
@ -76,7 +80,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await generateSrcMainResources({ buildContext });
|
const resourcesDirPath = pathJoin(buildContext.keycloakifyBuildDirPath, "resources");
|
||||||
|
|
||||||
|
await generateSrcMainResources({
|
||||||
|
resourcesDirPath,
|
||||||
|
buildContext
|
||||||
|
});
|
||||||
|
|
||||||
run_post_build_script: {
|
run_post_build_script: {
|
||||||
if (buildContext.bundler !== "vite") {
|
if (buildContext.bundler !== "vite") {
|
||||||
@ -93,15 +102,17 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
build_jars: {
|
await buildJars({
|
||||||
if (process.env[skipBuildJarsEnvName]) {
|
resourcesDirPath,
|
||||||
break build_jars;
|
buildContext,
|
||||||
}
|
onlyBuildJarFileBasename: process.env[onlyBuildJarFileBasenameEnvName]
|
||||||
|
});
|
||||||
|
|
||||||
await buildJars({ buildContext });
|
rmSync(resourcesDirPath, { recursive: true });
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)
|
chalk.green(
|
||||||
|
`✓ keycloak theme built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -134,20 +134,6 @@ program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
|
||||||
.command({
|
|
||||||
name: "download-keycloak-default-theme",
|
|
||||||
description: "Download the built-in Keycloak theme."
|
|
||||||
})
|
|
||||||
.task({
|
|
||||||
skip,
|
|
||||||
handler: async cliCommandOptions => {
|
|
||||||
const { command } = await import("./download-keycloak-default-theme");
|
|
||||||
|
|
||||||
await command({ cliCommandOptions });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command({
|
.command({
|
||||||
name: "eject-page",
|
name: "eject-page",
|
||||||
|
@ -5,5 +5,5 @@ export type KeycloakVersionRange =
|
|||||||
export namespace KeycloakVersionRange {
|
export namespace KeycloakVersionRange {
|
||||||
export type WithoutAccountTheme = "21-and-below" | "22-and-above";
|
export type WithoutAccountTheme = "21-and-below" | "22-and-above";
|
||||||
|
|
||||||
export type WithAccountTheme = "21-and-below" | "23" | "24-and-above";
|
export type WithAccountTheme = "21-and-below" | "23" | "24" | "25-and-above";
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { vitePluginSubScriptEnvNames } from "./constants";
|
|||||||
export type BuildContext = {
|
export type BuildContext = {
|
||||||
bundler: "vite" | "webpack";
|
bundler: "vite" | "webpack";
|
||||||
themeVersion: string;
|
themeVersion: string;
|
||||||
themeNames: string[];
|
themeNames: [string, ...string[]];
|
||||||
extraThemeProperties: string[] | undefined;
|
extraThemeProperties: string[] | undefined;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
artifactId: string;
|
artifactId: string;
|
||||||
@ -147,7 +147,7 @@ export function getBuildContext(params: {
|
|||||||
...resolvedViteConfig?.buildOptions
|
...resolvedViteConfig?.buildOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
const themeNames = (() => {
|
const themeNames = ((): [string, ...string[]] => {
|
||||||
if (buildOptions.themeName === undefined) {
|
if (buildOptions.themeName === undefined) {
|
||||||
return [
|
return [
|
||||||
parsedPackageJson.name
|
parsedPackageJson.name
|
||||||
@ -161,7 +161,11 @@ export function getBuildContext(params: {
|
|||||||
return [buildOptions.themeName];
|
return [buildOptions.themeName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildOptions.themeName;
|
const [mainThemeName, ...themeVariantNames] = buildOptions.themeName;
|
||||||
|
|
||||||
|
assert(mainThemeName !== undefined);
|
||||||
|
|
||||||
|
return [mainThemeName, ...themeVariantNames];
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const projectBuildDirPath = (() => {
|
const projectBuildDirPath = (() => {
|
||||||
|
@ -16,7 +16,7 @@ export const vitePluginSubScriptEnvNames = {
|
|||||||
resolveViteConfig: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG"
|
resolveViteConfig: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const skipBuildJarsEnvName = "KEYCLOAKIFY_SKIP_BUILD_JAR";
|
export const onlyBuildJarFileBasenameEnvName = "KEYCLOAKIFY_ONLY_BUILD_JAR_FILE_BASENAME";
|
||||||
|
|
||||||
export const loginThemePageIds = [
|
export const loginThemePageIds = [
|
||||||
"login.ftl",
|
"login.ftl",
|
||||||
|
@ -46,7 +46,7 @@ export async function generateKcGenTs(params: {
|
|||||||
``,
|
``,
|
||||||
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
|
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
|
||||||
``,
|
``,
|
||||||
`export const KcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
|
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
|
||||||
``,
|
``,
|
||||||
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
|
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
@ -58,7 +58,8 @@ export async function generateKcGenTs(params: {
|
|||||||
2
|
2
|
||||||
)};`,
|
)};`,
|
||||||
``,
|
``,
|
||||||
`/* prettier-ignore-end */`
|
`/* prettier-ignore-end */`,
|
||||||
|
``
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"utf8"
|
"utf8"
|
||||||
);
|
);
|
||||||
|
@ -1,50 +1,73 @@
|
|||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
import { join as pathJoin, dirname as pathDirname } from "path";
|
||||||
import type { ThemeType } from "./constants";
|
import type { ThemeType } from "./constants";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { extractArchive } from "../tools/extractArchive";
|
||||||
|
|
||||||
export type MetaInfKeycloakTheme = {
|
export type MetaInfKeycloakTheme = {
|
||||||
themes: { name: string; types: (ThemeType | "email")[] }[];
|
themes: { name: string; types: (ThemeType | "email")[] }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getMetaInfKeycloakThemesJsonFilePath(params: {
|
export function getMetaInfKeycloakThemesJsonFilePath(params: {
|
||||||
keycloakifyBuildDirPath: string;
|
resourcesDirPath: string;
|
||||||
}) {
|
}) {
|
||||||
const { keycloakifyBuildDirPath } = params;
|
const { resourcesDirPath } = params;
|
||||||
|
|
||||||
return pathJoin(
|
return pathJoin(
|
||||||
keycloakifyBuildDirPath === "." ? "" : keycloakifyBuildDirPath,
|
resourcesDirPath === "." ? "" : resourcesDirPath,
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"META-INF",
|
"META-INF",
|
||||||
"keycloak-themes.json"
|
"keycloak-themes.json"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readMetaInfKeycloakThemes(params: {
|
export function readMetaInfKeycloakThemes_fromResourcesDirPath(params: {
|
||||||
keycloakifyBuildDirPath: string;
|
resourcesDirPath: string;
|
||||||
}): MetaInfKeycloakTheme {
|
}) {
|
||||||
const { keycloakifyBuildDirPath } = params;
|
const { resourcesDirPath } = params;
|
||||||
|
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
fs
|
fs
|
||||||
.readFileSync(
|
.readFileSync(
|
||||||
getMetaInfKeycloakThemesJsonFilePath({
|
getMetaInfKeycloakThemesJsonFilePath({
|
||||||
keycloakifyBuildDirPath
|
resourcesDirPath
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.toString("utf8")
|
.toString("utf8")
|
||||||
) as MetaInfKeycloakTheme;
|
) as MetaInfKeycloakTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function readMetaInfKeycloakThemes_fromJar(params: {
|
||||||
|
jarFilePath: string;
|
||||||
|
}): Promise<MetaInfKeycloakTheme> {
|
||||||
|
const { jarFilePath } = params;
|
||||||
|
let metaInfKeycloakThemes: MetaInfKeycloakTheme | undefined = undefined;
|
||||||
|
|
||||||
|
await extractArchive({
|
||||||
|
archiveFilePath: jarFilePath,
|
||||||
|
onArchiveFile: async ({ relativeFilePathInArchive, readFile, earlyExit }) => {
|
||||||
|
if (
|
||||||
|
relativeFilePathInArchive ===
|
||||||
|
getMetaInfKeycloakThemesJsonFilePath({ resourcesDirPath: "." })
|
||||||
|
) {
|
||||||
|
metaInfKeycloakThemes = JSON.parse((await readFile()).toString("utf8"));
|
||||||
|
earlyExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(metaInfKeycloakThemes !== undefined);
|
||||||
|
|
||||||
|
return metaInfKeycloakThemes;
|
||||||
|
}
|
||||||
|
|
||||||
export function writeMetaInfKeycloakThemes(params: {
|
export function writeMetaInfKeycloakThemes(params: {
|
||||||
keycloakifyBuildDirPath: string;
|
resourcesDirPath: string;
|
||||||
metaInfKeycloakThemes: MetaInfKeycloakTheme;
|
metaInfKeycloakThemes: MetaInfKeycloakTheme;
|
||||||
}) {
|
}) {
|
||||||
const { keycloakifyBuildDirPath, metaInfKeycloakThemes } = params;
|
const { resourcesDirPath, metaInfKeycloakThemes } = params;
|
||||||
|
|
||||||
const metaInfKeycloakThemesJsonPath = getMetaInfKeycloakThemesJsonFilePath({
|
const metaInfKeycloakThemesJsonPath = getMetaInfKeycloakThemesJsonFilePath({
|
||||||
keycloakifyBuildDirPath
|
resourcesDirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -9,9 +9,10 @@ import { id } from "tsafe/id";
|
|||||||
|
|
||||||
export async function promptKeycloakVersion(params: {
|
export async function promptKeycloakVersion(params: {
|
||||||
startingFromMajor: number | undefined;
|
startingFromMajor: number | undefined;
|
||||||
|
excludeMajorVersions: number[];
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
}) {
|
}) {
|
||||||
const { startingFromMajor, cacheDirPath } = params;
|
const { startingFromMajor, excludeMajorVersions, cacheDirPath } = params;
|
||||||
|
|
||||||
const { getLatestsSemVersionedTag } = (() => {
|
const { getLatestsSemVersionedTag } = (() => {
|
||||||
const { octokit } = (() => {
|
const { octokit } = (() => {
|
||||||
@ -95,6 +96,10 @@ export async function promptKeycloakVersion(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (excludeMajorVersions.includes(semVersionedTag.version.major)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentSemVersionedTag = semVersionedTagByMajor.get(
|
const currentSemVersionedTag = semVersionedTagByMajor.get(
|
||||||
semVersionedTag.version.major
|
semVersionedTag.version.major
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { skipBuildJarsEnvName } from "../shared/constants";
|
import { onlyBuildJarFileBasenameEnvName } from "../shared/constants";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { Deferred } from "evt/tools/Deferred";
|
import { Deferred } from "evt/tools/Deferred";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
@ -14,10 +14,10 @@ export type BuildContextLike = {
|
|||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
export async function keycloakifyBuild(params: {
|
export async function keycloakifyBuild(params: {
|
||||||
doSkipBuildJars: boolean;
|
onlyBuildJarFileBasename: string | undefined;
|
||||||
buildContext: BuildContextLike;
|
buildContext: BuildContextLike;
|
||||||
}): Promise<{ isKeycloakifyBuildSuccess: boolean }> {
|
}): Promise<{ isKeycloakifyBuildSuccess: boolean }> {
|
||||||
const { buildContext, doSkipBuildJars } = params;
|
const { buildContext, onlyBuildJarFileBasename } = params;
|
||||||
|
|
||||||
const dResult = new Deferred<{ isSuccess: boolean }>();
|
const dResult = new Deferred<{ isSuccess: boolean }>();
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export async function keycloakifyBuild(params: {
|
|||||||
cwd: buildContext.projectDirPath,
|
cwd: buildContext.projectDirPath,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
...(doSkipBuildJars ? { [skipBuildJarsEnvName]: "true" } : {})
|
[onlyBuildJarFileBasenameEnvName]: onlyBuildJarFileBasename
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
2155
src/bin/start-keycloak/myrealm-realm-18.json
Normal file
2155
src/bin/start-keycloak/myrealm-realm-18.json
Normal file
File diff suppressed because it is too large
Load Diff
2186
src/bin/start-keycloak/myrealm-realm-19.json
Normal file
2186
src/bin/start-keycloak/myrealm-realm-19.json
Normal file
File diff suppressed because it is too large
Load Diff
2197
src/bin/start-keycloak/myrealm-realm-20.json
Normal file
2197
src/bin/start-keycloak/myrealm-realm-20.json
Normal file
File diff suppressed because it is too large
Load Diff
2201
src/bin/start-keycloak/myrealm-realm-21.json
Normal file
2201
src/bin/start-keycloak/myrealm-realm-21.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -587,7 +587,9 @@
|
|||||||
"publicClient": true,
|
"publicClient": true,
|
||||||
"frontchannelLogout": false,
|
"frontchannelLogout": false,
|
||||||
"protocol": "openid-connect",
|
"protocol": "openid-connect",
|
||||||
"attributes": {},
|
"attributes": {
|
||||||
|
"post.logout.redirect.uris": "+"
|
||||||
|
},
|
||||||
"authenticationFlowBindingOverrides": {},
|
"authenticationFlowBindingOverrides": {},
|
||||||
"fullScopeAllowed": false,
|
"fullScopeAllowed": false,
|
||||||
"nodeReRegistrationTimeout": 0,
|
"nodeReRegistrationTimeout": 0,
|
||||||
@ -619,7 +621,9 @@
|
|||||||
"publicClient": false,
|
"publicClient": false,
|
||||||
"frontchannelLogout": false,
|
"frontchannelLogout": false,
|
||||||
"protocol": "openid-connect",
|
"protocol": "openid-connect",
|
||||||
"attributes": {},
|
"attributes": {
|
||||||
|
"post.logout.redirect.uris": "+"
|
||||||
|
},
|
||||||
"authenticationFlowBindingOverrides": {},
|
"authenticationFlowBindingOverrides": {},
|
||||||
"fullScopeAllowed": false,
|
"fullScopeAllowed": false,
|
||||||
"nodeReRegistrationTimeout": 0,
|
"nodeReRegistrationTimeout": 0,
|
||||||
@ -695,7 +699,9 @@
|
|||||||
"publicClient": false,
|
"publicClient": false,
|
||||||
"frontchannelLogout": false,
|
"frontchannelLogout": false,
|
||||||
"protocol": "openid-connect",
|
"protocol": "openid-connect",
|
||||||
"attributes": {},
|
"attributes": {
|
||||||
|
"post.logout.redirect.uris": "+"
|
||||||
|
},
|
||||||
"authenticationFlowBindingOverrides": {},
|
"authenticationFlowBindingOverrides": {},
|
||||||
"fullScopeAllowed": false,
|
"fullScopeAllowed": false,
|
||||||
"nodeReRegistrationTimeout": 0,
|
"nodeReRegistrationTimeout": 0,
|
||||||
@ -783,6 +789,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"introspection.token.claim": "true",
|
"introspection.token.claim": "true",
|
||||||
"multivalued": "true",
|
"multivalued": "true",
|
||||||
|
"userinfo.token.claim": "true",
|
||||||
"user.attribute": "foo",
|
"user.attribute": "foo",
|
||||||
"id.token.claim": "true",
|
"id.token.claim": "true",
|
||||||
"access.token.claim": "true",
|
"access.token.claim": "true",
|
||||||
@ -827,7 +834,8 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"id.token.claim": "true",
|
"id.token.claim": "true",
|
||||||
"introspection.token.claim": "true",
|
"introspection.token.claim": "true",
|
||||||
"access.token.claim": "true"
|
"access.token.claim": "true",
|
||||||
|
"userinfo.token.claim": "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1348,10 +1356,10 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"allowed-protocol-mapper-types": [
|
"allowed-protocol-mapper-types": [
|
||||||
"oidc-sha256-pairwise-sub-mapper",
|
"oidc-sha256-pairwise-sub-mapper",
|
||||||
|
"oidc-address-mapper",
|
||||||
|
"saml-user-property-mapper",
|
||||||
"oidc-full-name-mapper",
|
"oidc-full-name-mapper",
|
||||||
"oidc-usermodel-attribute-mapper",
|
"oidc-usermodel-attribute-mapper",
|
||||||
"saml-user-property-mapper",
|
|
||||||
"oidc-address-mapper",
|
|
||||||
"saml-role-list-mapper",
|
"saml-role-list-mapper",
|
||||||
"saml-user-attribute-mapper",
|
"saml-user-attribute-mapper",
|
||||||
"oidc-usermodel-property-mapper"
|
"oidc-usermodel-property-mapper"
|
||||||
@ -1423,13 +1431,13 @@
|
|||||||
"subComponents": {},
|
"subComponents": {},
|
||||||
"config": {
|
"config": {
|
||||||
"allowed-protocol-mapper-types": [
|
"allowed-protocol-mapper-types": [
|
||||||
|
"saml-user-property-mapper",
|
||||||
|
"oidc-full-name-mapper",
|
||||||
"saml-user-attribute-mapper",
|
"saml-user-attribute-mapper",
|
||||||
"oidc-sha256-pairwise-sub-mapper",
|
"oidc-sha256-pairwise-sub-mapper",
|
||||||
"oidc-usermodel-attribute-mapper",
|
|
||||||
"oidc-address-mapper",
|
|
||||||
"saml-role-list-mapper",
|
"saml-role-list-mapper",
|
||||||
"oidc-full-name-mapper",
|
"oidc-address-mapper",
|
||||||
"saml-user-property-mapper",
|
"oidc-usermodel-attribute-mapper",
|
||||||
"oidc-usermodel-property-mapper"
|
"oidc-usermodel-property-mapper"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2043,7 +2051,7 @@
|
|||||||
"name": "Terms and Conditions",
|
"name": "Terms and Conditions",
|
||||||
"providerId": "TERMS_AND_CONDITIONS",
|
"providerId": "TERMS_AND_CONDITIONS",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"defaultAction": false,
|
"defaultAction": true,
|
||||||
"priority": 20,
|
"priority": 20,
|
||||||
"config": {}
|
"config": {}
|
||||||
},
|
},
|
||||||
@ -2122,8 +2130,8 @@
|
|||||||
"cibaExpiresIn": "120",
|
"cibaExpiresIn": "120",
|
||||||
"cibaAuthRequestedUserHint": "login_hint",
|
"cibaAuthRequestedUserHint": "login_hint",
|
||||||
"oauth2DeviceCodeLifespan": "600",
|
"oauth2DeviceCodeLifespan": "600",
|
||||||
"oauth2DevicePollingInterval": "5",
|
|
||||||
"clientOfflineSessionMaxLifespan": "0",
|
"clientOfflineSessionMaxLifespan": "0",
|
||||||
|
"oauth2DevicePollingInterval": "5",
|
||||||
"clientSessionIdleTimeout": "0",
|
"clientSessionIdleTimeout": "0",
|
||||||
"parRequestUriLifespan": "60",
|
"parRequestUriLifespan": "60",
|
||||||
"clientSessionMaxLifespan": "0",
|
"clientSessionMaxLifespan": "0",
|
||||||
|
@ -1501,14 +1501,14 @@
|
|||||||
"subComponents": {},
|
"subComponents": {},
|
||||||
"config": {
|
"config": {
|
||||||
"allowed-protocol-mapper-types": [
|
"allowed-protocol-mapper-types": [
|
||||||
"oidc-sha256-pairwise-sub-mapper",
|
|
||||||
"saml-role-list-mapper",
|
"saml-role-list-mapper",
|
||||||
"oidc-address-mapper",
|
"oidc-address-mapper",
|
||||||
"oidc-usermodel-property-mapper",
|
"oidc-usermodel-property-mapper",
|
||||||
|
"oidc-sha256-pairwise-sub-mapper",
|
||||||
"saml-user-attribute-mapper",
|
"saml-user-attribute-mapper",
|
||||||
|
"oidc-usermodel-attribute-mapper",
|
||||||
"oidc-full-name-mapper",
|
"oidc-full-name-mapper",
|
||||||
"saml-user-property-mapper",
|
"saml-user-property-mapper"
|
||||||
"oidc-usermodel-attribute-mapper"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1541,13 +1541,13 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"allowed-protocol-mapper-types": [
|
"allowed-protocol-mapper-types": [
|
||||||
"oidc-sha256-pairwise-sub-mapper",
|
"oidc-sha256-pairwise-sub-mapper",
|
||||||
"saml-user-property-mapper",
|
|
||||||
"oidc-usermodel-attribute-mapper",
|
|
||||||
"oidc-address-mapper",
|
|
||||||
"oidc-usermodel-property-mapper",
|
"oidc-usermodel-property-mapper",
|
||||||
|
"oidc-full-name-mapper",
|
||||||
|
"oidc-address-mapper",
|
||||||
|
"oidc-usermodel-attribute-mapper",
|
||||||
|
"saml-user-property-mapper",
|
||||||
"saml-role-list-mapper",
|
"saml-role-list-mapper",
|
||||||
"saml-user-attribute-mapper",
|
"saml-user-attribute-mapper"
|
||||||
"oidc-full-name-mapper"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2200,7 +2200,7 @@
|
|||||||
"name": "Terms and Conditions",
|
"name": "Terms and Conditions",
|
||||||
"providerId": "TERMS_AND_CONDITIONS",
|
"providerId": "TERMS_AND_CONDITIONS",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"defaultAction": false,
|
"defaultAction": true,
|
||||||
"priority": 20,
|
"priority": 20,
|
||||||
"config": {}
|
"config": {}
|
||||||
},
|
},
|
||||||
@ -2307,7 +2307,7 @@
|
|||||||
"cibaInterval": "5",
|
"cibaInterval": "5",
|
||||||
"realmReusableOtpCode": "false"
|
"realmReusableOtpCode": "false"
|
||||||
},
|
},
|
||||||
"keycloakVersion": "24.0.4",
|
"keycloakVersion": "24.0.5",
|
||||||
"userManagedAccessAllowed": false,
|
"userManagedAccessAllowed": false,
|
||||||
"clientProfiles": {
|
"clientProfiles": {
|
||||||
"profiles": []
|
"profiles": []
|
||||||
|
2400
src/bin/start-keycloak/myrealm-realm-25.json
Normal file
2400
src/bin/start-keycloak/myrealm-realm-25.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@ import { getBuildContext } from "../shared/buildContext";
|
|||||||
import { exclude } from "tsafe/exclude";
|
import { exclude } from "tsafe/exclude";
|
||||||
import type { CliCommandOptions as CliCommandOptions_common } from "../main";
|
import type { CliCommandOptions as CliCommandOptions_common } from "../main";
|
||||||
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
|
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
|
||||||
import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes";
|
import { readMetaInfKeycloakThemes_fromJar } from "../shared/metaInfKeycloakThemes";
|
||||||
import { accountV1ThemeName, containerName } from "../shared/constants";
|
import { accountV1ThemeName, containerName } from "../shared/constants";
|
||||||
import { SemVer } from "../tools/SemVer";
|
import { SemVer } from "../tools/SemVer";
|
||||||
import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange";
|
import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange";
|
||||||
@ -21,6 +21,9 @@ import * as runExclusive from "run-exclusive";
|
|||||||
import { extractArchive } from "../tools/extractArchive";
|
import { extractArchive } from "../tools/extractArchive";
|
||||||
import { appBuild } from "./appBuild";
|
import { appBuild } from "./appBuild";
|
||||||
import { keycloakifyBuild } from "./keycloakifyBuild";
|
import { keycloakifyBuild } from "./keycloakifyBuild";
|
||||||
|
import { isInside } from "../tools/isInside";
|
||||||
|
import { existsAsync } from "../tools/fs.existsAsync";
|
||||||
|
import { rm } from "../tools/fs.rm";
|
||||||
|
|
||||||
export type CliCommandOptions = CliCommandOptions_common & {
|
export type CliCommandOptions = CliCommandOptions_common & {
|
||||||
port: number;
|
port: number;
|
||||||
@ -98,7 +101,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
|
const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
|
||||||
doSkipBuildJars: false,
|
onlyBuildJarFileBasename: undefined,
|
||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -112,13 +115,31 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metaInfKeycloakThemes = readMetaInfKeycloakThemes({
|
const { doesImplementAccountTheme } = await (async () => {
|
||||||
keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath
|
const latestJarFilePath = fs
|
||||||
});
|
.readdirSync(buildContext.keycloakifyBuildDirPath)
|
||||||
|
.filter(fileBasename => fileBasename.endsWith(".jar"))
|
||||||
|
.map(fileBasename =>
|
||||||
|
pathJoin(buildContext.keycloakifyBuildDirPath, fileBasename)
|
||||||
|
)
|
||||||
|
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0];
|
||||||
|
|
||||||
const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(
|
assert(latestJarFilePath !== undefined);
|
||||||
({ name }) => name === accountV1ThemeName
|
|
||||||
);
|
const metaInfKeycloakThemes = await readMetaInfKeycloakThemes_fromJar({
|
||||||
|
jarFilePath: latestJarFilePath
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainThemeEntry = metaInfKeycloakThemes.themes.find(
|
||||||
|
({ name }) => name === buildContext.themeNames[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(mainThemeEntry !== undefined);
|
||||||
|
|
||||||
|
const doesImplementAccountTheme = mainThemeEntry.types.includes("account");
|
||||||
|
|
||||||
|
return { doesImplementAccountTheme };
|
||||||
|
})();
|
||||||
|
|
||||||
const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } =
|
const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } =
|
||||||
await (async function getKeycloakMajor(): Promise<{
|
await (async function getKeycloakMajor(): Promise<{
|
||||||
@ -138,7 +159,8 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { keycloakVersion } = await promptKeycloakVersion({
|
const { keycloakVersion } = await promptKeycloakVersion({
|
||||||
startingFromMajor: 17,
|
startingFromMajor: 18,
|
||||||
|
excludeMajorVersions: [22],
|
||||||
cacheDirPath: buildContext.cacheDirPath
|
cacheDirPath: buildContext.cacheDirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -172,7 +194,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
return "23" as const;
|
return "23" as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "24-and-above" as const;
|
if (keycloakMajorVersionNumber === 24) {
|
||||||
|
return "24" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "25-and-above" as const;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
assert<
|
assert<
|
||||||
@ -206,6 +232,10 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
|
|
||||||
const realmJsonFilePath = await (async () => {
|
const realmJsonFilePath = await (async () => {
|
||||||
if (cliCommandOptions.realmJsonFilePath !== undefined) {
|
if (cliCommandOptions.realmJsonFilePath !== undefined) {
|
||||||
|
if (cliCommandOptions.realmJsonFilePath === "none") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
chalk.green(
|
chalk.green(
|
||||||
`Using realm json file: ${cliCommandOptions.realmJsonFilePath}`
|
`Using realm json file: ${cliCommandOptions.realmJsonFilePath}`
|
||||||
@ -262,65 +292,34 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
|
|
||||||
const jarFilePath = pathJoin(buildContext.keycloakifyBuildDirPath, jarFileBasename);
|
const jarFilePath = pathJoin(buildContext.keycloakifyBuildDirPath, jarFileBasename);
|
||||||
|
|
||||||
const { doUseBuiltInAccountV1Theme } = await (async () => {
|
async function extractThemeResourcesFromJar() {
|
||||||
let doUseBuiltInAccountV1Theme = false;
|
|
||||||
|
|
||||||
await extractArchive({
|
await extractArchive({
|
||||||
archiveFilePath: jarFilePath,
|
archiveFilePath: jarFilePath,
|
||||||
onArchiveFile: async ({ relativeFilePathInArchive, readFile, earlyExit }) => {
|
onArchiveFile: async ({ relativeFilePathInArchive, writeFile }) => {
|
||||||
for (const themeName of buildContext.themeNames) {
|
if (isInside({ dirPath: "theme", filePath: relativeFilePathInArchive })) {
|
||||||
if (
|
await writeFile({
|
||||||
relativeFilePathInArchive ===
|
filePath: pathJoin(
|
||||||
pathJoin("theme", themeName, "account", "theme.properties")
|
buildContext.keycloakifyBuildDirPath,
|
||||||
) {
|
relativeFilePathInArchive
|
||||||
if (
|
)
|
||||||
(await readFile())
|
});
|
||||||
.toString("utf8")
|
|
||||||
.includes("parent=keycloak")
|
|
||||||
) {
|
|
||||||
doUseBuiltInAccountV1Theme = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
earlyExit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return { doUseBuiltInAccountV1Theme };
|
{
|
||||||
})();
|
const destDirPath = pathJoin(buildContext.keycloakifyBuildDirPath, "theme");
|
||||||
|
if (await existsAsync(destDirPath)) {
|
||||||
|
await rm(destDirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const accountThemePropertyPatch = !doUseBuiltInAccountV1Theme
|
await extractThemeResourcesFromJar();
|
||||||
? undefined
|
|
||||||
: () => {
|
|
||||||
for (const themeName of buildContext.themeNames) {
|
|
||||||
const filePath = pathJoin(
|
|
||||||
buildContext.keycloakifyBuildDirPath,
|
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"theme",
|
|
||||||
themeName,
|
|
||||||
"account",
|
|
||||||
"theme.properties"
|
|
||||||
);
|
|
||||||
|
|
||||||
const sourceCode = fs.readFileSync(filePath);
|
const jarFilePath_cacheDir = pathJoin(buildContext.cacheDirPath, jarFileBasename);
|
||||||
|
|
||||||
const modifiedSourceCode = Buffer.from(
|
fs.copyFileSync(jarFilePath, jarFilePath_cacheDir);
|
||||||
sourceCode
|
|
||||||
.toString("utf8")
|
|
||||||
.replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
|
|
||||||
"utf8"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(Buffer.compare(modifiedSourceCode, sourceCode) !== 0);
|
|
||||||
|
|
||||||
fs.writeFileSync(filePath, modifiedSourceCode);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
accountThemePropertyPatch?.();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
child_process.execSync(`docker rm --force ${containerName}`, {
|
child_process.execSync(`docker rm --force ${containerName}`, {
|
||||||
@ -342,20 +341,28 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
"-v",
|
"-v",
|
||||||
`${realmJsonFilePath}:/opt/keycloak/data/import/myrealm-realm.json`
|
`${realmJsonFilePath}:/opt/keycloak/data/import/myrealm-realm.json`
|
||||||
]),
|
]),
|
||||||
...["-v", `${jarFilePath}:/opt/keycloak/providers/keycloak-theme.jar`],
|
...[
|
||||||
|
"-v",
|
||||||
|
`${jarFilePath_cacheDir}:/opt/keycloak/providers/keycloak-theme.jar`
|
||||||
|
],
|
||||||
...(keycloakMajorVersionNumber <= 20
|
...(keycloakMajorVersionNumber <= 20
|
||||||
? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"]
|
? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"]
|
||||||
: []),
|
: []),
|
||||||
...[
|
...[
|
||||||
...buildContext.themeNames,
|
...buildContext.themeNames,
|
||||||
...(doUseBuiltInAccountV1Theme ? [] : [accountV1ThemeName])
|
...(fs.existsSync(
|
||||||
|
pathJoin(
|
||||||
|
buildContext.keycloakifyBuildDirPath,
|
||||||
|
"theme",
|
||||||
|
accountV1ThemeName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
? [accountV1ThemeName]
|
||||||
|
: [])
|
||||||
]
|
]
|
||||||
.map(themeName => ({
|
.map(themeName => ({
|
||||||
localDirPath: pathJoin(
|
localDirPath: pathJoin(
|
||||||
buildContext.keycloakifyBuildDirPath,
|
buildContext.keycloakifyBuildDirPath,
|
||||||
"src",
|
|
||||||
"main",
|
|
||||||
"resources",
|
|
||||||
"theme",
|
"theme",
|
||||||
themeName
|
themeName
|
||||||
),
|
),
|
||||||
@ -451,7 +458,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
|
const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
|
||||||
doSkipBuildJars: true,
|
onlyBuildJarFileBasename: jarFileBasename,
|
||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -459,7 +466,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
accountThemePropertyPatch?.();
|
await extractThemeResourcesFromJar();
|
||||||
|
|
||||||
console.log(chalk.green("Theme rebuilt and updated in Keycloak."));
|
console.log(chalk.green("Theme rebuilt and updated in Keycloak."));
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@ import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
|||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { I18n } from "keycloakify/login/i18n";
|
import type { I18n } from "keycloakify/login/i18n";
|
||||||
import type { KcContext } from "keycloakify/login/KcContext";
|
import type { KcContext } from "keycloakify/login/KcContext";
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
||||||
|
|
||||||
const Login = lazy(() => import("keycloakify/login/pages/Login"));
|
const Login = lazy(() => import("keycloakify/login/pages/Login"));
|
||||||
const Register = lazy(() => import("keycloakify/login/pages/Register"));
|
const Register = lazy(() => import("keycloakify/login/pages/Register"));
|
||||||
|
@ -209,17 +209,13 @@ export declare namespace KcContext {
|
|||||||
export type Register = Common & {
|
export type Register = Common & {
|
||||||
pageId: "register.ftl";
|
pageId: "register.ftl";
|
||||||
profile: UserProfile;
|
profile: UserProfile;
|
||||||
|
passwordPolicies?: PasswordPolicies;
|
||||||
url: {
|
url: {
|
||||||
registrationAction: string;
|
registrationAction: string;
|
||||||
};
|
};
|
||||||
passwordRequired: boolean;
|
passwordRequired: boolean;
|
||||||
recaptchaRequired: boolean;
|
recaptchaRequired: boolean;
|
||||||
recaptchaSiteKey?: string;
|
recaptchaSiteKey?: string;
|
||||||
/**
|
|
||||||
* Theses values are added by: https://github.com/jcputney/keycloak-theme-additional-info-extension
|
|
||||||
* A Keycloak Java extension used as dependency in Keycloakify.
|
|
||||||
*/
|
|
||||||
passwordPolicies?: PasswordPolicies;
|
|
||||||
termsAcceptanceRequired?: boolean;
|
termsAcceptanceRequired?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -479,16 +475,19 @@ export declare namespace KcContext {
|
|||||||
export type LoginUpdateProfile = Common & {
|
export type LoginUpdateProfile = Common & {
|
||||||
pageId: "login-update-profile.ftl";
|
pageId: "login-update-profile.ftl";
|
||||||
profile: UserProfile;
|
profile: UserProfile;
|
||||||
|
passwordPolicies?: PasswordPolicies;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IdpReviewUserProfile = Common & {
|
export type IdpReviewUserProfile = Common & {
|
||||||
pageId: "idp-review-user-profile.ftl";
|
pageId: "idp-review-user-profile.ftl";
|
||||||
profile: UserProfile;
|
profile: UserProfile;
|
||||||
|
passwordPolicies?: PasswordPolicies;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateEmail = Common & {
|
export type UpdateEmail = Common & {
|
||||||
pageId: "update-email.ftl";
|
pageId: "update-email.ftl";
|
||||||
profile: UserProfile;
|
profile: UserProfile;
|
||||||
|
passwordPolicies?: PasswordPolicies;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SelectAuthenticator = Common & {
|
export type SelectAuthenticator = Common & {
|
||||||
@ -752,6 +751,10 @@ export declare namespace Validators {
|
|||||||
assert<Equals<OnlyInExpected, never>>();
|
assert<Equals<OnlyInExpected, never>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theses values are added by: https://github.com/jcputney/keycloak-theme-additional-info-extension
|
||||||
|
* A Keycloak Java extension used as dependency in Keycloakify.
|
||||||
|
*/
|
||||||
export type PasswordPolicies = {
|
export type PasswordPolicies = {
|
||||||
/** The minimum length of the password */
|
/** The minimum length of the password */
|
||||||
length?: number;
|
length?: number;
|
||||||
|
@ -4,33 +4,15 @@ import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
|||||||
import {
|
import {
|
||||||
useUserProfileForm,
|
useUserProfileForm,
|
||||||
getButtonToDisplayForMultivaluedAttributeField,
|
getButtonToDisplayForMultivaluedAttributeField,
|
||||||
type KcContextLike,
|
|
||||||
type FormAction,
|
type FormAction,
|
||||||
type FormFieldError
|
type FormFieldError
|
||||||
} from "keycloakify/login/lib/useUserProfileForm";
|
} from "keycloakify/login/lib/useUserProfileForm";
|
||||||
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
||||||
import type { Attribute } from "keycloakify/login/KcContext";
|
import type { Attribute } from "keycloakify/login/KcContext";
|
||||||
|
import type { KcContext } from "./KcContext";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
|
|
||||||
export type UserProfileFormFieldsProps = {
|
export default function UserProfileFormFields(props: UserProfileFormFieldsProps<KcContext, I18n>) {
|
||||||
kcContext: KcContextLike;
|
|
||||||
i18n: I18n;
|
|
||||||
kcClsx: KcClsx;
|
|
||||||
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
|
||||||
doMakeUserConfirmPassword: boolean;
|
|
||||||
BeforeField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
|
|
||||||
AfterField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type BeforeAfterFieldProps = {
|
|
||||||
attribute: Attribute;
|
|
||||||
dispatchFormAction: React.Dispatch<FormAction>;
|
|
||||||
displayableErrors: FormFieldError[];
|
|
||||||
valueOrValues: string | string[];
|
|
||||||
kcClsx: KcClsx;
|
|
||||||
i18n: I18n;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|
||||||
const { kcContext, i18n, kcClsx, onIsFormSubmittableValueChange, doMakeUserConfirmPassword, BeforeField, AfterField } = props;
|
const { kcContext, i18n, kcClsx, onIsFormSubmittableValueChange, doMakeUserConfirmPassword, BeforeField, AfterField } = props;
|
||||||
|
|
||||||
const { advancedMsg } = i18n;
|
const { advancedMsg } = i18n;
|
||||||
|
22
src/login/UserProfileFormFieldsProps.tsx
Normal file
22
src/login/UserProfileFormFieldsProps.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm";
|
||||||
|
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||||
|
import type { Attribute } from "keycloakify/login/KcContext";
|
||||||
|
|
||||||
|
export type UserProfileFormFieldsProps<KcContext = any, I18n = any> = {
|
||||||
|
kcContext: Extract<KcContext, { profile: unknown }>;
|
||||||
|
i18n: I18n;
|
||||||
|
kcClsx: KcClsx;
|
||||||
|
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
||||||
|
doMakeUserConfirmPassword: boolean;
|
||||||
|
BeforeField?: (props: BeforeAfterFieldProps<I18n>) => JSX.Element | null;
|
||||||
|
AfterField?: (props: BeforeAfterFieldProps<I18n>) => JSX.Element | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BeforeAfterFieldProps<I18n> = {
|
||||||
|
attribute: Attribute;
|
||||||
|
dispatchFormAction: React.Dispatch<FormAction>;
|
||||||
|
displayableErrors: FormFieldError[];
|
||||||
|
valueOrValues: string | string[];
|
||||||
|
kcClsx: KcClsx;
|
||||||
|
i18n: I18n;
|
||||||
|
};
|
@ -79,9 +79,9 @@ export type KcContextLike = KcContextLike_i18n &
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<Extract<KcContext.Register, { pageId: "register.ftl" }> extends KcContextLike ? true : false>();
|
assert<Extract<Extract<KcContext, { profile: unknown }>, { pageId: "register.ftl" }> extends KcContextLike ? true : false>();
|
||||||
|
|
||||||
export type ParamsOfUseUserProfileForm = {
|
export type UseUserProfileFormParams = {
|
||||||
kcContext: KcContextLike;
|
kcContext: KcContextLike;
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
doMakeUserConfirmPassword: boolean;
|
doMakeUserConfirmPassword: boolean;
|
||||||
@ -105,7 +105,7 @@ namespace internal {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
|
export function useUserProfileForm(params: UseUserProfileFormParams): ReturnTypeOfUseUserProfileForm {
|
||||||
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
|
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
|
||||||
|
|
||||||
const { insertScriptTags } = useInsertScriptTags({
|
const { insertScriptTags } = useInsertScriptTags({
|
||||||
@ -130,168 +130,137 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
const initialState = useMemo((): internal.State => {
|
const initialState = useMemo((): internal.State => {
|
||||||
// NOTE: We don't use te kcContext.profile.attributes directly because
|
// NOTE: We don't use te kcContext.profile.attributes directly because
|
||||||
// they don't includes the password and password confirm fields and we want to add them.
|
// they don't includes the password and password confirm fields and we want to add them.
|
||||||
// Also, we want to polyfill the attributes for older Keycloak version before User Profile was introduced.
|
// We also want to apply some retro-compatibility and consistency patches.
|
||||||
// Finally we want to patch the changes made by Keycloak on the attributes format so we have an homogeneous
|
const attributes: Attribute[] = (() => {
|
||||||
// attributes format to work with.
|
mock_user_profile_attributes_for_older_keycloak_versions: {
|
||||||
const syntheticAttributes = (() => {
|
if (
|
||||||
const syntheticAttributes: Attribute[] = [];
|
"profile" in kcContext &&
|
||||||
|
"attributesByName" in kcContext.profile &&
|
||||||
|
Object.keys(kcContext.profile.attributesByName).length !== 0
|
||||||
|
) {
|
||||||
|
break mock_user_profile_attributes_for_older_keycloak_versions;
|
||||||
|
}
|
||||||
|
|
||||||
const attributes = (() => {
|
if ("register" in kcContext && kcContext.register instanceof Object && "formData" in kcContext.register) {
|
||||||
retrocompat_patch: {
|
//NOTE: Handle legacy register.ftl page
|
||||||
if (
|
return (["firstName", "lastName", "email", "username"] as const)
|
||||||
"profile" in kcContext &&
|
.filter(name => (name !== "username" ? true : !kcContext.realm.registrationEmailAsUsername))
|
||||||
"attributesByName" in kcContext.profile &&
|
.map(name =>
|
||||||
Object.keys(kcContext.profile.attributesByName).length !== 0
|
|
||||||
) {
|
|
||||||
break retrocompat_patch;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("register" in kcContext && kcContext.register instanceof Object && "formData" in kcContext.register) {
|
|
||||||
//NOTE: Handle legacy register.ftl page
|
|
||||||
return (["firstName", "lastName", "email", "username"] as const)
|
|
||||||
.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: (() => {
|
|
||||||
switch (name) {
|
|
||||||
case "email":
|
|
||||||
return "email";
|
|
||||||
case "username":
|
|
||||||
return "username";
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("user" in kcContext && kcContext.user instanceof Object) {
|
|
||||||
//NOTE: Handle legacy login-update-profile.ftl
|
|
||||||
return (["username", "email", "firstName", "lastName"] as const)
|
|
||||||
.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: (() => {
|
|
||||||
switch (name) {
|
|
||||||
case "email":
|
|
||||||
return "email";
|
|
||||||
case "username":
|
|
||||||
return "username";
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("email" in kcContext && kcContext.email instanceof Object) {
|
|
||||||
//NOTE: Handle legacy update-email.ftl
|
|
||||||
return [
|
|
||||||
id<Attribute>({
|
id<Attribute>({
|
||||||
name: "email",
|
name: name,
|
||||||
displayName: id<`\${${MessageKey}}`>(`\${email}`),
|
displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
|
||||||
required: true,
|
required: true,
|
||||||
value: (kcContext.email as any).value ?? "",
|
value: (kcContext.register as any).formData[name] ?? "",
|
||||||
html5DataAnnotations: {},
|
html5DataAnnotations: {},
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
validators: {},
|
validators: {},
|
||||||
annotations: {},
|
annotations: {},
|
||||||
autocomplete: "email"
|
autocomplete: (() => {
|
||||||
|
switch (name) {
|
||||||
|
case "email":
|
||||||
|
return "email";
|
||||||
|
case "username":
|
||||||
|
return "username";
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
})()
|
||||||
})
|
})
|
||||||
];
|
);
|
||||||
}
|
|
||||||
|
|
||||||
assert(false, "Unable to mock user profile from the current kcContext");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(kcContext.profile.attributesByName).map(attribute_pre_group_patch => {
|
if ("user" in kcContext && kcContext.user instanceof Object) {
|
||||||
if (typeof attribute_pre_group_patch.group === "string" && attribute_pre_group_patch.group !== "") {
|
//NOTE: Handle legacy login-update-profile.ftl
|
||||||
const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } =
|
return (["username", "email", "firstName", "lastName"] as const)
|
||||||
attribute_pre_group_patch as Attribute & {
|
.filter(name => (name !== "username" ? true : (kcContext.user as any).editUsernameAllowed))
|
||||||
group: string;
|
.map(name =>
|
||||||
groupDisplayHeader?: string;
|
id<Attribute>({
|
||||||
groupDisplayDescription?: string;
|
name: name,
|
||||||
groupAnnotations: Record<string, string>;
|
displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
|
||||||
};
|
required: true,
|
||||||
|
value: (kcContext as any).user[name] ?? "",
|
||||||
|
html5DataAnnotations: {},
|
||||||
|
readOnly: false,
|
||||||
|
validators: {},
|
||||||
|
annotations: {},
|
||||||
|
autocomplete: (() => {
|
||||||
|
switch (name) {
|
||||||
|
case "email":
|
||||||
|
return "email";
|
||||||
|
case "username":
|
||||||
|
return "username";
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return id<Attribute>({
|
if ("email" in kcContext && kcContext.email instanceof Object) {
|
||||||
...rest,
|
//NOTE: Handle legacy update-email.ftl
|
||||||
group: {
|
return [
|
||||||
name: group,
|
id<Attribute>({
|
||||||
displayHeader: groupDisplayHeader,
|
name: "email",
|
||||||
displayDescription: groupDisplayDescription,
|
displayName: id<`\${${MessageKey}}`>(`\${email}`),
|
||||||
html5DataAnnotations: {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return attribute_pre_group_patch;
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
for (const attribute of attributes) {
|
|
||||||
syntheticAttributes.push(structuredCloneButFunctions(attribute));
|
|
||||||
|
|
||||||
add_password_and_password_confirm: {
|
|
||||||
if (!kcContext.passwordRequired) {
|
|
||||||
break add_password_and_password_confirm;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
|
|
||||||
// NOTE: We want to add password and password-confirm after the field that identifies the user.
|
|
||||||
// It's either email or username.
|
|
||||||
break add_password_and_password_confirm;
|
|
||||||
}
|
|
||||||
|
|
||||||
syntheticAttributes.push(
|
|
||||||
{
|
|
||||||
name: "password",
|
|
||||||
displayName: id<`\${${MessageKey}}`>("${password}"),
|
|
||||||
required: true,
|
required: true,
|
||||||
|
value: (kcContext.email as any).value ?? "",
|
||||||
|
html5DataAnnotations: {},
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
validators: {},
|
validators: {},
|
||||||
annotations: {},
|
annotations: {},
|
||||||
autocomplete: "new-password",
|
autocomplete: "email"
|
||||||
html5DataAnnotations: {},
|
})
|
||||||
// NOTE: Compat with Keycloak version prior to 24
|
];
|
||||||
...({ groupAnnotations: {} } as {})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 {})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(false, "Unable to mock user profile from the current kcContext");
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Consistency patch
|
return Object.values(kcContext.profile.attributesByName).map(structuredCloneButFunctions);
|
||||||
syntheticAttributes.forEach(attribute => {
|
})();
|
||||||
|
|
||||||
|
// Retro-compatibility and consistency patches
|
||||||
|
attributes.forEach(attribute => {
|
||||||
|
patch_legacy_group: {
|
||||||
|
if (typeof attribute.group !== "string") {
|
||||||
|
break patch_legacy_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { group, groupDisplayHeader, groupDisplayDescription /*, groupAnnotations*/ } = attribute as Attribute & {
|
||||||
|
group: string;
|
||||||
|
groupDisplayHeader?: string;
|
||||||
|
groupDisplayDescription?: string;
|
||||||
|
groupAnnotations: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
delete attribute.group;
|
||||||
|
// @ts-expect-error
|
||||||
|
delete attribute.groupDisplayHeader;
|
||||||
|
// @ts-expect-error
|
||||||
|
delete attribute.groupDisplayDescription;
|
||||||
|
// @ts-expect-error
|
||||||
|
delete attribute.groupAnnotations;
|
||||||
|
|
||||||
|
if (group === "") {
|
||||||
|
break patch_legacy_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute.group = {
|
||||||
|
name: group,
|
||||||
|
displayHeader: groupDisplayHeader,
|
||||||
|
displayDescription: groupDisplayDescription,
|
||||||
|
html5DataAnnotations: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes with options rendered by default as select inputs
|
||||||
|
if (attribute.validators.options !== undefined && attribute.annotations.inputType === undefined) {
|
||||||
|
attribute.annotations.inputType = "select";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consistency patch on values/value property
|
||||||
|
{
|
||||||
if (getIsMultivaluedSingleField({ attribute })) {
|
if (getIsMultivaluedSingleField({ attribute })) {
|
||||||
attribute.multivalued = true;
|
attribute.multivalued = true;
|
||||||
}
|
}
|
||||||
@ -303,65 +272,98 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
attribute.value ??= attribute.values?.[0];
|
attribute.value ??= attribute.values?.[0];
|
||||||
delete attribute.values;
|
delete attribute.values;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return syntheticAttributes;
|
add_password_and_password_confirm: {
|
||||||
})();
|
if (!kcContext.passwordRequired) {
|
||||||
|
break add_password_and_password_confirm;
|
||||||
const initialFormFieldState = (() => {
|
|
||||||
const out: {
|
|
||||||
attribute: Attribute;
|
|
||||||
valueOrValues: string | string[];
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
for (const attribute of syntheticAttributes) {
|
|
||||||
handle_multi_valued_attribute: {
|
|
||||||
if (!attribute.multivalued) {
|
|
||||||
break handle_multi_valued_attribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
const values = attribute.values?.length ? attribute.values : [""];
|
|
||||||
|
|
||||||
apply_validator_min_range: {
|
|
||||||
if (getIsMultivaluedSingleField({ attribute })) {
|
|
||||||
break apply_validator_min_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validator = attribute.validators.multivalued;
|
|
||||||
|
|
||||||
if (validator === undefined) {
|
|
||||||
break apply_validator_min_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { min: minStr } = validator;
|
|
||||||
|
|
||||||
if (!minStr) {
|
|
||||||
break apply_validator_min_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
const min = parseInt(`${minStr}`);
|
|
||||||
|
|
||||||
for (let index = values.length; index < min; index++) {
|
|
||||||
values.push("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push({
|
|
||||||
attribute,
|
|
||||||
valueOrValues: values
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push({
|
|
||||||
attribute,
|
|
||||||
valueOrValues: attribute.value ?? ""
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
attributes.forEach((attribute, i) => {
|
||||||
})();
|
if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
|
||||||
|
// NOTE: We want to add password and password-confirm after the field that identifies the user.
|
||||||
|
// It's either email or username.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes.splice(
|
||||||
|
i + 1,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
name: "password",
|
||||||
|
displayName: id<`\${${MessageKey}}`>("${password}"),
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
validators: {},
|
||||||
|
annotations: {},
|
||||||
|
autocomplete: "new-password",
|
||||||
|
html5DataAnnotations: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password-confirm",
|
||||||
|
displayName: id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
validators: {},
|
||||||
|
annotations: {},
|
||||||
|
html5DataAnnotations: {},
|
||||||
|
autocomplete: "new-password"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialFormFieldState: {
|
||||||
|
attribute: Attribute;
|
||||||
|
valueOrValues: string | string[];
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
for (const attribute of attributes) {
|
||||||
|
handle_multi_valued_attribute: {
|
||||||
|
if (!attribute.multivalued) {
|
||||||
|
break handle_multi_valued_attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = attribute.values?.length ? attribute.values : [""];
|
||||||
|
|
||||||
|
apply_validator_min_range: {
|
||||||
|
if (getIsMultivaluedSingleField({ attribute })) {
|
||||||
|
break apply_validator_min_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validator = attribute.validators.multivalued;
|
||||||
|
|
||||||
|
if (validator === undefined) {
|
||||||
|
break apply_validator_min_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { min: minStr } = validator;
|
||||||
|
|
||||||
|
if (!minStr) {
|
||||||
|
break apply_validator_min_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
const min = parseInt(`${minStr}`);
|
||||||
|
|
||||||
|
for (let index = values.length; index < min; index++) {
|
||||||
|
values.push("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initialFormFieldState.push({
|
||||||
|
attribute,
|
||||||
|
valueOrValues: values
|
||||||
|
});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialFormFieldState.push({
|
||||||
|
attribute,
|
||||||
|
valueOrValues: attribute.value ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const initialState: internal.State = {
|
const initialState: internal.State = {
|
||||||
formFieldStates: initialFormFieldState.map(({ attribute, valueOrValues }) => ({
|
formFieldStates: initialFormFieldState.map(({ attribute, valueOrValues }) => ({
|
||||||
|
@ -2,7 +2,7 @@ import { useState } from "react";
|
|||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
@ -3,7 +3,7 @@ import { Markdown } from "keycloakify/tools/Markdown";
|
|||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
|
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
|
||||||
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const formatNumber = (input: string, format: string): string => {
|
export const formatNumber = (input: string, format: string) => {
|
||||||
if (!input) {
|
if (!input) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -20,7 +20,8 @@ export const formatNumber = (input: string, format: string): string => {
|
|||||||
let rawValue = input.replace(/\D+/g, "");
|
let rawValue = input.replace(/\D+/g, "");
|
||||||
|
|
||||||
// make sure the value is a number
|
// make sure the value is a number
|
||||||
if (`${parseInt(rawValue)}` !== rawValue) {
|
// @ts-expect-error: It's Keycloak's code, we trust it.
|
||||||
|
if (parseInt(rawValue) != rawValue) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export function keycloakify(params?: Params) {
|
|||||||
let shouldGenerateSourcemap: boolean | undefined = undefined;
|
let shouldGenerateSourcemap: boolean | undefined = undefined;
|
||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
name: "keycloakify" as const,
|
name: "keycloakify",
|
||||||
configResolved: async resolvedConfig => {
|
configResolved: async resolvedConfig => {
|
||||||
shouldGenerateSourcemap = resolvedConfig.build.sourcemap !== false;
|
shouldGenerateSourcemap = resolvedConfig.build.sourcemap !== false;
|
||||||
|
|
||||||
@ -47,6 +47,10 @@ export function keycloakify(params?: Params) {
|
|||||||
|
|
||||||
const buildContext = JSON.parse(envValue) as BuildContext;
|
const buildContext = JSON.parse(envValue) as BuildContext;
|
||||||
|
|
||||||
|
process.chdir(
|
||||||
|
pathJoin(buildContext.keycloakifyBuildDirPath, "resources")
|
||||||
|
);
|
||||||
|
|
||||||
await postBuild?.(buildContext);
|
await postBuild?.(buildContext);
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import DefaultPage from "../../dist/account/Fallback";
|
import DefaultPage from "../../dist/account/DefaultPage";
|
||||||
import { useI18n } from "./i18n";
|
import { useI18n } from "./i18n";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import Template from "../../dist/account/Template";
|
import Template from "../../dist/account/Template";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import DefaultPage from "../../dist/login/Fallback";
|
import DefaultPage from "../../dist/login/DefaultPage";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import { useI18n } from "./i18n";
|
import { useI18n } from "./i18n";
|
||||||
import { useDownloadTerms } from "../../dist/login/lib/useDownloadTerms";
|
import { useDownloadTerms } from "../../dist/login/lib/useDownloadTerms";
|
||||||
|
@ -11144,11 +11144,6 @@ schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.1.2:
|
|||||||
ajv "^6.12.5"
|
ajv "^6.12.5"
|
||||||
ajv-keywords "^3.5.2"
|
ajv-keywords "^3.5.2"
|
||||||
|
|
||||||
scripting-tools@^0.19.13:
|
|
||||||
version "0.19.14"
|
|
||||||
resolved "https://registry.yarnpkg.com/scripting-tools/-/scripting-tools-0.19.14.tgz#d46cdea3dcf042b103b1712103b007e72c4901d5"
|
|
||||||
integrity sha512-KGRES70dEmcaCdpx3R88bLWmfA4mQ/EGikCQy0FGTZwx3y9F5yYkzEhwp02+ZTgpvF25JcNOhDBbOqL6z92kwg==
|
|
||||||
|
|
||||||
semver-compare@^1.0.0:
|
semver-compare@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||||
|
Reference in New Issue
Block a user