2022-08-16 14:41:06 +07:00
|
|
|
import { assert } from "tsafe/assert";
|
|
|
|
import { id } from "tsafe/id";
|
|
|
|
import { parse as urlParse } from "url";
|
2023-02-04 17:44:02 +01:00
|
|
|
import { typeGuard } from "tsafe/typeGuard";
|
2023-02-04 18:02:39 +01:00
|
|
|
import { symToStr } from "tsafe/symToStr";
|
2023-04-01 14:02:32 +02:00
|
|
|
import { bundlers, getParsedPackageJson, type Bundler } from "./parsedPackageJson";
|
2023-04-02 03:10:16 +02:00
|
|
|
import * as fs from "fs";
|
|
|
|
import { join as pathJoin, sep as pathSep } from "path";
|
2023-04-19 05:04:11 +02:00
|
|
|
import parseArgv from "minimist";
|
2022-08-16 14:41:06 +07:00
|
|
|
|
|
|
|
/** Consolidated build option gathered form CLI arguments and config in package.json */
|
|
|
|
export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets;
|
|
|
|
|
|
|
|
export namespace BuildOptions {
|
|
|
|
export type Common = {
|
2022-09-08 12:06:26 +03:00
|
|
|
isSilent: boolean;
|
2023-04-06 16:38:13 +02:00
|
|
|
themeVersion: string;
|
2022-08-16 14:41:06 +07:00
|
|
|
themeName: string;
|
2023-06-08 23:09:14 +02:00
|
|
|
extraThemeNames: string[];
|
2023-03-16 22:13:46 +01:00
|
|
|
extraLoginPages: string[] | undefined;
|
|
|
|
extraAccountPages: string[] | undefined;
|
2022-08-16 14:41:06 +07:00
|
|
|
extraThemeProperties?: string[];
|
|
|
|
groupId: string;
|
2023-02-04 19:36:42 +01:00
|
|
|
artifactId: string;
|
2023-02-04 18:02:39 +01:00
|
|
|
bundler: Bundler;
|
2023-03-25 04:27:23 +01:00
|
|
|
keycloakVersionDefaultAssets: string;
|
2023-04-02 03:10:16 +02:00
|
|
|
/** Directory of your built react project. Defaults to {cwd}/build */
|
|
|
|
reactAppBuildDirPath: string;
|
|
|
|
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
|
|
|
|
keycloakifyBuildDirPath: string;
|
2022-08-16 14:41:06 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
export type Standalone = Common & {
|
|
|
|
isStandalone: true;
|
|
|
|
urlPathname: string | undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains;
|
|
|
|
|
|
|
|
export namespace ExternalAssets {
|
|
|
|
export type CommonExternalAssets = Common & {
|
|
|
|
isStandalone: false;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type SameDomain = CommonExternalAssets & {
|
2022-09-07 11:25:46 +02:00
|
|
|
areAppAndKeycloakServerSharingSameDomain: true;
|
2022-08-16 14:41:06 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
export type DifferentDomains = CommonExternalAssets & {
|
2022-09-07 11:25:46 +02:00
|
|
|
areAppAndKeycloakServerSharingSameDomain: false;
|
2022-08-16 14:41:06 +07:00
|
|
|
urlOrigin: string;
|
|
|
|
urlPathname: string | undefined;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-19 05:04:11 +02:00
|
|
|
export function readBuildOptions(params: { projectDirPath: string; processArgv: string[] }): BuildOptions {
|
|
|
|
const { projectDirPath, processArgv } = params;
|
2023-04-18 03:10:29 +02:00
|
|
|
|
2023-04-19 05:04:11 +02:00
|
|
|
const { isExternalAssetsCliParamProvided, isSilentCliParamProvided } = (() => {
|
|
|
|
const argv = parseArgv(processArgv);
|
|
|
|
|
|
|
|
return {
|
|
|
|
"isSilentCliParamProvided": typeof argv["silent"] === "boolean" ? argv["silent"] : false,
|
|
|
|
"isExternalAssetsCliParamProvided": typeof argv["external-assets"] === "boolean" ? argv["external-assets"] : false
|
|
|
|
};
|
|
|
|
})();
|
2022-08-16 14:41:06 +07:00
|
|
|
|
2023-04-02 03:10:16 +02:00
|
|
|
const parsedPackageJson = getParsedPackageJson({ projectDirPath });
|
2022-08-16 14:41:06 +07:00
|
|
|
|
|
|
|
const url = (() => {
|
|
|
|
const { homepage } = parsedPackageJson;
|
|
|
|
|
|
|
|
let url: URL | undefined = undefined;
|
|
|
|
|
|
|
|
if (homepage !== undefined) {
|
|
|
|
url = new URL(homepage);
|
|
|
|
}
|
|
|
|
|
2023-04-02 03:10:16 +02:00
|
|
|
const CNAME = (() => {
|
|
|
|
const cnameFilePath = pathJoin(projectDirPath, "public", "CNAME");
|
|
|
|
|
|
|
|
if (!fs.existsSync(cnameFilePath)) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fs.readFileSync(cnameFilePath).toString("utf8");
|
|
|
|
})();
|
|
|
|
|
2022-08-16 14:41:06 +07:00
|
|
|
if (CNAME !== undefined) {
|
|
|
|
url = new URL(`https://${CNAME.replace(/\s+$/, "")}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (url === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
"origin": url.origin,
|
|
|
|
"pathname": (() => {
|
|
|
|
const out = url.pathname.replace(/([^/])$/, "$1/");
|
|
|
|
|
|
|
|
return out === "/" ? undefined : out;
|
2022-08-20 11:44:48 +07:00
|
|
|
})()
|
2022-08-16 14:41:06 +07:00
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
const common: BuildOptions.Common = (() => {
|
|
|
|
const { name, keycloakify = {}, version, homepage } = parsedPackageJson;
|
|
|
|
|
2023-06-08 23:09:14 +02:00
|
|
|
const {
|
|
|
|
extraPages,
|
|
|
|
extraLoginPages,
|
|
|
|
extraAccountPages,
|
|
|
|
extraThemeProperties,
|
|
|
|
groupId,
|
|
|
|
artifactId,
|
|
|
|
bundler,
|
|
|
|
keycloakVersionDefaultAssets,
|
|
|
|
extraThemeNames = []
|
|
|
|
} = keycloakify ?? {};
|
2022-08-16 14:41:06 +07:00
|
|
|
|
2023-04-01 14:02:32 +02:00
|
|
|
const themeName =
|
|
|
|
keycloakify.themeName ??
|
|
|
|
name
|
|
|
|
.replace(/^@(.*)/, "$1")
|
|
|
|
.split("/")
|
|
|
|
.join("-");
|
2022-08-16 14:41:06 +07:00
|
|
|
|
|
|
|
return {
|
|
|
|
themeName,
|
2023-06-08 23:09:14 +02:00
|
|
|
extraThemeNames,
|
2023-02-04 17:44:02 +01:00
|
|
|
"bundler": (() => {
|
|
|
|
const { KEYCLOAKIFY_BUNDLER } = process.env;
|
|
|
|
|
|
|
|
assert(
|
|
|
|
typeGuard<Bundler | undefined>(
|
|
|
|
KEYCLOAKIFY_BUNDLER,
|
2023-02-04 18:02:39 +01:00
|
|
|
[undefined, ...id<readonly string[]>(bundlers)].includes(KEYCLOAKIFY_BUNDLER)
|
|
|
|
),
|
|
|
|
`${symToStr({ KEYCLOAKIFY_BUNDLER })} should be one of ${bundlers.join(", ")}`
|
2023-02-04 17:44:02 +01:00
|
|
|
);
|
|
|
|
|
2023-02-05 14:58:38 +01:00
|
|
|
return KEYCLOAKIFY_BUNDLER ?? bundler ?? "keycloakify";
|
2023-02-04 17:44:02 +01:00
|
|
|
})(),
|
2023-02-04 19:36:42 +01:00
|
|
|
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? artifactId ?? `${themeName}-keycloak-theme`,
|
2022-08-16 14:41:06 +07:00
|
|
|
"groupId": (() => {
|
|
|
|
const fallbackGroupId = `${themeName}.keycloak`;
|
|
|
|
|
|
|
|
return (
|
2023-02-03 14:28:06 +01:00
|
|
|
process.env.KEYCLOAKIFY_GROUP_ID ??
|
|
|
|
groupId ??
|
2022-08-16 14:41:06 +07:00
|
|
|
(!homepage
|
|
|
|
? fallbackGroupId
|
|
|
|
: urlParse(homepage)
|
|
|
|
.host?.replace(/:[0-9]+$/, "")
|
|
|
|
?.split(".")
|
|
|
|
.reverse()
|
|
|
|
.join(".") ?? fallbackGroupId) + ".keycloak"
|
|
|
|
);
|
|
|
|
})(),
|
2023-04-06 16:38:13 +02:00
|
|
|
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? process.env.KEYCLOAKIFY_VERSION ?? version ?? "0.0.0",
|
2023-03-16 22:13:46 +01:00
|
|
|
"extraLoginPages": [...(extraPages ?? []), ...(extraLoginPages ?? [])],
|
|
|
|
extraAccountPages,
|
2022-09-08 12:06:26 +03:00
|
|
|
extraThemeProperties,
|
2023-04-19 05:04:11 +02:00
|
|
|
"isSilent": isSilentCliParamProvided,
|
|
|
|
"keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3",
|
2023-04-02 03:10:16 +02:00
|
|
|
"reactAppBuildDirPath": (() => {
|
|
|
|
let { reactAppBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {};
|
|
|
|
|
|
|
|
if (reactAppBuildDirPath === undefined) {
|
|
|
|
return pathJoin(projectDirPath, "build");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pathSep === "\\") {
|
|
|
|
reactAppBuildDirPath = reactAppBuildDirPath.replace(/\//g, pathSep);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reactAppBuildDirPath.startsWith(`.${pathSep}`)) {
|
|
|
|
return pathJoin(projectDirPath, reactAppBuildDirPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
return reactAppBuildDirPath;
|
|
|
|
})(),
|
|
|
|
"keycloakifyBuildDirPath": (() => {
|
|
|
|
let { keycloakifyBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {};
|
|
|
|
|
|
|
|
if (keycloakifyBuildDirPath === undefined) {
|
|
|
|
return pathJoin(projectDirPath, "build_keycloak");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pathSep === "\\") {
|
|
|
|
keycloakifyBuildDirPath = keycloakifyBuildDirPath.replace(/\//g, pathSep);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (keycloakifyBuildDirPath.startsWith(`.${pathSep}`)) {
|
|
|
|
return pathJoin(projectDirPath, keycloakifyBuildDirPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
return keycloakifyBuildDirPath;
|
2023-06-19 00:09:21 +02:00
|
|
|
})()
|
2022-08-16 14:41:06 +07:00
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
if (isExternalAssetsCliParamProvided) {
|
|
|
|
const commonExternalAssets = id<BuildOptions.ExternalAssets.CommonExternalAssets>({
|
|
|
|
...common,
|
2022-08-20 11:44:48 +07:00
|
|
|
"isStandalone": false
|
2022-08-16 14:41:06 +07:00
|
|
|
});
|
|
|
|
|
2022-09-07 11:25:46 +02:00
|
|
|
if (parsedPackageJson.keycloakify?.areAppAndKeycloakServerSharingSameDomain) {
|
2022-08-16 14:41:06 +07:00
|
|
|
return id<BuildOptions.ExternalAssets.SameDomain>({
|
|
|
|
...commonExternalAssets,
|
2022-09-07 11:25:46 +02:00
|
|
|
"areAppAndKeycloakServerSharingSameDomain": true
|
2022-08-16 14:41:06 +07:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
assert(
|
|
|
|
url !== undefined,
|
|
|
|
[
|
|
|
|
"Can't compile in external assets mode if we don't know where",
|
|
|
|
"the app will be hosted.",
|
|
|
|
"You should provide a homepage field in the package.json (or create a",
|
|
|
|
"public/CNAME file.",
|
|
|
|
"Alternatively, if your app and the Keycloak server are on the same domain, ",
|
|
|
|
"eg https://example.com is your app and https://example.com/auth is the keycloak",
|
2022-09-07 11:25:46 +02:00
|
|
|
'admin UI, you can set "keycloakify": { "areAppAndKeycloakServerSharingSameDomain": true }',
|
2022-08-20 11:44:48 +07:00
|
|
|
"in your package.json"
|
|
|
|
].join(" ")
|
2022-08-16 14:41:06 +07:00
|
|
|
);
|
|
|
|
|
|
|
|
return id<BuildOptions.ExternalAssets.DifferentDomains>({
|
|
|
|
...commonExternalAssets,
|
2022-09-07 11:25:46 +02:00
|
|
|
"areAppAndKeycloakServerSharingSameDomain": false,
|
2022-08-16 14:41:06 +07:00
|
|
|
"urlOrigin": url.origin,
|
2022-08-20 11:44:48 +07:00
|
|
|
"urlPathname": url.pathname
|
2022-08-16 14:41:06 +07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return id<BuildOptions.Standalone>({
|
|
|
|
...common,
|
|
|
|
"isStandalone": true,
|
2022-08-20 11:44:48 +07:00
|
|
|
"urlPathname": url?.pathname
|
2022-08-16 14:41:06 +07:00
|
|
|
});
|
|
|
|
}
|