From 53e38336fbf6ae81f04cff5e25dc2ae647959eef Mon Sep 17 00:00:00 2001 From: garronej Date: Sun, 2 Apr 2023 03:10:16 +0200 Subject: [PATCH] #287 Refacror --- src/bin/download-builtin-keycloak-theme.ts | 14 ++++- src/bin/eject-keycloak-page.ts | 8 +-- src/bin/getSrcDirPath.ts | 43 +++++++++++++ src/bin/initialize-email-theme.ts | 6 +- src/bin/keycloakify/BuildOptions.ts | 63 ++++++++++++++++--- src/bin/keycloakify/build-paths.ts | 72 ---------------------- src/bin/keycloakify/keycloakify.ts | 35 +++++------ src/bin/keycloakify/parsedPackageJson.ts | 22 ++++--- test/bin/setupSampleReactProject.spec.ts | 29 +++++++-- 9 files changed, 166 insertions(+), 126 deletions(-) create mode 100644 src/bin/getSrcDirPath.ts delete mode 100644 src/bin/keycloakify/build-paths.ts diff --git a/src/bin/download-builtin-keycloak-theme.ts b/src/bin/download-builtin-keycloak-theme.ts index e3ac6ba3..1735c1ae 100644 --- a/src/bin/download-builtin-keycloak-theme.ts +++ b/src/bin/download-builtin-keycloak-theme.ts @@ -4,7 +4,7 @@ import { downloadAndUnzip } from "./tools/downloadAndUnzip"; import { promptKeycloakVersion } from "./promptKeycloakVersion"; import { getCliOptions } from "./tools/cliOptions"; import { getLogger } from "./tools/logger"; -import { getKeycloakBuildPath } from "./keycloakify/build-paths"; +import { readBuildOptions } from "./keycloakify/BuildOptions"; export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) { const { keycloakVersion, destDirPath } = params; @@ -25,7 +25,17 @@ async function main() { const logger = getLogger({ isSilent }); const { keycloakVersion } = await promptKeycloakVersion(); - const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme"); + const destDirPath = pathJoin( + readBuildOptions({ + "isSilent": true, + "isExternalAssetsCliParamProvided": false, + "projectDirPath": process.cwd() + }).keycloakifyBuildDirPath, + "src", + "main", + "resources", + "theme" + ); logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`); diff --git a/src/bin/eject-keycloak-page.ts b/src/bin/eject-keycloak-page.ts index f6094c54..e60737bb 100644 --- a/src/bin/eject-keycloak-page.ts +++ b/src/bin/eject-keycloak-page.ts @@ -16,10 +16,10 @@ import { existsSync } from "fs"; import { join as pathJoin, relative as pathRelative } from "path"; import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase"; import { assert, Equals } from "tsafe/assert"; -import { getThemeSrcDirPath } from "./keycloakify/build-paths"; +import { getThemeSrcDirPath } from "./getSrcDirPath"; (async () => { - const projectRootDir = getProjectRoot(); + const projectDirPath = getProjectRoot(); console.log("Select a theme type"); @@ -51,7 +51,7 @@ import { getThemeSrcDirPath } from "./keycloakify/build-paths"; const pageBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(/ftl$/, "tsx"); - const { themeSrcDirPath } = getThemeSrcDirPath(); + const { themeSrcDirPath } = getThemeSrcDirPath({ "projectDirPath": projectDirPath }); if (themeSrcDirPath === undefined) { throw new Error("Couldn't locate your theme sources"); @@ -65,7 +65,7 @@ import { getThemeSrcDirPath } from "./keycloakify/build-paths"; process.exit(-1); } - writeFile(targetFilePath, await readFile(pathJoin(projectRootDir, "src", themeType, "pages", pageBasename))); + writeFile(targetFilePath, await readFile(pathJoin(projectDirPath, "src", themeType, "pages", pageBasename))); console.log(`${pathRelative(process.cwd(), targetFilePath)} created`); })(); diff --git a/src/bin/getSrcDirPath.ts b/src/bin/getSrcDirPath.ts new file mode 100644 index 00000000..ce3c8b9e --- /dev/null +++ b/src/bin/getSrcDirPath.ts @@ -0,0 +1,43 @@ +import * as fs from "fs"; +import { exclude } from "tsafe"; +import { crawl } from "./tools/crawl"; +import { join as pathJoin } from "path"; + +const themeSrcDirBasename = "keycloak-theme"; + +export function getThemeSrcDirPath(params: { projectDirPath: string }) { + const { projectDirPath } = params; + + const srcDirPath = pathJoin(projectDirPath, "src"); + + const themeSrcDirPath: string | undefined = crawl(srcDirPath) + .map(fileRelativePath => { + const split = fileRelativePath.split(themeSrcDirBasename); + + if (split.length !== 2) { + return undefined; + } + + return pathJoin(srcDirPath, split[0] + themeSrcDirBasename); + }) + .filter(exclude(undefined))[0]; + + if (themeSrcDirPath === undefined) { + if (fs.existsSync(pathJoin(srcDirPath, "login")) || fs.existsSync(pathJoin(srcDirPath, "account"))) { + return { "themeSrcDirPath": srcDirPath }; + } + return { "themeSrcDirPath": undefined }; + } + + return { themeSrcDirPath }; +} + +export function getEmailThemeSrcDirPath(params: { projectDirPath: string }) { + const { projectDirPath } = params; + + const { themeSrcDirPath } = getThemeSrcDirPath({ projectDirPath }); + + const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email"); + + return { emailThemeSrcDirPath }; +} diff --git a/src/bin/initialize-email-theme.ts b/src/bin/initialize-email-theme.ts index 5f08f811..1c9cce24 100644 --- a/src/bin/initialize-email-theme.ts +++ b/src/bin/initialize-email-theme.ts @@ -7,13 +7,15 @@ import { promptKeycloakVersion } from "./promptKeycloakVersion"; import * as fs from "fs"; import { getCliOptions } from "./tools/cliOptions"; import { getLogger } from "./tools/logger"; -import { getEmailThemeSrcDirPath } from "./keycloakify/build-paths"; +import { getEmailThemeSrcDirPath } from "./getSrcDirPath"; export async function main() { const { isSilent } = getCliOptions(process.argv.slice(2)); const logger = getLogger({ isSilent }); - const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath(); + const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath({ + "projectDirPath": process.cwd() + }); if (emailThemeSrcDirPath === undefined) { logger.warn("Couldn't locate your theme source directory"); diff --git a/src/bin/keycloakify/BuildOptions.ts b/src/bin/keycloakify/BuildOptions.ts index b3321a04..aa68cd41 100644 --- a/src/bin/keycloakify/BuildOptions.ts +++ b/src/bin/keycloakify/BuildOptions.ts @@ -4,7 +4,8 @@ import { parse as urlParse } from "url"; import { typeGuard } from "tsafe/typeGuard"; import { symToStr } from "tsafe/symToStr"; import { bundlers, getParsedPackageJson, type Bundler } from "./parsedPackageJson"; -import { getAppInputPath, getKeycloakBuildPath } from "./build-paths"; +import * as fs from "fs"; +import { join as pathJoin, sep as pathSep } from "path"; /** Consolidated build option gathered form CLI arguments and config in package.json */ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets; @@ -21,10 +22,10 @@ export namespace BuildOptions { artifactId: string; bundler: Bundler; keycloakVersionDefaultAssets: string; - // Directory of your built react project. Defaults to {cwd}/build - appInputPath: string; - // Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak - keycloakBuildPath: 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; customUserAttributes: string[]; }; @@ -52,10 +53,10 @@ export namespace BuildOptions { } } -export function readBuildOptions(params: { CNAME: string | undefined; isExternalAssetsCliParamProvided: boolean; isSilent: boolean }): BuildOptions { - const { CNAME, isExternalAssetsCliParamProvided, isSilent } = params; +export function readBuildOptions(params: { projectDirPath: string; isExternalAssetsCliParamProvided: boolean; isSilent: boolean }): BuildOptions { + const { projectDirPath, isExternalAssetsCliParamProvided, isSilent } = params; - const parsedPackageJson = getParsedPackageJson(); + const parsedPackageJson = getParsedPackageJson({ projectDirPath }); const url = (() => { const { homepage } = parsedPackageJson; @@ -66,6 +67,16 @@ export function readBuildOptions(params: { CNAME: string | undefined; isExternal url = new URL(homepage); } + const CNAME = (() => { + const cnameFilePath = pathJoin(projectDirPath, "public", "CNAME"); + + if (!fs.existsSync(cnameFilePath)) { + return undefined; + } + + return fs.readFileSync(cnameFilePath).toString("utf8"); + })(); + if (CNAME !== undefined) { url = new URL(`https://${CNAME.replace(/\s+$/, "")}`); } @@ -134,8 +145,40 @@ export function readBuildOptions(params: { CNAME: string | undefined; isExternal extraThemeProperties, isSilent, "keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3", - appInputPath: getAppInputPath(), - keycloakBuildPath: getKeycloakBuildPath(), + "reactAppBuildDirPath": (() => { + let { reactAppBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {}; + + if (reactAppBuildDirPath === undefined) { + return pathJoin(projectDirPath, "build"); + } + + if (pathSep === "\\") { + reactAppBuildDirPath = reactAppBuildDirPath.replace(/\//g, pathSep); + } + + if (reactAppBuildDirPath.startsWith(`.${pathSep}`)) { + return pathJoin(projectDirPath, reactAppBuildDirPath); + } + + return reactAppBuildDirPath; + })(), + "keycloakifyBuildDirPath": (() => { + let { keycloakifyBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {}; + + if (keycloakifyBuildDirPath === undefined) { + return pathJoin(projectDirPath, "build_keycloak"); + } + + if (pathSep === "\\") { + keycloakifyBuildDirPath = keycloakifyBuildDirPath.replace(/\//g, pathSep); + } + + if (keycloakifyBuildDirPath.startsWith(`.${pathSep}`)) { + return pathJoin(projectDirPath, keycloakifyBuildDirPath); + } + + return keycloakifyBuildDirPath; + })(), "customUserAttributes": keycloakify.customUserAttributes ?? [] }; })(); diff --git a/src/bin/keycloakify/build-paths.ts b/src/bin/keycloakify/build-paths.ts deleted file mode 100644 index 2de13bcb..00000000 --- a/src/bin/keycloakify/build-paths.ts +++ /dev/null @@ -1,72 +0,0 @@ -import * as fs from "fs"; -import { exclude } from "tsafe"; -import { crawl } from "../tools/crawl"; -import { pathJoin } from "../tools/pathJoin"; -import { getParsedPackageJson } from "./parsedPackageJson"; - -const DEFAULT_APP_INPUT_PATH = "build"; - -const DEFAULT_KEYCLOAK_BUILD_PATH = "build_keycloak"; - -const THEME_SRC_DIR_BASENAME = "keycloak-theme"; - -export const getReactProjectDirPath = () => process.cwd(); - -export const getCnamePath = () => pathJoin(getReactProjectDirPath(), "public", "CNAME"); - -const parseAppInputPath = (path?: string) => { - if (!path) { - return pathJoin(process.cwd(), DEFAULT_APP_INPUT_PATH); - } else if (path.startsWith("./")) { - return pathJoin(process.cwd(), path.replace("./", "")); - } - return path; -}; - -const parseKeycloakBuildPath = (path?: string) => { - if (!path) { - return pathJoin(process.cwd(), DEFAULT_KEYCLOAK_BUILD_PATH); - } else if (path.startsWith("./")) { - return pathJoin(process.cwd(), path.replace("./", "")); - } - return path; -}; - -export const getAppInputPath = () => { - return parseAppInputPath(getParsedPackageJson().keycloakify?.appInputPath); -}; - -export const getKeycloakBuildPath = () => { - return parseKeycloakBuildPath(getParsedPackageJson().keycloakify?.keycloakBuildPath); -}; -export const getThemeSrcDirPath = () => { - const srcDirPath = pathJoin(getReactProjectDirPath(), "src"); - - const themeSrcDirPath: string | undefined = crawl(srcDirPath) - .map(fileRelativePath => { - const split = fileRelativePath.split(THEME_SRC_DIR_BASENAME); - - if (split.length !== 2) { - return undefined; - } - - return pathJoin(srcDirPath, split[0] + THEME_SRC_DIR_BASENAME); - }) - .filter(exclude(undefined))[0]; - if (themeSrcDirPath === undefined) { - if (fs.existsSync(pathJoin(srcDirPath, "login")) || fs.existsSync(pathJoin(srcDirPath, "account"))) { - return { "themeSrcDirPath": srcDirPath }; - } - return { "themeSrcDirPath": undefined }; - } - - return { themeSrcDirPath }; -}; - -export const getEmailThemeSrcDirPath = () => { - const { themeSrcDirPath } = getThemeSrcDirPath(); - - const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email"); - - return { emailThemeSrcDirPath }; -}; diff --git a/src/bin/keycloakify/keycloakify.ts b/src/bin/keycloakify/keycloakify.ts index b06f51d5..4817efd4 100644 --- a/src/bin/keycloakify/keycloakify.ts +++ b/src/bin/keycloakify/keycloakify.ts @@ -10,32 +10,25 @@ import { getCliOptions } from "../tools/cliOptions"; import jar from "../tools/jar"; import { assert } from "tsafe/assert"; import { Equals } from "tsafe"; -import { getEmailThemeSrcDirPath } from "./build-paths"; -import { getCnamePath, getAppInputPath, getKeycloakBuildPath, getReactProjectDirPath } from "./build-paths"; +import { getEmailThemeSrcDirPath } from "../getSrcDirPath"; export async function main() { const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2)); const logger = getLogger({ isSilent }); logger.log("🔏 Building the keycloak theme...⌚"); + const projectDirPath = process.cwd(); + const buildOptions = readBuildOptions({ - "CNAME": (() => { - const cnameFilePath = getCnamePath(); - - if (!fs.existsSync(cnameFilePath)) { - return undefined; - } - - return fs.readFileSync(cnameFilePath).toString("utf8"); - })(), + projectDirPath, "isExternalAssetsCliParamProvided": hasExternalAssets, "isSilent": isSilent }); const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({ - keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath, + keycloakThemeBuildingDirPath: buildOptions.keycloakifyBuildDirPath, "emailThemeSrcDirPath": (() => { - const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath(); + const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath({ projectDirPath }); if (emailThemeSrcDirPath === undefined || !fs.existsSync(emailThemeSrcDirPath)) { return; @@ -43,13 +36,13 @@ export async function main() { return emailThemeSrcDirPath; })(), - "reactAppBuildDirPath": getAppInputPath(), + "reactAppBuildDirPath": buildOptions.reactAppBuildDirPath, buildOptions, "keycloakVersion": buildOptions.keycloakVersionDefaultAssets }); const { jarFilePath } = generateJavaStackFiles({ - keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath, + keycloakThemeBuildingDirPath: buildOptions.keycloakifyBuildDirPath, doBundlesEmailTemplate, buildOptions }); @@ -61,7 +54,7 @@ export async function main() { case "keycloakify": logger.log("🫶 Let keycloakify do its thang"); await jar({ - "rootPath": pathJoin(buildOptions.keycloakBuildPath, "src", "main", "resources"), + "rootPath": pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources"), "version": buildOptions.version, "groupId": buildOptions.groupId, "artifactId": buildOptions.artifactId, @@ -70,7 +63,7 @@ export async function main() { break; case "mvn": logger.log("🫙 Run maven to deliver a jar"); - child_process.execSync("mvn package", { "cwd": buildOptions.keycloakBuildPath }); + child_process.execSync("mvn package", { "cwd": buildOptions.keycloakifyBuildDirPath }); break; default: assert>(false); @@ -80,7 +73,7 @@ export async function main() { const containerKeycloakVersion = "20.0.1"; generateStartKeycloakTestingContainer({ - keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath, + keycloakThemeBuildingDirPath: buildOptions.keycloakifyBuildDirPath, "keycloakVersion": containerKeycloakVersion, buildOptions }); @@ -88,7 +81,7 @@ export async function main() { logger.log( [ "", - `✅ Your keycloak theme has been generated and bundled into ./${pathRelative(getReactProjectDirPath(), jarFilePath)} 🚀`, + `✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(projectDirPath, 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. @@ -123,8 +116,8 @@ 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( - getReactProjectDirPath(), - pathJoin(getKeycloakBuildPath(), generateStartKeycloakTestingContainer.basename) + projectDirPath, + pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename) )} 👈`, "", `Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags`, diff --git a/src/bin/keycloakify/parsedPackageJson.ts b/src/bin/keycloakify/parsedPackageJson.ts index 5487bc96..4b0c9665 100644 --- a/src/bin/keycloakify/parsedPackageJson.ts +++ b/src/bin/keycloakify/parsedPackageJson.ts @@ -4,10 +4,9 @@ import type { Equals } from "tsafe"; import { z } from "zod"; import { pathJoin } from "../tools/pathJoin"; -const reactProjectDirPath = process.cwd(); export const bundlers = ["mvn", "keycloakify", "none"] as const; export type Bundler = (typeof bundlers)[number]; -type ParsedPackageJson = { +export type ParsedPackageJson = { name: string; version: string; homepage?: string; @@ -22,8 +21,8 @@ type ParsedPackageJson = { groupId?: string; bundler?: Bundler; keycloakVersionDefaultAssets?: string; - appInputPath?: string; - keycloakBuildPath?: string; + reactAppBuildDirPath?: string; + keycloakifyBuildDirPath?: string; customUserAttributes?: string[]; themeName?: string; }; @@ -44,8 +43,8 @@ const zParsedPackageJson = z.object({ "groupId": z.string().optional(), "bundler": z.enum(bundlers).optional(), "keycloakVersionDefaultAssets": z.string().optional(), - "appInputPath": z.string().optional(), - "keycloakBuildPath": z.string().optional(), + "reactAppBuildDirPath": z.string().optional(), + "keycloakifyBuildDirPath": z.string().optional(), "customUserAttributes": z.array(z.string()).optional(), "themeName": z.string().optional() }) @@ -55,8 +54,11 @@ const zParsedPackageJson = z.object({ assert, ParsedPackageJson>>(); let parsedPackageJson: undefined | ReturnType<(typeof zParsedPackageJson)["parse"]>; -export const getParsedPackageJson = () => { - if (parsedPackageJson) return parsedPackageJson; - parsedPackageJson = zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"))); +export function getParsedPackageJson(params: { projectDirPath: string }) { + const { projectDirPath } = params; + if (parsedPackageJson) { + return parsedPackageJson; + } + parsedPackageJson = zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(projectDirPath, "package.json")).toString("utf8"))); return parsedPackageJson; -}; +} diff --git a/test/bin/setupSampleReactProject.spec.ts b/test/bin/setupSampleReactProject.spec.ts index b5491c7e..327da6ac 100644 --- a/test/bin/setupSampleReactProject.spec.ts +++ b/test/bin/setupSampleReactProject.spec.ts @@ -4,8 +4,8 @@ import { join as pathJoin } from "path"; import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip"; import { main as initializeEmailTheme } from "keycloakify/bin/initialize-email-theme"; import { it, describe, afterAll, beforeAll, beforeEach, vi } from "vitest"; -import { getKeycloakBuildPath } from "keycloakify/bin/keycloakify/build-paths"; import { downloadBuiltinKeycloakTheme } from "keycloakify/bin/download-builtin-keycloak-theme"; +import { readBuildOptions } from "keycloakify/bin/keycloakify/BuildOptions"; export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project"); @@ -33,7 +33,6 @@ describe("Sample Project", () => { // Monkey patching the cwd to the app location for the duration of this test process.cwd = () => sampleReactProjectDirPath; }); - afterAll(() => { fs.rmSync(sampleReactProjectDirPath, { "recursive": true }); process.cwd = nativeCwd; @@ -52,7 +51,17 @@ describe("Sample Project", () => { await setupSampleReactProject(sampleReactProjectDirPath); await initializeEmailTheme(); - const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme"); + const destDirPath = pathJoin( + readBuildOptions({ + "isExternalAssetsCliParamProvided": false, + "isSilent": true, + "projectDirPath": process.cwd() + }).keycloakifyBuildDirPath, + "src", + "main", + "resources", + "theme" + ); await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", isSilent: false }); }, { timeout: 90000 } @@ -62,14 +71,24 @@ describe("Sample Project", () => { async () => { parsedPackageJson = { "keycloakify": { - "appInputPath": "./custom_input/build", + "reactAppBuildDirPath": "./custom_input/build", "keycloakBuildDir": "./custom_output" } }; await setupSampleReactProject(pathJoin(sampleReactProjectDirPath, "custom_input")); await initializeEmailTheme(); - const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme"); + const destDirPath = pathJoin( + readBuildOptions({ + "isExternalAssetsCliParamProvided": false, + "isSilent": true, + "projectDirPath": process.cwd() + }).keycloakifyBuildDirPath, + "src", + "main", + "resources", + "theme" + ); await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", isSilent: false }); }, { timeout: 90000 }