Make hot reloading when testing account theme with older Keycloak version work

This commit is contained in:
Joseph Garrone
2024-05-26 19:29:12 +02:00
parent 53955a0713
commit 68f5ee42e6
3 changed files with 106 additions and 110 deletions

View File

@ -1,6 +1,5 @@
import { skipBuildJarsEnvName } from "../shared/constants"; import { skipBuildJarsEnvName } from "../shared/constants";
import * as child_process from "child_process"; import * as child_process from "child_process";
import chalk from "chalk";
import { Deferred } from "evt/tools/Deferred"; import { Deferred } from "evt/tools/Deferred";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { BuildOptions } from "../shared/buildOptions"; import type { BuildOptions } from "../shared/buildOptions";
@ -34,15 +33,6 @@ export async function keycloakifyBuild(params: {
child.stderr.on("data", data => process.stderr.write(data)); child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => {
if (code !== 0) {
console.log(chalk.yellow("Theme not updated, build failed"));
return;
}
console.log(chalk.green("Rebuild done"));
});
child.on("exit", code => dResult.resolve({ isSuccess: code === 0 })); child.on("exit", code => dResult.resolve({ isSuccess: code === 0 }));
const { isSuccess } = await dResult.pr; const { isSuccess } = await dResult.pr;

View File

@ -2,11 +2,7 @@ import { readBuildOptions } from "../shared/buildOptions";
import type { CliCommandOptions as CliCommandOptions_common } from "../main"; import type { CliCommandOptions as CliCommandOptions_common } from "../main";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion"; import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes"; import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes";
import { import { accountV1ThemeName, containerName } from "../shared/constants";
accountV1ThemeName,
skipBuildJarsEnvName,
containerName
} from "../shared/constants";
import { SemVer } from "../tools/SemVer"; import { SemVer } from "../tools/SemVer";
import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange"; import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange";
import { getJarFileBasename } from "../shared/getJarFileBasename"; import { getJarFileBasename } from "../shared/getJarFileBasename";
@ -18,11 +14,12 @@ import chalk from "chalk";
import chokidar from "chokidar"; import chokidar from "chokidar";
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce"; 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 { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import cliSelect from "cli-select"; import cliSelect from "cli-select";
import * as runExclusive from "run-exclusive"; import * as runExclusive from "run-exclusive";
import { extractArchive } from "../tools/extractArchive"; import { extractArchive } from "../tools/extractArchive";
import { appBuild } from "./appBuild";
import { keycloakifyBuild } from "./keycloakifyBuild";
export type CliCommandOptions = CliCommandOptions_common & { export type CliCommandOptions = CliCommandOptions_common & {
port: number; port: number;
@ -85,20 +82,36 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const buildOptions = readBuildOptions({ cliCommandOptions }); const buildOptions = readBuildOptions({ cliCommandOptions });
exit_if_theme_not_built: { {
if (fs.existsSync(buildOptions.keycloakifyBuildDirPath)) { const { isAppBuildSuccess } = await appBuild({
break exit_if_theme_not_built; doSkipIfReactAppBuildDirExists: true,
} buildOptions
});
if (!isAppBuildSuccess) {
console.log( console.log(
[ chalk.red(
`${chalk.red("The theme has not been built.")}`, `App build failed, exiting. Try running 'yarn build-keycloak-theme' and see what's wrong.`
`Please run ${chalk.bold("npx vite && npx keycloakify build")} first.` )
].join(" ")
); );
process.exit(1); process.exit(1);
} }
const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
doSkipBuildJars: false,
buildOptions
});
if (!isKeycloakifyBuildSuccess) {
console.log(
chalk.red(
`Keycloakify build failed, exiting. Try running 'yarn build-keycloak-theme' and see what's wrong.`
)
);
process.exit(1);
}
}
const metaInfKeycloakThemes = readMetaInfKeycloakThemes({ const metaInfKeycloakThemes = readMetaInfKeycloakThemes({
keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath keycloakifyBuildDirPath: buildOptions.keycloakifyBuildDirPath
}); });
@ -255,11 +268,12 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename); const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename);
let doLinkAccountV1Theme = false; const { doUseBuiltInAccountV1Theme } = await (async () => {
let doUseBuiltInAccountV1Theme = false;
await extractArchive({ await extractArchive({
archiveFilePath: jarFilePath, archiveFilePath: jarFilePath,
onArchiveFile: async ({ relativeFilePathInArchive, readFile, writeFile }) => { onArchiveFile: async ({ relativeFilePathInArchive, readFile, earlyExit }) => {
for (const themeName of buildOptions.themeNames) { for (const themeName of buildOptions.themeNames) {
if ( if (
relativeFilePathInArchive === relativeFilePathInArchive ===
@ -268,13 +282,25 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
if ( if (
(await readFile()) (await readFile())
.toString("utf8") .toString("utf8")
.includes(`parent=${accountV1ThemeName}`) .includes(`parent=keycloak`)
) { ) {
doLinkAccountV1Theme = true; doUseBuiltInAccountV1Theme = true;
} }
await writeFile({ earlyExit();
filePath: pathJoin( }
}
}
});
return { doUseBuiltInAccountV1Theme };
})();
const accountThemePropertyPatch = !doUseBuiltInAccountV1Theme
? undefined
: () => {
for (const themeName of buildOptions.themeNames) {
const filePath = pathJoin(
buildOptions.keycloakifyBuildDirPath, buildOptions.keycloakifyBuildDirPath,
"src", "src",
"main", "main",
@ -283,12 +309,24 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
themeName, themeName,
"account", "account",
"theme.properties" "theme.properties"
) );
});
const sourceCode = fs.readFileSync(filePath);
const modifiedSourceCode = Buffer.from(
sourceCode
.toString("utf8")
.replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
"utf8"
);
assert(Buffer.compare(modifiedSourceCode, sourceCode) !== 0);
fs.writeFileSync(filePath, modifiedSourceCode);
} }
} };
}
}); accountThemePropertyPatch?.();
const spawnArgs = [ const spawnArgs = [
"docker", "docker",
@ -389,67 +427,30 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
} }
{ {
const runBuildKeycloakTheme = runExclusive.build(async () => { const runFullBuild = runExclusive.build(async () => {
console.log(chalk.cyan("Detected changes in the theme. Rebuilding ...")); console.log(chalk.cyan("Detected changes in the theme. Rebuilding ..."));
{ const { isAppBuildSuccess } = await appBuild({
const dResult = new Deferred<{ isSuccess: boolean }>(); doSkipIfReactAppBuildDirExists: false,
buildOptions
const child = child_process.spawn("npx", ["vite", "build"], {
cwd: buildOptions.reactAppRootDirPath
}); });
child.stdout.on("data", data => { if (!isAppBuildSuccess) {
if (data.toString("utf8").includes("gzip:")) {
return; return;
} }
process.stdout.write(data); const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
doSkipBuildJars: true,
buildOptions
}); });
child.stderr.on("data", data => process.stderr.write(data)); if (!isKeycloakifyBuildSuccess) {
child.on("exit", code => dResult.resolve({ isSuccess: code === 0 }));
const { isSuccess } = await dResult.pr;
if (!isSuccess) {
return;
}
}
{
const dResult = new Deferred<{ isSuccess: boolean }>();
const child = child_process.spawn("npx", ["keycloakify", "build"], {
cwd: buildOptions.reactAppRootDirPath,
env: {
...process.env,
[skipBuildJarsEnvName]: "true"
}
});
child.stdout.on("data", data => process.stdout.write(data));
child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => {
if (code !== 0) {
console.log(chalk.yellow("Theme not updated, build failed"));
return; return;
} }
console.log(chalk.green("Rebuild done")); accountThemePropertyPatch?.();
});
child.on("exit", code => dResult.resolve({ isSuccess: code === 0 })); console.log(chalk.green("Theme rebuilt and updated in Keycloak."));
const { isSuccess } = await dResult.pr;
if (!isSuccess) {
return;
}
}
}); });
const { waitForDebounce } = waitForDebounceFactory({ delay: 400 }); const { waitForDebounce } = waitForDebounceFactory({ delay: 400 });
@ -461,7 +462,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
.on("all", async () => { .on("all", async () => {
await waitForDebounce(); await waitForDebounce();
runBuildKeycloakTheme(); runFullBuild();
}); });
} }
} }

View File

@ -11,6 +11,7 @@ export async function extractArchive(params: {
relativeFilePathInArchive: string; relativeFilePathInArchive: string;
readFile: () => Promise<Buffer>; readFile: () => Promise<Buffer>;
writeFile: (params: { filePath: string; modifiedData?: Buffer }) => Promise<void>; writeFile: (params: { filePath: string; modifiedData?: Buffer }) => Promise<void>;
earlyExit: () => void;
}) => Promise<void>; }) => Promise<void>;
}) { }) {
const { archiveFilePath, onArchiveFile } = params; const { archiveFilePath, onArchiveFile } = params;
@ -107,7 +108,11 @@ export async function extractArchive(params: {
await onArchiveFile({ await onArchiveFile({
relativeFilePathInArchive: entry.fileName.split("/").join(pathSep), relativeFilePathInArchive: entry.fileName.split("/").join(pathSep),
readFile: () => readFile(entry), readFile: () => readFile(entry),
writeFile: params => writeFile(entry, params) writeFile: params => writeFile(entry, params),
earlyExit: () => {
zipFile.close();
dDone.resolve();
}
}); });
} }