2024-11-02 22:39:03 +01:00
|
|
|
import { assert, type Equals } from "tsafe/assert";
|
|
|
|
import { id } from "tsafe/id";
|
|
|
|
import { z } from "zod";
|
2024-11-09 14:02:19 +01:00
|
|
|
import { join as pathJoin, dirname as pathDirname } from "path";
|
2024-11-02 22:39:03 +01:00
|
|
|
import * as fsPr from "fs/promises";
|
|
|
|
import type { BuildContext } from "../shared/buildContext";
|
|
|
|
import { is } from "tsafe/is";
|
|
|
|
import { existsAsync } from "../tools/fs.existsAsync";
|
|
|
|
import { listInstalledModules } from "../tools/listInstalledModules";
|
|
|
|
import { crawlAsync } from "../tools/crawlAsync";
|
2024-11-17 19:22:34 +01:00
|
|
|
import { getIsPrettierAvailable, getPrettier } from "../tools/runPrettier";
|
2024-11-02 22:39:03 +01:00
|
|
|
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
|
|
|
|
import {
|
2024-11-09 14:02:19 +01:00
|
|
|
getUiModuleFileSourceCodeReadyToBeCopied,
|
|
|
|
type BuildContextLike as BuildContextLike_getUiModuleFileSourceCodeReadyToBeCopied
|
|
|
|
} from "./getUiModuleFileSourceCodeReadyToBeCopied";
|
2024-11-02 22:39:03 +01:00
|
|
|
import * as crypto from "crypto";
|
2024-11-09 14:02:19 +01:00
|
|
|
import { KEYCLOAK_THEME } from "../shared/constants";
|
2024-11-17 20:53:38 +01:00
|
|
|
import { exclude } from "tsafe/exclude";
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
export type UiModuleMeta = {
|
|
|
|
moduleName: string;
|
|
|
|
version: string;
|
|
|
|
files: {
|
|
|
|
fileRelativePath: string;
|
|
|
|
hash: string;
|
2024-11-09 14:02:19 +01:00
|
|
|
copyableFilePath: string;
|
2024-11-03 01:56:41 +01:00
|
|
|
}[];
|
|
|
|
peerDependencies: Record<string, string>;
|
2024-11-02 22:39:03 +01:00
|
|
|
};
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
const zUiModuleMeta = (() => {
|
|
|
|
type ExpectedType = UiModuleMeta;
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
const zTargetType = z.object({
|
|
|
|
moduleName: z.string(),
|
|
|
|
version: z.string(),
|
|
|
|
files: z.array(
|
|
|
|
z.object({
|
|
|
|
fileRelativePath: z.string(),
|
2024-11-09 14:02:19 +01:00
|
|
|
hash: z.string(),
|
|
|
|
copyableFilePath: z.string()
|
2024-11-02 22:39:03 +01:00
|
|
|
})
|
2024-11-03 01:56:41 +01:00
|
|
|
),
|
|
|
|
peerDependencies: z.record(z.string())
|
2024-11-02 22:39:03 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
type InferredType = z.infer<typeof zTargetType>;
|
|
|
|
|
|
|
|
assert<Equals<InferredType, ExpectedType>>();
|
|
|
|
|
|
|
|
return id<z.ZodType<ExpectedType>>(zTargetType);
|
|
|
|
})();
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
type ParsedCacheFile = {
|
|
|
|
keycloakifyVersion: string;
|
|
|
|
prettierConfigHash: string | null;
|
2024-11-09 14:02:19 +01:00
|
|
|
thisFilePath: string;
|
2024-11-03 01:56:41 +01:00
|
|
|
uiModuleMetas: UiModuleMeta[];
|
|
|
|
};
|
|
|
|
|
|
|
|
const zParsedCacheFile = (() => {
|
|
|
|
type ExpectedType = ParsedCacheFile;
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
const zTargetType = z.object({
|
|
|
|
keycloakifyVersion: z.string(),
|
|
|
|
prettierConfigHash: z.union([z.string(), z.null()]),
|
2024-11-09 14:02:19 +01:00
|
|
|
thisFilePath: z.string(),
|
2024-11-03 01:56:41 +01:00
|
|
|
uiModuleMetas: z.array(zUiModuleMeta)
|
2024-11-02 22:39:03 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
type InferredType = z.infer<typeof zTargetType>;
|
|
|
|
|
|
|
|
assert<Equals<InferredType, ExpectedType>>();
|
|
|
|
|
|
|
|
return id<z.ZodType<ExpectedType>>(zTargetType);
|
|
|
|
})();
|
|
|
|
|
2024-11-09 14:02:19 +01:00
|
|
|
const CACHE_FILE_RELATIVE_PATH = pathJoin("ui-modules", "cache.json");
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-09 14:02:19 +01:00
|
|
|
export type BuildContextLike =
|
|
|
|
BuildContextLike_getUiModuleFileSourceCodeReadyToBeCopied & {
|
|
|
|
cacheDirPath: string;
|
|
|
|
packageJsonFilePath: string;
|
|
|
|
projectDirPath: string;
|
|
|
|
};
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
export async function getUiModuleMetas(params: {
|
2024-11-02 22:39:03 +01:00
|
|
|
buildContext: BuildContextLike;
|
2024-11-03 01:56:41 +01:00
|
|
|
}): Promise<UiModuleMeta[]> {
|
2024-11-02 22:39:03 +01:00
|
|
|
const { buildContext } = params;
|
|
|
|
|
2024-11-09 14:02:19 +01:00
|
|
|
const cacheFilePath = pathJoin(buildContext.cacheDirPath, CACHE_FILE_RELATIVE_PATH);
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
const keycloakifyVersion = readThisNpmPackageVersion();
|
|
|
|
|
|
|
|
const prettierConfigHash = await (async () => {
|
|
|
|
if (!(await getIsPrettierAvailable())) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-11-17 19:22:34 +01:00
|
|
|
const { configHash } = await getPrettier();
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-17 19:22:34 +01:00
|
|
|
return configHash;
|
2024-11-02 22:39:03 +01:00
|
|
|
})();
|
|
|
|
|
2024-11-09 14:02:19 +01:00
|
|
|
const installedUiModules = await (async () => {
|
|
|
|
const installedModulesWithKeycloakifyInTheName = await listInstalledModules({
|
|
|
|
packageJsonFilePath: buildContext.packageJsonFilePath,
|
|
|
|
projectDirPath: buildContext.packageJsonFilePath,
|
|
|
|
filter: ({ moduleName }) =>
|
|
|
|
moduleName.includes("keycloakify") && moduleName !== "keycloakify"
|
|
|
|
});
|
|
|
|
|
2024-11-17 20:53:38 +01:00
|
|
|
return (
|
|
|
|
await Promise.all(
|
|
|
|
installedModulesWithKeycloakifyInTheName.map(async entry => {
|
|
|
|
if (!(await existsAsync(pathJoin(entry.dirPath, KEYCLOAK_THEME)))) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return entry;
|
|
|
|
})
|
2024-11-09 14:02:19 +01:00
|
|
|
)
|
2024-11-17 20:53:38 +01:00
|
|
|
).filter(exclude(undefined));
|
2024-11-09 14:02:19 +01:00
|
|
|
})();
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
const cacheContent = await (async () => {
|
|
|
|
if (!(await existsAsync(cacheFilePath))) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return await fsPr.readFile(cacheFilePath);
|
|
|
|
})();
|
|
|
|
|
|
|
|
const uiModuleMetas_cacheUpToDate: UiModuleMeta[] = await (async () => {
|
|
|
|
const parsedCacheFile: ParsedCacheFile | undefined = await (async () => {
|
|
|
|
if (cacheContent === undefined) {
|
2024-11-02 22:39:03 +01:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
const cacheContentStr = cacheContent.toString("utf8");
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
let parsedCacheFile: unknown;
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
try {
|
2024-11-03 01:56:41 +01:00
|
|
|
parsedCacheFile = JSON.parse(cacheContentStr);
|
2024-11-02 22:39:03 +01:00
|
|
|
} catch {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2024-11-03 01:56:41 +01:00
|
|
|
zParsedCacheFile.parse(parsedCacheFile);
|
2024-11-02 22:39:03 +01:00
|
|
|
} catch {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
assert(is<ParsedCacheFile>(parsedCacheFile));
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
return parsedCacheFile;
|
2024-11-02 22:39:03 +01:00
|
|
|
})();
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
if (parsedCacheFile === undefined) {
|
2024-11-02 22:39:03 +01:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
if (parsedCacheFile.keycloakifyVersion !== keycloakifyVersion) {
|
2024-11-02 22:39:03 +01:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
if (parsedCacheFile.prettierConfigHash !== prettierConfigHash) {
|
2024-11-02 22:39:03 +01:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2024-11-09 14:02:19 +01:00
|
|
|
if (parsedCacheFile.thisFilePath !== cacheFilePath) {
|
2024-11-03 01:56:41 +01:00
|
|
|
return [];
|
|
|
|
}
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
const uiModuleMetas_cacheUpToDate = parsedCacheFile.uiModuleMetas.filter(
|
|
|
|
uiModuleMeta => {
|
|
|
|
const correspondingInstalledUiModule = installedUiModules.find(
|
|
|
|
installedUiModule =>
|
|
|
|
installedUiModule.moduleName === uiModuleMeta.moduleName
|
|
|
|
);
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
if (correspondingInstalledUiModule === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return correspondingInstalledUiModule.version === uiModuleMeta.version;
|
|
|
|
}
|
|
|
|
);
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
return uiModuleMetas_cacheUpToDate;
|
2024-11-02 22:39:03 +01:00
|
|
|
})();
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
const uiModuleMetas = await Promise.all(
|
2024-11-02 22:39:03 +01:00
|
|
|
installedUiModules.map(
|
2024-11-03 01:56:41 +01:00
|
|
|
async ({
|
|
|
|
moduleName,
|
|
|
|
version,
|
|
|
|
peerDependencies,
|
|
|
|
dirPath
|
|
|
|
}): Promise<UiModuleMeta> => {
|
2024-11-02 22:39:03 +01:00
|
|
|
use_cache: {
|
2024-11-03 01:56:41 +01:00
|
|
|
const uiModuleMeta_cache = uiModuleMetas_cacheUpToDate.find(
|
|
|
|
uiModuleMeta => uiModuleMeta.moduleName === moduleName
|
2024-11-02 22:39:03 +01:00
|
|
|
);
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
if (uiModuleMeta_cache === undefined) {
|
2024-11-02 22:39:03 +01:00
|
|
|
break use_cache;
|
|
|
|
}
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
return uiModuleMeta_cache;
|
2024-11-02 22:39:03 +01:00
|
|
|
}
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
const files: UiModuleMeta["files"] = [];
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
{
|
2024-11-09 14:02:19 +01:00
|
|
|
const srcDirPath = pathJoin(dirPath, KEYCLOAK_THEME);
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
await crawlAsync({
|
|
|
|
dirPath: srcDirPath,
|
|
|
|
returnedPathsType: "relative to dirPath",
|
|
|
|
onFileFound: async fileRelativePath => {
|
2024-11-09 14:02:19 +01:00
|
|
|
const sourceCode =
|
|
|
|
await getUiModuleFileSourceCodeReadyToBeCopied({
|
|
|
|
buildContext,
|
|
|
|
fileRelativePath,
|
2024-11-02 22:39:03 +01:00
|
|
|
isForEjection: false,
|
2024-11-09 14:02:19 +01:00
|
|
|
uiModuleDirPath: dirPath,
|
2024-11-02 22:39:03 +01:00
|
|
|
uiModuleName: moduleName,
|
|
|
|
uiModuleVersion: version
|
2024-11-09 14:02:19 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
const hash = computeHash(sourceCode);
|
|
|
|
|
|
|
|
const copyableFilePath = pathJoin(
|
|
|
|
pathDirname(cacheFilePath),
|
|
|
|
KEYCLOAK_THEME,
|
|
|
|
fileRelativePath
|
|
|
|
);
|
|
|
|
|
|
|
|
{
|
|
|
|
const dirPath = pathDirname(copyableFilePath);
|
|
|
|
|
|
|
|
if (!(await existsAsync(dirPath))) {
|
|
|
|
await fsPr.mkdir(dirPath, { recursive: true });
|
2024-11-02 22:39:03 +01:00
|
|
|
}
|
2024-11-09 14:02:19 +01:00
|
|
|
}
|
2024-11-02 22:39:03 +01:00
|
|
|
|
2024-11-09 14:02:19 +01:00
|
|
|
fsPr.writeFile(copyableFilePath, sourceCode);
|
2024-11-02 22:39:03 +01:00
|
|
|
|
|
|
|
files.push({
|
|
|
|
fileRelativePath,
|
2024-11-09 14:02:19 +01:00
|
|
|
hash,
|
|
|
|
copyableFilePath
|
2024-11-02 22:39:03 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
return id<UiModuleMeta>({
|
2024-11-02 22:39:03 +01:00
|
|
|
moduleName,
|
2024-11-03 01:56:41 +01:00
|
|
|
version,
|
|
|
|
files,
|
|
|
|
peerDependencies
|
2024-11-02 22:39:03 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2024-11-03 01:56:41 +01:00
|
|
|
update_cache: {
|
|
|
|
const parsedCacheFile = id<ParsedCacheFile>({
|
|
|
|
keycloakifyVersion,
|
|
|
|
prettierConfigHash,
|
2024-11-09 14:02:19 +01:00
|
|
|
thisFilePath: cacheFilePath,
|
2024-11-03 01:56:41 +01:00
|
|
|
uiModuleMetas
|
|
|
|
});
|
|
|
|
|
|
|
|
const cacheContent_new = Buffer.from(
|
|
|
|
JSON.stringify(parsedCacheFile, null, 2),
|
|
|
|
"utf8"
|
|
|
|
);
|
|
|
|
|
|
|
|
if (cacheContent !== undefined && cacheContent_new.equals(cacheContent)) {
|
|
|
|
break update_cache;
|
|
|
|
}
|
|
|
|
|
|
|
|
create_dir: {
|
|
|
|
const dirPath = pathDirname(cacheFilePath);
|
|
|
|
|
|
|
|
if (await existsAsync(dirPath)) {
|
|
|
|
break create_dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
await fsPr.mkdir(dirPath, { recursive: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
await fsPr.writeFile(cacheFilePath, cacheContent_new);
|
|
|
|
}
|
|
|
|
|
|
|
|
return uiModuleMetas;
|
2024-11-02 22:39:03 +01:00
|
|
|
}
|
2024-11-09 14:02:19 +01:00
|
|
|
|
|
|
|
export function computeHash(data: Buffer) {
|
|
|
|
return crypto.createHash("sha256").update(data).digest("hex");
|
|
|
|
}
|