275 lines
8.2 KiB
TypeScript
Raw Normal View History

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-03 01:56:41 +01:00
import { join as pathJoin, sep as pathSep, 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";
import { getIsPrettierAvailable, getPrettierAndConfig } from "../tools/runPrettier";
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import {
getSourceCodeToCopyInUserCodebase,
type BuildContextLike as BuildContextLike_getSourceCodeToCopyInUserCodebase
} from "./getSourceCodeToCopyInUserCodebase";
import * as crypto from "crypto";
2024-11-03 01:56:41 +01:00
export type UiModuleMeta = {
moduleName: string;
version: string;
files: {
fileRelativePath: string;
hash: string;
}[];
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(),
hash: z.string()
})
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;
pathSep: string;
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-03 01:56:41 +01:00
pathSep: z.string(),
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-03 01:56:41 +01:00
const CACHE_FILE_BASENAME = "uiModulesMeta.json";
2024-11-02 22:39:03 +01:00
export type BuildContextLike = BuildContextLike_getSourceCodeToCopyInUserCodebase & {
cacheDirPath: string;
packageJsonFilePath: string;
projectDirPath: string;
};
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-03 01:56:41 +01:00
const cacheFilePath = pathJoin(buildContext.cacheDirPath, CACHE_FILE_BASENAME);
2024-11-02 22:39:03 +01:00
const keycloakifyVersion = readThisNpmPackageVersion();
const prettierConfigHash = await (async () => {
if (!(await getIsPrettierAvailable())) {
return null;
}
const { config } = await getPrettierAndConfig();
return crypto.createHash("sha256").update(JSON.stringify(config)).digest("hex");
})();
const installedUiModules = await listInstalledModules({
packageJsonFilePath: buildContext.packageJsonFilePath,
projectDirPath: buildContext.packageJsonFilePath,
filter: ({ moduleName }) =>
moduleName.includes("keycloakify") && moduleName.endsWith("-ui")
});
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-03 01:56:41 +01:00
if (parsedCacheFile.pathSep !== pathSep) {
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
{
const srcDirPath = pathJoin(dirPath, "src");
await crawlAsync({
dirPath: srcDirPath,
returnedPathsType: "relative to dirPath",
onFileFound: async fileRelativePath => {
const sourceCode = await getSourceCodeToCopyInUserCodebase({
buildContext,
relativeFromDirPath: srcDirPath,
fileRelativePath,
commentData: {
isForEjection: false,
uiModuleName: moduleName,
uiModuleVersion: version
}
});
const hash = crypto
.createHash("sha256")
.update(sourceCode)
.digest("hex");
files.push({
fileRelativePath,
hash
});
}
});
}
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,
pathSep,
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
}