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,18 +82,34 @@ 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(
chalk.red(
`App build failed, exiting. Try running 'yarn build-keycloak-theme' and see what's wrong.`
)
);
process.exit(1);
} }
console.log( const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
[ doSkipBuildJars: false,
`${chalk.red("The theme has not been built.")}`, buildOptions
`Please run ${chalk.bold("npx vite && npx keycloakify build")} first.` });
].join(" ")
); if (!isKeycloakifyBuildSuccess) {
process.exit(1); 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({
@ -255,40 +268,65 @@ 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 (
relativeFilePathInArchive ===
pathJoin("theme", themeName, "account", "theme.properties")
) {
if ( if (
(await readFile()) relativeFilePathInArchive ===
.toString("utf8") pathJoin("theme", themeName, "account", "theme.properties")
.includes(`parent=${accountV1ThemeName}`)
) { ) {
doLinkAccountV1Theme = true; if (
} (await readFile())
.toString("utf8")
.includes(`parent=keycloak`)
) {
doUseBuiltInAccountV1Theme = true;
}
await writeFile({ earlyExit();
filePath: pathJoin( }
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"theme",
themeName,
"account",
"theme.properties"
)
});
} }
} }
} });
});
return { doUseBuiltInAccountV1Theme };
})();
const accountThemePropertyPatch = !doUseBuiltInAccountV1Theme
? undefined
: () => {
for (const themeName of buildOptions.themeNames) {
const filePath = pathJoin(
buildOptions.keycloakifyBuildDirPath,
"src",
"main",
"resources",
"theme",
themeName,
"account",
"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"], { if (!isAppBuildSuccess) {
cwd: buildOptions.reactAppRootDirPath return;
});
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));
child.on("exit", code => dResult.resolve({ isSuccess: code === 0 }));
const { isSuccess } = await dResult.pr;
if (!isSuccess) {
return;
}
} }
{ const { isKeycloakifyBuildSuccess } = await keycloakifyBuild({
const dResult = new Deferred<{ isSuccess: boolean }>(); doSkipBuildJars: true,
buildOptions
});
const child = child_process.spawn("npx", ["keycloakify", "build"], { if (!isKeycloakifyBuildSuccess) {
cwd: buildOptions.reactAppRootDirPath, return;
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;
}
console.log(chalk.green("Rebuild done"));
});
child.on("exit", code => dResult.resolve({ isSuccess: code === 0 }));
const { isSuccess } = await dResult.pr;
if (!isSuccess) {
return;
}
} }
accountThemePropertyPatch?.();
console.log(chalk.green("Theme rebuilt and updated in Keycloak."));
}); });
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();
}
}); });
} }