Restore realm configuration

This commit is contained in:
Joseph Garrone
2024-05-20 15:34:07 +02:00
parent 64fe15cf8c
commit 7a89888d11
8 changed files with 2390 additions and 34 deletions

View File

@ -68,7 +68,11 @@ program
});
program
.command<{ port: number; keycloakVersion: string | undefined }>({
.command<{
port: number;
keycloakVersion: string | undefined;
realmJsonFilePath: string | undefined;
}>({
"name": "start-keycloak",
"description": "Spin up a pre configured Docker image of Keycloak to test your theme."
})
@ -81,7 +85,7 @@ program
return name;
})(),
"description": "Keycloak server port.",
"description": ["Keycloak server port.", "Example `--port 8085`"].join(" "),
"defaultValue": 8080
})
.option({
@ -93,9 +97,21 @@ program
return name;
})(),
"description": "Use a specific version of Keycloak.",
"description": ["Use a specific version of Keycloak.", "Example `--keycloak-version 21.1.1`"].join(" "),
"defaultValue": undefined
})
.option({
"key": "realmJsonFilePath",
"name": (() => {
const name = "import";
optionsKeys.push(name);
return name;
})(),
"defaultValue": undefined,
"description": ["Import your own realm configuration file", "Example `--import path/to/myrealm-realm.json`"].join(" ")
})
.task({
skip,
"handler": async cliCommandOptions => {

View File

@ -65,3 +65,5 @@ export const accountThemePageIds = [
export type LoginThemePageId = (typeof loginThemePageIds)[number];
export type AccountThemePageId = (typeof accountThemePageIds)[number];
export const containerName = "keycloak-keycloakify";

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
import { readBuildOptions } from "./shared/buildOptions";
import type { CliCommandOptions as CliCommandOptions_common } from "./main";
import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
import { readMetaInfKeycloakThemes } from "./shared/metaInfKeycloakThemes";
import { accountV1ThemeName, skipBuildJarsEnvName } from "./shared/constants";
import { SemVer } from "./tools/SemVer";
import type { KeycloakVersionRange } from "./shared/KeycloakVersionRange";
import { getJarFileBasename } from "./shared/getJarFileBasename";
import { readBuildOptions } from "../shared/buildOptions";
import type { CliCommandOptions as CliCommandOptions_common } from "../main";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes";
import { accountV1ThemeName, skipBuildJarsEnvName, containerName } from "../shared/constants";
import { SemVer } from "../tools/SemVer";
import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange";
import { getJarFileBasename } from "../shared/getJarFileBasename";
import { assert, type Equals } from "tsafe/assert";
import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, sep as pathSep, posix as pathPosix } from "path";
@ -13,12 +13,16 @@ import * as child_process from "child_process";
import chalk from "chalk";
import chokidar from "chokidar";
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath";
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
import { Deferred } from "evt/tools/Deferred";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import cliSelect from "cli-select";
import { isInside } from "../tools/isInside";
export type CliCommandOptions = CliCommandOptions_common & {
port: number;
keycloakVersion: string | undefined;
realmJsonFilePath: string | undefined;
};
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
@ -84,7 +88,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(({ name }) => name === accountV1ThemeName);
const { keycloakVersion, keycloakMajorNumber } = await (async function getKeycloakMajor(): Promise<{
const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } = await (async function getKeycloakMajor(): Promise<{
keycloakVersion: string;
keycloakMajorNumber: number;
}> {
@ -122,13 +126,13 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const keycloakVersionRange: KeycloakVersionRange = (() => {
if (doesImplementAccountTheme) {
const keycloakVersionRange = (() => {
if (keycloakMajorNumber <= 21) {
if (keycloakMajorVersionNumber <= 21) {
return "21-and-below" as const;
}
assert(keycloakMajorNumber !== 22);
assert(keycloakMajorVersionNumber !== 22);
if (keycloakMajorNumber === 23) {
if (keycloakMajorVersionNumber === 23) {
return "23" as const;
}
@ -140,7 +144,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
return keycloakVersionRange;
} else {
const keycloakVersionRange = (() => {
if (keycloakMajorNumber <= 21) {
if (keycloakMajorVersionNumber <= 21) {
return "21-and-below" as const;
}
@ -187,13 +191,46 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
})
.flat();
const containerName = "keycloak-keycloakify";
try {
child_process.execSync(`docker rm --force ${containerName}`, { "stdio": "ignore" });
} catch {}
const spawnParams = [
const realmJsonFilePath = await (async () => {
if (cliCommandOptions.realmJsonFilePath !== undefined) {
console.log(chalk.green(`Using realm json file: ${cliCommandOptions.realmJsonFilePath}`));
return getAbsoluteAndInOsFormatPath({
"pathIsh": cliCommandOptions.realmJsonFilePath,
"cwd": process.cwd()
});
}
const dirPath = pathJoin(getThisCodebaseRootDirPath(), "src", "bin", "start-keycloak");
const filePath = pathJoin(dirPath, `myrealm-realm-${keycloakMajorVersionNumber}.json`);
if (fs.existsSync(filePath)) {
return filePath;
}
console.log(`${chalk.yellow(`Keycloakify do not have a realm configuration for Keycloak ${keycloakMajorVersionNumber} yet.`)}`);
console.log(chalk.cyan("Select what configuration to use:"));
const { value } = await cliSelect<string>({
"values": [...fs.readdirSync(dirPath).filter(fileBasename => fileBasename.endsWith(".json")), "none"]
}).catch(() => {
process.exit(-1);
});
if (value === "none") {
return undefined;
}
return pathJoin(dirPath, value);
})();
const spawnArgs = [
"docker",
[
"run",
@ -201,19 +238,23 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
...["--name", containerName],
...["-e", "KEYCLOAK_ADMIN=admin"],
...["-e", "KEYCLOAK_ADMIN_PASSWORD=admin"],
...(realmJsonFilePath === undefined ? [] : ["-v", `${realmJsonFilePath}:/opt/keycloak/data/import/myrealm-realm.json`]),
...["-v", `${pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)}:/opt/keycloak/providers/keycloak-theme.jar`],
...(keycloakMajorNumber <= 20 ? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"] : []),
...(keycloakMajorVersionNumber <= 20 ? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"] : []),
...mountTargets.map(({ localPath, containerPath }) => ["-v", `${localPath}:${containerPath}:rw`]).flat(),
`quay.io/keycloak/keycloak:${keycloakVersion}`,
"start-dev",
...(21 <= keycloakMajorNumber && keycloakMajorNumber < 24 ? ["--features=declarative-user-profile"] : [])
...(21 <= keycloakMajorVersionNumber && keycloakMajorVersionNumber < 24 ? ["--features=declarative-user-profile"] : []),
...(realmJsonFilePath === undefined ? [] : ["--import-realm"])
],
{
"cwd": buildOptions.keycloakifyBuildDirPath
}
] as const;
const child = child_process.spawn(...spawnParams);
console.log(JSON.stringify(spawnArgs, null, 2));
const child = child_process.spawn(...spawnArgs);
child.stdout.on("data", data => process.stdout.write(data));
@ -247,7 +288,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`- user: ${chalk.cyan.bold("admin")}`,
`- password: ${chalk.cyan.bold("admin")}`,
"",
`Watching for changes in ${chalk.bold(`.${pathSep}${pathRelative(process.cwd(), srcDirPath)}`)} ...`
`Watching for changes in ${chalk.bold(`.${pathSep}${pathRelative(process.cwd(), srcDirPath)}`)}`
].join("\n")
);
};
@ -258,7 +299,20 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
{
const { waitForDebounce } = waitForDebounceFactory({ "delay": 400 });
chokidar.watch([srcDirPath, getThisCodebaseRootDirPath()], { "ignoreInitial": true }).on("all", async () => {
chokidar.watch([srcDirPath, getThisCodebaseRootDirPath()], { "ignoreInitial": true }).on("all", async (...[, filePath]) => {
if (
isInside({
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "src", "bin"),
filePath
}) ||
isInside({
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "bin"),
filePath
})
) {
return;
}
await waitForDebounce();
console.log(chalk.cyan("Detected changes in the theme. Rebuilding ..."));
@ -271,7 +325,13 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
"env": process.env
});
child.stdout.on("data", data => process.stdout.write(data));
child.stdout.on("data", data => {
if (data.toString("utf8").includes("gzip:")) {
return;
}
process.stdout.write(data);
});
child.stderr.on("data", data => process.stderr.write(data));