keycloakify 11.3.19 → 11.3.21
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.
- package/bin/124.index.js +676 -0
- package/bin/356.index.js +700 -0
- package/bin/40.index.js +41 -21
- package/bin/{903.index.js → 450.index.js} +2 -680
- package/bin/453.index.js +143 -85
- package/bin/573.index.js +51 -24
- package/bin/{599.index.js → 735.index.js} +438 -121
- package/bin/786.index.js +129 -78
- package/bin/805.index.js +674 -0
- package/bin/854.index.js +68 -0
- package/bin/{780.index.js → 921.index.js} +168 -109
- package/bin/932.index.js +41 -21
- package/bin/97.index.js +125 -75
- package/bin/eject-file.d.ts +7 -0
- package/bin/keycloakify/generateResources/generateMessageProperties.d.ts +1 -1
- package/bin/keycloakify/generateResources/readFieldNameUsage.d.ts +1 -1
- package/bin/main.js +70 -7
- package/bin/postinstall/getUiModuleFileSourceCodeReadyToBeCopied.d.ts +12 -0
- package/bin/postinstall/index.d.ts +1 -0
- package/bin/postinstall/installUiModulesPeerDependencies.d.ts +11 -0
- package/bin/postinstall/managedGitignoreFile.d.ts +14 -0
- package/bin/postinstall/postinstall.d.ts +4 -0
- package/bin/postinstall/uiModuleMeta.d.ts +21 -0
- package/bin/shared/buildContext.d.ts +3 -0
- package/bin/shared/constants.d.ts +2 -1
- package/bin/shared/constants.js +2 -1
- package/bin/shared/constants.js.map +1 -1
- package/bin/shared/customHandler.d.ts +1 -1
- package/bin/shared/customHandler.js.map +1 -1
- package/bin/shared/exitIfUncommittedChanges.d.ts +3 -0
- package/bin/tools/crawlAsync.d.ts +6 -0
- package/bin/tools/getInstalledModuleDirPath.d.ts +5 -0
- package/bin/tools/listInstalledModules.d.ts +12 -0
- package/bin/tools/nodeModulesBinDirPath.d.ts +1 -0
- package/bin/tools/runPrettier.d.ts +17 -0
- package/package.json +34 -6
- package/src/bin/add-story.ts +28 -10
- package/src/bin/eject-file.ts +68 -0
- package/src/bin/eject-page.ts +51 -31
- package/src/bin/initialize-account-theme/initialize-account-theme.ts +4 -32
- package/src/bin/initialize-account-theme/initializeAccountTheme_singlePage.ts +2 -16
- package/src/bin/keycloakify/generateResources/generateMessageProperties.ts +1 -1
- package/src/bin/keycloakify/generateResources/generateResources.ts +58 -26
- package/src/bin/keycloakify/generateResources/readFieldNameUsage.ts +1 -1
- package/src/bin/main.ts +50 -0
- package/src/bin/postinstall/getUiModuleFileSourceCodeReadyToBeCopied.ts +73 -0
- package/src/bin/postinstall/index.ts +1 -0
- package/src/bin/postinstall/installUiModulesPeerDependencies.ts +158 -0
- package/src/bin/postinstall/managedGitignoreFile.ts +136 -0
- package/src/bin/postinstall/postinstall.ts +79 -0
- package/src/bin/postinstall/uiModuleMeta.ts +309 -0
- package/src/bin/shared/buildContext.ts +11 -5
- package/src/bin/shared/constants.ts +3 -1
- package/src/bin/shared/customHandler.ts +1 -0
- package/src/bin/shared/customHandler_delegate.ts +2 -27
- package/src/bin/shared/exitIfUncommittedChanges.ts +36 -0
- package/src/bin/tools/crawlAsync.ts +51 -0
- package/src/bin/tools/getInstalledModuleDirPath.ts +51 -0
- package/src/bin/tools/listInstalledModules.ts +131 -0
- package/src/bin/tools/nodeModulesBinDirPath.ts +38 -0
- package/src/bin/tools/npmInstall.ts +411 -15
- package/src/bin/tools/readThisNpmPackageVersion.ts +8 -0
- package/src/bin/tools/runPrettier.ts +106 -0
- package/src/bin/update-kc-gen.ts +28 -17
- package/vite-plugin/index.js +9237 -9163
- package/bin/tools/runFormat.d.ts +0 -3
- package/src/bin/tools/runFormat.ts +0 -76
@@ -19,7 +19,8 @@ import {
|
|
19
19
|
type ThemeType,
|
20
20
|
LOGIN_THEME_PAGE_IDS,
|
21
21
|
ACCOUNT_THEME_PAGE_IDS,
|
22
|
-
WELL_KNOWN_DIRECTORY_BASE_NAME
|
22
|
+
WELL_KNOWN_DIRECTORY_BASE_NAME,
|
23
|
+
THEME_TYPES
|
23
24
|
} from "../../shared/constants";
|
24
25
|
import { assert, type Equals } from "tsafe/assert";
|
25
26
|
import { readFieldNameUsage } from "./readFieldNameUsage";
|
@@ -78,15 +79,29 @@ export async function generateResources(params: {
|
|
78
79
|
Record<ThemeType, (params: { messageDirPath: string; themeName: string }) => void>
|
79
80
|
> = {};
|
80
81
|
|
81
|
-
for (const themeType of
|
82
|
+
for (const themeType of THEME_TYPES) {
|
82
83
|
if (!buildContext.implementedThemeTypes[themeType].isImplemented) {
|
83
84
|
continue;
|
84
85
|
}
|
85
86
|
|
86
|
-
const
|
87
|
-
themeType === "account"
|
88
|
-
|
89
|
-
buildContext.implementedThemeTypes.account.
|
87
|
+
const getAccountThemeType = () => {
|
88
|
+
assert(themeType === "account");
|
89
|
+
|
90
|
+
assert(buildContext.implementedThemeTypes.account.isImplemented);
|
91
|
+
|
92
|
+
return buildContext.implementedThemeTypes.account.type;
|
93
|
+
};
|
94
|
+
|
95
|
+
const isSpa = (() => {
|
96
|
+
switch (themeType) {
|
97
|
+
case "login":
|
98
|
+
return false;
|
99
|
+
case "account":
|
100
|
+
return getAccountThemeType() === "Single-Page";
|
101
|
+
case "admin":
|
102
|
+
return true;
|
103
|
+
}
|
104
|
+
})();
|
90
105
|
|
91
106
|
const themeTypeDirPath = getThemeTypeDirPath({ themeName, themeType });
|
92
107
|
|
@@ -101,7 +116,7 @@ export async function generateResources(params: {
|
|
101
116
|
rmSync(destDirPath, { recursive: true, force: true });
|
102
117
|
|
103
118
|
if (
|
104
|
-
themeType
|
119
|
+
themeType !== "login" &&
|
105
120
|
buildContext.implementedThemeTypes.login.isImplemented
|
106
121
|
) {
|
107
122
|
// NOTE: We prevent doing it twice, it has been done for the login theme.
|
@@ -182,10 +197,13 @@ export async function generateResources(params: {
|
|
182
197
|
buildContext,
|
183
198
|
keycloakifyVersion: readThisNpmPackageVersion(),
|
184
199
|
themeType,
|
185
|
-
fieldNames:
|
186
|
-
|
187
|
-
themeType
|
188
|
-
|
200
|
+
fieldNames: isSpa
|
201
|
+
? []
|
202
|
+
: (assert(themeType !== "admin"),
|
203
|
+
readFieldNameUsage({
|
204
|
+
themeSrcDirPath: buildContext.themeSrcDirPath,
|
205
|
+
themeType
|
206
|
+
}))
|
189
207
|
});
|
190
208
|
|
191
209
|
[
|
@@ -194,10 +212,14 @@ export async function generateResources(params: {
|
|
194
212
|
case "login":
|
195
213
|
return LOGIN_THEME_PAGE_IDS;
|
196
214
|
case "account":
|
197
|
-
return
|
215
|
+
return getAccountThemeType() === "Single-Page"
|
216
|
+
? ["index.ftl"]
|
217
|
+
: ACCOUNT_THEME_PAGE_IDS;
|
218
|
+
case "admin":
|
219
|
+
return ["index.ftl"];
|
198
220
|
}
|
199
221
|
})(),
|
200
|
-
...(
|
222
|
+
...(isSpa
|
201
223
|
? []
|
202
224
|
: readExtraPagesNames({
|
203
225
|
themeType,
|
@@ -215,10 +237,12 @@ export async function generateResources(params: {
|
|
215
237
|
let languageTags: string[] | undefined = undefined;
|
216
238
|
|
217
239
|
i18n_messages_generation: {
|
218
|
-
if (
|
240
|
+
if (isSpa) {
|
219
241
|
break i18n_messages_generation;
|
220
242
|
}
|
221
243
|
|
244
|
+
assert(themeType !== "admin");
|
245
|
+
|
222
246
|
const wrap = generateMessageProperties({
|
223
247
|
buildContext,
|
224
248
|
themeType
|
@@ -231,16 +255,15 @@ export async function generateResources(params: {
|
|
231
255
|
writeMessagePropertiesFiles;
|
232
256
|
}
|
233
257
|
|
234
|
-
|
235
|
-
if (!
|
236
|
-
break
|
237
|
-
}
|
238
|
-
if (buildContext.implementedThemeTypes.account.type !== "Single-Page") {
|
239
|
-
break bring_in_account_v3_i18n_messages;
|
258
|
+
bring_in_spas_messages: {
|
259
|
+
if (!isSpa) {
|
260
|
+
break bring_in_spas_messages;
|
240
261
|
}
|
241
262
|
|
263
|
+
assert(themeType !== "login");
|
264
|
+
|
242
265
|
const accountUiDirPath = child_process
|
243
|
-
.execSync(
|
266
|
+
.execSync(`npm list @keycloakify/keycloak-${themeType}-ui --parseable`, {
|
244
267
|
cwd: pathDirname(buildContext.packageJsonFilePath)
|
245
268
|
})
|
246
269
|
.toString("utf8")
|
@@ -255,7 +278,7 @@ export async function generateResources(params: {
|
|
255
278
|
}
|
256
279
|
|
257
280
|
const messagesDirPath_dest = pathJoin(
|
258
|
-
getThemeTypeDirPath({ themeName, themeType
|
281
|
+
getThemeTypeDirPath({ themeName, themeType }),
|
259
282
|
"messages"
|
260
283
|
);
|
261
284
|
|
@@ -267,7 +290,7 @@ export async function generateResources(params: {
|
|
267
290
|
apply_theme_changes: {
|
268
291
|
const messagesDirPath_theme = pathJoin(
|
269
292
|
buildContext.themeSrcDirPath,
|
270
|
-
|
293
|
+
themeType,
|
271
294
|
"messages"
|
272
295
|
);
|
273
296
|
|
@@ -316,7 +339,7 @@ export async function generateResources(params: {
|
|
316
339
|
}
|
317
340
|
|
318
341
|
keycloak_static_resources: {
|
319
|
-
if (
|
342
|
+
if (isSpa) {
|
320
343
|
break keycloak_static_resources;
|
321
344
|
}
|
322
345
|
|
@@ -339,13 +362,22 @@ export async function generateResources(params: {
|
|
339
362
|
`parent=${(() => {
|
340
363
|
switch (themeType) {
|
341
364
|
case "account":
|
342
|
-
|
365
|
+
switch (getAccountThemeType()) {
|
366
|
+
case "Multi-Page":
|
367
|
+
return "account-v1";
|
368
|
+
case "Single-Page":
|
369
|
+
return "base";
|
370
|
+
}
|
343
371
|
case "login":
|
344
372
|
return "keycloak";
|
373
|
+
case "admin":
|
374
|
+
return "base";
|
345
375
|
}
|
346
376
|
assert<Equals<typeof themeType, never>>(false);
|
347
377
|
})()}`,
|
348
|
-
...(
|
378
|
+
...(themeType === "account" && getAccountThemeType() === "Single-Page"
|
379
|
+
? ["deprecatedMode=false"]
|
380
|
+
: []),
|
349
381
|
...(buildContext.extraThemeProperties ?? []),
|
350
382
|
...buildContext.environmentVariables.map(
|
351
383
|
({ name, default: defaultValue }) =>
|
@@ -7,7 +7,7 @@ import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPa
|
|
7
7
|
/** Assumes the theme type exists */
|
8
8
|
export function readFieldNameUsage(params: {
|
9
9
|
themeSrcDirPath: string;
|
10
|
-
themeType: ThemeType
|
10
|
+
themeType: Exclude<ThemeType, "admin">;
|
11
11
|
}): string[] {
|
12
12
|
const { themeSrcDirPath, themeType } = params;
|
13
13
|
|
package/src/bin/main.ts
CHANGED
@@ -227,6 +227,56 @@ program
|
|
227
227
|
}
|
228
228
|
});
|
229
229
|
|
230
|
+
program
|
231
|
+
.command({
|
232
|
+
name: "postinstall",
|
233
|
+
description: "Initialize all the Keycloakify UI modules installed in the project."
|
234
|
+
})
|
235
|
+
.task({
|
236
|
+
skip,
|
237
|
+
handler: async ({ projectDirPath }) => {
|
238
|
+
const { command } = await import("./postinstall");
|
239
|
+
|
240
|
+
await command({ buildContext: getBuildContext({ projectDirPath }) });
|
241
|
+
}
|
242
|
+
});
|
243
|
+
|
244
|
+
program
|
245
|
+
.command<{
|
246
|
+
file: string;
|
247
|
+
}>({
|
248
|
+
name: "eject-file",
|
249
|
+
description: [
|
250
|
+
"WARNING: Not usable yet, will be used for future features",
|
251
|
+
"Take ownership over a given file"
|
252
|
+
].join(" ")
|
253
|
+
})
|
254
|
+
.option({
|
255
|
+
key: "file",
|
256
|
+
name: (() => {
|
257
|
+
const name = "file";
|
258
|
+
|
259
|
+
optionsKeys.push(name);
|
260
|
+
|
261
|
+
return name;
|
262
|
+
})(),
|
263
|
+
description: [
|
264
|
+
"Relative path of the file relative to the directory of your keycloak theme source",
|
265
|
+
"Example `--file src/login/page/Login.tsx`"
|
266
|
+
].join(" ")
|
267
|
+
})
|
268
|
+
.task({
|
269
|
+
skip,
|
270
|
+
handler: async ({ projectDirPath, file }) => {
|
271
|
+
const { command } = await import("./eject-file");
|
272
|
+
|
273
|
+
await command({
|
274
|
+
buildContext: getBuildContext({ projectDirPath }),
|
275
|
+
cliCommandOptions: { file }
|
276
|
+
});
|
277
|
+
}
|
278
|
+
});
|
279
|
+
|
230
280
|
// Fallback to build command if no command is provided
|
231
281
|
{
|
232
282
|
const [, , ...rest] = process.argv;
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import { getIsPrettierAvailable, runPrettier } from "../tools/runPrettier";
|
2
|
+
import * as fsPr from "fs/promises";
|
3
|
+
import { join as pathJoin, sep as pathSep } from "path";
|
4
|
+
import { assert } from "tsafe/assert";
|
5
|
+
import type { BuildContext } from "../shared/buildContext";
|
6
|
+
import { KEYCLOAK_THEME } from "../shared/constants";
|
7
|
+
|
8
|
+
export type BuildContextLike = {
|
9
|
+
themeSrcDirPath: string;
|
10
|
+
};
|
11
|
+
|
12
|
+
assert<BuildContext extends BuildContextLike ? true : false>();
|
13
|
+
|
14
|
+
export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
|
15
|
+
buildContext: BuildContextLike;
|
16
|
+
fileRelativePath: string;
|
17
|
+
isForEjection: boolean;
|
18
|
+
uiModuleDirPath: string;
|
19
|
+
uiModuleName: string;
|
20
|
+
uiModuleVersion: string;
|
21
|
+
}): Promise<Buffer> {
|
22
|
+
const {
|
23
|
+
buildContext,
|
24
|
+
uiModuleDirPath,
|
25
|
+
fileRelativePath,
|
26
|
+
isForEjection,
|
27
|
+
uiModuleName,
|
28
|
+
uiModuleVersion
|
29
|
+
} = params;
|
30
|
+
|
31
|
+
let sourceCode = (
|
32
|
+
await fsPr.readFile(pathJoin(uiModuleDirPath, KEYCLOAK_THEME, fileRelativePath))
|
33
|
+
).toString("utf8");
|
34
|
+
|
35
|
+
const comment = (() => {
|
36
|
+
if (isForEjection) {
|
37
|
+
return [
|
38
|
+
`/*`,
|
39
|
+
` * This file was ejected from ${uiModuleName} version ${uiModuleVersion}.`,
|
40
|
+
` */`
|
41
|
+
].join("\n");
|
42
|
+
} else {
|
43
|
+
return [
|
44
|
+
`/*`,
|
45
|
+
` *`,
|
46
|
+
` * WARNING: Before modifying this file run the following command:`,
|
47
|
+
` * `,
|
48
|
+
` * $ npx keycloakify eject-file --file ${fileRelativePath.split(pathSep).join("/")}`,
|
49
|
+
` * `,
|
50
|
+
` * This file comes from ${uiModuleName} version ${uiModuleVersion}.`,
|
51
|
+
` *`,
|
52
|
+
` */`
|
53
|
+
].join("\n");
|
54
|
+
}
|
55
|
+
})();
|
56
|
+
|
57
|
+
sourceCode = [comment, ``, sourceCode].join("\n");
|
58
|
+
|
59
|
+
const destFilePath = pathJoin(buildContext.themeSrcDirPath, fileRelativePath);
|
60
|
+
|
61
|
+
format: {
|
62
|
+
if (!(await getIsPrettierAvailable())) {
|
63
|
+
break format;
|
64
|
+
}
|
65
|
+
|
66
|
+
sourceCode = await runPrettier({
|
67
|
+
filePath: destFilePath,
|
68
|
+
sourceCode
|
69
|
+
});
|
70
|
+
}
|
71
|
+
|
72
|
+
return Buffer.from(sourceCode, "utf8");
|
73
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./postinstall";
|
@@ -0,0 +1,158 @@
|
|
1
|
+
import { assert, type Equals } from "tsafe/assert";
|
2
|
+
import { is } from "tsafe/is";
|
3
|
+
import type { BuildContext } from "../shared/buildContext";
|
4
|
+
import type { UiModuleMeta } from "./uiModuleMeta";
|
5
|
+
import { z } from "zod";
|
6
|
+
import { id } from "tsafe/id";
|
7
|
+
import * as fsPr from "fs/promises";
|
8
|
+
import { SemVer } from "../tools/SemVer";
|
9
|
+
import { same } from "evt/tools/inDepth/same";
|
10
|
+
import { runPrettier, getIsPrettierAvailable } from "../tools/runPrettier";
|
11
|
+
import { npmInstall } from "../tools/npmInstall";
|
12
|
+
import { dirname as pathDirname } from "path";
|
13
|
+
|
14
|
+
export type BuildContextLike = {
|
15
|
+
packageJsonFilePath: string;
|
16
|
+
};
|
17
|
+
|
18
|
+
assert<BuildContext extends BuildContextLike ? true : false>();
|
19
|
+
|
20
|
+
export type UiModuleMetaLike = {
|
21
|
+
moduleName: string;
|
22
|
+
peerDependencies: Record<string, string>;
|
23
|
+
};
|
24
|
+
|
25
|
+
assert<UiModuleMeta extends UiModuleMetaLike ? true : false>();
|
26
|
+
|
27
|
+
export async function installUiModulesPeerDependencies(params: {
|
28
|
+
buildContext: BuildContextLike;
|
29
|
+
uiModuleMetas: UiModuleMetaLike[];
|
30
|
+
}): Promise<void | never> {
|
31
|
+
const { buildContext, uiModuleMetas } = params;
|
32
|
+
|
33
|
+
const { uiModulesPerDependencies } = (() => {
|
34
|
+
const uiModulesPerDependencies: Record<string, string> = {};
|
35
|
+
|
36
|
+
for (const { peerDependencies } of uiModuleMetas) {
|
37
|
+
for (const [peerDependencyName, versionRange_candidate] of Object.entries(
|
38
|
+
peerDependencies
|
39
|
+
)) {
|
40
|
+
const versionRange = (() => {
|
41
|
+
const versionRange_current =
|
42
|
+
uiModulesPerDependencies[peerDependencyName];
|
43
|
+
|
44
|
+
if (versionRange_current === undefined) {
|
45
|
+
return versionRange_candidate;
|
46
|
+
}
|
47
|
+
|
48
|
+
if (versionRange_current === "*") {
|
49
|
+
return versionRange_candidate;
|
50
|
+
}
|
51
|
+
|
52
|
+
if (versionRange_candidate === "*") {
|
53
|
+
return versionRange_current;
|
54
|
+
}
|
55
|
+
|
56
|
+
const { versionRange } = [
|
57
|
+
versionRange_current,
|
58
|
+
versionRange_candidate
|
59
|
+
]
|
60
|
+
.map(versionRange => ({
|
61
|
+
versionRange,
|
62
|
+
semVer: SemVer.parse(
|
63
|
+
(() => {
|
64
|
+
if (
|
65
|
+
versionRange.startsWith("^") ||
|
66
|
+
versionRange.startsWith("~")
|
67
|
+
) {
|
68
|
+
return versionRange.slice(1);
|
69
|
+
}
|
70
|
+
|
71
|
+
return versionRange;
|
72
|
+
})()
|
73
|
+
)
|
74
|
+
}))
|
75
|
+
.sort((a, b) => SemVer.compare(b.semVer, a.semVer))[0];
|
76
|
+
|
77
|
+
return versionRange;
|
78
|
+
})();
|
79
|
+
|
80
|
+
uiModulesPerDependencies[peerDependencyName] = versionRange;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
return { uiModulesPerDependencies };
|
85
|
+
})();
|
86
|
+
|
87
|
+
const parsedPackageJson = await (async () => {
|
88
|
+
type ParsedPackageJson = {
|
89
|
+
dependencies?: Record<string, string>;
|
90
|
+
devDependencies?: Record<string, string>;
|
91
|
+
};
|
92
|
+
|
93
|
+
const zParsedPackageJson = (() => {
|
94
|
+
type TargetType = ParsedPackageJson;
|
95
|
+
|
96
|
+
const zParsedPackageJson = z.object({
|
97
|
+
dependencies: z.record(z.string()).optional(),
|
98
|
+
devDependencies: z.record(z.string()).optional()
|
99
|
+
});
|
100
|
+
|
101
|
+
type InferredType = z.infer<typeof zParsedPackageJson>;
|
102
|
+
|
103
|
+
assert<Equals<InferredType, TargetType>>();
|
104
|
+
|
105
|
+
return id<z.ZodType<TargetType>>(zParsedPackageJson);
|
106
|
+
})();
|
107
|
+
|
108
|
+
const parsedPackageJson = JSON.parse(
|
109
|
+
(await fsPr.readFile(buildContext.packageJsonFilePath)).toString("utf8")
|
110
|
+
);
|
111
|
+
|
112
|
+
zParsedPackageJson.parse(parsedPackageJson);
|
113
|
+
|
114
|
+
assert(is<ParsedPackageJson>(parsedPackageJson));
|
115
|
+
|
116
|
+
return parsedPackageJson;
|
117
|
+
})();
|
118
|
+
|
119
|
+
const parsedPackageJson_before = JSON.parse(JSON.stringify(parsedPackageJson));
|
120
|
+
|
121
|
+
for (const [moduleName, versionRange] of Object.entries(uiModulesPerDependencies)) {
|
122
|
+
if (moduleName.startsWith("@types/")) {
|
123
|
+
(parsedPackageJson.devDependencies ??= {})[moduleName] = versionRange;
|
124
|
+
continue;
|
125
|
+
}
|
126
|
+
|
127
|
+
if (parsedPackageJson.devDependencies !== undefined) {
|
128
|
+
delete parsedPackageJson.devDependencies[moduleName];
|
129
|
+
}
|
130
|
+
|
131
|
+
(parsedPackageJson.dependencies ??= {})[moduleName] = versionRange;
|
132
|
+
}
|
133
|
+
|
134
|
+
if (same(parsedPackageJson, parsedPackageJson_before)) {
|
135
|
+
return;
|
136
|
+
}
|
137
|
+
|
138
|
+
let packageJsonContentStr = JSON.stringify(parsedPackageJson, null, 2);
|
139
|
+
|
140
|
+
format: {
|
141
|
+
if (!(await getIsPrettierAvailable())) {
|
142
|
+
break format;
|
143
|
+
}
|
144
|
+
|
145
|
+
packageJsonContentStr = await runPrettier({
|
146
|
+
sourceCode: packageJsonContentStr,
|
147
|
+
filePath: buildContext.packageJsonFilePath
|
148
|
+
});
|
149
|
+
}
|
150
|
+
|
151
|
+
await fsPr.writeFile(buildContext.packageJsonFilePath, packageJsonContentStr);
|
152
|
+
|
153
|
+
npmInstall({
|
154
|
+
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
155
|
+
});
|
156
|
+
|
157
|
+
process.exit(0);
|
158
|
+
}
|
@@ -0,0 +1,136 @@
|
|
1
|
+
import * as fsPr from "fs/promises";
|
2
|
+
import {
|
3
|
+
join as pathJoin,
|
4
|
+
sep as pathSep,
|
5
|
+
dirname as pathDirname,
|
6
|
+
relative as pathRelative
|
7
|
+
} from "path";
|
8
|
+
import { assert } from "tsafe/assert";
|
9
|
+
import type { BuildContext } from "../shared/buildContext";
|
10
|
+
import type { UiModuleMeta } from "./uiModuleMeta";
|
11
|
+
import { existsAsync } from "../tools/fs.existsAsync";
|
12
|
+
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
13
|
+
|
14
|
+
export type BuildContextLike = {
|
15
|
+
themeSrcDirPath: string;
|
16
|
+
};
|
17
|
+
|
18
|
+
assert<BuildContext extends BuildContextLike ? true : false>();
|
19
|
+
|
20
|
+
const DELIMITER_START = `# === Ejected files start ===`;
|
21
|
+
const DELIMITER_END = `# === Ejected files end =====`;
|
22
|
+
|
23
|
+
export async function writeManagedGitignoreFile(params: {
|
24
|
+
buildContext: BuildContextLike;
|
25
|
+
uiModuleMetas: UiModuleMeta[];
|
26
|
+
ejectedFilesRelativePaths: string[];
|
27
|
+
}): Promise<void> {
|
28
|
+
const { buildContext, uiModuleMetas, ejectedFilesRelativePaths } = params;
|
29
|
+
|
30
|
+
if (uiModuleMetas.length === 0) {
|
31
|
+
return;
|
32
|
+
}
|
33
|
+
|
34
|
+
const filePath = pathJoin(buildContext.themeSrcDirPath, ".gitignore");
|
35
|
+
|
36
|
+
const content_new = Buffer.from(
|
37
|
+
[
|
38
|
+
`# This file is managed by Keycloakify, do not edit it manually.`,
|
39
|
+
``,
|
40
|
+
DELIMITER_START,
|
41
|
+
...ejectedFilesRelativePaths
|
42
|
+
.map(fileRelativePath => fileRelativePath.split(pathSep).join("/"))
|
43
|
+
.map(line => `# ${line}`),
|
44
|
+
DELIMITER_END,
|
45
|
+
``,
|
46
|
+
...uiModuleMetas
|
47
|
+
.map(uiModuleMeta => [
|
48
|
+
`# === ${uiModuleMeta.moduleName} v${uiModuleMeta.version} ===`,
|
49
|
+
...uiModuleMeta.files
|
50
|
+
.map(({ fileRelativePath }) => fileRelativePath)
|
51
|
+
.filter(
|
52
|
+
fileRelativePath =>
|
53
|
+
!ejectedFilesRelativePaths.includes(fileRelativePath)
|
54
|
+
)
|
55
|
+
.map(
|
56
|
+
fileRelativePath =>
|
57
|
+
`/${fileRelativePath.split(pathSep).join("/").replace(/^\.\//, "")}`
|
58
|
+
),
|
59
|
+
|
60
|
+
``
|
61
|
+
])
|
62
|
+
.flat()
|
63
|
+
].join("\n"),
|
64
|
+
"utf8"
|
65
|
+
);
|
66
|
+
|
67
|
+
const content_current = await (async () => {
|
68
|
+
if (!(await existsAsync(filePath))) {
|
69
|
+
return undefined;
|
70
|
+
}
|
71
|
+
|
72
|
+
return await fsPr.readFile(filePath);
|
73
|
+
})();
|
74
|
+
|
75
|
+
if (content_current !== undefined && content_current.equals(content_new)) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
|
79
|
+
create_dir: {
|
80
|
+
const dirPath = pathDirname(filePath);
|
81
|
+
|
82
|
+
if (await existsAsync(dirPath)) {
|
83
|
+
break create_dir;
|
84
|
+
}
|
85
|
+
|
86
|
+
await fsPr.mkdir(dirPath, { recursive: true });
|
87
|
+
}
|
88
|
+
|
89
|
+
await fsPr.writeFile(filePath, content_new);
|
90
|
+
}
|
91
|
+
|
92
|
+
export async function readManagedGitignoreFile(params: {
|
93
|
+
buildContext: BuildContextLike;
|
94
|
+
}): Promise<{
|
95
|
+
ejectedFilesRelativePaths: string[];
|
96
|
+
}> {
|
97
|
+
const { buildContext } = params;
|
98
|
+
|
99
|
+
const filePath = pathJoin(buildContext.themeSrcDirPath, ".gitignore");
|
100
|
+
|
101
|
+
if (!(await existsAsync(filePath))) {
|
102
|
+
return { ejectedFilesRelativePaths: [] };
|
103
|
+
}
|
104
|
+
|
105
|
+
const contentStr = (await fsPr.readFile(filePath)).toString("utf8");
|
106
|
+
|
107
|
+
const payload = (() => {
|
108
|
+
const index_start = contentStr.indexOf(DELIMITER_START);
|
109
|
+
const index_end = contentStr.indexOf(DELIMITER_END);
|
110
|
+
|
111
|
+
if (index_start === -1 || index_end === -1) {
|
112
|
+
return undefined;
|
113
|
+
}
|
114
|
+
|
115
|
+
return contentStr.slice(index_start + DELIMITER_START.length, index_end).trim();
|
116
|
+
})();
|
117
|
+
|
118
|
+
if (payload === undefined) {
|
119
|
+
return { ejectedFilesRelativePaths: [] };
|
120
|
+
}
|
121
|
+
|
122
|
+
const ejectedFilesRelativePaths = payload
|
123
|
+
.split("\n")
|
124
|
+
.map(line => line.trim())
|
125
|
+
.map(line => line.replace(/^# /, ""))
|
126
|
+
.filter(line => line !== "")
|
127
|
+
.map(line =>
|
128
|
+
getAbsoluteAndInOsFormatPath({
|
129
|
+
cwd: buildContext.themeSrcDirPath,
|
130
|
+
pathIsh: line
|
131
|
+
})
|
132
|
+
)
|
133
|
+
.map(filePath => pathRelative(buildContext.themeSrcDirPath, filePath));
|
134
|
+
|
135
|
+
return { ejectedFilesRelativePaths };
|
136
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import type { BuildContext } from "../shared/buildContext";
|
2
|
+
import { getUiModuleMetas, computeHash } from "./uiModuleMeta";
|
3
|
+
import { installUiModulesPeerDependencies } from "./installUiModulesPeerDependencies";
|
4
|
+
import {
|
5
|
+
readManagedGitignoreFile,
|
6
|
+
writeManagedGitignoreFile
|
7
|
+
} from "./managedGitignoreFile";
|
8
|
+
import { dirname as pathDirname } from "path";
|
9
|
+
import { join as pathJoin } from "path";
|
10
|
+
import { existsAsync } from "../tools/fs.existsAsync";
|
11
|
+
import * as fsPr from "fs/promises";
|
12
|
+
|
13
|
+
export async function command(params: { buildContext: BuildContext }) {
|
14
|
+
const { buildContext } = params;
|
15
|
+
|
16
|
+
const uiModuleMetas = await getUiModuleMetas({ buildContext });
|
17
|
+
|
18
|
+
await installUiModulesPeerDependencies({
|
19
|
+
buildContext,
|
20
|
+
uiModuleMetas
|
21
|
+
});
|
22
|
+
|
23
|
+
const { ejectedFilesRelativePaths } = await readManagedGitignoreFile({
|
24
|
+
buildContext
|
25
|
+
});
|
26
|
+
|
27
|
+
await writeManagedGitignoreFile({
|
28
|
+
buildContext,
|
29
|
+
ejectedFilesRelativePaths,
|
30
|
+
uiModuleMetas
|
31
|
+
});
|
32
|
+
|
33
|
+
await Promise.all(
|
34
|
+
uiModuleMetas
|
35
|
+
.map(uiModuleMeta =>
|
36
|
+
Promise.all(
|
37
|
+
uiModuleMeta.files.map(
|
38
|
+
async ({ fileRelativePath, copyableFilePath, hash }) => {
|
39
|
+
if (ejectedFilesRelativePaths.includes(fileRelativePath)) {
|
40
|
+
return;
|
41
|
+
}
|
42
|
+
|
43
|
+
const destFilePath = pathJoin(
|
44
|
+
buildContext.themeSrcDirPath,
|
45
|
+
fileRelativePath
|
46
|
+
);
|
47
|
+
|
48
|
+
skip_condition: {
|
49
|
+
if (!(await existsAsync(destFilePath))) {
|
50
|
+
break skip_condition;
|
51
|
+
}
|
52
|
+
|
53
|
+
const destFileHash = computeHash(
|
54
|
+
await fsPr.readFile(destFilePath)
|
55
|
+
);
|
56
|
+
|
57
|
+
if (destFileHash !== hash) {
|
58
|
+
break skip_condition;
|
59
|
+
}
|
60
|
+
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
|
64
|
+
{
|
65
|
+
const dirName = pathDirname(copyableFilePath);
|
66
|
+
|
67
|
+
if (!(await existsAsync(dirName))) {
|
68
|
+
await fsPr.mkdir(dirName, { recursive: true });
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
await fsPr.copyFile(copyableFilePath, destFilePath);
|
73
|
+
}
|
74
|
+
)
|
75
|
+
)
|
76
|
+
)
|
77
|
+
.flat()
|
78
|
+
);
|
79
|
+
}
|