Refactor build option managment

This commit is contained in:
garronej
2023-09-03 23:26:34 +02:00
parent 8c3e9ff192
commit 18f0f3cce1
13 changed files with 148 additions and 128 deletions

View File

@ -1,7 +1,8 @@
import { parse as urlParse } from "url";
import { getParsedPackageJson } from "./parsedPackageJson";
import { join as pathJoin, sep as pathSep } from "path";
import { join as pathJoin } from "path";
import parseArgv from "minimist";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
/** Consolidated build option gathered form CLI arguments and config in package.json */
export type BuildOptions = {
@ -14,17 +15,20 @@ export type BuildOptions = {
artifactId: string;
doCreateJar: boolean;
loginThemeResourcesFromKeycloakVersion: string;
reactAppRootDirPath: string;
/** Directory of your built react project. Defaults to {cwd}/build */
reactAppBuildDirPath: string;
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
keycloakifyBuildDirPath: string;
publicDirPath: string;
cacheDirPath: string;
/** If your app is hosted under a subpath, it's the case in CRA if you have "homepage": "https://example.com/my-app" in your package.json
* In this case the urlPathname will be "/my-app/" */
urlPathname: string | undefined;
};
export function readBuildOptions(params: { projectDirPath: string; processArgv: string[] }): BuildOptions {
const { projectDirPath, processArgv } = params;
export function readBuildOptions(params: { reactAppRootDirPath: string; processArgv: string[] }): BuildOptions {
const { reactAppRootDirPath, processArgv } = params;
const { isSilentCliParamProvided } = (() => {
const argv = parseArgv(processArgv);
@ -34,7 +38,7 @@ export function readBuildOptions(params: { projectDirPath: string; processArgv:
};
})();
const parsedPackageJson = getParsedPackageJson({ projectDirPath });
const parsedPackageJson = getParsedPackageJson({ reactAppRootDirPath });
const { name, keycloakify = {}, version, homepage } = parsedPackageJson;
@ -55,6 +59,7 @@ export function readBuildOptions(params: { projectDirPath: string; processArgv:
.join("-");
return {
reactAppRootDirPath,
themeName,
extraThemeNames,
"doCreateJar": doCreateJar ?? true,
@ -78,40 +83,57 @@ export function readBuildOptions(params: { projectDirPath: string; processArgv:
extraThemeProperties,
"isSilent": isSilentCliParamProvided,
"loginThemeResourcesFromKeycloakVersion": loginThemeResourcesFromKeycloakVersion ?? "11.0.3",
"publicDirPath": (() => {
let { PUBLIC_DIR_PATH } = process.env;
if (PUBLIC_DIR_PATH !== undefined) {
return getAbsoluteAndInOsFormatPath({
"pathIsh": PUBLIC_DIR_PATH,
"cwd": reactAppRootDirPath
});
}
return pathJoin(reactAppRootDirPath, "public");
})(),
"reactAppBuildDirPath": (() => {
let { reactAppBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {};
const { reactAppBuildDirPath } = parsedPackageJson.keycloakify ?? {};
if (reactAppBuildDirPath === undefined) {
return pathJoin(projectDirPath, "build");
if (reactAppBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({
"pathIsh": reactAppBuildDirPath,
"cwd": reactAppRootDirPath
});
}
if (pathSep === "\\") {
reactAppBuildDirPath = reactAppBuildDirPath.replace(/\//g, pathSep);
}
if (reactAppBuildDirPath.startsWith(`.${pathSep}`)) {
return pathJoin(projectDirPath, reactAppBuildDirPath);
}
return reactAppBuildDirPath;
return pathJoin(reactAppRootDirPath, "build");
})(),
"keycloakifyBuildDirPath": (() => {
let { keycloakifyBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {};
const { keycloakifyBuildDirPath } = parsedPackageJson.keycloakify ?? {};
if (keycloakifyBuildDirPath === undefined) {
return pathJoin(projectDirPath, "build_keycloak");
if (keycloakifyBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({
"pathIsh": keycloakifyBuildDirPath,
"cwd": reactAppRootDirPath
});
}
if (pathSep === "\\") {
keycloakifyBuildDirPath = keycloakifyBuildDirPath.replace(/\//g, pathSep);
}
if (keycloakifyBuildDirPath.startsWith(`.${pathSep}`)) {
return pathJoin(projectDirPath, keycloakifyBuildDirPath);
}
return keycloakifyBuildDirPath;
return pathJoin(reactAppRootDirPath, "build_keycloak");
})(),
"cacheDirPath": pathJoin(
(() => {
let { XDG_CACHE_HOME } = process.env;
if (XDG_CACHE_HOME !== undefined) {
return getAbsoluteAndInOsFormatPath({
"pathIsh": XDG_CACHE_HOME,
"cwd": reactAppRootDirPath
});
}
return pathJoin(reactAppRootDirPath, "node_modules", ".cache");
})(),
"keycloakify"
),
"urlPathname": (() => {
const { homepage } = parsedPackageJson;

View File

@ -13,6 +13,7 @@ export type BuildOptionsLike = {
groupId: string;
artifactId: string;
themeVersion: string;
cacheDirPath: string;
};
{
@ -22,19 +23,13 @@ export type BuildOptionsLike = {
}
export async function generateJavaStackFiles(params: {
projectDirPath: string;
keycloakThemeBuildingDirPath: string;
implementedThemeTypes: Record<ThemeType | "email", boolean>;
buildOptions: BuildOptionsLike;
}): Promise<{
jarFilePath: string;
}> {
const {
projectDirPath,
buildOptions: { groupId, themeName, extraThemeNames, themeVersion, artifactId },
keycloakThemeBuildingDirPath,
implementedThemeTypes
} = params;
const { keycloakThemeBuildingDirPath, implementedThemeTypes, buildOptions } = params;
{
const { pomFileCode } = (function generatePomFileCode(): {
@ -46,10 +41,10 @@ export async function generateJavaStackFiles(params: {
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
` <modelVersion>4.0.0</modelVersion>`,
` <groupId>${groupId}</groupId>`,
` <artifactId>${artifactId}</artifactId>`,
` <version>${themeVersion}</version>`,
` <name>${artifactId}</name>`,
` <groupId>${buildOptions.groupId}</groupId>`,
` <artifactId>${buildOptions.artifactId}</artifactId>`,
` <version>${buildOptions.themeVersion}</version>`,
` <name>${buildOptions.artifactId}</name>`,
` <description />`,
` <packaging>jar</packaging>`,
` <properties>`,
@ -165,9 +160,9 @@ export async function generateJavaStackFiles(params: {
const builtinKeycloakThemeTmpDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "tmp_yxdE2_builtin_keycloak_theme");
await downloadBuiltinKeycloakTheme({
projectDirPath,
"destDirPath": builtinKeycloakThemeTmpDirPath,
"keycloakVersion": lastKeycloakVersionWithAccountV1
"keycloakVersion": lastKeycloakVersionWithAccountV1,
buildOptions
});
const accountV1DirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", accountV1, "account");
@ -249,7 +244,7 @@ export async function generateJavaStackFiles(params: {
"name": accountV1,
"types": ["account"]
},
...[themeName, ...extraThemeNames].map(themeName => ({
...[buildOptions.themeName, ...buildOptions.extraThemeNames].map(themeName => ({
"name": themeName,
"types": Object.entries(implementedThemeTypes)
.filter(([, isImplemented]) => isImplemented)
@ -266,6 +261,6 @@ export async function generateJavaStackFiles(params: {
}
return {
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${artifactId}-${themeVersion}.jar`)
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`)
};
}

View File

@ -3,22 +3,30 @@ import * as fs from "fs";
import { join as pathJoin } from "path";
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
import { resources_common, type ThemeType } from "../../constants";
import { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert";
import * as crypto from "crypto";
export type BuildOptionsLike = {
cacheDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function downloadKeycloakStaticResources(
// prettier-ignore
params: {
projectDirPath: string;
themeType: ThemeType;
themeDirPath: string;
keycloakVersion: string;
usedResources: {
resourcesCommonFilePaths: string[];
resourcesFilePaths: string[];
} | undefined
} | undefined;
buildOptions: BuildOptionsLike;
}
) {
const { projectDirPath, themeType, themeDirPath, keycloakVersion, usedResources } = params;
const { themeType, themeDirPath, keycloakVersion, usedResources, buildOptions } = params;
const tmpDirPath = pathJoin(
themeDirPath,
@ -27,9 +35,9 @@ export async function downloadKeycloakStaticResources(
);
await downloadBuiltinKeycloakTheme({
projectDirPath,
keycloakVersion,
"destDirPath": tmpDirPath
"destDirPath": tmpDirPath,
buildOptions
});
const resourcesPath = pathJoin(themeDirPath, themeType, "resources");

View File

@ -20,31 +20,23 @@ export type BuildOptionsLike = {
themeVersion: string;
loginThemeResourcesFromKeycloakVersion: string;
urlPathname: string | undefined;
keycloakifyBuildDirPath: string;
reactAppBuildDirPath: string;
cacheDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function generateTheme(params: {
projectDirPath: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
themeSrcDirPath: string;
keycloakifySrcDirPath: string;
buildOptions: BuildOptionsLike;
keycloakifyVersion: string;
}): Promise<void> {
const {
projectDirPath,
reactAppBuildDirPath,
keycloakThemeBuildingDirPath,
themeSrcDirPath,
keycloakifySrcDirPath,
buildOptions,
keycloakifyVersion
} = params;
const { themeSrcDirPath, keycloakifySrcDirPath, buildOptions, keycloakifyVersion } = params;
const getThemeDirPath = (themeType: ThemeType | "email") =>
pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, themeType);
pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", buildOptions.themeName, themeType);
let allCssGlobalsToDefine: Record<string, string> = {};
@ -66,12 +58,12 @@ export async function generateTheme(params: {
transformCodebase({
"destDirPath": pathJoin(themeDirPath, "resources", "build"),
"srcDirPath": reactAppBuildDirPath,
"srcDirPath": buildOptions.reactAppBuildDirPath,
"transformSourceCode": ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
if (
isInside({
"dirPath": pathJoin(reactAppBuildDirPath, keycloak_resources),
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
filePath
})
) {
@ -114,7 +106,7 @@ export async function generateTheme(params: {
generateFtlFilesCode_glob !== undefined
? generateFtlFilesCode_glob
: generateFtlFilesCodeFactory({
"indexHtmlCode": fs.readFileSync(pathJoin(reactAppBuildDirPath, "index.html")).toString("utf8"),
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
"cssGlobalsToDefine": allCssGlobalsToDefine,
buildOptions,
keycloakifyVersion,
@ -161,7 +153,6 @@ export async function generateTheme(params: {
});
await downloadKeycloakStaticResources({
projectDirPath,
"keycloakVersion": (() => {
switch (themeType) {
case "account":
@ -176,7 +167,8 @@ export async function generateTheme(params: {
keycloakifySrcDirPath,
themeSrcDirPath,
themeType
})
}),
buildOptions
});
fs.writeFileSync(

View File

@ -12,10 +12,10 @@ import { getProjectRoot } from "../tools/getProjectRoot";
import { objectKeys } from "tsafe/objectKeys";
export async function main() {
const projectDirPath = process.cwd();
const reactAppRootDirPath = process.cwd();
const buildOptions = readBuildOptions({
projectDirPath,
reactAppRootDirPath,
"processArgv": process.argv.slice(2)
});
@ -24,15 +24,12 @@ export async function main() {
const keycloakifyDirPath = getProjectRoot();
const { themeSrcDirPath } = getThemeSrcDirPath({ projectDirPath });
const { themeSrcDirPath } = getThemeSrcDirPath({ reactAppRootDirPath });
for (const themeName of [buildOptions.themeName, ...buildOptions.extraThemeNames]) {
await generateTheme({
projectDirPath,
"keycloakThemeBuildingDirPath": buildOptions.keycloakifyBuildDirPath,
themeSrcDirPath,
"keycloakifySrcDirPath": pathJoin(keycloakifyDirPath, "src"),
"reactAppBuildDirPath": buildOptions.reactAppBuildDirPath,
"buildOptions": {
...buildOptions,
"themeName": themeName
@ -48,7 +45,6 @@ export async function main() {
}
const { jarFilePath } = await generateJavaStackFiles({
projectDirPath,
"keycloakThemeBuildingDirPath": buildOptions.keycloakifyBuildDirPath,
"implementedThemeTypes": (() => {
const implementedThemeTypes = {
@ -89,7 +85,7 @@ export async function main() {
logger.log(
[
"",
`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(projectDirPath, jarFilePath)} 🚀`,
`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(reactAppRootDirPath, jarFilePath)} 🚀`,
`It is to be placed in "/opt/keycloak/providers" in the container running a quay.io/keycloak/keycloak Docker image.`,
"",
//TODO: Restore when we find a good Helm chart for Keycloak.
@ -124,7 +120,7 @@ export async function main() {
`To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
"",
`👉 $ .${pathSep}${pathRelative(
projectDirPath,
reactAppRootDirPath,
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename)
)} 👈`,
"",

View File

@ -45,11 +45,11 @@ export const zParsedPackageJson = z.object({
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
let parsedPackageJson: undefined | ReturnType<(typeof zParsedPackageJson)["parse"]>;
export function getParsedPackageJson(params: { projectDirPath: string }) {
const { projectDirPath } = params;
export function getParsedPackageJson(params: { reactAppRootDirPath: string }) {
const { reactAppRootDirPath } = params;
if (parsedPackageJson) {
return parsedPackageJson;
}
parsedPackageJson = zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(projectDirPath, "package.json")).toString("utf8")));
parsedPackageJson = zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(reactAppRootDirPath, "package.json")).toString("utf8")));
return parsedPackageJson;
}