keycloak_theme/src/bin/shared/buildContext.ts

355 lines
12 KiB
TypeScript
Raw Normal View History

2022-08-16 14:41:06 +07:00
import { parse as urlParse } from "url";
2023-09-03 23:26:34 +02:00
import { join as pathJoin } from "path";
2024-05-16 08:23:37 +02:00
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import { getNpmWorkspaceRootDirPath } from "../tools/getNpmWorkspaceRootDirPath";
import type { CliCommandOptions } from "../main";
import { z } from "zod";
import * as fs from "fs";
2024-06-13 22:58:32 +02:00
import { assert, type Equals } from "tsafe";
2024-05-16 08:23:37 +02:00
import * as child_process from "child_process";
import { vitePluginSubScriptEnvNames } from "./constants";
2022-08-16 14:41:06 +07:00
2024-06-09 09:15:16 +02:00
export type BuildContext = {
2024-01-30 05:54:36 +01:00
bundler: "vite" | "webpack";
2023-08-21 05:54:17 +02:00
themeVersion: string;
2024-06-10 07:57:12 +02:00
themeNames: [string, ...string[]];
2023-08-21 05:54:17 +02:00
extraThemeProperties: string[] | undefined;
groupId: string;
artifactId: string;
2023-09-03 21:02:51 +02:00
loginThemeResourcesFromKeycloakVersion: string;
projectDirPath: string;
projectBuildDirPath: string;
2023-08-21 05:54:17 +02:00
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
keycloakifyBuildDirPath: string;
2023-09-03 23:26:34 +02:00
publicDirPath: string;
cacheDirPath: string;
2023-08-21 05:54:17 +02:00
/** If your app is hosted under a subpath, it's the case in CRA if you have "homepage": "https://example.com/my-app" in your package.json
* In this case the urlPathname will be "/my-app/" */
urlPathname: string | undefined;
2024-01-30 00:06:17 +01:00
assetsDirPath: string;
2024-02-11 18:28:58 +01:00
npmWorkspaceRootDirPath: string;
2024-06-06 01:31:00 +02:00
kcContextExclusionsFtlCode: string | undefined;
environmentVariables: { name: string; default: string }[];
2023-08-21 05:54:17 +02:00
};
2022-08-16 14:41:06 +07:00
2024-06-09 09:15:16 +02:00
export type BuildOptions = {
themeName?: string | string[];
environmentVariables?: { name: string; default: string }[];
2024-05-16 08:23:37 +02:00
extraThemeProperties?: string[];
artifactId?: string;
groupId?: string;
loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string;
kcContextExclusionsFtl?: string;
2024-05-16 08:23:37 +02:00
};
export type ResolvedViteConfig = {
buildDir: string;
publicDir: string;
assetsDir: string;
urlPathname: string | undefined;
2024-06-09 09:15:16 +02:00
buildOptions: BuildOptions;
2024-05-16 08:23:37 +02:00
};
2024-06-09 09:15:16 +02:00
export function getBuildContext(params: {
2024-05-20 15:48:51 +02:00
cliCommandOptions: CliCommandOptions;
2024-06-09 09:15:16 +02:00
}): BuildContext {
const { cliCommandOptions } = params;
const projectDirPath = (() => {
if (cliCommandOptions.projectDirPath === undefined) {
return process.cwd();
}
return getAbsoluteAndInOsFormatPath({
pathIsh: cliCommandOptions.projectDirPath,
2024-05-20 15:48:51 +02:00
cwd: process.cwd()
});
})();
2024-05-16 08:23:37 +02:00
const { resolvedViteConfig } = (() => {
2024-05-20 15:48:51 +02:00
if (
fs
.readdirSync(projectDirPath)
2024-05-20 15:48:51 +02:00
.find(fileBasename => fileBasename.startsWith("vite.config")) ===
undefined
) {
return { resolvedViteConfig: undefined };
2024-05-16 08:23:37 +02:00
}
const output = child_process
.execSync("npx vite", {
cwd: projectDirPath,
2024-05-20 15:48:51 +02:00
env: {
2024-05-16 08:23:37 +02:00
...process.env,
[vitePluginSubScriptEnvNames.resolveViteConfig]: "true"
}
})
.toString("utf8");
2024-05-20 15:48:51 +02:00
assert(
output.includes(vitePluginSubScriptEnvNames.resolveViteConfig),
"Seems like the Keycloakify's Vite plugin is not installed."
);
2024-05-16 08:23:37 +02:00
2024-05-20 15:48:51 +02:00
const resolvedViteConfigStr = output
.split(vitePluginSubScriptEnvNames.resolveViteConfig)
.reverse()[0];
2024-05-16 08:23:37 +02:00
const resolvedViteConfig: ResolvedViteConfig = JSON.parse(resolvedViteConfigStr);
return { resolvedViteConfig };
})();
const parsedPackageJson = (() => {
2024-06-13 22:58:32 +02:00
type WebpackSpecificBuildOptions = {
projectBuildDirPath?: string;
};
2024-05-16 08:23:37 +02:00
type ParsedPackageJson = {
name: string;
version?: string;
homepage?: string;
2024-06-13 22:58:32 +02:00
keycloakify?: {
themeName?: string | string[];
environmentVariables?: { name: string; default: string }[];
extraThemeProperties?: string[];
artifactId?: string;
groupId?: string;
loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string;
kcContextExclusionsFtl?: string;
projectBuildDirPath?: string;
2024-05-20 15:48:51 +02:00
};
2024-05-16 08:23:37 +02:00
};
2024-06-13 22:58:32 +02:00
{
type Got = NonNullable<ParsedPackageJson["keycloakify"]>;
type Expected = BuildOptions & WebpackSpecificBuildOptions;
assert<Equals<Got, Expected>>();
}
2024-05-16 08:23:37 +02:00
const zParsedPackageJson = z.object({
2024-05-20 15:48:51 +02:00
name: z.string(),
version: z.string().optional(),
homepage: z.string().optional(),
keycloakify: z
2024-05-16 08:23:37 +02:00
.object({
2024-05-20 15:48:51 +02:00
extraThemeProperties: z.array(z.string()).optional(),
artifactId: z.string().optional(),
groupId: z.string().optional(),
loginThemeResourcesFromKeycloakVersion: z.string().optional(),
projectBuildDirPath: z.string().optional(),
2024-05-20 15:48:51 +02:00
keycloakifyBuildDirPath: z.string().optional(),
2024-06-13 22:58:32 +02:00
kcContextExclusionsFtl: z.string().optional(),
environmentVariables: z
.array(
z.object({
name: z.string(),
default: z.string()
})
)
.optional(),
2024-05-20 15:48:51 +02:00
themeName: z.union([z.string(), z.array(z.string())]).optional()
2024-05-16 08:23:37 +02:00
})
.optional()
});
{
2024-06-13 22:58:32 +02:00
type Got = z.infer<typeof zParsedPackageJson>;
2024-05-16 08:23:37 +02:00
type Expected = ParsedPackageJson;
2024-06-13 22:58:32 +02:00
assert<Equals<Got, Expected>>();
2024-05-16 08:23:37 +02:00
}
2024-05-20 15:48:51 +02:00
return zParsedPackageJson.parse(
JSON.parse(
fs.readFileSync(pathJoin(projectDirPath, "package.json")).toString("utf8")
2024-05-20 15:48:51 +02:00
)
);
2024-05-16 08:23:37 +02:00
})();
2024-06-09 09:15:16 +02:00
const buildOptions: BuildOptions = {
2024-05-16 08:23:37 +02:00
...parsedPackageJson.keycloakify,
2024-06-09 09:15:16 +02:00
...resolvedViteConfig?.buildOptions
};
2022-08-16 14:41:06 +07:00
2024-06-10 07:57:12 +02:00
const themeNames = ((): [string, ...string[]] => {
2024-06-09 09:15:16 +02:00
if (buildOptions.themeName === undefined) {
return [
2024-01-30 05:54:36 +01:00
parsedPackageJson.name
.replace(/^@(.*)/, "$1")
.split("/")
.join("-")
];
}
2022-08-16 14:41:06 +07:00
2024-06-09 09:15:16 +02:00
if (typeof buildOptions.themeName === "string") {
return [buildOptions.themeName];
}
2024-06-10 07:57:12 +02:00
const [mainThemeName, ...themeVariantNames] = buildOptions.themeName;
assert(mainThemeName !== undefined);
return [mainThemeName, ...themeVariantNames];
})();
2022-08-16 14:41:06 +07:00
const projectBuildDirPath = (() => {
2024-01-30 06:55:26 +01:00
webpack: {
if (resolvedViteConfig !== undefined) {
break webpack;
}
if (parsedPackageJson.keycloakify?.projectBuildDirPath !== undefined) {
2024-01-30 06:55:26 +01:00
return getAbsoluteAndInOsFormatPath({
pathIsh: parsedPackageJson.keycloakify.projectBuildDirPath,
cwd: projectDirPath
2024-01-30 06:55:26 +01:00
});
}
return pathJoin(projectDirPath, "build");
2024-01-30 06:55:26 +01:00
}
return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
2024-01-30 06:55:26 +01:00
})();
2024-01-30 05:54:36 +01:00
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({
projectDirPath,
2024-05-20 15:48:51 +02:00
dependencyExpected: "keycloakify"
});
2024-02-11 18:28:58 +01:00
2024-01-30 05:54:36 +01:00
return {
2024-05-20 15:48:51 +02:00
bundler: resolvedViteConfig !== undefined ? "vite" : "webpack",
themeVersion:
process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
themeNames,
2024-06-09 09:15:16 +02:00
extraThemeProperties: buildOptions.extraThemeProperties,
2024-05-20 15:48:51 +02:00
groupId: (() => {
const fallbackGroupId = `${themeNames[0]}.keycloak`;
2023-08-21 05:54:17 +02:00
return (
process.env.KEYCLOAKIFY_GROUP_ID ??
2024-06-09 09:15:16 +02:00
buildOptions.groupId ??
2024-01-30 05:54:36 +01:00
(parsedPackageJson.homepage === undefined
2023-08-21 05:54:17 +02:00
? fallbackGroupId
2024-01-30 05:54:36 +01:00
: urlParse(parsedPackageJson.homepage)
2023-08-21 05:54:17 +02:00
.host?.replace(/:[0-9]+$/, "")
?.split(".")
.reverse()
.join(".") ?? fallbackGroupId) + ".keycloak"
);
})(),
2024-05-20 15:48:51 +02:00
artifactId:
process.env.KEYCLOAKIFY_ARTIFACT_ID ??
2024-06-09 09:15:16 +02:00
buildOptions.artifactId ??
2024-05-20 15:48:51 +02:00
`${themeNames[0]}-keycloak-theme`,
loginThemeResourcesFromKeycloakVersion:
2024-06-09 09:15:16 +02:00
buildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4",
projectDirPath,
projectBuildDirPath,
2024-05-20 15:48:51 +02:00
keycloakifyBuildDirPath: (() => {
2024-06-09 09:15:16 +02:00
if (buildOptions.keycloakifyBuildDirPath !== undefined) {
return getAbsoluteAndInOsFormatPath({
2024-06-09 09:15:16 +02:00
pathIsh: buildOptions.keycloakifyBuildDirPath,
cwd: projectDirPath
});
}
return pathJoin(
projectDirPath,
2024-05-20 15:48:51 +02:00
resolvedViteConfig?.buildDir === undefined
? "build_keycloak"
: `${resolvedViteConfig.buildDir}_keycloak`
);
})(),
2024-05-20 15:48:51 +02:00
publicDirPath: (() => {
2024-01-30 06:55:26 +01:00
webpack: {
if (resolvedViteConfig !== undefined) {
break webpack;
}
2024-01-27 18:49:29 +01:00
2024-01-30 06:55:26 +01:00
if (process.env.PUBLIC_DIR_PATH !== undefined) {
return getAbsoluteAndInOsFormatPath({
2024-05-20 15:48:51 +02:00
pathIsh: process.env.PUBLIC_DIR_PATH,
cwd: projectDirPath
2024-01-30 06:55:26 +01:00
});
}
return pathJoin(projectDirPath, "public");
2023-08-21 05:54:17 +02:00
}
2022-08-16 14:41:06 +07:00
return pathJoin(projectDirPath, resolvedViteConfig.publicDir);
2023-08-21 05:54:17 +02:00
})(),
2024-05-20 15:48:51 +02:00
cacheDirPath: (() => {
const cacheDirPath = pathJoin(
(() => {
if (process.env.XDG_CACHE_HOME !== undefined) {
return getAbsoluteAndInOsFormatPath({
2024-05-20 15:48:51 +02:00
pathIsh: process.env.XDG_CACHE_HOME,
cwd: process.cwd()
});
}
return pathJoin(npmWorkspaceRootDirPath, "node_modules", ".cache");
})(),
"keycloakify"
);
return cacheDirPath;
})(),
2024-05-20 15:48:51 +02:00
urlPathname: (() => {
2024-01-30 06:55:26 +01:00
webpack: {
if (resolvedViteConfig !== undefined) {
break webpack;
}
const { homepage } = parsedPackageJson;
let url: URL | undefined = undefined;
2022-08-16 14:41:06 +07:00
2024-01-30 06:55:26 +01:00
if (homepage !== undefined) {
url = new URL(homepage);
}
if (url === undefined) {
return undefined;
}
2022-08-16 14:41:06 +07:00
2024-01-30 06:55:26 +01:00
const out = url.pathname.replace(/([^/])$/, "$1/");
return out === "/" ? undefined : out;
2023-08-21 05:54:17 +02:00
}
2022-08-16 14:41:06 +07:00
2024-01-30 06:55:26 +01:00
return resolvedViteConfig.urlPathname;
})(),
2024-05-20 15:48:51 +02:00
assetsDirPath: (() => {
2024-01-30 06:55:26 +01:00
webpack: {
if (resolvedViteConfig !== undefined) {
break webpack;
}
return pathJoin(projectBuildDirPath, "static");
2023-08-21 05:54:17 +02:00
}
2022-08-16 14:41:06 +07:00
return pathJoin(projectBuildDirPath, resolvedViteConfig.assetsDir);
})(),
2024-06-06 01:31:00 +02:00
npmWorkspaceRootDirPath,
kcContextExclusionsFtlCode: (() => {
if (buildOptions.kcContextExclusionsFtl === undefined) {
return undefined;
}
if (buildOptions.kcContextExclusionsFtl.endsWith(".ftl")) {
const kcContextExclusionsFtlPath = getAbsoluteAndInOsFormatPath({
pathIsh: buildOptions.kcContextExclusionsFtl,
cwd: projectDirPath
});
return fs.readFileSync(kcContextExclusionsFtlPath).toString("utf8");
}
return buildOptions.kcContextExclusionsFtl;
})(),
2024-06-09 09:15:16 +02:00
environmentVariables: buildOptions.environmentVariables ?? []
2023-08-21 05:54:17 +02:00
};
2022-08-16 14:41:06 +07:00
}