keycloakify 11.3.0-rc.3 → 11.3.0-rc.6

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.
@@ -5,7 +5,7 @@ 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
9
 
10
10
  export async function command(params: { buildContext: BuildContext }) {
11
11
  const { buildContext } = params;
@@ -94,7 +94,7 @@ export async function command(params: { buildContext: BuildContext }) {
94
94
 
95
95
  updateAccountThemeImplementationInConfig({ buildContext, accountThemeType });
96
96
 
97
- await generateKcGenTs({
97
+ await updateKcGenCommand({
98
98
  buildContext: {
99
99
  ...buildContext,
100
100
  implementedThemeTypes: {
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,157 +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
- console.log("before callHandlerIfAny");
172
-
173
- callHandlerIfAny({ buildContext, commandName });
174
-
175
- console.log("after callHandlerIfAny");
176
-
177
- const { command } = await import("./eject-page");
178
-
179
- await command({ buildContext });
180
- }
181
- });
182
- }
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";
183
94
 
184
- {
185
- const commandName = "add-story";
95
+ optionsKeys.push(name);
186
96
 
187
- program
188
- .command({
189
- name: commandName,
190
- description:
191
- "Add *.stories.tsx file for a specific page to in your Storybook."
192
- })
193
- .task({
194
- skip,
195
- handler: async ({ projectDirPath }) => {
196
- 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");
197
136
 
198
- callHandlerIfAny({ buildContext, commandName });
137
+ await command({
138
+ buildContext: getBuildContext({ projectDirPath }),
139
+ cliCommandOptions: { keycloakVersion, port, realmJsonFilePath }
140
+ });
141
+ }
142
+ });
199
143
 
200
- 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");
201
153
 
202
- await command({ buildContext });
203
- }
204
- });
205
- }
154
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
155
+ }
156
+ });
206
157
 
207
- {
208
- 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");
209
167
 
210
- program
211
- .command({
212
- name: comandName,
213
- description: "Initialize an email theme."
214
- })
215
- .task({
216
- skip,
217
- handler: async ({ projectDirPath }) => {
218
- const buildContext = getBuildContext({ projectDirPath });
168
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
169
+ }
170
+ });
219
171
 
220
- 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");
221
181
 
222
- await command({ buildContext });
223
- }
224
- });
225
- }
182
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
183
+ }
184
+ });
226
185
 
227
186
  program
228
187
  .command({
@@ -232,57 +191,41 @@ program
232
191
  .task({
233
192
  skip,
234
193
  handler: async ({ projectDirPath }) => {
235
- const buildContext = getBuildContext({ projectDirPath });
236
-
237
194
  const { command } = await import("./initialize-account-theme");
238
195
 
239
- await command({ buildContext });
196
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
240
197
  }
241
198
  });
242
199
 
243
- {
244
- const commandName = "copy-keycloak-resources-to-public";
245
-
246
- program
247
- .command({
248
- name: commandName,
249
- description:
250
- "(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
251
- })
252
- .task({
253
- skip,
254
- handler: async ({ projectDirPath }) => {
255
- const buildContext = getBuildContext({ projectDirPath });
256
-
257
- const { command } = await import("./copy-keycloak-resources-to-public");
258
-
259
- await command({ buildContext });
260
- }
261
- });
262
- }
263
-
264
- {
265
- const commandName = "update-kc-gen";
266
-
267
- program
268
- .command({
269
- name: commandName,
270
- description:
271
- "(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
272
- })
273
- .task({
274
- skip,
275
- handler: async ({ projectDirPath }) => {
276
- 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");
277
210
 
278
- callHandlerIfAny({ buildContext, commandName });
211
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
212
+ }
213
+ });
279
214
 
280
- 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");
281
225
 
282
- await command({ buildContext });
283
- }
284
- });
285
- }
226
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
227
+ }
228
+ });
286
229
 
287
230
  // Fallback to build command if no command is provided
288
231
  {
@@ -8,13 +8,12 @@ import {
8
8
  ApiVersion
9
9
  } from "./customHandler";
10
10
  import * as child_process from "child_process";
11
- import { is } from "tsafe/is";
12
11
  import { dirname as pathDirname } from "path";
13
12
  import * as fs from "fs";
14
13
 
15
14
  assert<Equals<ApiVersion, "v1">>();
16
15
 
17
- export function callHandlerIfAny(params: {
16
+ export function maybeDelegateCommandToCustomHandler(params: {
18
17
  commandName: CommandName;
19
18
  buildContext: BuildContext;
20
19
  }) {
@@ -34,16 +33,13 @@ export function callHandlerIfAny(params: {
34
33
  }
35
34
  });
36
35
  } catch (error: any) {
37
- console.log(error.message);
38
- console.log(error.status);
36
+ const status = error.status;
39
37
 
40
- assert(is<child_process.ExecException>(error));
41
-
42
- if (error.code === NOT_IMPLEMENTED_EXIT_CODE) {
38
+ if (status === NOT_IMPLEMENTED_EXIT_CODE) {
43
39
  return;
44
40
  }
45
41
 
46
- process.exit(error.code);
42
+ process.exit(status);
47
43
  }
48
44
 
49
45
  process.exit(0);
@@ -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