keycloakify 11.4.4 → 11.5.0

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.
Files changed (94) hide show
  1. package/account/i18n/withJsx/GenericI18n.d.ts +1 -1
  2. package/account/i18n/withJsx/GenericI18n.js.map +1 -1
  3. package/account/i18n/withJsx/useI18n.js.map +1 -1
  4. package/account/pages/PageProps.d.ts +1 -1
  5. package/bin/375.index.js +4089 -0
  6. package/bin/{20.index.js → 490.index.js} +378 -56
  7. package/bin/{36.index.js → 503.index.js} +53 -2
  8. package/bin/{450.index.js → 525.index.js} +2 -4085
  9. package/bin/653.index.js +108 -110
  10. package/bin/682.index.js +1885 -0
  11. package/bin/735.index.js +107 -109
  12. package/bin/921.index.js +1 -1
  13. package/bin/main.js +8 -2
  14. package/bin/shared/constants.d.ts +3 -0
  15. package/bin/shared/constants.js +3 -0
  16. package/bin/shared/constants.js.map +1 -1
  17. package/bin/start-keycloak/getSupportedDockerImageTags.d.ts +8 -0
  18. package/bin/start-keycloak/realmConfig/ParsedRealmJson.d.ts +38 -0
  19. package/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.d.ts +8 -0
  20. package/bin/start-keycloak/realmConfig/defaultConfig/index.d.ts +1 -0
  21. package/bin/start-keycloak/realmConfig/dumpContainerConfig.d.ts +9 -0
  22. package/bin/start-keycloak/realmConfig/index.d.ts +1 -0
  23. package/bin/start-keycloak/realmConfig/prepareRealmConfig.d.ts +15 -0
  24. package/bin/start-keycloak/realmConfig/realmConfig.d.ts +16 -0
  25. package/login/DefaultPage.d.ts +2 -1
  26. package/login/DefaultPage.js.map +1 -1
  27. package/login/UserProfileFormFields.d.ts +1 -1
  28. package/login/UserProfileFormFields.js.map +1 -1
  29. package/login/UserProfileFormFieldsProps.d.ts +1 -0
  30. package/login/i18n/withJsx/GenericI18n.d.ts +1 -1
  31. package/login/i18n/withJsx/GenericI18n.js.map +1 -1
  32. package/login/i18n/withJsx/useI18n.js.map +1 -1
  33. package/login/lib/useUserProfileForm.d.ts +1 -1
  34. package/login/lib/useUserProfileForm.js.map +1 -1
  35. package/login/pages/IdpReviewUserProfile.d.ts +2 -1
  36. package/login/pages/IdpReviewUserProfile.js.map +1 -1
  37. package/login/pages/Login.d.ts +1 -1
  38. package/login/pages/Login.js.map +1 -1
  39. package/login/pages/LoginPassword.d.ts +1 -1
  40. package/login/pages/LoginPassword.js.map +1 -1
  41. package/login/pages/LoginUpdatePassword.d.ts +1 -1
  42. package/login/pages/LoginUpdatePassword.js.map +1 -1
  43. package/login/pages/LoginUpdateProfile.d.ts +2 -1
  44. package/login/pages/LoginUpdateProfile.js.map +1 -1
  45. package/login/pages/PageProps.d.ts +1 -1
  46. package/login/pages/Register.d.ts +2 -1
  47. package/login/pages/Register.js.map +1 -1
  48. package/login/pages/UpdateEmail.d.ts +2 -1
  49. package/login/pages/UpdateEmail.js.map +1 -1
  50. package/package.json +35 -14
  51. package/src/account/i18n/withJsx/GenericI18n.tsx +1 -0
  52. package/src/account/i18n/withJsx/useI18n.tsx +1 -0
  53. package/src/account/pages/PageProps.ts +1 -0
  54. package/src/bin/shared/constants.ts +6 -0
  55. package/src/bin/start-keycloak/getSupportedDockerImageTags.ts +230 -0
  56. package/src/bin/start-keycloak/keycloakify-logging-1.0.3.jar +0 -0
  57. package/src/bin/start-keycloak/realmConfig/ParsedRealmJson.ts +118 -0
  58. package/src/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.ts +75 -0
  59. package/src/bin/start-keycloak/realmConfig/defaultConfig/index.ts +1 -0
  60. package/src/bin/start-keycloak/{myrealm-realm-18.json → realmConfig/defaultConfig/realm-kc-18.json} +123 -60
  61. package/src/bin/start-keycloak/{myrealm-realm-19.json → realmConfig/defaultConfig/realm-kc-19.json} +81 -41
  62. package/src/bin/start-keycloak/{myrealm-realm-20.json → realmConfig/defaultConfig/realm-kc-20.json} +83 -42
  63. package/src/bin/start-keycloak/{myrealm-realm-21.json → realmConfig/defaultConfig/realm-kc-21.json} +58 -17
  64. package/src/bin/start-keycloak/{myrealm-realm-23.json → realmConfig/defaultConfig/realm-kc-23.json} +64 -20
  65. package/src/bin/start-keycloak/{myrealm-realm-24.json → realmConfig/defaultConfig/realm-kc-24.json} +63 -19
  66. package/src/bin/start-keycloak/{myrealm-realm-25.json → realmConfig/defaultConfig/realm-kc-25.json} +75 -20
  67. package/src/bin/start-keycloak/{myrealm-realm-26.json → realmConfig/defaultConfig/realm-kc-26.json} +86 -20
  68. package/src/bin/start-keycloak/realmConfig/dumpContainerConfig.ts +147 -0
  69. package/src/bin/start-keycloak/realmConfig/index.ts +1 -0
  70. package/src/bin/start-keycloak/realmConfig/prepareRealmConfig.ts +302 -0
  71. package/src/bin/start-keycloak/realmConfig/realmConfig.ts +151 -0
  72. package/src/bin/start-keycloak/start-keycloak.ts +160 -184
  73. package/src/bin/start-keycloak/startViteDevServer.ts +1 -0
  74. package/src/login/DefaultPage.tsx +1 -0
  75. package/src/login/UserProfileFormFields.tsx +1 -0
  76. package/src/login/UserProfileFormFieldsProps.tsx +1 -0
  77. package/src/login/i18n/withJsx/GenericI18n.tsx +1 -0
  78. package/src/login/i18n/withJsx/useI18n.tsx +1 -0
  79. package/src/login/lib/useUserProfileForm.tsx +1 -0
  80. package/src/login/pages/IdpReviewUserProfile.tsx +1 -0
  81. package/src/login/pages/Login.tsx +1 -0
  82. package/src/login/pages/LoginPassword.tsx +1 -0
  83. package/src/login/pages/LoginUpdatePassword.tsx +1 -0
  84. package/src/login/pages/LoginUpdateProfile.tsx +1 -0
  85. package/src/login/pages/PageProps.ts +1 -0
  86. package/src/login/pages/Register.tsx +1 -0
  87. package/src/login/pages/UpdateEmail.tsx +1 -0
  88. package/src/tools/JSX.ts +5 -0
  89. package/tools/JSX.d.ts +5 -0
  90. package/tools/JSX.js +2 -0
  91. package/tools/JSX.js.map +1 -0
  92. package/vite-plugin/index.js +6 -0
  93. package/bin/392.index.js +0 -740
  94. package/bin/932.index.js +0 -327
@@ -0,0 +1,147 @@
1
+ import { CONTAINER_NAME } from "../../shared/constants";
2
+ import child_process from "child_process";
3
+ import { join as pathJoin } from "path";
4
+ import chalk from "chalk";
5
+ import { Deferred } from "evt/tools/Deferred";
6
+ import { assert, is } from "tsafe/assert";
7
+ import type { BuildContext } from "../../shared/buildContext";
8
+ import { type ParsedRealmJson, readRealmJsonFile } from "./ParsedRealmJson";
9
+
10
+ export type BuildContextLike = {
11
+ cacheDirPath: string;
12
+ };
13
+
14
+ assert<BuildContext extends BuildContextLike ? true : false>();
15
+
16
+ export async function dumpContainerConfig(params: {
17
+ realmName: string;
18
+ keycloakMajorVersionNumber: number;
19
+ buildContext: BuildContextLike;
20
+ }): Promise<ParsedRealmJson> {
21
+ const { realmName, keycloakMajorVersionNumber, buildContext } = params;
22
+
23
+ {
24
+ // https://github.com/keycloak/keycloak/issues/33800
25
+ const doesUseLockedH2Database = keycloakMajorVersionNumber >= 25;
26
+
27
+ if (doesUseLockedH2Database) {
28
+ child_process.execSync(
29
+ `docker exec ${CONTAINER_NAME} sh -c "cp -rp /opt/keycloak/data/h2 /tmp"`
30
+ );
31
+ }
32
+
33
+ const dCompleted = new Deferred<void>();
34
+
35
+ const child = child_process.spawn(
36
+ "docker",
37
+ [
38
+ ...["exec", CONTAINER_NAME],
39
+ ...["/opt/keycloak/bin/kc.sh", "export"],
40
+ ...["--dir", "/tmp"],
41
+ ...["--realm", realmName],
42
+ ...["--users", "realm_file"],
43
+ ...(!doesUseLockedH2Database
44
+ ? []
45
+ : [
46
+ ...["--db", "dev-file"],
47
+ ...[
48
+ "--db-url",
49
+ "'jdbc:h2:file:/tmp/h2/keycloakdb;NON_KEYWORDS=VALUE'"
50
+ ]
51
+ ])
52
+ ],
53
+ { shell: true }
54
+ );
55
+
56
+ let output = "";
57
+
58
+ const onExit = (code: number | null) => {
59
+ dCompleted.reject(new Error(`Exited with code ${code}`));
60
+ };
61
+
62
+ child.once("exit", onExit);
63
+
64
+ child.stdout.on("data", data => {
65
+ const outputStr = data.toString("utf8");
66
+
67
+ if (outputStr.includes("Export finished successfully")) {
68
+ child.removeListener("exit", onExit);
69
+
70
+ // NOTE: On older Keycloak versions the process keeps running after the export is done.
71
+ const timer = setTimeout(() => {
72
+ child.removeListener("exit", onExit2);
73
+ child.kill();
74
+ dCompleted.resolve();
75
+ }, 1500);
76
+
77
+ const onExit2 = () => {
78
+ clearTimeout(timer);
79
+ dCompleted.resolve();
80
+ };
81
+
82
+ child.once("exit", onExit2);
83
+ }
84
+
85
+ output += outputStr;
86
+ });
87
+
88
+ child.stderr.on("data", data => (output += chalk.red(data.toString("utf8"))));
89
+
90
+ try {
91
+ await dCompleted.pr;
92
+ } catch (error) {
93
+ assert(is<Error>(error));
94
+
95
+ console.log(chalk.red(error.message));
96
+
97
+ console.log(output);
98
+
99
+ process.exit(1);
100
+ }
101
+
102
+ if (doesUseLockedH2Database) {
103
+ const dCompleted = new Deferred<void>();
104
+
105
+ child_process.exec(
106
+ `docker exec ${CONTAINER_NAME} sh -c "rm -rf /tmp/h2"`,
107
+ error => {
108
+ if (error !== null) {
109
+ dCompleted.reject(error);
110
+ return;
111
+ }
112
+
113
+ dCompleted.resolve();
114
+ }
115
+ );
116
+
117
+ await dCompleted.pr;
118
+ }
119
+ }
120
+
121
+ const targetRealmConfigJsonFilePath_tmp = pathJoin(
122
+ buildContext.cacheDirPath,
123
+ "realm.json"
124
+ );
125
+
126
+ {
127
+ const dCompleted = new Deferred<void>();
128
+
129
+ child_process.exec(
130
+ `docker cp ${CONTAINER_NAME}:/tmp/${realmName}-realm.json ${targetRealmConfigJsonFilePath_tmp}`,
131
+ error => {
132
+ if (error !== null) {
133
+ dCompleted.reject(error);
134
+ return;
135
+ }
136
+
137
+ dCompleted.resolve();
138
+ }
139
+ );
140
+
141
+ await dCompleted.pr;
142
+ }
143
+
144
+ return readRealmJsonFile({
145
+ realmJsonFilePath: targetRealmConfigJsonFilePath_tmp
146
+ });
147
+ }
@@ -0,0 +1 @@
1
+ export * from "./realmConfig";
@@ -0,0 +1,302 @@
1
+ import { assert } from "tsafe/assert";
2
+ import type { ParsedRealmJson } from "./ParsedRealmJson";
3
+ import { getDefaultConfig } from "./defaultConfig";
4
+ import type { BuildContext } from "../../shared/buildContext";
5
+ import { objectKeys } from "tsafe/objectKeys";
6
+ import { TEST_APP_URL } from "../../shared/constants";
7
+ import { sameFactory } from "evt/tools/inDepth/same";
8
+
9
+ export type BuildContextLike = {
10
+ themeNames: BuildContext["themeNames"];
11
+ implementedThemeTypes: BuildContext["implementedThemeTypes"];
12
+ };
13
+
14
+ assert<BuildContext extends BuildContextLike ? true : false>;
15
+
16
+ export function prepareRealmConfig(params: {
17
+ parsedRealmJson: ParsedRealmJson;
18
+ keycloakMajorVersionNumber: number;
19
+ buildContext: BuildContextLike;
20
+ }): {
21
+ realmName: string;
22
+ clientName: string;
23
+ username: string;
24
+ } {
25
+ const { parsedRealmJson, keycloakMajorVersionNumber, buildContext } = params;
26
+
27
+ const { username } = addOrEditTestUser({
28
+ parsedRealmJson,
29
+ keycloakMajorVersionNumber
30
+ });
31
+
32
+ const { clientId } = addOrEditClient({
33
+ parsedRealmJson,
34
+ keycloakMajorVersionNumber
35
+ });
36
+
37
+ editAccountConsoleAndSecurityAdminConsole({ parsedRealmJson });
38
+
39
+ enableCustomThemes({
40
+ parsedRealmJson,
41
+ themeName: buildContext.themeNames[0],
42
+ implementedThemeTypes: buildContext.implementedThemeTypes
43
+ });
44
+
45
+ enable_custom_events_listeners: {
46
+ const name = "keycloakify-logging";
47
+
48
+ if (parsedRealmJson.eventsListeners.includes(name)) {
49
+ break enable_custom_events_listeners;
50
+ }
51
+
52
+ parsedRealmJson.eventsListeners.push(name);
53
+
54
+ parsedRealmJson.eventsListeners.sort();
55
+ }
56
+
57
+ return {
58
+ realmName: parsedRealmJson.realm,
59
+ clientName: clientId,
60
+ username
61
+ };
62
+ }
63
+
64
+ function enableCustomThemes(params: {
65
+ parsedRealmJson: ParsedRealmJson;
66
+ themeName: string;
67
+ implementedThemeTypes: BuildContextLike["implementedThemeTypes"];
68
+ }) {
69
+ const { parsedRealmJson, themeName, implementedThemeTypes } = params;
70
+
71
+ for (const themeType of objectKeys(implementedThemeTypes)) {
72
+ if (!implementedThemeTypes[themeType].isImplemented) {
73
+ continue;
74
+ }
75
+
76
+ parsedRealmJson[`${themeType}Theme` as const] = themeName;
77
+ }
78
+ }
79
+
80
+ function addOrEditTestUser(params: {
81
+ parsedRealmJson: ParsedRealmJson;
82
+ keycloakMajorVersionNumber: number;
83
+ }): { username: string } {
84
+ const { parsedRealmJson, keycloakMajorVersionNumber } = params;
85
+
86
+ const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
87
+
88
+ const [defaultUser_default] = parsedRealmJson_default.users;
89
+
90
+ assert(defaultUser_default !== undefined);
91
+
92
+ const defaultUser_preexisting = parsedRealmJson.users.find(
93
+ user => user.username === defaultUser_default.username
94
+ );
95
+
96
+ const newUser = structuredClone(
97
+ defaultUser_preexisting ??
98
+ (() => {
99
+ const firstUser = parsedRealmJson.users[0];
100
+
101
+ if (firstUser === undefined) {
102
+ return undefined;
103
+ }
104
+
105
+ const firstUserCopy = structuredClone(firstUser);
106
+
107
+ firstUserCopy.id = defaultUser_default.id;
108
+
109
+ return firstUserCopy;
110
+ })() ??
111
+ defaultUser_default
112
+ );
113
+
114
+ newUser.username = defaultUser_default.username;
115
+ newUser.email = defaultUser_default.email;
116
+
117
+ delete_existing_password_credential_if_any: {
118
+ const i = newUser.credentials.findIndex(
119
+ credential => credential.type === "password"
120
+ );
121
+
122
+ if (i === -1) {
123
+ break delete_existing_password_credential_if_any;
124
+ }
125
+
126
+ newUser.credentials.splice(i, 1);
127
+ }
128
+
129
+ {
130
+ const credential = defaultUser_default.credentials.find(
131
+ credential => credential.type === "password"
132
+ );
133
+
134
+ assert(credential !== undefined);
135
+
136
+ newUser.credentials.push(credential);
137
+ }
138
+
139
+ {
140
+ const nameByClientId = Object.fromEntries(
141
+ parsedRealmJson.clients.map(client => [client.id, client.clientId] as const)
142
+ );
143
+
144
+ const newClientRoles: NonNullable<
145
+ ParsedRealmJson["users"][number]["clientRoles"]
146
+ > = {};
147
+
148
+ for (const clientRole of Object.values(parsedRealmJson.roles.client).flat()) {
149
+ const clientName = nameByClientId[clientRole.containerId];
150
+
151
+ assert(clientName !== undefined);
152
+
153
+ (newClientRoles[clientName] ??= []).push(clientRole.name);
154
+ }
155
+
156
+ const { same: sameSet } = sameFactory({
157
+ takeIntoAccountArraysOrdering: false
158
+ });
159
+
160
+ for (const [clientName, roles] of Object.entries(newClientRoles)) {
161
+ keep_previous_ordering_if_possible: {
162
+ const roles_previous = newUser.clientRoles?.[clientName];
163
+
164
+ if (roles_previous === undefined) {
165
+ break keep_previous_ordering_if_possible;
166
+ }
167
+
168
+ if (!sameSet(roles_previous, roles)) {
169
+ break keep_previous_ordering_if_possible;
170
+ }
171
+
172
+ continue;
173
+ }
174
+
175
+ (newUser.clientRoles ??= {})[clientName] = roles;
176
+ }
177
+ }
178
+
179
+ if (defaultUser_preexisting === undefined) {
180
+ parsedRealmJson.users.push(newUser);
181
+ } else {
182
+ const i = parsedRealmJson.users.indexOf(defaultUser_preexisting);
183
+ assert(i !== -1);
184
+ parsedRealmJson.users[i] = newUser;
185
+ }
186
+
187
+ return { username: newUser.username };
188
+ }
189
+
190
+ function addOrEditClient(params: {
191
+ parsedRealmJson: ParsedRealmJson;
192
+ keycloakMajorVersionNumber: number;
193
+ }): { clientId: string } {
194
+ const { parsedRealmJson, keycloakMajorVersionNumber } = params;
195
+
196
+ const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
197
+
198
+ const testClient_default = (() => {
199
+ const clients = parsedRealmJson_default.clients.filter(client => {
200
+ return JSON.stringify(client).includes(TEST_APP_URL);
201
+ });
202
+
203
+ assert(clients.length === 1);
204
+
205
+ return clients[0];
206
+ })();
207
+
208
+ const clientIds_builtIn = parsedRealmJson_default.clients
209
+ .map(client => client.clientId)
210
+ .filter(clientId => clientId !== testClient_default.clientId);
211
+
212
+ const testClient_preexisting = (() => {
213
+ const clients = parsedRealmJson.clients
214
+ .filter(client => !clientIds_builtIn.includes(client.clientId))
215
+ .filter(client => client.protocol === "openid-connect");
216
+
217
+ {
218
+ const client = clients.find(
219
+ client => client.clientId === testClient_default.clientId
220
+ );
221
+
222
+ if (client !== undefined) {
223
+ return client;
224
+ }
225
+ }
226
+
227
+ {
228
+ const client = clients.find(
229
+ client =>
230
+ client.redirectUris?.find(redirectUri =>
231
+ redirectUri.startsWith(TEST_APP_URL)
232
+ ) !== undefined
233
+ );
234
+
235
+ if (client !== undefined) {
236
+ return client;
237
+ }
238
+ }
239
+
240
+ const [client] = clients;
241
+
242
+ if (client === undefined) {
243
+ return undefined;
244
+ }
245
+
246
+ return client;
247
+ })();
248
+
249
+ let testClient: typeof testClient_default;
250
+
251
+ if (testClient_preexisting !== undefined) {
252
+ testClient = testClient_preexisting;
253
+ } else {
254
+ testClient = structuredClone(testClient_default);
255
+ delete testClient.protocolMappers;
256
+ parsedRealmJson.clients.push(testClient);
257
+ }
258
+
259
+ testClient.redirectUris = [
260
+ `${TEST_APP_URL}/*`,
261
+ "http://localhost*",
262
+ "http://127.0.0.1*"
263
+ ]
264
+ .sort()
265
+ .reverse();
266
+
267
+ (testClient.attributes ??= {})["post.logout.redirect.uris"] = "+";
268
+
269
+ testClient.webOrigins = ["*"];
270
+
271
+ return { clientId: testClient.clientId };
272
+ }
273
+
274
+ function editAccountConsoleAndSecurityAdminConsole(params: {
275
+ parsedRealmJson: ParsedRealmJson;
276
+ }) {
277
+ const { parsedRealmJson } = params;
278
+
279
+ for (const clientId of ["account-console", "security-admin-console"]) {
280
+ const client = parsedRealmJson.clients.find(
281
+ client => client.clientId === clientId
282
+ );
283
+
284
+ assert(client !== undefined);
285
+
286
+ {
287
+ const arr = (client.redirectUris ??= []);
288
+
289
+ for (const value of ["http://localhost*", "http://127.0.0.1*"]) {
290
+ if (!arr.includes(value)) {
291
+ arr.push(value);
292
+ }
293
+ }
294
+
295
+ client.redirectUris?.sort().reverse();
296
+ }
297
+
298
+ (client.attributes ??= {})["post.logout.redirect.uris"] = "+";
299
+
300
+ client.webOrigins = ["*"];
301
+ }
302
+ }
@@ -0,0 +1,151 @@
1
+ import type { BuildContext } from "../../shared/buildContext";
2
+ import { assert } from "tsafe/assert";
3
+ import { runPrettier, getIsPrettierAvailable } from "../../tools/runPrettier";
4
+ import { getDefaultConfig } from "./defaultConfig";
5
+ import {
6
+ prepareRealmConfig,
7
+ type BuildContextLike as BuildContextLike_prepareRealmConfig
8
+ } from "./prepareRealmConfig";
9
+ import * as fs from "fs";
10
+ import {
11
+ join as pathJoin,
12
+ dirname as pathDirname,
13
+ relative as pathRelative,
14
+ sep as pathSep
15
+ } from "path";
16
+ import { existsAsync } from "../../tools/fs.existsAsync";
17
+ import { readRealmJsonFile, type ParsedRealmJson } from "./ParsedRealmJson";
18
+ import {
19
+ dumpContainerConfig,
20
+ type BuildContextLike as BuildContextLike_dumpContainerConfig
21
+ } from "./dumpContainerConfig";
22
+ import * as runExclusive from "run-exclusive";
23
+ import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
24
+ import chalk from "chalk";
25
+
26
+ export type BuildContextLike = BuildContextLike_dumpContainerConfig &
27
+ BuildContextLike_prepareRealmConfig & {
28
+ projectDirPath: string;
29
+ };
30
+
31
+ assert<BuildContext extends BuildContextLike ? true : false>;
32
+
33
+ export async function getRealmConfig(params: {
34
+ keycloakMajorVersionNumber: number;
35
+ realmJsonFilePath_userProvided: string | undefined;
36
+ buildContext: BuildContextLike;
37
+ }): Promise<{
38
+ realmJsonFilePath: string;
39
+ clientName: string;
40
+ realmName: string;
41
+ username: string;
42
+ onRealmConfigChange: () => Promise<void>;
43
+ }> {
44
+ const { keycloakMajorVersionNumber, realmJsonFilePath_userProvided, buildContext } =
45
+ params;
46
+
47
+ const realmJsonFilePath = pathJoin(
48
+ buildContext.projectDirPath,
49
+ ".keycloakify",
50
+ `realm-kc-${keycloakMajorVersionNumber}.json`
51
+ );
52
+
53
+ const parsedRealmJson = await (async () => {
54
+ if (realmJsonFilePath_userProvided !== undefined) {
55
+ return readRealmJsonFile({
56
+ realmJsonFilePath: realmJsonFilePath_userProvided
57
+ });
58
+ }
59
+
60
+ if (await existsAsync(realmJsonFilePath)) {
61
+ return readRealmJsonFile({
62
+ realmJsonFilePath
63
+ });
64
+ }
65
+
66
+ return getDefaultConfig({ keycloakMajorVersionNumber });
67
+ })();
68
+
69
+ const { clientName, realmName, username } = prepareRealmConfig({
70
+ parsedRealmJson,
71
+ buildContext,
72
+ keycloakMajorVersionNumber
73
+ });
74
+
75
+ {
76
+ const dirPath = pathDirname(realmJsonFilePath);
77
+
78
+ if (!(await existsAsync(dirPath))) {
79
+ fs.mkdirSync(dirPath, { recursive: true });
80
+ }
81
+ }
82
+
83
+ const writeRealmJsonFile = async (params: { parsedRealmJson: ParsedRealmJson }) => {
84
+ const { parsedRealmJson } = params;
85
+
86
+ let sourceCode = JSON.stringify(parsedRealmJson, null, 2);
87
+
88
+ if (await getIsPrettierAvailable()) {
89
+ sourceCode = await runPrettier({
90
+ sourceCode,
91
+ filePath: realmJsonFilePath
92
+ });
93
+ }
94
+
95
+ fs.writeFileSync(realmJsonFilePath, sourceCode);
96
+ };
97
+
98
+ await writeRealmJsonFile({ parsedRealmJson });
99
+
100
+ const { onRealmConfigChange } = (() => {
101
+ const run = runExclusive.build(async () => {
102
+ const start = Date.now();
103
+
104
+ console.log(
105
+ chalk.grey(`Changes detected to the '${realmName}' config, backing up...`)
106
+ );
107
+
108
+ const parsedRealmJson = await dumpContainerConfig({
109
+ buildContext,
110
+ realmName,
111
+ keycloakMajorVersionNumber
112
+ });
113
+
114
+ await writeRealmJsonFile({ parsedRealmJson });
115
+
116
+ console.log(
117
+ [
118
+ chalk.grey(
119
+ `Save changed to \`.${pathSep}${pathRelative(buildContext.projectDirPath, realmJsonFilePath)}\``
120
+ ),
121
+ chalk.grey(
122
+ `Next time you'll be running \`keycloakify start-keycloak\`, the realm '${realmName}' will be restored to this state.`
123
+ ),
124
+ chalk.green(
125
+ `✓ '${realmName}' config backed up completed in ${Date.now() - start}ms`
126
+ )
127
+ ].join("\n")
128
+ );
129
+ });
130
+
131
+ const { waitForDebounce } = waitForDebounceFactory({
132
+ delay: 1_000
133
+ });
134
+
135
+ async function onRealmConfigChange() {
136
+ await waitForDebounce();
137
+
138
+ run();
139
+ }
140
+
141
+ return { onRealmConfigChange };
142
+ })();
143
+
144
+ return {
145
+ realmJsonFilePath,
146
+ clientName,
147
+ realmName,
148
+ username,
149
+ onRealmConfigChange
150
+ };
151
+ }