diff --git a/src/bin/initialize-account-theme/initialize-account-theme.ts b/src/bin/initialize-account-theme/initialize-account-theme.ts index e14b3858..eac4313f 100644 --- a/src/bin/initialize-account-theme/initialize-account-theme.ts +++ b/src/bin/initialize-account-theme/initialize-account-theme.ts @@ -23,22 +23,6 @@ export async function command(params: { buildContext: BuildContext }) { const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account"); - if ( - fs.existsSync(accountThemeSrcDirPath) && - fs.readdirSync(accountThemeSrcDirPath).length > 0 - ) { - console.warn( - chalk.red( - `There is already a ${pathRelative( - process.cwd(), - accountThemeSrcDirPath - )} directory in your project. Aborting.` - ) - ); - - process.exit(-1); - } - exitIfUncommittedChanges({ projectDirPath: buildContext.projectDirPath }); @@ -51,17 +35,35 @@ export async function command(params: { buildContext: BuildContext }) { switch (accountThemeType) { case "Multi-Page": - fs.cpSync( - pathJoin( - getThisCodebaseRootDirPath(), - "src", - "bin", - "initialize-account-theme", - "multi-page-boilerplate" - ), - accountThemeSrcDirPath, - { recursive: true } - ); + { + if ( + fs.existsSync(accountThemeSrcDirPath) && + fs.readdirSync(accountThemeSrcDirPath).length > 0 + ) { + console.warn( + chalk.red( + `There is already a ${pathRelative( + process.cwd(), + accountThemeSrcDirPath + )} directory in your project. Aborting.` + ) + ); + + process.exit(-1); + } + + fs.cpSync( + pathJoin( + getThisCodebaseRootDirPath(), + "src", + "bin", + "initialize-account-theme", + "multi-page-boilerplate" + ), + accountThemeSrcDirPath, + { recursive: true } + ); + } break; case "Single-Page": { diff --git a/src/bin/initialize-email-theme.ts b/src/bin/initialize-email-theme.ts index 8d53599f..0ca3f8fc 100644 --- a/src/bin/initialize-email-theme.ts +++ b/src/bin/initialize-email-theme.ts @@ -1,17 +1,24 @@ -import { join as pathJoin, relative as pathRelative } from "path"; -import { transformCodebase } from "./tools/transformCodebase"; import type { BuildContext } from "./shared/buildContext"; -import * as fs from "fs"; -import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive"; +import cliSelect from "cli-select"; import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate"; -import { assert } from "tsafe/assert"; -import { getSupportedDockerImageTags } from "./start-keycloak/getSupportedDockerImageTags"; +import { exitIfUncommittedChanges } from "./shared/exitIfUncommittedChanges"; + +import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path"; +import * as fs from "fs"; +import { assert, is, type Equals } from "tsafe/assert"; +import { id } from "tsafe/id"; +import { addSyncExtensionsToPostinstallScript } from "./shared/addSyncExtensionsToPostinstallScript"; +import { getIsPrettierAvailable, runPrettier } from "./tools/runPrettier"; +import { npmInstall } from "./tools/npmInstall"; +import * as child_process from "child_process"; +import { z } from "zod"; +import chalk from "chalk"; export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ - commandName: "initialize-email-theme", + commandName: "initialize-account-theme", buildContext }); @@ -19,6 +26,10 @@ export async function command(params: { buildContext: BuildContext }) { return; } + exitIfUncommittedChanges({ + projectDirPath: buildContext.projectDirPath + }); + const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email"); if ( @@ -26,71 +37,110 @@ export async function command(params: { buildContext: BuildContext }) { fs.readdirSync(emailThemeSrcDirPath).length > 0 ) { console.warn( - `There is already a non empty ${pathRelative( - process.cwd(), - emailThemeSrcDirPath - )} directory in your project. Aborting.` + chalk.red( + `There is already a ${pathRelative( + process.cwd(), + emailThemeSrcDirPath + )} directory in your project. Aborting.` + ) ); process.exit(-1); } - console.log("Initialize with the base email theme from which version of Keycloak?"); + const { value: emailThemeType } = await cliSelect({ + values: ["native (FreeMarker)" as const, "jsx-email (React)" as const] + }).catch(() => { + process.exit(-1); + }); - const { extractedDirPath } = await downloadAndExtractArchive({ - url: await (async () => { - const { latestMajorTags } = await getSupportedDockerImageTags({ - buildContext + if (emailThemeType === "jsx-email (React)") { + console.log( + [ + "There is currently no automated support for keycloakify-email, it has to be done manually, see documentation:", + "https://docs.keycloakify.dev/theme-types/email-theme" + ].join("\n") + ); + + process.exit(0); + } + + const parsedPackageJson = (() => { + type ParsedPackageJson = { + scripts?: Record; + dependencies?: Record; + devDependencies?: Record; + }; + + const zParsedPackageJson = (() => { + type TargetType = ParsedPackageJson; + + const zTargetType = z.object({ + scripts: z.record(z.union([z.string(), z.undefined()])).optional(), + dependencies: z.record(z.union([z.string(), z.undefined()])).optional(), + devDependencies: z.record(z.union([z.string(), z.undefined()])).optional() }); - const keycloakVersion = latestMajorTags[0]; + assert, TargetType>>; - assert(keycloakVersion !== undefined); + return id>(zTargetType); + })(); + const parsedPackageJson = JSON.parse( + fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8") + ); - return `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`; - })(), - cacheDirPath: buildContext.cacheDirPath, - fetchOptions: buildContext.fetchOptions, - uniqueIdOfOnArchiveFile: "extractOnlyEmailTheme", - onArchiveFile: async ({ fileRelativePath, writeFile }) => { - const fileRelativePath_target = pathRelative( - pathJoin("theme", "base", "email"), - fileRelativePath - ); + zParsedPackageJson.parse(parsedPackageJson); - if (fileRelativePath_target.startsWith("..")) { - return; - } + assert(is(parsedPackageJson)); - await writeFile({ fileRelativePath: fileRelativePath_target }); - } + return parsedPackageJson; + })(); + + addSyncExtensionsToPostinstallScript({ + parsedPackageJson, + buildContext }); - transformCodebase({ - srcDirPath: extractedDirPath, - destDirPath: emailThemeSrcDirPath - }); + const moduleName = `@keycloakify/email-native`; + + const [version] = ( + JSON.parse( + child_process + .execSync(`npm show ${moduleName} versions --json`) + .toString("utf8") + .trim() + ) as string[] + ) + .reverse() + .filter(version => !version.includes("-")); + + assert(version !== undefined); + + (parsedPackageJson.dependencies ??= {})[moduleName] = `~${version}`; + + if (parsedPackageJson.devDependencies !== undefined) { + delete parsedPackageJson.devDependencies[moduleName]; + } { - const themePropertyFilePath = pathJoin(emailThemeSrcDirPath, "theme.properties"); + let sourceCode = JSON.stringify(parsedPackageJson, undefined, 2); + + if (await getIsPrettierAvailable()) { + sourceCode = await runPrettier({ + sourceCode, + filePath: buildContext.packageJsonFilePath + }); + } fs.writeFileSync( - themePropertyFilePath, - Buffer.from( - [ - `parent=base`, - fs.readFileSync(themePropertyFilePath).toString("utf8") - ].join("\n"), - "utf8" - ) + buildContext.packageJsonFilePath, + Buffer.from(sourceCode, "utf8") ); } - console.log( - `The \`${pathJoin( - ".", - pathRelative(process.cwd(), emailThemeSrcDirPath) - )}\` directory have been created.` - ); - console.log("You can delete any file you don't modify."); + await npmInstall({ + packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath) + }); + + console.log(chalk.green("Email theme initialized.")); } diff --git a/src/bin/shared/initializeSpa/addSyncExtensionsToPostinstallScript.ts b/src/bin/shared/addSyncExtensionsToPostinstallScript.ts similarity index 97% rename from src/bin/shared/initializeSpa/addSyncExtensionsToPostinstallScript.ts rename to src/bin/shared/addSyncExtensionsToPostinstallScript.ts index c0a15f5b..453d6d86 100644 --- a/src/bin/shared/initializeSpa/addSyncExtensionsToPostinstallScript.ts +++ b/src/bin/shared/addSyncExtensionsToPostinstallScript.ts @@ -1,6 +1,6 @@ import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path"; import { assert } from "tsafe/assert"; -import type { BuildContext } from "../buildContext"; +import type { BuildContext } from "./buildContext"; export type BuildContextLike = { projectDirPath: string; diff --git a/src/bin/shared/initializeSpa/initializeSpa.ts b/src/bin/shared/initializeSpa.ts similarity index 95% rename from src/bin/shared/initializeSpa/initializeSpa.ts rename to src/bin/shared/initializeSpa.ts index cbf595d8..dceb2107 100644 --- a/src/bin/shared/initializeSpa/initializeSpa.ts +++ b/src/bin/shared/initializeSpa.ts @@ -1,5 +1,5 @@ import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path"; -import type { BuildContext } from "../buildContext"; +import type { BuildContext } from "./buildContext"; import * as fs from "fs"; import { assert, is, type Equals } from "tsafe/assert"; import { id } from "tsafe/id"; @@ -7,8 +7,8 @@ import { addSyncExtensionsToPostinstallScript, type BuildContextLike as BuildContextLike_addSyncExtensionsToPostinstallScript } from "./addSyncExtensionsToPostinstallScript"; -import { getIsPrettierAvailable, runPrettier } from "../../tools/runPrettier"; -import { npmInstall } from "../../tools/npmInstall"; +import { getIsPrettierAvailable, runPrettier } from "../tools/runPrettier"; +import { npmInstall } from "../tools/npmInstall"; import * as child_process from "child_process"; import { z } from "zod"; import chalk from "chalk"; diff --git a/src/bin/shared/initializeSpa/index.ts b/src/bin/shared/initializeSpa/index.ts deleted file mode 100644 index d1feadc5..00000000 --- a/src/bin/shared/initializeSpa/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./initializeSpa"; diff --git a/src/bin/sync-extensions/getExtensionModuleFileSourceCodeReadyToBeCopied.ts b/src/bin/sync-extensions/getExtensionModuleFileSourceCodeReadyToBeCopied.ts index 7ae6cd6c..10a4272f 100644 --- a/src/bin/sync-extensions/getExtensionModuleFileSourceCodeReadyToBeCopied.ts +++ b/src/bin/sync-extensions/getExtensionModuleFileSourceCodeReadyToBeCopied.ts @@ -99,6 +99,12 @@ function addCommentToSourceCode(params: { return toResult(commentLines.map(line => `# ${line}`).join("\n")); } + if (fileRelativePath.endsWith(".ftl")) { + return toResult( + [`<#--`, ...commentLines.map(line => ` ${line}`), `-->`].join("\n") + ); + } + if (fileRelativePath.endsWith(".html") || fileRelativePath.endsWith(".svg")) { const comment = [ `