Implement stary-keycloak-container

This commit is contained in:
Joseph Garrone 2024-05-17 05:13:41 +02:00
parent e09acedf67
commit 9b27f25f6c
10 changed files with 209 additions and 77 deletions

View File

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

View File

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

View File

@ -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";

View File

@ -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<KeycloakVersionRange.WithAccountTheme | undefined>(
(() => {
switch (keycloakAccountV1Version) {
case null:
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return "21-and-below" as const;
case "1.1.5":
return undefined;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
case "0.3":
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return undefined;
case "1.1.5":
return "23" as const;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
case "0.4":
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return undefined;
case "1.1.5":
return "24-and-above" as const;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
}
})()
);
const keycloakVersionRange = (() => {
switch (keycloakAccountV1Version) {
case null:
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return "21-and-below" as const;
case "1.1.5":
return undefined;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
case "0.3":
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return undefined;
case "1.1.5":
return "23" as const;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
case "0.4":
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return undefined;
case "1.1.5":
return "24-and-above" as const;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
}
})();
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithAccountTheme | undefined>>();
return keycloakVersionRange;
} else {
return id<KeycloakVersionRange.WithoutAccountTheme | undefined>(
(() => {
switch (keycloakAccountV1Version) {
case null:
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return "21-and-below";
case "1.1.5":
return "22-and-above";
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
case "0.3":
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return undefined;
case "1.1.5":
return undefined;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
case "0.4":
switch (keycloakThemeAdditionalInfoExtensionVersion) {
case null:
return undefined;
case "1.1.5":
return undefined;
}
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(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<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
})();
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithoutAccountTheme | undefined>>();
return keycloakVersionRange;
}
}

View File

@ -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 & {

View File

@ -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";
}

View File

@ -1,4 +1,4 @@
import type { KeycloakVersionRange } from "./getKeycloakVersionRangeForJar";
import type { KeycloakVersionRange } from "./KeycloakVersionRange";
export function getJarFileBasename(params: { keycloakVersionRange: KeycloakVersionRange }) {
const { keycloakVersionRange } = params;

View File

@ -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) {

View File

@ -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<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithAccountTheme>>();
return keycloakVersionRange;
} else {
const keycloakVersionRange = (() => {
if (keycloakMajorNumber <= 21) {
return "21-and-below" as const;
}
return "22-and-above" as const;
})();
assert<Equals<typeof keycloakVersionRange, KeycloakVersionRange.WithoutAccountTheme>>();
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")));
}

View File

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