diff --git a/.gitignore b/.gitignore index c28b34d0..1c905308 100644 --- a/.gitignore +++ b/.gitignore @@ -41,9 +41,11 @@ jspm_packages .DS_Store /dist +# Test Build Directories /dist_test - /sample_react_project/ +/sample_custom_react_project/ +/keycloakify_starter_test/ /.yarn_home/ .idea diff --git a/.prettierignore b/.prettierignore index 896a774d..48693ecf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,12 +1,15 @@ node_modules/ /dist/ -/dist_test/ /CHANGELOG.md /.yarn_home/ /src/test/apps/ /src/tools/types/ -/sample_react_project /build_keycloak/ /.vscode/ /src/login/i18n/baseMessages/ /src/account/i18n/baseMessages/ +# Test Build Directories +/dist_test +/sample_react_project/ +/sample_custom_react_project/ +/keycloakify_starter_test/ \ No newline at end of file diff --git a/package.json b/package.json index 8f38ac39..e7a16dda 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,11 @@ "prepare": "yarn generate-i18n-messages", "build": "rimraf dist/ && tsc -p src/bin && tsc -p src/tsconfig.json && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/", "build:watch": "tsc -p src/tsconfig.json && (concurrently \"tsc -p src/tsconfig.json -w\" \"tsc-alias -p src/tsconfig.json\")", - "test:types": "tsc -p test/tsconfig.json --noEmit", "grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js", "copy-files": "copyfiles -u 1 src/**/*.ftl", "test": "yarn test:types && vitest run", - "test:sample-app": "yarn build:test && node dist_test/test/bin/main.js", + "test:keycloakify-starter": "ts-node scripts/test-keycloakify-starter", + "test:types": "tsc -p test/tsconfig.json --noEmit", "_format": "prettier '**/*.{ts,tsx,json,md}'", "format": "yarn _format --write", "format:check": "yarn _format --list-different", diff --git a/scripts/test-keycloakify-starter.ts b/scripts/test-keycloakify-starter.ts new file mode 100644 index 00000000..f443fa39 --- /dev/null +++ b/scripts/test-keycloakify-starter.ts @@ -0,0 +1,29 @@ +import { execSync } from "child_process"; +import { existsSync, readFileSync, rmSync, writeFileSync } from "fs"; +import path from "path"; + +const testDir = "keycloakify_starter_test"; + +if (existsSync(path.join(process.cwd(), testDir))) { + rmSync(path.join(process.cwd(), testDir), { recursive: true }); +} +// Build and link package +execSync("yarn build"); +const pkgJSON = JSON.parse(readFileSync(path.join(process.cwd(), "package.json")).toString("utf8")); +pkgJSON.main = "./index.js"; +pkgJSON.types = "./index.d.ts"; +pkgJSON.scripts.prepare = undefined; +writeFileSync(path.join(process.cwd(), "dist", "package.json"), JSON.stringify(pkgJSON)); +// Wrapped in a try/catch because unlink errors if the package isn't linked +try { + execSync("yarn unlink"); +} catch {} +execSync("yarn link", { cwd: path.join(process.cwd(), "dist") }); + +// Clone latest keycloakify-starter and link to keycloakify output +execSync(`git clone https://github.com/keycloakify/keycloakify-starter.git ${testDir}`); +execSync("yarn install", { cwd: path.join(process.cwd(), testDir) }); +execSync("yarn link keycloakify", { cwd: path.join(process.cwd(), testDir) }); + +//Ensure keycloak theme can be built +execSync("yarn build-keycloak-theme", { cwd: path.join(process.cwd(), testDir) }); diff --git a/src/bin/download-builtin-keycloak-theme.ts b/src/bin/download-builtin-keycloak-theme.ts index d09188b3..e3ac6ba3 100644 --- a/src/bin/download-builtin-keycloak-theme.ts +++ b/src/bin/download-builtin-keycloak-theme.ts @@ -1,11 +1,10 @@ #!/usr/bin/env node - -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"; +import { getKeycloakBuildPath } from "./keycloakify/build-paths"; export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) { const { keycloakVersion, destDirPath } = params; @@ -26,7 +25,7 @@ async function main() { const logger = getLogger({ isSilent }); const { keycloakVersion } = await promptKeycloakVersion(); - const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme"); + const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme"); logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`); @@ -38,5 +37,5 @@ async function main() { } if (require.main === module) { - main().catch(e => console.error(e)); + main(); } diff --git a/src/bin/eject-keycloak-page.ts b/src/bin/eject-keycloak-page.ts index 1d221f92..f6094c54 100644 --- a/src/bin/eject-keycloak-page.ts +++ b/src/bin/eject-keycloak-page.ts @@ -16,7 +16,7 @@ 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 "./getThemeSrcDirPath"; +import { getThemeSrcDirPath } from "./keycloakify/build-paths"; (async () => { const projectRootDir = getProjectRoot(); diff --git a/src/bin/getThemeSrcDirPath.ts b/src/bin/getThemeSrcDirPath.ts deleted file mode 100644 index ee9db95b..00000000 --- a/src/bin/getThemeSrcDirPath.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { join as pathJoin } from "path"; -import * as fs from "fs"; -import { crawl } from "./tools/crawl"; -import { exclude } from "tsafe/exclude"; - -const reactProjectDirPath = process.cwd(); - -const themeSrcDirBasename = "keycloak-theme"; - -export function getThemeSrcDirPath() { - const srcDirPath = pathJoin(reactProjectDirPath, "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 }; -} diff --git a/src/bin/initialize-email-theme.ts b/src/bin/initialize-email-theme.ts index 44207026..5f08f811 100644 --- a/src/bin/initialize-email-theme.ts +++ b/src/bin/initialize-email-theme.ts @@ -7,17 +7,9 @@ import { promptKeycloakVersion } from "./promptKeycloakVersion"; import * as fs from "fs"; import { getCliOptions } from "./tools/cliOptions"; import { getLogger } from "./tools/logger"; -import { getThemeSrcDirPath } from "./getThemeSrcDirPath"; +import { getEmailThemeSrcDirPath } from "./keycloakify/build-paths"; -export function getEmailThemeSrcDirPath() { - const { themeSrcDirPath } = getThemeSrcDirPath(); - - const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email"); - - return { emailThemeSrcDirPath }; -} - -async function main() { +export async function main() { const { isSilent } = getCliOptions(process.argv.slice(2)); const logger = getLogger({ isSilent }); diff --git a/src/bin/keycloakify/BuildOptions.ts b/src/bin/keycloakify/BuildOptions.ts index 6450f766..f0972b8b 100644 --- a/src/bin/keycloakify/BuildOptions.ts +++ b/src/bin/keycloakify/BuildOptions.ts @@ -1,51 +1,10 @@ -import { z } from "zod"; import { assert } from "tsafe/assert"; -import type { Equals } from "tsafe"; import { id } from "tsafe/id"; import { parse as urlParse } from "url"; import { typeGuard } from "tsafe/typeGuard"; import { symToStr } from "tsafe/symToStr"; - -const bundlers = ["mvn", "keycloakify", "none"] as const; -type Bundler = (typeof bundlers)[number]; -type ParsedPackageJson = { - name: string; - version: string; - homepage?: string; - keycloakify?: { - /** @deprecated: use extraLoginPages instead */ - extraPages?: string[]; - extraLoginPages?: string[]; - extraAccountPages?: string[]; - extraThemeProperties?: string[]; - areAppAndKeycloakServerSharingSameDomain?: boolean; - artifactId?: string; - groupId?: string; - bundler?: Bundler; - keycloakVersionDefaultAssets?: string; - }; -}; - -const zParsedPackageJson = z.object({ - "name": z.string(), - "version": z.string(), - "homepage": z.string().optional(), - "keycloakify": z - .object({ - "extraPages": z.array(z.string()).optional(), - "extraLoginPages": z.array(z.string()).optional(), - "extraAccountPages": z.array(z.string()).optional(), - "extraThemeProperties": z.array(z.string()).optional(), - "areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(), - "artifactId": z.string().optional(), - "groupId": z.string().optional(), - "bundler": z.enum(bundlers).optional(), - "keycloakVersionDefaultAssets": z.string().optional() - }) - .optional() -}); - -assert, ParsedPackageJson>>(); +import { Bundler, bundlers, getParsedPackageJson } from "./parsed-package-json"; +import { getAppInputPath, getKeycloakBuildPath } from "./build-paths"; /** Consolidated build option gathered form CLI arguments and config in package.json */ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets; @@ -62,6 +21,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; }; export type Standalone = Common & { @@ -88,15 +51,10 @@ export namespace BuildOptions { } } -export function readBuildOptions(params: { - packageJson: string; - CNAME: string | undefined; - isExternalAssetsCliParamProvided: boolean; - isSilent: boolean; -}): BuildOptions { - const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params; +export function readBuildOptions(params: { CNAME: string | undefined; isExternalAssetsCliParamProvided: boolean; isSilent: boolean }): BuildOptions { + const { CNAME, isExternalAssetsCliParamProvided, isSilent } = params; - const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson)); + const parsedPackageJson = getParsedPackageJson(); const url = (() => { const { homepage } = parsedPackageJson; @@ -172,7 +130,9 @@ export function readBuildOptions(params: { extraAccountPages, extraThemeProperties, isSilent, - "keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3" + "keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3", + appInputPath: getAppInputPath(), + keycloakBuildPath: getKeycloakBuildPath() }; })(); diff --git a/src/bin/keycloakify/build-paths.ts b/src/bin/keycloakify/build-paths.ts new file mode 100644 index 00000000..200139f1 --- /dev/null +++ b/src/bin/keycloakify/build-paths.ts @@ -0,0 +1,72 @@ +import * as fs from "fs"; +import { exclude } from "tsafe"; +import { crawl } from "../tools/crawl"; +import { pathJoin } from "../tools/pathJoin"; +import { getParsedPackageJson } from "./parsed-package-json"; + +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/index.ts b/src/bin/keycloakify/index.ts index 13d73c75..258b6e23 100644 --- a/src/bin/keycloakify/index.ts +++ b/src/bin/keycloakify/index.ts @@ -4,5 +4,5 @@ export * from "./keycloakify"; import { main } from "./keycloakify"; if (require.main === module) { - main().catch(e => console.error(e)); + main(); } diff --git a/src/bin/keycloakify/keycloakify.ts b/src/bin/keycloakify/keycloakify.ts index 7d2f6334..b06f51d5 100644 --- a/src/bin/keycloakify/keycloakify.ts +++ b/src/bin/keycloakify/keycloakify.ts @@ -10,11 +10,8 @@ import { getCliOptions } from "../tools/cliOptions"; import jar from "../tools/jar"; import { assert } from "tsafe/assert"; import { Equals } from "tsafe"; -import { getEmailThemeSrcDirPath } from "../initialize-email-theme"; - -const reactProjectDirPath = process.cwd(); - -export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak"); +import { getEmailThemeSrcDirPath } from "./build-paths"; +import { getCnamePath, getAppInputPath, getKeycloakBuildPath, getReactProjectDirPath } from "./build-paths"; export async function main() { const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2)); @@ -22,9 +19,8 @@ export async function main() { logger.log("🔏 Building the keycloak theme...⌚"); const buildOptions = readBuildOptions({ - "packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"), "CNAME": (() => { - const cnameFilePath = pathJoin(reactProjectDirPath, "public", "CNAME"); + const cnameFilePath = getCnamePath(); if (!fs.existsSync(cnameFilePath)) { return undefined; @@ -37,7 +33,7 @@ export async function main() { }); const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({ - keycloakThemeBuildingDirPath, + keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath, "emailThemeSrcDirPath": (() => { const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath(); @@ -47,13 +43,13 @@ export async function main() { return emailThemeSrcDirPath; })(), - "reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"), + "reactAppBuildDirPath": getAppInputPath(), buildOptions, "keycloakVersion": buildOptions.keycloakVersionDefaultAssets }); const { jarFilePath } = generateJavaStackFiles({ - keycloakThemeBuildingDirPath, + keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath, doBundlesEmailTemplate, buildOptions }); @@ -65,7 +61,7 @@ export async function main() { case "keycloakify": logger.log("🫶 Let keycloakify do its thang"); await jar({ - "rootPath": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources"), + "rootPath": pathJoin(buildOptions.keycloakBuildPath, "src", "main", "resources"), "version": buildOptions.version, "groupId": buildOptions.groupId, "artifactId": buildOptions.artifactId, @@ -74,7 +70,7 @@ export async function main() { break; case "mvn": logger.log("🫙 Run maven to deliver a jar"); - child_process.execSync("mvn package", { "cwd": keycloakThemeBuildingDirPath }); + child_process.execSync("mvn package", { "cwd": buildOptions.keycloakBuildPath }); break; default: assert>(false); @@ -84,7 +80,7 @@ export async function main() { const containerKeycloakVersion = "20.0.1"; generateStartKeycloakTestingContainer({ - keycloakThemeBuildingDirPath, + keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath, "keycloakVersion": containerKeycloakVersion, buildOptions }); @@ -92,7 +88,7 @@ export async function main() { logger.log( [ "", - `✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`, + `✅ Your keycloak theme has been generated and bundled into ./${pathRelative(getReactProjectDirPath(), 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. @@ -127,8 +123,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( - reactProjectDirPath, - pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename) + getReactProjectDirPath(), + pathJoin(getKeycloakBuildPath(), 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/parsed-package-json.ts b/src/bin/keycloakify/parsed-package-json.ts new file mode 100644 index 00000000..6f69e970 --- /dev/null +++ b/src/bin/keycloakify/parsed-package-json.ts @@ -0,0 +1,58 @@ +import * as fs from "fs"; +import { assert } from "tsafe"; +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 = { + name: string; + version: string; + homepage?: string; + keycloakify?: { + /** @deprecated: use extraLoginPages instead */ + extraPages?: string[]; + extraLoginPages?: string[]; + extraAccountPages?: string[]; + extraThemeProperties?: string[]; + areAppAndKeycloakServerSharingSameDomain?: boolean; + artifactId?: string; + groupId?: string; + bundler?: Bundler; + keycloakVersionDefaultAssets?: string; + appInputPath?: string; + keycloakBuildPath?: string; + }; +}; + +const zParsedPackageJson = z.object({ + "name": z.string(), + "version": z.string(), + "homepage": z.string().optional(), + "keycloakify": z + .object({ + "extraPages": z.array(z.string()).optional(), + "extraLoginPages": z.array(z.string()).optional(), + "extraAccountPages": z.array(z.string()).optional(), + "extraThemeProperties": z.array(z.string()).optional(), + "areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(), + "artifactId": z.string().optional(), + "groupId": z.string().optional(), + "bundler": z.enum(bundlers).optional(), + "keycloakVersionDefaultAssets": z.string().optional(), + "appInputPath": z.string().optional(), + "keycloakBuildPath": z.string().optional() + }) + .optional() +}); + +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"))); + return parsedPackageJson; +}; diff --git a/test/bin/generateKeycloakThemeResources.ts b/test/bin/generateKeycloakThemeResources.ts deleted file mode 100644 index f76e7bfd..00000000 --- a/test/bin/generateKeycloakThemeResources.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { join as pathJoin } from "path"; -import { generateKeycloakThemeResources } from "keycloakify/bin/keycloakify/generateKeycloakThemeResources"; -import { setupSampleReactProject, sampleReactProjectDirPath } from "./setupSampleReactProject"; - -setupSampleReactProject(); - -generateKeycloakThemeResources({ - "reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"), - "keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"), - "emailThemeSrcDirPath": undefined, - "keycloakVersion": "11.0.3", - "buildOptions": { - "themeName": "keycloakify-demo-app", - "extraLoginPages": ["my-custom-page.ftl"], - "extraThemeProperties": ["env=test"], - "isStandalone": true, - "urlPathname": "/keycloakify-demo-app/", - "isSilent": false - } -}); diff --git a/test/bin/main.ts b/test/bin/main.ts deleted file mode 100644 index 531d4b3d..00000000 --- a/test/bin/main.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { setupSampleReactProject, sampleReactProjectDirPath } from "./setupSampleReactProject"; -import * as st from "scripting-tools"; -import * as fs from "fs"; -import { join as pathJoin } from "path"; -import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js"; - -(async () => { - fs.rmSync(sampleReactProjectDirPath, { "recursive": true }); - - await setupSampleReactProject(); - - const binDirPath = pathJoin(getProjectRoot(), "dist_test", "src", "bin"); - - fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "keycloak-theme"), { "recursive": true }); - - st.execSyncTrace(`node ${pathJoin(binDirPath, "initialize-email-theme")}`, { "cwd": sampleReactProjectDirPath }); - - st.execSyncTrace(`node ${pathJoin(binDirPath, "download-builtin-keycloak-theme")}`, { "cwd": sampleReactProjectDirPath }); - - st.execSyncTrace( - //`node ${pathJoin(binDirPath, "keycloakify")} --external-assets`, - `node ${pathJoin(binDirPath, "keycloakify")}`, - { "cwd": sampleReactProjectDirPath } - ); -})(); diff --git a/test/bin/setupCustomReactProject.spec.ts b/test/bin/setupCustomReactProject.spec.ts new file mode 100644 index 00000000..3a4dc069 --- /dev/null +++ b/test/bin/setupCustomReactProject.spec.ts @@ -0,0 +1,62 @@ +import * as fs from "fs"; +import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js"; +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"; + +export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_custom_react_project"); + +async function setupSampleReactProject(destDir: string) { + await downloadAndUnzip({ + "url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip", + "destDirPath": destDir + }); +} +const nativeCwd = process.cwd; +vi.mock("keycloakify/bin/keycloakify/parsed-package-json", async () => ({ + ...((await vi.importActual("keycloakify/bin/keycloakify/parsed-package-json")) as Record), + getParsedPackageJson: () => ({ + keycloakify: { + appInputPath: "./custom_input/build", + keycloakBuildDir: "./custom_output" + } + }) +})); + +vi.mock("keycloakify/bin/promptKeycloakVersion", async () => ({ + ...((await vi.importActual("keycloakify/bin/promptKeycloakVersion")) as Record), + promptKeycloakVersion: () => ({ keycloakVersion: "11.0.3" }) +})); + +describe("Sample Project", () => { + beforeAll(() => { + // Monkey patching the cwd to the app location for the duration of this testv + process.cwd = () => sampleReactProjectDirPath; + }); + + afterAll(() => { + process.cwd = nativeCwd; + }); + beforeEach(() => { + if (fs.existsSync(sampleReactProjectDirPath)) { + fs.rmSync(sampleReactProjectDirPath, { "recursive": true }); + } + + fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "keycloak-theme"), { "recursive": true }); + fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "login"), { "recursive": true }); + }); + it( + "Sets up the project with a custom input and output directory without error", + async () => { + await setupSampleReactProject(pathJoin(sampleReactProjectDirPath, "custom_input")); + await initializeEmailTheme(); + + const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme"); + await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", isSilent: false }); + }, + { timeout: 30000 } + ); +}); diff --git a/test/bin/setupSampleReactProject.spec.ts b/test/bin/setupSampleReactProject.spec.ts new file mode 100644 index 00000000..5d85993a --- /dev/null +++ b/test/bin/setupSampleReactProject.spec.ts @@ -0,0 +1,59 @@ +import * as fs from "fs"; +import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js"; +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"; + +export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project"); + +async function setupSampleReactProject(destDir: string) { + await downloadAndUnzip({ + "url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip", + "destDirPath": destDir + }); +} + +vi.mock("keycloakify/bin/keycloakify/parsed-package-json", async () => ({ + ...((await vi.importActual("keycloakify/bin/keycloakify/parsed-package-json")) as Record), + getParsedPackageJson: () => ({}) +})); + +vi.mock("keycloakify/bin/promptKeycloakVersion", async () => ({ + ...((await vi.importActual("keycloakify/bin/promptKeycloakVersion")) as Record), + promptKeycloakVersion: () => ({ keycloakVersion: "11.0.3" }) +})); + +const nativeCwd = process.cwd; + +describe("Sample Project", () => { + beforeAll(() => { + // Monkey patching the cwd to the app location for the duration of this test + process.cwd = () => sampleReactProjectDirPath; + }); + + afterAll(() => { + process.cwd = nativeCwd; + }); + beforeEach(() => { + if (fs.existsSync(sampleReactProjectDirPath)) { + fs.rmSync(sampleReactProjectDirPath, { "recursive": true }); + } + + fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "keycloak-theme"), { "recursive": true }); + fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "login"), { "recursive": true }); + }); + it( + "Sets up the project without error", + async () => { + await setupSampleReactProject(sampleReactProjectDirPath); + await initializeEmailTheme(); + + const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme"); + await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", isSilent: false }); + }, + { timeout: 30000 } + ); +}); diff --git a/test/bin/setupSampleReactProject.ts b/test/bin/setupSampleReactProject.ts index 7b8308e8..12061e68 100644 --- a/test/bin/setupSampleReactProject.ts +++ b/test/bin/setupSampleReactProject.ts @@ -1,12 +1,8 @@ -import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js"; -import { join as pathJoin } from "path"; import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip"; -export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project"); - -export async function setupSampleReactProject() { +export async function setupSampleReactProject(destDirPath: string) { await downloadAndUnzip({ "url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip", - "destDirPath": sampleReactProjectDirPath + "destDirPath": destDirPath }); } diff --git a/vitest.config.ts b/vitest.config.ts index f372a9c9..aec1da63 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ test: { alias: { "keycloakify": path.resolve(__dirname, "./src") - } + }, + watchExclude: ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**", "**/sample_custom_react_project/**"] } });