keycloakify 11.3.19 → 11.3.20
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 +675 -0
- package/bin/356.index.js +689 -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 +71 -0
- package/src/bin/postinstall/index.ts +1 -0
- package/src/bin/postinstall/installUiModulesPeerDependencies.ts +157 -0
- package/src/bin/postinstall/managedGitignoreFile.ts +135 -0
- package/src/bin/postinstall/postinstall.ts +79 -0
- package/src/bin/postinstall/uiModuleMeta.ts +303 -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
@@ -0,0 +1,303 @@
|
|
1
|
+
import { assert, type Equals } from "tsafe/assert";
|
2
|
+
import { id } from "tsafe/id";
|
3
|
+
import { z } from "zod";
|
4
|
+
import { join as pathJoin, dirname as pathDirname } from "path";
|
5
|
+
import * as fsPr from "fs/promises";
|
6
|
+
import type { BuildContext } from "../shared/buildContext";
|
7
|
+
import { is } from "tsafe/is";
|
8
|
+
import { existsAsync } from "../tools/fs.existsAsync";
|
9
|
+
import { listInstalledModules } from "../tools/listInstalledModules";
|
10
|
+
import { crawlAsync } from "../tools/crawlAsync";
|
11
|
+
import { getIsPrettierAvailable, getPrettier } from "../tools/runPrettier";
|
12
|
+
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
|
13
|
+
import {
|
14
|
+
getUiModuleFileSourceCodeReadyToBeCopied,
|
15
|
+
type BuildContextLike as BuildContextLike_getUiModuleFileSourceCodeReadyToBeCopied
|
16
|
+
} from "./getUiModuleFileSourceCodeReadyToBeCopied";
|
17
|
+
import * as crypto from "crypto";
|
18
|
+
import { KEYCLOAK_THEME } from "../shared/constants";
|
19
|
+
|
20
|
+
export type UiModuleMeta = {
|
21
|
+
moduleName: string;
|
22
|
+
version: string;
|
23
|
+
files: {
|
24
|
+
fileRelativePath: string;
|
25
|
+
hash: string;
|
26
|
+
copyableFilePath: string;
|
27
|
+
}[];
|
28
|
+
peerDependencies: Record<string, string>;
|
29
|
+
};
|
30
|
+
|
31
|
+
const zUiModuleMeta = (() => {
|
32
|
+
type ExpectedType = UiModuleMeta;
|
33
|
+
|
34
|
+
const zTargetType = z.object({
|
35
|
+
moduleName: z.string(),
|
36
|
+
version: z.string(),
|
37
|
+
files: z.array(
|
38
|
+
z.object({
|
39
|
+
fileRelativePath: z.string(),
|
40
|
+
hash: z.string(),
|
41
|
+
copyableFilePath: z.string()
|
42
|
+
})
|
43
|
+
),
|
44
|
+
peerDependencies: z.record(z.string())
|
45
|
+
});
|
46
|
+
|
47
|
+
type InferredType = z.infer<typeof zTargetType>;
|
48
|
+
|
49
|
+
assert<Equals<InferredType, ExpectedType>>();
|
50
|
+
|
51
|
+
return id<z.ZodType<ExpectedType>>(zTargetType);
|
52
|
+
})();
|
53
|
+
|
54
|
+
type ParsedCacheFile = {
|
55
|
+
keycloakifyVersion: string;
|
56
|
+
prettierConfigHash: string | null;
|
57
|
+
thisFilePath: string;
|
58
|
+
uiModuleMetas: UiModuleMeta[];
|
59
|
+
};
|
60
|
+
|
61
|
+
const zParsedCacheFile = (() => {
|
62
|
+
type ExpectedType = ParsedCacheFile;
|
63
|
+
|
64
|
+
const zTargetType = z.object({
|
65
|
+
keycloakifyVersion: z.string(),
|
66
|
+
prettierConfigHash: z.union([z.string(), z.null()]),
|
67
|
+
thisFilePath: z.string(),
|
68
|
+
uiModuleMetas: z.array(zUiModuleMeta)
|
69
|
+
});
|
70
|
+
|
71
|
+
type InferredType = z.infer<typeof zTargetType>;
|
72
|
+
|
73
|
+
assert<Equals<InferredType, ExpectedType>>();
|
74
|
+
|
75
|
+
return id<z.ZodType<ExpectedType>>(zTargetType);
|
76
|
+
})();
|
77
|
+
|
78
|
+
const CACHE_FILE_RELATIVE_PATH = pathJoin("ui-modules", "cache.json");
|
79
|
+
|
80
|
+
export type BuildContextLike =
|
81
|
+
BuildContextLike_getUiModuleFileSourceCodeReadyToBeCopied & {
|
82
|
+
cacheDirPath: string;
|
83
|
+
packageJsonFilePath: string;
|
84
|
+
projectDirPath: string;
|
85
|
+
};
|
86
|
+
|
87
|
+
assert<BuildContext extends BuildContextLike ? true : false>();
|
88
|
+
|
89
|
+
export async function getUiModuleMetas(params: {
|
90
|
+
buildContext: BuildContextLike;
|
91
|
+
}): Promise<UiModuleMeta[]> {
|
92
|
+
const { buildContext } = params;
|
93
|
+
|
94
|
+
const cacheFilePath = pathJoin(buildContext.cacheDirPath, CACHE_FILE_RELATIVE_PATH);
|
95
|
+
|
96
|
+
const keycloakifyVersion = readThisNpmPackageVersion();
|
97
|
+
|
98
|
+
const prettierConfigHash = await (async () => {
|
99
|
+
if (!(await getIsPrettierAvailable())) {
|
100
|
+
return null;
|
101
|
+
}
|
102
|
+
|
103
|
+
const { configHash } = await getPrettier();
|
104
|
+
|
105
|
+
return configHash;
|
106
|
+
})();
|
107
|
+
|
108
|
+
const installedUiModules = await (async () => {
|
109
|
+
const installedModulesWithKeycloakifyInTheName = await listInstalledModules({
|
110
|
+
packageJsonFilePath: buildContext.packageJsonFilePath,
|
111
|
+
projectDirPath: buildContext.packageJsonFilePath,
|
112
|
+
filter: ({ moduleName }) =>
|
113
|
+
moduleName.includes("keycloakify") && moduleName !== "keycloakify"
|
114
|
+
});
|
115
|
+
|
116
|
+
return Promise.all(
|
117
|
+
installedModulesWithKeycloakifyInTheName.filter(async ({ dirPath }) =>
|
118
|
+
existsAsync(pathJoin(dirPath, KEYCLOAK_THEME))
|
119
|
+
)
|
120
|
+
);
|
121
|
+
})();
|
122
|
+
|
123
|
+
const cacheContent = await (async () => {
|
124
|
+
if (!(await existsAsync(cacheFilePath))) {
|
125
|
+
return undefined;
|
126
|
+
}
|
127
|
+
|
128
|
+
return await fsPr.readFile(cacheFilePath);
|
129
|
+
})();
|
130
|
+
|
131
|
+
const uiModuleMetas_cacheUpToDate: UiModuleMeta[] = await (async () => {
|
132
|
+
const parsedCacheFile: ParsedCacheFile | undefined = await (async () => {
|
133
|
+
if (cacheContent === undefined) {
|
134
|
+
return undefined;
|
135
|
+
}
|
136
|
+
|
137
|
+
const cacheContentStr = cacheContent.toString("utf8");
|
138
|
+
|
139
|
+
let parsedCacheFile: unknown;
|
140
|
+
|
141
|
+
try {
|
142
|
+
parsedCacheFile = JSON.parse(cacheContentStr);
|
143
|
+
} catch {
|
144
|
+
return undefined;
|
145
|
+
}
|
146
|
+
|
147
|
+
try {
|
148
|
+
zParsedCacheFile.parse(parsedCacheFile);
|
149
|
+
} catch {
|
150
|
+
return undefined;
|
151
|
+
}
|
152
|
+
|
153
|
+
assert(is<ParsedCacheFile>(parsedCacheFile));
|
154
|
+
|
155
|
+
return parsedCacheFile;
|
156
|
+
})();
|
157
|
+
|
158
|
+
if (parsedCacheFile === undefined) {
|
159
|
+
return [];
|
160
|
+
}
|
161
|
+
|
162
|
+
if (parsedCacheFile.keycloakifyVersion !== keycloakifyVersion) {
|
163
|
+
return [];
|
164
|
+
}
|
165
|
+
|
166
|
+
if (parsedCacheFile.prettierConfigHash !== prettierConfigHash) {
|
167
|
+
return [];
|
168
|
+
}
|
169
|
+
|
170
|
+
if (parsedCacheFile.thisFilePath !== cacheFilePath) {
|
171
|
+
return [];
|
172
|
+
}
|
173
|
+
|
174
|
+
const uiModuleMetas_cacheUpToDate = parsedCacheFile.uiModuleMetas.filter(
|
175
|
+
uiModuleMeta => {
|
176
|
+
const correspondingInstalledUiModule = installedUiModules.find(
|
177
|
+
installedUiModule =>
|
178
|
+
installedUiModule.moduleName === uiModuleMeta.moduleName
|
179
|
+
);
|
180
|
+
|
181
|
+
if (correspondingInstalledUiModule === undefined) {
|
182
|
+
return false;
|
183
|
+
}
|
184
|
+
|
185
|
+
return correspondingInstalledUiModule.version === uiModuleMeta.version;
|
186
|
+
}
|
187
|
+
);
|
188
|
+
|
189
|
+
return uiModuleMetas_cacheUpToDate;
|
190
|
+
})();
|
191
|
+
|
192
|
+
const uiModuleMetas = await Promise.all(
|
193
|
+
installedUiModules.map(
|
194
|
+
async ({
|
195
|
+
moduleName,
|
196
|
+
version,
|
197
|
+
peerDependencies,
|
198
|
+
dirPath
|
199
|
+
}): Promise<UiModuleMeta> => {
|
200
|
+
use_cache: {
|
201
|
+
const uiModuleMeta_cache = uiModuleMetas_cacheUpToDate.find(
|
202
|
+
uiModuleMeta => uiModuleMeta.moduleName === moduleName
|
203
|
+
);
|
204
|
+
|
205
|
+
if (uiModuleMeta_cache === undefined) {
|
206
|
+
break use_cache;
|
207
|
+
}
|
208
|
+
|
209
|
+
return uiModuleMeta_cache;
|
210
|
+
}
|
211
|
+
|
212
|
+
const files: UiModuleMeta["files"] = [];
|
213
|
+
|
214
|
+
{
|
215
|
+
const srcDirPath = pathJoin(dirPath, KEYCLOAK_THEME);
|
216
|
+
|
217
|
+
await crawlAsync({
|
218
|
+
dirPath: srcDirPath,
|
219
|
+
returnedPathsType: "relative to dirPath",
|
220
|
+
onFileFound: async fileRelativePath => {
|
221
|
+
const sourceCode =
|
222
|
+
await getUiModuleFileSourceCodeReadyToBeCopied({
|
223
|
+
buildContext,
|
224
|
+
fileRelativePath,
|
225
|
+
isForEjection: false,
|
226
|
+
uiModuleDirPath: dirPath,
|
227
|
+
uiModuleName: moduleName,
|
228
|
+
uiModuleVersion: version
|
229
|
+
});
|
230
|
+
|
231
|
+
const hash = computeHash(sourceCode);
|
232
|
+
|
233
|
+
const copyableFilePath = pathJoin(
|
234
|
+
pathDirname(cacheFilePath),
|
235
|
+
KEYCLOAK_THEME,
|
236
|
+
fileRelativePath
|
237
|
+
);
|
238
|
+
|
239
|
+
{
|
240
|
+
const dirPath = pathDirname(copyableFilePath);
|
241
|
+
|
242
|
+
if (!(await existsAsync(dirPath))) {
|
243
|
+
await fsPr.mkdir(dirPath, { recursive: true });
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
fsPr.writeFile(copyableFilePath, sourceCode);
|
248
|
+
|
249
|
+
files.push({
|
250
|
+
fileRelativePath,
|
251
|
+
hash,
|
252
|
+
copyableFilePath
|
253
|
+
});
|
254
|
+
}
|
255
|
+
});
|
256
|
+
}
|
257
|
+
|
258
|
+
return id<UiModuleMeta>({
|
259
|
+
moduleName,
|
260
|
+
version,
|
261
|
+
files,
|
262
|
+
peerDependencies
|
263
|
+
});
|
264
|
+
}
|
265
|
+
)
|
266
|
+
);
|
267
|
+
|
268
|
+
update_cache: {
|
269
|
+
const parsedCacheFile = id<ParsedCacheFile>({
|
270
|
+
keycloakifyVersion,
|
271
|
+
prettierConfigHash,
|
272
|
+
thisFilePath: cacheFilePath,
|
273
|
+
uiModuleMetas
|
274
|
+
});
|
275
|
+
|
276
|
+
const cacheContent_new = Buffer.from(
|
277
|
+
JSON.stringify(parsedCacheFile, null, 2),
|
278
|
+
"utf8"
|
279
|
+
);
|
280
|
+
|
281
|
+
if (cacheContent !== undefined && cacheContent_new.equals(cacheContent)) {
|
282
|
+
break update_cache;
|
283
|
+
}
|
284
|
+
|
285
|
+
create_dir: {
|
286
|
+
const dirPath = pathDirname(cacheFilePath);
|
287
|
+
|
288
|
+
if (await existsAsync(dirPath)) {
|
289
|
+
break create_dir;
|
290
|
+
}
|
291
|
+
|
292
|
+
await fsPr.mkdir(dirPath, { recursive: true });
|
293
|
+
}
|
294
|
+
|
295
|
+
await fsPr.writeFile(cacheFilePath, cacheContent_new);
|
296
|
+
}
|
297
|
+
|
298
|
+
return uiModuleMetas;
|
299
|
+
}
|
300
|
+
|
301
|
+
export function computeHash(data: Buffer) {
|
302
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
303
|
+
}
|
@@ -18,9 +18,8 @@ import {
|
|
18
18
|
import type { KeycloakVersionRange } from "./KeycloakVersionRange";
|
19
19
|
import { exclude } from "tsafe";
|
20
20
|
import { crawl } from "../tools/crawl";
|
21
|
-
import { THEME_TYPES } from "./constants";
|
21
|
+
import { THEME_TYPES, KEYCLOAK_THEME, type ThemeType } from "./constants";
|
22
22
|
import { objectEntries } from "tsafe/objectEntries";
|
23
|
-
import { type ThemeType } from "./constants";
|
24
23
|
import { id } from "tsafe/id";
|
25
24
|
import chalk from "chalk";
|
26
25
|
import { getProxyFetchOptions, type FetchOptionsLike } from "../tools/fetchProxyOptions";
|
@@ -52,6 +51,7 @@ export type BuildContext = {
|
|
52
51
|
account:
|
53
52
|
| { isImplemented: false }
|
54
53
|
| { isImplemented: true; type: "Single-Page" | "Multi-Page" };
|
54
|
+
admin: { isImplemented: boolean };
|
55
55
|
};
|
56
56
|
packageJsonFilePath: string;
|
57
57
|
bundler: "vite" | "webpack";
|
@@ -146,7 +146,10 @@ export function getBuildContext(params: {
|
|
146
146
|
returnedPathsType: "relative to dirPath"
|
147
147
|
})
|
148
148
|
.map(fileRelativePath => {
|
149
|
-
for (const themeSrcDirBasename of [
|
149
|
+
for (const themeSrcDirBasename of [
|
150
|
+
KEYCLOAK_THEME,
|
151
|
+
KEYCLOAK_THEME.replace(/-/g, "_")
|
152
|
+
]) {
|
150
153
|
const split = fileRelativePath.split(themeSrcDirBasename);
|
151
154
|
if (split.length === 2) {
|
152
155
|
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
|
@@ -172,7 +175,7 @@ export function getBuildContext(params: {
|
|
172
175
|
[
|
173
176
|
`Can't locate your Keycloak theme source directory in .${pathSep}${pathRelative(process.cwd(), srcDirPath)}`,
|
174
177
|
`Make sure to either use the Keycloakify CLI in the root of your Keycloakify project or use the --project CLI option`,
|
175
|
-
`If you are collocating your Keycloak theme with your app you must have a directory named '
|
178
|
+
`If you are collocating your Keycloak theme with your app you must have a directory named '${KEYCLOAK_THEME}' or '${KEYCLOAK_THEME.replace(/-/g, "_")}' in your 'src' directory`
|
176
179
|
].join("\n")
|
177
180
|
)
|
178
181
|
);
|
@@ -448,7 +451,10 @@ export function getBuildContext(params: {
|
|
448
451
|
isImplemented: true,
|
449
452
|
type: buildOptions.accountThemeImplementation
|
450
453
|
};
|
451
|
-
})()
|
454
|
+
})(),
|
455
|
+
admin: {
|
456
|
+
isImplemented: fs.existsSync(pathJoin(themeSrcDirPath, "admin"))
|
457
|
+
}
|
452
458
|
};
|
453
459
|
|
454
460
|
if (
|
@@ -4,7 +4,7 @@ export const WELL_KNOWN_DIRECTORY_BASE_NAME = {
|
|
4
4
|
DIST: "dist"
|
5
5
|
} as const;
|
6
6
|
|
7
|
-
export const THEME_TYPES = ["login", "account"] as const;
|
7
|
+
export const THEME_TYPES = ["login", "account", "admin"] as const;
|
8
8
|
|
9
9
|
export type ThemeType = (typeof THEME_TYPES)[number];
|
10
10
|
|
@@ -76,3 +76,5 @@ export const CUSTOM_HANDLER_ENV_NAMES = {
|
|
76
76
|
COMMAND_NAME: "KEYCLOAKIFY_COMMAND_NAME",
|
77
77
|
BUILD_CONTEXT: "KEYCLOAKIFY_BUILD_CONTEXT"
|
78
78
|
};
|
79
|
+
|
80
|
+
export const KEYCLOAK_THEME = "keycloak-theme";
|
@@ -8,7 +8,7 @@ import {
|
|
8
8
|
ApiVersion
|
9
9
|
} from "./customHandler";
|
10
10
|
import * as child_process from "child_process";
|
11
|
-
import {
|
11
|
+
import { getNodeModulesBinDirPath } from "../tools/nodeModulesBinDirPath";
|
12
12
|
import * as fs from "fs";
|
13
13
|
|
14
14
|
assert<Equals<ApiVersion, "v1">>();
|
@@ -19,32 +19,7 @@ export function maybeDelegateCommandToCustomHandler(params: {
|
|
19
19
|
}): { hasBeenHandled: boolean } {
|
20
20
|
const { commandName, buildContext } = params;
|
21
21
|
|
22
|
-
const nodeModulesBinDirPath = (
|
23
|
-
const binPath = process.argv[1];
|
24
|
-
|
25
|
-
const segments: string[] = [".bin"];
|
26
|
-
|
27
|
-
let foundNodeModules = false;
|
28
|
-
|
29
|
-
for (const segment of binPath.split(pathSep).reverse()) {
|
30
|
-
skip_segment: {
|
31
|
-
if (foundNodeModules) {
|
32
|
-
break skip_segment;
|
33
|
-
}
|
34
|
-
|
35
|
-
if (segment === "node_modules") {
|
36
|
-
foundNodeModules = true;
|
37
|
-
break skip_segment;
|
38
|
-
}
|
39
|
-
|
40
|
-
continue;
|
41
|
-
}
|
42
|
-
|
43
|
-
segments.unshift(segment);
|
44
|
-
}
|
45
|
-
|
46
|
-
return segments.join(pathSep);
|
47
|
-
})();
|
22
|
+
const nodeModulesBinDirPath = getNodeModulesBinDirPath();
|
48
23
|
|
49
24
|
if (!fs.readdirSync(nodeModulesBinDirPath).includes(BIN_NAME)) {
|
50
25
|
return { hasBeenHandled: false };
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import child_process from "child_process";
|
2
|
+
import chalk from "chalk";
|
3
|
+
|
4
|
+
export function exitIfUncommittedChanges(params: { projectDirPath: string }) {
|
5
|
+
const { projectDirPath } = params;
|
6
|
+
|
7
|
+
let hasUncommittedChanges: boolean | undefined = undefined;
|
8
|
+
|
9
|
+
try {
|
10
|
+
hasUncommittedChanges =
|
11
|
+
child_process
|
12
|
+
.execSync(`git status --porcelain`, {
|
13
|
+
cwd: projectDirPath
|
14
|
+
})
|
15
|
+
.toString()
|
16
|
+
.trim() !== "";
|
17
|
+
} catch {
|
18
|
+
// Probably not a git repository
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
|
22
|
+
if (!hasUncommittedChanges) {
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
console.warn(
|
26
|
+
[
|
27
|
+
chalk.red(
|
28
|
+
"Please commit or stash your changes before running this command.\n"
|
29
|
+
),
|
30
|
+
"This command will modify your project's files so it's better to have a clean working directory",
|
31
|
+
"so that you can easily see what has been changed and revert if needed."
|
32
|
+
].join(" ")
|
33
|
+
);
|
34
|
+
|
35
|
+
process.exit(-1);
|
36
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import * as fsPr from "fs/promises";
|
2
|
+
import { join as pathJoin, relative as pathRelative } from "path";
|
3
|
+
import { assert, type Equals } from "tsafe/assert";
|
4
|
+
|
5
|
+
/** List all files in a given directory return paths relative to the dir_path */
|
6
|
+
export async function crawlAsync(params: {
|
7
|
+
dirPath: string;
|
8
|
+
returnedPathsType: "absolute" | "relative to dirPath";
|
9
|
+
onFileFound: (filePath: string) => void;
|
10
|
+
}) {
|
11
|
+
const { dirPath, returnedPathsType, onFileFound } = params;
|
12
|
+
|
13
|
+
await crawlAsyncRec({
|
14
|
+
dirPath,
|
15
|
+
onFileFound: ({ filePath }) => {
|
16
|
+
switch (returnedPathsType) {
|
17
|
+
case "absolute":
|
18
|
+
onFileFound(filePath);
|
19
|
+
return;
|
20
|
+
case "relative to dirPath":
|
21
|
+
onFileFound(pathRelative(dirPath, filePath));
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
assert<Equals<typeof returnedPathsType, never>>();
|
25
|
+
}
|
26
|
+
});
|
27
|
+
}
|
28
|
+
|
29
|
+
async function crawlAsyncRec(params: {
|
30
|
+
dirPath: string;
|
31
|
+
onFileFound: (params: { filePath: string }) => void;
|
32
|
+
}) {
|
33
|
+
const { dirPath, onFileFound } = params;
|
34
|
+
|
35
|
+
await Promise.all(
|
36
|
+
(await fsPr.readdir(dirPath)).map(async basename => {
|
37
|
+
const fileOrDirPath = pathJoin(dirPath, basename);
|
38
|
+
|
39
|
+
const isDirectory = await fsPr
|
40
|
+
.lstat(fileOrDirPath)
|
41
|
+
.then(stat => stat.isDirectory());
|
42
|
+
|
43
|
+
if (isDirectory) {
|
44
|
+
await crawlAsyncRec({ dirPath: fileOrDirPath, onFileFound });
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
|
48
|
+
onFileFound({ filePath: fileOrDirPath });
|
49
|
+
})
|
50
|
+
);
|
51
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { join as pathJoin } from "path";
|
2
|
+
import { existsAsync } from "./fs.existsAsync";
|
3
|
+
import * as child_process from "child_process";
|
4
|
+
import { assert } from "tsafe/assert";
|
5
|
+
|
6
|
+
export async function getInstalledModuleDirPath(params: {
|
7
|
+
moduleName: string;
|
8
|
+
packageJsonDirPath: string;
|
9
|
+
projectDirPath: string;
|
10
|
+
}) {
|
11
|
+
const { moduleName, packageJsonDirPath, projectDirPath } = params;
|
12
|
+
|
13
|
+
common_case: {
|
14
|
+
const dirPath = pathJoin(
|
15
|
+
...[packageJsonDirPath, "node_modules", ...moduleName.split("/")]
|
16
|
+
);
|
17
|
+
|
18
|
+
if (!(await existsAsync(dirPath))) {
|
19
|
+
break common_case;
|
20
|
+
}
|
21
|
+
|
22
|
+
return dirPath;
|
23
|
+
}
|
24
|
+
|
25
|
+
node_modules_at_root_case: {
|
26
|
+
if (projectDirPath === packageJsonDirPath) {
|
27
|
+
break node_modules_at_root_case;
|
28
|
+
}
|
29
|
+
|
30
|
+
const dirPath = pathJoin(
|
31
|
+
...[projectDirPath, "node_modules", ...moduleName.split("/")]
|
32
|
+
);
|
33
|
+
|
34
|
+
if (!(await existsAsync(dirPath))) {
|
35
|
+
break node_modules_at_root_case;
|
36
|
+
}
|
37
|
+
|
38
|
+
return dirPath;
|
39
|
+
}
|
40
|
+
|
41
|
+
const dirPath = child_process
|
42
|
+
.execSync(`npm list ${moduleName}`, {
|
43
|
+
cwd: packageJsonDirPath
|
44
|
+
})
|
45
|
+
.toString("utf8")
|
46
|
+
.trim();
|
47
|
+
|
48
|
+
assert(dirPath !== "");
|
49
|
+
|
50
|
+
return dirPath;
|
51
|
+
}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import { assert, type Equals } from "tsafe/assert";
|
2
|
+
import { id } from "tsafe/id";
|
3
|
+
import { z } from "zod";
|
4
|
+
import { join as pathJoin, dirname as pathDirname } from "path";
|
5
|
+
import * as fsPr from "fs/promises";
|
6
|
+
import { is } from "tsafe/is";
|
7
|
+
import { getInstalledModuleDirPath } from "../tools/getInstalledModuleDirPath";
|
8
|
+
import { exclude } from "tsafe/exclude";
|
9
|
+
|
10
|
+
export async function listInstalledModules(params: {
|
11
|
+
packageJsonFilePath: string;
|
12
|
+
projectDirPath: string;
|
13
|
+
filter: (params: { moduleName: string }) => boolean;
|
14
|
+
}): Promise<
|
15
|
+
{
|
16
|
+
moduleName: string;
|
17
|
+
version: string;
|
18
|
+
dirPath: string;
|
19
|
+
peerDependencies: Record<string, string>;
|
20
|
+
}[]
|
21
|
+
> {
|
22
|
+
const { packageJsonFilePath, projectDirPath, filter } = params;
|
23
|
+
|
24
|
+
const parsedPackageJson = await readPackageJsonDependencies({
|
25
|
+
packageJsonFilePath
|
26
|
+
});
|
27
|
+
|
28
|
+
const uiModuleNames = (
|
29
|
+
[parsedPackageJson.dependencies, parsedPackageJson.devDependencies] as const
|
30
|
+
)
|
31
|
+
.filter(exclude(undefined))
|
32
|
+
.map(obj => Object.keys(obj))
|
33
|
+
.flat()
|
34
|
+
.filter(moduleName => filter({ moduleName }));
|
35
|
+
|
36
|
+
const result = await Promise.all(
|
37
|
+
uiModuleNames.map(async moduleName => {
|
38
|
+
const dirPath = await getInstalledModuleDirPath({
|
39
|
+
moduleName,
|
40
|
+
packageJsonDirPath: pathDirname(packageJsonFilePath),
|
41
|
+
projectDirPath
|
42
|
+
});
|
43
|
+
|
44
|
+
const { version, peerDependencies } =
|
45
|
+
await readPackageJsonVersionAndPeerDependencies({
|
46
|
+
packageJsonFilePath: pathJoin(dirPath, "package.json")
|
47
|
+
});
|
48
|
+
|
49
|
+
return { moduleName, version, peerDependencies, dirPath } as const;
|
50
|
+
})
|
51
|
+
);
|
52
|
+
|
53
|
+
return result;
|
54
|
+
}
|
55
|
+
|
56
|
+
const { readPackageJsonDependencies } = (() => {
|
57
|
+
type ParsedPackageJson = {
|
58
|
+
dependencies?: Record<string, string>;
|
59
|
+
devDependencies?: Record<string, string>;
|
60
|
+
};
|
61
|
+
|
62
|
+
const zParsedPackageJson = (() => {
|
63
|
+
type TargetType = ParsedPackageJson;
|
64
|
+
|
65
|
+
const zTargetType = z.object({
|
66
|
+
dependencies: z.record(z.string()).optional(),
|
67
|
+
devDependencies: z.record(z.string()).optional()
|
68
|
+
});
|
69
|
+
|
70
|
+
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
71
|
+
|
72
|
+
return id<z.ZodType<TargetType>>(zTargetType);
|
73
|
+
})();
|
74
|
+
|
75
|
+
async function readPackageJsonDependencies(params: { packageJsonFilePath: string }) {
|
76
|
+
const { packageJsonFilePath } = params;
|
77
|
+
|
78
|
+
const parsedPackageJson = JSON.parse(
|
79
|
+
(await fsPr.readFile(packageJsonFilePath)).toString("utf8")
|
80
|
+
);
|
81
|
+
|
82
|
+
zParsedPackageJson.parse(parsedPackageJson);
|
83
|
+
|
84
|
+
assert(is<ParsedPackageJson>(parsedPackageJson));
|
85
|
+
|
86
|
+
return parsedPackageJson;
|
87
|
+
}
|
88
|
+
|
89
|
+
return { readPackageJsonDependencies };
|
90
|
+
})();
|
91
|
+
|
92
|
+
const { readPackageJsonVersionAndPeerDependencies } = (() => {
|
93
|
+
type ParsedPackageJson = {
|
94
|
+
version: string;
|
95
|
+
peerDependencies?: Record<string, string>;
|
96
|
+
};
|
97
|
+
|
98
|
+
const zParsedPackageJson = (() => {
|
99
|
+
type TargetType = ParsedPackageJson;
|
100
|
+
|
101
|
+
const zTargetType = z.object({
|
102
|
+
version: z.string(),
|
103
|
+
peerDependencies: z.record(z.string()).optional()
|
104
|
+
});
|
105
|
+
|
106
|
+
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
107
|
+
|
108
|
+
return id<z.ZodType<TargetType>>(zTargetType);
|
109
|
+
})();
|
110
|
+
|
111
|
+
async function readPackageJsonVersionAndPeerDependencies(params: {
|
112
|
+
packageJsonFilePath: string;
|
113
|
+
}): Promise<{ version: string; peerDependencies: Record<string, string> }> {
|
114
|
+
const { packageJsonFilePath } = params;
|
115
|
+
|
116
|
+
const parsedPackageJson = JSON.parse(
|
117
|
+
(await fsPr.readFile(packageJsonFilePath)).toString("utf8")
|
118
|
+
);
|
119
|
+
|
120
|
+
zParsedPackageJson.parse(parsedPackageJson);
|
121
|
+
|
122
|
+
assert(is<ParsedPackageJson>(parsedPackageJson));
|
123
|
+
|
124
|
+
return {
|
125
|
+
version: parsedPackageJson.version,
|
126
|
+
peerDependencies: parsedPackageJson.peerDependencies ?? {}
|
127
|
+
};
|
128
|
+
}
|
129
|
+
|
130
|
+
return { readPackageJsonVersionAndPeerDependencies };
|
131
|
+
})();
|