keycloakify 11.7.4 → 11.8.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 (29) hide show
  1. package/bin/{911.index.js → 297.index.js} +18918 -11359
  2. package/bin/355.index.js +41 -703
  3. package/bin/363.index.js +3 -0
  4. package/bin/369.index.js +968 -0
  5. package/bin/656.index.js +111 -0
  6. package/bin/{288.index.js → 664.index.js} +13 -119
  7. package/bin/780.index.js +9 -7
  8. package/bin/880.index.js +215 -160
  9. package/bin/932.index.js +965 -0
  10. package/bin/97.index.js +2092 -770
  11. package/bin/main.js +52 -24
  12. package/bin/shared/buildContext.d.ts +11 -3
  13. package/package.json +10 -13
  14. package/src/bin/initialize-account-theme/initialize-account-theme.ts +29 -27
  15. package/src/bin/initialize-email-theme.ts +103 -53
  16. package/src/bin/keycloakify/generateResources/generateResources.ts +285 -205
  17. package/src/bin/shared/{initializeSpa/addSyncExtensionsToPostinstallScript.ts → addSyncExtensionsToPostinstallScript.ts} +1 -1
  18. package/src/bin/shared/buildContext.ts +69 -24
  19. package/src/bin/shared/{initializeSpa/initializeSpa.ts → initializeSpa.ts} +3 -3
  20. package/src/bin/sync-extensions/getExtensionModuleFileSourceCodeReadyToBeCopied.ts +6 -0
  21. package/vite-plugin/index.js +48 -20
  22. package/bin/313.index.js +0 -377
  23. package/bin/678.index.js +0 -7565
  24. package/bin/9.index.js +0 -850
  25. package/bin/947.index.js +0 -1565
  26. package/bin/shared/initializeSpa/index.d.ts +0 -1
  27. package/src/bin/shared/initializeSpa/index.ts +0 -1
  28. /package/bin/shared/{initializeSpa/addSyncExtensionsToPostinstallScript.d.ts → addSyncExtensionsToPostinstallScript.d.ts} +0 -0
  29. /package/bin/shared/{initializeSpa/initializeSpa.d.ts → initializeSpa.d.ts} +0 -0
@@ -41,6 +41,7 @@ import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPa
41
41
  import propertiesParser from "properties-parser";
42
42
  import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
43
43
  import { listInstalledModules } from "../../tools/listInstalledModules";
44
+ import { isInside } from "../../tools/isInside";
44
45
 
45
46
  export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
46
47
  BuildContextLike_generateMessageProperties & {
@@ -78,12 +79,23 @@ export async function generateResources(params: {
78
79
  };
79
80
 
80
81
  const writeMessagePropertiesFilesByThemeType: Partial<
81
- Record<ThemeType, (params: { messageDirPath: string; themeName: string }) => void>
82
+ Record<
83
+ ThemeType | "email",
84
+ (params: { messageDirPath: string; themeName: string }) => void
85
+ >
82
86
  > = {};
83
87
 
84
- for (const themeType of THEME_TYPES) {
85
- if (!buildContext.implementedThemeTypes[themeType].isImplemented) {
86
- continue;
88
+ for (const themeType of [...THEME_TYPES, "email"] as const) {
89
+ let isNative: boolean;
90
+
91
+ {
92
+ const v = buildContext.implementedThemeTypes[themeType];
93
+
94
+ if (!v.isImplemented && !v.isImplemented_native) {
95
+ continue;
96
+ }
97
+
98
+ isNative = !v.isImplemented && v.isImplemented_native;
87
99
  }
88
100
 
89
101
  const getAccountThemeType = () => {
@@ -102,12 +114,18 @@ export async function generateResources(params: {
102
114
  return getAccountThemeType() === "Single-Page";
103
115
  case "admin":
104
116
  return true;
117
+ case "email":
118
+ return false;
105
119
  }
106
120
  })();
107
121
 
108
122
  const themeTypeDirPath = getThemeTypeDirPath({ themeName, themeType });
109
123
 
110
124
  apply_replacers_and_move_to_theme_resources: {
125
+ if (isNative) {
126
+ break apply_replacers_and_move_to_theme_resources;
127
+ }
128
+
111
129
  const destDirPath = pathJoin(
112
130
  themeTypeDirPath,
113
131
  "resources",
@@ -191,59 +209,93 @@ export async function generateResources(params: {
191
209
  });
192
210
  }
193
211
 
194
- const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
195
- themeName,
196
- indexHtmlCode: fs
197
- .readFileSync(pathJoin(buildContext.projectBuildDirPath, "index.html"))
198
- .toString("utf8"),
199
- buildContext,
200
- keycloakifyVersion: readThisNpmPackageVersion(),
201
- themeType,
202
- fieldNames: isSpa
203
- ? []
204
- : (assert(themeType !== "admin"),
205
- readFieldNameUsage({
206
- themeSrcDirPath: buildContext.themeSrcDirPath,
207
- themeType
208
- }))
209
- });
212
+ generate_ftl_files: {
213
+ if (isNative) {
214
+ break generate_ftl_files;
215
+ }
210
216
 
211
- [
212
- ...(() => {
213
- switch (themeType) {
214
- case "login":
215
- return LOGIN_THEME_PAGE_IDS;
216
- case "account":
217
- return getAccountThemeType() === "Single-Page"
218
- ? ["index.ftl"]
219
- : ACCOUNT_THEME_PAGE_IDS;
220
- case "admin":
221
- return ["index.ftl"];
222
- }
223
- })(),
224
- ...(isSpa
225
- ? []
226
- : readExtraPagesNames({
227
- themeType,
228
- themeSrcDirPath: buildContext.themeSrcDirPath
229
- }))
230
- ].forEach(pageId => {
231
- const { ftlCode } = generateFtlFilesCode({ pageId });
217
+ assert(themeType !== "email");
232
218
 
233
- fs.writeFileSync(
234
- pathJoin(themeTypeDirPath, pageId),
235
- Buffer.from(ftlCode, "utf8")
236
- );
237
- });
219
+ const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
220
+ themeName,
221
+ indexHtmlCode: fs
222
+ .readFileSync(
223
+ pathJoin(buildContext.projectBuildDirPath, "index.html")
224
+ )
225
+ .toString("utf8"),
226
+ buildContext,
227
+ keycloakifyVersion: readThisNpmPackageVersion(),
228
+ themeType,
229
+ fieldNames: isSpa
230
+ ? []
231
+ : (assert(themeType !== "admin"),
232
+ readFieldNameUsage({
233
+ themeSrcDirPath: buildContext.themeSrcDirPath,
234
+ themeType
235
+ }))
236
+ });
237
+
238
+ [
239
+ ...(() => {
240
+ switch (themeType) {
241
+ case "login":
242
+ return LOGIN_THEME_PAGE_IDS;
243
+ case "account":
244
+ return getAccountThemeType() === "Single-Page"
245
+ ? ["index.ftl"]
246
+ : ACCOUNT_THEME_PAGE_IDS;
247
+ case "admin":
248
+ return ["index.ftl"];
249
+ }
250
+ })(),
251
+ ...(isSpa
252
+ ? []
253
+ : readExtraPagesNames({
254
+ themeType,
255
+ themeSrcDirPath: buildContext.themeSrcDirPath
256
+ }))
257
+ ].forEach(pageId => {
258
+ const { ftlCode } = generateFtlFilesCode({ pageId });
259
+
260
+ fs.writeFileSync(
261
+ pathJoin(themeTypeDirPath, pageId),
262
+ Buffer.from(ftlCode, "utf8")
263
+ );
264
+ });
265
+ }
266
+
267
+ copy_native_theme: {
268
+ if (!isNative) {
269
+ break copy_native_theme;
270
+ }
271
+
272
+ const dirPath = pathJoin(buildContext.themeSrcDirPath, themeType);
273
+
274
+ transformCodebase({
275
+ srcDirPath: dirPath,
276
+ destDirPath: getThemeTypeDirPath({ themeName, themeType }),
277
+ transformSourceCode: ({ fileRelativePath, sourceCode }) => {
278
+ if (isInside({ dirPath: "messages", filePath: fileRelativePath })) {
279
+ return undefined;
280
+ }
281
+
282
+ return { modifiedSourceCode: sourceCode };
283
+ }
284
+ });
285
+ }
238
286
 
239
287
  let languageTags: string[] | undefined = undefined;
240
288
 
241
289
  i18n_multi_page: {
290
+ if (isNative) {
291
+ break i18n_multi_page;
292
+ }
293
+
242
294
  if (isSpa) {
243
295
  break i18n_multi_page;
244
296
  }
245
297
 
246
- assert(themeType !== "admin");
298
+ assert(themeType !== "admin" && themeType !== "email");
247
299
 
248
300
  const wrap = generateMessageProperties({
249
301
  buildContext,
@@ -364,27 +416,24 @@ export async function generateResources(params: {
364
416
  );
365
417
  }
366
418
 
367
- i18n_single_page: {
368
- if (!isSpa) {
369
- break i18n_single_page;
419
+ i18n_for_spas_and_native: {
420
+ if (!isSpa && !isNative) {
421
+ break i18n_for_spas_and_native;
370
422
  }
371
423
 
372
424
  if (isLegacyAccountSpa) {
373
- break i18n_single_page;
425
+ break i18n_for_spas_and_native;
374
426
  }
375
427
 
376
- assert(themeType === "account" || themeType === "admin");
377
-
378
428
  const messagesDirPath_theme = pathJoin(
379
429
  buildContext.themeSrcDirPath,
380
430
  themeType,
381
- "i18n"
431
+ isNative ? "messages" : "i18n"
382
432
  );
383
433
 
384
- assert(
385
- fs.existsSync(messagesDirPath_theme),
386
- `${messagesDirPath_theme} is supposed to exist`
387
- );
434
+ if (!fs.existsSync(messagesDirPath_theme)) {
435
+ break i18n_for_spas_and_native;
436
+ }
388
437
 
389
438
  const propertiesByLang: Record<
390
439
  string,
@@ -524,6 +573,10 @@ export async function generateResources(params: {
524
573
  }
525
574
 
526
575
  keycloak_static_resources: {
576
+ if (isNative) {
577
+ break keycloak_static_resources;
578
+ }
579
+
527
580
  if (isSpa) {
528
581
  break keycloak_static_resources;
529
582
  }
@@ -540,77 +593,185 @@ export async function generateResources(params: {
540
593
  });
541
594
  }
542
595
 
543
- fs.writeFileSync(
544
- pathJoin(themeTypeDirPath, "theme.properties"),
545
- Buffer.from(
546
- [
547
- `parent=${(() => {
548
- switch (themeType) {
549
- case "account":
550
- switch (getAccountThemeType()) {
551
- case "Multi-Page":
552
- return "account-v1";
553
- case "Single-Page":
554
- return "base";
555
- }
556
- case "login":
557
- return "keycloak";
558
- case "admin":
559
- return "base";
560
- }
561
- assert<Equals<typeof themeType, never>>(false);
562
- })()}`,
563
- ...(themeType === "account" && getAccountThemeType() === "Single-Page"
564
- ? ["deprecatedMode=false"]
565
- : []),
566
- ...(buildContext.extraThemeProperties ?? []),
567
- ...[
568
- ...buildContext.environmentVariables,
569
- { name: KEYCLOAKIFY_SPA_DEV_SERVER_PORT, default: "" }
570
- ].map(
571
- ({ name, default: defaultValue }) =>
572
- `${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
573
- ),
574
- ...(languageTags === undefined
575
- ? []
576
- : [`locales=${languageTags.join(",")}`])
577
- ].join("\n\n"),
578
- "utf8"
579
- )
580
- );
581
- }
596
+ bring_in_account_v1: {
597
+ if (isNative) {
598
+ break bring_in_account_v1;
599
+ }
582
600
 
583
- email: {
584
- if (!buildContext.implementedThemeTypes.email.isImplemented) {
585
- break email;
586
- }
601
+ if (themeType !== "account") {
602
+ break bring_in_account_v1;
603
+ }
587
604
 
588
- const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
605
+ assert(buildContext.implementedThemeTypes.account.isImplemented);
589
606
 
590
- transformCodebase({
591
- srcDirPath: emailThemeSrcDirPath,
592
- destDirPath: getThemeTypeDirPath({ themeName, themeType: "email" })
593
- });
594
- }
607
+ if (buildContext.implementedThemeTypes.account.type !== "Multi-Page") {
608
+ break bring_in_account_v1;
609
+ }
595
610
 
596
- bring_in_account_v1: {
597
- if (!buildContext.implementedThemeTypes.account.isImplemented) {
598
- break bring_in_account_v1;
611
+ transformCodebase({
612
+ srcDirPath: pathJoin(getThisCodebaseRootDirPath(), "res", "account-v1"),
613
+ destDirPath: getThemeTypeDirPath({
614
+ themeName: "account-v1",
615
+ themeType: "account"
616
+ })
617
+ });
599
618
  }
600
619
 
601
- if (buildContext.implementedThemeTypes.account.type !== "Multi-Page") {
602
- break bring_in_account_v1;
620
+ generate_theme_properties: {
621
+ if (isNative) {
622
+ break generate_theme_properties;
623
+ }
624
+
625
+ assert(themeType !== "email");
626
+
627
+ fs.writeFileSync(
628
+ pathJoin(themeTypeDirPath, "theme.properties"),
629
+ Buffer.from(
630
+ [
631
+ `parent=${(() => {
632
+ switch (themeType) {
633
+ case "account":
634
+ switch (getAccountThemeType()) {
635
+ case "Multi-Page":
636
+ return "account-v1";
637
+ case "Single-Page":
638
+ return "base";
639
+ }
640
+ case "login":
641
+ return "keycloak";
642
+ case "admin":
643
+ return "base";
644
+ }
645
+ assert<Equals<typeof themeType, never>>;
646
+ })()}`,
647
+ ...(themeType === "account" &&
648
+ getAccountThemeType() === "Single-Page"
649
+ ? ["deprecatedMode=false"]
650
+ : []),
651
+ ...(buildContext.extraThemeProperties ?? []),
652
+ ...[
653
+ ...buildContext.environmentVariables,
654
+ { name: KEYCLOAKIFY_SPA_DEV_SERVER_PORT, default: "" }
655
+ ].map(
656
+ ({ name, default: defaultValue }) =>
657
+ `${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
658
+ ),
659
+ ...(languageTags === undefined
660
+ ? []
661
+ : [`locales=${languageTags.join(",")}`])
662
+ ].join("\n\n"),
663
+ "utf8"
664
+ )
665
+ );
603
666
  }
667
+ }
604
668
 
605
- transformCodebase({
606
- srcDirPath: pathJoin(getThisCodebaseRootDirPath(), "res", "account-v1"),
607
- destDirPath: getThemeTypeDirPath({
608
- themeName: "account-v1",
609
- themeType: "account"
610
- })
611
- });
669
+ for (const themeVariantName of buildContext.themeNames) {
670
+ for (const themeType of [...THEME_TYPES, "email"] as const) {
671
+ copy_main_theme_to_theme_variant_theme: {
672
+ let isNative: boolean;
673
+
674
+ {
675
+ const v = buildContext.implementedThemeTypes[themeType];
676
+
677
+ if (!v.isImplemented && !v.isImplemented_native) {
678
+ break copy_main_theme_to_theme_variant_theme;
679
+ }
680
+
681
+ isNative = !v.isImplemented && v.isImplemented_native;
682
+ }
683
+
684
+ if (themeVariantName === themeName) {
685
+ break copy_main_theme_to_theme_variant_theme;
686
+ }
687
+
688
+ transformCodebase({
689
+ srcDirPath: pathJoin(resourcesDirPath, "theme", themeName),
690
+ destDirPath: pathJoin(resourcesDirPath, "theme", themeVariantName),
691
+ transformSourceCode: isNative
692
+ ? undefined
693
+ : ({ fileRelativePath, sourceCode }) => {
694
+ if (
695
+ pathExtname(fileRelativePath) === ".ftl" &&
696
+ fileRelativePath.split(pathSep).length === 2
697
+ ) {
698
+ const modifiedSourceCode = Buffer.from(
699
+ Buffer.from(sourceCode)
700
+ .toString("utf-8")
701
+ .replace(
702
+ `"themeName": "${themeName}"`,
703
+ `"themeName": "${themeVariantName}"`
704
+ ),
705
+ "utf8"
706
+ );
707
+
708
+ assert(
709
+ Buffer.compare(modifiedSourceCode, sourceCode) !== 0
710
+ );
711
+
712
+ return { modifiedSourceCode };
713
+ }
714
+
715
+ return { modifiedSourceCode: sourceCode };
716
+ }
717
+ });
718
+ }
719
+ run_writeMessagePropertiesFiles: {
720
+ const writeMessagePropertiesFiles =
721
+ writeMessagePropertiesFilesByThemeType[themeType];
722
+
723
+ if (writeMessagePropertiesFiles === undefined) {
724
+ break run_writeMessagePropertiesFiles;
725
+ }
726
+
727
+ writeMessagePropertiesFiles({
728
+ messageDirPath: pathJoin(
729
+ getThemeTypeDirPath({ themeName: themeVariantName, themeType }),
730
+ "messages"
731
+ ),
732
+ themeName: themeVariantName
733
+ });
734
+ }
735
+ replace_xKeycloakify_themeName_in_native_ftl_files: {
736
+ {
737
+ const v = buildContext.implementedThemeTypes[themeType];
738
+
739
+ if (v.isImplemented || !v.isImplemented_native) {
740
+ break replace_xKeycloakify_themeName_in_native_ftl_files;
741
+ }
742
+ }
743
+
744
+ const emailThemeDirPath = getThemeTypeDirPath({
745
+ themeName,
746
+ themeType
747
+ });
748
+
749
+ transformCodebase({
750
+ srcDirPath: emailThemeDirPath,
751
+ destDirPath: emailThemeDirPath,
752
+ transformSourceCode: ({ filePath, sourceCode }) => {
753
+ if (!filePath.endsWith(".ftl")) {
754
+ return { modifiedSourceCode: sourceCode };
755
+ }
756
+
757
+ return {
758
+ modifiedSourceCode: Buffer.from(
759
+ sourceCode
760
+ .toString("utf8")
761
+ .replace(
762
+ /xKeycloakify\.themeName/g,
763
+ `"${themeName}"`
764
+ ),
765
+ "utf8"
766
+ )
767
+ };
768
+ }
769
+ });
770
+ }
771
+ }
612
772
  }
613
773
 
774
+ // Generate meta-inf/keycloak-themes.json
614
775
  {
615
776
  const metaInfKeycloakThemes: MetaInfKeycloakTheme = { themes: [] };
616
777
 
@@ -618,12 +779,15 @@ export async function generateResources(params: {
618
779
  metaInfKeycloakThemes.themes.push({
619
780
  name: themeName,
620
781
  types: objectEntries(buildContext.implementedThemeTypes)
621
- .filter(([, { isImplemented }]) => isImplemented)
782
+ .filter(([, v]) => v.isImplemented || v.isImplemented_native)
622
783
  .map(([themeType]) => themeType)
623
784
  });
624
785
  }
625
786
 
626
- if (buildContext.implementedThemeTypes.account.isImplemented) {
787
+ if (
788
+ buildContext.implementedThemeTypes.account.isImplemented &&
789
+ buildContext.implementedThemeTypes.account.type === "Multi-Page"
790
+ ) {
627
791
  metaInfKeycloakThemes.themes.push({
628
792
  name: "account-v1",
629
793
  types: ["account"]
@@ -635,88 +799,4 @@ export async function generateResources(params: {
635
799
  getNewMetaInfKeycloakTheme: () => metaInfKeycloakThemes
636
800
  });
637
801
  }
638
-
639
- for (const themeVariantName of buildContext.themeNames) {
640
- if (themeVariantName === themeName) {
641
- continue;
642
- }
643
-
644
- transformCodebase({
645
- srcDirPath: pathJoin(resourcesDirPath, "theme", themeName),
646
- destDirPath: pathJoin(resourcesDirPath, "theme", themeVariantName),
647
- transformSourceCode: ({ fileRelativePath, sourceCode }) => {
648
- if (
649
- pathExtname(fileRelativePath) === ".ftl" &&
650
- fileRelativePath.split(pathSep).length === 2
651
- ) {
652
- const modifiedSourceCode = Buffer.from(
653
- Buffer.from(sourceCode)
654
- .toString("utf-8")
655
- .replace(
656
- `"themeName": "${themeName}"`,
657
- `"themeName": "${themeVariantName}"`
658
- ),
659
- "utf8"
660
- );
661
-
662
- assert(Buffer.compare(modifiedSourceCode, sourceCode) !== 0);
663
-
664
- return { modifiedSourceCode };
665
- }
666
-
667
- return { modifiedSourceCode: sourceCode };
668
- }
669
- });
670
- }
671
-
672
- for (const themeName of buildContext.themeNames) {
673
- for (const [themeType, writeMessagePropertiesFiles] of objectEntries(
674
- writeMessagePropertiesFilesByThemeType
675
- )) {
676
- // NOTE: This is just a quirk of the type system: We can't really differentiate in a record
677
- // between the case where the key isn't present and the case where the value is `undefined`.
678
- if (writeMessagePropertiesFiles === undefined) {
679
- return;
680
- }
681
- writeMessagePropertiesFiles({
682
- messageDirPath: pathJoin(
683
- getThemeTypeDirPath({ themeName, themeType }),
684
- "messages"
685
- ),
686
- themeName
687
- });
688
- }
689
- }
690
-
691
- modify_email_theme_per_variant: {
692
- if (!buildContext.implementedThemeTypes.email.isImplemented) {
693
- break modify_email_theme_per_variant;
694
- }
695
-
696
- for (const themeName of buildContext.themeNames) {
697
- const emailThemeDirPath = getThemeTypeDirPath({
698
- themeName,
699
- themeType: "email"
700
- });
701
-
702
- transformCodebase({
703
- srcDirPath: emailThemeDirPath,
704
- destDirPath: emailThemeDirPath,
705
- transformSourceCode: ({ filePath, sourceCode }) => {
706
- if (!filePath.endsWith(".ftl")) {
707
- return { modifiedSourceCode: sourceCode };
708
- }
709
-
710
- return {
711
- modifiedSourceCode: Buffer.from(
712
- sourceCode
713
- .toString("utf8")
714
- .replace(/xKeycloakify\.themeName/g, `"${themeName}"`),
715
- "utf8"
716
- )
717
- };
718
- }
719
- });
720
- }
721
- }
722
802
  }
@@ -1,6 +1,6 @@
1
1
  import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
2
2
  import { assert } from "tsafe/assert";
3
- import type { BuildContext } from "../buildContext";
3
+ import type { BuildContext } from "./buildContext";
4
4
 
5
5
  export type BuildContextLike = {
6
6
  projectDirPath: string;