Checkpoint

This commit is contained in:
Joseph Garrone 2024-11-09 14:02:19 +01:00
parent a60a0d0696
commit a73281d46d
16 changed files with 224 additions and 104 deletions

View File

@ -115,7 +115,7 @@ import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
} }
run( run(
`npx ncc build ${join("dist", "vite-plugin", "index.js")} -o ${join( `npx ncc build ${join("dist", "vite-plugin", "index.js")} --external prettier -o ${join(
"dist", "dist",
"ncc_out" "ncc_out"
)}` )}`

View File

@ -14,7 +14,7 @@ import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
import { assert, Equals } from "tsafe/assert"; import { assert, Equals } from "tsafe/assert";
import type { BuildContext } from "./shared/buildContext"; import type { BuildContext } from "./shared/buildContext";
import chalk from "chalk"; import chalk from "chalk";
import { runFormat } from "./tools/runFormat"; import { runPrettier, getIsPrettierAvailable } from "./tools/runPrettier";
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate"; import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { buildContext: BuildContext }) {
@ -119,7 +119,7 @@ export async function command(params: { buildContext: BuildContext }) {
process.exit(-1); process.exit(-1);
} }
const componentCode = fs let sourceCode = fs
.readFileSync( .readFileSync(
pathJoin( pathJoin(
getThisCodebaseRootDirPath(), getThisCodebaseRootDirPath(),
@ -133,6 +133,17 @@ export async function command(params: { buildContext: BuildContext }) {
.replace('import React from "react";\n', "") .replace('import React from "react";\n', "")
.replace(/from "[./]+dist\//, 'from "keycloakify/'); .replace(/from "[./]+dist\//, 'from "keycloakify/');
run_prettier: {
if (!(await getIsPrettierAvailable())) {
break run_prettier;
}
sourceCode = await runPrettier({
filePath: targetFilePath,
sourceCode: sourceCode
});
}
{ {
const targetDirPath = pathDirname(targetFilePath); const targetDirPath = pathDirname(targetFilePath);
@ -141,11 +152,7 @@ export async function command(params: { buildContext: BuildContext }) {
} }
} }
fs.writeFileSync(targetFilePath, Buffer.from(componentCode, "utf8")); fs.writeFileSync(targetFilePath, Buffer.from(sourceCode, "utf8"));
runFormat({
packageJsonFilePath: buildContext.packageJsonFilePath
});
console.log( console.log(
[ [

View File

@ -22,7 +22,7 @@ import { assert, Equals } from "tsafe/assert";
import type { BuildContext } from "./shared/buildContext"; import type { BuildContext } from "./shared/buildContext";
import chalk from "chalk"; import chalk from "chalk";
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate"; import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
import { runFormat } from "./tools/runFormat"; import { runPrettier, getIsPrettierAvailable } from "./tools/runPrettier";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { buildContext: BuildContext }) {
const { buildContext } = params; const { buildContext } = params;
@ -217,7 +217,7 @@ export async function command(params: { buildContext: BuildContext }) {
process.exit(-1); process.exit(-1);
} }
const componentCode = fs let componentCode = fs
.readFileSync( .readFileSync(
pathJoin( pathJoin(
getThisCodebaseRootDirPath(), getThisCodebaseRootDirPath(),
@ -229,6 +229,17 @@ export async function command(params: { buildContext: BuildContext }) {
) )
.toString("utf8"); .toString("utf8");
run_prettier: {
if (!(await getIsPrettierAvailable())) {
break run_prettier;
}
componentCode = await runPrettier({
filePath: targetFilePath,
sourceCode: componentCode
});
}
{ {
const targetDirPath = pathDirname(targetFilePath); const targetDirPath = pathDirname(targetFilePath);
@ -239,10 +250,6 @@ export async function command(params: { buildContext: BuildContext }) {
fs.writeFileSync(targetFilePath, Buffer.from(componentCode, "utf8")); fs.writeFileSync(targetFilePath, Buffer.from(componentCode, "utf8"));
runFormat({
packageJsonFilePath: buildContext.packageJsonFilePath
});
console.log( console.log(
`${chalk.green("✓")} ${chalk.bold( `${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), targetFilePath)) pathJoin(".", pathRelative(process.cwd(), targetFilePath))

View File

@ -197,20 +197,6 @@ program
} }
}); });
program
.command({
name: "initialize-admin-theme",
description: "Initialize the admin theme."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const { command } = await import("./initialize-admin-theme");
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
program program
.command({ .command({
name: "copy-keycloak-resources-to-public", name: "copy-keycloak-resources-to-public",
@ -241,6 +227,20 @@ program
} }
}); });
program
.command({
name: "postinstall",
description: "Initialize all the Keycloakify UI modules installed in the project."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const { command } = await import("./postinstall");
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
// Fallback to build command if no command is provided // Fallback to build command if no command is provided
{ {
const [, , ...rest] = process.argv; const [, , ...rest] = process.argv;

View File

@ -3,6 +3,7 @@ import * as fsPr from "fs/promises";
import { join as pathJoin, sep as pathSep } from "path"; import { join as pathJoin, sep as pathSep } from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import type { BuildContext } from "../shared/buildContext"; import type { BuildContext } from "../shared/buildContext";
import { KEYCLOAK_THEME } from "../shared/constants";
export type BuildContextLike = { export type BuildContextLike = {
themeSrcDirPath: string; themeSrcDirPath: string;
@ -10,27 +11,32 @@ export type BuildContextLike = {
assert<BuildContext extends BuildContextLike ? true : false>(); assert<BuildContext extends BuildContextLike ? true : false>();
export async function getSourceCodeToCopyInUserCodebase(params: { export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
buildContext: BuildContextLike; buildContext: BuildContextLike;
relativeFromDirPath: string;
fileRelativePath: string; fileRelativePath: string;
commentData: {
isForEjection: boolean; isForEjection: boolean;
uiModuleDirPath: string;
uiModuleName: string; uiModuleName: string;
uiModuleVersion: string; uiModuleVersion: string;
}; }): Promise<Buffer> {
}): Promise<string> { const {
const { buildContext, relativeFromDirPath, fileRelativePath, commentData } = params; buildContext,
uiModuleDirPath,
fileRelativePath,
isForEjection,
uiModuleName,
uiModuleVersion
} = params;
let sourceCode = ( let sourceCode = (
await fsPr.readFile(pathJoin(relativeFromDirPath, fileRelativePath)) await fsPr.readFile(pathJoin(uiModuleDirPath, KEYCLOAK_THEME, fileRelativePath))
).toString("utf8"); ).toString("utf8");
const comment = (() => { const comment = (() => {
if (commentData.isForEjection) { if (isForEjection) {
return [ return [
`/*`, `/*`,
` This file was ejected from ${commentData.uiModuleName} version ${commentData.uiModuleVersion}.`, ` This file was ejected from ${uiModuleName} version ${uiModuleVersion}.`,
`*/` `*/`
].join("\n"); ].join("\n");
} else { } else {
@ -39,7 +45,7 @@ export async function getSourceCodeToCopyInUserCodebase(params: {
` WARNING: Before modifying this file run the following command:`, ` WARNING: Before modifying this file run the following command:`,
` \`npx keycloakify eject-file ${fileRelativePath.split(pathSep).join("/")}\``, ` \`npx keycloakify eject-file ${fileRelativePath.split(pathSep).join("/")}\``,
` `, ` `,
` This file comes from ${commentData.uiModuleName} version ${commentData.uiModuleVersion}.`, ` This file comes from ${uiModuleName} version ${uiModuleVersion}.`,
`*/` `*/`
]; ];
} }
@ -47,16 +53,18 @@ export async function getSourceCodeToCopyInUserCodebase(params: {
sourceCode = [comment, ``, sourceCode].join("\n"); sourceCode = [comment, ``, sourceCode].join("\n");
const destFilePath = pathJoin(buildContext.themeSrcDirPath, fileRelativePath);
format: { format: {
if (!(await getIsPrettierAvailable())) { if (!(await getIsPrettierAvailable())) {
break format; break format;
} }
sourceCode = await runPrettier({ sourceCode = await runPrettier({
filePath: pathJoin(buildContext.themeSrcDirPath, fileRelativePath), filePath: destFilePath,
sourceCode sourceCode
}); });
} }
return sourceCode; return Buffer.from(sourceCode, "utf8");
} }

View File

@ -0,0 +1 @@
export * from "./postinstall";

View File

@ -0,0 +1,79 @@
import type { BuildContext } from "../shared/buildContext";
import { getUiModuleMetas, computeHash } from "./uiModuleMeta";
import { installUiModulesPeerDependencies } from "./installUiModulesPeerDependencies";
import {
readManagedGitignoreFile,
writeManagedGitignoreFile
} from "./managedGitignoreFile";
import { dirname as pathDirname } from "path";
import { join as pathJoin } from "path";
import { existsAsync } from "../tools/fs.existsAsync";
import * as fsPr from "fs/promises";
export async function command(params: { buildContext: BuildContext }) {
const { buildContext } = params;
const uiModuleMetas = await getUiModuleMetas({ buildContext });
await installUiModulesPeerDependencies({
buildContext,
uiModuleMetas
});
const { ejectedFilesRelativePaths } = await readManagedGitignoreFile({
buildContext
});
await writeManagedGitignoreFile({
buildContext,
ejectedFilesRelativePaths,
uiModuleMetas
});
await Promise.all(
uiModuleMetas
.map(uiModuleMeta =>
Promise.all(
uiModuleMeta.files.map(
async ({ fileRelativePath, copyableFilePath, hash }) => {
if (ejectedFilesRelativePaths.includes(fileRelativePath)) {
return;
}
const destFilePath = pathJoin(
buildContext.themeSrcDirPath,
fileRelativePath
);
skip_condition: {
if (!(await existsAsync(destFilePath))) {
break skip_condition;
}
const destFileHash = computeHash(
await fsPr.readFile(destFilePath)
);
if (destFileHash !== hash) {
break skip_condition;
}
return;
}
{
const dirName = pathDirname(copyableFilePath);
if (!(await existsAsync(dirName))) {
await fsPr.mkdir(dirName, { recursive: true });
}
}
await fsPr.copyFile(copyableFilePath, destFilePath);
}
)
)
)
.flat()
);
}

View File

@ -1,7 +1,7 @@
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import { z } from "zod"; import { z } from "zod";
import { join as pathJoin, sep as pathSep, dirname as pathDirname } from "path"; import { join as pathJoin, dirname as pathDirname } from "path";
import * as fsPr from "fs/promises"; import * as fsPr from "fs/promises";
import type { BuildContext } from "../shared/buildContext"; import type { BuildContext } from "../shared/buildContext";
import { is } from "tsafe/is"; import { is } from "tsafe/is";
@ -11,10 +11,11 @@ import { crawlAsync } from "../tools/crawlAsync";
import { getIsPrettierAvailable, getPrettierAndConfig } from "../tools/runPrettier"; import { getIsPrettierAvailable, getPrettierAndConfig } from "../tools/runPrettier";
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion"; import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import { import {
getSourceCodeToCopyInUserCodebase, getUiModuleFileSourceCodeReadyToBeCopied,
type BuildContextLike as BuildContextLike_getSourceCodeToCopyInUserCodebase type BuildContextLike as BuildContextLike_getUiModuleFileSourceCodeReadyToBeCopied
} from "./getSourceCodeToCopyInUserCodebase"; } from "./getUiModuleFileSourceCodeReadyToBeCopied";
import * as crypto from "crypto"; import * as crypto from "crypto";
import { KEYCLOAK_THEME } from "../shared/constants";
export type UiModuleMeta = { export type UiModuleMeta = {
moduleName: string; moduleName: string;
@ -22,6 +23,7 @@ export type UiModuleMeta = {
files: { files: {
fileRelativePath: string; fileRelativePath: string;
hash: string; hash: string;
copyableFilePath: string;
}[]; }[];
peerDependencies: Record<string, string>; peerDependencies: Record<string, string>;
}; };
@ -35,7 +37,8 @@ const zUiModuleMeta = (() => {
files: z.array( files: z.array(
z.object({ z.object({
fileRelativePath: z.string(), fileRelativePath: z.string(),
hash: z.string() hash: z.string(),
copyableFilePath: z.string()
}) })
), ),
peerDependencies: z.record(z.string()) peerDependencies: z.record(z.string())
@ -51,7 +54,7 @@ const zUiModuleMeta = (() => {
type ParsedCacheFile = { type ParsedCacheFile = {
keycloakifyVersion: string; keycloakifyVersion: string;
prettierConfigHash: string | null; prettierConfigHash: string | null;
pathSep: string; thisFilePath: string;
uiModuleMetas: UiModuleMeta[]; uiModuleMetas: UiModuleMeta[];
}; };
@ -61,7 +64,7 @@ const zParsedCacheFile = (() => {
const zTargetType = z.object({ const zTargetType = z.object({
keycloakifyVersion: z.string(), keycloakifyVersion: z.string(),
prettierConfigHash: z.union([z.string(), z.null()]), prettierConfigHash: z.union([z.string(), z.null()]),
pathSep: z.string(), thisFilePath: z.string(),
uiModuleMetas: z.array(zUiModuleMeta) uiModuleMetas: z.array(zUiModuleMeta)
}); });
@ -72,9 +75,10 @@ const zParsedCacheFile = (() => {
return id<z.ZodType<ExpectedType>>(zTargetType); return id<z.ZodType<ExpectedType>>(zTargetType);
})(); })();
const CACHE_FILE_BASENAME = "uiModulesMeta.json"; const CACHE_FILE_RELATIVE_PATH = pathJoin("ui-modules", "cache.json");
export type BuildContextLike = BuildContextLike_getSourceCodeToCopyInUserCodebase & { export type BuildContextLike =
BuildContextLike_getUiModuleFileSourceCodeReadyToBeCopied & {
cacheDirPath: string; cacheDirPath: string;
packageJsonFilePath: string; packageJsonFilePath: string;
projectDirPath: string; projectDirPath: string;
@ -87,7 +91,7 @@ export async function getUiModuleMetas(params: {
}): Promise<UiModuleMeta[]> { }): Promise<UiModuleMeta[]> {
const { buildContext } = params; const { buildContext } = params;
const cacheFilePath = pathJoin(buildContext.cacheDirPath, CACHE_FILE_BASENAME); const cacheFilePath = pathJoin(buildContext.cacheDirPath, CACHE_FILE_RELATIVE_PATH);
const keycloakifyVersion = readThisNpmPackageVersion(); const keycloakifyVersion = readThisNpmPackageVersion();
@ -101,13 +105,21 @@ export async function getUiModuleMetas(params: {
return crypto.createHash("sha256").update(JSON.stringify(config)).digest("hex"); return crypto.createHash("sha256").update(JSON.stringify(config)).digest("hex");
})(); })();
const installedUiModules = await listInstalledModules({ const installedUiModules = await (async () => {
const installedModulesWithKeycloakifyInTheName = await listInstalledModules({
packageJsonFilePath: buildContext.packageJsonFilePath, packageJsonFilePath: buildContext.packageJsonFilePath,
projectDirPath: buildContext.packageJsonFilePath, projectDirPath: buildContext.packageJsonFilePath,
filter: ({ moduleName }) => filter: ({ moduleName }) =>
moduleName.includes("keycloakify") && moduleName.endsWith("-ui") moduleName.includes("keycloakify") && moduleName !== "keycloakify"
}); });
return Promise.all(
installedModulesWithKeycloakifyInTheName.filter(async ({ dirPath }) =>
existsAsync(pathJoin(dirPath, KEYCLOAK_THEME))
)
);
})();
const cacheContent = await (async () => { const cacheContent = await (async () => {
if (!(await existsAsync(cacheFilePath))) { if (!(await existsAsync(cacheFilePath))) {
return undefined; return undefined;
@ -155,7 +167,7 @@ export async function getUiModuleMetas(params: {
return []; return [];
} }
if (parsedCacheFile.pathSep !== pathSep) { if (parsedCacheFile.thisFilePath !== cacheFilePath) {
return []; return [];
} }
@ -200,31 +212,44 @@ export async function getUiModuleMetas(params: {
const files: UiModuleMeta["files"] = []; const files: UiModuleMeta["files"] = [];
{ {
const srcDirPath = pathJoin(dirPath, "src"); const srcDirPath = pathJoin(dirPath, KEYCLOAK_THEME);
await crawlAsync({ await crawlAsync({
dirPath: srcDirPath, dirPath: srcDirPath,
returnedPathsType: "relative to dirPath", returnedPathsType: "relative to dirPath",
onFileFound: async fileRelativePath => { onFileFound: async fileRelativePath => {
const sourceCode = await getSourceCodeToCopyInUserCodebase({ const sourceCode =
await getUiModuleFileSourceCodeReadyToBeCopied({
buildContext, buildContext,
relativeFromDirPath: srcDirPath,
fileRelativePath, fileRelativePath,
commentData: {
isForEjection: false, isForEjection: false,
uiModuleDirPath: dirPath,
uiModuleName: moduleName, uiModuleName: moduleName,
uiModuleVersion: version uiModuleVersion: version
}
}); });
const hash = crypto const hash = computeHash(sourceCode);
.createHash("sha256")
.update(sourceCode) const copyableFilePath = pathJoin(
.digest("hex"); pathDirname(cacheFilePath),
KEYCLOAK_THEME,
fileRelativePath
);
{
const dirPath = pathDirname(copyableFilePath);
if (!(await existsAsync(dirPath))) {
await fsPr.mkdir(dirPath, { recursive: true });
}
}
fsPr.writeFile(copyableFilePath, sourceCode);
files.push({ files.push({
fileRelativePath, fileRelativePath,
hash hash,
copyableFilePath
}); });
} }
}); });
@ -244,7 +269,7 @@ export async function getUiModuleMetas(params: {
const parsedCacheFile = id<ParsedCacheFile>({ const parsedCacheFile = id<ParsedCacheFile>({
keycloakifyVersion, keycloakifyVersion,
prettierConfigHash, prettierConfigHash,
pathSep, thisFilePath: cacheFilePath,
uiModuleMetas uiModuleMetas
}); });
@ -272,3 +297,7 @@ export async function getUiModuleMetas(params: {
return uiModuleMetas; return uiModuleMetas;
} }
export function computeHash(data: Buffer) {
return crypto.createHash("sha256").update(data).digest("hex");
}

View File

@ -18,9 +18,8 @@ import {
import type { KeycloakVersionRange } from "./KeycloakVersionRange"; import type { KeycloakVersionRange } from "./KeycloakVersionRange";
import { exclude } from "tsafe"; import { exclude } from "tsafe";
import { crawl } from "../tools/crawl"; import { crawl } from "../tools/crawl";
import { THEME_TYPES } from "./constants"; import { THEME_TYPES, KEYCLOAK_THEME, type ThemeType } from "./constants";
import { objectEntries } from "tsafe/objectEntries"; import { objectEntries } from "tsafe/objectEntries";
import { type ThemeType } from "./constants";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import chalk from "chalk"; import chalk from "chalk";
import { getProxyFetchOptions, type FetchOptionsLike } from "../tools/fetchProxyOptions"; import { getProxyFetchOptions, type FetchOptionsLike } from "../tools/fetchProxyOptions";
@ -147,7 +146,10 @@ export function getBuildContext(params: {
returnedPathsType: "relative to dirPath" returnedPathsType: "relative to dirPath"
}) })
.map(fileRelativePath => { .map(fileRelativePath => {
for (const themeSrcDirBasename of ["keycloak-theme", "keycloak_theme"]) { for (const themeSrcDirBasename of [
KEYCLOAK_THEME,
KEYCLOAK_THEME.replace(/-/g, "_")
]) {
const split = fileRelativePath.split(themeSrcDirBasename); const split = fileRelativePath.split(themeSrcDirBasename);
if (split.length === 2) { if (split.length === 2) {
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename); return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
@ -173,7 +175,7 @@ export function getBuildContext(params: {
[ [
`Can't locate your Keycloak theme source directory in .${pathSep}${pathRelative(process.cwd(), srcDirPath)}`, `Can't locate your Keycloak theme source directory in .${pathSep}${pathRelative(process.cwd(), srcDirPath)}`,
`Make sure to either use the Keycloakify CLI in the root of your Keycloakify project or use the --project CLI option`, `Make sure to either use the Keycloakify CLI in the root of your Keycloakify project or use the --project CLI option`,
`If you are collocating your Keycloak theme with your app you must have a directory named 'keycloak-theme' or 'keycloak_theme' in your 'src' directory` `If you are collocating your Keycloak theme with your app you must have a directory named '${KEYCLOAK_THEME}' or '${KEYCLOAK_THEME.replace(/-/g, "_")}' in your 'src' directory`
].join("\n") ].join("\n")
) )
); );

View File

@ -76,3 +76,5 @@ export const CUSTOM_HANDLER_ENV_NAMES = {
COMMAND_NAME: "KEYCLOAKIFY_COMMAND_NAME", COMMAND_NAME: "KEYCLOAKIFY_COMMAND_NAME",
BUILD_CONTEXT: "KEYCLOAKIFY_BUILD_CONTEXT" BUILD_CONTEXT: "KEYCLOAKIFY_BUILD_CONTEXT"
}; };
export const KEYCLOAK_THEME = "keycloak-theme";

View File

@ -1 +0,0 @@
export * from "./sync-ui-modules";

View File

@ -1,13 +0,0 @@
import type { BuildContext } from "./shared/buildContext";
import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
import { is } from "tsafe/is";
import { z } from "zod";
import { join as pathJoin } from "path";
import { existsAsync } from "./tools/fs.existsAsync";
import * as fsPr from "fs/promises";
export async function command(params: { buildContext: BuildContext }) {
const { buildContext } = params;
}

View File

@ -1,16 +1,14 @@
import { dirname as pathDirname, join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { existsAsync } from "./fs.existsAsync"; import { existsAsync } from "./fs.existsAsync";
import * as child_process from "child_process"; import * as child_process from "child_process";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
export async function getInstalledModuleDirPath(params: { export async function getInstalledModuleDirPath(params: {
moduleName: string; moduleName: string;
packageJsonFilePath: string; packageJsonDirPath: string;
projectDirPath: string; projectDirPath: string;
}) { }) {
const { moduleName, packageJsonFilePath, projectDirPath } = params; const { moduleName, packageJsonDirPath, projectDirPath } = params;
const packageJsonDirPath = pathDirname(packageJsonFilePath);
common_case: { common_case: {
const dirPath = pathJoin( const dirPath = pathJoin(

View File

@ -1,10 +1,11 @@
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import { z } from "zod"; import { z } from "zod";
import { join as pathJoin } from "path"; import { join as pathJoin, dirname as pathDirname } from "path";
import * as fsPr from "fs/promises"; import * as fsPr from "fs/promises";
import { is } from "tsafe/is"; import { is } from "tsafe/is";
import { getInstalledModuleDirPath } from "../tools/getInstalledModuleDirPath"; import { getInstalledModuleDirPath } from "../tools/getInstalledModuleDirPath";
import { exclude } from "tsafe/exclude";
export async function listInstalledModules(params: { export async function listInstalledModules(params: {
packageJsonFilePath: string; packageJsonFilePath: string;
@ -27,7 +28,7 @@ export async function listInstalledModules(params: {
const uiModuleNames = ( const uiModuleNames = (
[parsedPackageJson.dependencies, parsedPackageJson.devDependencies] as const [parsedPackageJson.dependencies, parsedPackageJson.devDependencies] as const
) )
.filter(obj => obj !== undefined) .filter(exclude(undefined))
.map(obj => Object.keys(obj)) .map(obj => Object.keys(obj))
.flat() .flat()
.filter(moduleName => filter({ moduleName })); .filter(moduleName => filter({ moduleName }));
@ -36,7 +37,7 @@ export async function listInstalledModules(params: {
uiModuleNames.map(async moduleName => { uiModuleNames.map(async moduleName => {
const dirPath = await getInstalledModuleDirPath({ const dirPath = await getInstalledModuleDirPath({
moduleName, moduleName,
packageJsonFilePath, packageJsonDirPath: pathDirname(packageJsonFilePath),
projectDirPath projectDirPath
}); });