Compare commits

..

15 Commits

Author SHA1 Message Date
947efe8d63 Bump version 2025-03-16 00:49:08 +01:00
64189bf8fe #815 2025-03-16 00:48:50 +01:00
400c630418 Bump version 2025-03-13 22:03:16 +01:00
402360b436 #814 https://github.com/keycloak/keycloak/issues/38029 2025-03-13 22:03:02 +01:00
9f001f1521 Bump version 2025-03-13 13:32:23 +01:00
368e3a32c5 #772 2025-03-13 13:32:08 +01:00
002e3d4b3d Bump version 2025-03-12 19:22:58 +01:00
f94f9b51c9 Fix Vitest VSCode extention 2025-03-12 19:22:32 +01:00
055b15bd46 Bump version 2025-03-12 00:53:05 +01:00
0e70b0b0de https://github.com/keycloak/keycloak/issues/38029 2025-03-12 00:52:50 +01:00
8faf9a3eed Bump version 2025-02-27 12:21:32 +01:00
075d9f9de5 #802 2025-02-27 12:21:32 +01:00
840079be32 Merge pull request #797 from keycloakify/all-contributors/add-bacongobbler
docs: add bacongobbler as a contributor for doc
2025-02-24 19:43:18 +01:00
50ae962f09 docs: update .all-contributorsrc [skip ci] 2025-02-24 18:43:07 +00:00
61aa1f9896 docs: update README.md [skip ci] 2025-02-24 18:43:06 +00:00
17 changed files with 176 additions and 25 deletions

View File

@ -336,6 +336,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "bacongobbler",
"name": "Matthew Fisher",
"avatar_url": "https://avatars.githubusercontent.com/u/1360539?v=4",
"profile": "https://blog.bacongobbler.com",
"contributions": [
"doc"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@ -170,6 +170,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr> </tr>
<tr> <tr>
<td align="center" valign="top" width="14.28%"><a href="http://t.me/AAT_L"><img src="https://avatars.githubusercontent.com/u/118743608?v=4?s=100" width="100px;" alt="Lesha"/><br /><sub><b>Lesha</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=EternalSide" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="http://t.me/AAT_L"><img src="https://avatars.githubusercontent.com/u/118743608?v=4?s=100" width="100px;" alt="Lesha"/><br /><sub><b>Lesha</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=EternalSide" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://blog.bacongobbler.com"><img src="https://avatars.githubusercontent.com/u/1360539?v=4?s=100" width="100px;" alt="Matthew Fisher"/><br /><sub><b>Matthew Fisher</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=bacongobbler" title="Documentation">📖</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,6 +1,6 @@
{ {
"name": "keycloakify", "name": "keycloakify",
"version": "11.8.16", "version": "11.8.22",
"description": "Framework to create custom Keycloak UIs", "description": "Framework to create custom Keycloak UIs",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -20,7 +20,7 @@ import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_dele
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 } = await maybeDelegateCommandToCustomHandler({
commandName: "add-story", commandName: "add-story",
buildContext buildContext
}); });

View File

@ -11,7 +11,7 @@ import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath";
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 } = await maybeDelegateCommandToCustomHandler({
commandName: "copy-keycloak-resources-to-public", commandName: "copy-keycloak-resources-to-public",
buildContext buildContext
}); });

View File

@ -22,7 +22,7 @@ import { runPrettier, getIsPrettierAvailable } from "./tools/runPrettier";
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 } = await maybeDelegateCommandToCustomHandler({
commandName: "eject-page", commandName: "eject-page",
buildContext buildContext
}); });

View File

@ -12,7 +12,7 @@ import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath"
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 } = await maybeDelegateCommandToCustomHandler({
commandName: "initialize-account-theme", commandName: "initialize-account-theme",
buildContext buildContext
}); });

View File

@ -7,7 +7,7 @@ import { command as updateKcGenCommand } from "./update-kc-gen";
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 } = await maybeDelegateCommandToCustomHandler({
commandName: "initialize-admin-theme", commandName: "initialize-admin-theme",
buildContext buildContext
}); });

View File

@ -17,8 +17,8 @@ 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 } = await maybeDelegateCommandToCustomHandler({
commandName: "initialize-account-theme", commandName: "initialize-email-theme",
buildContext buildContext
}); });

View File

@ -190,7 +190,7 @@ function decodeHtmlEntities(htmlStr){
<#-- https://github.com/keycloakify/keycloakify/discussions/406#discussioncomment-7514787 --> <#-- https://github.com/keycloakify/keycloakify/discussions/406#discussioncomment-7514787 -->
key == "loginAction" && key == "loginAction" &&
areSamePath(path, ["url"]) && areSamePath(path, ["url"]) &&
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(xKeycloakify.pageId) && ["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl", "login-oauth2-device-verify-user-code.ftl", "frontchannel-logout.ftl"]?seq_contains(xKeycloakify.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 -->

View File

@ -13,13 +13,15 @@ import * as fs from "fs";
assert<Equals<ApiVersion, "v1">>(); assert<Equals<ApiVersion, "v1">>();
export function maybeDelegateCommandToCustomHandler(params: { export async function maybeDelegateCommandToCustomHandler(params: {
commandName: CommandName; commandName: CommandName;
buildContext: BuildContext; buildContext: BuildContext;
}): { hasBeenHandled: boolean } { }): Promise<{ hasBeenHandled: boolean }> {
const { commandName, buildContext } = params; const { commandName, buildContext } = params;
const nodeModulesBinDirPath = getNodeModulesBinDirPath(); const nodeModulesBinDirPath = await getNodeModulesBinDirPath({
packageJsonFilePath: buildContext.packageJsonFilePath
});
if (!fs.readdirSync(nodeModulesBinDirPath).includes(BIN_NAME)) { if (!fs.readdirSync(nodeModulesBinDirPath).includes(BIN_NAME)) {
return { hasBeenHandled: false }; return { hasBeenHandled: false };

View File

@ -45,12 +45,12 @@ export async function getExtensionModuleFileSourceCodeReadyToBeCopied(params: {
`This file has been claimed for ownership from ${extensionModuleName} version ${extensionModuleVersion}.`, `This file has been claimed for ownership from ${extensionModuleName} version ${extensionModuleVersion}.`,
`To relinquish ownership and restore this file to its original content, run the following command:`, `To relinquish ownership and restore this file to its original content, run the following command:`,
``, ``,
`$ npx keycloakify own --path '${path}' --revert` `$ npx keycloakify own --path "${path}" --revert`
] ]
: [ : [
`WARNING: Before modifying this file, run the following command:`, `WARNING: Before modifying this file, run the following command:`,
``, ``,
`$ npx keycloakify own --path '${path}'`, `$ npx keycloakify own --path "${path}"`,
``, ``,
`This file is provided by ${extensionModuleName} version ${extensionModuleVersion}.`, `This file is provided by ${extensionModuleName} version ${extensionModuleVersion}.`,
`It was copied into your repository by the postinstall script: \`keycloakify sync-extensions\`.` `It was copied into your repository by the postinstall script: \`keycloakify sync-extensions\`.`

View File

@ -14,6 +14,8 @@ export function getAbsoluteAndInOsFormatPath(params: {
let pathOut = pathIsh; let pathOut = pathIsh;
pathOut = pathOut.replace(/^['"]/, "").replace(/['"]$/, "");
pathOut = pathOut.replace(/\//g, pathSep); pathOut = pathOut.replace(/\//g, pathSep);
if (pathOut.startsWith("~")) { if (pathOut.startsWith("~")) {

View File

@ -1,10 +1,29 @@
import { sep as pathSep } from "path"; import { sep as pathSep, dirname as pathDirname, join as pathJoin } from "path";
import { getThisCodebaseRootDirPath } from "./getThisCodebaseRootDirPath";
import { getInstalledModuleDirPath } from "./getInstalledModuleDirPath";
import { existsAsync } from "./fs.existsAsync";
import { z } from "zod";
import * as fs from "fs/promises";
import { assert, is, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
let cache: string | undefined = undefined; let cache_bestEffort: string | undefined = undefined;
export function getNodeModulesBinDirPath() { /** NOTE: Careful, this function can fail when the binary
if (cache !== undefined) { * Used is not in the node_modules directory of the project
return cache; * (for example when running tests with vscode extension we'll get
* '/Users/dylan/.vscode/extensions/vitest.explorer-1.16.0/dist/worker.js'
*
* instead of
* '/Users/joseph/.nvm/versions/node/v22.12.0/bin/node'
* or
* '/Users/joseph/github/keycloakify-starter/node_modules/.bin/vite'
*
* as the value of process.argv[1]
*/
function getNodeModulesBinDirPath_bestEffort() {
if (cache_bestEffort !== undefined) {
return cache_bestEffort;
} }
const binPath = process.argv[1]; const binPath = process.argv[1];
@ -30,9 +49,122 @@ export function getNodeModulesBinDirPath() {
segments.unshift(segment); segments.unshift(segment);
} }
if (!foundNodeModules) {
throw new Error(`Could not find node_modules in path ${binPath}`);
}
const nodeModulesBinDirPath = segments.join(pathSep); const nodeModulesBinDirPath = segments.join(pathSep);
cache = nodeModulesBinDirPath; cache_bestEffort = nodeModulesBinDirPath;
return nodeModulesBinDirPath; return nodeModulesBinDirPath;
} }
let cache_withPackageJsonFileDirPath:
| { packageJsonFilePath: string; nodeModulesBinDirPath: string }
| undefined = undefined;
async function getNodeModulesBinDirPath_withPackageJsonFileDirPath(params: {
packageJsonFilePath: string;
}): Promise<string> {
const { packageJsonFilePath } = params;
use_cache: {
if (cache_withPackageJsonFileDirPath === undefined) {
break use_cache;
}
if (
cache_withPackageJsonFileDirPath.packageJsonFilePath !== packageJsonFilePath
) {
cache_withPackageJsonFileDirPath = undefined;
break use_cache;
}
return cache_withPackageJsonFileDirPath.nodeModulesBinDirPath;
}
// [...]node_modules/keycloakify
const installedModuleDirPath = await getInstalledModuleDirPath({
// Here it will always be "keycloakify" but since we are in tools/ we make something generic
moduleName: await (async () => {
type ParsedPackageJson = {
name: string;
};
const zParsedPackageJson = (() => {
type TargetType = ParsedPackageJson;
const zTargetType = z.object({
name: z.string()
});
assert<Equals<z.infer<typeof zTargetType>, TargetType>>;
return id<z.ZodType<TargetType>>(zTargetType);
})();
const parsedPackageJson = JSON.parse(
(
await fs.readFile(
pathJoin(getThisCodebaseRootDirPath(), "package.json")
)
).toString("utf8")
);
zParsedPackageJson.parse(parsedPackageJson);
assert(is<ParsedPackageJson>(parsedPackageJson));
return parsedPackageJson.name;
})(),
packageJsonDirPath: pathDirname(packageJsonFilePath)
});
const segments = installedModuleDirPath.split(pathSep);
while (true) {
const segment = segments.pop();
if (segment === undefined) {
throw new Error(
`Could not find .bin directory relative to ${packageJsonFilePath}`
);
}
if (segment !== "node_modules") {
continue;
}
const candidate = pathJoin(segments.join(pathSep), segment, ".bin");
if (!(await existsAsync(candidate))) {
continue;
}
cache_withPackageJsonFileDirPath = {
packageJsonFilePath,
nodeModulesBinDirPath: candidate
};
break;
}
return cache_withPackageJsonFileDirPath.nodeModulesBinDirPath;
}
export function getNodeModulesBinDirPath(params: {
packageJsonFilePath: string;
}): Promise<string>;
export function getNodeModulesBinDirPath(params: {
packageJsonFilePath: undefined;
}): string;
export function getNodeModulesBinDirPath(params: {
packageJsonFilePath: string | undefined;
}): string | Promise<string> {
const { packageJsonFilePath } = params ?? {};
return packageJsonFilePath === undefined
? getNodeModulesBinDirPath_bestEffort()
: getNodeModulesBinDirPath_withPackageJsonFileDirPath({ packageJsonFilePath });
}

View File

@ -15,7 +15,9 @@ export async function getIsPrettierAvailable(): Promise<boolean> {
return getIsPrettierAvailable.cache; return getIsPrettierAvailable.cache;
} }
const nodeModulesBinDirPath = getNodeModulesBinDirPath(); const nodeModulesBinDirPath = getNodeModulesBinDirPath({
packageJsonFilePath: undefined
});
const prettierBinPath = pathJoin(nodeModulesBinDirPath, "prettier"); const prettierBinPath = pathJoin(nodeModulesBinDirPath, "prettier");
@ -51,7 +53,7 @@ export async function getPrettier(): Promise<PrettierAndConfigHash> {
// We make sure to only do that when linking, otherwise we import properly. // We make sure to only do that when linking, otherwise we import properly.
if (readThisNpmPackageVersion().startsWith("0.0.0")) { if (readThisNpmPackageVersion().startsWith("0.0.0")) {
eval( eval(
`${symToStr({ prettier })} = require("${pathResolve(pathJoin(getNodeModulesBinDirPath(), "..", "prettier"))}")` `${symToStr({ prettier })} = require("${pathResolve(pathJoin(getNodeModulesBinDirPath({ packageJsonFilePath: undefined }), "..", "prettier"))}")`
); );
assert(!is<undefined>(prettier)); assert(!is<undefined>(prettier));
@ -64,7 +66,7 @@ export async function getPrettier(): Promise<PrettierAndConfigHash> {
const configHash = await (async () => { const configHash = await (async () => {
const configFilePath = await prettier.resolveConfigFile( const configFilePath = await prettier.resolveConfigFile(
pathJoin(getNodeModulesBinDirPath(), "..") pathJoin(getNodeModulesBinDirPath({ packageJsonFilePath: undefined }), "..")
); );
if (configFilePath === null) { if (configFilePath === null) {

View File

@ -19,7 +19,7 @@ export async function command(params: { buildContext: BuildContext }) {
await command({ buildContext }); await command({ buildContext });
} }
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({
commandName: "update-kc-gen", commandName: "update-kc-gen",
buildContext buildContext
}); });

View File

@ -90,7 +90,6 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps<
{advancedMsg(attribute.annotations.inputHelperTextAfter)} {advancedMsg(attribute.annotations.inputHelperTextAfter)}
</div> </div>
)} )}
{AfterField !== undefined && ( {AfterField !== undefined && (
<AfterField <AfterField
attribute={attribute} attribute={attribute}
@ -107,6 +106,10 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps<
</Fragment> </Fragment>
); );
})} })}
{/* See: https://github.com/keycloak/keycloak/issues/38029 */}
{kcContext.locale !== undefined && formFieldStates.find(x => x.attribute.name === "locale") === undefined && (
<input type="hidden" name="locale" value={i18n.currentLanguage.languageTag} />
)}
</> </>
); );
} }