Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
9246a54c68 | |||
eaf9a5373a | |||
5b261c9916 | |||
73d155b3bb | |||
1f8b245499 |
29
README.md
29
README.md
@ -39,12 +39,6 @@
|
||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||
</p>
|
||||
|
||||
> Whether or not React is your preferred framework, Keycloakify
|
||||
> offers a solid option for building Keycloak themes.
|
||||
> It's not just a convenient way to create a Keycloak theme
|
||||
> when using React; it's a well-regarded solution that many
|
||||
> developers appreciate.
|
||||
|
||||
## Sponsor 👼
|
||||
|
||||
We are exclusively sponsored by [Cloud IAM](https://www.cloud-iam.com), a French company offering Keycloak as a service.
|
||||
@ -55,20 +49,12 @@ Their dedicated support helps us continue the development and maintenance of thi
|
||||
- Simplify and secure your Keycloak Identity and Access Management. Keycloak as a Service.
|
||||
- Custom theme building for your brand using Keycloakify.
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<i>Checkout <a href="https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github">Cloud IAM</a> and use promo code <code>keycloakify5</code></i>
|
||||
<a href="https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github">
|
||||
<img src="https://user-images.githubusercontent.com/6702424/233476937-e37b1dc6-5a1c-4a0d-ba02-61c2ce62ffb6.png" alt="Cloud IAM Logo" width="350"/>
|
||||
</a>
|
||||
<br/>
|
||||
<i>Use promo code <code>keycloakify5</code> </i>
|
||||
<br/>
|
||||
<i>5% of your annual subscription will be donated to us, and you'll get 5% off too.</i>
|
||||
</p>
|
||||
@ -122,9 +108,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
You are now expected to have a `"prepare": "copy-keycloak-resources-to-public",` in your package.json scripts.
|
||||
This script will create `public/keycloak-assets` when you run `yarn install` (If you are using another package manager
|
||||
like `pnpm` makes sure that `"prepare"` is actually ran.)
|
||||
[See the updated starter](https://github.com/keycloakify/keycloakify-starter/blob/94532fcf10bf8b19e0873be8575fd28a8958a806/package.json#L11). `public/keycloak-assets` shouldn't be tracked by GIT and is automatically ignored.
|
||||
[See the updated starter](https://github.com/keycloakify/keycloakify-starter/blob/94532fcf10bf8b19e0873be8575fd28a8958a806/package.json#L11).
|
||||
`public/keycloak-assets` shouldn't be tracked by GIT and is automatically ignored.
|
||||
|
||||
## 7.7
|
||||
## 7.7
|
||||
|
||||
- Better storybook support, see [the starter project](https://github.com/keycloakify/keycloakify-starter).
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "7.11.7",
|
||||
"version": "7.11.1",
|
||||
"description": "Create Keycloak themes using React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { AccountThemePageId, ThemeType } from "keycloakify/bin/keycloakify/generateFtl";
|
||||
import type { AccountThemePageId } from "keycloakify/bin/keycloakify/generateFtl";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
|
||||
@ -7,7 +7,6 @@ export type KcContext = KcContext.Password | KcContext.Account;
|
||||
export declare namespace KcContext {
|
||||
export type Common = {
|
||||
keycloakifyVersion: string;
|
||||
themeType: "account";
|
||||
locale?: {
|
||||
supported: {
|
||||
url: string;
|
||||
@ -85,15 +84,4 @@ export declare namespace KcContext {
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
type Got = KcContext["pageId"];
|
||||
type Expected = AccountThemePageId;
|
||||
|
||||
type OnlyInGot = Exclude<Got, Expected>;
|
||||
type OnlyInExpected = Exclude<Expected, Got>;
|
||||
|
||||
assert<Equals<OnlyInGot, never>>();
|
||||
assert<Equals<OnlyInExpected, never>>();
|
||||
}
|
||||
|
||||
assert<KcContext["themeType"] extends ThemeType ? true : false>();
|
||||
assert<Equals<KcContext["pageId"], AccountThemePageId>>();
|
||||
|
@ -7,6 +7,8 @@ import { pathBasename } from "keycloakify/tools/pathBasename";
|
||||
import { resourcesCommonDirPathRelativeToPublicDir } from "keycloakify/bin/mockTestingResourcesPath";
|
||||
import { symToStr } from "tsafe/symToStr";
|
||||
import { kcContextMocks, kcContextCommonMock } from "keycloakify/account/kcContext/kcContextMocks";
|
||||
import { id } from "tsafe/id";
|
||||
import { accountThemePageIds } from "keycloakify/bin/keycloakify/generateFtl/pageId";
|
||||
|
||||
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
@ -85,7 +87,7 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
||||
return { "kcContext": undefined as any };
|
||||
}
|
||||
|
||||
if (realKcContext.themeType !== "account") {
|
||||
if (id<readonly string[]>(accountThemePageIds).indexOf(realKcContext.pageId) < 0 && !("account" in realKcContext)) {
|
||||
return { "kcContext": undefined as any };
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
|
||||
|
||||
export const kcContextCommonMock: KcContext.Common = {
|
||||
"keycloakifyVersion": "0.0.0",
|
||||
"themeType": "account",
|
||||
"url": {
|
||||
"resourcesPath": pathJoin(PUBLIC_URL, resourcesDirPathRelativeToPublicDir),
|
||||
"resourcesCommonPath": pathJoin(PUBLIC_URL, resourcesCommonDirPathRelativeToPublicDir),
|
||||
|
@ -121,7 +121,6 @@
|
||||
|
||||
out["keycloakifyVersion"] = "KEYCLOAKIFY_VERSION_xEdKd3xEdr";
|
||||
out["themeVersion"] = "KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx";
|
||||
out["themeType"] = "KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr";
|
||||
out["pageId"] = "${pageId}";
|
||||
|
||||
return out;
|
||||
|
@ -55,9 +55,8 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
cssGlobalsToDefine: Record<string, string>;
|
||||
buildOptions: BuildOptionsLike;
|
||||
keycloakifyVersion: string;
|
||||
themeType: ThemeType;
|
||||
}) {
|
||||
const { cssGlobalsToDefine, indexHtmlCode, buildOptions, keycloakifyVersion, themeType } = params;
|
||||
const { cssGlobalsToDefine, indexHtmlCode, buildOptions, keycloakifyVersion } = params;
|
||||
|
||||
const $ = cheerio.load(indexHtmlCode);
|
||||
|
||||
@ -133,8 +132,7 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
buildOptions.customUserAttributes.length === 0 ? "" : ", " + buildOptions.customUserAttributes.map(name => `"${name}"`).join(", ")
|
||||
)
|
||||
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion)
|
||||
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion)
|
||||
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType),
|
||||
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion),
|
||||
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
||||
"<#if scripts??>",
|
||||
" <#list scripts as script>",
|
||||
|
@ -141,8 +141,7 @@ export async function generateTheme(params: {
|
||||
"indexHtmlCode": fs.readFileSync(pathJoin(reactAppBuildDirPath, "index.html")).toString("utf8"),
|
||||
"cssGlobalsToDefine": allCssGlobalsToDefine,
|
||||
buildOptions,
|
||||
keycloakifyVersion,
|
||||
themeType
|
||||
keycloakifyVersion
|
||||
});
|
||||
|
||||
return generateFtlFilesCode;
|
||||
|
@ -58,7 +58,7 @@ export async function main() {
|
||||
case "keycloakify":
|
||||
logger.log("🫶 Let keycloakify do its thang");
|
||||
await jar({
|
||||
"rootPath": buildOptions.keycloakifyBuildDirPath,
|
||||
"rootPath": pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources"),
|
||||
"version": buildOptions.themeVersion,
|
||||
"groupId": buildOptions.groupId,
|
||||
"artifactId": buildOptions.artifactId,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { dirname, relative, sep, join } from "path";
|
||||
import { dirname, relative, sep } from "path";
|
||||
import { createWriteStream } from "fs";
|
||||
|
||||
import walk from "./walk";
|
||||
@ -48,12 +48,8 @@ export async function jarStream({ groupId, artifactId, version, asyncPathGenerat
|
||||
for await (const entry of asyncPathGeneratorFn()) {
|
||||
if ("buffer" in entry) {
|
||||
zipFile.addBuffer(entry.buffer, entry.zipPath);
|
||||
} else if ("fsPath" in entry) {
|
||||
if (entry.fsPath.endsWith(sep)) {
|
||||
zipFile.addEmptyDirectory(entry.zipPath);
|
||||
} else {
|
||||
zipFile.addFile(entry.fsPath, entry.zipPath);
|
||||
}
|
||||
} else if ("fsPath" in entry && !entry.fsPath.endsWith(sep)) {
|
||||
zipFile.addFile(entry.fsPath, entry.zipPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,23 +65,15 @@ export async function jarStream({ groupId, artifactId, version, asyncPathGenerat
|
||||
* Create a jar archive, using the resources found at `rootPath` (a directory) and write the
|
||||
* archive to `targetPath` (a file). Use `groupId`, `artifactId` and `version` to define
|
||||
* the contents of the pom.properties file which is going to be added to the archive.
|
||||
* The root directory is expectedto have a conventional maven/gradle folder structure with a
|
||||
* single `pom.xml` file at the root and a `src/main/resources` directory containing all
|
||||
* application resources.
|
||||
*/
|
||||
export default async function jar({ groupId, artifactId, version, rootPath, targetPath }: JarArgs) {
|
||||
await mkdir(dirname(targetPath), { recursive: true });
|
||||
|
||||
const asyncPathGeneratorFn = async function* (): ZipEntryGenerator {
|
||||
const resourcesPath = join(rootPath, "src", "main", "resources");
|
||||
for await (const fsPath of walk(resourcesPath)) {
|
||||
const zipPath = relative(resourcesPath, fsPath).split(sep).join("/");
|
||||
for await (const fsPath of walk(rootPath)) {
|
||||
const zipPath = relative(rootPath, fsPath).split(sep).join("/");
|
||||
yield { fsPath, zipPath };
|
||||
}
|
||||
yield {
|
||||
fsPath: join(rootPath, "pom.xml"),
|
||||
zipPath: `META-INF/maven/${groupId}/${artifactId}/pom.xml`
|
||||
};
|
||||
};
|
||||
|
||||
const zipFile = await jarStream({ groupId, artifactId, version, asyncPathGeneratorFn });
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { LoginThemePageId, ThemeType } from "keycloakify/bin/keycloakify/generateFtl";
|
||||
import type { LoginThemePageId } from "keycloakify/bin/keycloakify/generateFtl";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
import type { MessageKey } from "../i18n/i18n";
|
||||
@ -38,7 +38,6 @@ export type KcContext =
|
||||
export declare namespace KcContext {
|
||||
export type Common = {
|
||||
keycloakifyVersion: string;
|
||||
themeType: "login";
|
||||
url: {
|
||||
loginAction: string;
|
||||
resourcesPath: string;
|
||||
@ -565,5 +564,3 @@ export declare namespace Validators {
|
||||
assert<Equals<OnlyInGot, never>>();
|
||||
assert<Equals<OnlyInExpected, never>>();
|
||||
}
|
||||
|
||||
assert<KcContext["themeType"] extends ThemeType ? true : false>();
|
||||
|
@ -11,6 +11,7 @@ import { pathJoin } from "keycloakify/bin/tools/pathJoin";
|
||||
import { pathBasename } from "keycloakify/tools/pathBasename";
|
||||
import { resourcesCommonDirPathRelativeToPublicDir } from "keycloakify/bin/mockTestingResourcesPath";
|
||||
import { symToStr } from "tsafe/symToStr";
|
||||
import { loginThemePageIds } from "keycloakify/bin/keycloakify/generateFtl/pageId";
|
||||
|
||||
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
@ -144,7 +145,7 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
||||
return { "kcContext": undefined as any };
|
||||
}
|
||||
|
||||
if (realKcContext.themeType !== "login") {
|
||||
if (id<readonly string[]>(loginThemePageIds).indexOf(realKcContext.pageId) < 0 && !("login" in realKcContext)) {
|
||||
return { "kcContext": undefined as any };
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,6 @@ const attributesByName = Object.fromEntries(attributes.map(attribute => [attribu
|
||||
|
||||
export const kcContextCommonMock: KcContext.Common = {
|
||||
"keycloakifyVersion": "0.0.0",
|
||||
"themeType": "login",
|
||||
"url": {
|
||||
"loginAction": "#",
|
||||
"resourcesPath": pathJoin(PUBLIC_URL, resourcesDirPathRelativeToPublicDir),
|
||||
|
@ -11,7 +11,7 @@ export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { p
|
||||
|
||||
return (
|
||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("emailVerifyTitle")}>
|
||||
<p className="instruction">{msg("emailVerifyInstruction1", user?.email ?? "")}</p>
|
||||
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
|
||||
<p className="instruction">
|
||||
{msg("emailVerifyInstruction2")}
|
||||
<br />
|
||||
|
@ -20,7 +20,7 @@ export default function RegisterUserProfile(props: PageProps<Extract<KcContext,
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
|
||||
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
||||
|
||||
return (
|
||||
<Template
|
||||
@ -32,7 +32,7 @@ export default function RegisterUserProfile(props: PageProps<Extract<KcContext,
|
||||
<form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post">
|
||||
<UserProfileFormFields
|
||||
kcContext={kcContext}
|
||||
onIsFormSubmittableValueChange={setIsFormSubmittable}
|
||||
onIsFormSubmittableValueChange={setIsFomSubmittable}
|
||||
i18n={i18n}
|
||||
getClassName={getClassName}
|
||||
/>
|
||||
@ -62,7 +62,7 @@ export default function RegisterUserProfile(props: PageProps<Extract<KcContext,
|
||||
)}
|
||||
type="submit"
|
||||
value={msgStr("doRegister")}
|
||||
disabled={!isFormSubmittable}
|
||||
disabled={!isFomSubmittable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import { fromBuffer, Entry, ZipFile } from "yauzl";
|
||||
import { it, describe, assert, afterAll } from "vitest";
|
||||
import { Readable } from "stream";
|
||||
import { tmpdir } from "os";
|
||||
import { mkdtemp, cp, mkdir, rm, writeFile } from "fs/promises";
|
||||
import { mkdtemp, cp, mkdir, rm } from "fs/promises";
|
||||
import path from "path";
|
||||
import { createReadStream } from "fs";
|
||||
import walk from "keycloakify/bin/tools/walk";
|
||||
@ -98,17 +98,12 @@ describe("jar", () => {
|
||||
|
||||
it("creates a jar from _real_ files without error", async () => {
|
||||
const tmp = await mkdtemp(path.join(tmpdir(), "kc-jar-test-"));
|
||||
|
||||
tmpDirs.push(tmp);
|
||||
|
||||
const rootPath = path.join(tmp, "root");
|
||||
const resourcesPath = path.join(tmp, "root", "src", "main", "resources");
|
||||
const rootPath = path.join(tmp, "src");
|
||||
const targetPath = path.join(tmp, "jar.jar");
|
||||
await mkdir(rootPath);
|
||||
|
||||
await mkdir(resourcesPath, { recursive: true });
|
||||
await writeFile(path.join(rootPath, "pom.xml"), "foo", "utf-8");
|
||||
|
||||
await cp(path.dirname(__dirname), resourcesPath, { recursive: true });
|
||||
await cp(path.dirname(__dirname), rootPath, { recursive: true });
|
||||
|
||||
await jar({ ...coords, rootPath, targetPath });
|
||||
|
||||
@ -119,12 +114,11 @@ describe("jar", () => {
|
||||
|
||||
assert.isOk(entries.has("META-INF/MANIFEST.MF"));
|
||||
assert.isOk(entries.has("META-INF/maven/someGroupId/someArtifactId/pom.properties"));
|
||||
assert.isOk(entries.has("META-INF/maven/someGroupId/someArtifactId/pom.xml"));
|
||||
|
||||
for await (const fsPath of walk(resourcesPath)) {
|
||||
for await (const fsPath of walk(rootPath)) {
|
||||
if (!fsPath.endsWith(path.sep)) {
|
||||
const rel = path.relative(resourcesPath, fsPath).replace(path.sep === "/" ? /\//g : /\\/g, "/");
|
||||
assert.isOk(zipPaths.includes(rel), `missing '${rel}' (${rel}, '${zipPaths.join("', '")}')`);
|
||||
const rel = path.relative(rootPath, fsPath).replace(path.sep === "/" ? /\//g : /\\/g, "/");
|
||||
assert.isOk(zipPaths.includes(rel), `missing ${rel} (${rel}, ${zipPaths.join(", ")})`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user