Compare commits
47 Commits
v9.4.0-rc.
...
v9.5.5
Author | SHA1 | Date | |
---|---|---|---|
fd7d2bb9bf | |||
63c40fd816 | |||
0569fa5e58 | |||
ba74952e0b | |||
20c28f785a | |||
e9b249ddc7 | |||
604bb484a3 | |||
010c93793a | |||
dc1d4a66f4 | |||
8ef633d7ef | |||
2176d33da1 | |||
5b794e2d22 | |||
ccd75d56c5 | |||
b700066833 | |||
546ee006d3 | |||
7f333a6a36 | |||
ae757ee371 | |||
69936750d5 | |||
442bfa4ed6 | |||
2a88e6802f | |||
bcc8b12e13 | |||
9b974505eb | |||
29b1c26771 | |||
02db20d98b | |||
757354df7d | |||
563518cf46 | |||
7c42d9082a | |||
040284af71 | |||
34f64184d9 | |||
b9abd74156 | |||
a1c0bfda6c | |||
617dcef09d | |||
d9c406800a | |||
54b869def1 | |||
d80a583979 | |||
99bfd7379b | |||
5f257382fa | |||
e3e6847c82 | |||
4ee0823acb | |||
d466123b1c | |||
21cbc14a48 | |||
b2f2c3e386 | |||
b03340ed10 | |||
5b563d8e9b | |||
2790487fc7 | |||
ad5a368065 | |||
7c0a631a9a |
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
@ -3,9 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- v5
|
|
||||||
- v6
|
|
||||||
- v7
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
23
README.md
23
README.md
@ -14,9 +14,6 @@
|
|||||||
<a href="https://github.com/garronej/keycloakify/blob/main/LICENSE">
|
<a href="https://github.com/garronej/keycloakify/blob/main/LICENSE">
|
||||||
<img src="https://img.shields.io/npm/l/keycloakify">
|
<img src="https://img.shields.io/npm/l/keycloakify">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/keycloakify/keycloakify/blob/729503fe31a155a823f46dd66ad4ff34ca274e0a/tsconfig.json#L14">
|
|
||||||
<img src="https://camo.githubusercontent.com/0f9fcc0ac1b8617ad4989364f60f78b2d6b32985ad6a508f215f14d8f897b8d3/68747470733a2f2f62616467656e2e6e65742f62616467652f547970655363726970742f7374726963742532302546302539462539322541412f626c7565">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/thomasdarimont/awesome-keycloak">
|
<a href="https://github.com/thomasdarimont/awesome-keycloak">
|
||||||
<img src="https://awesome.re/mentioned-badge.svg"/>
|
<img src="https://awesome.re/mentioned-badge.svg"/>
|
||||||
</a>
|
</a>
|
||||||
@ -43,11 +40,7 @@
|
|||||||
|
|
||||||
Keycloakify is fully compatible with Keycloak 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, [~~22~~](https://github.com/keycloakify/keycloakify/issues/389#issuecomment-1822509763), **23** [and up](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)!
|
Keycloakify is fully compatible with Keycloak 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, [~~22~~](https://github.com/keycloakify/keycloakify/issues/389#issuecomment-1822509763), **23** [and up](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)!
|
||||||
|
|
||||||
> 📣 I've observed that a few people have unstarred the project recently.
|
## Sponsor
|
||||||
> I'm concerned that I may have inadvertently introduced some misinformation in the documentation, leading to frustration.
|
|
||||||
> If you're having a negative experience, [please let me know so I can resolve the issue](https://github.com/keycloakify/keycloakify/discussions/507).
|
|
||||||
|
|
||||||
## Sponsor 👼
|
|
||||||
|
|
||||||
We are exclusively sponsored by [Cloud IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github), a French company offering Keycloak as a service.
|
We are exclusively sponsored by [Cloud IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github), a French company offering Keycloak as a service.
|
||||||
Their dedicated support helps us continue the development and maintenance of this project.
|
Their dedicated support helps us continue the development and maintenance of this project.
|
||||||
@ -130,6 +123,20 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
|
|
||||||
# Changelog highlights
|
# Changelog highlights
|
||||||
|
|
||||||
|
## 9.5
|
||||||
|
|
||||||
|
- Post build hook: You can now apply custom transformation to your theme files. [Learn more](https://docs.keycloakify.dev/build-options#postbuild-hook).
|
||||||
|
- You can now specify your option in the Keycloakify's Vite plugin instead in the package.json. [See example](https://docs.keycloakify.dev/build-options#themename).
|
||||||
|
|
||||||
|
## 9.4
|
||||||
|
|
||||||
|
**Vite Support! 🎉**
|
||||||
|
|
||||||
|
- [The starter is now a Vite project](https://github.com/keycloakify/keycloakify-starter).
|
||||||
|
The Webpack based starter is accessible [here](https://github.com/keycloakify/keycloakify-starter-cra).
|
||||||
|
- CRA (Webpack) remains supported for the forseable future.
|
||||||
|
- If you have a CRA Keycloakify theme that you wish to migrate to Vite checkout [this migration guide](https://docs.keycloakify.dev/migration-guides/cra-greater-than-vite).
|
||||||
|
|
||||||
## 9.0
|
## 9.0
|
||||||
|
|
||||||
Bring back support for account themes in Keycloak v23 and up! [See issue](https://github.com/keycloakify/keycloakify/issues/389).
|
Bring back support for account themes in Keycloak v23 and up! [See issue](https://github.com/keycloakify/keycloakify/issues/389).
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "9.4.0-rc.12",
|
"version": "9.5.5",
|
||||||
"description": "Create Keycloak themes using React",
|
"description": "Create Keycloak themes using React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -125,6 +125,7 @@
|
|||||||
"tsafe": "^1.6.0",
|
"tsafe": "^1.6.0",
|
||||||
"yauzl": "^2.10.0",
|
"yauzl": "^2.10.0",
|
||||||
"yazl": "^2.5.1",
|
"yazl": "^2.5.1",
|
||||||
"zod": "^3.17.10"
|
"zod": "^3.17.10",
|
||||||
|
"magic-string": "^0.30.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,7 @@ async function main() {
|
|||||||
|
|
||||||
const generatedFileHeader = [
|
const generatedFileHeader = [
|
||||||
`//This code was automatically generated by running ${pathRelative(thisCodebaseRootDirPath, __filename)}`,
|
`//This code was automatically generated by running ${pathRelative(thisCodebaseRootDirPath, __filename)}`,
|
||||||
"//PLEASE DO NOT EDIT MANUALLY",
|
"//PLEASE DO NOT EDIT MANUALLY"
|
||||||
""
|
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
languages.forEach(language => {
|
languages.forEach(language => {
|
||||||
@ -95,6 +94,7 @@ async function main() {
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
[
|
[
|
||||||
generatedFileHeader,
|
generatedFileHeader,
|
||||||
|
"",
|
||||||
"/* spell-checker: disable */",
|
"/* spell-checker: disable */",
|
||||||
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`,
|
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`,
|
||||||
"",
|
"",
|
||||||
@ -113,10 +113,15 @@ async function main() {
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
[
|
[
|
||||||
generatedFileHeader,
|
generatedFileHeader,
|
||||||
|
`import * as en from "./en";`,
|
||||||
|
"",
|
||||||
"export async function getMessages(currentLanguageTag: string) {",
|
"export async function getMessages(currentLanguageTag: string) {",
|
||||||
" const { default: messages } = await (() => {",
|
" const { default: messages } = await (() => {",
|
||||||
" switch (currentLanguageTag) {",
|
" switch (currentLanguageTag) {",
|
||||||
...languages.map(language => ` case "${language}": return import("./${language}");`),
|
` case "en": return en;`,
|
||||||
|
...languages
|
||||||
|
.filter(language => language !== "en")
|
||||||
|
.map(language => ` case "${language}": return import("./${language}");`),
|
||||||
' default: return { "default": {} };',
|
' default: return { "default": {} };',
|
||||||
" }",
|
" }",
|
||||||
" })();",
|
" })();",
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import type { I18n } from "keycloakify/account/i18n";
|
import type { I18n } from "keycloakify/account/i18n";
|
||||||
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
|
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
|
import type { KcContext } from "keycloakify/account/kcContext";
|
||||||
|
|
||||||
export type PageProps<KcContext, I18nExtended extends I18n> = {
|
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
|
||||||
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
||||||
kcContext: KcContext;
|
kcContext: NarowedKcContext;
|
||||||
i18n: I18nExtended;
|
i18n: I18nExtended;
|
||||||
doUseDefaultCss: boolean;
|
doUseDefaultCss: boolean;
|
||||||
classes?: Partial<Record<ClassKey, string>>;
|
classes?: Partial<Record<ClassKey, string>>;
|
||||||
|
@ -10,3 +10,5 @@ export const retrocompatPostfix = "_retrocompat";
|
|||||||
export const accountV1ThemeName = "account-v1";
|
export const accountV1ThemeName = "account-v1";
|
||||||
|
|
||||||
export type ThemeType = (typeof themeTypes)[number];
|
export type ThemeType = (typeof themeTypes)[number];
|
||||||
|
|
||||||
|
export const keycloakifyBuildOptionsForPostPostBuildScriptEnvName = "KEYCLOAKIFY_BUILD_OPTIONS_POST_POST_BUILD_SCRIPT";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { downloadKeycloakStaticResources, type BuildOptionsLike } from "./keycloakify/generateTheme/downloadKeycloakStaticResources";
|
import { downloadKeycloakStaticResources, type BuildOptionsLike } from "./keycloakify/generateTheme/downloadKeycloakStaticResources";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
import { readBuildOptions } from "./keycloakify/buildOptions";
|
import { readBuildOptions } from "./keycloakify/buildOptions";
|
||||||
import { themeTypes, keycloak_resources, lastKeycloakVersionWithAccountV1 } from "./constants";
|
import { themeTypes, keycloak_resources, lastKeycloakVersionWithAccountV1 } from "./constants";
|
||||||
import { readThisNpmProjectVersion } from "./tools/readThisNpmProjectVersion";
|
import { readThisNpmProjectVersion } from "./tools/readThisNpmProjectVersion";
|
||||||
@ -19,6 +19,7 @@ export async function copyKeycloakResourcesToPublic(params: { processArgv: strin
|
|||||||
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
|
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
|
||||||
|
|
||||||
const { keycloakifyBuildinfoRaw } = generateKeycloakifyBuildinfoRaw({
|
const { keycloakifyBuildinfoRaw } = generateKeycloakifyBuildinfoRaw({
|
||||||
|
destDirPath,
|
||||||
"keycloakifyVersion": readThisNpmProjectVersion(),
|
"keycloakifyVersion": readThisNpmProjectVersion(),
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
@ -72,12 +73,13 @@ export async function copyKeycloakResourcesToPublic(params: { processArgv: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateKeycloakifyBuildinfoRaw(params: {
|
export function generateKeycloakifyBuildinfoRaw(params: {
|
||||||
|
destDirPath: string;
|
||||||
keycloakifyVersion: string;
|
keycloakifyVersion: string;
|
||||||
buildOptions: BuildOptionsLike & {
|
buildOptions: BuildOptionsLike & {
|
||||||
loginThemeResourcesFromKeycloakVersion: string;
|
loginThemeResourcesFromKeycloakVersion: string;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
const { keycloakifyVersion, buildOptions } = params;
|
const { destDirPath, keycloakifyVersion, buildOptions } = params;
|
||||||
|
|
||||||
const { cacheDirPath, npmWorkspaceRootDirPath, loginThemeResourcesFromKeycloakVersion, ...rest } = buildOptions;
|
const { cacheDirPath, npmWorkspaceRootDirPath, loginThemeResourcesFromKeycloakVersion, ...rest } = buildOptions;
|
||||||
|
|
||||||
@ -88,8 +90,8 @@ export function generateKeycloakifyBuildinfoRaw(params: {
|
|||||||
keycloakifyVersion,
|
keycloakifyVersion,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
loginThemeResourcesFromKeycloakVersion,
|
loginThemeResourcesFromKeycloakVersion,
|
||||||
cacheDirPath,
|
"cacheDirPath": pathRelative(destDirPath, cacheDirPath),
|
||||||
npmWorkspaceRootDirPath
|
"npmWorkspaceRootDirPath": pathRelative(destDirPath, npmWorkspaceRootDirPath)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
|
25
src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts
Normal file
25
src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export type UserProvidedBuildOptions = {
|
||||||
|
extraThemeProperties?: string[];
|
||||||
|
artifactId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
doCreateJar?: boolean;
|
||||||
|
loginThemeResourcesFromKeycloakVersion?: string;
|
||||||
|
reactAppBuildDirPath?: string;
|
||||||
|
keycloakifyBuildDirPath?: string;
|
||||||
|
themeName?: string | string[];
|
||||||
|
doBuildRetrocompatAccountTheme?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const zUserProvidedBuildOptions = z.object({
|
||||||
|
"extraThemeProperties": z.array(z.string()).optional(),
|
||||||
|
"artifactId": z.string().optional(),
|
||||||
|
"groupId": z.string().optional(),
|
||||||
|
"doCreateJar": z.boolean().optional(),
|
||||||
|
"loginThemeResourcesFromKeycloakVersion": z.string().optional(),
|
||||||
|
"reactAppBuildDirPath": z.string().optional(),
|
||||||
|
"keycloakifyBuildDirPath": z.string().optional(),
|
||||||
|
"themeName": z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
"doBuildRetrocompatAccountTheme": z.boolean().optional()
|
||||||
|
});
|
@ -47,10 +47,15 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
|||||||
throw new Error("Keycloakify's Vite plugin output not found");
|
throw new Error("Keycloakify's Vite plugin output not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedPackageJson = readParsedPackageJson({ reactAppRootDirPath });
|
const { keycloakify: userProvidedBuildOptionsFromPackageJson, ...parsedPackageJson } = readParsedPackageJson({ reactAppRootDirPath });
|
||||||
|
|
||||||
|
const userProvidedBuildOptions = {
|
||||||
|
...userProvidedBuildOptionsFromPackageJson,
|
||||||
|
...resolvedViteConfig?.userProvidedBuildOptions
|
||||||
|
};
|
||||||
|
|
||||||
const themeNames = (() => {
|
const themeNames = (() => {
|
||||||
if (parsedPackageJson.keycloakify?.themeName === undefined) {
|
if (userProvidedBuildOptions.themeName === undefined) {
|
||||||
return [
|
return [
|
||||||
parsedPackageJson.name
|
parsedPackageJson.name
|
||||||
.replace(/^@(.*)/, "$1")
|
.replace(/^@(.*)/, "$1")
|
||||||
@ -59,11 +64,11 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof parsedPackageJson.keycloakify.themeName === "string") {
|
if (typeof userProvidedBuildOptions.themeName === "string") {
|
||||||
return [parsedPackageJson.keycloakify.themeName];
|
return [userProvidedBuildOptions.themeName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedPackageJson.keycloakify.themeName;
|
return userProvidedBuildOptions.themeName;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const reactAppBuildDirPath = (() => {
|
const reactAppBuildDirPath = (() => {
|
||||||
@ -72,9 +77,9 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
|||||||
break webpack;
|
break webpack;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedPackageJson.keycloakify?.reactAppBuildDirPath !== undefined) {
|
if (userProvidedBuildOptions.reactAppBuildDirPath !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
"pathIsh": parsedPackageJson.keycloakify?.reactAppBuildDirPath,
|
"pathIsh": userProvidedBuildOptions.reactAppBuildDirPath,
|
||||||
"cwd": reactAppRootDirPath
|
"cwd": reactAppRootDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -94,13 +99,13 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
|||||||
"isSilent": typeof argv["silent"] === "boolean" ? argv["silent"] : false,
|
"isSilent": typeof argv["silent"] === "boolean" ? argv["silent"] : false,
|
||||||
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
|
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
|
||||||
themeNames,
|
themeNames,
|
||||||
"extraThemeProperties": parsedPackageJson.keycloakify?.extraThemeProperties,
|
"extraThemeProperties": userProvidedBuildOptions.extraThemeProperties,
|
||||||
"groupId": (() => {
|
"groupId": (() => {
|
||||||
const fallbackGroupId = `${themeNames[0]}.keycloak`;
|
const fallbackGroupId = `${themeNames[0]}.keycloak`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
process.env.KEYCLOAKIFY_GROUP_ID ??
|
process.env.KEYCLOAKIFY_GROUP_ID ??
|
||||||
parsedPackageJson.keycloakify?.groupId ??
|
userProvidedBuildOptions.groupId ??
|
||||||
(parsedPackageJson.homepage === undefined
|
(parsedPackageJson.homepage === undefined
|
||||||
? fallbackGroupId
|
? fallbackGroupId
|
||||||
: urlParse(parsedPackageJson.homepage)
|
: urlParse(parsedPackageJson.homepage)
|
||||||
@ -110,20 +115,23 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
|||||||
.join(".") ?? fallbackGroupId) + ".keycloak"
|
.join(".") ?? fallbackGroupId) + ".keycloak"
|
||||||
);
|
);
|
||||||
})(),
|
})(),
|
||||||
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? parsedPackageJson.keycloakify?.artifactId ?? `${themeNames[0]}-keycloak-theme`,
|
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? userProvidedBuildOptions.artifactId ?? `${themeNames[0]}-keycloak-theme`,
|
||||||
"doCreateJar": parsedPackageJson.keycloakify?.doCreateJar ?? true,
|
"doCreateJar": userProvidedBuildOptions.doCreateJar ?? true,
|
||||||
"loginThemeResourcesFromKeycloakVersion": parsedPackageJson.keycloakify?.loginThemeResourcesFromKeycloakVersion ?? "11.0.3",
|
"loginThemeResourcesFromKeycloakVersion": userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "11.0.3",
|
||||||
reactAppRootDirPath,
|
reactAppRootDirPath,
|
||||||
reactAppBuildDirPath,
|
reactAppBuildDirPath,
|
||||||
"keycloakifyBuildDirPath": (() => {
|
"keycloakifyBuildDirPath": (() => {
|
||||||
if (parsedPackageJson.keycloakify?.keycloakifyBuildDirPath !== undefined) {
|
if (userProvidedBuildOptions.keycloakifyBuildDirPath !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
"pathIsh": parsedPackageJson.keycloakify?.keycloakifyBuildDirPath,
|
"pathIsh": userProvidedBuildOptions.keycloakifyBuildDirPath,
|
||||||
"cwd": reactAppRootDirPath
|
"cwd": reactAppRootDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedViteConfig?.buildDir === undefined ? "build_keycloak" : `${resolvedViteConfig.buildDir}_keycloak`;
|
return pathJoin(
|
||||||
|
reactAppRootDirPath,
|
||||||
|
resolvedViteConfig?.buildDir === undefined ? "build_keycloak" : `${resolvedViteConfig.buildDir}_keycloak`
|
||||||
|
);
|
||||||
})(),
|
})(),
|
||||||
"publicDirPath": (() => {
|
"publicDirPath": (() => {
|
||||||
webpack: {
|
webpack: {
|
||||||
@ -179,7 +187,7 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
|||||||
|
|
||||||
return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir);
|
return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir);
|
||||||
})(),
|
})(),
|
||||||
"doBuildRetrocompatAccountTheme": parsedPackageJson.keycloakify?.doBuildRetrocompatAccountTheme ?? true,
|
"doBuildRetrocompatAccountTheme": userProvidedBuildOptions.doBuildRetrocompatAccountTheme ?? true,
|
||||||
npmWorkspaceRootDirPath
|
npmWorkspaceRootDirPath
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,41 +3,20 @@ import { assert } from "tsafe";
|
|||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
|
import { type UserProvidedBuildOptions, zUserProvidedBuildOptions } from "./UserProvidedBuildOptions";
|
||||||
|
|
||||||
export type ParsedPackageJson = {
|
export type ParsedPackageJson = {
|
||||||
name: string;
|
name: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
homepage?: string;
|
homepage?: string;
|
||||||
keycloakify?: {
|
keycloakify?: UserProvidedBuildOptions;
|
||||||
extraThemeProperties?: string[];
|
|
||||||
artifactId?: string;
|
|
||||||
groupId?: string;
|
|
||||||
doCreateJar?: boolean;
|
|
||||||
loginThemeResourcesFromKeycloakVersion?: string;
|
|
||||||
reactAppBuildDirPath?: string;
|
|
||||||
keycloakifyBuildDirPath?: string;
|
|
||||||
themeName?: string | string[];
|
|
||||||
doBuildRetrocompatAccountTheme?: boolean;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const zParsedPackageJson = z.object({
|
const zParsedPackageJson = z.object({
|
||||||
"name": z.string(),
|
"name": z.string(),
|
||||||
"version": z.string().optional(),
|
"version": z.string().optional(),
|
||||||
"homepage": z.string().optional(),
|
"homepage": z.string().optional(),
|
||||||
"keycloakify": z
|
"keycloakify": zUserProvidedBuildOptions.optional()
|
||||||
.object({
|
|
||||||
"extraThemeProperties": z.array(z.string()).optional(),
|
|
||||||
"artifactId": z.string().optional(),
|
|
||||||
"groupId": z.string().optional(),
|
|
||||||
"doCreateJar": z.boolean().optional(),
|
|
||||||
"loginThemeResourcesFromKeycloakVersion": z.string().optional(),
|
|
||||||
"reactAppBuildDirPath": z.string().optional(),
|
|
||||||
"keycloakifyBuildDirPath": z.string().optional(),
|
|
||||||
"themeName": z.union([z.string(), z.array(z.string())]).optional(),
|
|
||||||
"doBuildRetrocompatAccountTheme": z.boolean().optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
|
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
|
||||||
|
@ -5,19 +5,22 @@ import { z } from "zod";
|
|||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { resolvedViteConfigJsonBasename } from "../../constants";
|
import { resolvedViteConfigJsonBasename } from "../../constants";
|
||||||
import type { OptionalIfCanBeUndefined } from "../../tools/OptionalIfCanBeUndefined";
|
import type { OptionalIfCanBeUndefined } from "../../tools/OptionalIfCanBeUndefined";
|
||||||
|
import { UserProvidedBuildOptions, zUserProvidedBuildOptions } from "./UserProvidedBuildOptions";
|
||||||
|
|
||||||
export type ResolvedViteConfig = {
|
export type ResolvedViteConfig = {
|
||||||
buildDir: string;
|
buildDir: string;
|
||||||
publicDir: string;
|
publicDir: string;
|
||||||
assetsDir: string;
|
assetsDir: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
|
userProvidedBuildOptions: UserProvidedBuildOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
const zResolvedViteConfig = z.object({
|
const zResolvedViteConfig = z.object({
|
||||||
"buildDir": z.string(),
|
"buildDir": z.string(),
|
||||||
"publicDir": z.string(),
|
"publicDir": z.string(),
|
||||||
"assetsDir": z.string(),
|
"assetsDir": z.string(),
|
||||||
"urlPathname": z.string().optional()
|
"urlPathname": z.string().optional(),
|
||||||
|
"userProvidedBuildOptions": zUserProvidedBuildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -431,7 +431,7 @@
|
|||||||
<#if isHash>
|
<#if isHash>
|
||||||
|
|
||||||
<#if path?size gt 10>
|
<#if path?size gt 10>
|
||||||
<#return "ABORT: Too many recursive calls">
|
<#return "ABORT: Too many recursive calls, path: " + path?join(".")>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#local keys = "">
|
<#local keys = "">
|
||||||
@ -463,9 +463,10 @@
|
|||||||
<#-- https://github.com/keycloakify/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
|
||||||
<#-- https://github.com/keycloakify/keycloakify/issues/109#issuecomment-1134610163 -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/109#issuecomment-1134610163 -->
|
||||||
<#-- https://github.com/keycloakify/keycloakify/issues/357 -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/357 -->
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/discussions/406#discussioncomment-7514787 -->
|
||||||
key == "loginAction" &&
|
key == "loginAction" &&
|
||||||
are_same_path(path, ["url"]) &&
|
are_same_path(path, ["url"]) &&
|
||||||
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl"]?seq_contains(pageId) &&
|
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||||
!(auth?has_content && auth.showTryAnotherWayLink())
|
!(auth?has_content && auth.showTryAnotherWayLink())
|
||||||
) || (
|
) || (
|
||||||
<#-- https://github.com/keycloakify/keycloakify/issues/362 -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/362 -->
|
||||||
@ -488,24 +489,33 @@
|
|||||||
!["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key)
|
!["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key)
|
||||||
) || (
|
) || (
|
||||||
"applications.ftl" == pageId &&
|
"applications.ftl" == pageId &&
|
||||||
are_same_path(path, ["applications", "applications", "*", "client", "realm"])
|
is_subpath(path, ["applications", "applications"]) &&
|
||||||
|
(
|
||||||
|
key == "realm" ||
|
||||||
|
key == "container"
|
||||||
|
)
|
||||||
) || (
|
) || (
|
||||||
"applications.ftl" == pageId &&
|
are_same_path(path, ["user"]) &&
|
||||||
"masterAdminClient" == key
|
key == "delegateForUpdate"
|
||||||
)
|
)
|
||||||
>
|
>
|
||||||
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
<#local out_seq += ["/*If you need '" + path?join(".") + "." + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||||
<#continue>
|
<#continue>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#if pageId == "register.ftl" && key == "attemptedUsername" && are_same_path(path, ["auth"])>
|
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
||||||
|
<#if (
|
||||||
|
["register.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||||
|
key == "attemptedUsername" && are_same_path(path, ["auth"])
|
||||||
|
)>
|
||||||
<#attempt>
|
<#attempt>
|
||||||
<#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 -->
|
<#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 -->
|
||||||
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
|
||||||
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||||
|
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||||
<#continue>
|
<#continue>
|
||||||
</#if>
|
</#if>
|
||||||
<#recover>
|
<#recover>
|
||||||
|
<#local out_seq += ["/*Testing if attemptedUsername should be skipped throwed an exception */"]>
|
||||||
</#attempt>
|
</#attempt>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
@ -658,9 +668,9 @@
|
|||||||
<#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non enumerable object">
|
<#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non enumerable object">
|
||||||
|
|
||||||
</#function>
|
</#function>
|
||||||
<#function are_same_path path searchedPath>
|
<#function is_subpath path searchedPath>
|
||||||
|
|
||||||
<#if path?size != searchedPath?size>
|
<#if path?size < searchedPath?size>
|
||||||
<#return false>
|
<#return false>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
@ -668,8 +678,14 @@
|
|||||||
|
|
||||||
<#list path as property>
|
<#list path as property>
|
||||||
|
|
||||||
|
<#if i == searchedPath?size >
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#local searchedProperty=searchedPath[i]>
|
<#local searchedProperty=searchedPath[i]>
|
||||||
|
|
||||||
|
<#local i+= 1>
|
||||||
|
|
||||||
<#if searchedProperty?is_string && searchedProperty == "*">
|
<#if searchedProperty?is_string && searchedProperty == "*">
|
||||||
<#continue>
|
<#continue>
|
||||||
</#if>
|
</#if>
|
||||||
@ -686,11 +702,13 @@
|
|||||||
<#return false>
|
<#return false>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#local i+= 1>
|
|
||||||
|
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
<#return true>
|
<#return true>
|
||||||
|
|
||||||
</#function>
|
</#function>
|
||||||
|
|
||||||
|
<#function are_same_path path searchedPath>
|
||||||
|
<#return path?size == searchedPath?size && is_subpath(path, searchedPath)>
|
||||||
|
</#function>
|
||||||
</script>
|
</script>
|
@ -20,6 +20,7 @@ import { readFieldNameUsage } from "./readFieldNameUsage";
|
|||||||
import { readExtraPagesNames } from "./readExtraPageNames";
|
import { readExtraPagesNames } from "./readExtraPageNames";
|
||||||
import { generateMessageProperties } from "./generateMessageProperties";
|
import { generateMessageProperties } from "./generateMessageProperties";
|
||||||
import { bringInAccountV1 } from "./bringInAccountV1";
|
import { bringInAccountV1 } from "./bringInAccountV1";
|
||||||
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
bundler: "vite" | "webpack";
|
bundler: "vite" | "webpack";
|
||||||
@ -78,6 +79,11 @@ export async function generateTheme(params: {
|
|||||||
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
||||||
|
|
||||||
apply_replacers_and_move_to_theme_resources: {
|
apply_replacers_and_move_to_theme_resources: {
|
||||||
|
const destDirPath = pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir);
|
||||||
|
|
||||||
|
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
|
||||||
|
rmSync(destDirPath, { "recursive": true, "force": true });
|
||||||
|
|
||||||
if (themeType === "account" && implementedThemeTypes.login) {
|
if (themeType === "account" && implementedThemeTypes.login) {
|
||||||
// NOTE: We prevend doing it twice, it has been done for the login theme.
|
// NOTE: We prevend doing it twice, it has been done for the login theme.
|
||||||
|
|
||||||
@ -89,7 +95,7 @@ export async function generateTheme(params: {
|
|||||||
"resources",
|
"resources",
|
||||||
basenameOfTheKeycloakifyResourcesDir
|
basenameOfTheKeycloakifyResourcesDir
|
||||||
),
|
),
|
||||||
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir)
|
destDirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
break apply_replacers_and_move_to_theme_resources;
|
break apply_replacers_and_move_to_theme_resources;
|
||||||
@ -97,7 +103,7 @@ export async function generateTheme(params: {
|
|||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
||||||
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir),
|
destDirPath,
|
||||||
"transformSourceCode": ({ filePath, sourceCode }) => {
|
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||||
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
||||||
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
|
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
|
||||||
|
@ -9,6 +9,7 @@ import { getLogger } from "../tools/logger";
|
|||||||
import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
|
import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
|
||||||
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
||||||
import { readThisNpmProjectVersion } from "../tools/readThisNpmProjectVersion";
|
import { readThisNpmProjectVersion } from "../tools/readThisNpmProjectVersion";
|
||||||
|
import { keycloakifyBuildOptionsForPostPostBuildScriptEnvName } from "../constants";
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const buildOptions = readBuildOptions({
|
const buildOptions = readBuildOptions({
|
||||||
@ -36,9 +37,31 @@ export async function main() {
|
|||||||
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const containerKeycloakVersion = "23.0.6";
|
||||||
|
|
||||||
const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`);
|
const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`);
|
||||||
|
|
||||||
if (buildOptions.doCreateJar) {
|
generateStartKeycloakTestingContainer({
|
||||||
|
"keycloakVersion": containerKeycloakVersion,
|
||||||
|
jarFilePath,
|
||||||
|
buildOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
||||||
|
|
||||||
|
child_process.execSync("npx vite", {
|
||||||
|
"cwd": buildOptions.reactAppRootDirPath,
|
||||||
|
"env": {
|
||||||
|
...process.env,
|
||||||
|
[keycloakifyBuildOptionsForPostPostBuildScriptEnvName]: JSON.stringify(buildOptions)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
create_jar: {
|
||||||
|
if (!buildOptions.doCreateJar) {
|
||||||
|
break create_jar;
|
||||||
|
}
|
||||||
|
|
||||||
child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath });
|
child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath });
|
||||||
|
|
||||||
const jarDirPath = pathDirname(jarFilePath);
|
const jarDirPath = pathDirname(jarFilePath);
|
||||||
@ -59,14 +82,6 @@ export async function main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerKeycloakVersion = "23.0.0";
|
|
||||||
|
|
||||||
generateStartKeycloakTestingContainer({
|
|
||||||
"keycloakVersion": containerKeycloakVersion,
|
|
||||||
jarFilePath,
|
|
||||||
buildOptions
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log(
|
logger.log(
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
|
@ -1,3 +1 @@
|
|||||||
import { BASE_URL } from "./BASE_URL";
|
export const isStorybook = typeof window === "object" && Object.keys(window).find(key => key.startsWith("__STORYBOOK")) !== undefined;
|
||||||
|
|
||||||
export const isStorybook = BASE_URL.startsWith(".");
|
|
||||||
|
@ -84,7 +84,7 @@ export declare namespace KcContext {
|
|||||||
description?: string;
|
description?: string;
|
||||||
attributes: Record<string, string>;
|
attributes: Record<string, string>;
|
||||||
};
|
};
|
||||||
isAppInitiatedAction: boolean;
|
isAppInitiatedAction?: boolean;
|
||||||
messagesPerField: {
|
messagesPerField: {
|
||||||
/**
|
/**
|
||||||
* Return text if message for given field exists. Useful eg. to add css styles for fields with message.
|
* Return text if message for given field exists. Useful eg. to add css styles for fields with message.
|
||||||
@ -244,6 +244,17 @@ export declare namespace KcContext {
|
|||||||
|
|
||||||
export type Terms = Common & {
|
export type Terms = Common & {
|
||||||
pageId: "terms.ftl";
|
pageId: "terms.ftl";
|
||||||
|
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
|
||||||
|
user?: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
attributes: Record<string, string[]>;
|
||||||
|
email: string;
|
||||||
|
emailVerified: boolean;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
markedForEviction?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoginDeviceVerifyUserCode = Common & {
|
export type LoginDeviceVerifyUserCode = Common & {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import type { I18n } from "keycloakify/login/i18n";
|
import type { I18n } from "keycloakify/login/i18n";
|
||||||
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
|
import type { KcContext } from "keycloakify/account/kcContext";
|
||||||
|
|
||||||
export type PageProps<KcContext, I18nExtended extends I18n> = {
|
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
|
||||||
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
||||||
kcContext: KcContext;
|
kcContext: NarowedKcContext;
|
||||||
i18n: I18nExtended;
|
i18n: I18nExtended;
|
||||||
doUseDefaultCss: boolean;
|
doUseDefaultCss: boolean;
|
||||||
classes?: Partial<Record<ClassKey, string>>;
|
classes?: Partial<Record<ClassKey, string>>;
|
||||||
|
@ -1,28 +1,74 @@
|
|||||||
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
|
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
|
||||||
import type { Plugin } from "vite";
|
import type { Plugin } from "vite";
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { resolvedViteConfigJsonBasename, nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir, keycloak_resources } from "../bin/constants";
|
import {
|
||||||
|
resolvedViteConfigJsonBasename,
|
||||||
|
nameOfTheGlobal,
|
||||||
|
basenameOfTheKeycloakifyResourcesDir,
|
||||||
|
keycloak_resources,
|
||||||
|
keycloakifyBuildOptionsForPostPostBuildScriptEnvName
|
||||||
|
} from "../bin/constants";
|
||||||
import type { ResolvedViteConfig } from "../bin/keycloakify/buildOptions/resolvedViteConfig";
|
import type { ResolvedViteConfig } from "../bin/keycloakify/buildOptions/resolvedViteConfig";
|
||||||
import { getCacheDirPath } from "../bin/keycloakify/buildOptions/getCacheDirPath";
|
import { getCacheDirPath } from "../bin/keycloakify/buildOptions/getCacheDirPath";
|
||||||
import { replaceAll } from "../bin/tools/String.prototype.replaceAll";
|
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import { rm } from "../bin/tools/fs.rm";
|
import { rm } from "../bin/tools/fs.rm";
|
||||||
import { copyKeycloakResourcesToPublic } from "../bin/copy-keycloak-resources-to-public";
|
import { copyKeycloakResourcesToPublic } from "../bin/copy-keycloak-resources-to-public";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import type { BuildOptions } from "../bin/keycloakify/buildOptions";
|
||||||
|
import type { UserProvidedBuildOptions } from "../bin/keycloakify/buildOptions/UserProvidedBuildOptions";
|
||||||
|
import MagicString from "magic-string";
|
||||||
|
|
||||||
|
export type Params = UserProvidedBuildOptions & {
|
||||||
|
postBuild?: (buildOptions: Omit<BuildOptions, "bundler">) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function keycloakify(params?: Params) {
|
||||||
|
const { postBuild, ...userProvidedBuildOptions } = params ?? {};
|
||||||
|
|
||||||
export function keycloakify(): Plugin {
|
|
||||||
let reactAppRootDirPath: string | undefined = undefined;
|
let reactAppRootDirPath: string | undefined = undefined;
|
||||||
let urlPathname: string | undefined = undefined;
|
let urlPathname: string | undefined = undefined;
|
||||||
let buildDirPath: string | undefined = undefined;
|
let buildDirPath: string | undefined = undefined;
|
||||||
|
let command: "build" | "serve" | undefined = undefined;
|
||||||
|
let shouldGenerateSourcemap: boolean | undefined = undefined;
|
||||||
|
|
||||||
return {
|
const plugin = {
|
||||||
"name": "keycloakify",
|
"name": "keycloakify" as const,
|
||||||
"apply": "build",
|
|
||||||
"configResolved": async resolvedConfig => {
|
"configResolved": async resolvedConfig => {
|
||||||
|
shouldGenerateSourcemap = resolvedConfig.build.sourcemap !== false;
|
||||||
|
|
||||||
|
run_post_build_script: {
|
||||||
|
const buildOptionJson = process.env[keycloakifyBuildOptionsForPostPostBuildScriptEnvName];
|
||||||
|
|
||||||
|
if (buildOptionJson === undefined) {
|
||||||
|
break run_post_build_script;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postBuild === undefined) {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildOptions: BuildOptions = JSON.parse(buildOptionJson);
|
||||||
|
|
||||||
|
await postBuild(buildOptions);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
command = resolvedConfig.command;
|
||||||
|
|
||||||
reactAppRootDirPath = resolvedConfig.root;
|
reactAppRootDirPath = resolvedConfig.root;
|
||||||
urlPathname = (() => {
|
urlPathname = (() => {
|
||||||
let out = resolvedConfig.env.BASE_URL;
|
let out = resolvedConfig.env.BASE_URL;
|
||||||
|
|
||||||
|
if (out.startsWith(".") && command === "build" && resolvedConfig.envPrefix?.includes("STORYBOOK_") !== true) {
|
||||||
|
throw new Error(
|
||||||
|
[
|
||||||
|
`BASE_URL=${out} is not supported By Keycloakify. Use an absolute URL instead.`,
|
||||||
|
`If this is a problem, please open an issue at https://github.com/keycloakify/keycloakify/issues/new`
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (out === undefined) {
|
if (out === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -45,7 +91,7 @@ export function keycloakify(): Plugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!fs.existsSync(cacheDirPath)) {
|
if (!fs.existsSync(cacheDirPath)) {
|
||||||
fs.mkdirSync(cacheDirPath);
|
fs.mkdirSync(cacheDirPath, { "recursive": true });
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
@ -56,7 +102,8 @@ export function keycloakify(): Plugin {
|
|||||||
"publicDir": pathRelative(reactAppRootDirPath, resolvedConfig.publicDir),
|
"publicDir": pathRelative(reactAppRootDirPath, resolvedConfig.publicDir),
|
||||||
"assetsDir": resolvedConfig.build.assetsDir,
|
"assetsDir": resolvedConfig.build.assetsDir,
|
||||||
"buildDir": resolvedConfig.build.outDir,
|
"buildDir": resolvedConfig.build.outDir,
|
||||||
urlPathname
|
urlPathname,
|
||||||
|
userProvidedBuildOptions
|
||||||
}),
|
}),
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
@ -70,60 +117,76 @@ export function keycloakify(): Plugin {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
"transform": (code, id) => {
|
"transform": (code, id) => {
|
||||||
assert(reactAppRootDirPath !== undefined);
|
assert(command !== undefined);
|
||||||
|
assert(shouldGenerateSourcemap !== undefined);
|
||||||
|
|
||||||
let transformedCode: string | undefined = undefined;
|
if (command !== "build") {
|
||||||
|
|
||||||
replace_import_meta_env_base_url_in_source_code: {
|
|
||||||
{
|
|
||||||
const isWithinSourceDirectory = id.startsWith(pathJoin(reactAppRootDirPath, "src") + pathSep);
|
|
||||||
|
|
||||||
if (!isWithinSourceDirectory) {
|
|
||||||
break replace_import_meta_env_base_url_in_source_code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
|
|
||||||
|
|
||||||
{
|
|
||||||
const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
|
|
||||||
|
|
||||||
if (!isTypeScriptFile && !isJavascriptFile) {
|
|
||||||
break replace_import_meta_env_base_url_in_source_code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const windowToken = isJavascriptFile ? "window" : "(window as any)";
|
|
||||||
|
|
||||||
if (transformedCode === undefined) {
|
|
||||||
transformedCode = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformedCode = replaceAll(
|
|
||||||
transformedCode,
|
|
||||||
"import.meta.env.BASE_URL",
|
|
||||||
[
|
|
||||||
`(`,
|
|
||||||
`(${windowToken}.${nameOfTheGlobal} === undefined || import.meta.env.MODE === "development") ?`,
|
|
||||||
` "${urlPathname ?? "/"}" :`,
|
|
||||||
` \`\${${windowToken}.${nameOfTheGlobal}.url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/\``,
|
|
||||||
`)`
|
|
||||||
].join("")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transformedCode === undefined) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(reactAppRootDirPath !== undefined);
|
||||||
|
|
||||||
|
{
|
||||||
|
const isWithinSourceDirectory = id.startsWith(pathJoin(reactAppRootDirPath, "src") + pathSep);
|
||||||
|
|
||||||
|
if (!isWithinSourceDirectory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
|
||||||
|
const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
|
||||||
|
|
||||||
|
if (!isTypeScriptFile && !isJavascriptFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformedCode = new MagicString(code);
|
||||||
|
|
||||||
|
transformedCode.replaceAll(
|
||||||
|
/import\.meta\.env(?:(?:\.BASE_URL)|(?:\["BASE_URL"\]))/g,
|
||||||
|
[
|
||||||
|
`(`,
|
||||||
|
`(window.${nameOfTheGlobal} === undefined || import.meta.env.MODE === "development")?`,
|
||||||
|
`"${urlPathname ?? "/"}":`,
|
||||||
|
`(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/")`,
|
||||||
|
`)`
|
||||||
|
].join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!transformedCode.hasChanged()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldGenerateSourcemap) {
|
||||||
|
return transformedCode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = transformedCode.generateMap({
|
||||||
|
"source": id,
|
||||||
|
"includeContent": true,
|
||||||
|
"hires": true
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"code": transformedCode
|
"code": transformedCode.toString(),
|
||||||
|
"map": map.toString()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
"buildEnd": async () => {
|
"closeBundle": async () => {
|
||||||
|
assert(command !== undefined);
|
||||||
|
|
||||||
|
if (command !== "build") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(buildDirPath !== undefined);
|
assert(buildDirPath !== undefined);
|
||||||
|
|
||||||
await rm(pathJoin(buildDirPath, keycloak_resources), { "recursive": true, "force": true });
|
await rm(pathJoin(buildDirPath, keycloak_resources), { "recursive": true, "force": true });
|
||||||
}
|
}
|
||||||
};
|
} satisfies Plugin;
|
||||||
|
|
||||||
|
return plugin as any;
|
||||||
}
|
}
|
||||||
|
@ -1665,7 +1665,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.15":
|
||||||
version "1.4.15"
|
version "1.4.15"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||||
@ -8352,6 +8352,13 @@ lz-string@^1.4.4:
|
|||||||
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
||||||
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||||
|
|
||||||
|
magic-string@^0.30.7:
|
||||||
|
version "0.30.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.7.tgz#0cecd0527d473298679da95a2d7aeb8c64048505"
|
||||||
|
integrity sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||||
|
|
||||||
make-dir@^2.0.0, make-dir@^2.1.0:
|
make-dir@^2.0.0, make-dir@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||||
|
Reference in New Issue
Block a user