keycloakify 11.3.0-rc.4 → 11.3.0-rc.7

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.
@@ -1,9 +1,15 @@
1
1
  import { copyKeycloakResourcesToPublic } from "./shared/copyKeycloakResourcesToPublic";
2
2
  import type { BuildContext } from "./shared/buildContext";
3
+ import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
3
4
 
4
5
  export async function command(params: { buildContext: BuildContext }) {
5
6
  const { buildContext } = params;
6
7
 
8
+ maybeDelegateCommandToCustomHandler({
9
+ commandName: "copy-keycloak-resources-to-public",
10
+ buildContext
11
+ });
12
+
7
13
  copyKeycloakResourcesToPublic({
8
14
  buildContext
9
15
  });
@@ -5,11 +5,17 @@ import chalk from "chalk";
5
5
  import { join as pathJoin, relative as pathRelative } from "path";
6
6
  import * as fs from "fs";
7
7
  import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig";
8
- import { generateKcGenTs } from "../shared/generateKcGenTs";
8
+ import { command as updateKcGenCommand } from "../update-kc-gen";
9
+ import { maybeDelegateCommandToCustomHandler } from "../shared/customHandler_delegate";
9
10
 
10
11
  export async function command(params: { buildContext: BuildContext }) {
11
12
  const { buildContext } = params;
12
13
 
14
+ maybeDelegateCommandToCustomHandler({
15
+ commandName: "initialize-account-theme",
16
+ buildContext
17
+ });
18
+
13
19
  const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
14
20
 
15
21
  if (
@@ -94,7 +100,7 @@ export async function command(params: { buildContext: BuildContext }) {
94
100
 
95
101
  updateAccountThemeImplementationInConfig({ buildContext, accountThemeType });
96
102
 
97
- await generateKcGenTs({
103
+ await updateKcGenCommand({
98
104
  buildContext: {
99
105
  ...buildContext,
100
106
  implementedThemeTypes: {
@@ -8,12 +8,14 @@ import { id } from "tsafe/id";
8
8
 
9
9
  export type BuildContextLike = {
10
10
  bundler: BuildContext["bundler"];
11
+ projectDirPath: string;
12
+ packageJsonFilePath: string;
11
13
  };
12
14
 
13
15
  assert<BuildContext extends BuildContextLike ? true : false>();
14
16
 
15
17
  export function updateAccountThemeImplementationInConfig(params: {
16
- buildContext: BuildContext;
18
+ buildContext: BuildContextLike;
17
19
  accountThemeType: "Single-Page" | "Multi-Page";
18
20
  }) {
19
21
  const { buildContext, accountThemeType } = params;
@@ -4,10 +4,16 @@ import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
4
4
  import type { BuildContext } from "./shared/buildContext";
5
5
  import * as fs from "fs";
6
6
  import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive";
7
+ import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
7
8
 
8
9
  export async function command(params: { buildContext: BuildContext }) {
9
10
  const { buildContext } = params;
10
11
 
12
+ maybeDelegateCommandToCustomHandler({
13
+ commandName: "initialize-email-theme",
14
+ buildContext
15
+ });
16
+
11
17
  const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
12
18
 
13
19
  if (
package/src/bin/main.ts CHANGED
@@ -4,7 +4,6 @@ import { termost } from "termost";
4
4
  import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
5
5
  import * as child_process from "child_process";
6
6
  import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx";
7
- import { callHandlerIfAny } from "./shared/customHandler_caller";
8
7
  import { getBuildContext } from "./shared/buildContext";
9
8
 
10
9
  type CliCommandOptions = {
@@ -72,153 +71,117 @@ program
72
71
  .task({
73
72
  skip,
74
73
  handler: async ({ projectDirPath }) => {
75
- const buildContext = getBuildContext({ projectDirPath });
76
-
77
74
  const { command } = await import("./keycloakify");
78
75
 
79
- await command({ buildContext });
76
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
80
77
  }
81
78
  });
82
79
 
83
- {
84
- const commandName = "start-keycloak";
85
-
86
- program
87
- .command<{
88
- port: number | undefined;
89
- keycloakVersion: string | undefined;
90
- realmJsonFilePath: string | undefined;
91
- }>({
92
- name: commandName,
93
- description:
94
- "Spin up a pre configured Docker image of Keycloak to test your theme."
95
- })
96
- .option({
97
- key: "port",
98
- name: (() => {
99
- const name = "port";
100
-
101
- optionsKeys.push(name);
102
-
103
- return name;
104
- })(),
105
- description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
106
- defaultValue: undefined
107
- })
108
- .option({
109
- key: "keycloakVersion",
110
- name: (() => {
111
- const name = "keycloak-version";
112
-
113
- optionsKeys.push(name);
114
-
115
- return name;
116
- })(),
117
- description: [
118
- "Use a specific version of Keycloak.",
119
- "Example `--keycloak-version 21.1.1`"
120
- ].join(" "),
121
- defaultValue: undefined
122
- })
123
- .option({
124
- key: "realmJsonFilePath",
125
- name: (() => {
126
- const name = "import";
127
-
128
- optionsKeys.push(name);
129
-
130
- return name;
131
- })(),
132
- defaultValue: undefined,
133
- description: [
134
- "Import your own realm configuration file",
135
- "Example `--import path/to/myrealm-realm.json`"
136
- ].join(" ")
137
- })
138
- .task({
139
- skip,
140
- handler: async ({
141
- projectDirPath,
142
- keycloakVersion,
143
- port,
144
- realmJsonFilePath
145
- }) => {
146
- const buildContext = getBuildContext({ projectDirPath });
147
-
148
- const { command } = await import("./start-keycloak");
149
-
150
- await command({
151
- buildContext,
152
- cliCommandOptions: { keycloakVersion, port, realmJsonFilePath }
153
- });
154
- }
155
- });
156
- }
157
-
158
- {
159
- const commandName = "eject-page";
160
-
161
- program
162
- .command({
163
- name: commandName,
164
- description: "Eject a Keycloak page."
165
- })
166
- .task({
167
- skip,
168
- handler: async ({ projectDirPath }) => {
169
- const buildContext = getBuildContext({ projectDirPath });
170
-
171
- callHandlerIfAny({ buildContext, commandName });
172
-
173
- const { command } = await import("./eject-page");
174
-
175
- await command({ buildContext });
176
- }
177
- });
178
- }
80
+ program
81
+ .command<{
82
+ port: number | undefined;
83
+ keycloakVersion: string | undefined;
84
+ realmJsonFilePath: string | undefined;
85
+ }>({
86
+ name: "start-keycloak",
87
+ description:
88
+ "Spin up a pre configured Docker image of Keycloak to test your theme."
89
+ })
90
+ .option({
91
+ key: "port",
92
+ name: (() => {
93
+ const name = "port";
179
94
 
180
- {
181
- const commandName = "add-story";
95
+ optionsKeys.push(name);
182
96
 
183
- program
184
- .command({
185
- name: commandName,
186
- description:
187
- "Add *.stories.tsx file for a specific page to in your Storybook."
188
- })
189
- .task({
190
- skip,
191
- handler: async ({ projectDirPath }) => {
192
- const buildContext = getBuildContext({ projectDirPath });
97
+ return name;
98
+ })(),
99
+ description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
100
+ defaultValue: undefined
101
+ })
102
+ .option({
103
+ key: "keycloakVersion",
104
+ name: (() => {
105
+ const name = "keycloak-version";
106
+
107
+ optionsKeys.push(name);
108
+
109
+ return name;
110
+ })(),
111
+ description: [
112
+ "Use a specific version of Keycloak.",
113
+ "Example `--keycloak-version 21.1.1`"
114
+ ].join(" "),
115
+ defaultValue: undefined
116
+ })
117
+ .option({
118
+ key: "realmJsonFilePath",
119
+ name: (() => {
120
+ const name = "import";
121
+
122
+ optionsKeys.push(name);
123
+
124
+ return name;
125
+ })(),
126
+ defaultValue: undefined,
127
+ description: [
128
+ "Import your own realm configuration file",
129
+ "Example `--import path/to/myrealm-realm.json`"
130
+ ].join(" ")
131
+ })
132
+ .task({
133
+ skip,
134
+ handler: async ({ projectDirPath, keycloakVersion, port, realmJsonFilePath }) => {
135
+ const { command } = await import("./start-keycloak");
193
136
 
194
- callHandlerIfAny({ buildContext, commandName });
137
+ await command({
138
+ buildContext: getBuildContext({ projectDirPath }),
139
+ cliCommandOptions: { keycloakVersion, port, realmJsonFilePath }
140
+ });
141
+ }
142
+ });
195
143
 
196
- const { command } = await import("./add-story");
144
+ program
145
+ .command({
146
+ name: "eject-page",
147
+ description: "Eject a Keycloak page."
148
+ })
149
+ .task({
150
+ skip,
151
+ handler: async ({ projectDirPath }) => {
152
+ const { command } = await import("./eject-page");
197
153
 
198
- await command({ buildContext });
199
- }
200
- });
201
- }
154
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
155
+ }
156
+ });
202
157
 
203
- {
204
- const comandName = "initialize-login-theme";
158
+ program
159
+ .command({
160
+ name: "add-story",
161
+ description: "Add *.stories.tsx file for a specific page to in your Storybook."
162
+ })
163
+ .task({
164
+ skip,
165
+ handler: async ({ projectDirPath }) => {
166
+ const { command } = await import("./add-story");
205
167
 
206
- program
207
- .command({
208
- name: comandName,
209
- description: "Initialize an email theme."
210
- })
211
- .task({
212
- skip,
213
- handler: async ({ projectDirPath }) => {
214
- const buildContext = getBuildContext({ projectDirPath });
168
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
169
+ }
170
+ });
215
171
 
216
- const { command } = await import("./initialize-email-theme");
172
+ program
173
+ .command({
174
+ name: "initialize-login-theme",
175
+ description: "Initialize an email theme."
176
+ })
177
+ .task({
178
+ skip,
179
+ handler: async ({ projectDirPath }) => {
180
+ const { command } = await import("./initialize-email-theme");
217
181
 
218
- await command({ buildContext });
219
- }
220
- });
221
- }
182
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
183
+ }
184
+ });
222
185
 
223
186
  program
224
187
  .command({
@@ -228,57 +191,41 @@ program
228
191
  .task({
229
192
  skip,
230
193
  handler: async ({ projectDirPath }) => {
231
- const buildContext = getBuildContext({ projectDirPath });
232
-
233
194
  const { command } = await import("./initialize-account-theme");
234
195
 
235
- await command({ buildContext });
196
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
236
197
  }
237
198
  });
238
199
 
239
- {
240
- const commandName = "copy-keycloak-resources-to-public";
241
-
242
- program
243
- .command({
244
- name: commandName,
245
- description:
246
- "(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
247
- })
248
- .task({
249
- skip,
250
- handler: async ({ projectDirPath }) => {
251
- const buildContext = getBuildContext({ projectDirPath });
252
-
253
- const { command } = await import("./copy-keycloak-resources-to-public");
254
-
255
- await command({ buildContext });
256
- }
257
- });
258
- }
259
-
260
- {
261
- const commandName = "update-kc-gen";
262
-
263
- program
264
- .command({
265
- name: commandName,
266
- description:
267
- "(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
268
- })
269
- .task({
270
- skip,
271
- handler: async ({ projectDirPath }) => {
272
- const buildContext = getBuildContext({ projectDirPath });
200
+ program
201
+ .command({
202
+ name: "copy-keycloak-resources-to-public",
203
+ description:
204
+ "(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
205
+ })
206
+ .task({
207
+ skip,
208
+ handler: async ({ projectDirPath }) => {
209
+ const { command } = await import("./copy-keycloak-resources-to-public");
273
210
 
274
- callHandlerIfAny({ buildContext, commandName });
211
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
212
+ }
213
+ });
275
214
 
276
- const { command } = await import("./update-kc-gen");
215
+ program
216
+ .command({
217
+ name: "update-kc-gen",
218
+ description:
219
+ "(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
220
+ })
221
+ .task({
222
+ skip,
223
+ handler: async ({ projectDirPath }) => {
224
+ const { command } = await import("./update-kc-gen");
277
225
 
278
- await command({ buildContext });
279
- }
280
- });
281
- }
226
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
227
+ }
228
+ });
282
229
 
283
230
  // Fallback to build command if no command is provided
284
231
  {
@@ -6,7 +6,13 @@ export const BIN_NAME = "_keycloakify-custom-handler";
6
6
 
7
7
  export const NOT_IMPLEMENTED_EXIT_CODE = 78;
8
8
 
9
- export type CommandName = "update-kc-gen" | "eject-page" | "add-story";
9
+ export type CommandName =
10
+ | "update-kc-gen"
11
+ | "eject-page"
12
+ | "add-story"
13
+ | "initialize-account-theme"
14
+ | "initialize-email-theme"
15
+ | "copy-keycloak-resources-to-public";
10
16
 
11
17
  export type ApiVersion = "v1";
12
18
 
@@ -13,7 +13,7 @@ import * as fs from "fs";
13
13
 
14
14
  assert<Equals<ApiVersion, "v1">>();
15
15
 
16
- export function callHandlerIfAny(params: {
16
+ export function maybeDelegateCommandToCustomHandler(params: {
17
17
  commandName: CommandName;
18
18
  buildContext: BuildContext;
19
19
  }) {
@@ -1,8 +1,116 @@
1
1
  import type { BuildContext } from "./shared/buildContext";
2
- import { generateKcGenTs } from "./shared/generateKcGenTs";
2
+ import * as fs from "fs/promises";
3
+ import { join as pathJoin } from "path";
4
+ import { existsAsync } from "./tools/fs.existsAsync";
5
+ import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
3
6
 
4
7
  export async function command(params: { buildContext: BuildContext }) {
5
8
  const { buildContext } = params;
6
9
 
7
- await generateKcGenTs({ buildContext });
10
+ maybeDelegateCommandToCustomHandler({
11
+ commandName: "update-kc-gen",
12
+ buildContext
13
+ });
14
+
15
+ const filePath = pathJoin(buildContext.themeSrcDirPath, `kc.gen.tsx`);
16
+
17
+ const currentContent = (await existsAsync(filePath))
18
+ ? await fs.readFile(filePath)
19
+ : undefined;
20
+
21
+ const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented;
22
+ const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented;
23
+
24
+ const newContent = Buffer.from(
25
+ [
26
+ `/* prettier-ignore-start */`,
27
+ ``,
28
+ `/* eslint-disable */`,
29
+ ``,
30
+ `// @ts-nocheck`,
31
+ ``,
32
+ `// noinspection JSUnusedGlobalSymbols`,
33
+ ``,
34
+ `// This file is auto-generated by Keycloakify`,
35
+ ``,
36
+ `import { lazy, Suspense, type ReactNode } from "react";`,
37
+ ``,
38
+ `export type ThemeName = ${buildContext.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
39
+ ``,
40
+ `export const themeNames: ThemeName[] = [${buildContext.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
41
+ ``,
42
+ `export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? "never" : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
43
+ ``,
44
+ `export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
45
+ ``,
46
+ `export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
47
+ Object.fromEntries(
48
+ buildContext.environmentVariables.map(
49
+ ({ name, default: defaultValue }) => [name, defaultValue]
50
+ )
51
+ ),
52
+ null,
53
+ 2
54
+ )};`,
55
+ ``,
56
+ `export type KcContext =`,
57
+ hasLoginTheme && ` | import("./login/KcContext").KcContext`,
58
+ hasAccountTheme && ` | import("./account/KcContext").KcContext`,
59
+ ` ;`,
60
+ ``,
61
+ `declare global {`,
62
+ ` interface Window {`,
63
+ ` kcContext?: KcContext;`,
64
+ ` }`,
65
+ `}`,
66
+ ``,
67
+ hasLoginTheme &&
68
+ `export const KcLoginPage = lazy(() => import("./login/KcPage"));`,
69
+ hasAccountTheme &&
70
+ `export const KcAccountPage = lazy(() => import("./account/KcPage"));`,
71
+ ``,
72
+ `export function KcPage(`,
73
+ ` props: {`,
74
+ ` kcContext: KcContext;`,
75
+ ` fallback?: ReactNode;`,
76
+ ` }`,
77
+ `) {`,
78
+ ` const { kcContext, fallback } = props;`,
79
+ ` return (`,
80
+ ` <Suspense fallback={fallback}>`,
81
+ ` {(() => {`,
82
+ ` switch (kcContext.themeType) {`,
83
+ hasLoginTheme &&
84
+ ` case "login": return <KcLoginPage kcContext={kcContext} />;`,
85
+ hasAccountTheme &&
86
+ ` case "account": return <KcAccountPage kcContext={kcContext} />;`,
87
+ ` }`,
88
+ ` })()}`,
89
+ ` </Suspense>`,
90
+ ` );`,
91
+ `}`,
92
+ ``,
93
+ `/* prettier-ignore-end */`,
94
+ ``
95
+ ]
96
+ .filter(item => typeof item === "string")
97
+ .join("\n"),
98
+ "utf8"
99
+ );
100
+
101
+ if (currentContent !== undefined && currentContent.equals(newContent)) {
102
+ return;
103
+ }
104
+
105
+ await fs.writeFile(filePath, newContent);
106
+
107
+ delete_legacy_file: {
108
+ const legacyFilePath = filePath.replace(/tsx$/, "ts");
109
+
110
+ if (!(await existsAsync(legacyFilePath))) {
111
+ break delete_legacy_file;
112
+ }
113
+
114
+ await fs.unlink(legacyFilePath);
115
+ }
8
116
  }
@@ -15,7 +15,7 @@ import {
15
15
  type ResolvedViteConfig
16
16
  } from "../bin/shared/buildContext";
17
17
  import MagicString from "magic-string";
18
- import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
18
+ import { command as updateKcGenCommand } from "../bin/update-kc-gen";
19
19
 
20
20
  export namespace keycloakify {
21
21
  export type Params = BuildOptions & {
@@ -125,8 +125,9 @@ export function keycloakify(params: keycloakify.Params) {
125
125
  projectDirPath
126
126
  });
127
127
 
128
- copyKeycloakResourcesToPublic({ buildContext }),
129
- await generateKcGenTs({ buildContext });
128
+ copyKeycloakResourcesToPublic({ buildContext });
129
+
130
+ await updateKcGenCommand({ buildContext });
130
131
  },
131
132
  transform: (code, id) => {
132
133
  assert(command !== undefined);
@@ -115,7 +115,6 @@ export const WithFavoritePet: Story = {
115
115
  )
116
116
  };
117
117
 
118
-
119
118
  export const WithNewsletter: Story = {
120
119
  render: () => (
121
120
  <KcPageStory
@@ -132,7 +131,7 @@ export const WithNewsletter: Story = {
132
131
  },
133
132
  annotations: {
134
133
  inputOptionLabels: {
135
- "yes": "I want my email inbox filled with spam"
134
+ yes: "I want my email inbox filled with spam"
136
135
  },
137
136
  inputType: "multiselect-checkboxes"
138
137
  },
@@ -140,13 +139,12 @@ export const WithNewsletter: Story = {
140
139
  readOnly: false
141
140
  } satisfies Attribute
142
141
  }
143
- },
142
+ }
144
143
  }}
145
144
  />
146
145
  )
147
146
  };
148
147
 
149
-
150
148
  export const WithEmailAsUsername: Story = {
151
149
  render: () => (
152
150
  <KcPageStory