keycloakify 11.5.4 → 11.6.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.
@@ -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
@@ -259,11 +259,12 @@ program
259
259
  .option({
260
260
  key: "file",
261
261
  name: (() => {
262
- const name = "file";
262
+ const long = "file";
263
+ const short = "f";
263
264
 
264
- optionsKeys.push(name);
265
+ optionsKeys.push(long, short);
265
266
 
266
- return name;
267
+ return { long, short };
267
268
  })(),
268
269
  description: [
269
270
  "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
+ }