Introduce start-keycloak-container command
This commit is contained in:
parent
14fe55e5c4
commit
3cd3e08ede
@ -4,6 +4,7 @@ import { keycloakAccountV1Versions, keycloakThemeAdditionalInfoExtensionVersions
|
|||||||
import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar";
|
import { getKeycloakVersionRangeForJar } from "./getKeycloakVersionRangeForJar";
|
||||||
import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar";
|
import { buildJar, BuildOptionsLike as BuildOptionsLike_buildJar } from "./buildJar";
|
||||||
import type { BuildOptions } from "../../shared/buildOptions";
|
import type { BuildOptions } from "../../shared/buildOptions";
|
||||||
|
import { getJarFileBasename } from "./getJarFileBasename";
|
||||||
|
|
||||||
export type BuildOptionsLike = BuildOptionsLike_buildJar & {
|
export type BuildOptionsLike = BuildOptionsLike_buildJar & {
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
@ -11,14 +12,9 @@ export type BuildOptionsLike = BuildOptionsLike_buildJar & {
|
|||||||
|
|
||||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
|
||||||
export async function buildJars(params: {
|
export async function buildJars(params: { doesImplementAccountTheme: boolean; buildOptions: BuildOptionsLike }): Promise<void> {
|
||||||
doesImplementAccountTheme: boolean;
|
|
||||||
buildOptions: BuildOptionsLike;
|
|
||||||
}): Promise<{ lastJarFileBasename: string }> {
|
|
||||||
const { doesImplementAccountTheme, buildOptions } = params;
|
const { doesImplementAccountTheme, buildOptions } = params;
|
||||||
|
|
||||||
let lastJarFileBasename: string | undefined = undefined;
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
keycloakAccountV1Versions
|
keycloakAccountV1Versions
|
||||||
.map(keycloakAccountV1Version =>
|
.map(keycloakAccountV1Version =>
|
||||||
@ -38,11 +34,12 @@ export async function buildJars(params: {
|
|||||||
})
|
})
|
||||||
.filter(exclude(undefined))
|
.filter(exclude(undefined))
|
||||||
.map(({ keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange }) => {
|
.map(({ keycloakThemeAdditionalInfoExtensionVersion, keycloakVersionRange }) => {
|
||||||
const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`;
|
const { jarFileBasename } = getJarFileBasename({ keycloakVersionRange });
|
||||||
|
|
||||||
lastJarFileBasename = jarFileBasename;
|
return {
|
||||||
|
keycloakThemeAdditionalInfoExtensionVersion,
|
||||||
return { keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename };
|
jarFileBasename
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.map(({ keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }) =>
|
.map(({ keycloakThemeAdditionalInfoExtensionVersion, jarFileBasename }) =>
|
||||||
buildJar({
|
buildJar({
|
||||||
@ -55,8 +52,4 @@ export async function buildJars(params: {
|
|||||||
)
|
)
|
||||||
.flat()
|
.flat()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(lastJarFileBasename !== undefined);
|
|
||||||
|
|
||||||
return { lastJarFileBasename };
|
|
||||||
}
|
}
|
||||||
|
9
src/bin/keycloakify/buildJars/getJarFileBasename.ts
Normal file
9
src/bin/keycloakify/buildJars/getJarFileBasename.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import type { KeycloakVersionRange } from "./getKeycloakVersionRangeForJar";
|
||||||
|
|
||||||
|
export function getJarFileBasename(params: { keycloakVersionRange: KeycloakVersionRange }) {
|
||||||
|
const { keycloakVersionRange } = params;
|
||||||
|
|
||||||
|
const jarFileBasename = `keycloak-theme-for-kc-${keycloakVersionRange}.jar`;
|
||||||
|
|
||||||
|
return { jarFileBasename };
|
||||||
|
}
|
@ -1,37 +1,83 @@
|
|||||||
import { assert, type Equals } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
import type { KeycloakAccountV1Version, KeycloakThemeAdditionalInfoExtensionVersion } from "./extensionVersions";
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
export function getKeycloakVersionRangeForJar(params: {
|
export function getKeycloakVersionRangeForJar(params: {
|
||||||
doesImplementAccountTheme: boolean;
|
doesImplementAccountTheme: boolean;
|
||||||
keycloakAccountV1Version: KeycloakAccountV1Version;
|
keycloakAccountV1Version: KeycloakAccountV1Version;
|
||||||
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
|
keycloakThemeAdditionalInfoExtensionVersion: KeycloakThemeAdditionalInfoExtensionVersion;
|
||||||
}): string | undefined {
|
}): KeycloakVersionRange | undefined {
|
||||||
const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, doesImplementAccountTheme } = params;
|
const { keycloakAccountV1Version, keycloakThemeAdditionalInfoExtensionVersion, doesImplementAccountTheme } = params;
|
||||||
|
|
||||||
|
if (doesImplementAccountTheme) {
|
||||||
|
return id<KeycloakVersionRange.WithAccountTheme | undefined>(
|
||||||
|
(() => {
|
||||||
switch (keycloakAccountV1Version) {
|
switch (keycloakAccountV1Version) {
|
||||||
case null:
|
case null:
|
||||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||||
case null:
|
case null:
|
||||||
return doesImplementAccountTheme ? "21-and-below" : "21-and-below";
|
return "21-and-below" as const;
|
||||||
case "1.1.5":
|
case "1.1.5":
|
||||||
return doesImplementAccountTheme ? undefined : "22-and-above";
|
return undefined;
|
||||||
}
|
}
|
||||||
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
||||||
case "0.3":
|
case "0.3":
|
||||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||||
case null:
|
case null:
|
||||||
return doesImplementAccountTheme ? undefined : undefined;
|
return undefined;
|
||||||
case "1.1.5":
|
case "1.1.5":
|
||||||
return doesImplementAccountTheme ? "23" : undefined;
|
return "23" as const;
|
||||||
}
|
}
|
||||||
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
||||||
case "0.4":
|
case "0.4":
|
||||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||||
case null:
|
case null:
|
||||||
return doesImplementAccountTheme ? undefined : undefined;
|
return undefined;
|
||||||
case "1.1.5":
|
case "1.1.5":
|
||||||
return doesImplementAccountTheme ? "24-and-above" : undefined;
|
return "24-and-above" as const;
|
||||||
}
|
}
|
||||||
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(false);
|
||||||
}
|
}
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ export async function generateSrcMainResources(params: {
|
|||||||
buildOptions: BuildOptionsLike;
|
buildOptions: BuildOptionsLike;
|
||||||
keycloakifyVersion: string;
|
keycloakifyVersion: string;
|
||||||
srcMainResourcesDirPath: string;
|
srcMainResourcesDirPath: string;
|
||||||
}): Promise<{ doesImplementAccountTheme: boolean }> {
|
}): Promise<void> {
|
||||||
const { themeName, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion, srcMainResourcesDirPath } = params;
|
const { themeName, themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion, srcMainResourcesDirPath } = params;
|
||||||
|
|
||||||
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
|
const getThemeTypeDirPath = (params: { themeType: ThemeType | "email" }) => {
|
||||||
@ -75,7 +75,7 @@ export async function generateSrcMainResources(params: {
|
|||||||
rmSync(destDirPath, { "recursive": true, "force": true });
|
rmSync(destDirPath, { "recursive": true, "force": true });
|
||||||
|
|
||||||
if (themeType === "account" && implementedThemeTypes.login) {
|
if (themeType === "account" && implementedThemeTypes.login) {
|
||||||
// NOTE: We prevend doing it twice, it has been done for the login theme.
|
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"srcDirPath": pathJoin(
|
"srcDirPath": pathJoin(
|
||||||
@ -263,6 +263,4 @@ export async function generateSrcMainResources(params: {
|
|||||||
|
|
||||||
fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8"));
|
fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { "doesImplementAccountTheme": implementedThemeTypes.account };
|
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,14 @@ export async function generateTheme(params: {
|
|||||||
keycloakifySrcDirPath: string;
|
keycloakifySrcDirPath: string;
|
||||||
buildOptions: BuildOptionsLike;
|
buildOptions: BuildOptionsLike;
|
||||||
keycloakifyVersion: string;
|
keycloakifyVersion: string;
|
||||||
}): Promise<{ doesImplementAccountTheme: boolean }> {
|
}): Promise<void> {
|
||||||
const { themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params;
|
const { themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params;
|
||||||
|
|
||||||
const [themeName, ...themeVariantNames] = buildOptions.themeNames;
|
const [themeName, ...themeVariantNames] = buildOptions.themeNames;
|
||||||
|
|
||||||
const srcMainResourcesDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources");
|
const srcMainResourcesDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources");
|
||||||
|
|
||||||
const { doesImplementAccountTheme } = await generateSrcMainResources({
|
await generateSrcMainResources({
|
||||||
themeName,
|
themeName,
|
||||||
srcMainResourcesDirPath,
|
srcMainResourcesDirPath,
|
||||||
themeSrcDirPath,
|
themeSrcDirPath,
|
||||||
@ -39,6 +39,4 @@ export async function generateTheme(params: {
|
|||||||
srcMainResourcesDirPath
|
srcMainResourcesDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { doesImplementAccountTheme };
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { generateTheme } from "./generateTheme";
|
import { generateTheme } from "./generateTheme";
|
||||||
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
|
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 { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { readBuildOptions } from "../shared/buildOptions";
|
import { readBuildOptions } from "../shared/buildOptions";
|
||||||
import { getLogger } from "../tools/logger";
|
import { getLogger } from "../tools/logger";
|
||||||
@ -18,6 +17,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
const buildOptions = readBuildOptions({ cliCommandOptions });
|
const buildOptions = readBuildOptions({ cliCommandOptions });
|
||||||
|
|
||||||
const logger = getLogger({ "isSilent": buildOptions.isSilent });
|
const logger = getLogger({ "isSilent": buildOptions.isSilent });
|
||||||
|
|
||||||
logger.log("🔏 Building the keycloak theme...⌚");
|
logger.log("🔏 Building the keycloak theme...⌚");
|
||||||
|
|
||||||
const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath });
|
const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath });
|
||||||
@ -30,7 +30,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { doesImplementAccountTheme } = await generateTheme({
|
await generateTheme({
|
||||||
themeSrcDirPath,
|
themeSrcDirPath,
|
||||||
"keycloakifySrcDirPath": pathJoin(getThisCodebaseRootDirPath(), "src"),
|
"keycloakifySrcDirPath": pathJoin(getThisCodebaseRootDirPath(), "src"),
|
||||||
"keycloakifyVersion": readThisNpmProjectVersion(),
|
"keycloakifyVersion": readThisNpmProjectVersion(),
|
||||||
@ -51,51 +51,15 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lastJarFileBasename } = await buildJars({
|
await buildJars({
|
||||||
doesImplementAccountTheme,
|
|
||||||
buildOptions
|
|
||||||
});
|
|
||||||
|
|
||||||
generateStartKeycloakTestingContainer({
|
|
||||||
"jarFilePath": pathJoin(buildOptions.keycloakifyBuildDirPath, lastJarFileBasename),
|
|
||||||
doesImplementAccountTheme,
|
doesImplementAccountTheme,
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log(
|
logger.log(
|
||||||
[
|
|
||||||
`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathJoin(
|
`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathJoin(
|
||||||
pathRelative(buildOptions.reactAppRootDirPath, buildOptions.keycloakifyBuildDirPath),
|
pathRelative(process.cwd(), buildOptions.keycloakifyBuildDirPath),
|
||||||
"keycloak-theme-for-kc-*.jar"
|
"keycloak-theme-for-kc-*.jar"
|
||||||
)}`,
|
)}`
|
||||||
"",
|
|
||||||
`To test your theme locally you can spin up a Keycloak container image with the theme pre loaded by running:`,
|
|
||||||
"",
|
|
||||||
`👉 $ .${pathSep}${pathRelative(
|
|
||||||
buildOptions.reactAppRootDirPath,
|
|
||||||
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename)
|
|
||||||
)} 👈`,
|
|
||||||
``,
|
|
||||||
`Once your container is up and running: `,
|
|
||||||
"- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
|
|
||||||
`- Create a realm: Master -> AddRealm -> Name: myrealm`,
|
|
||||||
`- Enable registration: Realm settings -> Login tab -> User registration: on`,
|
|
||||||
`- Enable the Account theme (optional): Realm settings -> Themes tab -> Account theme: ${buildOptions.themeNames[0]}`,
|
|
||||||
` Clients -> account -> Login theme: ${buildOptions.themeNames[0]}`,
|
|
||||||
`- Enable the email theme (optional): Realm settings -> Themes tab -> Email theme: ${buildOptions.themeNames[0]} (option will appear only if you have ran npx initialize-email-theme)`,
|
|
||||||
`- Create a client Clients -> Create -> Client ID: myclient`,
|
|
||||||
` Root URL: https://www.keycloak.org/app/`,
|
|
||||||
` Valid redirect URIs: https://www.keycloak.org/app* http://localhost* (localhost is optional)`,
|
|
||||||
` Valid post logout redirect URIs: https://www.keycloak.org/app* http://localhost*`,
|
|
||||||
` Web origins: *`,
|
|
||||||
` Login Theme: ${buildOptions.themeNames[0]}`,
|
|
||||||
` Save (button at the bottom of the page)`,
|
|
||||||
``,
|
|
||||||
`- Go to 👉 https://www.keycloak.org/app/ 👈 Click "Save" then "Sign in". You should see your login page`,
|
|
||||||
`- Got to 👉 http://localhost:8080/realms/myrealm/account 👈 to see your account theme`,
|
|
||||||
``,
|
|
||||||
`Video tutorial: https://youtu.be/WMyGZNHQkjU`,
|
|
||||||
``
|
|
||||||
].join("\n")
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,18 @@ program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command({
|
||||||
|
"name": "start-keycloak-container",
|
||||||
|
"description": "Spin up a Keycloak container with the theme preloaded and the realm pre configured."
|
||||||
|
})
|
||||||
|
.task({
|
||||||
|
"handler": async cliCommandOptions => {
|
||||||
|
const { command } = await import("./start-keycloak-container");
|
||||||
|
return command({ cliCommandOptions });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Fallback to build command if no command is provided
|
// Fallback to build command if no command is provided
|
||||||
{
|
{
|
||||||
const [, , ...rest] = process.argv;
|
const [, , ...rest] = process.argv;
|
||||||
|
10
src/bin/start-keycloak-container.ts
Normal file
10
src/bin/start-keycloak-container.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { readBuildOptions } from "./shared/buildOptions";
|
||||||
|
import type { CliCommandOptions } from "./main";
|
||||||
|
|
||||||
|
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||||
|
const { cliCommandOptions } = params;
|
||||||
|
|
||||||
|
const buildOptions = readBuildOptions({ cliCommandOptions });
|
||||||
|
|
||||||
|
console.log("TODO", buildOptions);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user