Fix some errors implementing the new account SPA feature

This commit is contained in:
Joseph Garrone 2024-12-27 01:36:29 +01:00
parent 488dd2c6b9
commit c87b6153bb
6 changed files with 102 additions and 54 deletions

View File

@ -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<Buffer>({
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) {

View File

@ -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) {

View File

@ -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"
});

View File

@ -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

View File

@ -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;
}

View File

@ -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<string, string>;
}[]
> {
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 } =