feat(config): add ability to customize input/output directory
This commit is contained in:
@ -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<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, 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()
|
||||
};
|
||||
})();
|
||||
|
||||
|
72
src/bin/keycloakify/build-paths.ts
Normal file
72
src/bin/keycloakify/build-paths.ts
Normal 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 };
|
||||
};
|
@ -4,5 +4,5 @@ export * from "./keycloakify";
|
||||
import { main } from "./keycloakify";
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(e => console.error(e));
|
||||
main();
|
||||
}
|
||||
|
@ -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<Equals<typeof buildOptions.bundler, never>>(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`,
|
||||
|
58
src/bin/keycloakify/parsed-package-json.ts
Normal file
58
src/bin/keycloakify/parsed-package-json.ts
Normal 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;
|
||||
};
|
Reference in New Issue
Block a user