From 53955a0713e1e48ab09022b7d39d52535945ed9a Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sun, 26 May 2024 17:58:41 +0200 Subject: [PATCH] Build the app when running npx keycloak start-keycloak --- src/bin/start-keycloak/appBuild.ts | 128 +++++++++++++++++++++ src/bin/start-keycloak/keycloakifyBuild.ts | 51 ++++++++ 2 files changed, 179 insertions(+) create mode 100644 src/bin/start-keycloak/appBuild.ts create mode 100644 src/bin/start-keycloak/keycloakifyBuild.ts diff --git a/src/bin/start-keycloak/appBuild.ts b/src/bin/start-keycloak/appBuild.ts new file mode 100644 index 00000000..78176f34 --- /dev/null +++ b/src/bin/start-keycloak/appBuild.ts @@ -0,0 +1,128 @@ +import * as child_process from "child_process"; +import { Deferred } from "evt/tools/Deferred"; +import { assert } from "tsafe/assert"; +import { is } from "tsafe/is"; +import type { BuildOptions } from "../shared/buildOptions"; +import * as fs from "fs"; +import { join as pathJoin } from "path"; + +export type BuildOptionsLike = { + reactAppRootDirPath: string; + keycloakifyBuildDirPath: string; + bundler: "vite" | "webpack"; + npmWorkspaceRootDirPath: string; + reactAppBuildDirPath: string; +}; + +assert(); + +export async function appBuild(params: { + doSkipIfReactAppBuildDirExists: boolean; + buildOptions: BuildOptionsLike; +}): Promise<{ isAppBuildSuccess: boolean }> { + const { doSkipIfReactAppBuildDirExists, buildOptions } = params; + + if ( + doSkipIfReactAppBuildDirExists && + fs.existsSync(buildOptions.reactAppBuildDirPath) + ) { + return { isAppBuildSuccess: true }; + } + + const { bundler } = buildOptions; + + const { command, args, cwd } = (() => { + switch (bundler) { + case "vite": + return { + command: "npx", + args: ["vite", "build"], + cwd: buildOptions.reactAppRootDirPath + }; + case "webpack": { + for (const dirPath of [ + buildOptions.reactAppRootDirPath, + buildOptions.npmWorkspaceRootDirPath + ]) { + try { + const parsedPackageJson = JSON.parse( + fs + .readFileSync(pathJoin(dirPath, "package.json")) + .toString("utf8") + ); + + const [scriptName] = + Object.entries(parsedPackageJson.scripts).find( + ([, scriptValue]) => { + assert(is(scriptValue)); + if ( + scriptValue.includes("webpack") && + scriptValue.includes("--mode production") + ) { + return true; + } + + if ( + scriptValue.includes("react-scripts") && + scriptValue.includes("build") + ) { + return true; + } + + if ( + scriptValue.includes("react-app-rewired") && + scriptValue.includes("build") + ) { + return true; + } + + if ( + scriptValue.includes("craco") && + scriptValue.includes("build") + ) { + return true; + } + } + ) ?? []; + + if (scriptName === undefined) { + continue; + } + + return { + command: "npm", + args: ["run", scriptName], + cwd: dirPath + }; + } catch { + continue; + } + } + + throw new Error( + "Keycloakify was unable to determine which script is responsible for building the app." + ); + } + } + })(); + + const dResult = new Deferred<{ isSuccess: boolean }>(); + + const child = child_process.spawn(command, args, { cwd }); + + 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; + + return { isAppBuildSuccess: isSuccess }; +} diff --git a/src/bin/start-keycloak/keycloakifyBuild.ts b/src/bin/start-keycloak/keycloakifyBuild.ts new file mode 100644 index 00000000..c9ab8018 --- /dev/null +++ b/src/bin/start-keycloak/keycloakifyBuild.ts @@ -0,0 +1,51 @@ +import { skipBuildJarsEnvName } from "../shared/constants"; +import * as child_process from "child_process"; +import chalk from "chalk"; +import { Deferred } from "evt/tools/Deferred"; +import { assert } from "tsafe/assert"; +import type { BuildOptions } from "../shared/buildOptions"; + +export type BuildOptionsLike = { + reactAppRootDirPath: string; + keycloakifyBuildDirPath: string; + bundler: "vite" | "webpack"; + npmWorkspaceRootDirPath: string; +}; + +assert(); + +export async function keycloakifyBuild(params: { + doSkipBuildJars: boolean; + buildOptions: BuildOptionsLike; +}): Promise<{ isKeycloakifyBuildSuccess: boolean }> { + const { buildOptions, doSkipBuildJars } = params; + + const dResult = new Deferred<{ isSuccess: boolean }>(); + + const child = child_process.spawn("npx", ["keycloakify", "build"], { + cwd: buildOptions.reactAppRootDirPath, + env: { + ...process.env, + ...(doSkipBuildJars ? {} : { [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; + + return { isKeycloakifyBuildSuccess: isSuccess }; +}