keycloakify 11.6.2 → 11.7.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 (72) hide show
  1. package/README.md +1 -1
  2. package/bin/{735.index.js → 288.index.js} +226 -360
  3. package/bin/313.index.js +377 -0
  4. package/bin/{174.index.js → 33.index.js} +37 -2
  5. package/bin/{840.index.js → 355.index.js} +348 -97
  6. package/bin/363.index.js +157 -145
  7. package/bin/453.index.js +5 -41
  8. package/bin/{503.index.js → 678.index.js} +577 -53
  9. package/bin/{921.index.js → 780.index.js} +54 -45
  10. package/bin/880.index.js +206 -21
  11. package/bin/9.index.js +850 -0
  12. package/bin/{525.index.js → 911.index.js} +1302 -2
  13. package/bin/930.index.js +3 -4
  14. package/bin/{153.index.js → 947.index.js} +22 -797
  15. package/bin/main.js +11 -10
  16. package/bin/shared/initializeSpa/index.d.ts +1 -0
  17. package/bin/shared/initializeSpa/initializeSpa.d.ts +9 -0
  18. package/bin/tools/getInstalledModuleDirPath.d.ts +0 -1
  19. package/bin/tools/isRootPath.d.ts +1 -0
  20. package/bin/tools/listInstalledModules.d.ts +0 -1
  21. package/package.json +22 -44
  22. package/src/bin/eject-page.ts +7 -81
  23. package/src/bin/initialize-account-theme/initialize-account-theme.ts +15 -14
  24. package/src/bin/initialize-admin-theme.ts +17 -124
  25. package/src/bin/initialize-email-theme.ts +10 -34
  26. package/src/bin/keycloakify/generateResources/generateResources.ts +49 -21
  27. package/src/bin/main.ts +5 -4
  28. package/src/bin/own.ts +1 -2
  29. package/src/bin/shared/{addSyncExtensionsToPostinstallScript.ts → initializeSpa/addSyncExtensionsToPostinstallScript.ts} +2 -2
  30. package/src/bin/shared/initializeSpa/index.ts +1 -0
  31. package/src/bin/shared/initializeSpa/initializeSpa.ts +149 -0
  32. package/src/bin/sync-extensions/extensionModuleMeta.ts +0 -1
  33. package/src/bin/sync-extensions/getExtensionModuleFileSourceCodeReadyToBeCopied.ts +4 -1
  34. package/src/bin/tools/getInstalledModuleDirPath.ts +24 -22
  35. package/src/bin/tools/isRootPath.ts +22 -0
  36. package/src/bin/tools/listInstalledModules.ts +2 -4
  37. package/src/bin/tsconfig.json +1 -1
  38. package/bin/375.index.js +0 -4089
  39. package/bin/490.index.js +0 -1108
  40. package/bin/568.index.js +0 -1867
  41. package/bin/743.index.js +0 -69
  42. package/bin/initialize-account-theme/copyBoilerplate.d.ts +0 -4
  43. package/bin/initialize-account-theme/initializeAccountTheme_multiPage.d.ts +0 -3
  44. package/bin/initialize-account-theme/initializeAccountTheme_singlePage.d.ts +0 -11
  45. package/bin/shared/getLatestsSemVersionedTag.d.ts +0 -15
  46. package/bin/shared/promptKeycloakVersion.d.ts +0 -10
  47. package/bin/tools/OptionalIfCanBeUndefined.d.ts +0 -14
  48. package/bin/tools/crc32.d.ts +0 -9
  49. package/bin/tools/deflate.d.ts +0 -24
  50. package/bin/tools/octokit-addons/getLatestsSemVersionedTag.d.ts +0 -15
  51. package/bin/tools/octokit-addons/listTags.d.ts +0 -13
  52. package/bin/tools/tee.d.ts +0 -3
  53. package/bin/tools/trimIndent.d.ts +0 -5
  54. package/src/bin/initialize-account-theme/copyBoilerplate.ts +0 -32
  55. package/src/bin/initialize-account-theme/initializeAccountTheme_multiPage.ts +0 -21
  56. package/src/bin/initialize-account-theme/initializeAccountTheme_singlePage.ts +0 -142
  57. package/src/bin/initialize-account-theme/src/single-page/KcContext.ts +0 -7
  58. package/src/bin/initialize-account-theme/src/single-page/KcPage.tsx +0 -11
  59. package/src/bin/shared/getLatestsSemVersionedTag.ts +0 -201
  60. package/src/bin/shared/promptKeycloakVersion.ts +0 -72
  61. package/src/bin/tools/OptionalIfCanBeUndefined.ts +0 -12
  62. package/src/bin/tools/crc32.ts +0 -73
  63. package/src/bin/tools/deflate.ts +0 -61
  64. package/src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts +0 -47
  65. package/src/bin/tools/octokit-addons/listTags.ts +0 -60
  66. package/src/bin/tools/tee.ts +0 -39
  67. package/src/bin/tools/trimIndent.ts +0 -49
  68. /package/bin/shared/{addSyncExtensionsToPostinstallScript.d.ts → initializeSpa/addSyncExtensionsToPostinstallScript.d.ts} +0 -0
  69. /package/src/bin/initialize-account-theme/{src/multi-page → multi-page-boilerplate}/KcContext.ts +0 -0
  70. /package/src/bin/initialize-account-theme/{src/multi-page → multi-page-boilerplate}/KcPage.tsx +0 -0
  71. /package/src/bin/initialize-account-theme/{src/multi-page → multi-page-boilerplate}/KcPageStory.tsx +0 -0
  72. /package/src/bin/initialize-account-theme/{src/multi-page → multi-page-boilerplate}/i18n.ts +0 -0
@@ -1,13 +1,11 @@
1
1
  import { join as pathJoin, relative as pathRelative } from "path";
2
2
  import { transformCodebase } from "./tools/transformCodebase";
3
- import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
4
3
  import type { BuildContext } from "./shared/buildContext";
5
4
  import * as fs from "fs";
6
5
  import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive";
7
6
  import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
8
- import fetch from "make-fetch-happen";
9
- import { SemVer } from "./tools/SemVer";
10
7
  import { assert } from "tsafe/assert";
8
+ import { getSupportedDockerImageTags } from "./start-keycloak/getSupportedDockerImageTags";
11
9
 
12
10
  export async function command(params: { buildContext: BuildContext }) {
13
11
  const { buildContext } = params;
@@ -39,40 +37,18 @@ export async function command(params: { buildContext: BuildContext }) {
39
37
 
40
38
  console.log("Initialize with the base email theme from which version of Keycloak?");
41
39
 
42
- let { keycloakVersion } = await promptKeycloakVersion({
43
- // NOTE: This is arbitrary
44
- startingFromMajor: 17,
45
- excludeMajorVersions: [],
46
- doOmitPatch: false,
47
- buildContext
48
- });
49
-
50
- const getUrl = (keycloakVersion: string) => {
51
- return `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`;
52
- };
53
-
54
- keycloakVersion = await (async () => {
55
- const keycloakVersionParsed = SemVer.parse(keycloakVersion);
56
-
57
- while (true) {
58
- const url = getUrl(SemVer.stringify(keycloakVersionParsed));
59
-
60
- const response = await fetch(url, buildContext.fetchOptions);
61
-
62
- if (response.ok) {
63
- break;
64
- }
65
-
66
- assert(keycloakVersionParsed.patch !== 0);
40
+ const { extractedDirPath } = await downloadAndExtractArchive({
41
+ url: await (async () => {
42
+ const { latestMajorTags } = await getSupportedDockerImageTags({
43
+ buildContext
44
+ });
67
45
 
68
- keycloakVersionParsed.patch--;
69
- }
46
+ const keycloakVersion = latestMajorTags[0];
70
47
 
71
- return SemVer.stringify(keycloakVersionParsed);
72
- })();
48
+ assert(keycloakVersion !== undefined);
73
49
 
74
- const { extractedDirPath } = await downloadAndExtractArchive({
75
- url: getUrl(keycloakVersion),
50
+ return `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`;
51
+ })(),
76
52
  cacheDirPath: buildContext.cacheDirPath,
77
53
  fetchOptions: buildContext.fetchOptions,
78
54
  uniqueIdOfOnArchiveFile: "extractOnlyEmailTheme",
@@ -37,10 +37,10 @@ import {
37
37
  } from "../../shared/metaInfKeycloakThemes";
38
38
  import { objectEntries } from "tsafe/objectEntries";
39
39
  import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
40
- import * as child_process from "child_process";
41
40
  import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
42
41
  import propertiesParser from "properties-parser";
43
42
  import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
43
+ import { listInstalledModules } from "../../tools/listInstalledModules";
44
44
 
45
45
  export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
46
46
  BuildContextLike_generateMessageProperties & {
@@ -238,9 +238,9 @@ export async function generateResources(params: {
238
238
 
239
239
  let languageTags: string[] | undefined = undefined;
240
240
 
241
- i18n_messages_generation: {
241
+ i18n_multi_page: {
242
242
  if (isSpa) {
243
- break i18n_messages_generation;
243
+ break i18n_multi_page;
244
244
  }
245
245
 
246
246
  assert(themeType !== "admin");
@@ -257,23 +257,43 @@ export async function generateResources(params: {
257
257
  writeMessagePropertiesFiles;
258
258
  }
259
259
 
260
- bring_in_account_spa_messages: {
260
+ let isLegacyAccountSpa = false;
261
+
262
+ // NOTE: Eventually remove this block.
263
+ i18n_single_page_account_legacy: {
261
264
  if (!isSpa) {
262
- break bring_in_account_spa_messages;
265
+ break i18n_single_page_account_legacy;
263
266
  }
264
267
 
265
268
  if (themeType !== "account") {
266
- break bring_in_account_spa_messages;
269
+ break i18n_single_page_account_legacy;
267
270
  }
268
271
 
269
- const accountUiDirPath = child_process
270
- .execSync(`npm list @keycloakify/keycloak-account-ui --parseable`, {
271
- cwd: pathDirname(buildContext.packageJsonFilePath)
272
- })
273
- .toString("utf8")
274
- .trim();
272
+ const [moduleMeta] = await listInstalledModules({
273
+ packageJsonFilePath: buildContext.packageJsonFilePath,
274
+ filter: ({ moduleName }) =>
275
+ moduleName === "@keycloakify/keycloak-account-ui"
276
+ });
277
+
278
+ assert(
279
+ moduleMeta !== undefined,
280
+ `@keycloakify/keycloak-account-ui is supposed to be installed`
281
+ );
282
+
283
+ {
284
+ const [majorStr] = moduleMeta.version.split(".");
285
+
286
+ if (majorStr.length === 6) {
287
+ // NOTE: Now we use the format MMmmpp (Major, minor, patch) for example for
288
+ // 26.0.7 it would be 260007.
289
+ break i18n_single_page_account_legacy;
290
+ } else {
291
+ // 25.0.4-rc.5 or later
292
+ isLegacyAccountSpa = true;
293
+ }
294
+ }
275
295
 
276
- const messageDirPath_defaults = pathJoin(accountUiDirPath, "messages");
296
+ const messageDirPath_defaults = pathJoin(moduleMeta.dirPath, "messages");
277
297
 
278
298
  if (!fs.existsSync(messageDirPath_defaults)) {
279
299
  throw new Error(
@@ -281,6 +301,8 @@ export async function generateResources(params: {
281
301
  );
282
302
  }
283
303
 
304
+ isLegacyAccountSpa = true;
305
+
284
306
  const messagesDirPath_dest = pathJoin(
285
307
  getThemeTypeDirPath({ themeName, themeType: "account" }),
286
308
  "messages"
@@ -342,14 +364,20 @@ export async function generateResources(params: {
342
364
  );
343
365
  }
344
366
 
345
- bring_in_admin_messages: {
346
- if (themeType !== "admin") {
347
- break bring_in_admin_messages;
367
+ i18n_single_page: {
368
+ if (!isSpa) {
369
+ break i18n_single_page;
370
+ }
371
+
372
+ if (isLegacyAccountSpa) {
373
+ break i18n_single_page;
348
374
  }
349
375
 
376
+ assert(themeType === "account" || themeType === "admin");
377
+
350
378
  const messagesDirPath_theme = pathJoin(
351
379
  buildContext.themeSrcDirPath,
352
- "admin",
380
+ themeType,
353
381
  "i18n"
354
382
  );
355
383
 
@@ -423,7 +451,7 @@ export async function generateResources(params: {
423
451
 
424
452
  propertiesByLang[parsedBasename.lang] ??= {
425
453
  base: createObjectThatThrowsIfAccessed<Buffer>({
426
- debugMessage: `No base ${parsedBasename.lang} translation for admin theme`
454
+ debugMessage: `No base ${parsedBasename.lang} translation for ${themeType} theme`
427
455
  }),
428
456
  override: undefined,
429
457
  overrideByThemeName: {}
@@ -446,7 +474,9 @@ export async function generateResources(params: {
446
474
  ] = buffer;
447
475
  });
448
476
 
449
- writeMessagePropertiesFilesByThemeType.admin = ({
477
+ languageTags = Object.keys(propertiesByLang);
478
+
479
+ writeMessagePropertiesFilesByThemeType[themeType] = ({
450
480
  messageDirPath,
451
481
  themeName
452
482
  }) => {
@@ -456,8 +486,6 @@ export async function generateResources(params: {
456
486
 
457
487
  Object.entries(propertiesByLang).forEach(
458
488
  ([lang, { base, override, overrideByThemeName }]) => {
459
- (languageTags ??= []).push(lang);
460
-
461
489
  const messages = propertiesParser.parse(base.toString("utf8"));
462
490
 
463
491
  if (override !== undefined) {
package/src/bin/main.ts CHANGED
@@ -303,7 +303,7 @@ program
303
303
  key: "path",
304
304
  name: (() => {
305
305
  const long = "path";
306
- const short = "p";
306
+ const short = "t";
307
307
 
308
308
  optionsKeys.push(long, short);
309
309
 
@@ -318,11 +318,12 @@ program
318
318
  .option({
319
319
  key: "revert",
320
320
  name: (() => {
321
- const name = "revert";
321
+ const long = "revert";
322
+ const short = "r";
322
323
 
323
- optionsKeys.push(name);
324
+ optionsKeys.push(long, short);
324
325
 
325
- return name;
326
+ return { long, short };
326
327
  })(),
327
328
  description: [
328
329
  "Restores a file or directory to its original auto-generated state,",
package/src/bin/own.ts CHANGED
@@ -124,8 +124,7 @@ async function command_own(params: Params_subcommands) {
124
124
  ] of targetFileRelativePathsByExtensionModuleMeta.entries()) {
125
125
  const extensionModuleDirPath = await getInstalledModuleDirPath({
126
126
  moduleName: extensionModuleMeta.moduleName,
127
- packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath),
128
- projectDirPath: buildContext.projectDirPath
127
+ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
129
128
  });
130
129
 
131
130
  for (const fileRelativePath of fileRelativePaths) {
@@ -1,6 +1,6 @@
1
1
  import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
2
2
  import { assert } from "tsafe/assert";
3
- import type { BuildContext } from "./buildContext";
3
+ import type { BuildContext } from "../buildContext";
4
4
 
5
5
  export type BuildContextLike = {
6
6
  projectDirPath: string;
@@ -56,7 +56,7 @@ export function addSyncExtensionsToPostinstallScript(params: {
56
56
  continue;
57
57
  }
58
58
 
59
- if (cmd_preexisting.includes(cmd_base)) {
59
+ if (!cmd_preexisting.includes(cmd_base)) {
60
60
  scripts[scriptName] = generateCmd({ cmd_preexisting });
61
61
  return;
62
62
  }
@@ -0,0 +1 @@
1
+ export * from "./initializeSpa";
@@ -0,0 +1,149 @@
1
+ import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
2
+ import type { BuildContext } from "../buildContext";
3
+ import * as fs from "fs";
4
+ import { assert, is, type Equals } from "tsafe/assert";
5
+ import { id } from "tsafe/id";
6
+ import {
7
+ addSyncExtensionsToPostinstallScript,
8
+ type BuildContextLike as BuildContextLike_addSyncExtensionsToPostinstallScript
9
+ } from "./addSyncExtensionsToPostinstallScript";
10
+ import { getIsPrettierAvailable, runPrettier } from "../../tools/runPrettier";
11
+ import { npmInstall } from "../../tools/npmInstall";
12
+ import * as child_process from "child_process";
13
+ import { z } from "zod";
14
+ import chalk from "chalk";
15
+
16
+ export type BuildContextLike = BuildContextLike_addSyncExtensionsToPostinstallScript & {
17
+ themeSrcDirPath: string;
18
+ packageJsonFilePath: string;
19
+ };
20
+
21
+ assert<BuildContext extends BuildContextLike ? true : false>();
22
+
23
+ export async function initializeSpa(params: {
24
+ themeType: "account" | "admin";
25
+ buildContext: BuildContextLike;
26
+ }) {
27
+ const { themeType, buildContext } = params;
28
+
29
+ {
30
+ const themeTypeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, themeType);
31
+
32
+ if (
33
+ fs.existsSync(themeTypeSrcDirPath) &&
34
+ fs.readdirSync(themeTypeSrcDirPath).length > 0
35
+ ) {
36
+ console.warn(
37
+ chalk.red(
38
+ `There is already a ${pathRelative(
39
+ process.cwd(),
40
+ themeTypeSrcDirPath
41
+ )} directory in your project. Aborting.`
42
+ )
43
+ );
44
+
45
+ process.exit(-1);
46
+ }
47
+ }
48
+
49
+ const parsedPackageJson = (() => {
50
+ type ParsedPackageJson = {
51
+ scripts?: Record<string, string | undefined>;
52
+ dependencies?: Record<string, string | undefined>;
53
+ devDependencies?: Record<string, string | undefined>;
54
+ };
55
+
56
+ const zParsedPackageJson = (() => {
57
+ type TargetType = ParsedPackageJson;
58
+
59
+ const zTargetType = z.object({
60
+ scripts: z.record(z.union([z.string(), z.undefined()])).optional(),
61
+ dependencies: z.record(z.union([z.string(), z.undefined()])).optional(),
62
+ devDependencies: z.record(z.union([z.string(), z.undefined()])).optional()
63
+ });
64
+
65
+ assert<Equals<z.infer<typeof zTargetType>, TargetType>>;
66
+
67
+ return id<z.ZodType<TargetType>>(zTargetType);
68
+ })();
69
+ const parsedPackageJson = JSON.parse(
70
+ fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
71
+ );
72
+
73
+ zParsedPackageJson.parse(parsedPackageJson);
74
+
75
+ assert(is<ParsedPackageJson>(parsedPackageJson));
76
+
77
+ return parsedPackageJson;
78
+ })();
79
+
80
+ addSyncExtensionsToPostinstallScript({
81
+ parsedPackageJson,
82
+ buildContext
83
+ });
84
+
85
+ const uiSharedMajor = (() => {
86
+ const dependencies = {
87
+ ...parsedPackageJson.devDependencies,
88
+ ...parsedPackageJson.dependencies
89
+ };
90
+
91
+ const version = dependencies["@keycloakify/keycloak-ui-shared"];
92
+
93
+ if (version === undefined) {
94
+ return undefined;
95
+ }
96
+
97
+ const match = version.match(/^[^~]?(\d+)\./);
98
+
99
+ if (match === null) {
100
+ return undefined;
101
+ }
102
+
103
+ return match[1];
104
+ })();
105
+
106
+ const moduleName = `@keycloakify/keycloak-${themeType}-ui`;
107
+
108
+ const version = (
109
+ JSON.parse(
110
+ child_process
111
+ .execSync(`npm show ${moduleName} versions --json`)
112
+ .toString("utf8")
113
+ .trim()
114
+ ) as string[]
115
+ )
116
+ .reverse()
117
+ .filter(version => !version.includes("-"))
118
+ .find(version =>
119
+ uiSharedMajor === undefined ? true : version.startsWith(`${uiSharedMajor}.`)
120
+ );
121
+
122
+ assert(version !== undefined);
123
+
124
+ (parsedPackageJson.dependencies ??= {})[moduleName] = `~${version}`;
125
+
126
+ if (parsedPackageJson.devDependencies !== undefined) {
127
+ delete parsedPackageJson.devDependencies[moduleName];
128
+ }
129
+
130
+ {
131
+ let sourceCode = JSON.stringify(parsedPackageJson, undefined, 2);
132
+
133
+ if (await getIsPrettierAvailable()) {
134
+ sourceCode = await runPrettier({
135
+ sourceCode,
136
+ filePath: buildContext.packageJsonFilePath
137
+ });
138
+ }
139
+
140
+ fs.writeFileSync(
141
+ buildContext.packageJsonFilePath,
142
+ Buffer.from(sourceCode, "utf8")
143
+ );
144
+ }
145
+
146
+ await npmInstall({
147
+ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
148
+ });
149
+ }
@@ -109,7 +109,6 @@ export async function getExtensionModuleMetas(params: {
109
109
  const installedExtensionModules = await (async () => {
110
110
  const installedModulesWithKeycloakifyInTheName = await listInstalledModules({
111
111
  packageJsonFilePath: buildContext.packageJsonFilePath,
112
- projectDirPath: buildContext.packageJsonFilePath,
113
112
  filter: ({ moduleName }) =>
114
113
  moduleName.includes("keycloakify") && moduleName !== "keycloakify"
115
114
  });
@@ -104,7 +104,10 @@ function addCommentToSourceCode(params: {
104
104
  `<!--`,
105
105
  ...commentLines.map(
106
106
  line =>
107
- ` ${line.replace("--path", "-p").replace("Before modifying", "Before modifying or replacing")}`
107
+ ` ${line
108
+ .replace("--path", "-t")
109
+ .replace("--revert", "-r")
110
+ .replace("Before modifying", "Before modifying or replacing")}`
108
111
  ),
109
112
  `-->`
110
113
  ].join("\n");
@@ -2,40 +2,42 @@ import { join as pathJoin } from "path";
2
2
  import { existsAsync } from "./fs.existsAsync";
3
3
  import * as child_process from "child_process";
4
4
  import { assert } from "tsafe/assert";
5
+ import { getIsRootPath } from "../tools/isRootPath";
5
6
 
6
7
  export async function getInstalledModuleDirPath(params: {
7
8
  moduleName: string;
8
9
  packageJsonDirPath: string;
9
- projectDirPath: string;
10
10
  }) {
11
- const { moduleName, packageJsonDirPath, projectDirPath } = params;
11
+ const { moduleName, packageJsonDirPath } = params;
12
12
 
13
- common_case: {
14
- const dirPath = pathJoin(
15
- ...[packageJsonDirPath, "node_modules", ...moduleName.split("/")]
16
- );
13
+ {
14
+ let dirPath = packageJsonDirPath;
17
15
 
18
- if (!(await existsAsync(dirPath))) {
19
- break common_case;
20
- }
16
+ while (true) {
17
+ const dirPath_candidate = pathJoin(
18
+ dirPath,
19
+ "node_modules",
20
+ ...moduleName.split("/")
21
+ );
21
22
 
22
- return dirPath;
23
- }
23
+ let doesExist: boolean;
24
24
 
25
- node_modules_at_root_case: {
26
- if (projectDirPath === packageJsonDirPath) {
27
- break node_modules_at_root_case;
28
- }
25
+ try {
26
+ doesExist = await existsAsync(dirPath_candidate);
27
+ } catch {
28
+ doesExist = false;
29
+ }
29
30
 
30
- const dirPath = pathJoin(
31
- ...[projectDirPath, "node_modules", ...moduleName.split("/")]
32
- );
31
+ if (doesExist) {
32
+ return dirPath_candidate;
33
+ }
33
34
 
34
- if (!(await existsAsync(dirPath))) {
35
- break node_modules_at_root_case;
36
- }
35
+ if (getIsRootPath(dirPath)) {
36
+ break;
37
+ }
37
38
 
38
- return dirPath;
39
+ dirPath = pathJoin(dirPath, "..");
40
+ }
39
41
  }
40
42
 
41
43
  const dirPath = child_process
@@ -0,0 +1,22 @@
1
+ import { normalize as pathNormalize } from "path";
2
+
3
+ export function getIsRootPath(filePath: string): boolean {
4
+ const path_normalized = pathNormalize(filePath);
5
+
6
+ // Unix-like root ("/")
7
+ if (path_normalized === "/") {
8
+ return true;
9
+ }
10
+
11
+ // Check for Windows drive root (e.g., "C:\\")
12
+ if (/^[a-zA-Z]:\\$/.test(path_normalized)) {
13
+ return true;
14
+ }
15
+
16
+ // Check for UNC root (e.g., "\\server\share")
17
+ if (/^\\\\[^\\]+\\[^\\]+\\?$/.test(path_normalized)) {
18
+ return true;
19
+ }
20
+
21
+ return false;
22
+ }
@@ -8,7 +8,6 @@ import { exclude } from "tsafe/exclude";
8
8
 
9
9
  export async function listInstalledModules(params: {
10
10
  packageJsonFilePath: string;
11
- projectDirPath: string;
12
11
  filter: (params: { moduleName: string }) => boolean;
13
12
  }): Promise<
14
13
  {
@@ -18,7 +17,7 @@ export async function listInstalledModules(params: {
18
17
  peerDependencies: Record<string, string>;
19
18
  }[]
20
19
  > {
21
- const { packageJsonFilePath, projectDirPath, filter } = params;
20
+ const { packageJsonFilePath, filter } = params;
22
21
 
23
22
  const parsedPackageJson = await readPackageJsonDependencies({
24
23
  packageJsonFilePath
@@ -36,8 +35,7 @@ export async function listInstalledModules(params: {
36
35
  extensionModuleNames.map(async moduleName => {
37
36
  const dirPath = await getInstalledModuleDirPath({
38
37
  moduleName,
39
- packageJsonDirPath: pathDirname(packageJsonFilePath),
40
- projectDirPath
38
+ packageJsonDirPath: pathDirname(packageJsonFilePath)
41
39
  });
42
40
 
43
41
  const { version, peerDependencies } =
@@ -10,5 +10,5 @@
10
10
  "rootDir": "."
11
11
  },
12
12
  "include": ["**/*.ts", "**/*.tsx"],
13
- "exclude": ["initialize-account-theme/src"]
13
+ "exclude": ["initialize-account-theme/multi-page-boilerplate"]
14
14
  }