diff --git a/src/bin/keycloakify/generateResources/generateResources.ts b/src/bin/keycloakify/generateResources/generateResources.ts index 4b3881a0..ac2d6ca5 100644 --- a/src/bin/keycloakify/generateResources/generateResources.ts +++ b/src/bin/keycloakify/generateResources/generateResources.ts @@ -37,10 +37,10 @@ import { } from "../../shared/metaInfKeycloakThemes"; import { objectEntries } from "tsafe/objectEntries"; import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile"; -import * as child_process from "child_process"; import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath"; import propertiesParser from "properties-parser"; import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed"; +import { listInstalledModules } from "../../tools/listInstalledModules"; export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode & BuildContextLike_generateMessageProperties & { @@ -238,9 +238,9 @@ export async function generateResources(params: { let languageTags: string[] | undefined = undefined; - i18n_messages_generation: { + i18n_multi_page: { if (isSpa) { - break i18n_messages_generation; + break i18n_multi_page; } assert(themeType !== "admin"); @@ -257,23 +257,43 @@ export async function generateResources(params: { writeMessagePropertiesFiles; } - bring_in_account_spa_messages: { + let isLegacyAccountSpa = false; + + // NOTE: Eventually remove this block. + i18n_single_page_account_legacy: { if (!isSpa) { - break bring_in_account_spa_messages; + break i18n_single_page_account_legacy; } if (themeType !== "account") { - break bring_in_account_spa_messages; + break i18n_single_page_account_legacy; } - const accountUiDirPath = child_process - .execSync(`npm list @keycloakify/keycloak-account-ui --parseable`, { - cwd: pathDirname(buildContext.packageJsonFilePath) - }) - .toString("utf8") - .trim(); + const [moduleMeta] = await listInstalledModules({ + packageJsonFilePath: buildContext.packageJsonFilePath, + filter: ({ moduleName }) => + moduleName === "@keycloakify/keycloak-account-ui" + }); - const messageDirPath_defaults = pathJoin(accountUiDirPath, "messages"); + assert( + moduleMeta !== undefined, + `@keycloakify/keycloak-account-ui is supposed to be installed` + ); + + { + const [majorStr] = moduleMeta.version.split("."); + + if (majorStr.length === 6) { + // NOTE: Now we use the format MMmmpp (Major, minor, patch) for example for + // 26.0.7 it would be 260007. + break i18n_single_page_account_legacy; + } else { + // 25.0.4-rc.5 or later + isLegacyAccountSpa = true; + } + } + + const messageDirPath_defaults = pathJoin(moduleMeta.dirPath, "messages"); if (!fs.existsSync(messageDirPath_defaults)) { throw new Error( @@ -281,6 +301,8 @@ export async function generateResources(params: { ); } + isLegacyAccountSpa = true; + const messagesDirPath_dest = pathJoin( getThemeTypeDirPath({ themeName, themeType: "account" }), "messages" @@ -342,14 +364,20 @@ export async function generateResources(params: { ); } - bring_in_admin_messages: { - if (themeType !== "admin") { - break bring_in_admin_messages; + i18n_single_page: { + if (!isSpa) { + break i18n_single_page; } + if (isLegacyAccountSpa) { + break i18n_single_page; + } + + assert(themeType === "account" || themeType === "admin"); + const messagesDirPath_theme = pathJoin( buildContext.themeSrcDirPath, - "admin", + themeType, "i18n" ); @@ -423,7 +451,7 @@ export async function generateResources(params: { propertiesByLang[parsedBasename.lang] ??= { base: createObjectThatThrowsIfAccessed({ - debugMessage: `No base ${parsedBasename.lang} translation for admin theme` + debugMessage: `No base ${parsedBasename.lang} translation for ${themeType} theme` }), override: undefined, overrideByThemeName: {} @@ -446,7 +474,9 @@ export async function generateResources(params: { ] = buffer; }); - writeMessagePropertiesFilesByThemeType.admin = ({ + languageTags = Object.keys(propertiesByLang); + + writeMessagePropertiesFilesByThemeType[themeType] = ({ messageDirPath, themeName }) => { @@ -456,8 +486,6 @@ export async function generateResources(params: { Object.entries(propertiesByLang).forEach( ([lang, { base, override, overrideByThemeName }]) => { - (languageTags ??= []).push(lang); - const messages = propertiesParser.parse(base.toString("utf8")); if (override !== undefined) { diff --git a/src/bin/own.ts b/src/bin/own.ts index 83f3b6be..802e01fa 100644 --- a/src/bin/own.ts +++ b/src/bin/own.ts @@ -124,8 +124,7 @@ async function command_own(params: Params_subcommands) { ] of targetFileRelativePathsByExtensionModuleMeta.entries()) { const extensionModuleDirPath = await getInstalledModuleDirPath({ moduleName: extensionModuleMeta.moduleName, - packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath), - projectDirPath: buildContext.projectDirPath + packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath) }); for (const fileRelativePath of fileRelativePaths) { diff --git a/src/bin/sync-extensions/extensionModuleMeta.ts b/src/bin/sync-extensions/extensionModuleMeta.ts index 3cc7d8d3..124d1e69 100644 --- a/src/bin/sync-extensions/extensionModuleMeta.ts +++ b/src/bin/sync-extensions/extensionModuleMeta.ts @@ -109,7 +109,6 @@ export async function getExtensionModuleMetas(params: { const installedExtensionModules = await (async () => { const installedModulesWithKeycloakifyInTheName = await listInstalledModules({ packageJsonFilePath: buildContext.packageJsonFilePath, - projectDirPath: buildContext.packageJsonFilePath, filter: ({ moduleName }) => moduleName.includes("keycloakify") && moduleName !== "keycloakify" }); diff --git a/src/bin/tools/getInstalledModuleDirPath.ts b/src/bin/tools/getInstalledModuleDirPath.ts index 7915e6e9..4335fe38 100644 --- a/src/bin/tools/getInstalledModuleDirPath.ts +++ b/src/bin/tools/getInstalledModuleDirPath.ts @@ -2,40 +2,42 @@ import { join as pathJoin } from "path"; import { existsAsync } from "./fs.existsAsync"; import * as child_process from "child_process"; import { assert } from "tsafe/assert"; +import { getIsRootPath } from "../tools/isRootPath"; export async function getInstalledModuleDirPath(params: { moduleName: string; packageJsonDirPath: string; - projectDirPath: string; }) { - const { moduleName, packageJsonDirPath, projectDirPath } = params; + const { moduleName, packageJsonDirPath } = params; - common_case: { - const dirPath = pathJoin( - ...[packageJsonDirPath, "node_modules", ...moduleName.split("/")] - ); + { + let dirPath = packageJsonDirPath; - if (!(await existsAsync(dirPath))) { - break common_case; + while (true) { + const dirPath_candidate = pathJoin( + dirPath, + "node_modules", + ...moduleName.split("/") + ); + + let doesExist: boolean; + + try { + doesExist = await existsAsync(dirPath_candidate); + } catch { + doesExist = false; + } + + if (doesExist) { + return dirPath_candidate; + } + + if (getIsRootPath(dirPath)) { + break; + } + + dirPath = pathJoin(dirPath, ".."); } - - return dirPath; - } - - node_modules_at_root_case: { - if (projectDirPath === packageJsonDirPath) { - break node_modules_at_root_case; - } - - const dirPath = pathJoin( - ...[projectDirPath, "node_modules", ...moduleName.split("/")] - ); - - if (!(await existsAsync(dirPath))) { - break node_modules_at_root_case; - } - - return dirPath; } const dirPath = child_process diff --git a/src/bin/tools/isRootPath.ts b/src/bin/tools/isRootPath.ts new file mode 100644 index 00000000..0fec5264 --- /dev/null +++ b/src/bin/tools/isRootPath.ts @@ -0,0 +1,22 @@ +import { normalize as pathNormalize } from "path"; + +export function getIsRootPath(filePath: string): boolean { + const path_normalized = pathNormalize(filePath); + + // Unix-like root ("/") + if (path_normalized === "/") { + return true; + } + + // Check for Windows drive root (e.g., "C:\\") + if (/^[a-zA-Z]:\\$/.test(path_normalized)) { + return true; + } + + // Check for UNC root (e.g., "\\server\share") + if (/^\\\\[^\\]+\\[^\\]+\\?$/.test(path_normalized)) { + return true; + } + + return false; +} diff --git a/src/bin/tools/listInstalledModules.ts b/src/bin/tools/listInstalledModules.ts index d173c73c..dcd107d3 100644 --- a/src/bin/tools/listInstalledModules.ts +++ b/src/bin/tools/listInstalledModules.ts @@ -8,7 +8,6 @@ import { exclude } from "tsafe/exclude"; export async function listInstalledModules(params: { packageJsonFilePath: string; - projectDirPath: string; filter: (params: { moduleName: string }) => boolean; }): Promise< { @@ -18,7 +17,7 @@ export async function listInstalledModules(params: { peerDependencies: Record; }[] > { - const { packageJsonFilePath, projectDirPath, filter } = params; + const { packageJsonFilePath, filter } = params; const parsedPackageJson = await readPackageJsonDependencies({ packageJsonFilePath @@ -36,8 +35,7 @@ export async function listInstalledModules(params: { extensionModuleNames.map(async moduleName => { const dirPath = await getInstalledModuleDirPath({ moduleName, - packageJsonDirPath: pathDirname(packageJsonFilePath), - projectDirPath + packageJsonDirPath: pathDirname(packageJsonFilePath) }); const { version, peerDependencies } =