keycloakify 9.3.0 → 9.4.0-rc.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 (182) hide show
  1. package/README.md +5 -0
  2. package/account/kcContext/getKcContextFromWindow.js +2 -2
  3. package/account/kcContext/getKcContextFromWindow.js.map +1 -1
  4. package/bin/constants.d.ts +4 -1
  5. package/bin/constants.js +5 -2
  6. package/bin/constants.js.map +1 -1
  7. package/bin/copy-keycloak-resources-to-public.js +3 -4
  8. package/bin/copy-keycloak-resources-to-public.js.map +1 -1
  9. package/bin/download-builtin-keycloak-theme.js +98 -31
  10. package/bin/download-builtin-keycloak-theme.js.map +1 -1
  11. package/bin/eject-keycloak-page.js +2 -2
  12. package/bin/eject-keycloak-page.js.map +1 -1
  13. package/bin/{getSrcDirPath.js → getThemeSrcDirPath.js} +1 -1
  14. package/bin/getThemeSrcDirPath.js.map +1 -0
  15. package/bin/initialize-email-theme.js +6 -5
  16. package/bin/initialize-email-theme.js.map +1 -1
  17. package/bin/keycloakify/{BuildOptions.d.ts → buildOptions/buildOptions.d.ts} +2 -1
  18. package/bin/keycloakify/buildOptions/buildOptions.js +136 -0
  19. package/bin/keycloakify/buildOptions/buildOptions.js.map +1 -0
  20. package/bin/keycloakify/buildOptions/getKeycloakifyBuildDirPath.d.ts +7 -0
  21. package/bin/keycloakify/buildOptions/getKeycloakifyBuildDirPath.js +27 -0
  22. package/bin/keycloakify/buildOptions/getKeycloakifyBuildDirPath.js.map +1 -0
  23. package/bin/keycloakify/buildOptions/index.d.ts +1 -0
  24. package/bin/keycloakify/{generateJavaStackFiles → buildOptions}/index.js +1 -1
  25. package/bin/keycloakify/buildOptions/index.js.map +1 -0
  26. package/bin/keycloakify/buildOptions/parsedPackageJson.d.ts +19 -0
  27. package/bin/keycloakify/{parsedPackageJson.js → buildOptions/parsedPackageJson.js} +6 -7
  28. package/bin/keycloakify/buildOptions/parsedPackageJson.js.map +1 -0
  29. package/bin/keycloakify/buildOptions/resolvedViteConfig.d.ts +12 -0
  30. package/bin/keycloakify/buildOptions/resolvedViteConfig.js +82 -0
  31. package/bin/keycloakify/buildOptions/resolvedViteConfig.js.map +1 -0
  32. package/bin/keycloakify/generateFtl/generateFtl.d.ts +4 -1
  33. package/bin/keycloakify/generateFtl/generateFtl.js +5 -5
  34. package/bin/keycloakify/generateFtl/generateFtl.js.map +1 -1
  35. package/bin/keycloakify/generatePom.d.ts +12 -0
  36. package/bin/keycloakify/generatePom.js +59 -0
  37. package/bin/keycloakify/generatePom.js.map +1 -0
  38. package/bin/keycloakify/{generateJavaStackFiles → generateTheme}/bringInAccountV1.d.ts +2 -1
  39. package/bin/keycloakify/{generateJavaStackFiles → generateTheme}/bringInAccountV1.js +21 -67
  40. package/bin/keycloakify/generateTheme/bringInAccountV1.js.map +1 -0
  41. package/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.d.ts +0 -3
  42. package/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.js +5 -34
  43. package/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.js.map +1 -1
  44. package/bin/keycloakify/generateTheme/generateTheme.d.ts +4 -1
  45. package/bin/keycloakify/generateTheme/generateTheme.js +123 -70
  46. package/bin/keycloakify/generateTheme/generateTheme.js.map +1 -1
  47. package/bin/keycloakify/keycloakify.js +13 -72
  48. package/bin/keycloakify/keycloakify.js.map +1 -1
  49. package/bin/keycloakify/replacers/replaceImportsInCssCode.js +2 -1
  50. package/bin/keycloakify/replacers/replaceImportsInCssCode.js.map +1 -1
  51. package/bin/keycloakify/replacers/replaceImportsInInlineCssCode.js +2 -1
  52. package/bin/keycloakify/replacers/replaceImportsInInlineCssCode.js.map +1 -1
  53. package/bin/keycloakify/replacers/replaceImportsInJsCode/index.d.ts +1 -0
  54. package/bin/keycloakify/replacers/replaceImportsInJsCode/index.js +18 -0
  55. package/bin/keycloakify/replacers/replaceImportsInJsCode/index.js.map +1 -0
  56. package/bin/keycloakify/replacers/replaceImportsInJsCode/replaceImportsInJsCode.d.ts +12 -0
  57. package/bin/keycloakify/replacers/replaceImportsInJsCode/replaceImportsInJsCode.js +70 -0
  58. package/bin/keycloakify/replacers/replaceImportsInJsCode/replaceImportsInJsCode.js.map +1 -0
  59. package/bin/keycloakify/replacers/replaceImportsInJsCode/vite.d.ts +13 -0
  60. package/bin/keycloakify/replacers/replaceImportsInJsCode/vite.js +95 -0
  61. package/bin/keycloakify/replacers/replaceImportsInJsCode/vite.js.map +1 -0
  62. package/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.d.ts +12 -0
  63. package/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.js +108 -0
  64. package/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.js.map +1 -0
  65. package/bin/promptKeycloakVersion.js +0 -1
  66. package/bin/promptKeycloakVersion.js.map +1 -1
  67. package/bin/tools/OptionalIfCanBeUndefined.d.ts +14 -0
  68. package/bin/tools/OptionalIfCanBeUndefined.js +3 -0
  69. package/bin/tools/OptionalIfCanBeUndefined.js.map +1 -0
  70. package/bin/tools/SemVer.d.ts +26 -0
  71. package/bin/tools/{NpmModuleVersion.js → SemVer.js} +39 -23
  72. package/bin/tools/SemVer.js.map +1 -0
  73. package/bin/tools/String.prototype.replaceAll.d.ts +1 -0
  74. package/bin/tools/String.prototype.replaceAll.js +29 -0
  75. package/bin/tools/String.prototype.replaceAll.js.map +1 -0
  76. package/bin/tools/crawl.js +9 -9
  77. package/bin/tools/crawl.js.map +1 -1
  78. package/bin/tools/downloadAndUnzip.js +30 -19
  79. package/bin/tools/downloadAndUnzip.js.map +1 -1
  80. package/bin/tools/fs.rm.d.ts +8 -0
  81. package/bin/tools/fs.rm.js +151 -0
  82. package/bin/tools/fs.rm.js.map +1 -0
  83. package/bin/tools/fs.rmSync.d.ts +8 -0
  84. package/bin/tools/fs.rmSync.js +58 -0
  85. package/bin/tools/fs.rmSync.js.map +1 -0
  86. package/bin/tools/getAbsoluteAndInOsFormatPath.js +1 -0
  87. package/bin/tools/getAbsoluteAndInOsFormatPath.js.map +1 -1
  88. package/bin/tools/octokit-addons/getLatestsSemVersionedTag.d.ts +2 -3
  89. package/bin/tools/octokit-addons/getLatestsSemVersionedTag.js +6 -6
  90. package/bin/tools/octokit-addons/getLatestsSemVersionedTag.js.map +1 -1
  91. package/bin/tools/transformCodebase.d.ts +5 -1
  92. package/bin/tools/transformCodebase.js +31 -13
  93. package/bin/tools/transformCodebase.js.map +1 -1
  94. package/login/kcContext/getKcContextFromWindow.js +2 -2
  95. package/login/kcContext/getKcContextFromWindow.js.map +1 -1
  96. package/login/lib/useGetClassName.js +1 -1
  97. package/login/lib/useGetClassName.js.map +1 -1
  98. package/login/pages/LoginOtp.js +5 -43
  99. package/login/pages/LoginOtp.js.map +1 -1
  100. package/package.json +104 -53
  101. package/src/account/kcContext/getKcContextFromWindow.ts +2 -2
  102. package/src/bin/constants.ts +4 -1
  103. package/src/bin/copy-keycloak-resources-to-public.ts +2 -3
  104. package/src/bin/download-builtin-keycloak-theme.ts +96 -48
  105. package/src/bin/eject-keycloak-page.ts +1 -1
  106. package/src/bin/initialize-email-theme.ts +4 -3
  107. package/src/bin/keycloakify/buildOptions/buildOptions.ts +185 -0
  108. package/src/bin/keycloakify/buildOptions/getKeycloakifyBuildDirPath.ts +33 -0
  109. package/src/bin/keycloakify/buildOptions/index.ts +1 -0
  110. package/src/bin/keycloakify/{parsedPackageJson.ts → buildOptions/parsedPackageJson.ts} +4 -6
  111. package/src/bin/keycloakify/buildOptions/resolvedViteConfig.ts +85 -0
  112. package/src/bin/keycloakify/generateFtl/generateFtl.ts +12 -8
  113. package/src/bin/keycloakify/generatePom.ts +70 -0
  114. package/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts +1 -1
  115. package/src/bin/keycloakify/generateTheme/bringInAccountV1.ts +83 -0
  116. package/src/bin/keycloakify/generateTheme/downloadKeycloakStaticResources.ts +11 -52
  117. package/src/bin/keycloakify/generateTheme/generateTheme.ts +141 -51
  118. package/src/bin/keycloakify/keycloakify.ts +11 -58
  119. package/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts +3 -2
  120. package/src/bin/keycloakify/replacers/replaceImportsInInlineCssCode.ts +3 -2
  121. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/index.ts +1 -0
  122. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/replaceImportsInJsCode.ts +66 -0
  123. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts +85 -0
  124. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.ts +90 -0
  125. package/src/bin/promptKeycloakVersion.ts +0 -1
  126. package/src/bin/tools/OptionalIfCanBeUndefined.ts +12 -0
  127. package/src/bin/tools/SemVer.ts +99 -0
  128. package/src/bin/tools/String.prototype.replaceAll.ts +30 -0
  129. package/src/bin/tools/crawl.ts +8 -8
  130. package/src/bin/tools/downloadAndUnzip.ts +11 -4
  131. package/src/bin/tools/fs.rm.ts +43 -0
  132. package/src/bin/tools/fs.rmSync.ts +34 -0
  133. package/src/bin/tools/getAbsoluteAndInOsFormatPath.ts +2 -0
  134. package/src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts +9 -9
  135. package/src/bin/tools/transformCodebase.ts +35 -14
  136. package/src/login/kcContext/getKcContextFromWindow.ts +2 -2
  137. package/src/login/lib/useGetClassName.ts +1 -1
  138. package/src/login/pages/LoginOtp.tsx +64 -94
  139. package/src/tsconfig.json +1 -1
  140. package/src/vite-plugin/config.json +232 -0
  141. package/src/vite-plugin/index.ts +1 -0
  142. package/src/vite-plugin/tsconfig.json +18 -0
  143. package/src/vite-plugin/vite-plugin.ts +128 -0
  144. package/vite-plugin/index.d.ts +1 -0
  145. package/vite-plugin/index.js +18 -0
  146. package/vite-plugin/index.js.map +1 -0
  147. package/vite-plugin/tsconfig.tsbuildinfo +1 -0
  148. package/vite-plugin/vite-plugin.d.ts +2 -0
  149. package/vite-plugin/vite-plugin.js +118 -0
  150. package/vite-plugin/vite-plugin.js.map +1 -0
  151. package/bin/getSrcDirPath.js.map +0 -1
  152. package/bin/keycloakify/BuildOptions.js +0 -113
  153. package/bin/keycloakify/BuildOptions.js.map +0 -1
  154. package/bin/keycloakify/ftlValuesGlobalName.d.ts +0 -1
  155. package/bin/keycloakify/ftlValuesGlobalName.js +0 -5
  156. package/bin/keycloakify/ftlValuesGlobalName.js.map +0 -1
  157. package/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.js.map +0 -1
  158. package/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.d.ts +0 -16
  159. package/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.js +0 -205
  160. package/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.js.map +0 -1
  161. package/bin/keycloakify/generateJavaStackFiles/index.d.ts +0 -1
  162. package/bin/keycloakify/generateJavaStackFiles/index.js.map +0 -1
  163. package/bin/keycloakify/generateTheme/readStaticResourcesUsage.d.ts +0 -15
  164. package/bin/keycloakify/generateTheme/readStaticResourcesUsage.js +0 -158
  165. package/bin/keycloakify/generateTheme/readStaticResourcesUsage.js.map +0 -1
  166. package/bin/keycloakify/parsedPackageJson.d.ts +0 -108
  167. package/bin/keycloakify/parsedPackageJson.js.map +0 -1
  168. package/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.d.ts +0 -5
  169. package/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.js +0 -74
  170. package/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.js.map +0 -1
  171. package/bin/tools/NpmModuleVersion.d.ts +0 -22
  172. package/bin/tools/NpmModuleVersion.js.map +0 -1
  173. package/src/bin/keycloakify/BuildOptions.ts +0 -157
  174. package/src/bin/keycloakify/ftlValuesGlobalName.ts +0 -1
  175. package/src/bin/keycloakify/generateJavaStackFiles/bringInAccountV1.ts +0 -101
  176. package/src/bin/keycloakify/generateJavaStackFiles/generateJavaStackFiles.ts +0 -141
  177. package/src/bin/keycloakify/generateJavaStackFiles/index.ts +0 -1
  178. package/src/bin/keycloakify/generateTheme/readStaticResourcesUsage.ts +0 -76
  179. package/src/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.ts +0 -51
  180. package/src/bin/tools/NpmModuleVersion.ts +0 -73
  181. /package/bin/{getSrcDirPath.d.ts → getThemeSrcDirPath.d.ts} +0 -0
  182. /package/src/bin/{getSrcDirPath.ts → getThemeSrcDirPath.ts} +0 -0
@@ -1,28 +1,38 @@
1
1
  import { transformCodebase } from "../../tools/transformCodebase";
2
2
  import * as fs from "fs";
3
- import { join as pathJoin, basename as pathBasename, resolve as pathResolve } from "path";
4
- import { replaceImportsFromStaticInJsCode } from "../replacers/replaceImportsFromStaticInJsCode";
3
+ import { join as pathJoin, basename as pathBasename, resolve as pathResolve, dirname as pathDirname } from "path";
4
+ import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
5
5
  import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
6
6
  import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl";
7
- import { themeTypes, type ThemeType, lastKeycloakVersionWithAccountV1, keycloak_resources, retrocompatPostfix, accountV1 } from "../../constants";
7
+ import {
8
+ type ThemeType,
9
+ lastKeycloakVersionWithAccountV1,
10
+ keycloak_resources,
11
+ retrocompatPostfix,
12
+ accountV1ThemeName,
13
+ basenameOfTheKeycloakifyResourcesDir
14
+ } from "../../constants";
8
15
  import { isInside } from "../../tools/isInside";
9
- import type { BuildOptions } from "../BuildOptions";
16
+ import type { BuildOptions } from "../buildOptions";
10
17
  import { assert, type Equals } from "tsafe/assert";
11
18
  import { downloadKeycloakStaticResources } from "./downloadKeycloakStaticResources";
12
19
  import { readFieldNameUsage } from "./readFieldNameUsage";
13
20
  import { readExtraPagesNames } from "./readExtraPageNames";
14
21
  import { generateMessageProperties } from "./generateMessageProperties";
15
- import { readStaticResourcesUsage } from "./readStaticResourcesUsage";
22
+ import { bringInAccountV1 } from "./bringInAccountV1";
16
23
 
17
24
  export type BuildOptionsLike = {
25
+ bundler: "vite" | "webpack";
18
26
  extraThemeProperties: string[] | undefined;
19
27
  themeVersion: string;
20
28
  loginThemeResourcesFromKeycloakVersion: string;
21
- urlPathname: string | undefined;
22
29
  keycloakifyBuildDirPath: string;
23
30
  reactAppBuildDirPath: string;
24
31
  cacheDirPath: string;
32
+ assetsDirPath: string;
33
+ urlPathname: string | undefined;
25
34
  doBuildRetrocompatAccountTheme: boolean;
35
+ themeNames: string[];
26
36
  };
27
37
 
28
38
  assert<BuildOptions extends BuildOptionsLike ? true : false>();
@@ -49,29 +59,47 @@ export async function generateTheme(params: {
49
59
  );
50
60
  };
51
61
 
52
- let allCssGlobalsToDefine: Record<string, string> = {};
62
+ const cssGlobalsToDefine: Record<string, string> = {};
53
63
 
54
- let generateFtlFilesCode_glob: ReturnType<typeof generateFtlFilesCodeFactory>["generateFtlFilesCode"] | undefined = undefined;
64
+ const implementedThemeTypes: Record<ThemeType | "email", boolean> = {
65
+ "login": false,
66
+ "account": false,
67
+ "email": false
68
+ };
55
69
 
56
- for (const themeType of themeTypes) {
70
+ for (const themeType of ["login", "account"] as const) {
57
71
  if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
58
72
  continue;
59
73
  }
60
74
 
61
- const themeTypeDirPath = getThemeTypeDirPath({ themeType });
75
+ implementedThemeTypes[themeType] = true;
62
76
 
63
- copy_app_resources_to_theme_path: {
64
- const isFirstPass = themeType.indexOf(themeType) === 0;
77
+ const themeTypeDirPath = getThemeTypeDirPath({ themeType });
65
78
 
66
- if (!isFirstPass) {
67
- break copy_app_resources_to_theme_path;
79
+ apply_replacers_and_move_to_theme_resources: {
80
+ if (themeType === "account" && implementedThemeTypes.login) {
81
+ // NOTE: We prevend doing it twice, it has been done for the login theme.
82
+
83
+ transformCodebase({
84
+ "srcDirPath": pathJoin(
85
+ getThemeTypeDirPath({
86
+ "themeType": "login"
87
+ }),
88
+ "resources",
89
+ basenameOfTheKeycloakifyResourcesDir
90
+ ),
91
+ "destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir)
92
+ });
93
+
94
+ break apply_replacers_and_move_to_theme_resources;
68
95
  }
69
96
 
70
97
  transformCodebase({
71
- "destDirPath": pathJoin(themeTypeDirPath, "resources", "build"),
72
98
  "srcDirPath": buildOptions.reactAppBuildDirPath,
99
+ "destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir),
73
100
  "transformSourceCode": ({ filePath, sourceCode }) => {
74
101
  //NOTE: Prevent cycles, excludes the folder we generated for debug in public/
102
+ // This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
75
103
  if (
76
104
  isInside({
77
105
  "dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
@@ -82,27 +110,21 @@ export async function generateTheme(params: {
82
110
  }
83
111
 
84
112
  if (/\.css?$/i.test(filePath)) {
85
- const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({
113
+ const { cssGlobalsToDefine: cssGlobalsToDefineForThisFile, fixedCssCode } = replaceImportsInCssCode({
86
114
  "cssCode": sourceCode.toString("utf8")
87
115
  });
88
116
 
89
- register_css_variables: {
90
- if (!isFirstPass) {
91
- break register_css_variables;
92
- }
93
-
94
- allCssGlobalsToDefine = {
95
- ...allCssGlobalsToDefine,
96
- ...cssGlobalsToDefine
97
- };
98
- }
117
+ Object.entries(cssGlobalsToDefineForThisFile).forEach(([key, value]) => {
118
+ cssGlobalsToDefine[key] = value;
119
+ });
99
120
 
100
121
  return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
101
122
  }
102
123
 
103
124
  if (/\.js?$/i.test(filePath)) {
104
- const { fixedJsCode } = replaceImportsFromStaticInJsCode({
105
- "jsCode": sourceCode.toString("utf8")
125
+ const { fixedJsCode } = replaceImportsInJsCode({
126
+ "jsCode": sourceCode.toString("utf8"),
127
+ buildOptions
106
128
  });
107
129
 
108
130
  return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
@@ -113,22 +135,19 @@ export async function generateTheme(params: {
113
135
  });
114
136
  }
115
137
 
116
- const generateFtlFilesCode =
117
- generateFtlFilesCode_glob !== undefined
118
- ? generateFtlFilesCode_glob
119
- : generateFtlFilesCodeFactory({
120
- themeName,
121
- "indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
122
- "cssGlobalsToDefine": allCssGlobalsToDefine,
123
- buildOptions,
124
- keycloakifyVersion,
125
- themeType,
126
- "fieldNames": readFieldNameUsage({
127
- keycloakifySrcDirPath,
128
- themeSrcDirPath,
129
- themeType
130
- })
131
- }).generateFtlFilesCode;
138
+ const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
139
+ themeName,
140
+ "indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
141
+ cssGlobalsToDefine,
142
+ buildOptions,
143
+ keycloakifyVersion,
144
+ themeType,
145
+ "fieldNames": readFieldNameUsage({
146
+ keycloakifySrcDirPath,
147
+ themeSrcDirPath,
148
+ themeType
149
+ })
150
+ });
132
151
 
133
152
  [
134
153
  ...(() => {
@@ -175,11 +194,6 @@ export async function generateTheme(params: {
175
194
  })(),
176
195
  "themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")),
177
196
  themeType,
178
- "usedResources": readStaticResourcesUsage({
179
- keycloakifySrcDirPath,
180
- themeSrcDirPath,
181
- themeType
182
- }),
183
197
  buildOptions
184
198
  });
185
199
 
@@ -190,7 +204,7 @@ export async function generateTheme(params: {
190
204
  `parent=${(() => {
191
205
  switch (themeType) {
192
206
  case "account":
193
- return accountV1;
207
+ return accountV1ThemeName;
194
208
  case "login":
195
209
  return "keycloak";
196
210
  }
@@ -209,7 +223,10 @@ export async function generateTheme(params: {
209
223
  "transformSourceCode": ({ filePath, sourceCode }) => {
210
224
  if (pathBasename(filePath) === "theme.properties") {
211
225
  return {
212
- "modifiedSourceCode": Buffer.from(sourceCode.toString("utf8").replace(`parent=${accountV1}`, "parent=keycloak"), "utf8")
226
+ "modifiedSourceCode": Buffer.from(
227
+ sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
228
+ "utf8"
229
+ )
213
230
  };
214
231
  }
215
232
 
@@ -226,9 +243,82 @@ export async function generateTheme(params: {
226
243
  break email;
227
244
  }
228
245
 
246
+ implementedThemeTypes.email = true;
247
+
229
248
  transformCodebase({
230
249
  "srcDirPath": emailThemeSrcDirPath,
231
250
  "destDirPath": getThemeTypeDirPath({ "themeType": "email" })
232
251
  });
233
252
  }
253
+
254
+ const parsedKeycloakThemeJson: { themes: { name: string; types: string[] }[] } = { "themes": [] };
255
+
256
+ buildOptions.themeNames.forEach(themeName =>
257
+ parsedKeycloakThemeJson.themes.push({
258
+ "name": themeName,
259
+ "types": Object.entries(implementedThemeTypes)
260
+ .filter(([, isImplemented]) => isImplemented)
261
+ .map(([themeType]) => themeType)
262
+ })
263
+ );
264
+
265
+ account_specific_extra_work: {
266
+ if (!implementedThemeTypes.account) {
267
+ break account_specific_extra_work;
268
+ }
269
+
270
+ await bringInAccountV1({ buildOptions });
271
+
272
+ parsedKeycloakThemeJson.themes.push({
273
+ "name": accountV1ThemeName,
274
+ "types": ["account"]
275
+ });
276
+
277
+ add_retrocompat_account_theme: {
278
+ if (!buildOptions.doBuildRetrocompatAccountTheme) {
279
+ break add_retrocompat_account_theme;
280
+ }
281
+
282
+ transformCodebase({
283
+ "srcDirPath": getThemeTypeDirPath({ "themeType": "account" }),
284
+ "destDirPath": getThemeTypeDirPath({ "themeType": "account", "isRetrocompat": true }),
285
+ "transformSourceCode": ({ filePath, sourceCode }) => {
286
+ if (pathBasename(filePath) === "theme.properties") {
287
+ return {
288
+ "modifiedSourceCode": Buffer.from(
289
+ sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
290
+ "utf8"
291
+ )
292
+ };
293
+ }
294
+
295
+ return { "modifiedSourceCode": sourceCode };
296
+ }
297
+ });
298
+
299
+ buildOptions.themeNames.forEach(themeName =>
300
+ parsedKeycloakThemeJson.themes.push({
301
+ "name": `${themeName}${retrocompatPostfix}`,
302
+ "types": ["account"]
303
+ })
304
+ );
305
+ }
306
+ }
307
+
308
+ {
309
+ const keycloakThemeJsonFilePath = pathJoin(
310
+ buildOptions.keycloakifyBuildDirPath,
311
+ "src",
312
+ "main",
313
+ "resources",
314
+ "META-INF",
315
+ "keycloak-themes.json"
316
+ );
317
+
318
+ try {
319
+ fs.mkdirSync(pathDirname(keycloakThemeJsonFilePath));
320
+ } catch {}
321
+
322
+ fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8"));
323
+ }
234
324
  }
@@ -1,15 +1,14 @@
1
1
  import { generateTheme } from "./generateTheme";
2
- import { generateJavaStackFiles } from "./generateJavaStackFiles";
2
+ import { generatePom } from "./generatePom";
3
3
  import { join as pathJoin, relative as pathRelative, basename as pathBasename, dirname as pathDirname, sep as pathSep } from "path";
4
4
  import * as child_process from "child_process";
5
5
  import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
6
6
  import * as fs from "fs";
7
- import { readBuildOptions } from "./BuildOptions";
7
+ import { readBuildOptions } from "./buildOptions";
8
8
  import { getLogger } from "../tools/logger";
9
9
  import { assert } from "tsafe/assert";
10
- import { getThemeSrcDirPath } from "../getSrcDirPath";
10
+ import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
11
11
  import { getProjectRoot } from "../tools/getProjectRoot";
12
- import { objectKeys } from "tsafe/objectKeys";
13
12
 
14
13
  export async function main() {
15
14
  const reactAppRootDirPath = process.cwd();
@@ -42,25 +41,13 @@ export async function main() {
42
41
  });
43
42
  }
44
43
 
45
- const { jarFilePath } = await generateJavaStackFiles({
46
- "implementedThemeTypes": (() => {
47
- const implementedThemeTypes = {
48
- "login": false,
49
- "account": false,
50
- "email": false
51
- };
52
-
53
- for (const themeType of objectKeys(implementedThemeTypes)) {
54
- if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
55
- continue;
56
- }
57
- implementedThemeTypes[themeType] = true;
58
- }
59
-
60
- return implementedThemeTypes;
61
- })(),
62
- buildOptions
63
- });
44
+ {
45
+ const { pomFileCode } = generatePom({ buildOptions });
46
+
47
+ fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
48
+ }
49
+
50
+ const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`);
64
51
 
65
52
  if (buildOptions.doCreateJar) {
66
53
  child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath });
@@ -96,39 +83,7 @@ export async function main() {
96
83
  "",
97
84
  ...(!buildOptions.doCreateJar
98
85
  ? []
99
- : [
100
- `✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(reactAppRootDirPath, jarFilePath)} 🚀`,
101
- `It is to be placed in "/opt/keycloak/providers" in the container running a quay.io/keycloak/keycloak Docker image.`,
102
- ""
103
- ]),
104
- //TODO: Restore when we find a good Helm chart for Keycloak.
105
- //"Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
106
- "",
107
- "value.yaml: ",
108
- " extraInitContainers: |",
109
- " - name: realm-ext-provider",
110
- " image: curlimages/curl",
111
- " imagePullPolicy: IfNotPresent",
112
- " command:",
113
- " - sh",
114
- " args:",
115
- " - -c",
116
- ` - curl -L -f -S -o /extensions/${pathBasename(jarFilePath)} https://AN.URL.FOR/${pathBasename(jarFilePath)}`,
117
- " volumeMounts:",
118
- " - name: extensions",
119
- " mountPath: /extensions",
120
- " ",
121
- " extraVolumeMounts: |",
122
- " - name: extensions",
123
- " mountPath: /opt/keycloak/providers",
124
- " extraEnv: |",
125
- " - name: KEYCLOAK_USER",
126
- " value: admin",
127
- " - name: KEYCLOAK_PASSWORD",
128
- " value: xxxxxxxxx",
129
- " - name: JAVA_OPTS",
130
- " value: -Dkeycloak.profile=preview",
131
- "",
86
+ : [`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(reactAppRootDirPath, jarFilePath)} 🚀`]),
132
87
  "",
133
88
  `To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
134
89
  "",
@@ -136,8 +91,6 @@ export async function main() {
136
91
  reactAppRootDirPath,
137
92
  pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename)
138
93
  )} 👈`,
139
- "",
140
- `Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags`,
141
94
  ``,
142
95
  `Once your container is up and running: `,
143
96
  "- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
@@ -1,6 +1,7 @@
1
1
  import * as crypto from "crypto";
2
- import type { BuildOptions } from "../BuildOptions";
2
+ import type { BuildOptions } from "../buildOptions";
3
3
  import { assert } from "tsafe/assert";
4
+ import { basenameOfTheKeycloakifyResourcesDir } from "../../constants";
4
5
 
5
6
  export type BuildOptionsLike = {
6
7
  urlPathname: string | undefined;
@@ -45,7 +46,7 @@ export function generateCssCodeToDefineGlobals(params: { cssGlobalsToDefine: Rec
45
46
  `--${cssVariableName}:`,
46
47
  cssGlobalsToDefine[cssVariableName].replace(
47
48
  new RegExp(`url\\(${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`, "g"),
48
- "url(${url.resourcesPath}/build/"
49
+ `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
49
50
  )
50
51
  ].join(" ")
51
52
  )
@@ -1,5 +1,6 @@
1
- import type { BuildOptions } from "../BuildOptions";
1
+ import type { BuildOptions } from "../buildOptions";
2
2
  import { assert } from "tsafe/assert";
3
+ import { basenameOfTheKeycloakifyResourcesDir } from "../../constants";
3
4
 
4
5
  export type BuildOptionsLike = {
5
6
  urlPathname: string | undefined;
@@ -16,7 +17,7 @@ export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOp
16
17
  buildOptions.urlPathname === undefined
17
18
  ? /url\(["']?\/([^/][^)"']+)["']?\)/g
18
19
  : new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
19
- (...[, group]) => `url(\${url.resourcesPath}/build/${group})`
20
+ (...[, group]) => `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})`
20
21
  );
21
22
 
22
23
  return { fixedCssCode };
@@ -0,0 +1 @@
1
+ export * from "./replaceImportsInJsCode";
@@ -0,0 +1,66 @@
1
+ import { assert } from "tsafe/assert";
2
+ import type { BuildOptions } from "../../buildOptions";
3
+ import { replaceImportsInJsCode_vite } from "./vite";
4
+ import { replaceImportsInJsCode_webpack } from "./webpack";
5
+ import * as fs from "fs";
6
+
7
+ export type BuildOptionsLike = {
8
+ reactAppBuildDirPath: string;
9
+ assetsDirPath: string;
10
+ urlPathname: string | undefined;
11
+ bundler: "vite" | "webpack";
12
+ };
13
+
14
+ assert<BuildOptions extends BuildOptionsLike ? true : false>();
15
+
16
+ export function replaceImportsInJsCode(params: { jsCode: string; buildOptions: BuildOptionsLike }) {
17
+ const { jsCode, buildOptions } = params;
18
+
19
+ const { fixedJsCode } = (() => {
20
+ switch (buildOptions.bundler) {
21
+ case "vite":
22
+ return replaceImportsInJsCode_vite({
23
+ jsCode,
24
+ buildOptions,
25
+ "basenameOfAssetsFiles": readAssetsDirSync({
26
+ "assetsDirPath": params.buildOptions.assetsDirPath
27
+ })
28
+ });
29
+ case "webpack":
30
+ return replaceImportsInJsCode_webpack({
31
+ jsCode,
32
+ buildOptions
33
+ });
34
+ }
35
+ })();
36
+
37
+ return { fixedJsCode };
38
+ }
39
+
40
+ const { readAssetsDirSync } = (() => {
41
+ let cache:
42
+ | {
43
+ assetsDirPath: string;
44
+ basenameOfAssetsFiles: string[];
45
+ }
46
+ | undefined = undefined;
47
+
48
+ function readAssetsDirSync(params: { assetsDirPath: string }): string[] {
49
+ const { assetsDirPath } = params;
50
+
51
+ if (cache !== undefined && cache.assetsDirPath === assetsDirPath) {
52
+ return cache.basenameOfAssetsFiles;
53
+ }
54
+
55
+ const basenameOfAssetsFiles = fs.readdirSync(assetsDirPath);
56
+
57
+ cache = {
58
+ assetsDirPath,
59
+ basenameOfAssetsFiles
60
+ };
61
+
62
+ return basenameOfAssetsFiles;
63
+ }
64
+
65
+ return { readAssetsDirSync };
66
+ })();
@@ -0,0 +1,85 @@
1
+ import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../constants";
2
+ import { assert } from "tsafe/assert";
3
+ import type { BuildOptions } from "../../buildOptions";
4
+ import * as nodePath from "path";
5
+ import { replaceAll } from "../../../tools/String.prototype.replaceAll";
6
+
7
+ export type BuildOptionsLike = {
8
+ reactAppBuildDirPath: string;
9
+ assetsDirPath: string;
10
+ urlPathname: string | undefined;
11
+ };
12
+
13
+ assert<BuildOptions extends BuildOptionsLike ? true : false>();
14
+
15
+ export function replaceImportsInJsCode_vite(params: {
16
+ jsCode: string;
17
+ buildOptions: BuildOptionsLike;
18
+ basenameOfAssetsFiles: string[];
19
+ systemType?: "posix" | "win32";
20
+ }): {
21
+ fixedJsCode: string;
22
+ } {
23
+ const { jsCode, buildOptions, basenameOfAssetsFiles, systemType = nodePath.sep === "/" ? "posix" : "win32" } = params;
24
+
25
+ const { relative: pathRelative, sep: pathSep } = nodePath[systemType];
26
+
27
+ let fixedJsCode = jsCode;
28
+
29
+ replace_base_javacript_import: {
30
+ if (buildOptions.urlPathname === undefined) {
31
+ break replace_base_javacript_import;
32
+ }
33
+ // Optimization
34
+ if (!jsCode.includes(buildOptions.urlPathname)) {
35
+ break replace_base_javacript_import;
36
+ }
37
+
38
+ // Replace `Hv=function(e){return"/abcde12345/"+e}` by `Hv=function(e){return"/"+e}`
39
+ fixedJsCode = fixedJsCode.replace(
40
+ new RegExp(
41
+ `([\\w\\$][\\w\\d\\$]*)=function\\(([\\w\\$][\\w\\d\\$]*)\\)\\{return"${replaceAll(buildOptions.urlPathname, "/", "\\/")}"\\+\\2\\}`,
42
+ "g"
43
+ ),
44
+ (...[, funcName, paramName]) => `${funcName}=function(${paramName}){return"/"+${paramName}}`
45
+ );
46
+ }
47
+
48
+ replace_javascript_relatives_import_paths: {
49
+ // Example: "assets/ or "foo/bar/"
50
+ const staticDir = (() => {
51
+ let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath);
52
+
53
+ out = replaceAll(out, pathSep, "/") + "/";
54
+
55
+ if (out === "/") {
56
+ throw new Error(`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`);
57
+ }
58
+
59
+ return out;
60
+ })();
61
+
62
+ // Optimization
63
+ if (!jsCode.includes(staticDir)) {
64
+ break replace_javascript_relatives_import_paths;
65
+ }
66
+
67
+ basenameOfAssetsFiles
68
+ .map(basenameOfAssetsFile => `${staticDir}${basenameOfAssetsFile}`)
69
+ .forEach(relativePathOfAssetFile => {
70
+ fixedJsCode = replaceAll(
71
+ fixedJsCode,
72
+ `"${relativePathOfAssetFile}"`,
73
+ `(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/${relativePathOfAssetFile}")`
74
+ );
75
+
76
+ fixedJsCode = replaceAll(
77
+ fixedJsCode,
78
+ `"${buildOptions.urlPathname ?? "/"}${relativePathOfAssetFile}"`,
79
+ `(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/${relativePathOfAssetFile}")`
80
+ );
81
+ });
82
+ }
83
+
84
+ return { fixedJsCode };
85
+ }
@@ -0,0 +1,90 @@
1
+ import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../constants";
2
+ import { assert } from "tsafe/assert";
3
+ import type { BuildOptions } from "../../buildOptions";
4
+ import * as nodePath from "path";
5
+ import { replaceAll } from "../../../tools/String.prototype.replaceAll";
6
+
7
+ export type BuildOptionsLike = {
8
+ reactAppBuildDirPath: string;
9
+ assetsDirPath: string;
10
+ urlPathname: string | undefined;
11
+ };
12
+
13
+ assert<BuildOptions extends BuildOptionsLike ? true : false>();
14
+
15
+ export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOptions: BuildOptionsLike; systemType?: "posix" | "win32" }): {
16
+ fixedJsCode: string;
17
+ } {
18
+ const { jsCode, buildOptions, systemType = nodePath.sep === "/" ? "posix" : "win32" } = params;
19
+
20
+ const { relative: pathRelative, sep: pathSep } = nodePath[systemType];
21
+
22
+ let fixedJsCode = jsCode;
23
+
24
+ if (buildOptions.urlPathname !== undefined) {
25
+ // "__esModule",{value:!0})},n.p="/foo-bar/",function(){if("undefined" -> ... n.p="/" ...
26
+ fixedJsCode = fixedJsCode.replace(
27
+ new RegExp(`,([a-zA-Z]\\.[a-zA-Z])="${replaceAll(buildOptions.urlPathname, "/", "\\/")}",`, "g"),
28
+ (...[, assignTo]) => `,${assignTo}="/",`
29
+ );
30
+ }
31
+
32
+ // d={NODE_ENV:"production",PUBLIC_URL:"/foo-bar",WDS_SOCKET_HOST
33
+ // d={NODE_ENV:"production",PUBLIC_URL:"",WDS_SOCKET_HOST
34
+ // ->
35
+ // ... PUBLIC_URL:window.kcContext.url.resourcesPath+"/build" ...
36
+ fixedJsCode = fixedJsCode.replace(
37
+ new RegExp(
38
+ `NODE_ENV:"production",PUBLIC_URL:"${
39
+ buildOptions.urlPathname !== undefined ? replaceAll(buildOptions.urlPathname.slice(0, -1), "/", "\\/") : ""
40
+ }"`,
41
+ "g"
42
+ ),
43
+ `NODE_ENV:"production",PUBLIC_URL:window.${nameOfTheGlobal}.url.resourcesPath+"/${basenameOfTheKeycloakifyResourcesDir}"`
44
+ );
45
+
46
+ // Example: "static/ or "foo/bar/"
47
+ const staticDir = (() => {
48
+ let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath);
49
+
50
+ out = replaceAll(out, pathSep, "/") + "/";
51
+
52
+ if (out === "/") {
53
+ throw new Error(`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`);
54
+ }
55
+
56
+ return out;
57
+ })();
58
+
59
+ const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
60
+ new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=(function\\(([a-z]+)\\){return|([a-z]+)=>)"${staticDir.replace(/\//g, "\\/")}${language}\\/"`, "g"),
61
+ (...[, n, u, matchedFunction, eForFunction]) => {
62
+ const isArrowFunction = matchedFunction.includes("=>");
63
+ const e = isArrowFunction ? matchedFunction.replace("=>", "").trim() : eForFunction;
64
+
65
+ return `
66
+ ${n}[(function(){
67
+ var pd = Object.getOwnPropertyDescriptor(${n}, "p");
68
+ if( pd === undefined || pd.configurable ){
69
+ Object.defineProperty(${n}, "p", {
70
+ get: function() { return window.${nameOfTheGlobal}.url.resourcesPath; },
71
+ set: function() {}
72
+ });
73
+ }
74
+ return "${u}";
75
+ })()] = ${isArrowFunction ? `${e} =>` : `function(${e}) { return `} "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}${language}/"`
76
+ .replace(/\s+/g, " ")
77
+ .trim();
78
+ }
79
+ ];
80
+
81
+ fixedJsCode = fixedJsCode
82
+ .replace(...getReplaceArgs("js"))
83
+ .replace(...getReplaceArgs("css"))
84
+ .replace(
85
+ new RegExp(`[a-zA-Z]+\\.[a-zA-Z]+\\+"${staticDir.replace(/\//g, "\\/")}`, "g"),
86
+ `window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}`
87
+ );
88
+
89
+ return { fixedJsCode };
90
+ }