Refactor checkpoint

This commit is contained in:
Joseph Garrone 2024-09-08 00:06:47 +02:00
parent 8340608045
commit 93c1c56279
28 changed files with 402 additions and 897 deletions

2
.gitignore vendored
View File

@ -50,6 +50,8 @@ jspm_packages
/src/login/i18n/messages_defaultSet/
/src/account/i18n/messages_defaultSet/
/resources/
/account-v1/
# VS Code devcontainers
.devcontainer

View File

@ -7,7 +7,7 @@
"url": "git://github.com/keycloakify/keycloakify.git"
},
"scripts": {
"prepare": "tsx scripts/generate-i18n-messages.ts",
"prepare": "tsx scripts/prepare/main.ts",
"build": "tsx scripts/build.ts",
"storybook": "tsx scripts/start-storybook.ts",
"link-in-starter": "tsx scripts/link-in-starter.ts",

View File

@ -4,6 +4,7 @@ import { join } from "path";
import { assert } from "tsafe/assert";
import { transformCodebase } from "../src/bin/tools/transformCodebase";
import chalk from "chalk";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../src/bin/shared/constants";
console.log(chalk.cyan("Building Keycloakify..."));
@ -136,9 +137,17 @@ fs.rmSync(join("dist", "ncc_out"), { recursive: true });
assert(hasBeenPatched);
}
fs.rmSync(join("dist", "src"), { recursive: true, force: true });
for (const dirBasename of [
"src",
WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES,
WELL_KNOWN_DIRECTORY_BASE_NAME.ACCOUNT_V1
]) {
const destDirPath = join("dist", dirBasename);
fs.cpSync("src", join("dist", "src"), { recursive: true });
fs.rmSync(destDirPath, { recursive: true, force: true });
fs.cpSync(dirBasename, destDirPath, { recursive: true });
}
transformCodebase({
srcDirPath: join("stories"),

View File

@ -0,0 +1,4 @@
export const KEYCLOAK_VERSION = {
FOR_LOGIN_THEME: "25.0.4",
FOR_ACCOUNT_MULTI_PAGE: "21.1.2"
};

View File

@ -0,0 +1,77 @@
import * as fs from "fs";
import { join as pathJoin } from "path";
import { KEYCLOAK_VERSION } from "./constants";
import { transformCodebase } from "../../src/bin/tools/transformCodebase";
import { downloadKeycloakDefaultTheme } from "./downloadKeycloakDefaultTheme";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../src/bin/shared/constants";
import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath";
import { accountMultiPageSupportedLanguages } from "./generateI18nMessages";
export async function createAccountV1Dir() {
const { extractedDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion: KEYCLOAK_VERSION.FOR_ACCOUNT_MULTI_PAGE
});
// TODO: Exclude unused resources.
const destDirPath = pathJoin(
getThisCodebaseRootDirPath(),
WELL_KNOWN_DIRECTORY_BASE_NAME.ACCOUNT_V1
);
transformCodebase({
srcDirPath: pathJoin(extractedDirPath, "base", "account"),
destDirPath
});
transformCodebase({
srcDirPath: pathJoin(extractedDirPath, "keycloak", "account", "resources"),
destDirPath: pathJoin(destDirPath, "resources")
});
transformCodebase({
srcDirPath: pathJoin(extractedDirPath, "keycloak", "common", "resources"),
destDirPath: pathJoin(
destDirPath,
"resources",
WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON
)
});
fs.writeFileSync(
pathJoin(destDirPath, "theme.properties"),
Buffer.from(
[
"accountResourceProvider=account-v1",
"",
`locales=${accountMultiPageSupportedLanguages.join(",")}`,
"",
"styles=" +
[
"css/account.css",
"img/icon-sidebar-active.png",
"img/logo.png",
...[
"patternfly.min.css",
"patternfly-additions.min.css",
"patternfly-additions.min.css"
].map(
fileBasename =>
`${WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON}/node_modules/patternfly/dist/css/${fileBasename}`
)
].join(" "),
"",
"##### css classes for form buttons",
"# main class used for all buttons",
"kcButtonClass=btn",
"# classes defining priority of the button - primary or default (there is typically only one priority button for the form)",
"kcButtonPrimaryClass=btn-primary",
"kcButtonDefaultClass=btn-default",
"# classes defining size of the button",
"kcButtonLargeClass=btn-lg",
""
].join("\n"),
"utf8"
)
);
}

View File

@ -0,0 +1,70 @@
import { join as pathJoin } from "path";
import { downloadKeycloakDefaultTheme } from "./downloadKeycloakDefaultTheme";
import { KEYCLOAK_VERSION } from "./constants";
import { transformCodebase } from "../../src/bin/tools/transformCodebase";
import { existsAsync } from "../../src/bin/tools/fs.existsAsync";
import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../src/bin/shared/constants";
import { assert, type Equals } from "tsafe/assert";
export async function createResourcesDir() {
await Promise.all(
(["login", "account"] as const).map(async themeType => {
const keycloakVersion = (() => {
switch (themeType) {
case "login":
return KEYCLOAK_VERSION.FOR_LOGIN_THEME;
case "account":
return KEYCLOAK_VERSION.FOR_ACCOUNT_MULTI_PAGE;
}
assert<Equals<typeof themeType, never>>();
})();
const { extractedDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion
});
const destDirPath = pathJoin(
getThisCodebaseRootDirPath(),
WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES,
themeType
);
base_resources: {
const srcDirPath = pathJoin(
extractedDirPath,
"base",
themeType,
"resources"
);
if (!(await existsAsync(srcDirPath))) {
break base_resources;
}
transformCodebase({
srcDirPath,
destDirPath
});
}
transformCodebase({
srcDirPath: pathJoin(
extractedDirPath,
"keycloak",
themeType,
"resources"
),
destDirPath
});
transformCodebase({
srcDirPath: pathJoin(extractedDirPath, "keycloak", "common", "resources"),
destDirPath: pathJoin(
destDirPath,
WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON
)
});
})
);
}

View File

@ -0,0 +1,34 @@
import { relative as pathRelative } from "path";
import { downloadAndExtractArchive } from "../../src/bin/tools/downloadAndExtractArchive";
import { getProxyFetchOptions } from "../../src/bin/tools/fetchProxyOptions";
import { join as pathJoin } from "path";
import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath";
export async function downloadKeycloakDefaultTheme(params: { keycloakVersion: string }) {
const { keycloakVersion } = params;
const { extractedDirPath } = await downloadAndExtractArchive({
url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
cacheDirPath: pathJoin(
getThisCodebaseRootDirPath(),
"node_modules",
".cache",
"scripts"
),
fetchOptions: getProxyFetchOptions({
npmConfigGetCwd: getThisCodebaseRootDirPath()
}),
uniqueIdOfOnArchiveFile: "downloadKeycloakDefaultTheme",
onArchiveFile: async ({ fileRelativePath, writeFile }) => {
const fileRelativePath_target = pathRelative("theme", fileRelativePath);
if (fileRelativePath_target.startsWith("..")) {
return;
}
await writeFile({ fileRelativePath: fileRelativePath_target });
}
});
return { extractedDirPath };
}

View File

@ -8,15 +8,12 @@ import {
} from "path";
import { assert } from "tsafe/assert";
import { same } from "evt/tools/inDepth";
import { crawl } from "../src/bin/tools/crawl";
import { downloadKeycloakDefaultTheme } from "../src/bin/shared/downloadKeycloakDefaultTheme";
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
import { deepAssign } from "../src/tools/deepAssign";
import { getProxyFetchOptions } from "../src/bin/tools/fetchProxyOptions";
import {
THEME_TYPES,
LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1
} from "../src/bin/shared/constants";
import { crawl } from "../../src/bin/tools/crawl";
import { downloadKeycloakDefaultTheme } from "./downloadKeycloakDefaultTheme";
import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath";
import { deepAssign } from "../../src/tools/deepAssign";
import { THEME_TYPES } from "../../src/bin/shared/constants";
import { KEYCLOAK_VERSION } from "./constants";
// NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
// update the version array for generating for newer version.
@ -24,7 +21,7 @@ import {
//@ts-ignore
const propertiesParser = require("properties-parser");
async function main() {
export async function generateI18nMessages() {
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
type Dictionary = { [idiomId: string]: string };
@ -32,30 +29,19 @@ async function main() {
const record: { [themeType: string]: { [language: string]: Dictionary } } = {};
for (const themeType of THEME_TYPES) {
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
const { extractedDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion: (() => {
switch (themeType) {
case "login":
return "25.0.4";
return KEYCLOAK_VERSION.FOR_LOGIN_THEME;
case "account":
return LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1;
}
})(),
buildContext: {
cacheDirPath: pathJoin(
thisCodebaseRootDirPath,
"node_modules",
".cache",
"keycloakify"
),
fetchOptions: getProxyFetchOptions({
npmConfigGetCwd: thisCodebaseRootDirPath
})
return KEYCLOAK_VERSION.FOR_ACCOUNT_MULTI_PAGE;
}
})()
});
{
const baseThemeDirPath = pathJoin(defaultThemeDirPath, "base");
const baseThemeDirPath = pathJoin(extractedDirPath, "base");
const re = new RegExp(
`^([^\\${pathSep}]+)\\${pathSep}messages\\${pathSep}messages_([^.]+).properties$`
);
@ -597,30 +583,34 @@ const keycloakifyExtraMessages_login: Record<
/* spell-checker: enable */
};
export const accountMultiPageSupportedLanguages = [
"en",
"ar",
"ca",
"cs",
"da",
"de",
"es",
"fi",
"fr",
"hu",
"it",
"ja",
"lt",
"lv",
"nl",
"no",
"pl",
"pt-BR",
"ru",
"sk",
"sv",
"tr",
"zh-CN"
] as const;
const keycloakifyExtraMessages_account: Record<
| "en"
| "ar"
| "ca"
| "cs"
| "da"
| "de"
| "es"
| "fi"
| "fr"
| "hu"
| "it"
| "ja"
| "lt"
| "lv"
| "nl"
| "no"
| "pl"
| "pt-BR"
| "ru"
| "sk"
| "sv"
| "tr"
| "zh-CN",
(typeof accountMultiPageSupportedLanguages)[number],
Record<"newPasswordSameAsOld" | "passwordConfirmNotMatch", string>
> = {
en: {
@ -719,7 +709,3 @@ const keycloakifyExtraMessages_account: Record<
}
/* spell-checker: enable */
};
if (require.main === module) {
main();
}

13
scripts/prepare/main.ts Normal file
View File

@ -0,0 +1,13 @@
import { generateI18nMessages } from "./generateI18nMessages";
import { createAccountV1Dir } from "./createAccountV1Dir";
import { createResourcesDir } from "./createResourcesDir";
(async () => {
console.log("Pulling i18n messages...");
await generateI18nMessages();
console.log("Creating account-v1 dir...");
await createAccountV1Dir();
console.log("Creating resources dir...");
await createResourcesDir();
console.log("Done!");
})();

View File

@ -1,10 +1,10 @@
import "keycloakify/tools/Object.fromEntries";
import { RESOURCES_COMMON, KEYCLOAK_RESOURCES } from "keycloakify/bin/shared/constants";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "keycloakify/bin/shared/constants";
import { id } from "tsafe/id";
import type { KcContext } from "./KcContext";
import { BASE_URL } from "keycloakify/lib/BASE_URL";
const resourcesPath = `${BASE_URL}${KEYCLOAK_RESOURCES}/account/resources`;
const resourcesPath = `${BASE_URL}${WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY}/account`;
export const kcContextCommonMock: KcContext.Common = {
themeVersion: "0.0.0",
@ -13,7 +13,7 @@ export const kcContextCommonMock: KcContext.Common = {
themeName: "my-theme-name",
url: {
resourcesPath,
resourcesCommonPath: `${resourcesPath}/${RESOURCES_COMMON}`,
resourcesCommonPath: `${resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON}`,
resourceUrl: "#",
accountUrl: "#",
applicationsUrl: "#",

View File

@ -7,7 +7,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const buildContext = getBuildContext({ cliCommandOptions });
await copyKeycloakResourcesToPublic({
copyKeycloakResourcesToPublic({
buildContext
});
}

View File

@ -11,11 +11,7 @@ import * as fs from "fs";
import { join as pathJoin } from "path";
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import {
type ThemeType,
BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR,
RESOURCES_COMMON
} from "../../shared/constants";
import { type ThemeType, WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
export type BuildContextLike = BuildContextLike_replaceImportsInJsCode &
@ -94,7 +90,7 @@ export function generateFtlFilesCodeFactory(params: {
new RegExp(
`^${(buildContext.urlPathname ?? "/").replace(/\//g, "\\/")}`
),
`\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/`
`\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/`
)
);
})
@ -119,7 +115,7 @@ export function generateFtlFilesCodeFactory(params: {
.replace("{{keycloakifyVersion}}", keycloakifyVersion)
.replace("{{themeVersion}}", buildContext.themeVersion)
.replace("{{fieldNames}}", fieldNames.map(name => `"${name}"`).join(", "))
.replace("{{RESOURCES_COMMON}}", RESOURCES_COMMON)
.replace("{{RESOURCES_COMMON}}", WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON)
.replace(
"{{userDefinedExclusions}}",
buildContext.kcContextExclusionsFtlCode ?? ""

View File

@ -1,89 +0,0 @@
import * as fs from "fs";
import { join as pathJoin } from "path";
import { assert } from "tsafe/assert";
import type { BuildContext } from "../../shared/buildContext";
import {
RESOURCES_COMMON,
LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1,
ACCOUNT_V1_THEME_NAME
} from "../../shared/constants";
import {
downloadKeycloakDefaultTheme,
BuildContextLike as BuildContextLike_downloadKeycloakDefaultTheme
} from "../../shared/downloadKeycloakDefaultTheme";
import { transformCodebase } from "../../tools/transformCodebase";
export type BuildContextLike = BuildContextLike_downloadKeycloakDefaultTheme;
assert<BuildContext extends BuildContextLike ? true : false>();
export async function bringInAccountV1(params: {
resourcesDirPath: string;
buildContext: BuildContextLike;
}) {
const { resourcesDirPath, buildContext } = params;
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion: LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1,
buildContext
});
const accountV1DirPath = pathJoin(
resourcesDirPath,
"theme",
ACCOUNT_V1_THEME_NAME,
"account"
);
transformCodebase({
srcDirPath: pathJoin(defaultThemeDirPath, "base", "account"),
destDirPath: accountV1DirPath
});
transformCodebase({
srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", "account", "resources"),
destDirPath: pathJoin(accountV1DirPath, "resources")
});
transformCodebase({
srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", "common", "resources"),
destDirPath: pathJoin(accountV1DirPath, "resources", RESOURCES_COMMON)
});
fs.writeFileSync(
pathJoin(accountV1DirPath, "theme.properties"),
Buffer.from(
[
"accountResourceProvider=account-v1",
"",
"locales=ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
"",
"styles=" +
[
"css/account.css",
"img/icon-sidebar-active.png",
"img/logo.png",
...[
"patternfly.min.css",
"patternfly-additions.min.css",
"patternfly-additions.min.css"
].map(
fileBasename =>
`${RESOURCES_COMMON}/node_modules/patternfly/dist/css/${fileBasename}`
)
].join(" "),
"",
"##### css classes for form buttons",
"# main class used for all buttons",
"kcButtonClass=btn",
"# classes defining priority of the button - primary or default (there is typically only one priority button for the form)",
"kcButtonPrimaryClass=btn-primary",
"kcButtonDefaultClass=btn-default",
"# classes defining size of the button",
"kcButtonLargeClass=btn-lg",
""
].join("\n"),
"utf8"
)
);
}

View File

@ -1,11 +1,6 @@
import { transformCodebase } from "../../tools/transformCodebase";
import * as fs from "fs";
import {
join as pathJoin,
resolve as pathResolve,
relative as pathRelative,
dirname as pathDirname
} from "path";
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
import {
@ -14,26 +9,15 @@ import {
} from "../generateFtl";
import {
type ThemeType,
LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1,
KEYCLOAK_RESOURCES,
ACCOUNT_V1_THEME_NAME,
BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR,
LOGIN_THEME_PAGE_IDS,
ACCOUNT_THEME_PAGE_IDS
ACCOUNT_THEME_PAGE_IDS,
WELL_KNOWN_DIRECTORY_BASE_NAME
} from "../../shared/constants";
import type { BuildContext } from "../../shared/buildContext";
import { assert, type Equals } from "tsafe/assert";
import {
downloadKeycloakStaticResources,
type BuildContextLike as BuildContextLike_downloadKeycloakStaticResources
} from "../../shared/downloadKeycloakStaticResources";
import { readFieldNameUsage } from "./readFieldNameUsage";
import { readExtraPagesNames } from "./readExtraPageNames";
import { generateMessageProperties } from "./generateMessageProperties";
import {
bringInAccountV1,
type BuildContextLike as BuildContextLike_bringInAccountV1
} from "./bringInAccountV1";
import { rmSync } from "../../tools/fs.rmSync";
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
import {
@ -43,12 +27,10 @@ import {
import { objectEntries } from "tsafe/objectEntries";
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
import * as child_process from "child_process";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
BuildContextLike_downloadKeycloakStaticResources &
BuildContextLike_bringInAccountV1 & {
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode & {
extraThemeProperties: string[] | undefined;
loginThemeResourcesFromKeycloakVersion: string;
projectDirPath: string;
projectBuildDirPath: string;
environmentVariables: { name: string; default: string }[];
@ -88,7 +70,7 @@ export async function generateResourcesForMainTheme(params: {
const destDirPath = pathJoin(
themeTypeDirPath,
"resources",
BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR
WELL_KNOWN_DIRECTORY_BASE_NAME.DIST
);
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
@ -106,7 +88,7 @@ export async function generateResourcesForMainTheme(params: {
themeType: "login"
}),
"resources",
BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR
WELL_KNOWN_DIRECTORY_BASE_NAME.DIST
),
destDirPath
});
@ -117,7 +99,7 @@ export async function generateResourcesForMainTheme(params: {
{
const dirPath = pathJoin(
buildContext.projectBuildDirPath,
KEYCLOAK_RESOURCES
WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY
);
if (fs.existsSync(dirPath)) {
@ -125,7 +107,7 @@ export async function generateResourcesForMainTheme(params: {
throw new Error(
[
`Keycloakify build error: The ${KEYCLOAK_RESOURCES} directory shouldn't exist in your build directory.`,
`Keycloakify build error: The ${WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY} directory shouldn't exist in your build directory.`,
`(${pathRelative(process.cwd(), dirPath)}).\n`,
`Theses assets are only required for local development with Storybook.",
"Please remove this directory as an additional step of your command.\n`,
@ -232,23 +214,50 @@ export async function generateResourcesForMainTheme(params: {
});
}
bring_in_account_v3_i18n_messages: {
if (!buildContext.implementedThemeTypes.account.isImplemented) {
break bring_in_account_v3_i18n_messages;
}
if (buildContext.implementedThemeTypes.account.type !== "Single-Page") {
break bring_in_account_v3_i18n_messages;
}
const accountUiDirPath = child_process
.execSync("npm list @keycloakify/keycloak-account-ui --parseable", {
cwd: pathDirname(buildContext.packageJsonFilePath)
})
.toString("utf8")
.trim();
const messagesDirPath = pathJoin(accountUiDirPath, "messages");
if (!fs.existsSync(messagesDirPath)) {
throw new Error(
`Please update @keycloakify/keycloak-account-ui to 25.0.4-rc.5 or later.`
);
}
transformCodebase({
srcDirPath: messagesDirPath,
destDirPath: pathJoin(
getThemeTypeDirPath({ themeType: "account" }),
"messages"
)
});
}
keycloak_static_resources: {
if (isForAccountSpa) {
break keycloak_static_resources;
}
await downloadKeycloakStaticResources({
keycloakVersion: (() => {
switch (themeType) {
case "account":
return LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1;
case "login":
return buildContext.loginThemeResourcesFromKeycloakVersion;
}
})(),
themeDirPath: pathResolve(pathJoin(themeTypeDirPath, "..")),
themeType,
buildContext
transformCodebase({
srcDirPath: pathJoin(
getThisCodebaseRootDirPath(),
WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES,
themeType
),
destDirPath: pathJoin(themeTypeDirPath, "resources")
});
}
@ -259,7 +268,7 @@ export async function generateResourcesForMainTheme(params: {
`parent=${(() => {
switch (themeType) {
case "account":
return isForAccountSpa ? "base" : ACCOUNT_V1_THEME_NAME;
return isForAccountSpa ? "base" : "account-v1";
case "login":
return "keycloak";
}
@ -299,41 +308,12 @@ export async function generateResourcesForMainTheme(params: {
break bring_in_account_v1;
}
await bringInAccountV1({
resourcesDirPath,
buildContext
});
}
bring_in_account_v3_i18n_messages: {
if (!buildContext.implementedThemeTypes.account.isImplemented) {
break bring_in_account_v3_i18n_messages;
}
if (buildContext.implementedThemeTypes.account.type !== "Single-Page") {
break bring_in_account_v3_i18n_messages;
}
const accountUiDirPath = child_process
.execSync("npm list @keycloakify/keycloak-account-ui --parseable", {
cwd: pathDirname(buildContext.packageJsonFilePath)
})
.toString("utf8")
.trim();
const messagesDirPath = pathJoin(accountUiDirPath, "messages");
if (!fs.existsSync(messagesDirPath)) {
throw new Error(
`Please update @keycloakify/keycloak-account-ui to 25.0.4-rc.5 or later.`
);
}
transformCodebase({
srcDirPath: messagesDirPath,
destDirPath: pathJoin(
getThemeTypeDirPath({ themeType: "account" }),
"messages"
)
srcDirPath: pathJoin(
getThisCodebaseRootDirPath(),
WELL_KNOWN_DIRECTORY_BASE_NAME.ACCOUNT_V1
),
destDirPath: pathJoin(resourcesDirPath, "theme", "account-v1", "account")
});
}
@ -349,7 +329,7 @@ export async function generateResourcesForMainTheme(params: {
if (buildContext.implementedThemeTypes.account.isImplemented) {
metaInfKeycloakThemes.themes.push({
name: ACCOUNT_V1_THEME_NAME,
name: "account-v1",
types: ["account"]
});
}

View File

@ -1,5 +1,5 @@
import type { BuildContext } from "../../shared/buildContext";
import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "../../shared/constants";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants";
import { assert } from "tsafe/assert";
import { posix } from "path";
@ -50,7 +50,7 @@ export function replaceImportsInCssCode(params: {
break inline_style_in_html;
}
return `url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}${assetFileAbsoluteUrlPathname}")`;
return `url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}${assetFileAbsoluteUrlPathname}")`;
}
const assetFileRelativeUrlPathname = posix.relative(

View File

@ -1,4 +1,4 @@
import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "../../../shared/constants";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../../shared/constants";
import { assert } from "tsafe/assert";
import type { BuildContext } from "../../../shared/buildContext";
import * as nodePath from "path";
@ -85,13 +85,13 @@ export function replaceImportsInJsCode_vite(params: {
fixedJsCode = replaceAll(
fixedJsCode,
`"${relativePathOfAssetFile}"`,
`(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${relativePathOfAssetFile}")`
`(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${relativePathOfAssetFile}")`
);
fixedJsCode = replaceAll(
fixedJsCode,
`"${buildContext.urlPathname ?? "/"}${relativePathOfAssetFile}"`,
`(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${relativePathOfAssetFile}")`
`(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${relativePathOfAssetFile}")`
);
});
}

View File

@ -1,4 +1,4 @@
import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "../../../shared/constants";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../../shared/constants";
import { assert } from "tsafe/assert";
import type { BuildContext } from "../../../shared/buildContext";
import * as nodePath from "path";
@ -90,7 +90,7 @@ export function replaceImportsInJsCode_webpack(params: {
return "${u}";
})()] = ${
isArrowFunction ? `${e} =>` : `function(${e}) { return `
} "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${staticDir}${language}/"`
} "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${staticDir}${language}/"`
.replace(/\s+/g, " ")
.trim();
}
@ -104,7 +104,7 @@ export function replaceImportsInJsCode_webpack(params: {
`[a-zA-Z]+\\.[a-zA-Z]+\\+"${staticDir.replace(/\//g, "\\/")}`,
"g"
),
`window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/${staticDir}`
`window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/${staticDir}`
);
return { fixedJsCode };

View File

@ -14,8 +14,7 @@ import { assert, type Equals } from "tsafe/assert";
import * as child_process from "child_process";
import {
VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES,
BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME,
LOGIN_THEME_RESOURCES_FROM_KEYCLOAK_VERSION_DEFAULT
BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME
} from "./constants";
import type { KeycloakVersionRange } from "./KeycloakVersionRange";
import { exclude } from "tsafe";
@ -33,7 +32,6 @@ export type BuildContext = {
extraThemeProperties: string[] | undefined;
groupId: string;
artifactId: string;
loginThemeResourcesFromKeycloakVersion: string;
projectDirPath: string;
projectBuildDirPath: string;
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
@ -85,7 +83,6 @@ export type BuildOptions = {
extraThemeProperties?: string[];
artifactId?: string;
groupId?: string;
loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string;
kcContextExclusionsFtl?: string;
startKeycloakOptions?: {
@ -357,7 +354,6 @@ export function getBuildContext(params: {
extraThemeProperties: z.array(z.string()).optional(),
artifactId: z.string().optional(),
groupId: z.string().optional(),
loginThemeResourcesFromKeycloakVersion: z.string().optional(),
keycloakifyBuildDirPath: z.string().optional(),
kcContextExclusionsFtl: z.string().optional(),
startKeycloakOptions: zStartKeycloakOptions.optional()
@ -545,9 +541,6 @@ export function getBuildContext(params: {
process.env.KEYCLOAKIFY_ARTIFACT_ID ??
buildOptions.artifactId ??
`${themeNames[0]}-keycloak-theme`,
loginThemeResourcesFromKeycloakVersion:
buildOptions.loginThemeResourcesFromKeycloakVersion ??
LOGIN_THEME_RESOURCES_FROM_KEYCLOAK_VERSION_DEFAULT,
projectDirPath,
projectBuildDirPath,
keycloakifyBuildDirPath: (() => {

View File

@ -1,10 +1,12 @@
export const KEYCLOAK_RESOURCES = "keycloak-resources";
export const RESOURCES_COMMON = "resources-common";
export const LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 = "21.1.2";
export const BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR = "dist";
export const WELL_KNOWN_DIRECTORY_BASE_NAME = {
DOT_KEYCLOAKIFY: ".keycloakify",
RESOURCES_COMMON: "resources-common",
DIST: "dist",
RESOURCES: "resources",
ACCOUNT_V1: "account-v1"
};
export const THEME_TYPES = ["login", "account"] as const;
export const ACCOUNT_V1_THEME_NAME = "account-v1";
export type ThemeType = (typeof THEME_TYPES)[number];
@ -71,5 +73,3 @@ export type AccountThemePageId = (typeof ACCOUNT_THEME_PAGE_IDS)[number];
export const CONTAINER_NAME = "keycloak-keycloakify";
export const FALLBACK_LANGUAGE_TAG = "en";
export const LOGIN_THEME_RESOURCES_FROM_KEYCLOAK_VERSION_DEFAULT = "24.0.4";

View File

@ -1,44 +1,34 @@
import {
downloadKeycloakStaticResources,
type BuildContextLike as BuildContextLike_downloadKeycloakStaticResources
} from "./downloadKeycloakStaticResources";
import { join as pathJoin, relative as pathRelative } from "path";
import {
THEME_TYPES,
KEYCLOAK_RESOURCES,
LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1
} from "../shared/constants";
import { join as pathJoin, dirname as pathDirname } from "path";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "../shared/constants";
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import { assert } from "tsafe/assert";
import * as fs from "fs";
import { rmSync } from "../tools/fs.rmSync";
import type { BuildContext } from "./buildContext";
import { transformCodebase } from "../tools/transformCodebase";
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
export type BuildContextLike = BuildContextLike_downloadKeycloakStaticResources & {
loginThemeResourcesFromKeycloakVersion: string;
export type BuildContextLike = {
publicDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function copyKeycloakResourcesToPublic(params: {
export function copyKeycloakResourcesToPublic(params: {
buildContext: BuildContextLike;
}) {
const { buildContext } = params;
const destDirPath = pathJoin(buildContext.publicDirPath, KEYCLOAK_RESOURCES);
const destDirPath = pathJoin(
buildContext.publicDirPath,
WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY
);
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
const keycloakifyBuildinfoRaw = JSON.stringify(
{
destDirPath,
keycloakifyVersion: readThisNpmPackageVersion(),
buildContext: {
loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(),
cacheDirPath: pathRelative(destDirPath, buildContext.cacheDirPath),
fetchOptions: buildContext.fetchOptions
}
keycloakifyVersion: readThisNpmPackageVersion()
},
null,
2
@ -62,35 +52,33 @@ export async function copyKeycloakResourcesToPublic(params: {
rmSync(destDirPath, { force: true, recursive: true });
// NOTE: To remove in a while, remove the legacy keycloak-resources directory
rmSync(pathJoin(pathDirname(destDirPath), "keycloak-resources"), {
force: true,
recursive: true
});
fs.mkdirSync(destDirPath, { recursive: true });
fs.writeFileSync(pathJoin(destDirPath, ".gitignore"), Buffer.from("*", "utf8"));
for (const themeType of THEME_TYPES) {
await downloadKeycloakStaticResources({
keycloakVersion: (() => {
switch (themeType) {
case "login":
return buildContext.loginThemeResourcesFromKeycloakVersion;
case "account":
return LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1;
}
})(),
themeType,
themeDirPath: destDirPath,
buildContext
transformCodebase({
srcDirPath: pathJoin(
getThisCodebaseRootDirPath(),
WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES
),
destDirPath
});
}
fs.writeFileSync(
pathJoin(destDirPath, "README.txt"),
Buffer.from(
// prettier-ignore
[
"This is just a test folder that helps develop",
"the login and register page without having to run a Keycloak container\n",
"This directory will be automatically excluded from the final build."
].join(" ")
"This directory is only used in dev mode by Keycloakify",
"It won't be included in your final build.",
"Do not modify anything in this directory.",
].join("\n")
)
);

View File

@ -1,338 +0,0 @@
import { join as pathJoin, relative as pathRelative } from "path";
import { type BuildContext } from "../buildContext";
import { assert } from "tsafe/assert";
import { LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 } from "../constants";
import { downloadAndExtractArchive } from "../../tools/downloadAndExtractArchive";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
import * as fsPr from "fs/promises";
export type BuildContextLike = {
cacheDirPath: string;
fetchOptions: BuildContext["fetchOptions"];
};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function downloadKeycloakDefaultTheme(params: {
keycloakVersion: string;
buildContext: BuildContextLike;
}): Promise<{ defaultThemeDirPath: string }> {
const { keycloakVersion, buildContext } = params;
let kcNodeModulesKeepFilePaths: Set<string> | undefined = undefined;
let kcNodeModulesKeepFilePaths_lastAccountV1: Set<string> | undefined = undefined;
let areExtraAssetsFor24Copied = false;
const { extractedDirPath } = await downloadAndExtractArchive({
url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
cacheDirPath: buildContext.cacheDirPath,
fetchOptions: buildContext.fetchOptions,
uniqueIdOfOnArchiveFile: "downloadKeycloakDefaultTheme",
onArchiveFile: async params => {
const fileRelativePath = pathRelative("theme", params.fileRelativePath);
if (fileRelativePath.startsWith("..")) {
return;
}
skip_keycloak_v2: {
if (!fileRelativePath.startsWith(pathJoin("keycloak.v2"))) {
break skip_keycloak_v2;
}
return;
}
const { readFile, writeFile } = params;
last_account_v1_transformations: {
if (LAST_KEYCLOAK_VERSION_WITH_ACCOUNT_V1 !== keycloakVersion) {
break last_account_v1_transformations;
}
skip_web_modules: {
if (
!fileRelativePath.startsWith(
pathJoin("keycloak", "common", "resources", "web_modules")
)
) {
break skip_web_modules;
}
return;
}
skip_lib: {
if (
!fileRelativePath.startsWith(
pathJoin("keycloak", "common", "resources", "lib")
)
) {
break skip_lib;
}
return;
}
skip_node_modules: {
const nodeModulesRelativeDirPath = pathJoin(
"keycloak",
"common",
"resources",
"node_modules"
);
if (!fileRelativePath.startsWith(nodeModulesRelativeDirPath)) {
break skip_node_modules;
}
if (kcNodeModulesKeepFilePaths_lastAccountV1 === undefined) {
kcNodeModulesKeepFilePaths_lastAccountV1 = new Set([
pathJoin("patternfly", "dist", "css", "patternfly.min.css"),
pathJoin(
"patternfly",
"dist",
"css",
"patternfly-additions.min.css"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Regular-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Bold-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Light-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Semibold-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.ttf"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.woff"
)
]);
}
const fileRelativeToNodeModulesPath = fileRelativePath.substring(
nodeModulesRelativeDirPath.length + 1
);
if (
kcNodeModulesKeepFilePaths_lastAccountV1.has(
fileRelativeToNodeModulesPath
)
) {
break skip_node_modules;
}
return;
}
patch_account_css: {
if (
fileRelativePath !==
pathJoin("keycloak", "account", "resources", "css", "account.css")
) {
break patch_account_css;
}
await writeFile({
fileRelativePath,
modifiedData: Buffer.from(
(await readFile())
.toString("utf8")
.replace("top: -34px;", "top: -34px !important;"),
"utf8"
)
});
return;
}
}
copy_extra_assets: {
if (keycloakVersion !== "24.0.4") {
break copy_extra_assets;
}
if (areExtraAssetsFor24Copied) {
break copy_extra_assets;
}
const extraAssetsDirPath = pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"shared",
"downloadKeycloakDefaultTheme",
"extra-assets"
);
await Promise.all(
["webauthnAuthenticate.js", "passkeysConditionalAuth.js"].map(
async fileBasename =>
writeFile({
fileRelativePath: pathJoin(
"base",
"login",
"resources",
"js",
fileBasename
),
modifiedData: await fsPr.readFile(
pathJoin(extraAssetsDirPath, fileBasename)
)
})
)
);
}
skip_unused_resources: {
if (keycloakVersion !== "24.0.4") {
break skip_unused_resources;
}
skip_node_modules: {
const nodeModulesRelativeDirPath = pathJoin(
"keycloak",
"common",
"resources",
"node_modules"
);
if (!fileRelativePath.startsWith(nodeModulesRelativeDirPath)) {
break skip_node_modules;
}
if (kcNodeModulesKeepFilePaths === undefined) {
kcNodeModulesKeepFilePaths = new Set([
pathJoin("@patternfly", "patternfly", "patternfly.min.css"),
pathJoin("patternfly", "dist", "css", "patternfly.min.css"),
pathJoin(
"patternfly",
"dist",
"css",
"patternfly-additions.min.css"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Regular-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Light-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Bold-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Bold-webfont.woff"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Bold-webfont.ttf"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"fontawesome-webfont.woff2"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.ttf"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"PatternFlyIcons-webfont.woff"
),
pathJoin(
"patternfly",
"dist",
"fonts",
"OpenSans-Semibold-webfont.woff2"
),
pathJoin("patternfly", "dist", "img", "bg-login.jpg"),
pathJoin("jquery", "dist", "jquery.min.js")
]);
}
const fileRelativeToNodeModulesPath = fileRelativePath.substring(
nodeModulesRelativeDirPath.length + 1
);
if (kcNodeModulesKeepFilePaths.has(fileRelativeToNodeModulesPath)) {
break skip_node_modules;
}
return;
}
skip_vendor: {
if (
!fileRelativePath.startsWith(
pathJoin("keycloak", "common", "resources", "vendor")
)
) {
break skip_vendor;
}
return;
}
skip_rollup_config: {
if (
fileRelativePath !==
pathJoin("keycloak", "common", "resources", "rollup.config.js")
) {
break skip_rollup_config;
}
return;
}
}
await writeFile({ fileRelativePath });
}
});
return { defaultThemeDirPath: extractedDirPath };
}

View File

@ -1,79 +0,0 @@
import { base64url } from "rfc4648";
import { returnSuccess, returnFailure } from "./webauthnAuthenticate.js";
export function initAuthenticate(input) {
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
returnFailure(input.errmsg);
return;
}
if (input.isUserIdentified || typeof PublicKeyCredential.isConditionalMediationAvailable === "undefined") {
document.getElementById("kc-form-passkey-button").style.display = 'block';
} else {
tryAutoFillUI(input);
}
}
function doAuthenticate(input) {
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
returnFailure(input.errmsg);
return;
}
const publicKey = {
rpId : input.rpId,
challenge: base64url.parse(input.challenge, { loose: true })
};
publicKey.allowCredentials = !input.isUserIdentified ? [] : getAllowCredentials();
if (input.createTimeout !== 0) {
publicKey.timeout = input.createTimeout * 1000;
}
if (input.userVerification !== 'not specified') {
publicKey.userVerification = input.userVerification;
}
return navigator.credentials.get({
publicKey: publicKey,
...input.additionalOptions
});
}
async function tryAutoFillUI(input) {
const isConditionalMediationAvailable = await PublicKeyCredential.isConditionalMediationAvailable();
if (isConditionalMediationAvailable) {
document.getElementById("kc-form-login").style.display = "block";
input.additionalOptions = { mediation: 'conditional'};
try {
const result = await doAuthenticate(input);
returnSuccess(result);
} catch (error) {
returnFailure(error);
}
} else {
document.getElementById("kc-form-passkey-button").style.display = 'block';
}
}
function getAllowCredentials() {
const allowCredentials = [];
const authnUse = document.forms['authn_select'].authn_use_chk;
if (authnUse !== undefined) {
if (authnUse.length === undefined) {
allowCredentials.push({
id: base64url.parse(authnUse.value, {loose: true}),
type: 'public-key',
});
} else {
authnUse.forEach((entry) =>
allowCredentials.push({
id: base64url.parse(entry.value, {loose: true}),
type: 'public-key',
}));
}
}
return allowCredentials;
}

View File

@ -1,82 +0,0 @@
import { base64url } from "rfc4648";
export async function authenticateByWebAuthn(input) {
if (!input.isUserIdentified) {
try {
const result = await doAuthenticate([], input.challenge, input.userVerification, input.rpId, input.createTimeout, input.errmsg);
returnSuccess(result);
} catch (error) {
returnFailure(error);
}
return;
}
checkAllowCredentials(input.challenge, input.userVerification, input.rpId, input.createTimeout, input.errmsg);
}
async function checkAllowCredentials(challenge, userVerification, rpId, createTimeout, errmsg) {
const allowCredentials = [];
const authnUse = document.forms['authn_select'].authn_use_chk;
if (authnUse !== undefined) {
if (authnUse.length === undefined) {
allowCredentials.push({
id: base64url.parse(authnUse.value, {loose: true}),
type: 'public-key',
});
} else {
authnUse.forEach((entry) =>
allowCredentials.push({
id: base64url.parse(entry.value, {loose: true}),
type: 'public-key',
}));
}
}
try {
const result = await doAuthenticate(allowCredentials, challenge, userVerification, rpId, createTimeout, errmsg);
returnSuccess(result);
} catch (error) {
returnFailure(error);
}
}
function doAuthenticate(allowCredentials, challenge, userVerification, rpId, createTimeout, errmsg) {
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
returnFailure(errmsg);
return;
}
const publicKey = {
rpId : rpId,
challenge: base64url.parse(challenge, { loose: true })
};
if (createTimeout !== 0) {
publicKey.timeout = createTimeout * 1000;
}
if (allowCredentials.length) {
publicKey.allowCredentials = allowCredentials;
}
if (userVerification !== 'not specified') {
publicKey.userVerification = userVerification;
}
return navigator.credentials.get({publicKey});
}
export function returnSuccess(result) {
document.getElementById("clientDataJSON").value = base64url.stringify(new Uint8Array(result.response.clientDataJSON), { pad: false });
document.getElementById("authenticatorData").value = base64url.stringify(new Uint8Array(result.response.authenticatorData), { pad: false });
document.getElementById("signature").value = base64url.stringify(new Uint8Array(result.response.signature), { pad: false });
document.getElementById("credentialId").value = result.id;
if (result.response.userHandle) {
document.getElementById("userHandle").value = base64url.stringify(new Uint8Array(result.response.userHandle), { pad: false });
}
document.getElementById("webauth").submit();
}
export function returnFailure(err) {
document.getElementById("error").value = err;
document.getElementById("webauth").submit();
}

View File

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

View File

@ -1,53 +0,0 @@
import { transformCodebase } from "../tools/transformCodebase";
import { join as pathJoin } from "path";
import {
downloadKeycloakDefaultTheme,
type BuildContextLike as BuildContextLike_downloadKeycloakDefaultTheme
} from "./downloadKeycloakDefaultTheme";
import { RESOURCES_COMMON, type ThemeType } from "./constants";
import type { BuildContext } from "./buildContext";
import { assert } from "tsafe/assert";
import { existsAsync } from "../tools/fs.existsAsync";
export type BuildContextLike = BuildContextLike_downloadKeycloakDefaultTheme & {};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function downloadKeycloakStaticResources(params: {
themeType: ThemeType;
themeDirPath: string;
keycloakVersion: string;
buildContext: BuildContextLike;
}) {
const { themeType, themeDirPath, keycloakVersion, buildContext } = params;
const { defaultThemeDirPath } = await downloadKeycloakDefaultTheme({
keycloakVersion,
buildContext
});
const resourcesDirPath = pathJoin(themeDirPath, themeType, "resources");
repatriate_base_resources: {
const srcDirPath = pathJoin(defaultThemeDirPath, "base", themeType, "resources");
if (!(await existsAsync(srcDirPath))) {
break repatriate_base_resources;
}
transformCodebase({
srcDirPath,
destDirPath: resourcesDirPath
});
}
transformCodebase({
srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", themeType, "resources"),
destDirPath: resourcesDirPath
});
transformCodebase({
srcDirPath: pathJoin(defaultThemeDirPath, "keycloak", "common", "resources"),
destDirPath: pathJoin(resourcesDirPath, RESOURCES_COMMON)
});
}

View File

@ -1,8 +1,7 @@
import "keycloakify/tools/Object.fromEntries";
import type { KcContext, Attribute } from "./KcContext";
import {
RESOURCES_COMMON,
KEYCLOAK_RESOURCES,
WELL_KNOWN_DIRECTORY_BASE_NAME,
type LoginThemePageId
} from "keycloakify/bin/shared/constants";
import { id } from "tsafe/id";
@ -76,7 +75,7 @@ const attributesByName = Object.fromEntries(
]).map(attribute => [attribute.name, attribute])
);
const resourcesPath = `${BASE_URL}${KEYCLOAK_RESOURCES}/login/resources`;
const resourcesPath = `${BASE_URL}${WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY}/login`;
export const kcContextCommonMock: KcContext.Common = {
themeVersion: "0.0.0",
@ -86,7 +85,7 @@ export const kcContextCommonMock: KcContext.Common = {
url: {
loginAction: "#",
resourcesPath,
resourcesCommonPath: `${resourcesPath}/${RESOURCES_COMMON}`,
resourcesCommonPath: `${resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON}`,
loginRestartFlowUrl: "#",
loginUrl: "#",
ssoLoginInOtherTabsUrl: "#"

View File

@ -1,8 +1,7 @@
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
import type { Plugin } from "vite";
import {
BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR,
KEYCLOAK_RESOURCES,
WELL_KNOWN_DIRECTORY_BASE_NAME,
VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES
} from "../bin/shared/constants";
import { id } from "tsafe/id";
@ -128,14 +127,8 @@ export function keycloakify(params: keycloakify.Params) {
}
});
await Promise.all([
copyKeycloakResourcesToPublic({
buildContext
}),
generateKcGenTs({
buildContext
})
]);
copyKeycloakResourcesToPublic({ buildContext }),
await generateKcGenTs({ buildContext });
},
transform: (code, id) => {
assert(command !== undefined);
@ -174,7 +167,7 @@ export function keycloakify(params: keycloakify.Params) {
`(`,
`(window.kcContext === undefined || import.meta.env.MODE === "development")?`,
`"${urlPathname ?? "/"}":`,
`(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/")`,
`(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`,
`)`
].join("")
);
@ -207,10 +200,13 @@ export function keycloakify(params: keycloakify.Params) {
assert(buildDirPath !== undefined);
await rm(pathJoin(buildDirPath, KEYCLOAK_RESOURCES), {
await rm(
pathJoin(buildDirPath, WELL_KNOWN_DIRECTORY_BASE_NAME.DOT_KEYCLOAKIFY),
{
recursive: true,
force: true
});
}
);
}
} satisfies Plugin;

View File

@ -2,7 +2,7 @@ import { replaceImportsInJsCode_vite } from "keycloakify/bin/keycloakify/replace
import { replaceImportsInJsCode_webpack } from "keycloakify/bin/keycloakify/replacers/replaceImportsInJsCode/webpack";
import { replaceImportsInCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode";
import { expect, it, describe } from "vitest";
import { BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR } from "keycloakify/bin/shared/constants";
import { WELL_KNOWN_DIRECTORY_BASE_NAME } from "keycloakify/bin/shared/constants";
describe("js replacer - vite", () => {
it("replaceImportsInJsCode_vite - 1", () => {
@ -87,13 +87,13 @@ describe("js replacer - vite", () => {
});
const fixedJsCodeExpected = `
S=(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
S=(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
function __vite__mapDeps(indexes) {
if (!__vite__mapDeps.viteFileDeps) {
__vite__mapDeps.viteFileDeps = [
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/Login-dJpPRzM4.js"),
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/index-XwzrZ5Gu.js")
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/Login-dJpPRzM4.js"),
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/index-XwzrZ5Gu.js")
]
}
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
@ -146,13 +146,13 @@ describe("js replacer - vite", () => {
});
const fixedJsCodeExpected = `
S=(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/foo/bar/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
S=(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/foo/bar/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
function __vite__mapDeps(indexes) {
if (!__vite__mapDeps.viteFileDeps) {
__vite__mapDeps.viteFileDeps = [
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/foo/bar/Login-dJpPRzM4.js"),
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/foo/bar/index-XwzrZ5Gu.js")
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/foo/bar/Login-dJpPRzM4.js"),
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/foo/bar/index-XwzrZ5Gu.js")
]
}
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
@ -205,13 +205,13 @@ describe("js replacer - vite", () => {
});
const fixedJsCodeExpected = `
S=(window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
S=(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
function __vite__mapDeps(indexes) {
if (!__vite__mapDeps.viteFileDeps) {
__vite__mapDeps.viteFileDeps = [
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/Login-dJpPRzM4.js"),
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/index-XwzrZ5Gu.js")
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/Login-dJpPRzM4.js"),
(window.kcContext["x-keycloakify"].resourcesPath.substring(1) + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/index-XwzrZ5Gu.js")
]
}
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
@ -267,13 +267,13 @@ describe("js replacer - webpack", () => {
const fixedJsCodeExpected = `
function f() {
return window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/" + ({}[e] || e) + "." + {
return window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
function sameAsF() {
return window.kcContext["x-keycloakify"].resourcesPath + "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/" + ({}[e] || e) + "." + {
return window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
@ -288,7 +288,7 @@ describe("js replacer - webpack", () => {
}
return "u";
})()] = function(e) {
return "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/" + e + "." + {
return "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/" + e + "." + {
147: "6c5cee76",
787: "8da10fcf",
922: "be170a73"
@ -305,7 +305,7 @@ describe("js replacer - webpack", () => {
}
return "miniCssF";
})()] = function(e) {
return "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/css/" + e + "." + {
return "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/css/" + e + "." + {
164:"dcfd7749",
908:"67c9ed2c"
} [e] + ".chunk.css"
@ -320,7 +320,7 @@ describe("js replacer - webpack", () => {
});
}
return "u";
})()] = e => "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
})()] = e => "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
t[(function(){
var pd = Object.getOwnPropertyDescriptor(t, "p");
@ -331,7 +331,7 @@ describe("js replacer - webpack", () => {
});
}
return "miniCssF";
})()] = e => "/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
})()] = e => "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
`;
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
@ -495,15 +495,15 @@ describe("css replacer", () => {
const fixedCssCodeExpected = `
.my-div {
background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/background.png") no-repeat center center;
background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/background.png") no-repeat center center;
}
.my-div2 {
background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/background.png") repeat center center;
background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/background.png") repeat center center;
}
.my-div3 {
background-image: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/media/something.svg");
background-image: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/media/something.svg");
}
`;
@ -533,15 +533,15 @@ describe("css replacer", () => {
const fixedCssCodeExpected = `
.my-div {
background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/background.png") no-repeat center center;
background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/background.png") no-repeat center center;
}
.my-div2 {
background: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/background.png") repeat center center;
background: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/background.png") repeat center center;
}
.my-div3 {
background-image: url("\${xKeycloakify.resourcesPath}/${BASENAME_OF_KEYCLOAKIFY_RESOURCES_DIR}/assets/media/something.svg");
background-image: url("\${xKeycloakify.resourcesPath}/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/assets/media/something.svg");
}
`;