From 35b012b93753605dadfe83295a05ad2a5bb24d24 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sat, 5 Oct 2024 20:30:09 +0200 Subject: [PATCH] Implement custom handler cli hook --- package.json | 1 + src/bin/add-story.ts | 11 +- src/bin/copy-keycloak-resources-to-public.ts | 9 +- src/bin/eject-page.ts | 11 +- .../initialize-account-theme.ts | 9 +- src/bin/initialize-email-theme.ts | 9 +- src/bin/keycloakify/keycloakify.ts | 9 +- src/bin/main.ts | 297 +++++++++++------- src/bin/shared/buildContext.ts | 9 +- src/bin/shared/constants.ts | 5 + src/bin/shared/customHandler.ts | 35 +++ src/bin/shared/customHandler_caller.ts | 47 +++ src/bin/start-keycloak/start-keycloak.ts | 22 +- src/bin/update-kc-gen.ts | 11 +- src/vite-plugin/vite-plugin.ts | 4 +- 15 files changed, 300 insertions(+), 189 deletions(-) create mode 100644 src/bin/shared/customHandler.ts create mode 100644 src/bin/shared/customHandler_caller.ts diff --git a/package.json b/package.json index 0564ddf0..70ca8dae 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dist/bin/*.index.js", "dist/bin/*.node", "dist/bin/shared/constants.js", + "dist/bin/shared/customHandler.js", "dist/bin/shared/*.d.ts", "dist/bin/shared/*.js.map", "!dist/vite-plugin/", diff --git a/src/bin/add-story.ts b/src/bin/add-story.ts index b3ed5be7..b2be6d65 100644 --- a/src/bin/add-story.ts +++ b/src/bin/add-story.ts @@ -13,16 +13,11 @@ import * as fs from "fs"; import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path"; import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase"; import { assert, Equals } from "tsafe/assert"; -import type { CliCommandOptions } from "./main"; -import { getBuildContext } from "./shared/buildContext"; +import type { BuildContext } from "./shared/buildContext"; import chalk from "chalk"; -export async function command(params: { cliCommandOptions: CliCommandOptions }) { - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ - cliCommandOptions - }); +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; console.log(chalk.cyan("Theme type:")); diff --git a/src/bin/copy-keycloak-resources-to-public.ts b/src/bin/copy-keycloak-resources-to-public.ts index b245076a..ba944068 100644 --- a/src/bin/copy-keycloak-resources-to-public.ts +++ b/src/bin/copy-keycloak-resources-to-public.ts @@ -1,11 +1,8 @@ import { copyKeycloakResourcesToPublic } from "./shared/copyKeycloakResourcesToPublic"; -import { getBuildContext } from "./shared/buildContext"; -import type { CliCommandOptions } from "./main"; +import type { BuildContext } from "./shared/buildContext"; -export async function command(params: { cliCommandOptions: CliCommandOptions }) { - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ cliCommandOptions }); +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; copyKeycloakResourcesToPublic({ buildContext diff --git a/src/bin/eject-page.ts b/src/bin/eject-page.ts index 404483ec..8874fd99 100644 --- a/src/bin/eject-page.ts +++ b/src/bin/eject-page.ts @@ -20,16 +20,11 @@ import { } from "path"; import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase"; import { assert, Equals } from "tsafe/assert"; -import type { CliCommandOptions } from "./main"; -import { getBuildContext } from "./shared/buildContext"; +import type { BuildContext } from "./shared/buildContext"; import chalk from "chalk"; -export async function command(params: { cliCommandOptions: CliCommandOptions }) { - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ - cliCommandOptions - }); +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; console.log(chalk.cyan("Theme type:")); diff --git a/src/bin/initialize-account-theme/initialize-account-theme.ts b/src/bin/initialize-account-theme/initialize-account-theme.ts index e15b95ba..e29b6733 100644 --- a/src/bin/initialize-account-theme/initialize-account-theme.ts +++ b/src/bin/initialize-account-theme/initialize-account-theme.ts @@ -1,5 +1,4 @@ -import { getBuildContext } from "../shared/buildContext"; -import type { CliCommandOptions } from "../main"; +import type { BuildContext } from "../shared/buildContext"; import cliSelect from "cli-select"; import child_process from "child_process"; import chalk from "chalk"; @@ -8,10 +7,8 @@ import * as fs from "fs"; import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig"; import { generateKcGenTs } from "../shared/generateKcGenTs"; -export async function command(params: { cliCommandOptions: CliCommandOptions }) { - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ cliCommandOptions }); +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account"); diff --git a/src/bin/initialize-email-theme.ts b/src/bin/initialize-email-theme.ts index f2520645..dfa0287c 100644 --- a/src/bin/initialize-email-theme.ts +++ b/src/bin/initialize-email-theme.ts @@ -1,15 +1,12 @@ import { join as pathJoin, relative as pathRelative } from "path"; import { transformCodebase } from "./tools/transformCodebase"; import { promptKeycloakVersion } from "./shared/promptKeycloakVersion"; -import { getBuildContext } from "./shared/buildContext"; +import type { BuildContext } from "./shared/buildContext"; import * as fs from "fs"; -import type { CliCommandOptions } from "./main"; import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive"; -export async function command(params: { cliCommandOptions: CliCommandOptions }) { - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ cliCommandOptions }); +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email"); diff --git a/src/bin/keycloakify/keycloakify.ts b/src/bin/keycloakify/keycloakify.ts index b39dcad8..0d027103 100644 --- a/src/bin/keycloakify/keycloakify.ts +++ b/src/bin/keycloakify/keycloakify.ts @@ -2,19 +2,16 @@ import { generateResources } from "./generateResources"; import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path"; import * as child_process from "child_process"; import * as fs from "fs"; -import { getBuildContext } from "../shared/buildContext"; +import type { BuildContext } from "../shared/buildContext"; import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../shared/constants"; import { buildJars } from "./buildJars"; -import type { CliCommandOptions } from "../main"; import chalk from "chalk"; import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion"; import * as os from "os"; import { rmSync } from "../tools/fs.rmSync"; -export async function command(params: { cliCommandOptions: CliCommandOptions }) { - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ cliCommandOptions }); +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; exit_if_maven_not_installed: { let commandOutput: Buffer | undefined = undefined; diff --git a/src/bin/main.ts b/src/bin/main.ts index 9f54242f..7ef2a9b8 100644 --- a/src/bin/main.ts +++ b/src/bin/main.ts @@ -4,8 +4,10 @@ import { termost } from "termost"; import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion"; import * as child_process from "child_process"; import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx"; +import { callHandlerIfAny } from "./shared/customHandler_caller"; +import { getBuildContext } from "./shared/buildContext"; -export type CliCommandOptions = { +type CliCommandOptions = { projectDirPath: string | undefined; }; @@ -69,115 +71,154 @@ program }) .task({ skip, - handler: async cliCommandOptions => { + handler: async ({ projectDirPath }) => { + const buildContext = getBuildContext({ projectDirPath }); + const { command } = await import("./keycloakify"); - await command({ cliCommandOptions }); + await command({ buildContext }); } }); -program - .command<{ - port: number | undefined; - keycloakVersion: string | undefined; - realmJsonFilePath: string | undefined; - }>({ - name: "start-keycloak", - description: - "Spin up a pre configured Docker image of Keycloak to test your theme." - }) - .option({ - key: "port", - name: (() => { - const name = "port"; +{ + const commandName = "start-keycloak"; - optionsKeys.push(name); + program + .command<{ + port: number | undefined; + keycloakVersion: string | undefined; + realmJsonFilePath: string | undefined; + }>({ + name: commandName, + description: + "Spin up a pre configured Docker image of Keycloak to test your theme." + }) + .option({ + key: "port", + name: (() => { + const name = "port"; - return name; - })(), - description: ["Keycloak server port.", "Example `--port 8085`"].join(" "), - defaultValue: undefined - }) - .option({ - key: "keycloakVersion", - name: (() => { - const name = "keycloak-version"; + optionsKeys.push(name); - optionsKeys.push(name); + return name; + })(), + description: ["Keycloak server port.", "Example `--port 8085`"].join(" "), + defaultValue: undefined + }) + .option({ + key: "keycloakVersion", + name: (() => { + const name = "keycloak-version"; - return name; - })(), - description: [ - "Use a specific version of Keycloak.", - "Example `--keycloak-version 21.1.1`" - ].join(" "), - defaultValue: undefined - }) - .option({ - key: "realmJsonFilePath", - name: (() => { - const name = "import"; + optionsKeys.push(name); - optionsKeys.push(name); + return name; + })(), + description: [ + "Use a specific version of Keycloak.", + "Example `--keycloak-version 21.1.1`" + ].join(" "), + defaultValue: undefined + }) + .option({ + key: "realmJsonFilePath", + name: (() => { + const name = "import"; - return name; - })(), - defaultValue: undefined, - description: [ - "Import your own realm configuration file", - "Example `--import path/to/myrealm-realm.json`" - ].join(" ") - }) - .task({ - skip, - handler: async cliCommandOptions => { - const { command } = await import("./start-keycloak"); + optionsKeys.push(name); - await command({ cliCommandOptions }); - } - }); + return name; + })(), + defaultValue: undefined, + description: [ + "Import your own realm configuration file", + "Example `--import path/to/myrealm-realm.json`" + ].join(" ") + }) + .task({ + skip, + handler: async ({ + projectDirPath, + keycloakVersion, + port, + realmJsonFilePath + }) => { + const buildContext = getBuildContext({ projectDirPath }); -program - .command({ - name: "eject-page", - description: "Eject a Keycloak page." - }) - .task({ - skip, - handler: async cliCommandOptions => { - const { command } = await import("./eject-page"); + const { command } = await import("./start-keycloak"); - await command({ cliCommandOptions }); - } - }); + await command({ + buildContext, + cliCommandOptions: { keycloakVersion, port, realmJsonFilePath } + }); + } + }); +} -program - .command({ - name: "add-story", - description: "Add *.stories.tsx file for a specific page to in your Storybook." - }) - .task({ - skip, - handler: async cliCommandOptions => { - const { command } = await import("./add-story"); +{ + const commandName = "eject-page"; - await command({ cliCommandOptions }); - } - }); + program + .command({ + name: commandName, + description: "Eject a Keycloak page." + }) + .task({ + skip, + handler: async ({ projectDirPath }) => { + const buildContext = getBuildContext({ projectDirPath }); -program - .command({ - name: "initialize-email-theme", - description: "Initialize an email theme." - }) - .task({ - skip, - handler: async cliCommandOptions => { - const { command } = await import("./initialize-email-theme"); + callHandlerIfAny({ buildContext, commandName }); - await command({ cliCommandOptions }); - } - }); + const { command } = await import("./eject-page"); + + await command({ buildContext }); + } + }); +} + +{ + const commandName = "add-story"; + + program + .command({ + name: commandName, + description: + "Add *.stories.tsx file for a specific page to in your Storybook." + }) + .task({ + skip, + handler: async ({ projectDirPath }) => { + const buildContext = getBuildContext({ projectDirPath }); + + callHandlerIfAny({ buildContext, commandName }); + + const { command } = await import("./add-story"); + + await command({ buildContext }); + } + }); +} + +{ + const comandName = "initialize-login-theme"; + + program + .command({ + name: comandName, + description: "Initialize an email theme." + }) + .task({ + skip, + handler: async ({ projectDirPath }) => { + const buildContext = getBuildContext({ projectDirPath }); + + const { command } = await import("./initialize-email-theme"); + + await command({ buildContext }); + } + }); +} program .command({ @@ -186,42 +227,58 @@ program }) .task({ skip, - handler: async cliCommandOptions => { + handler: async ({ projectDirPath }) => { + const buildContext = getBuildContext({ projectDirPath }); + const { command } = await import("./initialize-account-theme"); - await command({ cliCommandOptions }); + await command({ buildContext }); } }); -program - .command({ - name: "copy-keycloak-resources-to-public", - description: - "(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory." - }) - .task({ - skip, - handler: async cliCommandOptions => { - const { command } = await import("./copy-keycloak-resources-to-public"); +{ + const commandName = "copy-keycloak-resources-to-public"; - await command({ cliCommandOptions }); - } - }); + program + .command({ + name: commandName, + description: + "(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory." + }) + .task({ + skip, + handler: async ({ projectDirPath }) => { + const buildContext = getBuildContext({ projectDirPath }); -program - .command({ - name: "update-kc-gen", - description: - "(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project." - }) - .task({ - skip, - handler: async cliCommandOptions => { - const { command } = await import("./update-kc-gen"); + const { command } = await import("./copy-keycloak-resources-to-public"); - await command({ cliCommandOptions }); - } - }); + await command({ buildContext }); + } + }); +} + +{ + const commandName = "update-kc-gen"; + + program + .command({ + name: commandName, + description: + "(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project." + }) + .task({ + skip, + handler: async ({ projectDirPath }) => { + const buildContext = getBuildContext({ projectDirPath }); + + callHandlerIfAny({ buildContext, commandName }); + + const { command } = await import("./update-kc-gen"); + + await command({ buildContext }); + } + }); +} // Fallback to build command if no command is provided { diff --git a/src/bin/shared/buildContext.ts b/src/bin/shared/buildContext.ts index 5fc09c3a..fa658097 100644 --- a/src/bin/shared/buildContext.ts +++ b/src/bin/shared/buildContext.ts @@ -7,7 +7,6 @@ import { dirname as pathDirname } from "path"; import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath"; -import type { CliCommandOptions } from "../main"; import { z } from "zod"; import * as fs from "fs"; import { assert, type Equals } from "tsafe/assert"; @@ -129,14 +128,12 @@ export type ResolvedViteConfig = { }; export function getBuildContext(params: { - cliCommandOptions: CliCommandOptions; + projectDirPath: string | undefined; }): BuildContext { - const { cliCommandOptions } = params; - const projectDirPath = - cliCommandOptions.projectDirPath !== undefined + params.projectDirPath !== undefined ? getAbsoluteAndInOsFormatPath({ - pathIsh: cliCommandOptions.projectDirPath, + pathIsh: params.projectDirPath, cwd: process.cwd() }) : process.cwd(); diff --git a/src/bin/shared/constants.ts b/src/bin/shared/constants.ts index d0707a90..d2137b14 100644 --- a/src/bin/shared/constants.ts +++ b/src/bin/shared/constants.ts @@ -71,3 +71,8 @@ export type AccountThemePageId = (typeof ACCOUNT_THEME_PAGE_IDS)[number]; export const CONTAINER_NAME = "keycloak-keycloakify"; export const FALLBACK_LANGUAGE_TAG = "en"; + +export const CUSTOM_HANDLER_ENV_NAMES = { + COMMAND_NAME: "KEYCLOAKIFY_COMMAND_NAME", + BUILD_CONTEXT: "KEYCLOAKIFY_BUILD_CONTEXT" +}; diff --git a/src/bin/shared/customHandler.ts b/src/bin/shared/customHandler.ts new file mode 100644 index 00000000..d858aaa5 --- /dev/null +++ b/src/bin/shared/customHandler.ts @@ -0,0 +1,35 @@ +import { assert } from "tsafe/assert"; +import type { BuildContext } from "./buildContext"; +import { CUSTOM_HANDLER_ENV_NAMES } from "./constants"; + +export const BIN_NAME = "_keycloakify-custom-handler"; + +export const NOT_IMPLEMENTED_EXIT_CODE = 78; + +export type CommandName = "update-kc-gen" | "eject-page" | "add-story"; + +export type ApiVersion = "v1"; + +export function readParams(params: { apiVersion: ApiVersion }) { + const { apiVersion } = params; + + assert(apiVersion === "v1"); + + const commandName = (() => { + const envValue = process.env[CUSTOM_HANDLER_ENV_NAMES.COMMAND_NAME]; + + assert(envValue !== undefined); + + return envValue as CommandName; + })(); + + const buildContext = (() => { + const envValue = process.env[CUSTOM_HANDLER_ENV_NAMES.BUILD_CONTEXT]; + + assert(envValue !== undefined); + + return JSON.parse(envValue) as BuildContext; + })(); + + return { commandName, buildContext }; +} diff --git a/src/bin/shared/customHandler_caller.ts b/src/bin/shared/customHandler_caller.ts new file mode 100644 index 00000000..38f25972 --- /dev/null +++ b/src/bin/shared/customHandler_caller.ts @@ -0,0 +1,47 @@ +import { assert, type Equals } from "tsafe/assert"; +import type { BuildContext } from "./buildContext"; +import { CUSTOM_HANDLER_ENV_NAMES } from "./constants"; +import { + NOT_IMPLEMENTED_EXIT_CODE, + type CommandName, + BIN_NAME, + ApiVersion +} from "./customHandler"; +import * as child_process from "child_process"; +import { is } from "tsafe/is"; +import { dirname as pathDirname } from "path"; +import * as fs from "fs"; + +assert>(); + +export function callHandlerIfAny(params: { + commandName: CommandName; + buildContext: BuildContext; +}) { + const { commandName, buildContext } = params; + + if (!fs.readdirSync(pathDirname(process.argv[1])).includes(BIN_NAME)) { + return; + } + + try { + child_process.execSync(`npx ${BIN_NAME}`, { + stdio: "inherit", + env: { + ...process.env, + [CUSTOM_HANDLER_ENV_NAMES.COMMAND_NAME]: commandName, + [CUSTOM_HANDLER_ENV_NAMES.BUILD_CONTEXT]: JSON.stringify(buildContext) + } + }); + } catch (error) { + assert(is(error)); + + if (error.code === NOT_IMPLEMENTED_EXIT_CODE) { + return; + } + + process.exit(error.code); + } + + process.exit(0); +} diff --git a/src/bin/start-keycloak/start-keycloak.ts b/src/bin/start-keycloak/start-keycloak.ts index c85b09dc..e243fe2b 100644 --- a/src/bin/start-keycloak/start-keycloak.ts +++ b/src/bin/start-keycloak/start-keycloak.ts @@ -1,6 +1,5 @@ -import { getBuildContext } from "../shared/buildContext"; +import type { BuildContext } from "../shared/buildContext"; import { exclude } from "tsafe/exclude"; -import type { CliCommandOptions as CliCommandOptions_common } from "../main"; import { promptKeycloakVersion } from "../shared/promptKeycloakVersion"; import { CONTAINER_NAME } from "../shared/constants"; import { SemVer } from "../tools/SemVer"; @@ -29,13 +28,14 @@ import { existsAsync } from "../tools/fs.existsAsync"; import { rm } from "../tools/fs.rm"; import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive"; -export type CliCommandOptions = CliCommandOptions_common & { - port: number | undefined; - keycloakVersion: string | undefined; - realmJsonFilePath: string | undefined; -}; - -export async function command(params: { cliCommandOptions: CliCommandOptions }) { +export async function command(params: { + buildContext: BuildContext; + cliCommandOptions: { + port: number | undefined; + keycloakVersion: string | undefined; + realmJsonFilePath: string | undefined; + }; +}) { exit_if_docker_not_installed: { let commandOutput: string | undefined = undefined; @@ -88,9 +88,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions }) process.exit(1); } - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ cliCommandOptions }); + const { cliCommandOptions, buildContext } = params; const { dockerImageTag } = await (async () => { if (cliCommandOptions.keycloakVersion !== undefined) { diff --git a/src/bin/update-kc-gen.ts b/src/bin/update-kc-gen.ts index 585b0f22..b3d9ce15 100644 --- a/src/bin/update-kc-gen.ts +++ b/src/bin/update-kc-gen.ts @@ -1,13 +1,8 @@ -import type { CliCommandOptions } from "./main"; -import { getBuildContext } from "./shared/buildContext"; +import type { BuildContext } from "./shared/buildContext"; import { generateKcGenTs } from "./shared/generateKcGenTs"; -export async function command(params: { cliCommandOptions: CliCommandOptions }) { - const { cliCommandOptions } = params; - - const buildContext = getBuildContext({ - cliCommandOptions - }); +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; await generateKcGenTs({ buildContext }); } diff --git a/src/vite-plugin/vite-plugin.ts b/src/vite-plugin/vite-plugin.ts index f22cb62a..3a7f3d26 100644 --- a/src/vite-plugin/vite-plugin.ts +++ b/src/vite-plugin/vite-plugin.ts @@ -122,9 +122,7 @@ export function keycloakify(params: keycloakify.Params) { } const buildContext = getBuildContext({ - cliCommandOptions: { - projectDirPath - } + projectDirPath }); copyKeycloakResourcesToPublic({ buildContext }),