Compare commits
24 Commits
v10.0.0-rc
...
v10.0.0-rc
Author | SHA1 | Date | |
---|---|---|---|
96fc779ec8 | |||
9605e17e96 | |||
111c1675f9 | |||
d547ec3126 | |||
0ce6a7be7f | |||
89d9208f44 | |||
3e80aaf242 | |||
86c3159ded | |||
230e05abc0 | |||
ff2e6e6432 | |||
dc00be9be6 | |||
77249d8a58 | |||
b9ee0afe7f | |||
db23ab0bc2 | |||
13dc47533c | |||
0091a888bc | |||
724b585004 | |||
c0d127e4f4 | |||
951c202fd0 | |||
a578b86715 | |||
b6b384854e | |||
dac937060d | |||
c628183773 | |||
eaacaa6966 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "10.0.0-rc.114",
|
"version": "10.0.0-rc.121",
|
||||||
"description": "Create Keycloak themes using React",
|
"description": "Create Keycloak themes using React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { join } from "path";
|
import { copyKeycloakResourcesToStorybookStaticDir } from "./copyKeycloakResourcesToStorybookStaticDir";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
run("yarn build");
|
run("yarn build");
|
||||||
|
|
||||||
run(`node ${join("dist", "bin", "main.js")} copy-keycloak-resources-to-public`, {
|
await copyKeycloakResourcesToStorybookStaticDir();
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
PUBLIC_DIR_PATH: join(".storybook", "static")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
run("npx build-storybook");
|
run("npx build-storybook");
|
||||||
|
})();
|
||||||
|
|
||||||
function run(command: string, options?: { env?: NodeJS.ProcessEnv }) {
|
function run(command: string, options?: { env?: NodeJS.ProcessEnv }) {
|
||||||
console.log(`$ ${command}`);
|
console.log(`$ ${command}`);
|
||||||
|
18
scripts/copyKeycloakResourcesToStorybookStaticDir.ts
Normal file
18
scripts/copyKeycloakResourcesToStorybookStaticDir.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { copyKeycloakResourcesToPublic } from "../src/bin/shared/copyKeycloakResourcesToPublic";
|
||||||
|
import { getProxyFetchOptions } from "../src/bin/tools/fetchProxyOptions";
|
||||||
|
import { LOGIN_THEME_RESOURCES_FROMkEYCLOAK_VERSION_DEFAULT } from "../src/bin/shared/constants";
|
||||||
|
|
||||||
|
export async function copyKeycloakResourcesToStorybookStaticDir() {
|
||||||
|
await copyKeycloakResourcesToPublic({
|
||||||
|
buildContext: {
|
||||||
|
cacheDirPath: pathJoin(__dirname, "..", "node_modules", ".cache", "scripts"),
|
||||||
|
fetchOptions: getProxyFetchOptions({
|
||||||
|
npmConfigGetCwd: pathJoin(__dirname, "..")
|
||||||
|
}),
|
||||||
|
loginThemeResourcesFromKeycloakVersion:
|
||||||
|
LOGIN_THEME_RESOURCES_FROMkEYCLOAK_VERSION_DEFAULT,
|
||||||
|
publicDirPath: pathJoin(__dirname, "..", ".storybook", "static")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -44,6 +44,12 @@ const commonThirdPartyDeps = [
|
|||||||
.replace(/"!dist\//g, '"!')
|
.replace(/"!dist\//g, '"!')
|
||||||
.replace(/"!\.\/dist\//g, '"!./');
|
.replace(/"!\.\/dist\//g, '"!./');
|
||||||
|
|
||||||
|
modifiedPackageJsonContent = JSON.stringify(
|
||||||
|
{ ...JSON.parse(modifiedPackageJsonContent), version: "0.0.0" },
|
||||||
|
null,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
pathJoin(rootDirPath, "dist", "package.json"),
|
pathJoin(rootDirPath, "dist", "package.json"),
|
||||||
Buffer.from(modifiedPackageJsonContent, "utf8")
|
Buffer.from(modifiedPackageJsonContent, "utf8")
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import * as fs from "fs";
|
|
||||||
import { join } from "path";
|
|
||||||
import { startRebuildOnSrcChange } from "./startRebuildOnSrcChange";
|
import { startRebuildOnSrcChange } from "./startRebuildOnSrcChange";
|
||||||
|
import { copyKeycloakResourcesToStorybookStaticDir } from "./copyKeycloakResourcesToStorybookStaticDir";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
run("yarn build");
|
run("yarn build");
|
||||||
|
|
||||||
run(`node ${join("dist", "bin", "main.js")} copy-keycloak-resources-to-public`, {
|
await copyKeycloakResourcesToStorybookStaticDir();
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
PUBLIC_DIR_PATH: join(".storybook", "static")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const child = child_process.spawn("npx", ["start-storybook", "-p", "6006"], {
|
const child = child_process.spawn("npx", ["start-storybook", "-p", "6006"], {
|
||||||
@ -25,6 +20,7 @@ run(`node ${join("dist", "bin", "main.js")} copy-keycloak-resources-to-public`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
startRebuildOnSrcChange();
|
startRebuildOnSrcChange();
|
||||||
|
})();
|
||||||
|
|
||||||
function run(command: string, options?: { env?: NodeJS.ProcessEnv }) {
|
function run(command: string, options?: { env?: NodeJS.ProcessEnv }) {
|
||||||
console.log(`$ ${command}`);
|
console.log(`$ ${command}`);
|
||||||
|
@ -8,7 +8,7 @@ export default function FederatedIdentity(props: PageProps<Extract<KcContext, {
|
|||||||
const { url, federatedIdentity, stateChecker } = kcContext;
|
const { url, federatedIdentity, stateChecker } = kcContext;
|
||||||
const { msg } = i18n;
|
const { msg } = i18n;
|
||||||
return (
|
return (
|
||||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="federatedIdentity">
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="social">
|
||||||
<div className="main-layout social">
|
<div className="main-layout social">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-10">
|
<div className="col-md-10">
|
||||||
|
32
src/bin/initialize-account-theme/copyBoilerplate.ts
Normal file
32
src/bin/initialize-account-theme/copyBoilerplate.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
|
||||||
|
export function copyBoilerplate(params: {
|
||||||
|
accountThemeType: "Single-Page" | "Multi-Page";
|
||||||
|
accountThemeSrcDirPath: string;
|
||||||
|
}) {
|
||||||
|
const { accountThemeType, accountThemeSrcDirPath } = params;
|
||||||
|
|
||||||
|
fs.cpSync(
|
||||||
|
pathJoin(
|
||||||
|
getThisCodebaseRootDirPath(),
|
||||||
|
"src",
|
||||||
|
"bin",
|
||||||
|
"initialize-account-theme",
|
||||||
|
"src",
|
||||||
|
(() => {
|
||||||
|
switch (accountThemeType) {
|
||||||
|
case "Single-Page":
|
||||||
|
return "single-page";
|
||||||
|
case "Multi-Page":
|
||||||
|
return "multi-page";
|
||||||
|
}
|
||||||
|
assert<Equals<typeof accountThemeType, never>>(false);
|
||||||
|
})()
|
||||||
|
),
|
||||||
|
accountThemeSrcDirPath,
|
||||||
|
{ recursive: true }
|
||||||
|
);
|
||||||
|
}
|
1
src/bin/initialize-account-theme/index.ts
Normal file
1
src/bin/initialize-account-theme/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./initialize-account-theme";
|
112
src/bin/initialize-account-theme/initialize-account-theme.ts
Normal file
112
src/bin/initialize-account-theme/initialize-account-theme.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { getBuildContext } from "../shared/buildContext";
|
||||||
|
import type { CliCommandOptions } from "../main";
|
||||||
|
import cliSelect from "cli-select";
|
||||||
|
import child_process from "child_process";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig";
|
||||||
|
import { generateKcGenTs } from "../shared/generateKcGenTs";
|
||||||
|
|
||||||
|
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||||
|
const { cliCommandOptions } = params;
|
||||||
|
|
||||||
|
const buildContext = getBuildContext({ cliCommandOptions });
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_if_uncommitted_changes: {
|
||||||
|
let hasUncommittedChanges: boolean | undefined = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
hasUncommittedChanges =
|
||||||
|
child_process
|
||||||
|
.execSync(`git status --porcelain`, {
|
||||||
|
cwd: buildContext.projectDirPath
|
||||||
|
})
|
||||||
|
.toString()
|
||||||
|
.trim() !== "";
|
||||||
|
} catch {
|
||||||
|
// Probably not a git repository
|
||||||
|
break exit_if_uncommitted_changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasUncommittedChanges) {
|
||||||
|
break exit_if_uncommitted_changes;
|
||||||
|
}
|
||||||
|
console.warn(
|
||||||
|
[
|
||||||
|
chalk.red(
|
||||||
|
"Please commit or stash your changes before running this command.\n"
|
||||||
|
),
|
||||||
|
"This command will modify your project's files so it's better to have a clean working directory",
|
||||||
|
"so that you can easily see what has been changed and revert if needed."
|
||||||
|
].join(" ")
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value: accountThemeType } = await cliSelect({
|
||||||
|
values: ["Single-Page" as const, "Multi-Page" as const]
|
||||||
|
}).catch(() => {
|
||||||
|
process.exit(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (accountThemeType) {
|
||||||
|
case "Multi-Page":
|
||||||
|
{
|
||||||
|
const { initializeAccountTheme_multiPage } = await import(
|
||||||
|
"./initializeAccountTheme_multiPage"
|
||||||
|
);
|
||||||
|
|
||||||
|
await initializeAccountTheme_multiPage({
|
||||||
|
accountThemeSrcDirPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Single-Page":
|
||||||
|
{
|
||||||
|
const { initializeAccountTheme_singlePage } = await import(
|
||||||
|
"./initializeAccountTheme_singlePage"
|
||||||
|
);
|
||||||
|
|
||||||
|
await initializeAccountTheme_singlePage({
|
||||||
|
accountThemeSrcDirPath,
|
||||||
|
buildContext
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAccountThemeImplementationInConfig({ buildContext, accountThemeType });
|
||||||
|
|
||||||
|
await generateKcGenTs({
|
||||||
|
buildContext: {
|
||||||
|
...buildContext,
|
||||||
|
implementedThemeTypes: {
|
||||||
|
...buildContext.implementedThemeTypes,
|
||||||
|
account: {
|
||||||
|
isImplemented: true,
|
||||||
|
type: accountThemeType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { relative as pathRelative } from "path";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { copyBoilerplate } from "./copyBoilerplate";
|
||||||
|
|
||||||
|
export async function initializeAccountTheme_multiPage(params: {
|
||||||
|
accountThemeSrcDirPath: string;
|
||||||
|
}) {
|
||||||
|
const { accountThemeSrcDirPath } = params;
|
||||||
|
|
||||||
|
copyBoilerplate({
|
||||||
|
accountThemeType: "Multi-Page",
|
||||||
|
accountThemeSrcDirPath
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
[
|
||||||
|
chalk.green("The Multi-Page account theme has been initialized."),
|
||||||
|
`Directory created: ${chalk.bold(pathRelative(process.cwd(), accountThemeSrcDirPath))}`
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,150 @@
|
|||||||
|
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
|
||||||
|
import type { BuildContext } from "../shared/buildContext";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { getLatestsSemVersionedTag } from "../shared/getLatestsSemVersionedTag";
|
||||||
|
import fetch from "make-fetch-happen";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
import { is } from "tsafe/is";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import { npmInstall } from "../tools/npmInstall";
|
||||||
|
import { copyBoilerplate } from "./copyBoilerplate";
|
||||||
|
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
||||||
|
|
||||||
|
type BuildContextLike = {
|
||||||
|
cacheDirPath: string;
|
||||||
|
fetchOptions: BuildContext["fetchOptions"];
|
||||||
|
packageJsonFilePath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
|
export async function initializeAccountTheme_singlePage(params: {
|
||||||
|
accountThemeSrcDirPath: string;
|
||||||
|
buildContext: BuildContextLike;
|
||||||
|
}) {
|
||||||
|
const { accountThemeSrcDirPath, buildContext } = params;
|
||||||
|
|
||||||
|
const OWNER = "keycloakify";
|
||||||
|
const REPO = "keycloak-account-ui";
|
||||||
|
|
||||||
|
const [semVersionedTag] = await getLatestsSemVersionedTag({
|
||||||
|
cacheDirPath: buildContext.cacheDirPath,
|
||||||
|
owner: OWNER,
|
||||||
|
repo: REPO,
|
||||||
|
count: 1,
|
||||||
|
doIgnoreReleaseCandidates: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const dependencies = await fetch(
|
||||||
|
`https://raw.githubusercontent.com/${OWNER}/${REPO}/${semVersionedTag.tag}/dependencies.gen.json`,
|
||||||
|
buildContext.fetchOptions
|
||||||
|
)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(
|
||||||
|
(() => {
|
||||||
|
type Dependencies = {
|
||||||
|
dependencies: Record<string, string>;
|
||||||
|
devDependencies?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zDependencies = (() => {
|
||||||
|
type TargetType = Dependencies;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
dependencies: z.record(z.string()),
|
||||||
|
devDependencies: z.record(z.string()).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return o => zDependencies.parse(o);
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
|
||||||
|
dependencies.dependencies["@keycloakify/keycloak-account-ui"] = semVersionedTag.tag;
|
||||||
|
|
||||||
|
const parsedPackageJson = (() => {
|
||||||
|
type ParsedPackageJson = {
|
||||||
|
dependencies?: Record<string, string>;
|
||||||
|
devDependencies?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zParsedPackageJson = (() => {
|
||||||
|
type TargetType = ParsedPackageJson;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
dependencies: z.record(z.string()).optional(),
|
||||||
|
devDependencies: z.record(z.string()).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
const parsedPackageJson = JSON.parse(
|
||||||
|
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
|
||||||
|
);
|
||||||
|
|
||||||
|
zParsedPackageJson.parse(parsedPackageJson);
|
||||||
|
|
||||||
|
assert(is<ParsedPackageJson>(parsedPackageJson));
|
||||||
|
|
||||||
|
return parsedPackageJson;
|
||||||
|
})();
|
||||||
|
|
||||||
|
parsedPackageJson.dependencies = {
|
||||||
|
...parsedPackageJson.dependencies,
|
||||||
|
...dependencies.dependencies
|
||||||
|
};
|
||||||
|
|
||||||
|
parsedPackageJson.devDependencies = {
|
||||||
|
...parsedPackageJson.devDependencies,
|
||||||
|
...dependencies.devDependencies
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(parsedPackageJson.devDependencies).length === 0) {
|
||||||
|
delete parsedPackageJson.devDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
buildContext.packageJsonFilePath,
|
||||||
|
JSON.stringify(parsedPackageJson, undefined, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
run_npm_install: {
|
||||||
|
if (
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(pathJoin(getThisCodebaseRootDirPath(), "package.json"))
|
||||||
|
.toString("utf8")
|
||||||
|
)["version"] === "0.0.0"
|
||||||
|
) {
|
||||||
|
//NOTE: Linked version
|
||||||
|
break run_npm_install;
|
||||||
|
}
|
||||||
|
|
||||||
|
npmInstall({ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath) });
|
||||||
|
}
|
||||||
|
|
||||||
|
copyBoilerplate({
|
||||||
|
accountThemeType: "Single-Page",
|
||||||
|
accountThemeSrcDirPath
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
[
|
||||||
|
chalk.green(
|
||||||
|
"The Single-Page account theme has been successfully initialized."
|
||||||
|
),
|
||||||
|
`Using Account UI of Keycloak version: ${chalk.bold(semVersionedTag.tag.split("-")[0])}`,
|
||||||
|
`Directory created: ${chalk.bold(pathRelative(process.cwd(), accountThemeSrcDirPath))}`,
|
||||||
|
`Dependencies added to your project's package.json: `,
|
||||||
|
chalk.bold(JSON.stringify(dependencies, null, 2))
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
12
src/bin/initialize-account-theme/src/multi-page/KcContext.ts
Normal file
12
src/bin/initialize-account-theme/src/multi-page/KcContext.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
|
import type { ExtendKcContext } from "keycloakify/account";
|
||||||
|
import type { KcEnvName, ThemeName } from "../kc.gen";
|
||||||
|
|
||||||
|
export type KcContextExtension = {
|
||||||
|
themeName: ThemeName;
|
||||||
|
properties: Record<KcEnvName, string> & {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KcContextExtensionPerPage = {};
|
||||||
|
|
||||||
|
export type KcContext = ExtendKcContext<KcContextExtension, KcContextExtensionPerPage>;
|
25
src/bin/initialize-account-theme/src/multi-page/KcPage.tsx
Normal file
25
src/bin/initialize-account-theme/src/multi-page/KcPage.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import type { ClassKey } from "keycloakify/account";
|
||||||
|
import type { KcContext } from "./KcContext";
|
||||||
|
import { useI18n } from "./i18n";
|
||||||
|
import DefaultPage from "keycloakify/account/DefaultPage";
|
||||||
|
import Template from "keycloakify/account/Template";
|
||||||
|
|
||||||
|
export default function KcPage(props: { kcContext: KcContext }) {
|
||||||
|
const { kcContext } = props;
|
||||||
|
|
||||||
|
const { i18n } = useI18n({ kcContext });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
{(() => {
|
||||||
|
switch (kcContext.pageId) {
|
||||||
|
default:
|
||||||
|
return <DefaultPage kcContext={kcContext} i18n={i18n} classes={classes} Template={Template} doUseDefaultCss={true} />;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = {} satisfies { [key in ClassKey]?: string };
|
@ -0,0 +1,38 @@
|
|||||||
|
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||||
|
import type { KcContext } from "./KcContext";
|
||||||
|
import { createGetKcContextMock } from "keycloakify/account/KcContext";
|
||||||
|
import type { KcContextExtension, KcContextExtensionPerPage } from "./KcContext";
|
||||||
|
import KcPage from "./KcPage";
|
||||||
|
import { themeNames, kcEnvDefaults } from "../kc.gen";
|
||||||
|
|
||||||
|
const kcContextExtension: KcContextExtension = {
|
||||||
|
themeName: themeNames[0],
|
||||||
|
properties: {
|
||||||
|
...kcEnvDefaults
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const kcContextExtensionPerPage: KcContextExtensionPerPage = {};
|
||||||
|
|
||||||
|
export const { getKcContextMock } = createGetKcContextMock({
|
||||||
|
kcContextExtension,
|
||||||
|
kcContextExtensionPerPage,
|
||||||
|
overrides: {},
|
||||||
|
overridesPerPage: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function createKcPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
|
||||||
|
const { pageId } = params;
|
||||||
|
|
||||||
|
function KcPageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
|
||||||
|
const { kcContext: overrides } = props;
|
||||||
|
|
||||||
|
const kcContextMock = getKcContextMock({
|
||||||
|
pageId,
|
||||||
|
overrides
|
||||||
|
});
|
||||||
|
|
||||||
|
return <KcPage kcContext={kcContextMock} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { KcPageStory };
|
||||||
|
}
|
5
src/bin/initialize-account-theme/src/multi-page/i18n.ts
Normal file
5
src/bin/initialize-account-theme/src/multi-page/i18n.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createUseI18n } from "keycloakify/account";
|
||||||
|
|
||||||
|
export const { useI18n, ofTypeI18n } = createUseI18n({});
|
||||||
|
|
||||||
|
export type I18n = typeof ofTypeI18n;
|
@ -0,0 +1,7 @@
|
|||||||
|
import type { KcContextLike } from "@keycloakify/keycloak-account-ui";
|
||||||
|
import type { KcEnvName } from "../kc.gen";
|
||||||
|
|
||||||
|
export type KcContext = KcContextLike & {
|
||||||
|
themeType: "account";
|
||||||
|
properties: Record<KcEnvName, string>;
|
||||||
|
};
|
11
src/bin/initialize-account-theme/src/single-page/KcPage.tsx
Normal file
11
src/bin/initialize-account-theme/src/single-page/KcPage.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { lazy } from "react";
|
||||||
|
import { KcAccountUiLoader } from "@keycloakify/keycloak-account-ui";
|
||||||
|
import type { KcContext } from "./KcContext";
|
||||||
|
|
||||||
|
const KcAccountUi = lazy(() => import("@keycloakify/keycloak-account-ui/KcAccountUi"));
|
||||||
|
|
||||||
|
export default function KcPage(props: { kcContext: KcContext }) {
|
||||||
|
const { kcContext } = props;
|
||||||
|
|
||||||
|
return <KcAccountUiLoader kcContext={kcContext} KcAccountUi={KcAccountUi} />;
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
import type { BuildContext } from "../shared/buildContext";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
|
||||||
|
export type BuildContextLike = {
|
||||||
|
bundler: BuildContext["bundler"];
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
|
export function updateAccountThemeImplementationInConfig(params: {
|
||||||
|
buildContext: BuildContext;
|
||||||
|
accountThemeType: "Single-Page" | "Multi-Page";
|
||||||
|
}) {
|
||||||
|
const { buildContext, accountThemeType } = params;
|
||||||
|
|
||||||
|
switch (buildContext.bundler) {
|
||||||
|
case "vite":
|
||||||
|
{
|
||||||
|
const viteConfigPath = pathJoin(
|
||||||
|
buildContext.projectDirPath,
|
||||||
|
"vite.config.ts"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(viteConfigPath)) {
|
||||||
|
console.log(
|
||||||
|
chalk.bold(
|
||||||
|
`You must manually set the accountThemeImplementation to "${accountThemeType}" in your vite config`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viteConfigContent = fs
|
||||||
|
.readFileSync(viteConfigPath)
|
||||||
|
.toString("utf8");
|
||||||
|
|
||||||
|
const modifiedViteConfigContent = viteConfigContent.replace(
|
||||||
|
/accountThemeImplementation\s*:\s*"none"/,
|
||||||
|
`accountThemeImplementation: "${accountThemeType}"`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (modifiedViteConfigContent === viteConfigContent) {
|
||||||
|
console.log(
|
||||||
|
chalk.bold(
|
||||||
|
`You must manually set the accountThemeImplementation to "${accountThemeType}" in your vite.config.ts`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(viteConfigPath, modifiedViteConfigContent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "webpack":
|
||||||
|
{
|
||||||
|
const parsedPackageJson = (() => {
|
||||||
|
type ParsedPackageJson = {
|
||||||
|
keycloakify: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zParsedPackageJson = (() => {
|
||||||
|
type TargetType = ParsedPackageJson;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
keycloakify: z.record(z.string())
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return zParsedPackageJson.parse(
|
||||||
|
JSON.parse(
|
||||||
|
fs
|
||||||
|
.readFileSync(buildContext.packageJsonFilePath)
|
||||||
|
.toString("utf8")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
parsedPackageJson.keycloakify.accountThemeImplementation =
|
||||||
|
accountThemeType;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ export type BuildContextLike = BuildContextLike_generatePom & {
|
|||||||
artifactId: string;
|
artifactId: string;
|
||||||
themeVersion: string;
|
themeVersion: string;
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"];
|
implementedThemeTypes: BuildContext["implementedThemeTypes"];
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -135,7 +135,7 @@ export async function buildJar(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
route_legacy_pages: {
|
route_legacy_pages: {
|
||||||
if (!buildContext.recordIsImplementedByThemeType.login) {
|
if (!buildContext.implementedThemeTypes.login.isImplemented) {
|
||||||
break route_legacy_pages;
|
break route_legacy_pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,9 +10,8 @@ import type { BuildContext } from "../../shared/buildContext";
|
|||||||
export type BuildContextLike = BuildContextLike_buildJar & {
|
export type BuildContextLike = BuildContextLike_buildJar & {
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"];
|
implementedThemeTypes: BuildContext["implementedThemeTypes"];
|
||||||
jarTargets: BuildContext["jarTargets"];
|
jarTargets: BuildContext["jarTargets"];
|
||||||
doUseAccountV3: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -24,8 +23,8 @@ export async function buildJars(params: {
|
|||||||
const { resourcesDirPath, buildContext } = params;
|
const { resourcesDirPath, buildContext } = params;
|
||||||
|
|
||||||
const doesImplementAccountV1Theme =
|
const doesImplementAccountV1Theme =
|
||||||
buildContext.recordIsImplementedByThemeType.account &&
|
buildContext.implementedThemeTypes.account.isImplemented &&
|
||||||
!buildContext.doUseAccountV3;
|
buildContext.implementedThemeTypes.account.type === "Multi-Page";
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
keycloakAccountV1Versions
|
keycloakAccountV1Versions
|
||||||
|
@ -53,10 +53,9 @@ export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
|||||||
projectDirPath: string;
|
projectDirPath: string;
|
||||||
projectBuildDirPath: string;
|
projectBuildDirPath: string;
|
||||||
environmentVariables: { name: string; default: string }[];
|
environmentVariables: { name: string; default: string }[];
|
||||||
recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"];
|
implementedThemeTypes: BuildContext["implementedThemeTypes"];
|
||||||
themeSrcDirPath: string;
|
themeSrcDirPath: string;
|
||||||
bundler: { type: "vite" } | { type: "webpack" };
|
bundler: "vite" | "webpack";
|
||||||
doUseAccountV3: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -74,11 +73,15 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const themeType of ["login", "account"] as const) {
|
for (const themeType of ["login", "account"] as const) {
|
||||||
const isAccountV3 = themeType === "account" && buildContext.doUseAccountV3;
|
if (!buildContext.implementedThemeTypes[themeType].isImplemented) {
|
||||||
if (!buildContext.recordIsImplementedByThemeType[themeType]) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isForAccountSpa =
|
||||||
|
themeType === "account" &&
|
||||||
|
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
||||||
|
buildContext.implementedThemeTypes.account.type === "Single-Page");
|
||||||
|
|
||||||
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
||||||
|
|
||||||
apply_replacers_and_move_to_theme_resources: {
|
apply_replacers_and_move_to_theme_resources: {
|
||||||
@ -93,7 +96,7 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
themeType === "account" &&
|
themeType === "account" &&
|
||||||
buildContext.recordIsImplementedByThemeType.login
|
buildContext.implementedThemeTypes.login.isImplemented
|
||||||
) {
|
) {
|
||||||
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
||||||
|
|
||||||
@ -118,7 +121,7 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (fs.existsSync(dirPath)) {
|
if (fs.existsSync(dirPath)) {
|
||||||
assert(buildContext.bundler.type === "webpack");
|
assert(buildContext.bundler === "webpack");
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
[
|
[
|
||||||
@ -184,10 +187,10 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
case "login":
|
case "login":
|
||||||
return LOGIN_THEME_PAGE_IDS;
|
return LOGIN_THEME_PAGE_IDS;
|
||||||
case "account":
|
case "account":
|
||||||
return isAccountV3 ? ["index.ftl"] : ACCOUNT_THEME_PAGE_IDS;
|
return isForAccountSpa ? ["index.ftl"] : ACCOUNT_THEME_PAGE_IDS;
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
...(isAccountV3
|
...(isForAccountSpa
|
||||||
? []
|
? []
|
||||||
: readExtraPagesNames({
|
: readExtraPagesNames({
|
||||||
themeType,
|
themeType,
|
||||||
@ -203,7 +206,7 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
i18n_messages_generation: {
|
i18n_messages_generation: {
|
||||||
if (isAccountV3) {
|
if (isForAccountSpa) {
|
||||||
break i18n_messages_generation;
|
break i18n_messages_generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +233,7 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keycloak_static_resources: {
|
keycloak_static_resources: {
|
||||||
if (isAccountV3) {
|
if (isForAccountSpa) {
|
||||||
break keycloak_static_resources;
|
break keycloak_static_resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,13 +259,13 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
`parent=${(() => {
|
`parent=${(() => {
|
||||||
switch (themeType) {
|
switch (themeType) {
|
||||||
case "account":
|
case "account":
|
||||||
return isAccountV3 ? "base" : ACCOUNT_V1_THEME_NAME;
|
return isForAccountSpa ? "base" : ACCOUNT_V1_THEME_NAME;
|
||||||
case "login":
|
case "login":
|
||||||
return "keycloak";
|
return "keycloak";
|
||||||
}
|
}
|
||||||
assert<Equals<typeof themeType, never>>(false);
|
assert<Equals<typeof themeType, never>>(false);
|
||||||
})()}`,
|
})()}`,
|
||||||
...(isAccountV3 ? ["deprecatedMode=false"] : []),
|
...(isForAccountSpa ? ["deprecatedMode=false"] : []),
|
||||||
...(buildContext.extraThemeProperties ?? []),
|
...(buildContext.extraThemeProperties ?? []),
|
||||||
...buildContext.environmentVariables.map(
|
...buildContext.environmentVariables.map(
|
||||||
({ name, default: defaultValue }) =>
|
({ name, default: defaultValue }) =>
|
||||||
@ -275,7 +278,7 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
email: {
|
email: {
|
||||||
if (!buildContext.recordIsImplementedByThemeType.email) {
|
if (!buildContext.implementedThemeTypes.email.isImplemented) {
|
||||||
break email;
|
break email;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,11 +291,11 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bring_in_account_v1: {
|
bring_in_account_v1: {
|
||||||
if (buildContext.doUseAccountV3) {
|
if (!buildContext.implementedThemeTypes.account.isImplemented) {
|
||||||
break bring_in_account_v1;
|
break bring_in_account_v1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!buildContext.recordIsImplementedByThemeType.account) {
|
if (buildContext.implementedThemeTypes.account.type !== "Multi-Page") {
|
||||||
break bring_in_account_v1;
|
break bring_in_account_v1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +306,10 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bring_in_account_v3_i18n_messages: {
|
bring_in_account_v3_i18n_messages: {
|
||||||
if (!buildContext.doUseAccountV3) {
|
if (!buildContext.implementedThemeTypes.account.isImplemented) {
|
||||||
|
break bring_in_account_v3_i18n_messages;
|
||||||
|
}
|
||||||
|
if (buildContext.implementedThemeTypes.account.type !== "Single-Page") {
|
||||||
break bring_in_account_v3_i18n_messages;
|
break bring_in_account_v3_i18n_messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,12 +346,12 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
|
|
||||||
metaInfKeycloakThemes.themes.push({
|
metaInfKeycloakThemes.themes.push({
|
||||||
name: themeName,
|
name: themeName,
|
||||||
types: objectEntries(buildContext.recordIsImplementedByThemeType)
|
types: objectEntries(buildContext.implementedThemeTypes)
|
||||||
.filter(([, isImplemented]) => isImplemented)
|
.filter(([, { isImplemented }]) => isImplemented)
|
||||||
.map(([themeType]) => themeType)
|
.map(([themeType]) => themeType)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (buildContext.recordIsImplementedByThemeType.account) {
|
if (buildContext.implementedThemeTypes.account.isImplemented) {
|
||||||
metaInfKeycloakThemes.themes.push({
|
metaInfKeycloakThemes.themes.push({
|
||||||
name: ACCOUNT_V1_THEME_NAME,
|
name: ACCOUNT_V1_THEME_NAME,
|
||||||
types: ["account"]
|
types: ["account"]
|
||||||
|
@ -85,7 +85,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
});
|
});
|
||||||
|
|
||||||
run_post_build_script: {
|
run_post_build_script: {
|
||||||
if (buildContext.bundler.type !== "vite") {
|
if (buildContext.bundler !== "vite") {
|
||||||
break run_post_build_script;
|
break run_post_build_script;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ export type BuildContextLike = {
|
|||||||
projectBuildDirPath: string;
|
projectBuildDirPath: string;
|
||||||
assetsDirPath: string;
|
assetsDirPath: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
bundler: { type: "vite" } | { type: "webpack" };
|
bundler: "vite" | "webpack";
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -20,7 +20,7 @@ export function replaceImportsInJsCode(params: {
|
|||||||
const { jsCode, buildContext } = params;
|
const { jsCode, buildContext } = params;
|
||||||
|
|
||||||
const { fixedJsCode } = (() => {
|
const { fixedJsCode } = (() => {
|
||||||
switch (buildContext.bundler.type) {
|
switch (buildContext.bundler) {
|
||||||
case "vite":
|
case "vite":
|
||||||
return replaceImportsInJsCode_vite({
|
return replaceImportsInJsCode_vite({
|
||||||
jsCode,
|
jsCode,
|
||||||
|
@ -179,6 +179,20 @@ program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command({
|
||||||
|
name: "initialize-account-theme",
|
||||||
|
description: "Initialize the account theme."
|
||||||
|
})
|
||||||
|
.task({
|
||||||
|
skip,
|
||||||
|
handler: async cliCommandOptions => {
|
||||||
|
const { command } = await import("./initialize-account-theme");
|
||||||
|
|
||||||
|
await command({ cliCommandOptions });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command({
|
.command({
|
||||||
name: "copy-keycloak-resources-to-public",
|
name: "copy-keycloak-resources-to-public",
|
||||||
|
@ -14,17 +14,16 @@ import { assert, type Equals } from "tsafe/assert";
|
|||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import {
|
import {
|
||||||
VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES,
|
VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES,
|
||||||
BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME
|
BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME,
|
||||||
|
LOGIN_THEME_RESOURCES_FROMkEYCLOAK_VERSION_DEFAULT
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
import type { KeycloakVersionRange } from "./KeycloakVersionRange";
|
import type { KeycloakVersionRange } from "./KeycloakVersionRange";
|
||||||
import { exclude } from "tsafe";
|
import { exclude } from "tsafe";
|
||||||
import { crawl } from "../tools/crawl";
|
import { crawl } from "../tools/crawl";
|
||||||
import { THEME_TYPES } from "./constants";
|
import { THEME_TYPES } from "./constants";
|
||||||
import { objectFromEntries } from "tsafe/objectFromEntries";
|
|
||||||
import { objectEntries } from "tsafe/objectEntries";
|
import { objectEntries } from "tsafe/objectEntries";
|
||||||
import { type ThemeType } from "./constants";
|
import { type ThemeType } from "./constants";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import { symToStr } from "tsafe/symToStr";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
|
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
|
||||||
|
|
||||||
@ -49,23 +48,23 @@ export type BuildContext = {
|
|||||||
kcContextExclusionsFtlCode: string | undefined;
|
kcContextExclusionsFtlCode: string | undefined;
|
||||||
environmentVariables: { name: string; default: string }[];
|
environmentVariables: { name: string; default: string }[];
|
||||||
themeSrcDirPath: string;
|
themeSrcDirPath: string;
|
||||||
recordIsImplementedByThemeType: Readonly<Record<ThemeType | "email", boolean>>;
|
implementedThemeTypes: {
|
||||||
|
login: { isImplemented: boolean };
|
||||||
|
email: { isImplemented: boolean };
|
||||||
|
account:
|
||||||
|
| { isImplemented: false }
|
||||||
|
| { isImplemented: true; type: "Single-Page" | "Multi-Page" };
|
||||||
|
};
|
||||||
|
packageJsonFilePath: string;
|
||||||
|
bundler: "vite" | "webpack";
|
||||||
jarTargets: {
|
jarTargets: {
|
||||||
keycloakVersionRange: KeycloakVersionRange;
|
keycloakVersionRange: KeycloakVersionRange;
|
||||||
jarFileBasename: string;
|
jarFileBasename: string;
|
||||||
}[];
|
}[];
|
||||||
bundler:
|
|
||||||
| {
|
|
||||||
type: "vite";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "webpack";
|
|
||||||
packageJsonDirPath: string;
|
|
||||||
packageJsonScripts: Record<string, string>;
|
|
||||||
};
|
|
||||||
doUseAccountV3: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert<Equals<keyof BuildContext["implementedThemeTypes"], ThemeType | "email">>();
|
||||||
|
|
||||||
export type BuildOptions = {
|
export type BuildOptions = {
|
||||||
themeName?: string | string[];
|
themeName?: string | string[];
|
||||||
themeVersion?: string;
|
themeVersion?: string;
|
||||||
@ -76,21 +75,30 @@ export type BuildOptions = {
|
|||||||
loginThemeResourcesFromKeycloakVersion?: string;
|
loginThemeResourcesFromKeycloakVersion?: string;
|
||||||
keycloakifyBuildDirPath?: string;
|
keycloakifyBuildDirPath?: string;
|
||||||
kcContextExclusionsFtl?: string;
|
kcContextExclusionsFtl?: string;
|
||||||
/** https://docs.keycloakify.dev/v/v10/targetting-specific-keycloak-versions */
|
} & BuildOptions.AccountThemeImplAndKeycloakVersionTargets;
|
||||||
keycloakVersionTargets?: BuildOptions.KeycloakVersionTargets;
|
|
||||||
doUseAccountV3?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export namespace BuildOptions {
|
export namespace BuildOptions {
|
||||||
export type KeycloakVersionTargets =
|
export type AccountThemeImplAndKeycloakVersionTargets =
|
||||||
| ({ hasAccountTheme: true } & Record<
|
| AccountThemeImplAndKeycloakVersionTargets.MultiPageApp
|
||||||
|
| AccountThemeImplAndKeycloakVersionTargets.SinglePageAppOrNone;
|
||||||
|
|
||||||
|
export namespace AccountThemeImplAndKeycloakVersionTargets {
|
||||||
|
export type MultiPageApp = {
|
||||||
|
accountThemeImplementation: "Multi-Page";
|
||||||
|
keycloakVersionTargets?: Record<
|
||||||
KeycloakVersionRange.WithAccountV1Theme,
|
KeycloakVersionRange.WithAccountV1Theme,
|
||||||
string | boolean
|
string | boolean
|
||||||
>)
|
>;
|
||||||
| ({ hasAccountTheme: false } & Record<
|
};
|
||||||
|
|
||||||
|
export type SinglePageAppOrNone = {
|
||||||
|
accountThemeImplementation: "Single-Page" | "none";
|
||||||
|
keycloakVersionTargets?: Record<
|
||||||
KeycloakVersionRange.WithoutAccountV1Theme,
|
KeycloakVersionRange.WithoutAccountV1Theme,
|
||||||
string | boolean
|
string | boolean
|
||||||
>);
|
>;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResolvedViteConfig = {
|
export type ResolvedViteConfig = {
|
||||||
@ -231,7 +239,6 @@ export function getBuildContext(params: {
|
|||||||
projectBuildDirPath?: string;
|
projectBuildDirPath?: string;
|
||||||
staticDirPathInProjectBuildDirPath?: string;
|
staticDirPathInProjectBuildDirPath?: string;
|
||||||
publicDirPath?: string;
|
publicDirPath?: string;
|
||||||
doUseAccountV3?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ParsedPackageJson = {
|
type ParsedPackageJson = {
|
||||||
@ -241,20 +248,66 @@ export function getBuildContext(params: {
|
|||||||
keycloakify?: BuildOptions_packageJson;
|
keycloakify?: BuildOptions_packageJson;
|
||||||
};
|
};
|
||||||
|
|
||||||
const zParsedPackageJson = z.object({
|
const zMultiPageApp = (() => {
|
||||||
name: z.string().optional(),
|
type TargetType =
|
||||||
version: z.string().optional(),
|
BuildOptions.AccountThemeImplAndKeycloakVersionTargets.MultiPageApp;
|
||||||
homepage: z.string().optional(),
|
|
||||||
keycloakify: id<z.ZodType<BuildOptions_packageJson>>(
|
const zTargetType = z.object({
|
||||||
(() => {
|
accountThemeImplementation: z.literal("Multi-Page"),
|
||||||
const zBuildOptions_packageJson = z.object({
|
keycloakVersionTargets: z
|
||||||
extraThemeProperties: z.array(z.string()).optional(),
|
.object({
|
||||||
artifactId: z.string().optional(),
|
"21-and-below": z.union([z.boolean(), z.string()]),
|
||||||
groupId: z.string().optional(),
|
"23": z.union([z.boolean(), z.string()]),
|
||||||
loginThemeResourcesFromKeycloakVersion: z.string().optional(),
|
"24": z.union([z.boolean(), z.string()]),
|
||||||
projectBuildDirPath: z.string().optional(),
|
"25-and-above": z.union([z.boolean(), z.string()])
|
||||||
keycloakifyBuildDirPath: z.string().optional(),
|
})
|
||||||
kcContextExclusionsFtl: z.string().optional(),
|
.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const zSinglePageApp = (() => {
|
||||||
|
type TargetType =
|
||||||
|
BuildOptions.AccountThemeImplAndKeycloakVersionTargets.SinglePageAppOrNone;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
accountThemeImplementation: z.union([
|
||||||
|
z.literal("Single-Page"),
|
||||||
|
z.literal("none")
|
||||||
|
]),
|
||||||
|
keycloakVersionTargets: z
|
||||||
|
.object({
|
||||||
|
"21-and-below": z.union([z.boolean(), z.string()]),
|
||||||
|
"22-and-above": z.union([z.boolean(), z.string()])
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const zAccountThemeImplAndKeycloakVersionTargets = (() => {
|
||||||
|
type TargetType = BuildOptions.AccountThemeImplAndKeycloakVersionTargets;
|
||||||
|
|
||||||
|
const zTargetType = z.union([zMultiPageApp, zSinglePageApp]);
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const zBuildOptions = (() => {
|
||||||
|
type TargetType = BuildOptions;
|
||||||
|
|
||||||
|
const zTargetType = z.intersection(
|
||||||
|
z.object({
|
||||||
|
themeName: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
themeVersion: z.string().optional(),
|
||||||
environmentVariables: z
|
environmentVariables: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@ -263,63 +316,52 @@ export function getBuildContext(params: {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
themeName: z.union([z.string(), z.array(z.string())]).optional(),
|
extraThemeProperties: z.array(z.string()).optional(),
|
||||||
themeVersion: z.string().optional(),
|
artifactId: z.string().optional(),
|
||||||
staticDirPathInProjectBuildDirPath: z.string().optional(),
|
groupId: z.string().optional(),
|
||||||
publicDirPath: z.string().optional(),
|
loginThemeResourcesFromKeycloakVersion: z.string().optional(),
|
||||||
keycloakVersionTargets: id<
|
keycloakifyBuildDirPath: z.string().optional(),
|
||||||
z.ZodType<BuildOptions.KeycloakVersionTargets>
|
kcContextExclusionsFtl: z.string().optional()
|
||||||
>(
|
|
||||||
(() => {
|
|
||||||
const zKeycloakVersionTargets = z.union([
|
|
||||||
z.object({
|
|
||||||
hasAccountTheme: z.literal(true),
|
|
||||||
"21-and-below": z.union([
|
|
||||||
z.boolean(),
|
|
||||||
z.string()
|
|
||||||
]),
|
|
||||||
"23": z.union([z.boolean(), z.string()]),
|
|
||||||
"24": z.union([z.boolean(), z.string()]),
|
|
||||||
"25-and-above": z.union([z.boolean(), z.string()])
|
|
||||||
}),
|
}),
|
||||||
|
zAccountThemeImplAndKeycloakVersionTargets
|
||||||
|
);
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const zBuildOptions_packageJson = (() => {
|
||||||
|
type TargetType = BuildOptions_packageJson;
|
||||||
|
|
||||||
|
const zTargetType = z.intersection(
|
||||||
|
zBuildOptions,
|
||||||
z.object({
|
z.object({
|
||||||
hasAccountTheme: z.literal(false),
|
projectBuildDirPath: z.string().optional(),
|
||||||
"21-and-below": z.union([
|
staticDirPathInProjectBuildDirPath: z.string().optional(),
|
||||||
z.boolean(),
|
publicDirPath: z.string().optional()
|
||||||
z.string()
|
|
||||||
]),
|
|
||||||
"22-and-above": z.union([z.boolean(), z.string()])
|
|
||||||
})
|
})
|
||||||
]);
|
);
|
||||||
|
|
||||||
{
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
type Got = z.infer<typeof zKeycloakVersionTargets>;
|
|
||||||
type Expected = BuildOptions.KeycloakVersionTargets;
|
|
||||||
assert<Equals<Got, Expected>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return zKeycloakVersionTargets;
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
})()
|
})();
|
||||||
).optional(),
|
|
||||||
doUseAccountV3: z.boolean().optional()
|
const zParsedPackageJson = (() => {
|
||||||
|
type TargetType = ParsedPackageJson;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
version: z.string().optional(),
|
||||||
|
homepage: z.string().optional(),
|
||||||
|
keycloakify: zBuildOptions_packageJson.optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
type Got = z.infer<typeof zBuildOptions_packageJson>;
|
|
||||||
type Expected = BuildOptions_packageJson;
|
|
||||||
assert<Equals<Got, Expected>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return zBuildOptions_packageJson;
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
})()
|
})();
|
||||||
).optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
type Got = z.infer<typeof zParsedPackageJson>;
|
|
||||||
type Expected = ParsedPackageJson;
|
|
||||||
assert<Equals<Got, Expected>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
const configurationPackageJsonFilePath = (() => {
|
const configurationPackageJsonFilePath = (() => {
|
||||||
const rootPackageJsonFilePath = pathJoin(projectDirPath, "package.json");
|
const rootPackageJsonFilePath = pathJoin(projectDirPath, "package.json");
|
||||||
@ -334,17 +376,63 @@ export function getBuildContext(params: {
|
|||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const buildOptions = {
|
const bundler = resolvedViteConfig !== undefined ? "vite" : "webpack";
|
||||||
...parsedPackageJson.keycloakify,
|
|
||||||
...resolvedViteConfig?.buildOptions
|
if (bundler === "vite" && parsedPackageJson.keycloakify !== undefined) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
`In vite projects, provide your Keycloakify options in vite.config.ts, not in package.json`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildOptions: BuildOptions = (() => {
|
||||||
|
switch (bundler) {
|
||||||
|
case "vite":
|
||||||
|
assert(resolvedViteConfig !== undefined);
|
||||||
|
return resolvedViteConfig.buildOptions;
|
||||||
|
case "webpack":
|
||||||
|
assert(parsedPackageJson.keycloakify !== undefined);
|
||||||
|
return parsedPackageJson.keycloakify;
|
||||||
|
}
|
||||||
|
assert<Equals<typeof bundler, never>>(false);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const implementedThemeTypes: BuildContext["implementedThemeTypes"] = {
|
||||||
|
login: {
|
||||||
|
isImplemented: fs.existsSync(pathJoin(themeSrcDirPath, "login"))
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
isImplemented: fs.existsSync(pathJoin(themeSrcDirPath, "email"))
|
||||||
|
},
|
||||||
|
account: (() => {
|
||||||
|
if (buildOptions.accountThemeImplementation === "none") {
|
||||||
|
return { isImplemented: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isImplemented: true,
|
||||||
|
type: buildOptions.accountThemeImplementation
|
||||||
|
};
|
||||||
|
})()
|
||||||
};
|
};
|
||||||
|
|
||||||
const recordIsImplementedByThemeType = objectFromEntries(
|
if (
|
||||||
(["login", "account", "email"] as const).map(themeType => [
|
implementedThemeTypes.account.isImplemented &&
|
||||||
themeType,
|
!fs.existsSync(pathJoin(themeSrcDirPath, "account"))
|
||||||
fs.existsSync(pathJoin(themeSrcDirPath, themeType))
|
) {
|
||||||
])
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
[
|
||||||
|
`You have set 'accountThemeImplementation' to '${implementedThemeTypes.account.type}'`,
|
||||||
|
`but the 'account' directory is missing in your theme source directory`,
|
||||||
|
"Use the `npx keycloakify initialize-account-theme` command to create it"
|
||||||
|
].join(" ")
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
const themeNames = ((): [string, ...string[]] => {
|
const themeNames = ((): [string, ...string[]] => {
|
||||||
if (buildOptions.themeName === undefined) {
|
if (buildOptions.themeName === undefined) {
|
||||||
@ -371,13 +459,15 @@ export function getBuildContext(params: {
|
|||||||
|
|
||||||
const projectBuildDirPath = (() => {
|
const projectBuildDirPath = (() => {
|
||||||
webpack: {
|
webpack: {
|
||||||
if (resolvedViteConfig !== undefined) {
|
if (bundler !== "webpack") {
|
||||||
break webpack;
|
break webpack;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildOptions.projectBuildDirPath !== undefined) {
|
assert(parsedPackageJson.keycloakify !== undefined);
|
||||||
|
|
||||||
|
if (parsedPackageJson.keycloakify.projectBuildDirPath !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: buildOptions.projectBuildDirPath,
|
pathIsh: parsedPackageJson.keycloakify.projectBuildDirPath,
|
||||||
cwd: projectDirPath
|
cwd: projectDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -385,34 +475,15 @@ export function getBuildContext(params: {
|
|||||||
return pathJoin(projectDirPath, "build");
|
return pathJoin(projectDirPath, "build");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(bundler === "vite");
|
||||||
|
assert(resolvedViteConfig !== undefined);
|
||||||
|
|
||||||
return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
|
return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const bundler = resolvedViteConfig !== undefined ? "vite" : "webpack";
|
|
||||||
|
|
||||||
const doUseAccountV3 = buildOptions.doUseAccountV3 ?? false;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bundler:
|
bundler,
|
||||||
resolvedViteConfig !== undefined
|
packageJsonFilePath,
|
||||||
? { type: "vite" }
|
|
||||||
: (() => {
|
|
||||||
const { scripts } = z
|
|
||||||
.object({
|
|
||||||
scripts: z.record(z.string()).optional()
|
|
||||||
})
|
|
||||||
.parse(
|
|
||||||
JSON.parse(
|
|
||||||
fs.readFileSync(packageJsonFilePath).toString("utf8")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "webpack",
|
|
||||||
packageJsonDirPath: pathDirname(packageJsonFilePath),
|
|
||||||
packageJsonScripts: scripts ?? {}
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
themeVersion: buildOptions.themeVersion ?? parsedPackageJson.version ?? "0.0.0",
|
themeVersion: buildOptions.themeVersion ?? parsedPackageJson.version ?? "0.0.0",
|
||||||
themeNames,
|
themeNames,
|
||||||
extraThemeProperties: buildOptions.extraThemeProperties,
|
extraThemeProperties: buildOptions.extraThemeProperties,
|
||||||
@ -436,7 +507,8 @@ export function getBuildContext(params: {
|
|||||||
buildOptions.artifactId ??
|
buildOptions.artifactId ??
|
||||||
`${themeNames[0]}-keycloak-theme`,
|
`${themeNames[0]}-keycloak-theme`,
|
||||||
loginThemeResourcesFromKeycloakVersion:
|
loginThemeResourcesFromKeycloakVersion:
|
||||||
buildOptions.loginThemeResourcesFromKeycloakVersion ?? "24.0.4",
|
buildOptions.loginThemeResourcesFromKeycloakVersion ??
|
||||||
|
LOGIN_THEME_RESOURCES_FROMkEYCLOAK_VERSION_DEFAULT,
|
||||||
projectDirPath,
|
projectDirPath,
|
||||||
projectBuildDirPath,
|
projectBuildDirPath,
|
||||||
keycloakifyBuildDirPath: (() => {
|
keycloakifyBuildDirPath: (() => {
|
||||||
@ -463,13 +535,15 @@ export function getBuildContext(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
webpack: {
|
webpack: {
|
||||||
if (resolvedViteConfig !== undefined) {
|
if (bundler !== "webpack") {
|
||||||
break webpack;
|
break webpack;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildOptions.publicDirPath !== undefined) {
|
assert(parsedPackageJson.keycloakify !== undefined);
|
||||||
|
|
||||||
|
if (parsedPackageJson.keycloakify.publicDirPath !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: buildOptions.publicDirPath,
|
pathIsh: parsedPackageJson.keycloakify.publicDirPath,
|
||||||
cwd: projectDirPath
|
cwd: projectDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -477,10 +551,12 @@ export function getBuildContext(params: {
|
|||||||
return pathJoin(projectDirPath, "public");
|
return pathJoin(projectDirPath, "public");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(bundler === "vite");
|
||||||
|
assert(resolvedViteConfig !== undefined);
|
||||||
|
|
||||||
return pathJoin(projectDirPath, resolvedViteConfig.publicDir);
|
return pathJoin(projectDirPath, resolvedViteConfig.publicDir);
|
||||||
})(),
|
})(),
|
||||||
cacheDirPath: (() => {
|
cacheDirPath: pathJoin(
|
||||||
const cacheDirPath = pathJoin(
|
|
||||||
(() => {
|
(() => {
|
||||||
if (process.env.XDG_CACHE_HOME !== undefined) {
|
if (process.env.XDG_CACHE_HOME !== undefined) {
|
||||||
return getAbsoluteAndInOsFormatPath({
|
return getAbsoluteAndInOsFormatPath({
|
||||||
@ -496,13 +572,10 @@ export function getBuildContext(params: {
|
|||||||
);
|
);
|
||||||
})(),
|
})(),
|
||||||
"keycloakify"
|
"keycloakify"
|
||||||
);
|
),
|
||||||
|
|
||||||
return cacheDirPath;
|
|
||||||
})(),
|
|
||||||
urlPathname: (() => {
|
urlPathname: (() => {
|
||||||
webpack: {
|
webpack: {
|
||||||
if (resolvedViteConfig !== undefined) {
|
if (bundler !== "webpack") {
|
||||||
break webpack;
|
break webpack;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,23 +595,35 @@ export function getBuildContext(params: {
|
|||||||
return out === "/" ? undefined : out;
|
return out === "/" ? undefined : out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(bundler === "vite");
|
||||||
|
assert(resolvedViteConfig !== undefined);
|
||||||
|
|
||||||
return resolvedViteConfig.urlPathname;
|
return resolvedViteConfig.urlPathname;
|
||||||
})(),
|
})(),
|
||||||
assetsDirPath: (() => {
|
assetsDirPath: (() => {
|
||||||
webpack: {
|
webpack: {
|
||||||
if (resolvedViteConfig !== undefined) {
|
if (bundler !== "webpack") {
|
||||||
break webpack;
|
break webpack;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildOptions.staticDirPathInProjectBuildDirPath !== undefined) {
|
assert(parsedPackageJson.keycloakify !== undefined);
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedPackageJson.keycloakify.staticDirPathInProjectBuildDirPath !==
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
getAbsoluteAndInOsFormatPath({
|
getAbsoluteAndInOsFormatPath({
|
||||||
pathIsh: buildOptions.staticDirPathInProjectBuildDirPath,
|
pathIsh:
|
||||||
|
parsedPackageJson.keycloakify
|
||||||
|
.staticDirPathInProjectBuildDirPath,
|
||||||
cwd: projectBuildDirPath
|
cwd: projectBuildDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return pathJoin(projectBuildDirPath, "static");
|
return pathJoin(projectBuildDirPath, "static");
|
||||||
}
|
}
|
||||||
|
assert(bundler === "vite");
|
||||||
|
assert(resolvedViteConfig !== undefined);
|
||||||
|
|
||||||
return pathJoin(projectBuildDirPath, resolvedViteConfig.assetsDir);
|
return pathJoin(projectBuildDirPath, resolvedViteConfig.assetsDir);
|
||||||
})(),
|
})(),
|
||||||
@ -559,7 +644,7 @@ export function getBuildContext(params: {
|
|||||||
return buildOptions.kcContextExclusionsFtl;
|
return buildOptions.kcContextExclusionsFtl;
|
||||||
})(),
|
})(),
|
||||||
environmentVariables: buildOptions.environmentVariables ?? [],
|
environmentVariables: buildOptions.environmentVariables ?? [],
|
||||||
recordIsImplementedByThemeType,
|
implementedThemeTypes,
|
||||||
themeSrcDirPath,
|
themeSrcDirPath,
|
||||||
fetchOptions: getProxyFetchOptions({
|
fetchOptions: getProxyFetchOptions({
|
||||||
npmConfigGetCwd: (function callee(upCount: number): string {
|
npmConfigGetCwd: (function callee(upCount: number): string {
|
||||||
@ -613,10 +698,10 @@ export function getBuildContext(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const keycloakVersionRange: KeycloakVersionRange = (() => {
|
const keycloakVersionRange: KeycloakVersionRange = (() => {
|
||||||
const doesImplementAccountV1Theme =
|
if (
|
||||||
!doUseAccountV3 && recordIsImplementedByThemeType.account;
|
implementedThemeTypes.account.isImplemented &&
|
||||||
|
implementedThemeTypes.account.type === "Multi-Page"
|
||||||
if (doesImplementAccountV1Theme) {
|
) {
|
||||||
const keycloakVersionRange = (() => {
|
const keycloakVersionRange = (() => {
|
||||||
if (buildForKeycloakMajorVersionNumber <= 21) {
|
if (buildForKeycloakMajorVersionNumber <= 21) {
|
||||||
return "21-and-below" as const;
|
return "21-and-below" as const;
|
||||||
@ -703,7 +788,10 @@ export function getBuildContext(params: {
|
|||||||
const jarTargets_default = (() => {
|
const jarTargets_default = (() => {
|
||||||
const jarTargets: BuildContext["jarTargets"] = [];
|
const jarTargets: BuildContext["jarTargets"] = [];
|
||||||
|
|
||||||
if (!doUseAccountV3 && recordIsImplementedByThemeType.account) {
|
if (
|
||||||
|
implementedThemeTypes.account.isImplemented &&
|
||||||
|
implementedThemeTypes.account.type === "Multi-Page"
|
||||||
|
) {
|
||||||
for (const keycloakVersionRange of [
|
for (const keycloakVersionRange of [
|
||||||
"21-and-below",
|
"21-and-below",
|
||||||
"23",
|
"23",
|
||||||
@ -748,79 +836,11 @@ export function getBuildContext(params: {
|
|||||||
return jarTargets_default;
|
return jarTargets_default;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
buildOptions.keycloakVersionTargets.hasAccountTheme !== doUseAccountV3
|
|
||||||
? false
|
|
||||||
: recordIsImplementedByThemeType.account
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
chalk.red(
|
|
||||||
(() => {
|
|
||||||
const { keycloakVersionTargets } = buildOptions;
|
|
||||||
|
|
||||||
let message = `Bad ${symToStr({ keycloakVersionTargets })} configuration.\n`;
|
|
||||||
|
|
||||||
if (keycloakVersionTargets.hasAccountTheme) {
|
|
||||||
message +=
|
|
||||||
"Your codebase does not seem to implement an account theme ";
|
|
||||||
} else {
|
|
||||||
message += "Your codebase implements an account theme ";
|
|
||||||
}
|
|
||||||
|
|
||||||
const { hasAccountTheme } = keycloakVersionTargets;
|
|
||||||
|
|
||||||
message += `but you have set ${symToStr({ keycloakVersionTargets })}.${symToStr({ hasAccountTheme })}`;
|
|
||||||
message += ` to ${hasAccountTheme} in your `;
|
|
||||||
message += (() => {
|
|
||||||
switch (bundler) {
|
|
||||||
case "vite":
|
|
||||||
return "vite.config.ts";
|
|
||||||
case "webpack":
|
|
||||||
return "package.json";
|
|
||||||
}
|
|
||||||
assert<Equals<typeof bundler, never>>(false);
|
|
||||||
})();
|
|
||||||
message += `. Please set it to ${!hasAccountTheme} `;
|
|
||||||
message +=
|
|
||||||
"and fill up the relevant keycloak version ranges.\n";
|
|
||||||
message += "Example:\n";
|
|
||||||
message += JSON.stringify(
|
|
||||||
id<Pick<BuildOptions, "keycloakVersionTargets">>({
|
|
||||||
keycloakVersionTargets: {
|
|
||||||
hasAccountTheme:
|
|
||||||
recordIsImplementedByThemeType.account,
|
|
||||||
...objectFromEntries(
|
|
||||||
jarTargets_default.map(
|
|
||||||
({
|
|
||||||
keycloakVersionRange,
|
|
||||||
jarFileBasename
|
|
||||||
}) => [
|
|
||||||
keycloakVersionRange,
|
|
||||||
jarFileBasename
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
message +=
|
|
||||||
"\nSee: https://docs.keycloakify.dev/v/v10/targetting-specific-keycloak-versions";
|
|
||||||
|
|
||||||
return message;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const jarTargets: BuildContext["jarTargets"] = [];
|
const jarTargets: BuildContext["jarTargets"] = [];
|
||||||
|
|
||||||
const { hasAccountTheme, ...rest } = buildOptions.keycloakVersionTargets;
|
for (const [keycloakVersionRange, jarNameOrBoolean] of objectEntries(
|
||||||
|
buildOptions.keycloakVersionTargets
|
||||||
for (const [keycloakVersionRange, jarNameOrBoolean] of objectEntries(rest)) {
|
)) {
|
||||||
if (jarNameOrBoolean === false) {
|
if (jarNameOrBoolean === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -871,7 +891,6 @@ export function getBuildContext(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return jarTargets;
|
return jarTargets;
|
||||||
})(),
|
})()
|
||||||
doUseAccountV3
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -69,3 +69,5 @@ export type AccountThemePageId = (typeof ACCOUNT_THEME_PAGE_IDS)[number];
|
|||||||
export const CONTAINER_NAME = "keycloak-keycloakify";
|
export const CONTAINER_NAME = "keycloak-keycloakify";
|
||||||
|
|
||||||
export const FALLBACK_LANGUAGE_TAG = "en";
|
export const FALLBACK_LANGUAGE_TAG = "en";
|
||||||
|
|
||||||
|
export const LOGIN_THEME_RESOURCES_FROMkEYCLOAK_VERSION_DEFAULT = "24.0.4";
|
||||||
|
@ -1,14 +1,21 @@
|
|||||||
import { assert } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
import type { BuildContext } from "./buildContext";
|
import type { BuildContext } from "./buildContext";
|
||||||
import * as fs from "fs/promises";
|
import * as fs from "fs/promises";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { existsAsync } from "../tools/fs.existsAsync";
|
import { existsAsync } from "../tools/fs.existsAsync";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
||||||
themeNames: string[];
|
themeNames: string[];
|
||||||
environmentVariables: { name: string; default: string }[];
|
environmentVariables: { name: string; default: string }[];
|
||||||
themeSrcDirPath: string;
|
themeSrcDirPath: string;
|
||||||
|
implementedThemeTypes: Pick<
|
||||||
|
BuildContext["implementedThemeTypes"],
|
||||||
|
"login" | "account"
|
||||||
|
>;
|
||||||
|
packageJsonFilePath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -18,12 +25,53 @@ export async function generateKcGenTs(params: {
|
|||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
|
|
||||||
const filePath = pathJoin(buildContext.themeSrcDirPath, "kc.gen.ts");
|
const isReactProject: boolean = await (async () => {
|
||||||
|
const parsedPackageJson = await (async () => {
|
||||||
|
type ParsedPackageJson = {
|
||||||
|
dependencies?: Record<string, string>;
|
||||||
|
devDependencies?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zParsedPackageJson = (() => {
|
||||||
|
type TargetType = ParsedPackageJson;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
dependencies: z.record(z.string()).optional(),
|
||||||
|
devDependencies: z.record(z.string()).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return zParsedPackageJson.parse(
|
||||||
|
JSON.parse(
|
||||||
|
(await fs.readFile(buildContext.packageJsonFilePath)).toString("utf8")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
...parsedPackageJson.dependencies,
|
||||||
|
...parsedPackageJson.devDependencies
|
||||||
|
}.react !== undefined
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const filePath = pathJoin(
|
||||||
|
buildContext.themeSrcDirPath,
|
||||||
|
`kc.gen.ts${isReactProject ? "x" : ""}`
|
||||||
|
);
|
||||||
|
|
||||||
const currentContent = (await existsAsync(filePath))
|
const currentContent = (await existsAsync(filePath))
|
||||||
? await fs.readFile(filePath)
|
? await fs.readFile(filePath)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
|
||||||
|
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
|
||||||
|
|
||||||
const newContent = Buffer.from(
|
const newContent = Buffer.from(
|
||||||
[
|
[
|
||||||
`/* prettier-ignore-start */`,
|
`/* prettier-ignore-start */`,
|
||||||
@ -36,6 +84,8 @@ export async function generateKcGenTs(params: {
|
|||||||
``,
|
``,
|
||||||
`// This file is auto-generated by Keycloakify`,
|
`// This file is auto-generated by Keycloakify`,
|
||||||
``,
|
``,
|
||||||
|
isReactProject && `import { lazy, Suspense, type ReactNode } from "react";`,
|
||||||
|
``,
|
||||||
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
|
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
|
||||||
``,
|
``,
|
||||||
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
|
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
|
||||||
@ -54,9 +104,52 @@ export async function generateKcGenTs(params: {
|
|||||||
2
|
2
|
||||||
)};`,
|
)};`,
|
||||||
``,
|
``,
|
||||||
|
`export type KcContext =`,
|
||||||
|
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
|
||||||
|
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
|
||||||
|
` ;`,
|
||||||
|
``,
|
||||||
|
`declare global {`,
|
||||||
|
` interface Window {`,
|
||||||
|
` kcContext?: KcContext;`,
|
||||||
|
` }`,
|
||||||
|
`}`,
|
||||||
|
``,
|
||||||
|
...(!isReactProject
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
hasLoginTheme &&
|
||||||
|
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
|
||||||
|
hasAccountTheme &&
|
||||||
|
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
|
||||||
|
``,
|
||||||
|
`export function KcPage(`,
|
||||||
|
` props: {`,
|
||||||
|
` kcContext: KcContext;`,
|
||||||
|
` fallback?: ReactNode;`,
|
||||||
|
` }`,
|
||||||
|
`) {`,
|
||||||
|
` const { kcContext, fallback } = props;`,
|
||||||
|
` return (`,
|
||||||
|
` <Suspense fallback={fallback}>`,
|
||||||
|
` {(() => {`,
|
||||||
|
` switch (kcContext.themeType) {`,
|
||||||
|
hasLoginTheme &&
|
||||||
|
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
|
||||||
|
hasAccountTheme &&
|
||||||
|
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
|
||||||
|
` }`,
|
||||||
|
` })()}`,
|
||||||
|
` </Suspense>`,
|
||||||
|
` );`,
|
||||||
|
`}`
|
||||||
|
]),
|
||||||
|
``,
|
||||||
`/* prettier-ignore-end */`,
|
`/* prettier-ignore-end */`,
|
||||||
``
|
``
|
||||||
].join("\n"),
|
]
|
||||||
|
.filter(item => typeof item === "string")
|
||||||
|
.join("\n"),
|
||||||
"utf8"
|
"utf8"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -65,4 +158,18 @@ export async function generateKcGenTs(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(filePath, newContent);
|
await fs.writeFile(filePath, newContent);
|
||||||
|
|
||||||
|
delete_legacy_file: {
|
||||||
|
if (!isReactProject) {
|
||||||
|
break delete_legacy_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyFilePath = filePath.replace(/tsx$/, "ts");
|
||||||
|
|
||||||
|
if (!(await existsAsync(legacyFilePath))) {
|
||||||
|
break delete_legacy_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.unlink(legacyFilePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
180
src/bin/shared/getLatestsSemVersionedTag.ts
Normal file
180
src/bin/shared/getLatestsSemVersionedTag.ts
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { getLatestsSemVersionedTagFactory } from "../tools/octokit-addons/getLatestsSemVersionedTag";
|
||||||
|
import { Octokit } from "@octokit/rest";
|
||||||
|
import type { ReturnType } from "tsafe";
|
||||||
|
import type { Param0 } from "tsafe";
|
||||||
|
import { join as pathJoin, dirname as pathDirname } from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import type { SemVer } from "../tools/SemVer";
|
||||||
|
import { same } from "evt/tools/inDepth/same";
|
||||||
|
|
||||||
|
type GetLatestsSemVersionedTag = ReturnType<
|
||||||
|
typeof getLatestsSemVersionedTagFactory
|
||||||
|
>["getLatestsSemVersionedTag"];
|
||||||
|
|
||||||
|
type Params = Param0<GetLatestsSemVersionedTag>;
|
||||||
|
type R = ReturnType<GetLatestsSemVersionedTag>;
|
||||||
|
|
||||||
|
let getLatestsSemVersionedTag_stateless: GetLatestsSemVersionedTag | undefined =
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
const CACHE_VERSION = 1;
|
||||||
|
|
||||||
|
type Cache = {
|
||||||
|
version: typeof CACHE_VERSION;
|
||||||
|
entries: {
|
||||||
|
time: number;
|
||||||
|
params: Params;
|
||||||
|
result: R;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getLatestsSemVersionedTag({
|
||||||
|
cacheDirPath,
|
||||||
|
...params
|
||||||
|
}: Params & { cacheDirPath: string }): Promise<R> {
|
||||||
|
const cacheFilePath = pathJoin(cacheDirPath, "latest-sem-versioned-tags.json");
|
||||||
|
|
||||||
|
const cacheLookupResult = (() => {
|
||||||
|
const getResult_currentCache = (currentCacheEntries: Cache["entries"]) => ({
|
||||||
|
hasCachedResult: false as const,
|
||||||
|
currentCache: {
|
||||||
|
version: CACHE_VERSION,
|
||||||
|
entries: currentCacheEntries
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!fs.existsSync(cacheFilePath)) {
|
||||||
|
return getResult_currentCache([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cache_json = fs.readFileSync(cacheFilePath).toString("utf8");
|
||||||
|
} catch {
|
||||||
|
return getResult_currentCache([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_json_parsed: unknown;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cache_json_parsed = JSON.parse(cache_json);
|
||||||
|
} catch {
|
||||||
|
return getResult_currentCache([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const zSemVer = (() => {
|
||||||
|
type TargetType = SemVer;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
major: z.number(),
|
||||||
|
minor: z.number(),
|
||||||
|
patch: z.number(),
|
||||||
|
rc: z.number().optional(),
|
||||||
|
parsedFrom: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const zCache = (() => {
|
||||||
|
type TargetType = Cache;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
version: z.literal(CACHE_VERSION),
|
||||||
|
entries: z.array(
|
||||||
|
z.object({
|
||||||
|
time: z.number(),
|
||||||
|
params: z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
count: z.number(),
|
||||||
|
doIgnoreReleaseCandidates: z.boolean()
|
||||||
|
}),
|
||||||
|
result: z.array(
|
||||||
|
z.object({
|
||||||
|
tag: z.string(),
|
||||||
|
version: zSemVer
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
|
||||||
|
let cache: Cache;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cache = zCache.parse(cache_json_parsed);
|
||||||
|
} catch {
|
||||||
|
return getResult_currentCache([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheEntry = cache.entries.find(e => same(e.params, params));
|
||||||
|
|
||||||
|
if (cacheEntry === undefined) {
|
||||||
|
return getResult_currentCache(cache.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Date.now() - cacheEntry.time > 3_600_000) {
|
||||||
|
return getResult_currentCache(cache.entries.filter(e => e !== cacheEntry));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hasCachedResult: true as const,
|
||||||
|
cachedResult: cacheEntry.result
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (cacheLookupResult.hasCachedResult) {
|
||||||
|
return cacheLookupResult.cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { currentCache } = cacheLookupResult;
|
||||||
|
|
||||||
|
getLatestsSemVersionedTag_stateless ??= (() => {
|
||||||
|
const octokit = (() => {
|
||||||
|
const githubToken = process.env.GITHUB_TOKEN;
|
||||||
|
|
||||||
|
const octokit = new Octokit(
|
||||||
|
githubToken === undefined ? undefined : { auth: githubToken }
|
||||||
|
);
|
||||||
|
|
||||||
|
return octokit;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({
|
||||||
|
octokit
|
||||||
|
});
|
||||||
|
|
||||||
|
return getLatestsSemVersionedTag;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const result = await getLatestsSemVersionedTag_stateless(params);
|
||||||
|
|
||||||
|
currentCache.entries.push({
|
||||||
|
time: Date.now(),
|
||||||
|
params,
|
||||||
|
result
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const dirPath = pathDirname(cacheFilePath);
|
||||||
|
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(cacheFilePath, JSON.stringify(currentCache, null, 2));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
@ -1,11 +1,6 @@
|
|||||||
import { getLatestsSemVersionedTagFactory } from "../tools/octokit-addons/getLatestsSemVersionedTag";
|
import { getLatestsSemVersionedTag } from "./getLatestsSemVersionedTag";
|
||||||
import { Octokit } from "@octokit/rest";
|
|
||||||
import cliSelect from "cli-select";
|
import cliSelect from "cli-select";
|
||||||
import { SemVer } from "../tools/SemVer";
|
import { SemVer } from "../tools/SemVer";
|
||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import type { ReturnType } from "tsafe";
|
|
||||||
import { id } from "tsafe/id";
|
|
||||||
|
|
||||||
export async function promptKeycloakVersion(params: {
|
export async function promptKeycloakVersion(params: {
|
||||||
startingFromMajor: number | undefined;
|
startingFromMajor: number | undefined;
|
||||||
@ -14,80 +9,16 @@ export async function promptKeycloakVersion(params: {
|
|||||||
}) {
|
}) {
|
||||||
const { startingFromMajor, excludeMajorVersions, cacheDirPath } = params;
|
const { startingFromMajor, excludeMajorVersions, cacheDirPath } = params;
|
||||||
|
|
||||||
const { getLatestsSemVersionedTag } = (() => {
|
|
||||||
const { octokit } = (() => {
|
|
||||||
const githubToken = process.env.GITHUB_TOKEN;
|
|
||||||
|
|
||||||
const octokit = new Octokit(
|
|
||||||
githubToken === undefined ? undefined : { auth: githubToken }
|
|
||||||
);
|
|
||||||
|
|
||||||
return { octokit };
|
|
||||||
})();
|
|
||||||
|
|
||||||
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({
|
|
||||||
octokit
|
|
||||||
});
|
|
||||||
|
|
||||||
return { getLatestsSemVersionedTag };
|
|
||||||
})();
|
|
||||||
|
|
||||||
const semVersionedTagByMajor = new Map<number, { tag: string; version: SemVer }>();
|
const semVersionedTagByMajor = new Map<number, { tag: string; version: SemVer }>();
|
||||||
|
|
||||||
const semVersionedTags = await (async () => {
|
|
||||||
const cacheFilePath = pathJoin(cacheDirPath, "keycloak-versions.json");
|
|
||||||
|
|
||||||
type Cache = {
|
|
||||||
time: number;
|
|
||||||
semVersionedTags: ReturnType<typeof getLatestsSemVersionedTag>;
|
|
||||||
};
|
|
||||||
|
|
||||||
use_cache: {
|
|
||||||
if (!fs.existsSync(cacheFilePath)) {
|
|
||||||
break use_cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cache: Cache = JSON.parse(
|
|
||||||
fs.readFileSync(cacheFilePath).toString("utf8")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Date.now() - cache.time > 3_600_000) {
|
|
||||||
fs.unlinkSync(cacheFilePath);
|
|
||||||
break use_cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache.semVersionedTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
const semVersionedTags = await getLatestsSemVersionedTag({
|
const semVersionedTags = await getLatestsSemVersionedTag({
|
||||||
|
cacheDirPath,
|
||||||
count: 50,
|
count: 50,
|
||||||
owner: "keycloak",
|
owner: "keycloak",
|
||||||
repo: "keycloak"
|
repo: "keycloak",
|
||||||
|
doIgnoreReleaseCandidates: true
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
|
||||||
const dirPath = pathDirname(cacheFilePath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(dirPath)) {
|
|
||||||
fs.mkdirSync(dirPath, { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
cacheFilePath,
|
|
||||||
JSON.stringify(
|
|
||||||
id<Cache>({
|
|
||||||
time: Date.now(),
|
|
||||||
semVersionedTags
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return semVersionedTags;
|
|
||||||
})();
|
|
||||||
|
|
||||||
semVersionedTags.forEach(semVersionedTag => {
|
semVersionedTags.forEach(semVersionedTag => {
|
||||||
if (
|
if (
|
||||||
startingFromMajor !== undefined &&
|
startingFromMajor !== undefined &&
|
||||||
|
@ -5,12 +5,15 @@ import type { BuildContext } from "../shared/buildContext";
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { sep as pathSep, join as pathJoin } from "path";
|
import { sep as pathSep, join as pathJoin } from "path";
|
||||||
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { dirname as pathDirname, relative as pathRelative } from "path";
|
||||||
|
|
||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
bundler: BuildContext["bundler"];
|
bundler: BuildContext["bundler"];
|
||||||
projectBuildDirPath: string;
|
projectBuildDirPath: string;
|
||||||
|
packageJsonFilePath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -20,7 +23,7 @@ export async function appBuild(params: {
|
|||||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
|
|
||||||
switch (buildContext.bundler.type) {
|
switch (buildContext.bundler) {
|
||||||
case "vite":
|
case "vite":
|
||||||
return appBuild_vite({ buildContext });
|
return appBuild_vite({ buildContext });
|
||||||
case "webpack":
|
case "webpack":
|
||||||
@ -33,7 +36,7 @@ async function appBuild_vite(params: {
|
|||||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
|
|
||||||
assert(buildContext.bundler.type === "vite");
|
assert(buildContext.bundler === "vite");
|
||||||
|
|
||||||
const dIsSuccess = new Deferred<boolean>();
|
const dIsSuccess = new Deferred<boolean>();
|
||||||
|
|
||||||
@ -66,17 +69,18 @@ async function appBuild_webpack(params: {
|
|||||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
|
|
||||||
assert(buildContext.bundler.type === "webpack");
|
assert(buildContext.bundler === "webpack");
|
||||||
|
|
||||||
const entries = Object.entries(buildContext.bundler.packageJsonScripts).filter(
|
const entries = Object.entries(
|
||||||
([, scriptCommand]) => scriptCommand.includes("keycloakify build")
|
(JSON.parse(fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8"))
|
||||||
);
|
.scripts ?? {}) as Record<string, string>
|
||||||
|
).filter(([, scriptCommand]) => scriptCommand.includes("keycloakify build"));
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.red(
|
chalk.red(
|
||||||
[
|
[
|
||||||
`You should have a script in your package.json at ${buildContext.bundler.packageJsonDirPath}`,
|
`You should have a script in your package.json at ${pathRelative(process.cwd(), pathDirname(buildContext.packageJsonFilePath))}`,
|
||||||
`that includes the 'keycloakify build' command`
|
`that includes the 'keycloakify build' command`
|
||||||
].join(" ")
|
].join(" ")
|
||||||
)
|
)
|
||||||
@ -123,7 +127,7 @@ async function appBuild_webpack(params: {
|
|||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let commandCwd = buildContext.bundler.packageJsonDirPath;
|
let commandCwd = pathDirname(buildContext.packageJsonFilePath);
|
||||||
|
|
||||||
for (const subCommand of appBuildSubCommands) {
|
for (const subCommand of appBuildSubCommands) {
|
||||||
const dIsSuccess = new Deferred<boolean>();
|
const dIsSuccess = new Deferred<boolean>();
|
||||||
@ -152,7 +156,7 @@ async function appBuild_webpack(params: {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
pathJoin(
|
pathJoin(
|
||||||
buildContext.bundler.packageJsonDirPath,
|
pathDirname(buildContext.packageJsonFilePath),
|
||||||
"node_modules",
|
"node_modules",
|
||||||
".bin"
|
".bin"
|
||||||
),
|
),
|
||||||
|
@ -63,7 +63,7 @@ export async function downloadAndExtractArchive(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractDirBasename = `${archiveFileBasename.split(".")[0]}_${uniqueIdOfOnArchiveFile}_${crypto
|
const extractDirBasename = `${archiveFileBasename.replace(/\.([^.]+)$/, (...[, ext]) => `_${ext}`)}_${uniqueIdOfOnArchiveFile}_${crypto
|
||||||
.createHash("sha256")
|
.createHash("sha256")
|
||||||
.update(onArchiveFile.toString())
|
.update(onArchiveFile.toString())
|
||||||
.digest("hex")
|
.digest("hex")
|
||||||
@ -85,7 +85,9 @@ export async function downloadAndExtractArchive(params: {
|
|||||||
})()
|
})()
|
||||||
)
|
)
|
||||||
.map(async extractDirBasename => {
|
.map(async extractDirBasename => {
|
||||||
await rm(pathJoin(cacheDirPath, extractDirBasename), { recursive: true });
|
await rm(pathJoin(cacheDirPath, extractDirBasename), {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
await SuccessTracker.removeFromExtracted({
|
await SuccessTracker.removeFromExtracted({
|
||||||
cacheDirPath,
|
cacheDirPath,
|
||||||
extractDirBasename
|
extractDirBasename
|
||||||
|
63
src/bin/tools/npmInstall.ts
Normal file
63
src/bin/tools/npmInstall.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import * as child_process from "child_process";
|
||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
export function npmInstall(params: { packageJsonDirPath: string }) {
|
||||||
|
const { packageJsonDirPath } = params;
|
||||||
|
|
||||||
|
const packageManagerBinName = (() => {
|
||||||
|
const packageMangers = [
|
||||||
|
{
|
||||||
|
binName: "yarn",
|
||||||
|
lockFileBasename: "yarn.lock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
binName: "npm",
|
||||||
|
lockFileBasename: "package-lock.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
binName: "pnpm",
|
||||||
|
lockFileBasename: "pnpm-lock.yaml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
binName: "bun",
|
||||||
|
lockFileBasename: "bun.lockdb"
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
for (const packageManager of packageMangers) {
|
||||||
|
if (
|
||||||
|
fs.existsSync(
|
||||||
|
pathJoin(packageJsonDirPath, packageManager.lockFileBasename)
|
||||||
|
) ||
|
||||||
|
fs.existsSync(pathJoin(process.cwd(), packageManager.lockFileBasename))
|
||||||
|
) {
|
||||||
|
return packageManager.binName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
})();
|
||||||
|
|
||||||
|
install_dependencies: {
|
||||||
|
if (packageManagerBinName === undefined) {
|
||||||
|
break install_dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Installing the new dependencies...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
child_process.execSync(`${packageManagerBinName} install`, {
|
||||||
|
cwd: packageJsonDirPath,
|
||||||
|
stdio: "inherit"
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(
|
||||||
|
`\`${packageManagerBinName} install\` failed, continuing anyway...`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,13 +9,14 @@ export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
|
|||||||
owner: string;
|
owner: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
count: number;
|
count: number;
|
||||||
|
doIgnoreReleaseCandidates: boolean;
|
||||||
}): Promise<
|
}): Promise<
|
||||||
{
|
{
|
||||||
tag: string;
|
tag: string;
|
||||||
version: SemVer;
|
version: SemVer;
|
||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { owner, repo, count } = params;
|
const { owner, repo, count, doIgnoreReleaseCandidates } = params;
|
||||||
|
|
||||||
const semVersionedTags: { tag: string; version: SemVer }[] = [];
|
const semVersionedTags: { tag: string; version: SemVer }[] = [];
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version.rc !== undefined) {
|
if (doIgnoreReleaseCandidates && version.rc !== undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,5 +8,7 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "../../dist/bin",
|
"outDir": "../../dist/bin",
|
||||||
"rootDir": "."
|
"rootDir": "."
|
||||||
}
|
},
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["initialize-account-theme/src"]
|
||||||
}
|
}
|
||||||
|
@ -71,14 +71,10 @@ export function createGetKcClsx<ClassKey extends string>(params: {
|
|||||||
transform: classKey => {
|
transform: classKey => {
|
||||||
assert(is<ClassKey>(classKey));
|
assert(is<ClassKey>(classKey));
|
||||||
|
|
||||||
const className = classes?.[classKey];
|
|
||||||
|
|
||||||
return clsx(
|
return clsx(
|
||||||
classKey,
|
classKey,
|
||||||
doUseDefaultCss && !className?.split(" ").includes("CLEAR")
|
classes?.[classKey] ??
|
||||||
? defaultClasses[classKey]
|
(doUseDefaultCss ? defaultClasses[classKey] : undefined)
|
||||||
: undefined,
|
|
||||||
className
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useReducer, Fragment } from "react";
|
import { useEffect, useReducer, Fragment } from "react";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "keycloakify/tools/assert";
|
||||||
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||||
import {
|
import {
|
||||||
useUserProfileForm,
|
useUserProfileForm,
|
||||||
@ -70,7 +70,7 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps<
|
|||||||
{advancedMsg(attribute.annotations.inputHelperTextBefore)}
|
{advancedMsg(attribute.annotations.inputHelperTextBefore)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<InputFiledByType
|
<InputFieldByType
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
valueOrValues={valueOrValues}
|
valueOrValues={valueOrValues}
|
||||||
displayableErrors={displayableErrors}
|
displayableErrors={displayableErrors}
|
||||||
@ -196,7 +196,7 @@ function FieldErrors(props: { attribute: Attribute; displayableErrors: FormField
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type InputFiledByTypeProps = {
|
type InputFieldByTypeProps = {
|
||||||
attribute: Attribute;
|
attribute: Attribute;
|
||||||
valueOrValues: string | string[];
|
valueOrValues: string | string[];
|
||||||
displayableErrors: FormFieldError[];
|
displayableErrors: FormFieldError[];
|
||||||
@ -205,7 +205,7 @@ type InputFiledByTypeProps = {
|
|||||||
kcClsx: KcClsx;
|
kcClsx: KcClsx;
|
||||||
};
|
};
|
||||||
|
|
||||||
function InputFiledByType(props: InputFiledByTypeProps) {
|
function InputFieldByType(props: InputFieldByTypeProps) {
|
||||||
const { attribute, valueOrValues } = props;
|
const { attribute, valueOrValues } = props;
|
||||||
|
|
||||||
switch (attribute.annotations.inputType) {
|
switch (attribute.annotations.inputType) {
|
||||||
@ -274,9 +274,11 @@ function PasswordWrapper(props: { kcClsx: KcClsx; i18n: I18n; passwordInputId: s
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputTag(props: InputFiledByTypeProps & { fieldIndex: number | undefined }) {
|
function InputTag(props: InputFieldByTypeProps & { fieldIndex: number | undefined }) {
|
||||||
const { attribute, fieldIndex, kcClsx, dispatchFormAction, valueOrValues, i18n, displayableErrors } = props;
|
const { attribute, fieldIndex, kcClsx, dispatchFormAction, valueOrValues, i18n, displayableErrors } = props;
|
||||||
|
|
||||||
|
const { advancedMsgStr } = i18n;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
@ -305,7 +307,9 @@ function InputTag(props: InputFiledByTypeProps & { fieldIndex: number | undefine
|
|||||||
aria-invalid={displayableErrors.find(error => error.fieldIndex === fieldIndex) !== undefined}
|
aria-invalid={displayableErrors.find(error => error.fieldIndex === fieldIndex) !== undefined}
|
||||||
disabled={attribute.readOnly}
|
disabled={attribute.readOnly}
|
||||||
autoComplete={attribute.autocomplete}
|
autoComplete={attribute.autocomplete}
|
||||||
placeholder={attribute.annotations.inputTypePlaceholder}
|
placeholder={
|
||||||
|
attribute.annotations.inputTypePlaceholder === undefined ? undefined : advancedMsgStr(attribute.annotations.inputTypePlaceholder)
|
||||||
|
}
|
||||||
pattern={attribute.annotations.inputTypePattern}
|
pattern={attribute.annotations.inputTypePattern}
|
||||||
size={attribute.annotations.inputTypeSize === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeSize}`)}
|
size={attribute.annotations.inputTypeSize === undefined ? undefined : parseInt(`${attribute.annotations.inputTypeSize}`)}
|
||||||
maxLength={
|
maxLength={
|
||||||
@ -429,7 +433,7 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputTagSelects(props: InputFiledByTypeProps) {
|
function InputTagSelects(props: InputFieldByTypeProps) {
|
||||||
const { attribute, dispatchFormAction, kcClsx, valueOrValues } = props;
|
const { attribute, dispatchFormAction, kcClsx, valueOrValues } = props;
|
||||||
|
|
||||||
const { advancedMsg } = props.i18n;
|
const { advancedMsg } = props.i18n;
|
||||||
@ -537,7 +541,7 @@ function InputTagSelects(props: InputFiledByTypeProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextareaTag(props: InputFiledByTypeProps) {
|
function TextareaTag(props: InputFieldByTypeProps) {
|
||||||
const { attribute, dispatchFormAction, kcClsx, displayableErrors, valueOrValues } = props;
|
const { attribute, dispatchFormAction, kcClsx, displayableErrors, valueOrValues } = props;
|
||||||
|
|
||||||
assert(typeof valueOrValues === "string");
|
assert(typeof valueOrValues === "string");
|
||||||
@ -573,7 +577,7 @@ function TextareaTag(props: InputFiledByTypeProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectTag(props: InputFiledByTypeProps) {
|
function SelectTag(props: InputFieldByTypeProps) {
|
||||||
const { attribute, dispatchFormAction, kcClsx, displayableErrors, i18n, valueOrValues } = props;
|
const { attribute, dispatchFormAction, kcClsx, displayableErrors, i18n, valueOrValues } = props;
|
||||||
|
|
||||||
const { advancedMsg } = i18n;
|
const { advancedMsg } = i18n;
|
||||||
|
@ -429,6 +429,28 @@ export function useUserProfileForm(params: UseUserProfileFormParams): ReturnType
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trigger_password_confirm_validation_on_password_change: {
|
||||||
|
if (!doMakeUserConfirmPassword) {
|
||||||
|
break trigger_password_confirm_validation_on_password_change;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formAction.name !== "password") {
|
||||||
|
break trigger_password_confirm_validation_on_password_change;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = reducer(state, {
|
||||||
|
action: "update",
|
||||||
|
name: "password-confirm",
|
||||||
|
valueOrValues: (() => {
|
||||||
|
const formFieldState = state.formFieldStates.find(({ attribute }) => attribute.name === "password-confirm");
|
||||||
|
|
||||||
|
assert(formFieldState !== undefined);
|
||||||
|
|
||||||
|
return formFieldState.valueOrValues;
|
||||||
|
})()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case "focus lost":
|
case "focus lost":
|
||||||
if (formFieldState.hasLostFocusAtLeastOnce instanceof Array) {
|
if (formFieldState.hasLostFocusAtLeastOnce instanceof Array) {
|
||||||
|
@ -32,6 +32,7 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
|
|||||||
<label htmlFor="password-new" className={kcClsx("kcLabelClass")}>
|
<label htmlFor="password-new" className={kcClsx("kcLabelClass")}>
|
||||||
{msg("passwordNew")}
|
{msg("passwordNew")}
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<div className={kcClsx("kcInputWrapperClass")}>
|
<div className={kcClsx("kcInputWrapperClass")}>
|
||||||
<PasswordWrapper kcClsx={kcClsx} i18n={i18n} passwordInputId="password-new">
|
<PasswordWrapper kcClsx={kcClsx} i18n={i18n} passwordInputId="password-new">
|
||||||
<input
|
<input
|
||||||
@ -57,7 +58,6 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={kcClsx("kcFormGroupClass")}>
|
<div className={kcClsx("kcFormGroupClass")}>
|
||||||
<div className={kcClsx("kcLabelWrapperClass")}>
|
<div className={kcClsx("kcLabelWrapperClass")}>
|
||||||
@ -89,16 +89,15 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className={kcClsx("kcFormGroupClass")}>
|
<div className={kcClsx("kcFormGroupClass")}>
|
||||||
<LogoutOtherSessions kcClsx={kcClsx} i18n={i18n} />
|
<LogoutOtherSessions kcClsx={kcClsx} i18n={i18n} />
|
||||||
|
|
||||||
<div id="kc-form-buttons" className={kcClsx("kcFormButtonsClass")}>
|
<div id="kc-form-buttons" className={kcClsx("kcFormButtonsClass")}>
|
||||||
<input
|
<input
|
||||||
className={kcClsx(
|
className={kcClsx(
|
||||||
"kcButtonClass",
|
"kcButtonClass",
|
||||||
"kcButtonPrimaryClass",
|
"kcButtonPrimaryClass",
|
||||||
isAppInitiatedAction && "kcButtonBlockClass",
|
!isAppInitiatedAction && "kcButtonBlockClass",
|
||||||
"kcButtonLargeClass"
|
"kcButtonLargeClass"
|
||||||
)}
|
)}
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -116,7 +115,6 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
|
@ -18,12 +18,14 @@ import {
|
|||||||
import MagicString from "magic-string";
|
import MagicString from "magic-string";
|
||||||
import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
|
import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
|
||||||
|
|
||||||
|
export namespace keycloakify {
|
||||||
export type Params = BuildOptions & {
|
export type Params = BuildOptions & {
|
||||||
postBuild?: (buildContext: Omit<BuildContext, "bundler">) => Promise<void>;
|
postBuild?: (buildContext: Omit<BuildContext, "bundler">) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function keycloakify(params?: Params) {
|
export function keycloakify(params: keycloakify.Params) {
|
||||||
const { postBuild, ...buildOptions } = params ?? {};
|
const { postBuild, ...buildOptions } = params;
|
||||||
|
|
||||||
let projectDirPath: string | undefined = undefined;
|
let projectDirPath: string | undefined = undefined;
|
||||||
let urlPathname: string | undefined = undefined;
|
let urlPathname: string | undefined = undefined;
|
||||||
|
@ -16,5 +16,6 @@
|
|||||||
// https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010
|
// https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["../src", "."]
|
"include": ["../src", "."],
|
||||||
|
"exclude": ["../src/bin/initialize-account-theme/src"]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user