Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
c168c7b156 | |||
7a46115042 | |||
249a7bde89 | |||
813740a002 | |||
7840c2a6f5 | |||
8f6c0d36d9 | |||
12690b892b | |||
d01b4b71c9 | |||
c29e600786 | |||
6309b7c45d | |||
7e7996e40c | |||
deaeab0f61 | |||
6bd5451230 | |||
fb2d651a6f | |||
4845d7c32d | |||
c33c315120 | |||
99b8f1e789 | |||
6af13e1405 | |||
f59fa4238c | |||
248effc57d | |||
9e540b2c1f | |||
ab7b5ff490 | |||
486f944e0f | |||
6cc3d4c442 | |||
083290c6d4 |
1
.github/FUNDING.yaml
vendored
1
.github/FUNDING.yaml
vendored
@ -1,4 +1,3 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [garronej]
|
github: [garronej]
|
||||||
custom: ['https://www.ringerhq.com/experts/garronej']
|
|
||||||
|
@ -46,7 +46,7 @@ Keycloakify is fully compatible with Keycloak from version 11 to 26...[and beyon
|
|||||||
> 📣 **Keycloakify 26 Released**
|
> 📣 **Keycloakify 26 Released**
|
||||||
> Themes built with Keycloakify versions **prior** to Keycloak 26 are **incompatible** with Keycloak 26.
|
> Themes built with Keycloakify versions **prior** to Keycloak 26 are **incompatible** with Keycloak 26.
|
||||||
> To ensure compatibility, simply upgrade to the latest Keycloakify version for your major release (v10 or v11) and rebuild your theme.
|
> To ensure compatibility, simply upgrade to the latest Keycloakify version for your major release (v10 or v11) and rebuild your theme.
|
||||||
> No breaking changes have been introduced, but the target version ranges have been updated. For more details, see [this guide](https://docs.keycloakify.dev/targeting-specific-keycloak-versions).
|
> No breaking changes have been introduced, but the target version ranges have been updated. For more details, see [this guide](https://docs.keycloakify.dev/features/compiler-options/keycloakversiontargets).
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "11.7.0",
|
"version": "11.8.5",
|
||||||
"description": "Framework to create custom Keycloak UIs",
|
"description": "Framework to create custom Keycloak UIs",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -92,12 +92,14 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
const templateValue = "Template.tsx (Layout common to every page)";
|
const templateValue = "Template.tsx (Layout common to every page)";
|
||||||
const userProfileFormFieldsValue =
|
const userProfileFormFieldsValue =
|
||||||
"UserProfileFormFields.tsx (Renders the form of the register.ftl, login-update-profile.ftl, update-email.ftl and idp-review-user-profile.ftl)";
|
"UserProfileFormFields.tsx (Renders the form of the register.ftl, login-update-profile.ftl, update-email.ftl and idp-review-user-profile.ftl)";
|
||||||
|
const otherPageValue = "The page you're looking for isn't listed here";
|
||||||
|
|
||||||
const { value: pageIdOrComponent } = await cliSelect<
|
const { value: pageIdOrComponent } = await cliSelect<
|
||||||
| LoginThemePageId
|
| LoginThemePageId
|
||||||
| AccountThemePageId
|
| AccountThemePageId
|
||||||
| typeof templateValue
|
| typeof templateValue
|
||||||
| typeof userProfileFormFieldsValue
|
| typeof userProfileFormFieldsValue
|
||||||
|
| typeof otherPageValue
|
||||||
>({
|
>({
|
||||||
values: (() => {
|
values: (() => {
|
||||||
switch (themeType) {
|
switch (themeType) {
|
||||||
@ -105,10 +107,11 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
return [
|
return [
|
||||||
templateValue,
|
templateValue,
|
||||||
userProfileFormFieldsValue,
|
userProfileFormFieldsValue,
|
||||||
...LOGIN_THEME_PAGE_IDS
|
...LOGIN_THEME_PAGE_IDS,
|
||||||
|
otherPageValue
|
||||||
];
|
];
|
||||||
case "account":
|
case "account":
|
||||||
return [templateValue, ...ACCOUNT_THEME_PAGE_IDS];
|
return [templateValue, ...ACCOUNT_THEME_PAGE_IDS, otherPageValue];
|
||||||
}
|
}
|
||||||
assert<Equals<typeof themeType, never>>(false);
|
assert<Equals<typeof themeType, never>>(false);
|
||||||
})()
|
})()
|
||||||
@ -116,6 +119,17 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (pageIdOrComponent === otherPageValue) {
|
||||||
|
console.log(
|
||||||
|
[
|
||||||
|
"To style a page not included in the base Keycloak, such as one added by a third-party Keycloak extension,",
|
||||||
|
"refer to the documentation: https://docs.keycloakify.dev/features/styling-a-custom-page-not-included-in-base-keycloak"
|
||||||
|
].join(" ")
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`→ ${pageIdOrComponent}`);
|
console.log(`→ ${pageIdOrComponent}`);
|
||||||
|
|
||||||
const componentBasename = (() => {
|
const componentBasename = (() => {
|
||||||
|
@ -23,22 +23,6 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
|
|
||||||
const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
|
const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
|
||||||
|
|
||||||
if (
|
|
||||||
fs.existsSync(accountThemeSrcDirPath) &&
|
|
||||||
fs.readdirSync(accountThemeSrcDirPath).length > 0
|
|
||||||
) {
|
|
||||||
console.warn(
|
|
||||||
chalk.red(
|
|
||||||
`There is already a ${pathRelative(
|
|
||||||
process.cwd(),
|
|
||||||
accountThemeSrcDirPath
|
|
||||||
)} directory in your project. Aborting.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
process.exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
exitIfUncommittedChanges({
|
exitIfUncommittedChanges({
|
||||||
projectDirPath: buildContext.projectDirPath
|
projectDirPath: buildContext.projectDirPath
|
||||||
});
|
});
|
||||||
@ -51,17 +35,35 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
|
|
||||||
switch (accountThemeType) {
|
switch (accountThemeType) {
|
||||||
case "Multi-Page":
|
case "Multi-Page":
|
||||||
fs.cpSync(
|
{
|
||||||
pathJoin(
|
if (
|
||||||
getThisCodebaseRootDirPath(),
|
fs.existsSync(accountThemeSrcDirPath) &&
|
||||||
"src",
|
fs.readdirSync(accountThemeSrcDirPath).length > 0
|
||||||
"bin",
|
) {
|
||||||
"initialize-account-theme",
|
console.warn(
|
||||||
"multi-page-boilerplate"
|
chalk.red(
|
||||||
),
|
`There is already a ${pathRelative(
|
||||||
accountThemeSrcDirPath,
|
process.cwd(),
|
||||||
{ recursive: true }
|
accountThemeSrcDirPath
|
||||||
);
|
)} directory in your project. Aborting.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.cpSync(
|
||||||
|
pathJoin(
|
||||||
|
getThisCodebaseRootDirPath(),
|
||||||
|
"src",
|
||||||
|
"bin",
|
||||||
|
"initialize-account-theme",
|
||||||
|
"multi-page-boilerplate"
|
||||||
|
),
|
||||||
|
accountThemeSrcDirPath,
|
||||||
|
{ recursive: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "Single-Page":
|
case "Single-Page":
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { i18nBuilder } from "keycloakify/account";
|
import { i18nBuilder } from "keycloakify/account";
|
||||||
import type { ThemeName } from "../kc.gen";
|
import type { ThemeName } from "../kc.gen";
|
||||||
|
|
||||||
/** @see: https://docs.keycloakify.dev/i18n */
|
/** @see: https://docs.keycloakify.dev/features/i18n */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { useI18n, ofTypeI18n } = i18nBuilder.withThemeName<ThemeName>().build();
|
const { useI18n, ofTypeI18n } = i18nBuilder.withThemeName<ThemeName>().build();
|
||||||
|
|
||||||
type I18n = typeof ofTypeI18n;
|
type I18n = typeof ofTypeI18n;
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
|
||||||
import { transformCodebase } from "./tools/transformCodebase";
|
|
||||||
import type { BuildContext } from "./shared/buildContext";
|
import type { BuildContext } from "./shared/buildContext";
|
||||||
import * as fs from "fs";
|
import cliSelect from "cli-select";
|
||||||
import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive";
|
|
||||||
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
|
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
|
||||||
import { assert } from "tsafe/assert";
|
import { exitIfUncommittedChanges } from "./shared/exitIfUncommittedChanges";
|
||||||
import { getSupportedDockerImageTags } from "./start-keycloak/getSupportedDockerImageTags";
|
|
||||||
|
import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { assert, is, type Equals } from "tsafe/assert";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import { addSyncExtensionsToPostinstallScript } from "./shared/addSyncExtensionsToPostinstallScript";
|
||||||
|
import { getIsPrettierAvailable, runPrettier } from "./tools/runPrettier";
|
||||||
|
import { npmInstall } from "./tools/npmInstall";
|
||||||
|
import * as child_process from "child_process";
|
||||||
|
import { z } from "zod";
|
||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
export async function command(params: { buildContext: BuildContext }) {
|
export async function command(params: { buildContext: BuildContext }) {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
|
|
||||||
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({
|
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({
|
||||||
commandName: "initialize-email-theme",
|
commandName: "initialize-account-theme",
|
||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -19,6 +26,10 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitIfUncommittedChanges({
|
||||||
|
projectDirPath: buildContext.projectDirPath
|
||||||
|
});
|
||||||
|
|
||||||
const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
|
const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -26,71 +37,110 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
fs.readdirSync(emailThemeSrcDirPath).length > 0
|
fs.readdirSync(emailThemeSrcDirPath).length > 0
|
||||||
) {
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`There is already a non empty ${pathRelative(
|
chalk.red(
|
||||||
process.cwd(),
|
`There is already a ${pathRelative(
|
||||||
emailThemeSrcDirPath
|
process.cwd(),
|
||||||
)} directory in your project. Aborting.`
|
emailThemeSrcDirPath
|
||||||
|
)} directory in your project. Aborting.`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Initialize with the base email theme from which version of Keycloak?");
|
const { value: emailThemeType } = await cliSelect({
|
||||||
|
values: ["native (FreeMarker)" as const, "jsx-email (React)" as const]
|
||||||
|
}).catch(() => {
|
||||||
|
process.exit(-1);
|
||||||
|
});
|
||||||
|
|
||||||
const { extractedDirPath } = await downloadAndExtractArchive({
|
if (emailThemeType === "jsx-email (React)") {
|
||||||
url: await (async () => {
|
console.log(
|
||||||
const { latestMajorTags } = await getSupportedDockerImageTags({
|
[
|
||||||
buildContext
|
"There is currently no automated support for keycloakify-email, it has to be done manually, see documentation:",
|
||||||
|
"https://docs.keycloakify.dev/theme-types/email-theme"
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedPackageJson = (() => {
|
||||||
|
type ParsedPackageJson = {
|
||||||
|
scripts?: Record<string, string | undefined>;
|
||||||
|
dependencies?: Record<string, string | undefined>;
|
||||||
|
devDependencies?: Record<string, string | undefined>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zParsedPackageJson = (() => {
|
||||||
|
type TargetType = ParsedPackageJson;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
scripts: z.record(z.union([z.string(), z.undefined()])).optional(),
|
||||||
|
dependencies: z.record(z.union([z.string(), z.undefined()])).optional(),
|
||||||
|
devDependencies: z.record(z.union([z.string(), z.undefined()])).optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const keycloakVersion = latestMajorTags[0];
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>;
|
||||||
|
|
||||||
assert(keycloakVersion !== undefined);
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
const parsedPackageJson = JSON.parse(
|
||||||
|
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
|
||||||
|
);
|
||||||
|
|
||||||
return `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`;
|
zParsedPackageJson.parse(parsedPackageJson);
|
||||||
})(),
|
|
||||||
cacheDirPath: buildContext.cacheDirPath,
|
|
||||||
fetchOptions: buildContext.fetchOptions,
|
|
||||||
uniqueIdOfOnArchiveFile: "extractOnlyEmailTheme",
|
|
||||||
onArchiveFile: async ({ fileRelativePath, writeFile }) => {
|
|
||||||
const fileRelativePath_target = pathRelative(
|
|
||||||
pathJoin("theme", "base", "email"),
|
|
||||||
fileRelativePath
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fileRelativePath_target.startsWith("..")) {
|
assert(is<ParsedPackageJson>(parsedPackageJson));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile({ fileRelativePath: fileRelativePath_target });
|
return parsedPackageJson;
|
||||||
}
|
})();
|
||||||
|
|
||||||
|
addSyncExtensionsToPostinstallScript({
|
||||||
|
parsedPackageJson,
|
||||||
|
buildContext
|
||||||
});
|
});
|
||||||
|
|
||||||
transformCodebase({
|
const moduleName = `@keycloakify/email-native`;
|
||||||
srcDirPath: extractedDirPath,
|
|
||||||
destDirPath: emailThemeSrcDirPath
|
const [version] = (
|
||||||
});
|
JSON.parse(
|
||||||
|
child_process
|
||||||
|
.execSync(`npm show ${moduleName} versions --json`)
|
||||||
|
.toString("utf8")
|
||||||
|
.trim()
|
||||||
|
) as string[]
|
||||||
|
)
|
||||||
|
.reverse()
|
||||||
|
.filter(version => !version.includes("-"));
|
||||||
|
|
||||||
|
assert(version !== undefined);
|
||||||
|
|
||||||
|
(parsedPackageJson.dependencies ??= {})[moduleName] = `~${version}`;
|
||||||
|
|
||||||
|
if (parsedPackageJson.devDependencies !== undefined) {
|
||||||
|
delete parsedPackageJson.devDependencies[moduleName];
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const themePropertyFilePath = pathJoin(emailThemeSrcDirPath, "theme.properties");
|
let sourceCode = JSON.stringify(parsedPackageJson, undefined, 2);
|
||||||
|
|
||||||
|
if (await getIsPrettierAvailable()) {
|
||||||
|
sourceCode = await runPrettier({
|
||||||
|
sourceCode,
|
||||||
|
filePath: buildContext.packageJsonFilePath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
themePropertyFilePath,
|
buildContext.packageJsonFilePath,
|
||||||
Buffer.from(
|
Buffer.from(sourceCode, "utf8")
|
||||||
[
|
|
||||||
`parent=base`,
|
|
||||||
fs.readFileSync(themePropertyFilePath).toString("utf8")
|
|
||||||
].join("\n"),
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
await npmInstall({
|
||||||
`The \`${pathJoin(
|
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
||||||
".",
|
});
|
||||||
pathRelative(process.cwd(), emailThemeSrcDirPath)
|
|
||||||
)}\` directory have been created.`
|
console.log(chalk.green("Email theme initialized."));
|
||||||
);
|
|
||||||
console.log("You can delete any file you don't modify.");
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import { readFileSync } from "fs";
|
|||||||
import { isInside } from "../../tools/isInside";
|
import { isInside } from "../../tools/isInside";
|
||||||
import child_process from "child_process";
|
import child_process from "child_process";
|
||||||
import { rmSync } from "../../tools/fs.rmSync";
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
import { writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes";
|
|
||||||
import { existsAsync } from "../../tools/fs.existsAsync";
|
import { existsAsync } from "../../tools/fs.existsAsync";
|
||||||
|
|
||||||
export type BuildContextLike = BuildContextLike_generatePom & {
|
export type BuildContextLike = BuildContextLike_generatePom & {
|
||||||
@ -106,29 +105,55 @@ export async function buildJar(params: {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
remove_account_v1_in_meta_inf: {
|
{
|
||||||
if (!doesImplementAccountV1Theme) {
|
const filePath = pathJoin(
|
||||||
// NOTE: We do not have account v1 anyway
|
tmpResourcesDirPath,
|
||||||
break remove_account_v1_in_meta_inf;
|
"META-INF",
|
||||||
}
|
"keycloak-themes.json"
|
||||||
|
);
|
||||||
|
|
||||||
if (keycloakAccountV1Version !== null) {
|
await fs.mkdir(pathDirname(filePath));
|
||||||
// NOTE: No, we need to keep account-v1 in meta-inf
|
|
||||||
break remove_account_v1_in_meta_inf;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeMetaInfKeycloakThemes({
|
await fs.writeFile(
|
||||||
resourcesDirPath: tmpResourcesDirPath,
|
filePath,
|
||||||
getNewMetaInfKeycloakTheme: ({ metaInfKeycloakTheme }) => {
|
Buffer.from(
|
||||||
assert(metaInfKeycloakTheme !== undefined);
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
themes: await (async () => {
|
||||||
|
const dirPath = pathJoin(tmpResourcesDirPath, "theme");
|
||||||
|
|
||||||
metaInfKeycloakTheme.themes = metaInfKeycloakTheme.themes.filter(
|
const themeNames = (await fs.readdir(dirPath)).sort(
|
||||||
({ name }) => name !== "account-v1"
|
(a, b) => {
|
||||||
);
|
const indexA = buildContext.themeNames.indexOf(a);
|
||||||
|
const indexB = buildContext.themeNames.indexOf(b);
|
||||||
|
|
||||||
return metaInfKeycloakTheme;
|
const orderA = indexA === -1 ? Infinity : indexA;
|
||||||
}
|
const orderB = indexB === -1 ? Infinity : indexB;
|
||||||
});
|
|
||||||
|
return orderA - orderB;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
themeNames.map(async themeName => {
|
||||||
|
const types = await fs.readdir(
|
||||||
|
pathJoin(dirPath, themeName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: themeName,
|
||||||
|
types
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
route_legacy_pages: {
|
route_legacy_pages: {
|
||||||
|
@ -6,8 +6,7 @@ import {
|
|||||||
join as pathJoin,
|
join as pathJoin,
|
||||||
relative as pathRelative,
|
relative as pathRelative,
|
||||||
dirname as pathDirname,
|
dirname as pathDirname,
|
||||||
extname as pathExtname,
|
basename as pathBasename
|
||||||
sep as pathSep
|
|
||||||
} from "path";
|
} from "path";
|
||||||
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
||||||
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
||||||
@ -31,16 +30,13 @@ import {
|
|||||||
type BuildContextLike as BuildContextLike_generateMessageProperties
|
type BuildContextLike as BuildContextLike_generateMessageProperties
|
||||||
} from "./generateMessageProperties";
|
} from "./generateMessageProperties";
|
||||||
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
|
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
|
||||||
import {
|
|
||||||
writeMetaInfKeycloakThemes,
|
|
||||||
type MetaInfKeycloakTheme
|
|
||||||
} from "../../shared/metaInfKeycloakThemes";
|
|
||||||
import { objectEntries } from "tsafe/objectEntries";
|
|
||||||
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
|
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
|
||||||
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
||||||
import propertiesParser from "properties-parser";
|
import propertiesParser from "properties-parser";
|
||||||
import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
|
import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
|
||||||
import { listInstalledModules } from "../../tools/listInstalledModules";
|
import { listInstalledModules } from "../../tools/listInstalledModules";
|
||||||
|
import { isInside } from "../../tools/isInside";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
|
||||||
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
||||||
BuildContextLike_generateMessageProperties & {
|
BuildContextLike_generateMessageProperties & {
|
||||||
@ -61,6 +57,8 @@ export async function generateResources(params: {
|
|||||||
buildContext: BuildContextLike;
|
buildContext: BuildContextLike;
|
||||||
resourcesDirPath: string;
|
resourcesDirPath: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
const { resourcesDirPath, buildContext } = params;
|
const { resourcesDirPath, buildContext } = params;
|
||||||
|
|
||||||
const [themeName] = buildContext.themeNames;
|
const [themeName] = buildContext.themeNames;
|
||||||
@ -78,12 +76,23 @@ export async function generateResources(params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const writeMessagePropertiesFilesByThemeType: Partial<
|
const writeMessagePropertiesFilesByThemeType: Partial<
|
||||||
Record<ThemeType, (params: { messageDirPath: string; themeName: string }) => void>
|
Record<
|
||||||
|
ThemeType | "email",
|
||||||
|
(params: { messageDirPath: string; themeName: string }) => void
|
||||||
|
>
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
for (const themeType of THEME_TYPES) {
|
for (const themeType of [...THEME_TYPES, "email"] as const) {
|
||||||
if (!buildContext.implementedThemeTypes[themeType].isImplemented) {
|
let isNative: boolean;
|
||||||
continue;
|
|
||||||
|
{
|
||||||
|
const v = buildContext.implementedThemeTypes[themeType];
|
||||||
|
|
||||||
|
if (!v.isImplemented && !v.isImplemented_native) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
isNative = !v.isImplemented && v.isImplemented_native;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAccountThemeType = () => {
|
const getAccountThemeType = () => {
|
||||||
@ -102,12 +111,18 @@ export async function generateResources(params: {
|
|||||||
return getAccountThemeType() === "Single-Page";
|
return getAccountThemeType() === "Single-Page";
|
||||||
case "admin":
|
case "admin":
|
||||||
return true;
|
return true;
|
||||||
|
case "email":
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const themeTypeDirPath = getThemeTypeDirPath({ themeName, themeType });
|
const themeTypeDirPath = getThemeTypeDirPath({ themeName, themeType });
|
||||||
|
|
||||||
apply_replacers_and_move_to_theme_resources: {
|
apply_replacers_and_move_to_theme_resources: {
|
||||||
|
if (isNative) {
|
||||||
|
break apply_replacers_and_move_to_theme_resources;
|
||||||
|
}
|
||||||
|
|
||||||
const destDirPath = pathJoin(
|
const destDirPath = pathJoin(
|
||||||
themeTypeDirPath,
|
themeTypeDirPath,
|
||||||
"resources",
|
"resources",
|
||||||
@ -191,59 +206,93 @@ export async function generateResources(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
generate_ftl_files: {
|
||||||
themeName,
|
if (isNative) {
|
||||||
indexHtmlCode: fs
|
break generate_ftl_files;
|
||||||
.readFileSync(pathJoin(buildContext.projectBuildDirPath, "index.html"))
|
}
|
||||||
.toString("utf8"),
|
|
||||||
buildContext,
|
|
||||||
keycloakifyVersion: readThisNpmPackageVersion(),
|
|
||||||
themeType,
|
|
||||||
fieldNames: isSpa
|
|
||||||
? []
|
|
||||||
: (assert(themeType !== "admin"),
|
|
||||||
readFieldNameUsage({
|
|
||||||
themeSrcDirPath: buildContext.themeSrcDirPath,
|
|
||||||
themeType
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
assert(themeType !== "email");
|
||||||
...(() => {
|
|
||||||
switch (themeType) {
|
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
||||||
case "login":
|
themeName,
|
||||||
return LOGIN_THEME_PAGE_IDS;
|
indexHtmlCode: fs
|
||||||
case "account":
|
.readFileSync(
|
||||||
return getAccountThemeType() === "Single-Page"
|
pathJoin(buildContext.projectBuildDirPath, "index.html")
|
||||||
? ["index.ftl"]
|
)
|
||||||
: ACCOUNT_THEME_PAGE_IDS;
|
.toString("utf8"),
|
||||||
case "admin":
|
buildContext,
|
||||||
return ["index.ftl"];
|
keycloakifyVersion: readThisNpmPackageVersion(),
|
||||||
|
themeType,
|
||||||
|
fieldNames: isSpa
|
||||||
|
? []
|
||||||
|
: (assert(themeType !== "admin"),
|
||||||
|
readFieldNameUsage({
|
||||||
|
themeSrcDirPath: buildContext.themeSrcDirPath,
|
||||||
|
themeType
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
...(() => {
|
||||||
|
switch (themeType) {
|
||||||
|
case "login":
|
||||||
|
return LOGIN_THEME_PAGE_IDS;
|
||||||
|
case "account":
|
||||||
|
return getAccountThemeType() === "Single-Page"
|
||||||
|
? ["index.ftl"]
|
||||||
|
: ACCOUNT_THEME_PAGE_IDS;
|
||||||
|
case "admin":
|
||||||
|
return ["index.ftl"];
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
...(isSpa
|
||||||
|
? []
|
||||||
|
: readExtraPagesNames({
|
||||||
|
themeType,
|
||||||
|
themeSrcDirPath: buildContext.themeSrcDirPath
|
||||||
|
}))
|
||||||
|
].forEach(pageId => {
|
||||||
|
const { ftlCode } = generateFtlFilesCode({ pageId });
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
pathJoin(themeTypeDirPath, pageId),
|
||||||
|
Buffer.from(ftlCode, "utf8")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_native_theme: {
|
||||||
|
if (!isNative) {
|
||||||
|
break copy_native_theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirPath = pathJoin(buildContext.themeSrcDirPath, themeType);
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: dirPath,
|
||||||
|
destDirPath: getThemeTypeDirPath({ themeName, themeType }),
|
||||||
|
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
||||||
|
if (isInside({ dirPath: "messages", filePath: fileRelativePath })) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { modifiedSourceCode: sourceCode };
|
||||||
}
|
}
|
||||||
})(),
|
});
|
||||||
...(isSpa
|
}
|
||||||
? []
|
|
||||||
: readExtraPagesNames({
|
|
||||||
themeType,
|
|
||||||
themeSrcDirPath: buildContext.themeSrcDirPath
|
|
||||||
}))
|
|
||||||
].forEach(pageId => {
|
|
||||||
const { ftlCode } = generateFtlFilesCode({ pageId });
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
pathJoin(themeTypeDirPath, pageId),
|
|
||||||
Buffer.from(ftlCode, "utf8")
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let languageTags: string[] | undefined = undefined;
|
let languageTags: string[] | undefined = undefined;
|
||||||
|
|
||||||
i18n_multi_page: {
|
i18n_multi_page: {
|
||||||
|
if (isNative) {
|
||||||
|
break i18n_multi_page;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSpa) {
|
if (isSpa) {
|
||||||
break i18n_multi_page;
|
break i18n_multi_page;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(themeType !== "admin");
|
assert(themeType !== "admin" && themeType !== "email");
|
||||||
|
|
||||||
const wrap = generateMessageProperties({
|
const wrap = generateMessageProperties({
|
||||||
buildContext,
|
buildContext,
|
||||||
@ -364,27 +413,24 @@ export async function generateResources(params: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
i18n_single_page: {
|
i18n_for_spas_and_native: {
|
||||||
if (!isSpa) {
|
if (!isSpa && !isNative) {
|
||||||
break i18n_single_page;
|
break i18n_for_spas_and_native;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLegacyAccountSpa) {
|
if (isLegacyAccountSpa) {
|
||||||
break i18n_single_page;
|
break i18n_for_spas_and_native;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(themeType === "account" || themeType === "admin");
|
|
||||||
|
|
||||||
const messagesDirPath_theme = pathJoin(
|
const messagesDirPath_theme = pathJoin(
|
||||||
buildContext.themeSrcDirPath,
|
buildContext.themeSrcDirPath,
|
||||||
themeType,
|
themeType,
|
||||||
"i18n"
|
isNative ? "messages" : "i18n"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(
|
if (!fs.existsSync(messagesDirPath_theme)) {
|
||||||
fs.existsSync(messagesDirPath_theme),
|
break i18n_for_spas_and_native;
|
||||||
`${messagesDirPath_theme} is supposed to exist`
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const propertiesByLang: Record<
|
const propertiesByLang: Record<
|
||||||
string,
|
string,
|
||||||
@ -524,6 +570,10 @@ export async function generateResources(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keycloak_static_resources: {
|
keycloak_static_resources: {
|
||||||
|
if (isNative) {
|
||||||
|
break keycloak_static_resources;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSpa) {
|
if (isSpa) {
|
||||||
break keycloak_static_resources;
|
break keycloak_static_resources;
|
||||||
}
|
}
|
||||||
@ -540,183 +590,167 @@ export async function generateResources(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(
|
bring_in_account_v1: {
|
||||||
pathJoin(themeTypeDirPath, "theme.properties"),
|
if (isNative) {
|
||||||
Buffer.from(
|
break bring_in_account_v1;
|
||||||
[
|
|
||||||
`parent=${(() => {
|
|
||||||
switch (themeType) {
|
|
||||||
case "account":
|
|
||||||
switch (getAccountThemeType()) {
|
|
||||||
case "Multi-Page":
|
|
||||||
return "account-v1";
|
|
||||||
case "Single-Page":
|
|
||||||
return "base";
|
|
||||||
}
|
|
||||||
case "login":
|
|
||||||
return "keycloak";
|
|
||||||
case "admin":
|
|
||||||
return "base";
|
|
||||||
}
|
|
||||||
assert<Equals<typeof themeType, never>>(false);
|
|
||||||
})()}`,
|
|
||||||
...(themeType === "account" && getAccountThemeType() === "Single-Page"
|
|
||||||
? ["deprecatedMode=false"]
|
|
||||||
: []),
|
|
||||||
...(buildContext.extraThemeProperties ?? []),
|
|
||||||
...[
|
|
||||||
...buildContext.environmentVariables,
|
|
||||||
{ name: KEYCLOAKIFY_SPA_DEV_SERVER_PORT, default: "" }
|
|
||||||
].map(
|
|
||||||
({ name, default: defaultValue }) =>
|
|
||||||
`${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
|
|
||||||
),
|
|
||||||
...(languageTags === undefined
|
|
||||||
? []
|
|
||||||
: [`locales=${languageTags.join(",")}`])
|
|
||||||
].join("\n\n"),
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
email: {
|
|
||||||
if (!buildContext.implementedThemeTypes.email.isImplemented) {
|
|
||||||
break email;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: emailThemeSrcDirPath,
|
|
||||||
destDirPath: getThemeTypeDirPath({ themeName, themeType: "email" })
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bring_in_account_v1: {
|
|
||||||
if (!buildContext.implementedThemeTypes.account.isImplemented) {
|
|
||||||
break bring_in_account_v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildContext.implementedThemeTypes.account.type !== "Multi-Page") {
|
|
||||||
break bring_in_account_v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: pathJoin(getThisCodebaseRootDirPath(), "res", "account-v1"),
|
|
||||||
destDirPath: getThemeTypeDirPath({
|
|
||||||
themeName: "account-v1",
|
|
||||||
themeType: "account"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const metaInfKeycloakThemes: MetaInfKeycloakTheme = { themes: [] };
|
|
||||||
|
|
||||||
for (const themeName of buildContext.themeNames) {
|
|
||||||
metaInfKeycloakThemes.themes.push({
|
|
||||||
name: themeName,
|
|
||||||
types: objectEntries(buildContext.implementedThemeTypes)
|
|
||||||
.filter(([, { isImplemented }]) => isImplemented)
|
|
||||||
.map(([themeType]) => themeType)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildContext.implementedThemeTypes.account.isImplemented) {
|
|
||||||
metaInfKeycloakThemes.themes.push({
|
|
||||||
name: "account-v1",
|
|
||||||
types: ["account"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
writeMetaInfKeycloakThemes({
|
|
||||||
resourcesDirPath,
|
|
||||||
getNewMetaInfKeycloakTheme: () => metaInfKeycloakThemes
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const themeVariantName of buildContext.themeNames) {
|
|
||||||
if (themeVariantName === themeName) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
srcDirPath: pathJoin(resourcesDirPath, "theme", themeName),
|
|
||||||
destDirPath: pathJoin(resourcesDirPath, "theme", themeVariantName),
|
|
||||||
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
|
||||||
if (
|
|
||||||
pathExtname(fileRelativePath) === ".ftl" &&
|
|
||||||
fileRelativePath.split(pathSep).length === 2
|
|
||||||
) {
|
|
||||||
const modifiedSourceCode = Buffer.from(
|
|
||||||
Buffer.from(sourceCode)
|
|
||||||
.toString("utf-8")
|
|
||||||
.replace(
|
|
||||||
`"themeName": "${themeName}"`,
|
|
||||||
`"themeName": "${themeVariantName}"`
|
|
||||||
),
|
|
||||||
"utf8"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(Buffer.compare(modifiedSourceCode, sourceCode) !== 0);
|
|
||||||
|
|
||||||
return { modifiedSourceCode };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { modifiedSourceCode: sourceCode };
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const themeName of buildContext.themeNames) {
|
if (themeType !== "account") {
|
||||||
for (const [themeType, writeMessagePropertiesFiles] of objectEntries(
|
break bring_in_account_v1;
|
||||||
writeMessagePropertiesFilesByThemeType
|
|
||||||
)) {
|
|
||||||
// NOTE: This is just a quirk of the type system: We can't really differentiate in a record
|
|
||||||
// between the case where the key isn't present and the case where the value is `undefined`.
|
|
||||||
if (writeMessagePropertiesFiles === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
writeMessagePropertiesFiles({
|
|
||||||
messageDirPath: pathJoin(
|
|
||||||
getThemeTypeDirPath({ themeName, themeType }),
|
|
||||||
"messages"
|
|
||||||
),
|
|
||||||
themeName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modify_email_theme_per_variant: {
|
assert(buildContext.implementedThemeTypes.account.isImplemented);
|
||||||
if (!buildContext.implementedThemeTypes.email.isImplemented) {
|
|
||||||
break modify_email_theme_per_variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const themeName of buildContext.themeNames) {
|
if (buildContext.implementedThemeTypes.account.type !== "Multi-Page") {
|
||||||
const emailThemeDirPath = getThemeTypeDirPath({
|
break bring_in_account_v1;
|
||||||
themeName,
|
}
|
||||||
themeType: "email"
|
|
||||||
});
|
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
srcDirPath: emailThemeDirPath,
|
srcDirPath: pathJoin(getThisCodebaseRootDirPath(), "res", "account-v1"),
|
||||||
destDirPath: emailThemeDirPath,
|
destDirPath: getThemeTypeDirPath({
|
||||||
transformSourceCode: ({ filePath, sourceCode }) => {
|
themeName: "account-v1",
|
||||||
if (!filePath.endsWith(".ftl")) {
|
themeType: "account"
|
||||||
return { modifiedSourceCode: sourceCode };
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
modifiedSourceCode: Buffer.from(
|
|
||||||
sourceCode
|
|
||||||
.toString("utf8")
|
|
||||||
.replace(/xKeycloakify\.themeName/g, `"${themeName}"`),
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generate_theme_properties: {
|
||||||
|
if (isNative) {
|
||||||
|
break generate_theme_properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(themeType !== "email");
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
pathJoin(themeTypeDirPath, "theme.properties"),
|
||||||
|
Buffer.from(
|
||||||
|
[
|
||||||
|
`parent=${(() => {
|
||||||
|
switch (themeType) {
|
||||||
|
case "account":
|
||||||
|
switch (getAccountThemeType()) {
|
||||||
|
case "Multi-Page":
|
||||||
|
return "account-v1";
|
||||||
|
case "Single-Page":
|
||||||
|
return "base";
|
||||||
|
}
|
||||||
|
case "login":
|
||||||
|
return "keycloak";
|
||||||
|
case "admin":
|
||||||
|
return "base";
|
||||||
|
}
|
||||||
|
assert<Equals<typeof themeType, never>>;
|
||||||
|
})()}`,
|
||||||
|
...(themeType === "account" &&
|
||||||
|
getAccountThemeType() === "Single-Page"
|
||||||
|
? ["deprecatedMode=false"]
|
||||||
|
: []),
|
||||||
|
...(buildContext.extraThemeProperties ?? []),
|
||||||
|
...[
|
||||||
|
...buildContext.environmentVariables,
|
||||||
|
{ name: KEYCLOAKIFY_SPA_DEV_SERVER_PORT, default: "" }
|
||||||
|
].map(
|
||||||
|
({ name, default: defaultValue }) =>
|
||||||
|
`${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
|
||||||
|
),
|
||||||
|
...(languageTags === undefined
|
||||||
|
? []
|
||||||
|
: [`locales=${languageTags.join(",")}`])
|
||||||
|
].join("\n\n"),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const themeVariantName of [...buildContext.themeNames].reverse()) {
|
||||||
|
for (const themeType of [...THEME_TYPES, "email"] as const) {
|
||||||
|
copy_main_theme_to_theme_variant_theme: {
|
||||||
|
let isNative: boolean;
|
||||||
|
|
||||||
|
{
|
||||||
|
const v = buildContext.implementedThemeTypes[themeType];
|
||||||
|
|
||||||
|
if (!v.isImplemented && !v.isImplemented_native) {
|
||||||
|
break copy_main_theme_to_theme_variant_theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
isNative = !v.isImplemented && v.isImplemented_native;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNative && themeVariantName === themeName) {
|
||||||
|
break copy_main_theme_to_theme_variant_theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
srcDirPath: getThemeTypeDirPath({ themeName, themeType }),
|
||||||
|
destDirPath: getThemeTypeDirPath({
|
||||||
|
themeName: themeVariantName,
|
||||||
|
themeType
|
||||||
|
}),
|
||||||
|
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
|
||||||
|
patch_xKeycloakify_themeName: {
|
||||||
|
if (!fileRelativePath.endsWith(".ftl")) {
|
||||||
|
break patch_xKeycloakify_themeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isNative &&
|
||||||
|
pathBasename(fileRelativePath) !== fileRelativePath
|
||||||
|
) {
|
||||||
|
break patch_xKeycloakify_themeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifiedSourceCode = Buffer.from(
|
||||||
|
Buffer.from(sourceCode)
|
||||||
|
.toString("utf-8")
|
||||||
|
.replace(
|
||||||
|
...id<[string | RegExp, string]>(
|
||||||
|
isNative
|
||||||
|
? [
|
||||||
|
/xKeycloakify\.themeName/g,
|
||||||
|
`"${themeVariantName}"`
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
`"themeName": "${themeName}"`,
|
||||||
|
`"themeName": "${themeVariantName}"`
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isNative) {
|
||||||
|
assert(
|
||||||
|
Buffer.compare(modifiedSourceCode, sourceCode) !== 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { modifiedSourceCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { modifiedSourceCode: sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
run_writeMessagePropertiesFiles: {
|
||||||
|
const writeMessagePropertiesFiles =
|
||||||
|
writeMessagePropertiesFilesByThemeType[themeType];
|
||||||
|
|
||||||
|
if (writeMessagePropertiesFiles === undefined) {
|
||||||
|
break run_writeMessagePropertiesFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeMessagePropertiesFiles({
|
||||||
|
messageDirPath: pathJoin(
|
||||||
|
getThemeTypeDirPath({ themeName: themeVariantName, themeType }),
|
||||||
|
"messages"
|
||||||
|
),
|
||||||
|
themeName: themeVariantName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Generated resources in ${Date.now() - start}ms`);
|
||||||
}
|
}
|
||||||
|
@ -303,7 +303,7 @@ program
|
|||||||
key: "path",
|
key: "path",
|
||||||
name: (() => {
|
name: (() => {
|
||||||
const long = "path";
|
const long = "path";
|
||||||
const short = "p";
|
const short = "t";
|
||||||
|
|
||||||
optionsKeys.push(long, short);
|
optionsKeys.push(long, short);
|
||||||
|
|
||||||
@ -318,11 +318,12 @@ program
|
|||||||
.option({
|
.option({
|
||||||
key: "revert",
|
key: "revert",
|
||||||
name: (() => {
|
name: (() => {
|
||||||
const name = "revert";
|
const long = "revert";
|
||||||
|
const short = "r";
|
||||||
|
|
||||||
optionsKeys.push(name);
|
optionsKeys.push(long, short);
|
||||||
|
|
||||||
return name;
|
return { long, short };
|
||||||
})(),
|
})(),
|
||||||
description: [
|
description: [
|
||||||
"Restores a file or directory to its original auto-generated state,",
|
"Restores a file or directory to its original auto-generated state,",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
|
import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { BuildContext } from "../buildContext";
|
import type { BuildContext } from "./buildContext";
|
||||||
|
|
||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
@ -45,12 +45,16 @@ export type BuildContext = {
|
|||||||
environmentVariables: { name: string; default: string }[];
|
environmentVariables: { name: string; default: string }[];
|
||||||
themeSrcDirPath: string;
|
themeSrcDirPath: string;
|
||||||
implementedThemeTypes: {
|
implementedThemeTypes: {
|
||||||
login: { isImplemented: boolean };
|
login:
|
||||||
email: { isImplemented: boolean };
|
| { isImplemented: true }
|
||||||
|
| { isImplemented: false; isImplemented_native: boolean };
|
||||||
|
email: { isImplemented: false; isImplemented_native: boolean };
|
||||||
account:
|
account:
|
||||||
| { isImplemented: false }
|
| { isImplemented: false; isImplemented_native: boolean }
|
||||||
| { isImplemented: true; type: "Single-Page" | "Multi-Page" };
|
| { isImplemented: true; type: "Single-Page" | "Multi-Page" };
|
||||||
admin: { isImplemented: boolean };
|
admin:
|
||||||
|
| { isImplemented: true }
|
||||||
|
| { isImplemented: false; isImplemented_native: boolean };
|
||||||
};
|
};
|
||||||
packageJsonFilePath: string;
|
packageJsonFilePath: string;
|
||||||
bundler: "vite" | "webpack";
|
bundler: "vite" | "webpack";
|
||||||
@ -434,27 +438,68 @@ export function getBuildContext(params: {
|
|||||||
assert<Equals<typeof bundler, never>>(false);
|
assert<Equals<typeof bundler, never>>(false);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const implementedThemeTypes: BuildContext["implementedThemeTypes"] = {
|
const implementedThemeTypes: BuildContext["implementedThemeTypes"] = (() => {
|
||||||
login: {
|
const getIsNative = (dirPath: string) =>
|
||||||
isImplemented: fs.existsSync(pathJoin(themeSrcDirPath, "login"))
|
fs.existsSync(pathJoin(dirPath, "theme.properties"));
|
||||||
},
|
|
||||||
email: {
|
|
||||||
isImplemented: fs.existsSync(pathJoin(themeSrcDirPath, "email"))
|
|
||||||
},
|
|
||||||
account: (() => {
|
|
||||||
if (buildOptions.accountThemeImplementation === "none") {
|
|
||||||
return { isImplemented: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isImplemented: true,
|
login: (() => {
|
||||||
type: buildOptions.accountThemeImplementation
|
const dirPath = pathJoin(themeSrcDirPath, "login");
|
||||||
};
|
|
||||||
})(),
|
if (!fs.existsSync(dirPath)) {
|
||||||
admin: {
|
return { isImplemented: false, isImplemented_native: false };
|
||||||
isImplemented: fs.existsSync(pathJoin(themeSrcDirPath, "admin"))
|
}
|
||||||
}
|
|
||||||
};
|
if (getIsNative(dirPath)) {
|
||||||
|
return { isImplemented: false, isImplemented_native: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isImplemented: true };
|
||||||
|
})(),
|
||||||
|
email: (() => {
|
||||||
|
const dirPath = pathJoin(themeSrcDirPath, "email");
|
||||||
|
|
||||||
|
if (!fs.existsSync(dirPath) || !getIsNative(dirPath)) {
|
||||||
|
return { isImplemented: false, isImplemented_native: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isImplemented: false, isImplemented_native: true };
|
||||||
|
})(),
|
||||||
|
account: (() => {
|
||||||
|
const dirPath = pathJoin(themeSrcDirPath, "account");
|
||||||
|
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
return { isImplemented: false, isImplemented_native: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getIsNative(dirPath)) {
|
||||||
|
return { isImplemented: false, isImplemented_native: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildOptions.accountThemeImplementation === "none") {
|
||||||
|
return { isImplemented: false, isImplemented_native: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isImplemented: true,
|
||||||
|
type: buildOptions.accountThemeImplementation
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
admin: (() => {
|
||||||
|
const dirPath = pathJoin(themeSrcDirPath, "admin");
|
||||||
|
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
return { isImplemented: false, isImplemented_native: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getIsNative(dirPath)) {
|
||||||
|
return { isImplemented: false, isImplemented_native: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isImplemented: true };
|
||||||
|
})()
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
implementedThemeTypes.account.isImplemented &&
|
implementedThemeTypes.account.isImplemented &&
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
|
import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
|
||||||
import type { BuildContext } from "../buildContext";
|
import type { BuildContext } from "./buildContext";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { assert, is, type Equals } from "tsafe/assert";
|
import { assert, is, type Equals } from "tsafe/assert";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
@ -7,8 +7,8 @@ import {
|
|||||||
addSyncExtensionsToPostinstallScript,
|
addSyncExtensionsToPostinstallScript,
|
||||||
type BuildContextLike as BuildContextLike_addSyncExtensionsToPostinstallScript
|
type BuildContextLike as BuildContextLike_addSyncExtensionsToPostinstallScript
|
||||||
} from "./addSyncExtensionsToPostinstallScript";
|
} from "./addSyncExtensionsToPostinstallScript";
|
||||||
import { getIsPrettierAvailable, runPrettier } from "../../tools/runPrettier";
|
import { getIsPrettierAvailable, runPrettier } from "../tools/runPrettier";
|
||||||
import { npmInstall } from "../../tools/npmInstall";
|
import { npmInstall } from "../tools/npmInstall";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
@ -1 +0,0 @@
|
|||||||
export * from "./initializeSpa";
|
|
@ -1,40 +0,0 @@
|
|||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
|
||||||
import type { ThemeType } from "./constants";
|
|
||||||
import * as fs from "fs";
|
|
||||||
|
|
||||||
export type MetaInfKeycloakTheme = {
|
|
||||||
themes: { name: string; types: (ThemeType | "email")[] }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function writeMetaInfKeycloakThemes(params: {
|
|
||||||
resourcesDirPath: string;
|
|
||||||
getNewMetaInfKeycloakTheme: (params: {
|
|
||||||
metaInfKeycloakTheme: MetaInfKeycloakTheme | undefined;
|
|
||||||
}) => MetaInfKeycloakTheme;
|
|
||||||
}) {
|
|
||||||
const { resourcesDirPath, getNewMetaInfKeycloakTheme } = params;
|
|
||||||
|
|
||||||
const filePath = pathJoin(resourcesDirPath, "META-INF", "keycloak-themes.json");
|
|
||||||
|
|
||||||
const currentMetaInfKeycloakTheme = !fs.existsSync(filePath)
|
|
||||||
? undefined
|
|
||||||
: (JSON.parse(
|
|
||||||
fs.readFileSync(filePath).toString("utf8")
|
|
||||||
) as MetaInfKeycloakTheme);
|
|
||||||
|
|
||||||
const newMetaInfKeycloakThemes = getNewMetaInfKeycloakTheme({
|
|
||||||
metaInfKeycloakTheme: currentMetaInfKeycloakTheme
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
const dirPath = pathDirname(filePath);
|
|
||||||
if (!fs.existsSync(dirPath)) {
|
|
||||||
fs.mkdirSync(dirPath, { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
filePath,
|
|
||||||
Buffer.from(JSON.stringify(newMetaInfKeycloakThemes, null, 2), "utf8")
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,28 +1,20 @@
|
|||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { ParsedRealmJson } from "./ParsedRealmJson";
|
import type { ParsedRealmJson } from "./ParsedRealmJson";
|
||||||
import { getDefaultConfig } from "./defaultConfig";
|
import { getDefaultConfig } from "./defaultConfig";
|
||||||
import type { BuildContext } from "../../shared/buildContext";
|
import { TEST_APP_URL, type ThemeType, THEME_TYPES } from "../../shared/constants";
|
||||||
import { objectKeys } from "tsafe/objectKeys";
|
|
||||||
import { TEST_APP_URL } from "../../shared/constants";
|
|
||||||
import { sameFactory } from "evt/tools/inDepth/same";
|
import { sameFactory } from "evt/tools/inDepth/same";
|
||||||
|
|
||||||
export type BuildContextLike = {
|
|
||||||
themeNames: BuildContext["themeNames"];
|
|
||||||
implementedThemeTypes: BuildContext["implementedThemeTypes"];
|
|
||||||
};
|
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>;
|
|
||||||
|
|
||||||
export function prepareRealmConfig(params: {
|
export function prepareRealmConfig(params: {
|
||||||
parsedRealmJson: ParsedRealmJson;
|
parsedRealmJson: ParsedRealmJson;
|
||||||
keycloakMajorVersionNumber: number;
|
keycloakMajorVersionNumber: number;
|
||||||
buildContext: BuildContextLike;
|
parsedKeycloakThemesJsonEntry: { name: string; types: (ThemeType | "email")[] };
|
||||||
}): {
|
}): {
|
||||||
realmName: string;
|
realmName: string;
|
||||||
clientName: string;
|
clientName: string;
|
||||||
username: string;
|
username: string;
|
||||||
} {
|
} {
|
||||||
const { parsedRealmJson, keycloakMajorVersionNumber, buildContext } = params;
|
const { parsedRealmJson, keycloakMajorVersionNumber, parsedKeycloakThemesJsonEntry } =
|
||||||
|
params;
|
||||||
|
|
||||||
const { username } = addOrEditTestUser({
|
const { username } = addOrEditTestUser({
|
||||||
parsedRealmJson,
|
parsedRealmJson,
|
||||||
@ -38,8 +30,7 @@ export function prepareRealmConfig(params: {
|
|||||||
|
|
||||||
enableCustomThemes({
|
enableCustomThemes({
|
||||||
parsedRealmJson,
|
parsedRealmJson,
|
||||||
themeName: buildContext.themeNames[0],
|
parsedKeycloakThemesJsonEntry
|
||||||
implementedThemeTypes: buildContext.implementedThemeTypes
|
|
||||||
});
|
});
|
||||||
|
|
||||||
enable_custom_events_listeners: {
|
enable_custom_events_listeners: {
|
||||||
@ -63,17 +54,15 @@ export function prepareRealmConfig(params: {
|
|||||||
|
|
||||||
function enableCustomThemes(params: {
|
function enableCustomThemes(params: {
|
||||||
parsedRealmJson: ParsedRealmJson;
|
parsedRealmJson: ParsedRealmJson;
|
||||||
themeName: string;
|
parsedKeycloakThemesJsonEntry: { name: string; types: (ThemeType | "email")[] };
|
||||||
implementedThemeTypes: BuildContextLike["implementedThemeTypes"];
|
|
||||||
}) {
|
}) {
|
||||||
const { parsedRealmJson, themeName, implementedThemeTypes } = params;
|
const { parsedRealmJson, parsedKeycloakThemesJsonEntry } = params;
|
||||||
|
|
||||||
for (const themeType of objectKeys(implementedThemeTypes)) {
|
for (const themeType of [...THEME_TYPES, "email"] as const) {
|
||||||
if (!implementedThemeTypes[themeType].isImplemented) {
|
parsedRealmJson[`${themeType}Theme` as const] =
|
||||||
continue;
|
!parsedKeycloakThemesJsonEntry.types.includes(themeType)
|
||||||
}
|
? ""
|
||||||
|
: parsedKeycloakThemesJsonEntry.name;
|
||||||
parsedRealmJson[`${themeType}Theme` as const] = themeName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +101,6 @@ function addOrEditTestUser(params: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
newUser.username = defaultUser_default.username;
|
newUser.username = defaultUser_default.username;
|
||||||
newUser.email = defaultUser_default.email;
|
|
||||||
|
|
||||||
delete_existing_password_credential_if_any: {
|
delete_existing_password_credential_if_any: {
|
||||||
const i = newUser.credentials.findIndex(
|
const i = newUser.credentials.findIndex(
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import type { BuildContext } from "../../shared/buildContext";
|
import type { BuildContext } from "../../shared/buildContext";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { getDefaultConfig } from "./defaultConfig";
|
import { getDefaultConfig } from "./defaultConfig";
|
||||||
import {
|
import { prepareRealmConfig } from "./prepareRealmConfig";
|
||||||
prepareRealmConfig,
|
|
||||||
type BuildContextLike as BuildContextLike_prepareRealmConfig
|
|
||||||
} from "./prepareRealmConfig";
|
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import {
|
import {
|
||||||
join as pathJoin,
|
join as pathJoin,
|
||||||
@ -24,18 +21,19 @@ import {
|
|||||||
} from "./dumpContainerConfig";
|
} from "./dumpContainerConfig";
|
||||||
import * as runExclusive from "run-exclusive";
|
import * as runExclusive from "run-exclusive";
|
||||||
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
|
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
|
||||||
|
import type { ThemeType } from "../../shared/constants";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
||||||
export type BuildContextLike = BuildContextLike_dumpContainerConfig &
|
export type BuildContextLike = BuildContextLike_dumpContainerConfig & {
|
||||||
BuildContextLike_prepareRealmConfig & {
|
projectDirPath: string;
|
||||||
projectDirPath: string;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>;
|
assert<BuildContext extends BuildContextLike ? true : false>;
|
||||||
|
|
||||||
export async function getRealmConfig(params: {
|
export async function getRealmConfig(params: {
|
||||||
keycloakMajorVersionNumber: number;
|
keycloakMajorVersionNumber: number;
|
||||||
realmJsonFilePath_userProvided: string | undefined;
|
realmJsonFilePath_userProvided: string | undefined;
|
||||||
|
parsedKeycloakThemesJsonEntry: { name: string; types: (ThemeType | "email")[] };
|
||||||
buildContext: BuildContextLike;
|
buildContext: BuildContextLike;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
realmJsonFilePath: string;
|
realmJsonFilePath: string;
|
||||||
@ -44,8 +42,12 @@ export async function getRealmConfig(params: {
|
|||||||
username: string;
|
username: string;
|
||||||
onRealmConfigChange: () => Promise<void>;
|
onRealmConfigChange: () => Promise<void>;
|
||||||
}> {
|
}> {
|
||||||
const { keycloakMajorVersionNumber, realmJsonFilePath_userProvided, buildContext } =
|
const {
|
||||||
params;
|
keycloakMajorVersionNumber,
|
||||||
|
realmJsonFilePath_userProvided,
|
||||||
|
parsedKeycloakThemesJsonEntry,
|
||||||
|
buildContext
|
||||||
|
} = params;
|
||||||
|
|
||||||
const realmJsonFilePath = pathJoin(
|
const realmJsonFilePath = pathJoin(
|
||||||
buildContext.projectDirPath,
|
buildContext.projectDirPath,
|
||||||
@ -71,8 +73,8 @@ export async function getRealmConfig(params: {
|
|||||||
|
|
||||||
const { clientName, realmName, username } = prepareRealmConfig({
|
const { clientName, realmName, username } = prepareRealmConfig({
|
||||||
parsedRealmJson,
|
parsedRealmJson,
|
||||||
buildContext,
|
keycloakMajorVersionNumber,
|
||||||
keycloakMajorVersionNumber
|
parsedKeycloakThemesJsonEntry
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,8 @@ import {
|
|||||||
CONTAINER_NAME,
|
CONTAINER_NAME,
|
||||||
KEYCLOAKIFY_SPA_DEV_SERVER_PORT,
|
KEYCLOAKIFY_SPA_DEV_SERVER_PORT,
|
||||||
KEYCLOAKIFY_LOGIN_JAR_BASENAME,
|
KEYCLOAKIFY_LOGIN_JAR_BASENAME,
|
||||||
TEST_APP_URL
|
TEST_APP_URL,
|
||||||
|
ThemeType
|
||||||
} from "../shared/constants";
|
} from "../shared/constants";
|
||||||
import { SemVer } from "../tools/SemVer";
|
import { SemVer } from "../tools/SemVer";
|
||||||
import { assert, type Equals } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
@ -34,6 +35,7 @@ import { startViteDevServer } from "./startViteDevServer";
|
|||||||
import { getSupportedKeycloakMajorVersions } from "./realmConfig/defaultConfig";
|
import { getSupportedKeycloakMajorVersions } from "./realmConfig/defaultConfig";
|
||||||
import { getSupportedDockerImageTags } from "./getSupportedDockerImageTags";
|
import { getSupportedDockerImageTags } from "./getSupportedDockerImageTags";
|
||||||
import { getRealmConfig } from "./realmConfig";
|
import { getRealmConfig } from "./realmConfig";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
|
||||||
export async function command(params: {
|
export async function command(params: {
|
||||||
buildContext: BuildContext;
|
buildContext: BuildContext;
|
||||||
@ -270,32 +272,6 @@ export async function command(params: {
|
|||||||
return wrap.majorVersionNumber;
|
return wrap.majorVersionNumber;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const { clientName, onRealmConfigChange, realmJsonFilePath, realmName, username } =
|
|
||||||
await getRealmConfig({
|
|
||||||
keycloakMajorVersionNumber,
|
|
||||||
realmJsonFilePath_userProvided: await (async () => {
|
|
||||||
if (cliCommandOptions.realmJsonFilePath !== undefined) {
|
|
||||||
return getAbsoluteAndInOsFormatPath({
|
|
||||||
pathIsh: cliCommandOptions.realmJsonFilePath,
|
|
||||||
cwd: process.cwd()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildContext.startKeycloakOptions.realmJsonFilePath !== undefined) {
|
|
||||||
assert(
|
|
||||||
await existsAsync(
|
|
||||||
buildContext.startKeycloakOptions.realmJsonFilePath
|
|
||||||
),
|
|
||||||
`${pathRelative(process.cwd(), buildContext.startKeycloakOptions.realmJsonFilePath)} does not exist`
|
|
||||||
);
|
|
||||||
return buildContext.startKeycloakOptions.realmJsonFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
})(),
|
|
||||||
buildContext
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const { isAppBuildSuccess } = await appBuild({
|
const { isAppBuildSuccess } = await appBuild({
|
||||||
buildContext
|
buildContext
|
||||||
@ -376,10 +352,24 @@ export async function command(params: {
|
|||||||
))
|
))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let parsedKeycloakThemesJson = id<
|
||||||
|
{ themes: { name: string; types: (ThemeType | "email")[] }[] } | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
async function extractThemeResourcesFromJar() {
|
async function extractThemeResourcesFromJar() {
|
||||||
await extractArchive({
|
await extractArchive({
|
||||||
archiveFilePath: jarFilePath,
|
archiveFilePath: jarFilePath,
|
||||||
onArchiveFile: async ({ relativeFilePathInArchive, writeFile }) => {
|
onArchiveFile: async ({ relativeFilePathInArchive, writeFile, readFile }) => {
|
||||||
|
if (
|
||||||
|
relativeFilePathInArchive ===
|
||||||
|
pathJoin("META-INF", "keycloak-themes.json") &&
|
||||||
|
parsedKeycloakThemesJson === undefined
|
||||||
|
) {
|
||||||
|
parsedKeycloakThemesJson = JSON.parse(
|
||||||
|
(await readFile()).toString("utf8")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isInside({ dirPath: "theme", filePath: relativeFilePathInArchive })) {
|
if (isInside({ dirPath: "theme", filePath: relativeFilePathInArchive })) {
|
||||||
await writeFile({
|
await writeFile({
|
||||||
filePath: pathJoin(
|
filePath: pathJoin(
|
||||||
@ -401,6 +391,43 @@ export async function command(params: {
|
|||||||
|
|
||||||
await extractThemeResourcesFromJar();
|
await extractThemeResourcesFromJar();
|
||||||
|
|
||||||
|
assert(parsedKeycloakThemesJson !== undefined);
|
||||||
|
|
||||||
|
const { clientName, onRealmConfigChange, realmJsonFilePath, realmName, username } =
|
||||||
|
await getRealmConfig({
|
||||||
|
keycloakMajorVersionNumber,
|
||||||
|
parsedKeycloakThemesJsonEntry: (() => {
|
||||||
|
const entry = parsedKeycloakThemesJson.themes.find(
|
||||||
|
({ name }) => name === buildContext.themeNames[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(entry !== undefined);
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
})(),
|
||||||
|
realmJsonFilePath_userProvided: await (async () => {
|
||||||
|
if (cliCommandOptions.realmJsonFilePath !== undefined) {
|
||||||
|
return getAbsoluteAndInOsFormatPath({
|
||||||
|
pathIsh: cliCommandOptions.realmJsonFilePath,
|
||||||
|
cwd: process.cwd()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildContext.startKeycloakOptions.realmJsonFilePath !== undefined) {
|
||||||
|
assert(
|
||||||
|
await existsAsync(
|
||||||
|
buildContext.startKeycloakOptions.realmJsonFilePath
|
||||||
|
),
|
||||||
|
`${pathRelative(process.cwd(), buildContext.startKeycloakOptions.realmJsonFilePath)} does not exist`
|
||||||
|
);
|
||||||
|
return buildContext.startKeycloakOptions.realmJsonFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
})(),
|
||||||
|
buildContext
|
||||||
|
});
|
||||||
|
|
||||||
const jarFilePath_cacheDir = pathJoin(
|
const jarFilePath_cacheDir = pathJoin(
|
||||||
buildContext.cacheDirPath,
|
buildContext.cacheDirPath,
|
||||||
pathBasename(jarFilePath)
|
pathBasename(jarFilePath)
|
||||||
|
@ -99,12 +99,31 @@ function addCommentToSourceCode(params: {
|
|||||||
return toResult(commentLines.map(line => `# ${line}`).join("\n"));
|
return toResult(commentLines.map(line => `# ${line}`).join("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileRelativePath.endsWith(".ftl")) {
|
||||||
|
const comment = [`<#--`, ...commentLines.map(line => ` ${line}`), `-->`].join(
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sourceCode.trim().startsWith("<#ftl")) {
|
||||||
|
const [first, ...rest] = sourceCode.split(">");
|
||||||
|
|
||||||
|
const last = rest.join(">");
|
||||||
|
|
||||||
|
return [`${first}>`, comment, last].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return toResult(comment);
|
||||||
|
}
|
||||||
|
|
||||||
if (fileRelativePath.endsWith(".html") || fileRelativePath.endsWith(".svg")) {
|
if (fileRelativePath.endsWith(".html") || fileRelativePath.endsWith(".svg")) {
|
||||||
const comment = [
|
const comment = [
|
||||||
`<!--`,
|
`<!--`,
|
||||||
...commentLines.map(
|
...commentLines.map(
|
||||||
line =>
|
line =>
|
||||||
` ${line.replace("--path", "-p").replace("Before modifying", "Before modifying or replacing")}`
|
` ${line
|
||||||
|
.replace("--path", "-t")
|
||||||
|
.replace("--revert", "-r")
|
||||||
|
.replace("Before modifying", "Before modifying or replacing")}`
|
||||||
),
|
),
|
||||||
`-->`
|
`-->`
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
@ -155,8 +155,9 @@ export function keycloakify(params: keycloakify.Params) {
|
|||||||
{
|
{
|
||||||
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
|
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
|
||||||
const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
|
const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
|
||||||
|
const isSvelteFile = id.endsWith(".svelte");
|
||||||
|
|
||||||
if (!isTypeScriptFile && !isJavascriptFile) {
|
if (!isTypeScriptFile && !isJavascriptFile && !isSvelteFile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user