Implement admin theme support (checkpoint)
This commit is contained in:
parent
dc4eac1a04
commit
0e93d4ed09
@ -37,7 +37,7 @@ async function generateI18nMessages() {
|
||||
|
||||
const record: { [themeType: string]: { [language: string]: Dictionary } } = {};
|
||||
|
||||
for (const themeType of THEME_TYPES) {
|
||||
for (const themeType of THEME_TYPES.filter(themeType => themeType !== "admin")) {
|
||||
const { extractedDirPath } = await downloadKeycloakDefaultTheme({
|
||||
keycloakVersionId: (() => {
|
||||
switch (themeType) {
|
||||
|
@ -5,8 +5,7 @@ import {
|
||||
ACCOUNT_THEME_PAGE_IDS,
|
||||
type LoginThemePageId,
|
||||
type AccountThemePageId,
|
||||
THEME_TYPES,
|
||||
type ThemeType
|
||||
THEME_TYPES
|
||||
} from "./shared/constants";
|
||||
import { capitalize } from "tsafe/capitalize";
|
||||
import * as fs from "fs";
|
||||
@ -39,6 +38,8 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
return buildContext.implementedThemeTypes.account.isImplemented;
|
||||
case "login":
|
||||
return buildContext.implementedThemeTypes.login.isImplemented;
|
||||
case "admin":
|
||||
return buildContext.implementedThemeTypes.admin.isImplemented;
|
||||
}
|
||||
assert<Equals<typeof themeType, never>>(false);
|
||||
});
|
||||
@ -49,7 +50,7 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
return values[0];
|
||||
}
|
||||
|
||||
const { value } = await cliSelect<ThemeType>({
|
||||
const { value } = await cliSelect({
|
||||
values
|
||||
}).catch(() => {
|
||||
process.exit(-1);
|
||||
@ -68,6 +69,16 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
);
|
||||
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (themeType === "admin") {
|
||||
console.log(
|
||||
`${chalk.red("✗")} Sorry, there is no Storybook support for the Account UI.`
|
||||
);
|
||||
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`→ ${themeType}`);
|
||||
|
@ -7,8 +7,7 @@ import {
|
||||
ACCOUNT_THEME_PAGE_IDS,
|
||||
type LoginThemePageId,
|
||||
type AccountThemePageId,
|
||||
THEME_TYPES,
|
||||
type ThemeType
|
||||
THEME_TYPES
|
||||
} from "./shared/constants";
|
||||
import { capitalize } from "tsafe/capitalize";
|
||||
import * as fs from "fs";
|
||||
@ -46,6 +45,8 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
return buildContext.implementedThemeTypes.account.isImplemented;
|
||||
case "login":
|
||||
return buildContext.implementedThemeTypes.login.isImplemented;
|
||||
case "admin":
|
||||
return buildContext.implementedThemeTypes.admin.isImplemented;
|
||||
}
|
||||
assert<Equals<typeof themeType, never>>(false);
|
||||
});
|
||||
@ -56,7 +57,7 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
return values[0];
|
||||
}
|
||||
|
||||
const { value } = await cliSelect<ThemeType>({
|
||||
const { value } = await cliSelect({
|
||||
values
|
||||
}).catch(() => {
|
||||
process.exit(-1);
|
||||
@ -66,21 +67,22 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
})();
|
||||
|
||||
if (
|
||||
themeType === "account" &&
|
||||
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
||||
buildContext.implementedThemeTypes.account.type === "Single-Page")
|
||||
themeType === "admin" ||
|
||||
(themeType === "account" &&
|
||||
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
||||
buildContext.implementedThemeTypes.account.type === "Single-Page"))
|
||||
) {
|
||||
const srcDirPath = pathJoin(
|
||||
pathDirname(buildContext.packageJsonFilePath),
|
||||
"node_modules",
|
||||
"@keycloakify",
|
||||
"keycloak-account-ui",
|
||||
`keycloak-${themeType}-ui`,
|
||||
"src"
|
||||
);
|
||||
|
||||
console.log(
|
||||
[
|
||||
`There isn't an interactive CLI to eject components of the Single-Page Account theme.`,
|
||||
`There isn't an interactive CLI to eject components of the ${themeType} UI.`,
|
||||
`You can however copy paste into your codebase the any file or directory from the following source directory:`,
|
||||
``,
|
||||
`${chalk.bold(pathJoin(pathRelative(process.cwd(), srcDirPath)))}`,
|
||||
@ -89,7 +91,8 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
);
|
||||
|
||||
eject_entrypoint: {
|
||||
const kcAccountUiTsxFileRelativePath = "KcAccountUi.tsx";
|
||||
const kcAccountUiTsxFileRelativePath =
|
||||
`Kc${capitalize(themeType)}Ui.tsx` as const;
|
||||
|
||||
const accountThemeSrcDirPath = pathJoin(
|
||||
buildContext.themeSrcDirPath,
|
||||
@ -120,7 +123,7 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
).replace(/.tsx$/, "");
|
||||
|
||||
const modifiedKcPageTsxCode = kcPageTsxCode.replace(
|
||||
`@keycloakify/keycloak-account-ui/${componentName}`,
|
||||
`@keycloakify/keycloak-${themeType}-ui/${componentName}`,
|
||||
`./${componentName}`
|
||||
);
|
||||
|
||||
@ -146,6 +149,7 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`→ ${themeType}`);
|
||||
|
@ -1,12 +1,12 @@
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
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 { command as updateKcGenCommand } from "../update-kc-gen";
|
||||
import { maybeDelegateCommandToCustomHandler } from "../shared/customHandler_delegate";
|
||||
import { exitIfUncommittedChanges } from "../shared/exitIfUncommittedChanges";
|
||||
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
@ -38,37 +38,9 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
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);
|
||||
}
|
||||
exitIfUncommittedChanges({
|
||||
projectDirPath: buildContext.projectDirPath
|
||||
});
|
||||
|
||||
const { value: accountThemeType } = await cliSelect({
|
||||
values: ["Single-Page" as const, "Multi-Page" as const]
|
||||
|
19
src/bin/initialize-admin-theme/copyBoilerplate.ts
Normal file
19
src/bin/initialize-admin-theme/copyBoilerplate.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
||||
|
||||
export function copyBoilerplate(params: { adminThemeSrcDirPath: string }) {
|
||||
const { adminThemeSrcDirPath } = params;
|
||||
|
||||
fs.cpSync(
|
||||
pathJoin(
|
||||
getThisCodebaseRootDirPath(),
|
||||
"src",
|
||||
"bin",
|
||||
"initialize-admin-theme",
|
||||
"src"
|
||||
),
|
||||
adminThemeSrcDirPath,
|
||||
{ recursive: true }
|
||||
);
|
||||
}
|
1
src/bin/initialize-admin-theme/index.ts
Normal file
1
src/bin/initialize-admin-theme/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./initialize-admin-theme";
|
60
src/bin/initialize-admin-theme/initialize-admin-theme.ts
Normal file
60
src/bin/initialize-admin-theme/initialize-admin-theme.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import * as fs from "fs";
|
||||
import { command as updateKcGenCommand } from "../update-kc-gen";
|
||||
import { maybeDelegateCommandToCustomHandler } from "../shared/customHandler_delegate";
|
||||
import { exitIfUncommittedChanges } from "../shared/exitIfUncommittedChanges";
|
||||
import { initializeAdminTheme } from "./initializeAdminTheme";
|
||||
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({
|
||||
commandName: "initialize-admin-theme",
|
||||
buildContext
|
||||
});
|
||||
|
||||
if (hasBeenHandled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const adminThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "admin");
|
||||
|
||||
if (
|
||||
fs.existsSync(adminThemeSrcDirPath) &&
|
||||
fs.readdirSync(adminThemeSrcDirPath).length > 0
|
||||
) {
|
||||
console.warn(
|
||||
chalk.red(
|
||||
`There is already a ${pathRelative(
|
||||
process.cwd(),
|
||||
adminThemeSrcDirPath
|
||||
)} directory in your project. Aborting.`
|
||||
)
|
||||
);
|
||||
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
exitIfUncommittedChanges({
|
||||
projectDirPath: buildContext.projectDirPath
|
||||
});
|
||||
|
||||
await initializeAdminTheme({
|
||||
adminThemeSrcDirPath,
|
||||
buildContext
|
||||
});
|
||||
|
||||
await updateKcGenCommand({
|
||||
buildContext: {
|
||||
...buildContext,
|
||||
implementedThemeTypes: {
|
||||
...buildContext.implementedThemeTypes,
|
||||
admin: {
|
||||
isImplemented: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
150
src/bin/initialize-admin-theme/initializeAdminTheme.ts
Normal file
150
src/bin/initialize-admin-theme/initializeAdminTheme.ts
Normal file
@ -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,
|
||||
type BuildContextLike as BuildContextLike_getLatestsSemVersionedTag
|
||||
} from "../shared/getLatestsSemVersionedTag";
|
||||
import { SemVer } from "../tools/SemVer";
|
||||
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 = BuildContextLike_getLatestsSemVersionedTag & {
|
||||
fetchOptions: BuildContext["fetchOptions"];
|
||||
packageJsonFilePath: string;
|
||||
};
|
||||
|
||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||
|
||||
export async function initializeAdminTheme(params: {
|
||||
adminThemeSrcDirPath: string;
|
||||
buildContext: BuildContextLike;
|
||||
}) {
|
||||
const { adminThemeSrcDirPath, buildContext } = params;
|
||||
|
||||
const OWNER = "keycloakify";
|
||||
const REPO = "keycloak-admin-ui";
|
||||
|
||||
const [semVersionedTag] = await getLatestsSemVersionedTag({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
count: 1,
|
||||
doIgnoreReleaseCandidates: false,
|
||||
buildContext
|
||||
});
|
||||
|
||||
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-admin-ui"] = SemVer.stringify(
|
||||
semVersionedTag.version
|
||||
);
|
||||
|
||||
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({ adminThemeSrcDirPath });
|
||||
|
||||
console.log(
|
||||
[
|
||||
chalk.green("The Admin theme has been successfully initialized."),
|
||||
`Using Admin UI of Keycloak version: ${chalk.bold(semVersionedTag.tag.split("-")[0])}`,
|
||||
`Directory created: ${chalk.bold(pathRelative(process.cwd(), adminThemeSrcDirPath))}`,
|
||||
`Dependencies added to your project's package.json: `,
|
||||
chalk.bold(JSON.stringify(dependencies, null, 2))
|
||||
].join("\n")
|
||||
);
|
||||
}
|
7
src/bin/initialize-admin-theme/src/KcContext.ts
Normal file
7
src/bin/initialize-admin-theme/src/KcContext.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { KcContextLike } from "@keycloakify/keycloak-admin-ui";
|
||||
import type { KcEnvName } from "../kc.gen";
|
||||
|
||||
export type KcContext = KcContextLike & {
|
||||
themeType: "admin";
|
||||
properties: Record<KcEnvName, string>;
|
||||
};
|
11
src/bin/initialize-admin-theme/src/KcPage.tsx
Normal file
11
src/bin/initialize-admin-theme/src/KcPage.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { lazy } from "react";
|
||||
import { KcAdminUiLoader } from "@keycloakify/keycloak-admin-ui";
|
||||
import type { KcContext } from "./KcContext";
|
||||
|
||||
const KcAdminUi = lazy(() => import("@keycloakify/keycloak-admin-ui/KcAdminUi"));
|
||||
|
||||
export default function KcPage(props: { kcContext: KcContext }) {
|
||||
const { kcContext } = props;
|
||||
|
||||
return <KcAdminUiLoader kcContext={kcContext} KcAdminUi={KcAdminUi} />;
|
||||
}
|
@ -22,7 +22,7 @@ assert<BuildContext extends BuildContextLike ? true : false>();
|
||||
|
||||
export function generateMessageProperties(params: {
|
||||
buildContext: BuildContextLike;
|
||||
themeType: ThemeType;
|
||||
themeType: Exclude<ThemeType, "admin">;
|
||||
}): {
|
||||
languageTags: string[];
|
||||
writeMessagePropertiesFiles: (params: {
|
||||
|
@ -19,7 +19,8 @@ import {
|
||||
type ThemeType,
|
||||
LOGIN_THEME_PAGE_IDS,
|
||||
ACCOUNT_THEME_PAGE_IDS,
|
||||
WELL_KNOWN_DIRECTORY_BASE_NAME
|
||||
WELL_KNOWN_DIRECTORY_BASE_NAME,
|
||||
THEME_TYPES
|
||||
} from "../../shared/constants";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { readFieldNameUsage } from "./readFieldNameUsage";
|
||||
@ -78,15 +79,29 @@ export async function generateResources(params: {
|
||||
Record<ThemeType, (params: { messageDirPath: string; themeName: string }) => void>
|
||||
> = {};
|
||||
|
||||
for (const themeType of ["login", "account"] as const) {
|
||||
for (const themeType of THEME_TYPES) {
|
||||
if (!buildContext.implementedThemeTypes[themeType].isImplemented) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isForAccountSpa =
|
||||
themeType === "account" &&
|
||||
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
||||
buildContext.implementedThemeTypes.account.type === "Single-Page");
|
||||
const getAccountThemeType = () => {
|
||||
assert(themeType === "account");
|
||||
|
||||
assert(buildContext.implementedThemeTypes.account.isImplemented);
|
||||
|
||||
return buildContext.implementedThemeTypes.account.type;
|
||||
};
|
||||
|
||||
const isSpa = (() => {
|
||||
switch (themeType) {
|
||||
case "login":
|
||||
return false;
|
||||
case "account":
|
||||
return getAccountThemeType() === "Single-Page";
|
||||
case "admin":
|
||||
return true;
|
||||
}
|
||||
})();
|
||||
|
||||
const themeTypeDirPath = getThemeTypeDirPath({ themeName, themeType });
|
||||
|
||||
@ -101,7 +116,7 @@ export async function generateResources(params: {
|
||||
rmSync(destDirPath, { recursive: true, force: true });
|
||||
|
||||
if (
|
||||
themeType === "account" &&
|
||||
themeType !== "login" &&
|
||||
buildContext.implementedThemeTypes.login.isImplemented
|
||||
) {
|
||||
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
||||
@ -194,10 +209,14 @@ export async function generateResources(params: {
|
||||
case "login":
|
||||
return LOGIN_THEME_PAGE_IDS;
|
||||
case "account":
|
||||
return isForAccountSpa ? ["index.ftl"] : ACCOUNT_THEME_PAGE_IDS;
|
||||
return getAccountThemeType() === "Single-Page"
|
||||
? ["index.ftl"]
|
||||
: ACCOUNT_THEME_PAGE_IDS;
|
||||
case "admin":
|
||||
return ["index.ftl"];
|
||||
}
|
||||
})(),
|
||||
...(isForAccountSpa
|
||||
...(isSpa
|
||||
? []
|
||||
: readExtraPagesNames({
|
||||
themeType,
|
||||
@ -215,10 +234,12 @@ export async function generateResources(params: {
|
||||
let languageTags: string[] | undefined = undefined;
|
||||
|
||||
i18n_messages_generation: {
|
||||
if (isForAccountSpa) {
|
||||
if (isSpa) {
|
||||
break i18n_messages_generation;
|
||||
}
|
||||
|
||||
assert(themeType !== "admin");
|
||||
|
||||
const wrap = generateMessageProperties({
|
||||
buildContext,
|
||||
themeType
|
||||
@ -231,16 +252,15 @@ export async function generateResources(params: {
|
||||
writeMessagePropertiesFiles;
|
||||
}
|
||||
|
||||
bring_in_account_v3_i18n_messages: {
|
||||
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;
|
||||
bring_in_spas_messages: {
|
||||
if (!isSpa) {
|
||||
break bring_in_spas_messages;
|
||||
}
|
||||
|
||||
assert(themeType !== "login");
|
||||
|
||||
const accountUiDirPath = child_process
|
||||
.execSync("npm list @keycloakify/keycloak-account-ui --parseable", {
|
||||
.execSync(`npm list @keycloakify/keycloak-${themeType}-ui --parseable`, {
|
||||
cwd: pathDirname(buildContext.packageJsonFilePath)
|
||||
})
|
||||
.toString("utf8")
|
||||
@ -255,7 +275,7 @@ export async function generateResources(params: {
|
||||
}
|
||||
|
||||
const messagesDirPath_dest = pathJoin(
|
||||
getThemeTypeDirPath({ themeName, themeType: "account" }),
|
||||
getThemeTypeDirPath({ themeName, themeType }),
|
||||
"messages"
|
||||
);
|
||||
|
||||
@ -267,7 +287,7 @@ export async function generateResources(params: {
|
||||
apply_theme_changes: {
|
||||
const messagesDirPath_theme = pathJoin(
|
||||
buildContext.themeSrcDirPath,
|
||||
"account",
|
||||
themeType,
|
||||
"messages"
|
||||
);
|
||||
|
||||
@ -316,7 +336,7 @@ export async function generateResources(params: {
|
||||
}
|
||||
|
||||
keycloak_static_resources: {
|
||||
if (isForAccountSpa) {
|
||||
if (isSpa) {
|
||||
break keycloak_static_resources;
|
||||
}
|
||||
|
||||
@ -339,13 +359,22 @@ export async function generateResources(params: {
|
||||
`parent=${(() => {
|
||||
switch (themeType) {
|
||||
case "account":
|
||||
return isForAccountSpa ? "base" : "account-v1";
|
||||
switch (getAccountThemeType()) {
|
||||
case "Multi-Page":
|
||||
return "account-v1";
|
||||
case "Single-Page":
|
||||
return "base";
|
||||
}
|
||||
case "login":
|
||||
return "keycloak";
|
||||
case "admin":
|
||||
return "base";
|
||||
}
|
||||
assert<Equals<typeof themeType, never>>(false);
|
||||
})()}`,
|
||||
...(isForAccountSpa ? ["deprecatedMode=false"] : []),
|
||||
...(themeType === "account" && getAccountThemeType() === "Single-Page"
|
||||
? ["deprecatedMode=false"]
|
||||
: []),
|
||||
...(buildContext.extraThemeProperties ?? []),
|
||||
...buildContext.environmentVariables.map(
|
||||
({ name, default: defaultValue }) =>
|
||||
|
@ -197,6 +197,20 @@ program
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command({
|
||||
name: "initialize-admin-theme",
|
||||
description: "Initialize the admin theme."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const { command } = await import("./initialize-admin-theme");
|
||||
|
||||
await command({ buildContext: getBuildContext({ projectDirPath }) });
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command({
|
||||
name: "copy-keycloak-resources-to-public",
|
||||
|
@ -52,6 +52,7 @@ export type BuildContext = {
|
||||
account:
|
||||
| { isImplemented: false }
|
||||
| { isImplemented: true; type: "Single-Page" | "Multi-Page" };
|
||||
admin: { isImplemented: boolean };
|
||||
};
|
||||
packageJsonFilePath: string;
|
||||
bundler: "vite" | "webpack";
|
||||
@ -448,7 +449,10 @@ export function getBuildContext(params: {
|
||||
isImplemented: true,
|
||||
type: buildOptions.accountThemeImplementation
|
||||
};
|
||||
})()
|
||||
})(),
|
||||
admin: {
|
||||
isImplemented: fs.existsSync(pathJoin(themeSrcDirPath, "admin"))
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
|
@ -4,7 +4,7 @@ export const WELL_KNOWN_DIRECTORY_BASE_NAME = {
|
||||
DIST: "dist"
|
||||
} as const;
|
||||
|
||||
export const THEME_TYPES = ["login", "account"] as const;
|
||||
export const THEME_TYPES = ["login", "account", "admin"] as const;
|
||||
|
||||
export type ThemeType = (typeof THEME_TYPES)[number];
|
||||
|
||||
|
@ -11,6 +11,7 @@ export type CommandName =
|
||||
| "eject-page"
|
||||
| "add-story"
|
||||
| "initialize-account-theme"
|
||||
| "initialize-admin-theme"
|
||||
| "initialize-email-theme"
|
||||
| "copy-keycloak-resources-to-public";
|
||||
|
||||
|
36
src/bin/shared/exitIfUncommittedChanges.ts
Normal file
36
src/bin/shared/exitIfUncommittedChanges.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import child_process from "child_process";
|
||||
import chalk from "chalk";
|
||||
|
||||
export function exitIfUncommittedChanges(params: { projectDirPath: string }) {
|
||||
const { projectDirPath } = params;
|
||||
|
||||
let hasUncommittedChanges: boolean | undefined = undefined;
|
||||
|
||||
try {
|
||||
hasUncommittedChanges =
|
||||
child_process
|
||||
.execSync(`git status --porcelain`, {
|
||||
cwd: projectDirPath
|
||||
})
|
||||
.toString()
|
||||
.trim() !== "";
|
||||
} catch {
|
||||
// Probably not a git repository
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasUncommittedChanges) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
@ -10,5 +10,5 @@
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["initialize-account-theme/src"]
|
||||
"exclude": ["initialize-account-theme/src", "initialize-admin-theme/src"]
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
|
||||
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
|
||||
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
|
||||
const hasAdminTheme = buildContext.implementedThemeTypes.admin.isImplemented;
|
||||
|
||||
const newContent = [
|
||||
``,
|
||||
@ -54,6 +55,7 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
`export type KcContext =`,
|
||||
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
|
||||
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
|
||||
hasAdminTheme && ` | import("./admin/KcContext").KcContext`,
|
||||
` ;`,
|
||||
``,
|
||||
`declare global {`,
|
||||
@ -66,6 +68,8 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
|
||||
hasAccountTheme &&
|
||||
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
|
||||
hasAdminTheme &&
|
||||
`export const KcAdminPage = lazy(() => import("./admin/KcPage"));`,
|
||||
``,
|
||||
`export function KcPage(`,
|
||||
` props: {`,
|
||||
@ -82,6 +86,8 @@ export async function command(params: { buildContext: BuildContext }) {
|
||||
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
|
||||
hasAccountTheme &&
|
||||
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
|
||||
hasAdminTheme &&
|
||||
` case "admin": return <KcAdminPage kcContext={kcContext} />;`,
|
||||
` }`,
|
||||
` })()}`,
|
||||
` </Suspense>`,
|
||||
|
@ -17,5 +17,8 @@
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["../src", "."],
|
||||
"exclude": ["../src/bin/initialize-account-theme/src"]
|
||||
"exclude": [
|
||||
"../src/bin/initialize-account-theme/src",
|
||||
"../src/bin/initialize-admin-theme/src"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user