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

@ -9,17 +9,17 @@
"scripts": {
"prepare": "ts-node --skipProject scripts/generate-i18n-messages.ts && patch-package",
"build": "ts-node --skipProject scripts/build.ts",
"watch": "chokidar './src/**/*' -c 'yarn build'",
"storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006",
"link-in-starter": "ts-node --skipProject scripts/link-in-starter.ts",
"test": "yarn test:types && vitest run",
"test:types": "tsc -p test/tsconfig.json --noEmit",
"_format": "prettier '**/*.{ts,tsx,json,md}'",
"format": "yarn _format --write",
"format:check": "yarn _format --list-different",
"link-in-app": "ts-node --skipProject scripts/link-in-app.ts",
"link-in-starter": "ts-node --skipProject scripts/link-in-starter.ts",
"copy-keycloak-resources-to-storybook-static": "PUBLIC_DIR_PATH=.storybook/static node dist/bin/main.js copy-keycloak-resources-to-public",
"storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006",
"build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook"
"build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook",
"dump-keycloak-realm": "ts-node --skipProject scripts/dump-keycloak-realm.ts"
},
"bin": {
"keycloakify": "dist/bin/main.js"

View File

@ -1,6 +1,6 @@
import * as child_process from "child_process";
import * as fs from "fs";
import { join } from "path";
import { join, relative } from "path";
import { assert } from "tsafe/assert";
import { transformCodebase } from "../src/bin/tools/transformCodebase";
import chalk from "chalk";
@ -21,7 +21,9 @@ if (fs.existsSync(join("dist", "bin", "main.original.js"))) {
run(`npx tsc -p ${join("src", "bin", "tsconfig.json")}`);
fs.cpSync(join("dist", "bin", "main.js"), join("dist", "bin", "main.original.js"));
if (!fs.readFileSync(join("dist", "bin", "main.js")).toString("utf8").includes("ncc")) {
fs.cpSync(join("dist", "bin", "main.js"), join("dist", "bin", "main.original.js"));
}
run(`npx ncc build ${join("dist", "bin", "main.js")} -o ${join("dist", "ncc_out")}`);
@ -58,7 +60,9 @@ if (fs.existsSync(join("dist", "vite-plugin", "index.original.js"))) {
run(`npx tsc -p ${join("src", "vite-plugin", "tsconfig.json")}`);
fs.cpSync(join("dist", "vite-plugin", "index.js"), join("dist", "vite-plugin", "index.original.js"));
if (!fs.readFileSync(join("dist", "vite-plugin", "index.js")).toString("utf8").includes("ncc")) {
fs.cpSync(join("dist", "vite-plugin", "index.js"), join("dist", "vite-plugin", "index.original.js"));
}
run(`npx ncc build ${join("dist", "vite-plugin", "index.js")} -o ${join("dist", "ncc_out")}`);
@ -92,7 +96,7 @@ function patchDeprecatedBufferApiUsage(filePath: string) {
`var buffer = Buffer.allocUnsafe ? Buffer.allocUnsafe(toRead) : new Buffer(toRead);`
);
assert(after !== before);
assert(after !== before, `Patch failed for ${relative(process.cwd(), filePath)}`);
fs.writeFileSync(filePath, Buffer.from(after, "utf8"));
}

View File

@ -0,0 +1,26 @@
import { containerName } from "../src/bin/shared/constants";
import child_process from "child_process";
import { SemVer } from "../src/bin/tools/SemVer";
import { join as pathJoin, relative as pathRelative } from "path";
import chalk from "chalk";
run([`docker exec -it ${containerName}`, `/opt/keycloak/bin/kc.sh export`, `--dir /tmp`, `--realm myrealm`, `--users realm_file`].join(" "));
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) {
console.log(chalk.grey(`$ ${command}`));
return child_process.execSync(command, { "stdio": "inherit" });
}

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));