From 9b27f25f6c3865eeee7484e73c63c7c2f1de7c51 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Fri, 17 May 2024 05:13:41 +0200 Subject: [PATCH] Implement stary-keycloak-container --- src/bin/download-builtin-keycloak-theme.ts | 4 +- src/bin/initialize-email-theme.ts | 4 +- src/bin/keycloakify/buildJars/buildJars.ts | 2 +- .../getKeycloakVersionRangeForJar.ts | 119 +++++++--------- .../generateSrcMainResources.ts | 5 +- src/bin/shared/KeycloakVersionRange.ts | 7 + .../getJarFileBasename.ts | 2 +- src/bin/shared/promptKeycloakVersion.ts | 8 +- src/bin/start-keycloak-container.ts | 133 +++++++++++++++++- src/bin/tools/getNpmWorkspaceRootDirPath.ts | 2 +- 10 files changed, 209 insertions(+), 77 deletions(-) create mode 100644 src/bin/shared/KeycloakVersionRange.ts rename src/bin/{keycloakify/buildJars => shared}/getJarFileBasename.ts (76%) diff --git a/src/bin/download-builtin-keycloak-theme.ts b/src/bin/download-builtin-keycloak-theme.ts index 772d1c23..ba6deb56 100644 --- a/src/bin/download-builtin-keycloak-theme.ts +++ b/src/bin/download-builtin-keycloak-theme.ts @@ -14,7 +14,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) const { log } = getLogger({ "isSilent": buildOptions.isSilent }); - const { keycloakVersion } = await promptKeycloakVersion(); + const { keycloakVersion } = await promptKeycloakVersion({ + "startingFromMajor": undefined + }); const destDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme"); diff --git a/src/bin/initialize-email-theme.ts b/src/bin/initialize-email-theme.ts index 989cc26f..b6c09519 100644 --- a/src/bin/initialize-email-theme.ts +++ b/src/bin/initialize-email-theme.ts @@ -28,7 +28,9 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) process.exit(-1); } - const { keycloakVersion } = await promptKeycloakVersion(); + const { keycloakVersion } = await promptKeycloakVersion({ + "startingFromMajor": 17 + }); const builtinKeycloakThemeTmpDirPath = pathJoin(buildOptions.cacheDirPath, "initialize-email-theme_tmp"); diff --git a/src/bin/keycloakify/buildJars/buildJars.ts b/src/bin/keycloakify/buildJars/buildJars.ts index 7c7500dd..d16874de 100644 --- a/src/bin/keycloakify/buildJars/buildJars.ts +++ b/src/bin/keycloakify/buildJars/buildJars.ts @@ -4,7 +4,7 @@ import { keycloakAccountV1Versions, keycloakThemeAdditionalInfoExtensionVersions import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar"; import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar"; import type { BuildOptions } from "../../shared/buildOptions"; -import { getJarFileBasename } from "./getJarFileBasename"; +import { getJarFileBasename } from "../../shared/getJarFileBasename"; import { readMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes"; import { accountV1ThemeName } from "../../shared/constants"; diff --git a/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts index 6462fdb9..6dc7b49f 100644 --- a/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts +++ b/src/bin/keycloakify/buildJars/getKeycloakVersionRangeForJar.ts @@ -1,14 +1,6 @@ import { assert, type Equals } from "tsafe/assert"; import type { KeycloakAccountV1Version, KeycloakThemeAdditionalInfoExtensionVersion } from "./extensionVersions"; -import { id } from "tsafe/id"; - -export type KeycloakVersionRange = KeycloakVersionRange.WithAccountTheme | KeycloakVersionRange.WithoutAccountTheme; - -export namespace KeycloakVersionRange { - export type WithoutAccountTheme = "21-and-below" | "22-and-above"; - - export type WithAccountTheme = "21-and-below" | "23" | "24-and-above"; -} +import type { KeycloakVersionRange } from "../../shared/KeycloakVersionRange"; export function getKeycloakVersionRangeForJar(params: { doesImplementAccountTheme: boolean; @@ -18,66 +10,55 @@ export function getKeycloakVersionRangeForJar(params: { const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, doesImplementAccountTheme } = params; if (doesImplementAccountTheme) { - return id( - (() => { - switch (keycloakAccountV1Version) { - case null: - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return "21-and-below" as const; - case "1.1.5": - return undefined; - } - assert>(false); - case "0.3": - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return undefined; - case "1.1.5": - return "23" as const; - } - assert>(false); - case "0.4": - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return undefined; - case "1.1.5": - return "24-and-above" as const; - } - assert>(false); - } - })() - ); + const keycloakVersionRange = (() => { + switch (keycloakAccountV1Version) { + case null: + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return "21-and-below" as const; + case "1.1.5": + return undefined; + } + assert>(false); + case "0.3": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return undefined; + case "1.1.5": + return "23" as const; + } + assert>(false); + case "0.4": + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return undefined; + case "1.1.5": + return "24-and-above" as const; + } + assert>(false); + } + })(); + + assert>(); + + return keycloakVersionRange; } else { - return id( - (() => { - switch (keycloakAccountV1Version) { - case null: - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return "21-and-below"; - case "1.1.5": - return "22-and-above"; - } - assert>(false); - case "0.3": - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return undefined; - case "1.1.5": - return undefined; - } - assert>(false); - case "0.4": - switch (keycloakThemeAdditionalInfoExtensionVersion) { - case null: - return undefined; - case "1.1.5": - return undefined; - } - assert>(false); - } - })() - ); + const keycloakVersionRange = (() => { + if (keycloakAccountV1Version !== null) { + return undefined; + } + + switch (keycloakThemeAdditionalInfoExtensionVersion) { + case null: + return "21-and-below"; + case "1.1.5": + return "22-and-above"; + } + assert>(false); + })(); + + assert>(); + + return keycloakVersionRange; } } diff --git a/src/bin/keycloakify/generateSrcMainResources/generateSrcMainResources.ts b/src/bin/keycloakify/generateSrcMainResources/generateSrcMainResources.ts index d9f62ab2..01865235 100644 --- a/src/bin/keycloakify/generateSrcMainResources/generateSrcMainResources.ts +++ b/src/bin/keycloakify/generateSrcMainResources/generateSrcMainResources.ts @@ -1,6 +1,9 @@ import type { BuildOptions } from "../../shared/buildOptions"; import { assert } from "tsafe/assert"; -import { generateSrcMainResourcesForMainTheme, type BuildOptionsLike as BuildOptionsLike_generateSrcMainResourcesForMainTheme } from "./generateSrcMainResourcesForMainTheme"; +import { + generateSrcMainResourcesForMainTheme, + type BuildOptionsLike as BuildOptionsLike_generateSrcMainResourcesForMainTheme +} from "./generateSrcMainResourcesForMainTheme"; import { generateSrcMainResourcesForThemeVariant } from "./generateSrcMainResourcesForThemeVariant"; export type BuildOptionsLike = BuildOptionsLike_generateSrcMainResourcesForMainTheme & { diff --git a/src/bin/shared/KeycloakVersionRange.ts b/src/bin/shared/KeycloakVersionRange.ts new file mode 100644 index 00000000..43921953 --- /dev/null +++ b/src/bin/shared/KeycloakVersionRange.ts @@ -0,0 +1,7 @@ +export type KeycloakVersionRange = KeycloakVersionRange.WithAccountTheme | KeycloakVersionRange.WithoutAccountTheme; + +export namespace KeycloakVersionRange { + export type WithoutAccountTheme = "21-and-below" | "22-and-above"; + + export type WithAccountTheme = "21-and-below" | "23" | "24-and-above"; +} diff --git a/src/bin/keycloakify/buildJars/getJarFileBasename.ts b/src/bin/shared/getJarFileBasename.ts similarity index 76% rename from src/bin/keycloakify/buildJars/getJarFileBasename.ts rename to src/bin/shared/getJarFileBasename.ts index ab59605a..8c073ffc 100644 --- a/src/bin/keycloakify/buildJars/getJarFileBasename.ts +++ b/src/bin/shared/getJarFileBasename.ts @@ -1,4 +1,4 @@ -import type { KeycloakVersionRange } from "./getKeycloakVersionRangeForJar"; +import type { KeycloakVersionRange } from "./KeycloakVersionRange"; export function getJarFileBasename(params: { keycloakVersionRange: KeycloakVersionRange }) { const { keycloakVersionRange } = params; diff --git a/src/bin/shared/promptKeycloakVersion.ts b/src/bin/shared/promptKeycloakVersion.ts index b5d6fdea..7585d6a8 100644 --- a/src/bin/shared/promptKeycloakVersion.ts +++ b/src/bin/shared/promptKeycloakVersion.ts @@ -4,7 +4,9 @@ import cliSelect from "cli-select"; import { lastKeycloakVersionWithAccountV1 } from "./constants"; import { SemVer } from "../tools/SemVer"; -export async function promptKeycloakVersion() { +export async function promptKeycloakVersion(params: { startingFromMajor: number | undefined }) { + const { startingFromMajor } = params; + const { getLatestsSemVersionedTag } = (() => { const { octokit } = (() => { const githubToken = process.env.GITHUB_TOKEN; @@ -30,6 +32,10 @@ export async function promptKeycloakVersion() { "repo": "keycloak" }) ).forEach(semVersionedTag => { + if (startingFromMajor !== undefined && semVersionedTag.version.major < startingFromMajor) { + return; + } + const currentSemVersionedTag = semVersionedTagByMajor.get(semVersionedTag.version.major); if (currentSemVersionedTag !== undefined && SemVer.compare(semVersionedTag.version, currentSemVersionedTag.version) === -1) { diff --git a/src/bin/start-keycloak-container.ts b/src/bin/start-keycloak-container.ts index 535f2bb3..b9917636 100644 --- a/src/bin/start-keycloak-container.ts +++ b/src/bin/start-keycloak-container.ts @@ -1,10 +1,141 @@ import { readBuildOptions } from "./shared/buildOptions"; import type { CliCommandOptions } from "./main"; +import { promptKeycloakVersion } from "./shared/promptKeycloakVersion"; +import { readMetaInfKeycloakThemes } from "./shared/metaInfKeycloakThemes"; +import { accountV1ThemeName } 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, posix as pathPosix } from "path"; +import * as child_process from "child_process"; export async function command(params: { cliCommandOptions: CliCommandOptions }) { const { cliCommandOptions } = params; const buildOptions = readBuildOptions({ cliCommandOptions }); - console.log("TODO", buildOptions); + const metaInfKeycloakThemes = readMetaInfKeycloakThemes({ + "keycloakifyBuildDirPath": buildOptions.keycloakifyBuildDirPath + }); + + const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(({ name }) => name === accountV1ThemeName); + + const { keycloakVersion, keycloakMajorNumber } = await (async function getKeycloakMajor(): Promise<{ + keycloakVersion: string; + keycloakMajorNumber: number; + }> { + const { keycloakVersion } = await promptKeycloakVersion({ + "startingFromMajor": 17 + }); + + const keycloakMajorNumber = SemVer.parse(keycloakVersion).major; + + if (doesImplementAccountTheme && keycloakMajorNumber === 22) { + console.log([ + "Unfortunately, Keycloakify themes that implements an account theme do not work on Keycloak 22", + "Please select any other Keycloak version" + ]); + return getKeycloakMajor(); + } + + return { keycloakVersion, keycloakMajorNumber }; + })(); + + const keycloakVersionRange: KeycloakVersionRange = (() => { + if (doesImplementAccountTheme) { + const keycloakVersionRange = (() => { + if (keycloakMajorNumber <= 21) { + return "21-and-below" as const; + } + + assert(keycloakMajorNumber !== 22); + + if (keycloakMajorNumber === 23) { + return "23" as const; + } + + return "24-and-above" as const; + })(); + + assert>(); + + return keycloakVersionRange; + } else { + const keycloakVersionRange = (() => { + if (keycloakMajorNumber <= 21) { + return "21-and-below" as const; + } + + return "22-and-above" as const; + })(); + + assert>(); + + return keycloakVersionRange; + } + })(); + + const { jarFileBasename } = getJarFileBasename({ keycloakVersionRange }); + + const mountTargets = buildOptions.themeNames + .map(themeName => { + const themeEntry = metaInfKeycloakThemes.themes.find(({ name }) => name === themeName); + + assert(themeEntry !== undefined); + + return themeEntry.types + .map(themeType => { + const localPathDirname = pathJoin( + buildOptions.keycloakifyBuildDirPath, + "src", + "main", + "resources", + "theme", + themeName, + themeType + ); + + return fs + .readdirSync(localPathDirname) + .filter(fileOrDirectoryBasename => !fileOrDirectoryBasename.endsWith(".properties")) + .map(fileOrDirectoryBasename => ({ + "localPath": pathJoin(localPathDirname, fileOrDirectoryBasename), + "containerPath": pathPosix.join("/", "opt", "keycloak", "themes", themeName, themeType, fileOrDirectoryBasename) + })); + }) + .flat(); + }) + .flat(); + + const containerName = "keycloak-keycloakify"; + + try { + child_process.execSync(`docker rm ${containerName}`, { "stdio": "ignore" }); + } catch {} + + const child = child_process.spawn( + "docker", + [ + "run", + ...["-p", "8080:8080"], + ...["--name", containerName], + ...["-e", "KEYCLOAK_ADMIN=admin"], + ...["-e", "KEYCLOAK_ADMIN_PASSWORD=admin"], + ...["-v", `"${pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)}":"/opt/keycloak/providers/keycloak-theme.jar"`], + ...(keycloakMajorNumber <= 20 ? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"] : []), + ...mountTargets.map(({ localPath, containerPath }) => ["-v", `"${localPath}":"${containerPath}":rw`]).flat(), + ...["-it", `quay.io/keycloak/keycloak:${keycloakVersion}`], + "start-dev", + ...(21 <= keycloakMajorNumber && keycloakMajorNumber < 24 ? ["--features=declarative-user-profile"] : []) + ], + { + "cwd": buildOptions.keycloakifyBuildDirPath + } + ); + + child.stdout.on("data", data => console.log(data.toString("utf8"))); + + child.stderr.on("data", data => console.error(data.toString("utf8"))); } diff --git a/src/bin/tools/getNpmWorkspaceRootDirPath.ts b/src/bin/tools/getNpmWorkspaceRootDirPath.ts index db2310fe..e934a6fc 100644 --- a/src/bin/tools/getNpmWorkspaceRootDirPath.ts +++ b/src/bin/tools/getNpmWorkspaceRootDirPath.ts @@ -9,7 +9,7 @@ export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string const cwd = pathResolve(pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")])); try { - child_process.execSync("npm config get", { cwd, "stdio": ["pipe", "pipe", "pipe"] }); + child_process.execSync("npm config get", { cwd, "stdio": "ignore" }); } catch (error) { if (String(error).includes("ENOWORKSPACES")) { assert(cwd !== pathSep, "NPM workspace not found");