feat(config): add ability to customize input/output directory

This commit is contained in:
William Will 2023-03-30 22:09:27 +00:00
parent b863d9feb3
commit 4ebc1e671f
19 changed files with 325 additions and 174 deletions

4
.gitignore vendored
View File

@ -41,9 +41,11 @@ jspm_packages
.DS_Store .DS_Store
/dist /dist
# Test Build Directories
/dist_test /dist_test
/sample_react_project/ /sample_react_project/
/sample_custom_react_project/
/keycloakify_starter_test/
/.yarn_home/ /.yarn_home/
.idea .idea

View File

@ -1,12 +1,15 @@
node_modules/ node_modules/
/dist/ /dist/
/dist_test/
/CHANGELOG.md /CHANGELOG.md
/.yarn_home/ /.yarn_home/
/src/test/apps/ /src/test/apps/
/src/tools/types/ /src/tools/types/
/sample_react_project
/build_keycloak/ /build_keycloak/
/.vscode/ /.vscode/
/src/login/i18n/baseMessages/ /src/login/i18n/baseMessages/
/src/account/i18n/baseMessages/ /src/account/i18n/baseMessages/
# Test Build Directories
/dist_test
/sample_react_project/
/sample_custom_react_project/
/keycloakify_starter_test/

View File

@ -12,11 +12,11 @@
"prepare": "yarn generate-i18n-messages", "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": "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\")", "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", "grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"copy-files": "copyfiles -u 1 src/**/*.ftl", "copy-files": "copyfiles -u 1 src/**/*.ftl",
"test": "yarn test:types && vitest run", "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": "prettier '**/*.{ts,tsx,json,md}'",
"format": "yarn _format --write", "format": "yarn _format --write",
"format:check": "yarn _format --list-different", "format:check": "yarn _format --list-different",

View File

@ -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) });

View File

@ -1,11 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
import { keycloakThemeBuildingDirPath } from "./keycloakify";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { downloadAndUnzip } from "./tools/downloadAndUnzip"; import { downloadAndUnzip } from "./tools/downloadAndUnzip";
import { promptKeycloakVersion } from "./promptKeycloakVersion"; import { promptKeycloakVersion } from "./promptKeycloakVersion";
import { getCliOptions } from "./tools/cliOptions"; import { getCliOptions } from "./tools/cliOptions";
import { getLogger } from "./tools/logger"; import { getLogger } from "./tools/logger";
import { getKeycloakBuildPath } from "./keycloakify/build-paths";
export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) { export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) {
const { keycloakVersion, destDirPath } = params; const { keycloakVersion, destDirPath } = params;
@ -26,7 +25,7 @@ async function main() {
const logger = getLogger({ isSilent }); const logger = getLogger({ isSilent });
const { keycloakVersion } = await promptKeycloakVersion(); 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}`); logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
@ -38,5 +37,5 @@ async function main() {
} }
if (require.main === module) { if (require.main === module) {
main().catch(e => console.error(e)); main();
} }

View File

@ -16,7 +16,7 @@ import { existsSync } from "fs";
import { join as pathJoin, relative as pathRelative } from "path"; import { join as pathJoin, relative as pathRelative } from "path";
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase"; import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
import { assert, Equals } from "tsafe/assert"; import { assert, Equals } from "tsafe/assert";
import { getThemeSrcDirPath } from "./getThemeSrcDirPath"; import { getThemeSrcDirPath } from "./keycloakify/build-paths";
(async () => { (async () => {
const projectRootDir = getProjectRoot(); const projectRootDir = getProjectRoot();

View File

@ -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 };
}

View File

@ -7,17 +7,9 @@ import { promptKeycloakVersion } from "./promptKeycloakVersion";
import * as fs from "fs"; import * as fs from "fs";
import { getCliOptions } from "./tools/cliOptions"; import { getCliOptions } from "./tools/cliOptions";
import { getLogger } from "./tools/logger"; import { getLogger } from "./tools/logger";
import { getThemeSrcDirPath } from "./getThemeSrcDirPath"; import { getEmailThemeSrcDirPath } from "./keycloakify/build-paths";
export function getEmailThemeSrcDirPath() { export async function main() {
const { themeSrcDirPath } = getThemeSrcDirPath();
const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email");
return { emailThemeSrcDirPath };
}
async function main() {
const { isSilent } = getCliOptions(process.argv.slice(2)); const { isSilent } = getCliOptions(process.argv.slice(2));
const logger = getLogger({ isSilent }); const logger = getLogger({ isSilent });

View File

@ -1,51 +1,10 @@
import { z } from "zod";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import { parse as urlParse } from "url"; import { parse as urlParse } from "url";
import { typeGuard } from "tsafe/typeGuard"; import { typeGuard } from "tsafe/typeGuard";
import { symToStr } from "tsafe/symToStr"; import { symToStr } from "tsafe/symToStr";
import { Bundler, bundlers, getParsedPackageJson } from "./parsed-package-json";
const bundlers = ["mvn", "keycloakify", "none"] as const; import { getAppInputPath, getKeycloakBuildPath } from "./build-paths";
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<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
/** Consolidated build option gathered form CLI arguments and config in package.json */ /** Consolidated build option gathered form CLI arguments and config in package.json */
export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets; export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets;
@ -62,6 +21,10 @@ export namespace BuildOptions {
artifactId: string; artifactId: string;
bundler: Bundler; bundler: Bundler;
keycloakVersionDefaultAssets: string; 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 & { export type Standalone = Common & {
@ -88,15 +51,10 @@ export namespace BuildOptions {
} }
} }
export function readBuildOptions(params: { export function readBuildOptions(params: { CNAME: string | undefined; isExternalAssetsCliParamProvided: boolean; isSilent: boolean }): BuildOptions {
packageJson: string; const { CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
CNAME: string | undefined;
isExternalAssetsCliParamProvided: boolean;
isSilent: boolean;
}): BuildOptions {
const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson)); const parsedPackageJson = getParsedPackageJson();
const url = (() => { const url = (() => {
const { homepage } = parsedPackageJson; const { homepage } = parsedPackageJson;
@ -172,7 +130,9 @@ export function readBuildOptions(params: {
extraAccountPages, extraAccountPages,
extraThemeProperties, extraThemeProperties,
isSilent, isSilent,
"keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3" "keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3",
appInputPath: getAppInputPath(),
keycloakBuildPath: getKeycloakBuildPath()
}; };
})(); })();

View File

@ -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 };
};

View File

@ -4,5 +4,5 @@ export * from "./keycloakify";
import { main } from "./keycloakify"; import { main } from "./keycloakify";
if (require.main === module) { if (require.main === module) {
main().catch(e => console.error(e)); main();
} }

View File

@ -10,11 +10,8 @@ import { getCliOptions } from "../tools/cliOptions";
import jar from "../tools/jar"; import jar from "../tools/jar";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { Equals } from "tsafe"; import { Equals } from "tsafe";
import { getEmailThemeSrcDirPath } from "../initialize-email-theme"; import { getEmailThemeSrcDirPath } from "./build-paths";
import { getCnamePath, getAppInputPath, getKeycloakBuildPath, getReactProjectDirPath } from "./build-paths";
const reactProjectDirPath = process.cwd();
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
export async function main() { export async function main() {
const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2)); const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2));
@ -22,9 +19,8 @@ export async function main() {
logger.log("🔏 Building the keycloak theme...⌚"); logger.log("🔏 Building the keycloak theme...⌚");
const buildOptions = readBuildOptions({ const buildOptions = readBuildOptions({
"packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"),
"CNAME": (() => { "CNAME": (() => {
const cnameFilePath = pathJoin(reactProjectDirPath, "public", "CNAME"); const cnameFilePath = getCnamePath();
if (!fs.existsSync(cnameFilePath)) { if (!fs.existsSync(cnameFilePath)) {
return undefined; return undefined;
@ -37,7 +33,7 @@ export async function main() {
}); });
const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({ const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath,
"emailThemeSrcDirPath": (() => { "emailThemeSrcDirPath": (() => {
const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath(); const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath();
@ -47,13 +43,13 @@ export async function main() {
return emailThemeSrcDirPath; return emailThemeSrcDirPath;
})(), })(),
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"), "reactAppBuildDirPath": getAppInputPath(),
buildOptions, buildOptions,
"keycloakVersion": buildOptions.keycloakVersionDefaultAssets "keycloakVersion": buildOptions.keycloakVersionDefaultAssets
}); });
const { jarFilePath } = generateJavaStackFiles({ const { jarFilePath } = generateJavaStackFiles({
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath,
doBundlesEmailTemplate, doBundlesEmailTemplate,
buildOptions buildOptions
}); });
@ -65,7 +61,7 @@ export async function main() {
case "keycloakify": case "keycloakify":
logger.log("🫶 Let keycloakify do its thang"); logger.log("🫶 Let keycloakify do its thang");
await jar({ await jar({
"rootPath": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources"), "rootPath": pathJoin(buildOptions.keycloakBuildPath, "src", "main", "resources"),
"version": buildOptions.version, "version": buildOptions.version,
"groupId": buildOptions.groupId, "groupId": buildOptions.groupId,
"artifactId": buildOptions.artifactId, "artifactId": buildOptions.artifactId,
@ -74,7 +70,7 @@ export async function main() {
break; break;
case "mvn": case "mvn":
logger.log("🫙 Run maven to deliver a jar"); logger.log("🫙 Run maven to deliver a jar");
child_process.execSync("mvn package", { "cwd": keycloakThemeBuildingDirPath }); child_process.execSync("mvn package", { "cwd": buildOptions.keycloakBuildPath });
break; break;
default: default:
assert<Equals<typeof buildOptions.bundler, never>>(false); assert<Equals<typeof buildOptions.bundler, never>>(false);
@ -84,7 +80,7 @@ export async function main() {
const containerKeycloakVersion = "20.0.1"; const containerKeycloakVersion = "20.0.1";
generateStartKeycloakTestingContainer({ generateStartKeycloakTestingContainer({
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath,
"keycloakVersion": containerKeycloakVersion, "keycloakVersion": containerKeycloakVersion,
buildOptions buildOptions
}); });
@ -92,7 +88,7 @@ export async function main() {
logger.log( 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.`, `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. //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:`, `To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
"", "",
`👉 $ .${pathSep}${pathRelative( `👉 $ .${pathSep}${pathRelative(
reactProjectDirPath, getReactProjectDirPath(),
pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename) 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`, `Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags`,

View File

@ -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<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, 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;
};

View File

@ -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
}
});

View File

@ -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 }
);
})();

View File

@ -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<string, unknown>),
getParsedPackageJson: () => ({
keycloakify: {
appInputPath: "./custom_input/build",
keycloakBuildDir: "./custom_output"
}
})
}));
vi.mock("keycloakify/bin/promptKeycloakVersion", async () => ({
...((await vi.importActual("keycloakify/bin/promptKeycloakVersion")) as Record<string, unknown>),
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 }
);
});

View File

@ -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<string, unknown>),
getParsedPackageJson: () => ({})
}));
vi.mock("keycloakify/bin/promptKeycloakVersion", async () => ({
...((await vi.importActual("keycloakify/bin/promptKeycloakVersion")) as Record<string, unknown>),
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 }
);
});

View File

@ -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"; import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project"); export async function setupSampleReactProject(destDirPath: string) {
export async function setupSampleReactProject() {
await downloadAndUnzip({ await downloadAndUnzip({
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip", "url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
"destDirPath": sampleReactProjectDirPath "destDirPath": destDirPath
}); });
} }

View File

@ -6,6 +6,7 @@ export default defineConfig({
test: { test: {
alias: { alias: {
"keycloakify": path.resolve(__dirname, "./src") "keycloakify": path.resolve(__dirname, "./src")
} },
watchExclude: ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**", "**/sample_custom_react_project/**"]
} }
}); });