keycloakify 11.5.4 → 11.6.1

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 (31) hide show
  1. package/bin/153.index.js +81 -28
  2. package/bin/{805.index.js → 174.index.js} +111 -2
  3. package/bin/{356.index.js → 300.index.js} +67 -29
  4. package/bin/{33.index.js → 568.index.js} +2 -113
  5. package/bin/615.index.js +1009 -0
  6. package/bin/653.index.js +53 -135
  7. package/bin/735.index.js +53 -24
  8. package/bin/854.index.js +1 -1
  9. package/bin/{573.index.js → 880.index.js} +155 -9
  10. package/bin/921.index.js +1 -1
  11. package/bin/initialize-admin-theme.d.ts +4 -0
  12. package/bin/main.js +22 -9
  13. package/bin/shared/addPostinstallScriptIfNotPresent.d.ts +10 -0
  14. package/bin/shared/customHandler.d.ts +1 -1
  15. package/bin/shared/customHandler.js.map +1 -1
  16. package/bin/tools/createObjectThatThrowsIfAccessed.d.ts +21 -0
  17. package/bin/tools/npmInstall.d.ts +1 -1
  18. package/package.json +13 -5
  19. package/src/bin/initialize-account-theme/initializeAccountTheme_singlePage.ts +3 -1
  20. package/src/bin/initialize-admin-theme.ts +146 -0
  21. package/src/bin/keycloakify/generateResources/generateResources.ts +162 -6
  22. package/src/bin/main.ts +19 -4
  23. package/src/bin/postinstall/getUiModuleFileSourceCodeReadyToBeCopied.ts +63 -24
  24. package/src/bin/postinstall/installUiModulesPeerDependencies.ts +1 -1
  25. package/src/bin/postinstall/uiModuleMeta.ts +7 -1
  26. package/src/bin/shared/addPostinstallScriptIfNotPresent.ts +70 -0
  27. package/src/bin/shared/customHandler.ts +1 -0
  28. package/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-22.json +2201 -0
  29. package/src/bin/start-keycloak/start-keycloak.ts +131 -36
  30. package/src/bin/tools/createObjectThatThrowsIfAccessed.ts +90 -0
  31. package/src/bin/tools/npmInstall.ts +46 -9
@@ -40,6 +40,7 @@ import { escapeStringForPropertiesFile } from "../../tools/escapeStringForProper
40
40
  import * as child_process from "child_process";
41
41
  import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
42
42
  import propertiesParser from "properties-parser";
43
+ import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
43
44
 
44
45
  export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
45
46
  BuildContextLike_generateMessageProperties & {
@@ -256,15 +257,17 @@ export async function generateResources(params: {
256
257
  writeMessagePropertiesFiles;
257
258
  }
258
259
 
259
- bring_in_spas_messages: {
260
+ bring_in_account_spa_messages: {
260
261
  if (!isSpa) {
261
- break bring_in_spas_messages;
262
+ break bring_in_account_spa_messages;
262
263
  }
263
264
 
264
- assert(themeType !== "login");
265
+ if (themeType !== "account") {
266
+ break bring_in_account_spa_messages;
267
+ }
265
268
 
266
269
  const accountUiDirPath = child_process
267
- .execSync(`npm list @keycloakify/keycloak-${themeType}-ui --parseable`, {
270
+ .execSync(`npm list @keycloakify/keycloak-account-ui --parseable`, {
268
271
  cwd: pathDirname(buildContext.packageJsonFilePath)
269
272
  })
270
273
  .toString("utf8")
@@ -279,7 +282,7 @@ export async function generateResources(params: {
279
282
  }
280
283
 
281
284
  const messagesDirPath_dest = pathJoin(
282
- getThemeTypeDirPath({ themeName, themeType }),
285
+ getThemeTypeDirPath({ themeName, themeType: "account" }),
283
286
  "messages"
284
287
  );
285
288
 
@@ -291,7 +294,7 @@ export async function generateResources(params: {
291
294
  apply_theme_changes: {
292
295
  const messagesDirPath_theme = pathJoin(
293
296
  buildContext.themeSrcDirPath,
294
- themeType,
297
+ "account",
295
298
  "messages"
296
299
  );
297
300
 
@@ -339,6 +342,159 @@ export async function generateResources(params: {
339
342
  );
340
343
  }
341
344
 
345
+ bring_in_admin_messages: {
346
+ if (themeType !== "admin") {
347
+ break bring_in_admin_messages;
348
+ }
349
+
350
+ const messagesDirPath_theme = pathJoin(
351
+ buildContext.themeSrcDirPath,
352
+ "admin",
353
+ "i18n"
354
+ );
355
+
356
+ assert(
357
+ fs.existsSync(messagesDirPath_theme),
358
+ `${messagesDirPath_theme} is supposed to exist`
359
+ );
360
+
361
+ const propertiesByLang: Record<
362
+ string,
363
+ {
364
+ base: Buffer;
365
+ override: Buffer | undefined;
366
+ overrideByThemeName: Record<string, Buffer>;
367
+ }
368
+ > = {};
369
+
370
+ fs.readdirSync(messagesDirPath_theme).forEach(basename => {
371
+ type ParsedBasename = { lang: string } & (
372
+ | {
373
+ isOverride: false;
374
+ }
375
+ | {
376
+ isOverride: true;
377
+ themeName: string | undefined;
378
+ }
379
+ );
380
+
381
+ const parsedBasename = ((): ParsedBasename | undefined => {
382
+ const match = basename.match(/^messages_([^.]+)\.properties$/);
383
+
384
+ if (match === null) {
385
+ return undefined;
386
+ }
387
+
388
+ const discriminator = match[1];
389
+
390
+ const split = discriminator.split("_override");
391
+
392
+ if (split.length === 1) {
393
+ return {
394
+ lang: discriminator,
395
+ isOverride: false
396
+ };
397
+ }
398
+
399
+ assert(split.length === 2);
400
+
401
+ if (split[1] === "") {
402
+ return {
403
+ lang: split[0],
404
+ isOverride: true,
405
+ themeName: undefined
406
+ };
407
+ }
408
+
409
+ const match2 = split[1].match(/^_(.+)$/);
410
+
411
+ assert(match2 !== null);
412
+
413
+ return {
414
+ lang: split[0],
415
+ isOverride: true,
416
+ themeName: match2[1]
417
+ };
418
+ })();
419
+
420
+ if (parsedBasename === undefined) {
421
+ return;
422
+ }
423
+
424
+ propertiesByLang[parsedBasename.lang] ??= {
425
+ base: createObjectThatThrowsIfAccessed<Buffer>({
426
+ debugMessage: `No base ${parsedBasename.lang} translation for admin theme`
427
+ }),
428
+ override: undefined,
429
+ overrideByThemeName: {}
430
+ };
431
+
432
+ const buffer = fs.readFileSync(pathJoin(messagesDirPath_theme, basename));
433
+
434
+ if (parsedBasename.isOverride === false) {
435
+ propertiesByLang[parsedBasename.lang].base = buffer;
436
+ return;
437
+ }
438
+
439
+ if (parsedBasename.themeName === undefined) {
440
+ propertiesByLang[parsedBasename.lang].override = buffer;
441
+ return;
442
+ }
443
+
444
+ propertiesByLang[parsedBasename.lang].overrideByThemeName[
445
+ parsedBasename.themeName
446
+ ] = buffer;
447
+ });
448
+
449
+ writeMessagePropertiesFilesByThemeType.admin = ({
450
+ messageDirPath,
451
+ themeName
452
+ }) => {
453
+ if (!fs.existsSync(messageDirPath)) {
454
+ fs.mkdirSync(messageDirPath, { recursive: true });
455
+ }
456
+
457
+ Object.entries(propertiesByLang).forEach(
458
+ ([lang, { base, override, overrideByThemeName }]) => {
459
+ (languageTags ??= []).push(lang);
460
+
461
+ const messages = propertiesParser.parse(base.toString("utf8"));
462
+
463
+ if (override !== undefined) {
464
+ const overrideMessages = propertiesParser.parse(
465
+ override.toString("utf8")
466
+ );
467
+
468
+ Object.entries(overrideMessages).forEach(
469
+ ([key, value]) => (messages[key] = value)
470
+ );
471
+ }
472
+
473
+ if (themeName in overrideByThemeName) {
474
+ const overrideMessages = propertiesParser.parse(
475
+ overrideByThemeName[themeName].toString("utf8")
476
+ );
477
+
478
+ Object.entries(overrideMessages).forEach(
479
+ ([key, value]) => (messages[key] = value)
480
+ );
481
+ }
482
+
483
+ const editor = propertiesParser.createEditor();
484
+
485
+ Object.entries(messages).forEach(([key, value]) => {
486
+ editor.set(key, value);
487
+ });
488
+
489
+ fs.writeFileSync(
490
+ pathJoin(messageDirPath, `messages_${lang}.properties`),
491
+ Buffer.from(editor.toString(), "utf8")
492
+ );
493
+ }
494
+ );
495
+ };
496
+ }
497
+
342
498
  keycloak_static_resources: {
343
499
  if (isSpa) {
344
500
  break keycloak_static_resources;
package/src/bin/main.ts CHANGED
@@ -191,7 +191,7 @@ program
191
191
  program
192
192
  .command({
193
193
  name: "initialize-account-theme",
194
- description: "Initialize the account theme."
194
+ description: "Initialize an Account Single-Page or Multi-Page custom Account UI."
195
195
  })
196
196
  .task({
197
197
  skip,
@@ -202,6 +202,20 @@ program
202
202
  }
203
203
  });
204
204
 
205
+ program
206
+ .command({
207
+ name: "initialize-admin-theme",
208
+ description: "Initialize an Admin Console custom UI."
209
+ })
210
+ .task({
211
+ skip,
212
+ handler: async ({ projectDirPath }) => {
213
+ const { command } = await import("./initialize-admin-theme");
214
+
215
+ await command({ buildContext: getBuildContext({ projectDirPath }) });
216
+ }
217
+ });
218
+
205
219
  program
206
220
  .command({
207
221
  name: "copy-keycloak-resources-to-public",
@@ -259,11 +273,12 @@ program
259
273
  .option({
260
274
  key: "file",
261
275
  name: (() => {
262
- const name = "file";
276
+ const long = "file";
277
+ const short = "f";
263
278
 
264
- optionsKeys.push(name);
279
+ optionsKeys.push(long, short);
265
280
 
266
- return name;
281
+ return { long, short };
267
282
  })(),
268
283
  description: [
269
284
  "Relative path of the file relative to the directory of your keycloak theme source",
@@ -32,38 +32,20 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
32
32
  await fsPr.readFile(pathJoin(uiModuleDirPath, KEYCLOAK_THEME, fileRelativePath))
33
33
  ).toString("utf8");
34
34
 
35
- const toComment = (lines: string[]) => {
36
- for (const ext of [".ts", ".tsx", ".css", ".less", ".sass", ".js", ".jsx"]) {
37
- if (!fileRelativePath.endsWith(ext)) {
38
- continue;
39
- }
40
-
41
- return [`/**`, ...lines.map(line => ` * ${line}`), ` */`].join("\n");
42
- }
43
-
44
- if (fileRelativePath.endsWith(".html")) {
45
- return [`<!--`, ...lines.map(line => ` ${line}`), `-->`].join("\n");
46
- }
47
-
48
- return undefined;
49
- };
50
-
51
- const comment = toComment(
52
- isForEjection
35
+ sourceCode = addCommentToSourceCode({
36
+ sourceCode,
37
+ fileRelativePath,
38
+ commentLines: isForEjection
53
39
  ? [`This file was ejected from ${uiModuleName} version ${uiModuleVersion}.`]
54
40
  : [
55
41
  `WARNING: Before modifying this file run the following command:`,
56
42
  ``,
57
- `$ npx keycloakify eject-file --file ${fileRelativePath.split(pathSep).join("/")}`,
43
+ `$ npx keycloakify eject-file --file '${fileRelativePath.split(pathSep).join("/")}'`,
58
44
  ``,
59
45
  `This file comes from ${uiModuleName} version ${uiModuleVersion}.`,
60
46
  `This file has been copied over to your repo by your postinstall script: \`npx keycloakify postinstall\``
61
47
  ]
62
- );
63
-
64
- if (comment !== undefined) {
65
- sourceCode = [comment, ``, sourceCode].join("\n");
66
- }
48
+ });
67
49
 
68
50
  const destFilePath = pathJoin(buildContext.themeSrcDirPath, fileRelativePath);
69
51
 
@@ -80,3 +62,60 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
80
62
 
81
63
  return Buffer.from(sourceCode, "utf8");
82
64
  }
65
+
66
+ function addCommentToSourceCode(params: {
67
+ sourceCode: string;
68
+ fileRelativePath: string;
69
+ commentLines: string[];
70
+ }): string {
71
+ const { sourceCode, fileRelativePath, commentLines } = params;
72
+
73
+ const toResult = (comment: string) => {
74
+ return [comment, ``, sourceCode].join("\n");
75
+ };
76
+
77
+ for (const ext of [".ts", ".tsx", ".css", ".less", ".sass", ".js", ".jsx"]) {
78
+ if (!fileRelativePath.endsWith(ext)) {
79
+ continue;
80
+ }
81
+
82
+ return toResult(
83
+ [`/**`, ...commentLines.map(line => ` * ${line}`), ` */`].join("\n")
84
+ );
85
+ }
86
+
87
+ if (fileRelativePath.endsWith(".properties")) {
88
+ return toResult(commentLines.map(line => `# ${line}`).join("\n"));
89
+ }
90
+
91
+ if (fileRelativePath.endsWith(".html") || fileRelativePath.endsWith(".svg")) {
92
+ const comment = [
93
+ `<!--`,
94
+ ...commentLines.map(
95
+ line =>
96
+ ` ${line.replace("--file", "-f").replace("Before modifying", "Before modifying or replacing")}`
97
+ ),
98
+ `-->`
99
+ ].join("\n");
100
+
101
+ if (fileRelativePath.endsWith(".html") && sourceCode.trim().startsWith("<!")) {
102
+ const [first, ...rest] = sourceCode.split(">");
103
+
104
+ const last = rest.join(">");
105
+
106
+ return [`${first}>`, comment, last].join("\n");
107
+ }
108
+
109
+ if (fileRelativePath.endsWith(".svg") && sourceCode.trim().startsWith("<?")) {
110
+ const [first, ...rest] = sourceCode.split("?>");
111
+
112
+ const last = rest.join("?>");
113
+
114
+ return [`${first}?>`, comment, last].join("\n");
115
+ }
116
+
117
+ return toResult(comment);
118
+ }
119
+
120
+ return sourceCode;
121
+ }
@@ -149,7 +149,7 @@ export async function installUiModulesPeerDependencies(params: {
149
149
 
150
150
  await fsPr.writeFile(buildContext.packageJsonFilePath, packageJsonContentStr);
151
151
 
152
- npmInstall({
152
+ await npmInstall({
153
153
  packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
154
154
  });
155
155
 
@@ -16,6 +16,7 @@ import {
16
16
  import * as crypto from "crypto";
17
17
  import { KEYCLOAK_THEME } from "../shared/constants";
18
18
  import { exclude } from "tsafe/exclude";
19
+ import { isAmong } from "tsafe/isAmong";
19
20
 
20
21
  export type UiModuleMeta = {
21
22
  moduleName: string;
@@ -264,7 +265,12 @@ export async function getUiModuleMetas(params: {
264
265
  moduleName,
265
266
  version,
266
267
  files,
267
- peerDependencies
268
+ peerDependencies: Object.fromEntries(
269
+ Object.entries(peerDependencies).filter(
270
+ ([moduleName]) =>
271
+ !isAmong(["react", "@types/react"], moduleName)
272
+ )
273
+ )
268
274
  });
269
275
  }
270
276
  )
@@ -0,0 +1,70 @@
1
+ import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
2
+ import { assert } from "tsafe/assert";
3
+ import type { BuildContext } from "./buildContext";
4
+
5
+ export type BuildContextLike = {
6
+ projectDirPath: string;
7
+ packageJsonFilePath: string;
8
+ };
9
+
10
+ assert<BuildContext extends BuildContextLike ? true : false>();
11
+
12
+ export function addPostinstallScriptIfNotPresent(params: {
13
+ parsedPackageJson: { scripts?: Record<string, string | undefined> };
14
+ buildContext: BuildContextLike;
15
+ }) {
16
+ const { parsedPackageJson, buildContext } = params;
17
+
18
+ const cmd_base = "keycloakify postinstall";
19
+
20
+ const projectCliOptionValue = (() => {
21
+ const packageJsonDirPath = pathDirname(buildContext.packageJsonFilePath);
22
+
23
+ const relativePath = pathRelative(
24
+ packageJsonDirPath,
25
+ buildContext.projectDirPath
26
+ );
27
+
28
+ if (relativePath === "") {
29
+ return undefined;
30
+ }
31
+
32
+ return relativePath.split(pathSep).join("/");
33
+ })();
34
+
35
+ const generateCmd = (params: { cmd_preexisting: string | undefined }) => {
36
+ const { cmd_preexisting } = params;
37
+
38
+ let cmd = cmd_preexisting === undefined ? "" : `${cmd_preexisting} && `;
39
+
40
+ cmd += cmd_base;
41
+
42
+ if (projectCliOptionValue !== undefined) {
43
+ cmd += ` -p ${projectCliOptionValue}`;
44
+ }
45
+
46
+ return cmd;
47
+ };
48
+
49
+ {
50
+ const scripts = (parsedPackageJson.scripts ??= {});
51
+
52
+ for (const scriptName of ["postinstall", "prepare"]) {
53
+ const cmd_preexisting = scripts[scriptName];
54
+
55
+ if (cmd_preexisting === undefined) {
56
+ continue;
57
+ }
58
+
59
+ if (cmd_preexisting.includes(cmd_base)) {
60
+ scripts[scriptName] = generateCmd({ cmd_preexisting });
61
+ return;
62
+ }
63
+ }
64
+ }
65
+
66
+ parsedPackageJson.scripts = {
67
+ postinstall: generateCmd({ cmd_preexisting: undefined }),
68
+ ...parsedPackageJson.scripts
69
+ };
70
+ }
@@ -12,6 +12,7 @@ export type CommandName =
12
12
  | "add-story"
13
13
  | "initialize-account-theme"
14
14
  | "initialize-admin-theme"
15
+ | "initialize-admin-theme"
15
16
  | "initialize-email-theme"
16
17
  | "copy-keycloak-resources-to-public";
17
18