Make sure the update-kc-gen command is delegated when building with vite

This commit is contained in:
Joseph Garrone 2024-10-06 09:03:15 +02:00
parent 128b27221a
commit bc586eceef
7 changed files with 239 additions and 312 deletions

View File

@ -5,7 +5,7 @@ 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";
import { command as updateKcGenCommand } from "../update-kc-gen";
export async function command(params: { buildContext: BuildContext }) {
const { buildContext } = params;
@ -94,7 +94,7 @@ export async function command(params: { buildContext: BuildContext }) {
updateAccountThemeImplementationInConfig({ buildContext, accountThemeType });
await generateKcGenTs({
await updateKcGenCommand({
buildContext: {
...buildContext,
implementedThemeTypes: {

View File

@ -4,7 +4,6 @@ import { termost } from "termost";
import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
import * as child_process from "child_process";
import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx";
import { callHandlerIfAny } from "./shared/customHandler_caller";
import { getBuildContext } from "./shared/buildContext";
type CliCommandOptions = {
@ -72,153 +71,117 @@ program
.task({
skip,
handler: async ({ projectDirPath }) => {
const buildContext = getBuildContext({ projectDirPath });
const { command } = await import("./keycloakify");
await command({ buildContext });
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
{
const commandName = "start-keycloak";
program
.command<{
port: number | undefined;
keycloakVersion: string | undefined;
realmJsonFilePath: string | undefined;
}>({
name: "start-keycloak",
description:
"Spin up a pre configured Docker image of Keycloak to test your theme."
})
.option({
key: "port",
name: (() => {
const name = "port";
program
.command<{
port: number | undefined;
keycloakVersion: string | undefined;
realmJsonFilePath: string | undefined;
}>({
name: commandName,
description:
"Spin up a pre configured Docker image of Keycloak to test your theme."
})
.option({
key: "port",
name: (() => {
const name = "port";
optionsKeys.push(name);
optionsKeys.push(name);
return name;
})(),
description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
defaultValue: undefined
})
.option({
key: "keycloakVersion",
name: (() => {
const name = "keycloak-version";
return name;
})(),
description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
defaultValue: undefined
})
.option({
key: "keycloakVersion",
name: (() => {
const name = "keycloak-version";
optionsKeys.push(name);
optionsKeys.push(name);
return name;
})(),
description: [
"Use a specific version of Keycloak.",
"Example `--keycloak-version 21.1.1`"
].join(" "),
defaultValue: undefined
})
.option({
key: "realmJsonFilePath",
name: (() => {
const name = "import";
return name;
})(),
description: [
"Use a specific version of Keycloak.",
"Example `--keycloak-version 21.1.1`"
].join(" "),
defaultValue: undefined
})
.option({
key: "realmJsonFilePath",
name: (() => {
const name = "import";
optionsKeys.push(name);
optionsKeys.push(name);
return name;
})(),
defaultValue: undefined,
description: [
"Import your own realm configuration file",
"Example `--import path/to/myrealm-realm.json`"
].join(" ")
})
.task({
skip,
handler: async ({ projectDirPath, keycloakVersion, port, realmJsonFilePath }) => {
const { command } = await import("./start-keycloak");
return name;
})(),
defaultValue: undefined,
description: [
"Import your own realm configuration file",
"Example `--import path/to/myrealm-realm.json`"
].join(" ")
})
.task({
skip,
handler: async ({
projectDirPath,
keycloakVersion,
port,
realmJsonFilePath
}) => {
const buildContext = getBuildContext({ projectDirPath });
await command({
buildContext: getBuildContext({ projectDirPath }),
cliCommandOptions: { keycloakVersion, port, realmJsonFilePath }
});
}
});
const { command } = await import("./start-keycloak");
program
.command({
name: "eject-page",
description: "Eject a Keycloak page."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const { command } = await import("./eject-page");
await command({
buildContext,
cliCommandOptions: { keycloakVersion, port, realmJsonFilePath }
});
}
});
}
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
{
const commandName = "eject-page";
program
.command({
name: "add-story",
description: "Add *.stories.tsx file for a specific page to in your Storybook."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const { command } = await import("./add-story");
program
.command({
name: commandName,
description: "Eject a Keycloak page."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const buildContext = getBuildContext({ projectDirPath });
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
callHandlerIfAny({ buildContext, commandName });
program
.command({
name: "initialize-login-theme",
description: "Initialize an email theme."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const { command } = await import("./initialize-email-theme");
const { command } = await import("./eject-page");
await command({ buildContext });
}
});
}
{
const commandName = "add-story";
program
.command({
name: commandName,
description:
"Add *.stories.tsx file for a specific page to in your Storybook."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const buildContext = getBuildContext({ projectDirPath });
callHandlerIfAny({ buildContext, commandName });
const { command } = await import("./add-story");
await command({ buildContext });
}
});
}
{
const comandName = "initialize-login-theme";
program
.command({
name: comandName,
description: "Initialize an email theme."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const buildContext = getBuildContext({ projectDirPath });
const { command } = await import("./initialize-email-theme");
await command({ buildContext });
}
});
}
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
program
.command({
@ -228,57 +191,41 @@ program
.task({
skip,
handler: async ({ projectDirPath }) => {
const buildContext = getBuildContext({ projectDirPath });
const { command } = await import("./initialize-account-theme");
await command({ buildContext });
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
{
const commandName = "copy-keycloak-resources-to-public";
program
.command({
name: "copy-keycloak-resources-to-public",
description:
"(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const { command } = await import("./copy-keycloak-resources-to-public");
program
.command({
name: commandName,
description:
"(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const buildContext = getBuildContext({ projectDirPath });
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
const { command } = await import("./copy-keycloak-resources-to-public");
program
.command({
name: "update-kc-gen",
description:
"(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const { command } = await import("./update-kc-gen");
await command({ buildContext });
}
});
}
{
const commandName = "update-kc-gen";
program
.command({
name: commandName,
description:
"(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
})
.task({
skip,
handler: async ({ projectDirPath }) => {
const buildContext = getBuildContext({ projectDirPath });
callHandlerIfAny({ buildContext, commandName });
const { command } = await import("./update-kc-gen");
await command({ buildContext });
}
});
}
await command({ buildContext: getBuildContext({ projectDirPath }) });
}
});
// Fallback to build command if no command is provided
{

View File

@ -13,7 +13,7 @@ import * as fs from "fs";
assert<Equals<ApiVersion, "v1">>();
export function callHandlerIfAny(params: {
export function maybeDelegateCommandToCustomHandler(params: {
commandName: CommandName;
buildContext: BuildContext;
}) {

View File

@ -1,127 +0,0 @@
import { assert } from "tsafe/assert";
import type { BuildContext } from "./buildContext";
import * as fs from "fs/promises";
import { join as pathJoin } from "path";
import { existsAsync } from "../tools/fs.existsAsync";
export type BuildContextLike = {
projectDirPath: string;
themeNames: string[];
environmentVariables: { name: string; default: string }[];
themeSrcDirPath: string;
implementedThemeTypes: Pick<
BuildContext["implementedThemeTypes"],
"login" | "account"
>;
packageJsonFilePath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function generateKcGenTs(params: {
buildContext: BuildContextLike;
}): Promise<void> {
const { buildContext } = params;
const filePath = pathJoin(buildContext.themeSrcDirPath, `kc.gen.tsx`);
const currentContent = (await existsAsync(filePath))
? await fs.readFile(filePath)
: undefined;
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
const newContent = Buffer.from(
[
`/* prettier-ignore-start */`,
``,
`/* eslint-disable */`,
``,
`// @ts-nocheck`,
``,
`// noinspection JSUnusedGlobalSymbols`,
``,
`// This file is auto-generated by Keycloakify`,
``,
`import { lazy, Suspense, type ReactNode } from "react";`,
``,
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
``,
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
``,
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
``,
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
``,
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
Object.fromEntries(
buildContext.environmentVariables.map(
({ name, default: defaultValue }) => [name, defaultValue]
)
),
null,
2
)};`,
``,
`export type KcContext =`,
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
` ;`,
``,
`declare global {`,
` interface Window {`,
` kcContext?: KcContext;`,
` }`,
`}`,
``,
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 */`,
``
]
.filter(item => typeof item === "string")
.join("\n"),
"utf8"
);
if (currentContent !== undefined && currentContent.equals(newContent)) {
return;
}
await fs.writeFile(filePath, newContent);
delete_legacy_file: {
const legacyFilePath = filePath.replace(/tsx$/, "ts");
if (!(await existsAsync(legacyFilePath))) {
break delete_legacy_file;
}
await fs.unlink(legacyFilePath);
}
}

View File

@ -1,8 +1,116 @@
import type { BuildContext } from "./shared/buildContext";
import { generateKcGenTs } from "./shared/generateKcGenTs";
import * as fs from "fs/promises";
import { join as pathJoin } from "path";
import { existsAsync } from "./tools/fs.existsAsync";
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
export async function command(params: { buildContext: BuildContext }) {
const { buildContext } = params;
await generateKcGenTs({ buildContext });
maybeDelegateCommandToCustomHandler({
commandName: "update-kc-gen",
buildContext
});
const filePath = pathJoin(buildContext.themeSrcDirPath, `kc.gen.tsx`);
const currentContent = (await existsAsync(filePath))
? await fs.readFile(filePath)
: undefined;
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
const newContent = Buffer.from(
[
`/* prettier-ignore-start */`,
``,
`/* eslint-disable */`,
``,
`// @ts-nocheck`,
``,
`// noinspection JSUnusedGlobalSymbols`,
``,
`// This file is auto-generated by Keycloakify`,
``,
`import { lazy, Suspense, type ReactNode } from "react";`,
``,
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
``,
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
``,
`export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
``,
`export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
``,
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
Object.fromEntries(
buildContext.environmentVariables.map(
({ name, default: defaultValue }) => [name, defaultValue]
)
),
null,
2
)};`,
``,
`export type KcContext =`,
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
` ;`,
``,
`declare global {`,
` interface Window {`,
` kcContext?: KcContext;`,
` }`,
`}`,
``,
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 */`,
``
]
.filter(item => typeof item === "string")
.join("\n"),
"utf8"
);
if (currentContent !== undefined && currentContent.equals(newContent)) {
return;
}
await fs.writeFile(filePath, newContent);
delete_legacy_file: {
const legacyFilePath = filePath.replace(/tsx$/, "ts");
if (!(await existsAsync(legacyFilePath))) {
break delete_legacy_file;
}
await fs.unlink(legacyFilePath);
}
}

View File

@ -15,7 +15,7 @@ import {
type ResolvedViteConfig
} from "../bin/shared/buildContext";
import MagicString from "magic-string";
import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
import { command as updateKcGenCommand } from "../bin/update-kc-gen";
export namespace keycloakify {
export type Params = BuildOptions & {
@ -125,8 +125,9 @@ export function keycloakify(params: keycloakify.Params) {
projectDirPath
});
copyKeycloakResourcesToPublic({ buildContext }),
await generateKcGenTs({ buildContext });
copyKeycloakResourcesToPublic({ buildContext });
await updateKcGenCommand({ buildContext });
},
transform: (code, id) => {
assert(command !== undefined);

View File

@ -115,7 +115,6 @@ export const WithFavoritePet: Story = {
)
};
export const WithNewsletter: Story = {
render: () => (
<KcPageStory
@ -132,7 +131,7 @@ export const WithNewsletter: Story = {
},
annotations: {
inputOptionLabels: {
"yes": "I want my email inbox filled with spam"
yes: "I want my email inbox filled with spam"
},
inputType: "multiselect-checkboxes"
},
@ -140,13 +139,12 @@ export const WithNewsletter: Story = {
readOnly: false
} satisfies Attribute
}
},
}
}}
/>
)
};
export const WithEmailAsUsername: Story = {
render: () => (
<KcPageStory