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
@@ -142,15 +142,96 @@ export async function command(params: {
142
142
  ].join("\n")
143
143
  );
144
144
 
145
- const { value: tag } = await cliSelect<string>({
146
- values: latestMajorTags
147
- }).catch(() => {
148
- process.exit(-1);
149
- });
145
+ const tag_userSelected = await (async () => {
146
+ let tag: string;
147
+
148
+ let latestMajorTags_copy = [...latestMajorTags];
149
+
150
+ while (true) {
151
+ const { value } = await cliSelect<string>({
152
+ values: latestMajorTags_copy
153
+ }).catch(() => {
154
+ process.exit(-1);
155
+ });
156
+
157
+ tag = value;
158
+
159
+ {
160
+ const doImplementAccountMpa =
161
+ buildContext.implementedThemeTypes.account.isImplemented &&
162
+ buildContext.implementedThemeTypes.account.type === "Multi-Page";
163
+
164
+ if (doImplementAccountMpa && tag.startsWith("22.")) {
165
+ console.log(
166
+ chalk.yellow(
167
+ `You are implementing a Multi-Page Account theme. Keycloak 22 is not supported, select another version`
168
+ )
169
+ );
170
+ latestMajorTags_copy = latestMajorTags_copy.filter(
171
+ tag => !tag.startsWith("22.")
172
+ );
173
+ continue;
174
+ }
175
+ }
150
176
 
151
- console.log(`→ ${tag}`);
177
+ const readMajor = (tag: string) => {
178
+ const major = parseInt(tag.split(".")[0]);
179
+ assert(!isNaN(major));
180
+ return major;
181
+ };
152
182
 
153
- return { dockerImageTag: tag };
183
+ {
184
+ const major = readMajor(tag);
185
+
186
+ const doImplementAdminTheme =
187
+ buildContext.implementedThemeTypes.admin.isImplemented;
188
+
189
+ const getIsSupported = (major: number) => major >= 23;
190
+
191
+ if (doImplementAdminTheme && !getIsSupported(major)) {
192
+ console.log(
193
+ chalk.yellow(
194
+ `You are implementing an Admin theme. Only Keycloak 23 and later are supported, select another version`
195
+ )
196
+ );
197
+ latestMajorTags_copy = latestMajorTags_copy.filter(tag =>
198
+ getIsSupported(readMajor(tag))
199
+ );
200
+ continue;
201
+ }
202
+ }
203
+
204
+ {
205
+ const doImplementAccountSpa =
206
+ buildContext.implementedThemeTypes.account.isImplemented &&
207
+ buildContext.implementedThemeTypes.account.type === "Single-Page";
208
+
209
+ const major = readMajor(tag);
210
+
211
+ const getIsSupported = (major: number) => major >= 19;
212
+
213
+ if (doImplementAccountSpa && !getIsSupported(major)) {
214
+ console.log(
215
+ chalk.yellow(
216
+ `You are implementing a Single-Page Account theme. Only Keycloak 19 and later are supported, select another version`
217
+ )
218
+ );
219
+ latestMajorTags_copy = latestMajorTags_copy.filter(tag =>
220
+ getIsSupported(readMajor(tag))
221
+ );
222
+ continue;
223
+ }
224
+ }
225
+
226
+ break;
227
+ }
228
+
229
+ return tag;
230
+ })();
231
+
232
+ console.log(`→ ${tag_userSelected}`);
233
+
234
+ return { dockerImageTag: tag_userSelected };
154
235
  })();
155
236
 
156
237
  const keycloakMajorVersionNumber = (() => {
@@ -623,42 +704,56 @@ export async function command(params: {
623
704
  }
624
705
  )
625
706
  .on("all", async (...[, filePath]) => {
626
- ignore_account_spa: {
627
- const doImplementAccountSpa =
628
- buildContext.implementedThemeTypes.account.isImplemented &&
629
- buildContext.implementedThemeTypes.account.type === "Single-Page";
630
-
631
- if (!doImplementAccountSpa) {
632
- break ignore_account_spa;
707
+ ignore_path_covered_by_hmr: {
708
+ if (filePath.endsWith(".properties")) {
709
+ break ignore_path_covered_by_hmr;
633
710
  }
634
711
 
635
- if (
636
- !isInside({
637
- dirPath: pathJoin(buildContext.themeSrcDirPath, "account"),
638
- filePath
639
- })
640
- ) {
641
- break ignore_account_spa;
712
+ if (!doStartDevServer) {
713
+ break ignore_path_covered_by_hmr;
642
714
  }
643
715
 
644
- return;
645
- }
646
-
647
- ignore_admin: {
648
- if (!buildContext.implementedThemeTypes.admin.isImplemented) {
649
- break ignore_admin;
716
+ ignore_account_spa: {
717
+ const doImplementAccountSpa =
718
+ buildContext.implementedThemeTypes.account.isImplemented &&
719
+ buildContext.implementedThemeTypes.account.type ===
720
+ "Single-Page";
721
+
722
+ if (!doImplementAccountSpa) {
723
+ break ignore_account_spa;
724
+ }
725
+
726
+ if (
727
+ !isInside({
728
+ dirPath: pathJoin(
729
+ buildContext.themeSrcDirPath,
730
+ "account"
731
+ ),
732
+ filePath
733
+ })
734
+ ) {
735
+ break ignore_account_spa;
736
+ }
737
+
738
+ return;
650
739
  }
651
740
 
652
- if (
653
- !isInside({
654
- dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
655
- filePath
656
- })
657
- ) {
658
- break ignore_admin;
741
+ ignore_admin: {
742
+ if (!buildContext.implementedThemeTypes.admin.isImplemented) {
743
+ break ignore_admin;
744
+ }
745
+
746
+ if (
747
+ !isInside({
748
+ dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
749
+ filePath
750
+ })
751
+ ) {
752
+ break ignore_admin;
753
+ }
754
+
755
+ return;
659
756
  }
660
-
661
- return;
662
757
  }
663
758
 
664
759
  console.log(`Detected changes in ${filePath}`);
@@ -0,0 +1,90 @@
1
+ const keyIsTrapped = "isTrapped_zSskDe9d";
2
+
3
+ export class AccessError extends Error {
4
+ constructor(message: string) {
5
+ super(message);
6
+ Object.setPrototypeOf(this, new.target.prototype);
7
+ }
8
+ }
9
+
10
+ export function createObjectThatThrowsIfAccessed<T extends object>(params?: {
11
+ debugMessage?: string;
12
+ isPropertyWhitelisted?: (prop: string | number | symbol) => boolean;
13
+ }): T {
14
+ const { debugMessage = "", isPropertyWhitelisted = () => false } = params ?? {};
15
+
16
+ const get: NonNullable<ProxyHandler<T>["get"]> = (...args) => {
17
+ const [, prop] = args;
18
+
19
+ if (isPropertyWhitelisted(prop)) {
20
+ return Reflect.get(...args);
21
+ }
22
+
23
+ if (prop === keyIsTrapped) {
24
+ return true;
25
+ }
26
+
27
+ throw new AccessError(`Cannot access ${String(prop)} yet ${debugMessage}`);
28
+ };
29
+
30
+ const trappedObject = new Proxy<T>({} as any, {
31
+ get,
32
+ set: get
33
+ });
34
+
35
+ return trappedObject;
36
+ }
37
+
38
+ export function createObjectThatThrowsIfAccessedFactory(params: {
39
+ isPropertyWhitelisted?: (prop: string | number | symbol) => boolean;
40
+ }) {
41
+ const { isPropertyWhitelisted } = params;
42
+
43
+ return {
44
+ createObjectThatThrowsIfAccessed: <T extends object>(params?: {
45
+ debugMessage?: string;
46
+ }) => {
47
+ const { debugMessage } = params ?? {};
48
+
49
+ return createObjectThatThrowsIfAccessed<T>({
50
+ debugMessage,
51
+ isPropertyWhitelisted
52
+ });
53
+ }
54
+ };
55
+ }
56
+
57
+ export function isObjectThatThrowIfAccessed(obj: object) {
58
+ return (obj as any)[keyIsTrapped] === true;
59
+ }
60
+
61
+ export const THROW_IF_ACCESSED = {
62
+ __brand: "THROW_IF_ACCESSED"
63
+ };
64
+
65
+ export function createObjectWithSomePropertiesThatThrowIfAccessed<
66
+ T extends Record<string, unknown>
67
+ >(obj: { [K in keyof T]: T[K] | typeof THROW_IF_ACCESSED }, debugMessage?: string): T {
68
+ return Object.defineProperties(
69
+ obj,
70
+ Object.fromEntries(
71
+ Object.entries(obj)
72
+ .filter(([, value]) => value === THROW_IF_ACCESSED)
73
+ .map(([key]) => {
74
+ const getAndSet = () => {
75
+ throw new AccessError(
76
+ `Cannot access ${key} yet ${debugMessage ?? ""}`
77
+ );
78
+ };
79
+
80
+ const pd = {
81
+ get: getAndSet,
82
+ set: getAndSet,
83
+ enumerable: true
84
+ };
85
+
86
+ return [key, pd];
87
+ })
88
+ )
89
+ ) as any;
90
+ }
@@ -9,8 +9,9 @@ import { objectKeys } from "tsafe/objectKeys";
9
9
  import { getAbsoluteAndInOsFormatPath } from "./getAbsoluteAndInOsFormatPath";
10
10
  import { exclude } from "tsafe/exclude";
11
11
  import { rmSync } from "./fs.rmSync";
12
+ import { Deferred } from "evt/tools/Deferred";
12
13
 
13
- export function npmInstall(params: { packageJsonDirPath: string }) {
14
+ export async function npmInstall(params: { packageJsonDirPath: string }) {
14
15
  const { packageJsonDirPath } = params;
15
16
 
16
17
  const packageManagerBinName = (() => {
@@ -68,7 +69,7 @@ export function npmInstall(params: { packageJsonDirPath: string }) {
68
69
 
69
70
  console.log(chalk.green("Installing in a way that won't break the links..."));
70
71
 
71
- installWithoutBreakingLinks({
72
+ await installWithoutBreakingLinks({
72
73
  packageJsonDirPath,
73
74
  garronejLinkInfos
74
75
  });
@@ -77,9 +78,9 @@ export function npmInstall(params: { packageJsonDirPath: string }) {
77
78
  }
78
79
 
79
80
  try {
80
- child_process.execSync(`${packageManagerBinName} install`, {
81
- cwd: packageJsonDirPath,
82
- stdio: "inherit"
81
+ await runPackageManagerInstall({
82
+ packageManagerBinName,
83
+ cwd: packageJsonDirPath
83
84
  });
84
85
  } catch {
85
86
  console.log(
@@ -90,6 +91,42 @@ export function npmInstall(params: { packageJsonDirPath: string }) {
90
91
  }
91
92
  }
92
93
 
94
+ async function runPackageManagerInstall(params: {
95
+ packageManagerBinName: string;
96
+ cwd: string;
97
+ }) {
98
+ const { packageManagerBinName, cwd } = params;
99
+
100
+ const dCompleted = new Deferred<void>();
101
+
102
+ const child = child_process.spawn(packageManagerBinName, ["install"], {
103
+ cwd,
104
+ env: process.env,
105
+ shell: true
106
+ });
107
+
108
+ child.stdout.on("data", data => process.stdout.write(data));
109
+
110
+ child.stderr.on("data", data => {
111
+ if (data.toString("utf8").includes("has unmet peer dependency")) {
112
+ return;
113
+ }
114
+
115
+ process.stderr.write(data);
116
+ });
117
+
118
+ child.on("exit", code => {
119
+ if (code !== 0) {
120
+ dCompleted.reject(new Error(`Failed with code ${code}`));
121
+ return;
122
+ }
123
+
124
+ dCompleted.resolve();
125
+ });
126
+
127
+ await dCompleted.pr;
128
+ }
129
+
93
130
  function getGarronejLinkInfos(params: {
94
131
  packageJsonDirPath: string;
95
132
  }): { linkedModuleNames: string[]; yarnHomeDirPath: string } | undefined {
@@ -180,7 +217,7 @@ function getGarronejLinkInfos(params: {
180
217
  return { linkedModuleNames, yarnHomeDirPath };
181
218
  }
182
219
 
183
- function installWithoutBreakingLinks(params: {
220
+ async function installWithoutBreakingLinks(params: {
184
221
  packageJsonDirPath: string;
185
222
  garronejLinkInfos: Exclude<ReturnType<typeof getGarronejLinkInfos>, undefined>;
186
223
  }) {
@@ -261,9 +298,9 @@ function installWithoutBreakingLinks(params: {
261
298
  pathJoin(tmpProjectDirPath, YARN_LOCK)
262
299
  );
263
300
 
264
- child_process.execSync(`yarn install`, {
265
- cwd: tmpProjectDirPath,
266
- stdio: "inherit"
301
+ await runPackageManagerInstall({
302
+ packageManagerBinName: "yarn",
303
+ cwd: tmpProjectDirPath
267
304
  });
268
305
 
269
306
  // NOTE: Moving the modules from the tmp project to the actual project