diff --git a/package.json b/package.json index 175ea53a..2ccc6101 100755 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "devDependencies": { "@emotion/react": "^11.4.1", "@types/memoizee": "^0.4.7", + "@types/minimist": "^1.2.2", "@types/node": "^17.0.25", "@types/react": "18.0.9", "copyfiles": "^2.4.1", @@ -78,6 +79,7 @@ "evt": "^2.4.0", "memoizee": "^0.4.15", "minimal-polyfills": "^2.2.2", + "minimist": "^1.2.6", "path-browserify": "^1.0.1", "powerhooks": "^0.20.15", "react-markdown": "^5.0.3", diff --git a/src/bin/create-keycloak-email-directory.ts b/src/bin/create-keycloak-email-directory.ts index 9d72d0bb..1a3dc77d 100644 --- a/src/bin/create-keycloak-email-directory.ts +++ b/src/bin/create-keycloak-email-directory.ts @@ -6,11 +6,15 @@ import { join as pathJoin, basename as pathBasename } from "path"; import { transformCodebase } from "./tools/transformCodebase"; import { promptKeycloakVersion } from "./promptKeycloakVersion"; import * as fs from "fs"; +import { getCliOptions } from "./tools/cliOptions"; +import { getLogger } from "./tools/logger"; if (require.main === module) { (async () => { + const { isSilent } = getCliOptions(process.argv.slice(2)); + const logger = getLogger({ isSilent }); if (fs.existsSync(keycloakThemeEmailDirPath)) { - console.log(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`); + logger.warn(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`); process.exit(-1); } @@ -21,7 +25,8 @@ if (require.main === module) { downloadBuiltinKeycloakTheme({ keycloakVersion, - "destDirPath": builtinKeycloakThemeTmpDirPath + "destDirPath": builtinKeycloakThemeTmpDirPath, + "isSilent": isSilent }); transformCodebase({ @@ -29,7 +34,7 @@ if (require.main === module) { "destDirPath": keycloakThemeEmailDirPath }); - console.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`); + logger.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`); fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true }); })(); diff --git a/src/bin/download-builtin-keycloak-theme.ts b/src/bin/download-builtin-keycloak-theme.ts index 3aa7ede9..12bef872 100644 --- a/src/bin/download-builtin-keycloak-theme.ts +++ b/src/bin/download-builtin-keycloak-theme.ts @@ -4,31 +4,37 @@ import { keycloakThemeBuildingDirPath } from "./keycloakify"; import { join as pathJoin } from "path"; import { downloadAndUnzip } from "./tools/downloadAndUnzip"; import { promptKeycloakVersion } from "./promptKeycloakVersion"; +import { getCliOptions } from "./tools/cliOptions"; +import { getLogger } from "./tools/logger"; -export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string }) { - const { keycloakVersion, destDirPath } = params; +export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) { + const { keycloakVersion, destDirPath, isSilent } = params; for (const ext of ["", "-community"]) { downloadAndUnzip({ "destDirPath": destDirPath, "url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`, "pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`, - "cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache") + "cacheDirPath": pathJoin(keycloakThemeBuildingDirPath, ".cache"), + "isSilent": isSilent }); } } if (require.main === module) { (async () => { + const { isSilent } = getCliOptions(process.argv.slice(2)); + const logger = getLogger({ isSilent }); const { keycloakVersion } = await promptKeycloakVersion(); const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme"); - console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`); + logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`); downloadBuiltinKeycloakTheme({ keycloakVersion, - destDirPath + destDirPath, + isSilent }); })(); } diff --git a/src/bin/generate-i18n-messages.ts b/src/bin/generate-i18n-messages.ts index 431efe7d..2de2bf1d 100644 --- a/src/bin/generate-i18n-messages.ts +++ b/src/bin/generate-i18n-messages.ts @@ -5,6 +5,8 @@ import { crawl } from "./tools/crawl"; import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme"; import { getProjectRoot } from "./tools/getProjectRoot"; import { rm_rf, rm_r } from "./tools/rm"; +import { getCliOptions } from "./tools/cliOptions"; +import { getLogger } from "./tools/logger"; //NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files, // update the version array for generating for newer version. @@ -12,8 +14,11 @@ import { rm_rf, rm_r } from "./tools/rm"; //@ts-ignore const propertiesParser = require("properties-parser"); +const { isSilent } = getCliOptions(process.argv.slice(2)); +const logger = getLogger({ isSilent }); + for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) { - console.log({ keycloakVersion }); + logger.log(JSON.stringify({ keycloakVersion })); const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44"); @@ -21,7 +26,8 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) { downloadBuiltinKeycloakTheme({ keycloakVersion, - "destDirPath": tmpDirPath + "destDirPath": tmpDirPath, + "isSilent": isSilent }); type Dictionary = { [idiomId: string]: string }; @@ -75,7 +81,7 @@ for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) { ) ); - console.log(`${filePath} wrote`); + logger.log(`${filePath} wrote`); }); }); } diff --git a/src/bin/keycloakify/BuildOptions.ts b/src/bin/keycloakify/BuildOptions.ts index 92f7c13c..c6565e62 100644 --- a/src/bin/keycloakify/BuildOptions.ts +++ b/src/bin/keycloakify/BuildOptions.ts @@ -35,6 +35,7 @@ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets export namespace BuildOptions { export type Common = { + isSilent: boolean; version: string; themeName: string; extraPages?: string[]; @@ -71,8 +72,9 @@ export function readBuildOptions(params: { packageJson: string; CNAME: string | undefined; isExternalAssetsCliParamProvided: boolean; + isSilent: boolean; }): BuildOptions { - const { packageJson, CNAME, isExternalAssetsCliParamProvided } = params; + const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params; const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson)); @@ -130,7 +132,8 @@ export function readBuildOptions(params: { })(), "version": version, extraPages, - extraThemeProperties + extraThemeProperties, + isSilent }; })(); diff --git a/src/bin/keycloakify/generateKeycloakThemeResources.ts b/src/bin/keycloakify/generateKeycloakThemeResources.ts index c149957b..2f645fc2 100644 --- a/src/bin/keycloakify/generateKeycloakThemeResources.ts +++ b/src/bin/keycloakify/generateKeycloakThemeResources.ts @@ -11,6 +11,7 @@ import { isInside } from "../tools/isInside"; import type { BuildOptions } from "./BuildOptions"; import { assert } from "tsafe/assert"; import { Reflect } from "tsafe/Reflect"; +import { getLogger } from "../tools/logger"; export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; @@ -19,6 +20,7 @@ export namespace BuildOptionsLike { themeName: string; extraPages?: string[]; extraThemeProperties?: string[]; + isSilent: boolean; }; export type Standalone = Common & { @@ -60,6 +62,7 @@ export function generateKeycloakThemeResources(params: { }): { doBundlesEmailTemplate: boolean } { const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, keycloakThemeEmailDirPath, keycloakVersion, buildOptions } = params; + const logger = getLogger({ isSilent: buildOptions.isSilent }); const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, "login"); let allCssGlobalsToDefine: Record = {}; @@ -117,7 +120,7 @@ export function generateKeycloakThemeResources(params: { email: { if (!fs.existsSync(keycloakThemeEmailDirPath)) { - console.log( + logger.log( [ `Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`, `To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈` @@ -154,7 +157,8 @@ export function generateKeycloakThemeResources(params: { downloadBuiltinKeycloakTheme({ keycloakVersion, - "destDirPath": tmpDirPath + "destDirPath": tmpDirPath, + isSilent: buildOptions.isSilent }); const themeResourcesDirPath = pathJoin(themeDirPath, "resources"); diff --git a/src/bin/keycloakify/keycloakify.ts b/src/bin/keycloakify/keycloakify.ts index d30f3f2e..729461ae 100644 --- a/src/bin/keycloakify/keycloakify.ts +++ b/src/bin/keycloakify/keycloakify.ts @@ -5,6 +5,8 @@ import * as child_process from "child_process"; import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer"; import * as fs from "fs"; import { readBuildOptions } from "./BuildOptions"; +import { getLogger } from "../tools/logger"; +import { getCliOptions } from "../tools/cliOptions"; const reactProjectDirPath = process.cwd(); @@ -12,7 +14,9 @@ export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email"); export function main() { - console.log("🔏 Building the keycloak theme...⌚"); + const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2)); + const logger = getLogger({ isSilent }); + logger.log("🔏 Building the keycloak theme...⌚"); const buildOptions = readBuildOptions({ "packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"), @@ -25,7 +29,8 @@ export function main() { return fs.readFileSync(cnameFilePath).toString("utf8"); })(), - "isExternalAssetsCliParamProvided": process.argv[2]?.toLowerCase() === "--external-assets" + "isExternalAssetsCliParamProvided": hasExternalAssets, + "isSilent": isSilent }); const { doBundlesEmailTemplate } = generateKeycloakThemeResources({ @@ -59,7 +64,7 @@ export function main() { buildOptions }); - console.log( + logger.log( [ "", `✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`, diff --git a/src/bin/tools/cliOptions.ts b/src/bin/tools/cliOptions.ts new file mode 100644 index 00000000..9a1b39c6 --- /dev/null +++ b/src/bin/tools/cliOptions.ts @@ -0,0 +1,15 @@ +import parseArgv from "minimist"; + +export type CliOptions = { + isSilent: boolean; + hasExternalAssets: boolean; +}; + +export const getCliOptions = (processArgv: string[]): CliOptions => { + const argv = parseArgv(processArgv); + + return { + isSilent: typeof argv["silent"] === "boolean" ? argv["silent"] : false, + hasExternalAssets: argv["external-assets"] !== undefined + }; +}; diff --git a/src/bin/tools/downloadAndUnzip.ts b/src/bin/tools/downloadAndUnzip.ts index 1522e327..a84cadfb 100644 --- a/src/bin/tools/downloadAndUnzip.ts +++ b/src/bin/tools/downloadAndUnzip.ts @@ -6,7 +6,13 @@ import { rm, rm_rf } from "./rm"; import * as crypto from "crypto"; /** assert url ends with .zip */ -export function downloadAndUnzip(params: { url: string; destDirPath: string; pathOfDirToExtractInArchive?: string; cacheDirPath: string }) { +export function downloadAndUnzip(params: { + isSilent: boolean; + url: string; + destDirPath: string; + pathOfDirToExtractInArchive?: string; + cacheDirPath: string; +}) { const { url, destDirPath, pathOfDirToExtractInArchive, cacheDirPath } = params; const extractDirPath = pathJoin( @@ -54,7 +60,7 @@ export function downloadAndUnzip(params: { url: string; destDirPath: string; pat const zipFileBasename = pathBasename(url); - execSync(`curl -L ${url} -o ${zipFileBasename}`, { "cwd": extractDirPath }); + execSync(`curl -L ${url} -o ${zipFileBasename} ${params.isSilent ? "-s" : ""}`, { "cwd": extractDirPath }); execSync(`unzip -o ${zipFileBasename}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/**/*"`}`, { "cwd": extractDirPath diff --git a/src/bin/tools/logger.ts b/src/bin/tools/logger.ts new file mode 100644 index 00000000..638d93d5 --- /dev/null +++ b/src/bin/tools/logger.ts @@ -0,0 +1,27 @@ +type LoggerOpts = { + force?: boolean; +}; + +type Logger = { + log: (message: string, opts?: LoggerOpts) => void; + warn: (message: string) => void; + error: (message: string) => void; +}; + +export const getLogger = ({ isSilent }: { isSilent?: boolean } = {}): Logger => { + return { + log: (message, { force } = {}) => { + if (isSilent && !force) { + return; + } + + console.log(message); + }, + warn: message => { + console.warn(message); + }, + error: message => { + console.error(message); + } + }; +}; diff --git a/yarn.lock b/yarn.lock index bba0ebc0..0f6e47dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -265,6 +265,11 @@ resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.8.tgz#04adc0c266a0f5d72db0556fdda2ba17dad9b519" integrity sha512-qDpXKGgwKywnQt/64fH1O0LiPA++QGIYeykEUiZ51HymKVRLnUSGcRuF60IfpPeeXiuRwiR/W4y7S5VzbrgLCA== +"@types/minimist@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + "@types/node@^17.0.25": version "17.0.45" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" @@ -1188,6 +1193,11 @@ minimatch@^3.0.3, minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" +minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"