keycloakify 10.0.0-rc.120 → 10.0.0-rc.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/453.index.js +57 -5
- package/bin/780.index.js +167 -1
- package/bin/786.index.js +185 -0
- package/bin/97.index.js +27 -5
- package/bin/main.js +1 -1
- package/bin/shared/generateKcGenTs.d.ts +3 -0
- package/bin/shared/generateKcGenTs.js.map +1 -1
- package/package.json +2 -2
- package/src/bin/add-story.ts +37 -5
- package/src/bin/eject-page.ts +114 -6
- package/src/bin/initialize-account-theme/initialize-account-theme.ts +18 -1
- package/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl +12 -0
- package/src/bin/shared/generateKcGenTs.ts +110 -3
- package/stories/account/pages/Log.stories.tsx +1 -0
- package/vite-plugin/index.js +40910 -40823
- package/bin/538.index.js +0 -108
package/src/bin/eject-page.ts
CHANGED
@@ -12,7 +12,12 @@ import {
|
|
12
12
|
} from "./shared/constants";
|
13
13
|
import { capitalize } from "tsafe/capitalize";
|
14
14
|
import * as fs from "fs";
|
15
|
-
import {
|
15
|
+
import {
|
16
|
+
join as pathJoin,
|
17
|
+
relative as pathRelative,
|
18
|
+
dirname as pathDirname,
|
19
|
+
basename as pathBasename
|
20
|
+
} from "path";
|
16
21
|
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
17
22
|
import { assert, Equals } from "tsafe/assert";
|
18
23
|
import type { CliCommandOptions } from "./main";
|
@@ -28,11 +33,114 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|
28
33
|
|
29
34
|
console.log(chalk.cyan("Theme type:"));
|
30
35
|
|
31
|
-
const
|
32
|
-
values
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
const themeType = await (async () => {
|
37
|
+
const values = THEME_TYPES.filter(themeType => {
|
38
|
+
switch (themeType) {
|
39
|
+
case "account":
|
40
|
+
return buildContext.implementedThemeTypes.account.isImplemented;
|
41
|
+
case "login":
|
42
|
+
return buildContext.implementedThemeTypes.login.isImplemented;
|
43
|
+
}
|
44
|
+
assert<Equals<typeof themeType, never>>(false);
|
45
|
+
});
|
46
|
+
|
47
|
+
assert(values.length > 0, "No theme is implemented in this project");
|
48
|
+
|
49
|
+
if (values.length === 1) {
|
50
|
+
return values[0];
|
51
|
+
}
|
52
|
+
|
53
|
+
const { value } = await cliSelect<ThemeType>({
|
54
|
+
values
|
55
|
+
}).catch(() => {
|
56
|
+
process.exit(-1);
|
57
|
+
});
|
58
|
+
|
59
|
+
return value;
|
60
|
+
})();
|
61
|
+
|
62
|
+
if (
|
63
|
+
themeType === "account" &&
|
64
|
+
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
65
|
+
buildContext.implementedThemeTypes.account.type === "Single-Page")
|
66
|
+
) {
|
67
|
+
const srcDirPath = pathJoin(
|
68
|
+
pathDirname(buildContext.packageJsonFilePath),
|
69
|
+
"node_modules",
|
70
|
+
"@keycloakify",
|
71
|
+
"keycloak-account-ui",
|
72
|
+
"src"
|
73
|
+
);
|
74
|
+
|
75
|
+
console.log(
|
76
|
+
[
|
77
|
+
`There isn't an interactive CLI to eject components of the Single-Page Account theme.`,
|
78
|
+
`You can however copy paste into your codebase the any file or directory from the following source directory:`,
|
79
|
+
``,
|
80
|
+
`${chalk.bold(pathJoin(pathRelative(process.cwd(), srcDirPath)))}`,
|
81
|
+
``
|
82
|
+
].join("\n")
|
83
|
+
);
|
84
|
+
|
85
|
+
eject_entrypoint: {
|
86
|
+
const kcAccountUiTsxFileRelativePath = "KcAccountUi.tsx";
|
87
|
+
|
88
|
+
const accountThemeSrcDirPath = pathJoin(
|
89
|
+
buildContext.themeSrcDirPath,
|
90
|
+
"account"
|
91
|
+
);
|
92
|
+
|
93
|
+
const targetFilePath = pathJoin(
|
94
|
+
accountThemeSrcDirPath,
|
95
|
+
kcAccountUiTsxFileRelativePath
|
96
|
+
);
|
97
|
+
|
98
|
+
if (fs.existsSync(targetFilePath)) {
|
99
|
+
break eject_entrypoint;
|
100
|
+
}
|
101
|
+
|
102
|
+
fs.cpSync(
|
103
|
+
pathJoin(srcDirPath, kcAccountUiTsxFileRelativePath),
|
104
|
+
targetFilePath
|
105
|
+
);
|
106
|
+
|
107
|
+
{
|
108
|
+
const kcPageTsxFilePath = pathJoin(accountThemeSrcDirPath, "KcPage.tsx");
|
109
|
+
|
110
|
+
const kcPageTsxCode = fs.readFileSync(kcPageTsxFilePath).toString("utf8");
|
111
|
+
|
112
|
+
const componentName = pathBasename(
|
113
|
+
kcAccountUiTsxFileRelativePath
|
114
|
+
).replace(/.tsx$/, "");
|
115
|
+
|
116
|
+
const modifiedKcPageTsxCode = kcPageTsxCode.replace(
|
117
|
+
`@keycloakify/keycloak-account-ui/${componentName}`,
|
118
|
+
`./${componentName}`
|
119
|
+
);
|
120
|
+
|
121
|
+
fs.writeFileSync(
|
122
|
+
kcPageTsxFilePath,
|
123
|
+
Buffer.from(modifiedKcPageTsxCode, "utf8")
|
124
|
+
);
|
125
|
+
}
|
126
|
+
|
127
|
+
const routesTsxFilePath = pathRelative(
|
128
|
+
process.cwd(),
|
129
|
+
pathJoin(srcDirPath, "routes.tsx")
|
130
|
+
);
|
131
|
+
|
132
|
+
console.log(
|
133
|
+
[
|
134
|
+
`To help you get started ${chalk.bold(pathRelative(process.cwd(), targetFilePath))} has been copied into your project.`,
|
135
|
+
`The next step is usually to eject ${chalk.bold(routesTsxFilePath)}`,
|
136
|
+
`with \`cp ${routesTsxFilePath} ${pathRelative(process.cwd(), accountThemeSrcDirPath)}\``,
|
137
|
+
`then update the import of routes in ${kcAccountUiTsxFileRelativePath}.`
|
138
|
+
].join("\n")
|
139
|
+
);
|
140
|
+
}
|
141
|
+
|
142
|
+
process.exit(0);
|
143
|
+
}
|
36
144
|
|
37
145
|
console.log(`→ ${themeType}`);
|
38
146
|
|
@@ -6,6 +6,7 @@ import chalk from "chalk";
|
|
6
6
|
import { join as pathJoin, relative as pathRelative } from "path";
|
7
7
|
import * as fs from "fs";
|
8
8
|
import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig";
|
9
|
+
import { generateKcGenTs } from "../shared/generateKcGenTs";
|
9
10
|
|
10
11
|
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
11
12
|
const { cliCommandOptions } = params;
|
@@ -14,7 +15,10 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|
14
15
|
|
15
16
|
const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
|
16
17
|
|
17
|
-
if (
|
18
|
+
if (
|
19
|
+
fs.existsSync(accountThemeSrcDirPath) &&
|
20
|
+
fs.readdirSync(accountThemeSrcDirPath).length > 0
|
21
|
+
) {
|
18
22
|
console.warn(
|
19
23
|
chalk.red(
|
20
24
|
`There is already a ${pathRelative(
|
@@ -92,4 +96,17 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|
92
96
|
}
|
93
97
|
|
94
98
|
updateAccountThemeImplementationInConfig({ buildContext, accountThemeType });
|
99
|
+
|
100
|
+
await generateKcGenTs({
|
101
|
+
buildContext: {
|
102
|
+
...buildContext,
|
103
|
+
implementedThemeTypes: {
|
104
|
+
...buildContext.implementedThemeTypes,
|
105
|
+
account: {
|
106
|
+
isImplemented: true,
|
107
|
+
type: accountThemeType
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
});
|
95
112
|
}
|
@@ -208,6 +208,18 @@ function decodeHtmlEntities(htmlStr){
|
|
208
208
|
) || (
|
209
209
|
key == "attributes" &&
|
210
210
|
areSamePath(path, ["realm"])
|
211
|
+
) || (
|
212
|
+
xKeycloakify.pageId == "index.ftl" &&
|
213
|
+
xKeycloakify.themeType == "account" &&
|
214
|
+
areSamePath(path, ["realm"]) &&
|
215
|
+
![
|
216
|
+
"name",
|
217
|
+
"registrationEmailAsUsername",
|
218
|
+
"editUsernameAllowed",
|
219
|
+
"isInternationalizationEnabled",
|
220
|
+
"identityFederationEnabled",
|
221
|
+
"userManagedAccessAllowed"
|
222
|
+
]?seq_contains(key)
|
211
223
|
)
|
212
224
|
>
|
213
225
|
<#-- <#local outSeq += ["/*" + path?join(".") + "." + key + " excluded*/"]> -->
|
@@ -1,14 +1,21 @@
|
|
1
|
-
import { assert } from "tsafe/assert";
|
1
|
+
import { assert, type Equals } from "tsafe/assert";
|
2
|
+
import { id } from "tsafe/id";
|
2
3
|
import type { BuildContext } from "./buildContext";
|
3
4
|
import * as fs from "fs/promises";
|
4
5
|
import { join as pathJoin } from "path";
|
5
6
|
import { existsAsync } from "../tools/fs.existsAsync";
|
7
|
+
import { z } from "zod";
|
6
8
|
|
7
9
|
export type BuildContextLike = {
|
8
10
|
projectDirPath: string;
|
9
11
|
themeNames: string[];
|
10
12
|
environmentVariables: { name: string; default: string }[];
|
11
13
|
themeSrcDirPath: string;
|
14
|
+
implementedThemeTypes: Pick<
|
15
|
+
BuildContext["implementedThemeTypes"],
|
16
|
+
"login" | "account"
|
17
|
+
>;
|
18
|
+
packageJsonFilePath: string;
|
12
19
|
};
|
13
20
|
|
14
21
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
@@ -18,12 +25,53 @@ export async function generateKcGenTs(params: {
|
|
18
25
|
}): Promise<void> {
|
19
26
|
const { buildContext } = params;
|
20
27
|
|
21
|
-
const
|
28
|
+
const isReactProject: boolean = await (async () => {
|
29
|
+
const parsedPackageJson = await (async () => {
|
30
|
+
type ParsedPackageJson = {
|
31
|
+
dependencies?: Record<string, string>;
|
32
|
+
devDependencies?: Record<string, string>;
|
33
|
+
};
|
34
|
+
|
35
|
+
const zParsedPackageJson = (() => {
|
36
|
+
type TargetType = ParsedPackageJson;
|
37
|
+
|
38
|
+
const zTargetType = z.object({
|
39
|
+
dependencies: z.record(z.string()).optional(),
|
40
|
+
devDependencies: z.record(z.string()).optional()
|
41
|
+
});
|
42
|
+
|
43
|
+
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
44
|
+
|
45
|
+
return id<z.ZodType<TargetType>>(zTargetType);
|
46
|
+
})();
|
47
|
+
|
48
|
+
return zParsedPackageJson.parse(
|
49
|
+
JSON.parse(
|
50
|
+
(await fs.readFile(buildContext.packageJsonFilePath)).toString("utf8")
|
51
|
+
)
|
52
|
+
);
|
53
|
+
})();
|
54
|
+
|
55
|
+
return (
|
56
|
+
{
|
57
|
+
...parsedPackageJson.dependencies,
|
58
|
+
...parsedPackageJson.devDependencies
|
59
|
+
}.react !== undefined
|
60
|
+
);
|
61
|
+
})();
|
62
|
+
|
63
|
+
const filePath = pathJoin(
|
64
|
+
buildContext.themeSrcDirPath,
|
65
|
+
`kc.gen.ts${isReactProject ? "x" : ""}`
|
66
|
+
);
|
22
67
|
|
23
68
|
const currentContent = (await existsAsync(filePath))
|
24
69
|
? await fs.readFile(filePath)
|
25
70
|
: undefined;
|
26
71
|
|
72
|
+
const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
|
73
|
+
const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
|
74
|
+
|
27
75
|
const newContent = Buffer.from(
|
28
76
|
[
|
29
77
|
`/* prettier-ignore-start */`,
|
@@ -36,6 +84,8 @@ export async function generateKcGenTs(params: {
|
|
36
84
|
``,
|
37
85
|
`// This file is auto-generated by Keycloakify`,
|
38
86
|
``,
|
87
|
+
isReactProject && `import { lazy, Suspense, type ReactNode } from "react";`,
|
88
|
+
``,
|
39
89
|
`export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
|
40
90
|
``,
|
41
91
|
`export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
|
@@ -54,9 +104,52 @@ export async function generateKcGenTs(params: {
|
|
54
104
|
2
|
55
105
|
)};`,
|
56
106
|
``,
|
107
|
+
`export type KcContext =`,
|
108
|
+
hasLoginTheme && ` | import("./login/KcContext").KcContext`,
|
109
|
+
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
|
110
|
+
` ;`,
|
111
|
+
``,
|
112
|
+
`declare global {`,
|
113
|
+
` interface Window {`,
|
114
|
+
` kcContext?: KcContext;`,
|
115
|
+
` }`,
|
116
|
+
`}`,
|
117
|
+
``,
|
118
|
+
...(!isReactProject
|
119
|
+
? []
|
120
|
+
: [
|
121
|
+
hasLoginTheme &&
|
122
|
+
`export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
|
123
|
+
hasAccountTheme &&
|
124
|
+
`export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
|
125
|
+
``,
|
126
|
+
`export function KcPage(`,
|
127
|
+
` props: {`,
|
128
|
+
` kcContext: KcContext;`,
|
129
|
+
` fallback?: ReactNode;`,
|
130
|
+
` }`,
|
131
|
+
`) {`,
|
132
|
+
` const { kcContext, fallback } = props;`,
|
133
|
+
` return (`,
|
134
|
+
` <Suspense fallback={fallback}>`,
|
135
|
+
` {(() => {`,
|
136
|
+
` switch (kcContext.themeType) {`,
|
137
|
+
hasLoginTheme &&
|
138
|
+
` case "login": return <KcLoginPage kcContext={kcContext} />;`,
|
139
|
+
hasAccountTheme &&
|
140
|
+
` case "account": return <KcAccountPage kcContext={kcContext} />;`,
|
141
|
+
` }`,
|
142
|
+
` })()}`,
|
143
|
+
` </Suspense>`,
|
144
|
+
` );`,
|
145
|
+
`}`
|
146
|
+
]),
|
147
|
+
``,
|
57
148
|
`/* prettier-ignore-end */`,
|
58
149
|
``
|
59
|
-
]
|
150
|
+
]
|
151
|
+
.filter(item => typeof item === "string")
|
152
|
+
.join("\n"),
|
60
153
|
"utf8"
|
61
154
|
);
|
62
155
|
|
@@ -65,4 +158,18 @@ export async function generateKcGenTs(params: {
|
|
65
158
|
}
|
66
159
|
|
67
160
|
await fs.writeFile(filePath, newContent);
|
161
|
+
|
162
|
+
delete_legacy_file: {
|
163
|
+
if (!isReactProject) {
|
164
|
+
break delete_legacy_file;
|
165
|
+
}
|
166
|
+
|
167
|
+
const legacyFilePath = filePath.replace(/tsx$/, "ts");
|
168
|
+
|
169
|
+
if (!(await existsAsync(legacyFilePath))) {
|
170
|
+
break delete_legacy_file;
|
171
|
+
}
|
172
|
+
|
173
|
+
await fs.unlink(legacyFilePath);
|
174
|
+
}
|
68
175
|
}
|
@@ -15,6 +15,7 @@ export default meta;
|
|
15
15
|
|
16
16
|
type Story = StoryObj<typeof meta>;
|
17
17
|
|
18
|
+
// NOTE: Enable in your Keycloak realm with: https://github.com/user-attachments/assets/5fc5e49e-a172-4cb0-897a-49baac284b47
|
18
19
|
export const Default: Story = {
|
19
20
|
render: () => (
|
20
21
|
<KcPageStory
|