@git.zone/cli 2.19.4 → 2.19.6

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 (41) hide show
  1. package/.smartconfig.json +4 -4
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.gitzoneconfig.js +1 -1
  4. package/dist_ts/gitzone.cli.js +2 -2
  5. package/dist_ts/helpers.changelog.js +14 -6
  6. package/dist_ts/mod_commit/mod.helpers.js +5 -3
  7. package/dist_ts/mod_format/classes.diffreporter.js +3 -2
  8. package/dist_ts/mod_format/formatters/copy.formatter.js +3 -2
  9. package/dist_ts/mod_format/formatters/packagejson.formatter.js +3 -2
  10. package/dist_ts/mod_format/formatters/templates.formatter.js +3 -2
  11. package/dist_ts/mod_format/formatters/tsconfig.formatter.js +3 -2
  12. package/dist_ts/mod_meta/meta.classes.meta.js +1 -1
  13. package/dist_ts/mod_open/index.d.ts +1 -1
  14. package/dist_ts/mod_open/index.js +3 -3
  15. package/dist_ts/mod_services/classes.dockercontainer.js +5 -3
  16. package/dist_ts/mod_services/classes.serviceconfiguration.js +1 -1
  17. package/dist_ts/mod_services/classes.servicemanager.js +10 -7
  18. package/dist_ts/mod_template/index.d.ts +1 -1
  19. package/dist_ts/mod_template/index.js +6 -2
  20. package/dist_ts/mod_tools/classes.packagemanager.d.ts +4 -0
  21. package/dist_ts/mod_tools/classes.packagemanager.js +183 -5
  22. package/dist_ts/mod_tools/index.js +23 -14
  23. package/package.json +11 -11
  24. package/ts/00_commitinfo_data.ts +1 -1
  25. package/ts/classes.gitzoneconfig.ts +1 -1
  26. package/ts/gitzone.cli.ts +1 -1
  27. package/ts/helpers.changelog.ts +22 -8
  28. package/ts/mod_commit/mod.helpers.ts +4 -2
  29. package/ts/mod_format/classes.diffreporter.ts +2 -1
  30. package/ts/mod_format/formatters/copy.formatter.ts +2 -1
  31. package/ts/mod_format/formatters/packagejson.formatter.ts +2 -1
  32. package/ts/mod_format/formatters/templates.formatter.ts +2 -1
  33. package/ts/mod_format/formatters/tsconfig.formatter.ts +2 -1
  34. package/ts/mod_meta/meta.classes.meta.ts +1 -1
  35. package/ts/mod_open/index.ts +2 -2
  36. package/ts/mod_services/classes.dockercontainer.ts +5 -3
  37. package/ts/mod_services/classes.serviceconfiguration.ts +2 -2
  38. package/ts/mod_services/classes.servicemanager.ts +9 -6
  39. package/ts/mod_template/index.ts +5 -1
  40. package/ts/mod_tools/classes.packagemanager.ts +251 -4
  41. package/ts/mod_tools/index.ts +31 -13
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@git.zone/cli',
6
- version: '2.19.4',
6
+ version: '2.19.6',
7
7
  description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
8
8
  }
@@ -35,7 +35,7 @@ export class GitzoneConfig {
35
35
  return gitzoneConfig;
36
36
  }
37
37
 
38
- public data: IGitzoneConfigData;
38
+ public data!: IGitzoneConfigData;
39
39
 
40
40
  public async readConfigFromCwd() {
41
41
  const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd);
package/ts/gitzone.cli.ts CHANGED
@@ -105,7 +105,7 @@ export let run = async () => {
105
105
  const rawCliMode = await getRawCliMode();
106
106
 
107
107
  // get packageInfo
108
- const projectInfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
108
+ const projectInfo = await plugins.projectinfo.ProjectInfo.create(paths.packageDir);
109
109
  const projectInfoVersion = (projectInfo.npm as any)?.version;
110
110
  const packageVersion =
111
111
  typeof projectInfoVersion === "string" && projectInfoVersion.length > 0
@@ -19,6 +19,12 @@ export interface IPendingChangelog {
19
19
  isEmpty: boolean;
20
20
  }
21
21
 
22
+ interface IChangelogSection {
23
+ start: number;
24
+ bodyStart: number;
25
+ end: number;
26
+ }
27
+
22
28
  const bucketForCommitType = (commitType: string): TChangelogBucket => {
23
29
  switch (commitType) {
24
30
  case "BREAKING CHANGE":
@@ -48,7 +54,7 @@ const writeChangelog = async (filePath: string, content: string): Promise<void>
48
54
  const findPendingSection = (
49
55
  content: string,
50
56
  sectionName: string,
51
- ): { start: number; bodyStart: number; end: number } | null => {
57
+ ): IChangelogSection | null => {
52
58
  const headingRegex = new RegExp(`^##\\s+${sectionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
53
59
  const match = headingRegex.exec(content);
54
60
  if (!match || match.index === undefined) {
@@ -123,9 +129,11 @@ export const readPendingChangelog = async (
123
129
  filePath: string,
124
130
  sectionName = "Pending",
125
131
  ): Promise<IPendingChangelog> => {
126
- const content = await ensurePendingSection(filePath, sectionName);
127
- const pendingSection = findPendingSection(content, sectionName)!;
128
- const block = content.slice(pendingSection.bodyStart, pendingSection.end).trim();
132
+ const content = await readChangelog(filePath);
133
+ const pendingSection = findPendingSection(content, sectionName);
134
+ const block = pendingSection
135
+ ? content.slice(pendingSection.bodyStart, pendingSection.end).trim()
136
+ : "";
129
137
  return {
130
138
  block,
131
139
  isEmpty: block.length === 0,
@@ -149,8 +157,11 @@ export const movePendingToVersion = async (
149
157
  version: string,
150
158
  dateString: string,
151
159
  ): Promise<void> => {
152
- let content = await ensurePendingSection(filePath, sectionName);
153
- const pendingSection = findPendingSection(content, sectionName)!;
160
+ let content = await readChangelog(filePath);
161
+ const pendingSection = findPendingSection(content, sectionName);
162
+ if (!pendingSection) {
163
+ throw new Error("No pending changelog entries. Nothing to release.");
164
+ }
154
165
  const pendingBlock = content.slice(pendingSection.bodyStart, pendingSection.end).trim();
155
166
  if (!pendingBlock) {
156
167
  throw new Error("No pending changelog entries. Nothing to release.");
@@ -159,7 +170,10 @@ export const movePendingToVersion = async (
159
170
  const renderedHeading = versionHeading
160
171
  .replaceAll("{{version}}", version)
161
172
  .replaceAll("{{date}}", dateString);
162
- const nextContent = content.slice(pendingSection.end).replace(/^\n+/, "");
163
- content = `${content.slice(0, pendingSection.bodyStart)}\n\n${renderedHeading}\n\n${pendingBlock}\n\n${nextContent}`;
173
+ const beforePending = content.slice(0, pendingSection.start).trimEnd();
174
+ const afterPending = content.slice(pendingSection.end).replace(/^\n+/, "").trimEnd();
175
+ content = [beforePending, renderedHeading, pendingBlock, afterPending]
176
+ .filter((block) => block.length > 0)
177
+ .join("\n\n");
164
178
  await writeChangelog(filePath, content);
165
179
  };
@@ -27,7 +27,8 @@ export async function detectCurrentBranch(): Promise<string> {
27
27
  logger.log('info', `Detected current branch: ${branchName}`);
28
28
  return branchName;
29
29
  } catch (error) {
30
- logger.log('warn', `Failed to detect branch: ${error.message}, falling back to "master"`);
30
+ const errorMessage = error instanceof Error ? error.message : String(error);
31
+ logger.log('warn', `Failed to detect branch: ${errorMessage}, falling back to "master"`);
31
32
  return 'master';
32
33
  }
33
34
  }
@@ -225,6 +226,7 @@ export async function bumpProjectVersion(
225
226
 
226
227
  return newVersion;
227
228
  } catch (error) {
228
- throw new Error(`Failed to bump project version: ${error.message}`);
229
+ const errorMessage = error instanceof Error ? error.message : String(error);
230
+ throw new Error(`Failed to bump project version: ${errorMessage}`);
229
231
  }
230
232
  }
@@ -42,9 +42,10 @@ export class DiffReporter {
42
42
  change.content,
43
43
  );
44
44
  } catch (error) {
45
+ const errorMessage = error instanceof Error ? error.message : String(error);
45
46
  logger.log(
46
47
  'error',
47
- `Failed to generate diff for ${change.path}: ${error.message}`,
48
+ `Failed to generate diff for ${change.path}: ${errorMessage}`,
48
49
  );
49
50
  return null;
50
51
  }
@@ -93,7 +93,8 @@ export class CopyFormatter extends BaseFormatter {
93
93
  }
94
94
  }
95
95
  } catch (error) {
96
- logVerbose(`Failed to process pattern ${pattern.from}: ${error.message}`);
96
+ const errorMessage = error instanceof Error ? error.message : String(error);
97
+ logVerbose(`Failed to process pattern ${pattern.from}: ${errorMessage}`);
97
98
  }
98
99
  }
99
100
 
@@ -94,7 +94,8 @@ export class PackageJsonFormatter extends BaseFormatter {
94
94
  packageJson.pnpm = packageJson.pnpm || {};
95
95
  packageJson.pnpm.overrides = overrides;
96
96
  } catch (error) {
97
- logVerbose(`Could not read overrides.json: ${error.message}`);
97
+ const errorMessage = error instanceof Error ? error.message : String(error);
98
+ logVerbose(`Could not read overrides.json: ${errorMessage}`);
98
99
  }
99
100
 
100
101
  const newContent = JSON.stringify(packageJson, null, 2);
@@ -117,7 +117,8 @@ export class TemplatesFormatter extends BaseFormatter {
117
117
  try {
118
118
  renderedFiles = await this.renderTemplate(templateName);
119
119
  } catch (error) {
120
- logVerbose(`Failed to render template ${templateName}: ${error.message}`);
120
+ const errorMessage = error instanceof Error ? error.message : String(error);
121
+ logVerbose(`Failed to render template ${templateName}: ${errorMessage}`);
121
122
  return changes;
122
123
  }
123
124
 
@@ -46,7 +46,8 @@ export class TsconfigFormatter extends BaseFormatter {
46
46
  ];
47
47
  }
48
48
  } catch (error) {
49
- logVerbose(`Could not get tspublish modules: ${error.message}`);
49
+ const errorMessage = error instanceof Error ? error.message : String(error);
50
+ logVerbose(`Could not get tspublish modules: ${errorMessage}`);
50
51
  }
51
52
 
52
53
  tsconfigObject.compilerOptions.paths = { ...existingPaths, ...tspublishPaths };
@@ -26,7 +26,7 @@ export class Meta {
26
26
  /**
27
27
  * the meta repo data
28
28
  */
29
- public metaRepoData: interfaces.IMetaRepoData;
29
+ public metaRepoData!: interfaces.IMetaRepoData;
30
30
  public smartshellInstance = new plugins.smartshell.Smartshell({
31
31
  executor: 'bash',
32
32
  });
@@ -1,8 +1,8 @@
1
1
  import * as plugins from './mod.plugins.js';
2
2
  import * as paths from '../paths.js';
3
3
 
4
- export let run = (argvArg) => {
5
- let projectInfo = new plugins.projectinfo.ProjectInfo(paths.cwd);
4
+ export let run = async (argvArg) => {
5
+ let projectInfo = await plugins.projectinfo.ProjectInfo.create(paths.cwd);
6
6
  if (argvArg._[1] === 'ci') {
7
7
  plugins.smartopen.openUrl(
8
8
  `https://gitlab.com/${projectInfo.git.gituser}/${projectInfo.git.gitrepo}/settings/ci_cd`,
@@ -148,7 +148,8 @@ export class DockerContainer {
148
148
  const result = await this.smartshell.exec(command);
149
149
  return result.exitCode === 0;
150
150
  } catch (error) {
151
- logger.log('error', `Failed to run container: ${error.message}`);
151
+ const errorMessage = error instanceof Error ? error.message : String(error);
152
+ logger.log('error', `Failed to run container: ${errorMessage}`);
152
153
  return false;
153
154
  }
154
155
  }
@@ -177,7 +178,8 @@ export class DockerContainer {
177
178
  const result = await this.smartshell.exec(`docker logs ${tailFlag} ${containerName}`);
178
179
  return result.stdout;
179
180
  } catch (error) {
180
- return `Error getting logs: ${error.message}`;
181
+ const errorMessage = error instanceof Error ? error.message : String(error);
182
+ return `Error getting logs: ${errorMessage}`;
181
183
  }
182
184
  }
183
185
 
@@ -258,4 +260,4 @@ export class DockerContainer {
258
260
  return null;
259
261
  }
260
262
  }
261
- }
263
+ }
@@ -28,7 +28,7 @@ export interface IServiceConfig {
28
28
 
29
29
  export class ServiceConfiguration {
30
30
  private configPath: string;
31
- private config: IServiceConfig;
31
+ private config!: IServiceConfig;
32
32
  private docker: DockerContainer;
33
33
 
34
34
  constructor() {
@@ -515,4 +515,4 @@ export class ServiceConfiguration {
515
515
  logger.log('info', ` 📍 S3 Console: ${s3ConsolePort}`);
516
516
  logger.log('info', ` 📍 Elasticsearch: ${esPort}`);
517
517
  }
518
- }
518
+ }
@@ -61,13 +61,15 @@ export class ServiceManager {
61
61
  default: ['mongodb', 'minio', 'elasticsearch']
62
62
  });
63
63
 
64
- this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
64
+ const enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
65
+ this.enabledServices = enabledServices;
65
66
 
66
67
  // Save to .smartconfig.json
67
- await this.saveServiceConfiguration(this.enabledServices);
68
+ await this.saveServiceConfiguration(enabledServices);
68
69
  } else {
69
- this.enabledServices = gitzoneConfig.services;
70
- logger.log('info', `🔧 Enabled services: ${this.enabledServices.join(', ')}`);
70
+ const enabledServices = gitzoneConfig.services as string[];
71
+ this.enabledServices = enabledServices;
72
+ logger.log('info', `🔧 Enabled services: ${enabledServices.join(', ')}`);
71
73
  }
72
74
  }
73
75
 
@@ -902,10 +904,11 @@ export class ServiceManager {
902
904
  default: currentServices
903
905
  });
904
906
 
905
- this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
907
+ const enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
908
+ this.enabledServices = enabledServices;
906
909
 
907
910
  // Save to .smartconfig.json
908
- await this.saveServiceConfiguration(this.enabledServices);
911
+ await this.saveServiceConfiguration(enabledServices);
909
912
 
910
913
  logger.log('ok', '✅ Service configuration updated');
911
914
  }
@@ -15,7 +15,7 @@ export const isTemplate = async (templateNameArg: string) => {
15
15
  };
16
16
 
17
17
  export const getTemplate = async (templateNameArg: string) => {
18
- if (isTemplate(templateNameArg)) {
18
+ if (await isTemplate(templateNameArg)) {
19
19
  const localScafTemplate = new plugins.smartscaf.ScafTemplate(
20
20
  getTemplatePath(templateNameArg),
21
21
  );
@@ -50,6 +50,10 @@ export const run = async (argvArg: any) => {
50
50
  }
51
51
 
52
52
  const localScafTemplate = await getTemplate(chosenTemplate);
53
+ if (!localScafTemplate) {
54
+ logger.log('error', `Template ${chosenTemplate} not available`);
55
+ return;
56
+ }
53
57
  await localScafTemplate.askCliForMissingVariables();
54
58
  await localScafTemplate.writeToDisk(paths.cwd);
55
59
  };
@@ -70,7 +70,7 @@ export class PackageManagerUtil {
70
70
  }
71
71
 
72
72
  const currentVersion = await this.getCurrentPnpmVersion();
73
- const latestVersion = await this.getLatestVersion("pnpm", [
73
+ const latestVersion = await this.getLatestMatureVersion("pnpm", [
74
74
  "https://registry.npmjs.org",
75
75
  ]);
76
76
 
@@ -371,6 +371,51 @@ export class PackageManagerUtil {
371
371
  return null;
372
372
  }
373
373
 
374
+ public async getLatestMatureVersion(
375
+ packageName: string,
376
+ registries = [
377
+ "https://verdaccio.lossless.digital",
378
+ "https://registry.npmjs.org",
379
+ ],
380
+ ): Promise<string | null> {
381
+ const minimumReleaseAgeMinutes = await this.getMinimumReleaseAgeMinutes();
382
+
383
+ for (const registry of registries) {
384
+ const metadata = await this.getPackageMetadataFromRegistry(
385
+ registry,
386
+ packageName,
387
+ );
388
+ if (!metadata) {
389
+ continue;
390
+ }
391
+
392
+ const latest = getLatestVersionFromMetadata(metadata);
393
+ if (!latest && !minimumReleaseAgeMinutes) {
394
+ continue;
395
+ }
396
+ if (!minimumReleaseAgeMinutes) {
397
+ return latest;
398
+ }
399
+
400
+ const minimumReleaseAgeExcluded = latest
401
+ ? await this.isMinimumReleaseAgeExcluded(packageName, latest)
402
+ : false;
403
+ if (latest && minimumReleaseAgeExcluded) {
404
+ return latest;
405
+ }
406
+
407
+ const matureLatest = getLatestMatureVersionFromMetadata(
408
+ metadata,
409
+ minimumReleaseAgeMinutes,
410
+ );
411
+ if (matureLatest) {
412
+ return matureLatest;
413
+ }
414
+ }
415
+
416
+ return null;
417
+ }
418
+
374
419
  public async installLatest(
375
420
  packageName: string,
376
421
  version = "latest",
@@ -484,6 +529,42 @@ export class PackageManagerUtil {
484
529
  return this.shell.execSilent(`${pnpmCommand} ${commandArgs}`);
485
530
  }
486
531
 
532
+ private async getMinimumReleaseAgeMinutes(): Promise<number> {
533
+ try {
534
+ const result = await this.execPnpmSilent(
535
+ "config get minimum-release-age 2>/dev/null",
536
+ );
537
+ const rawValue = result?.stdout.trim() || "";
538
+ const value = Number.parseInt(rawValue, 10);
539
+ if (Number.isFinite(value) && value >= 0) {
540
+ return value;
541
+ }
542
+
543
+ const currentVersion = await this.getCurrentPnpmVersion();
544
+ return getDefaultMinimumReleaseAgeMinutes(currentVersion);
545
+ } catch {
546
+ const currentVersion = await this.getCurrentPnpmVersion();
547
+ return getDefaultMinimumReleaseAgeMinutes(currentVersion);
548
+ }
549
+ }
550
+
551
+ private async isMinimumReleaseAgeExcluded(
552
+ packageName: string,
553
+ version: string,
554
+ ): Promise<boolean> {
555
+ try {
556
+ const result = await this.execPnpmSilent(
557
+ "config get minimum-release-age-exclude 2>/dev/null",
558
+ );
559
+ const rawPatterns = parsePnpmConfigStringList(result?.stdout || "");
560
+ return rawPatterns.some((pattern) =>
561
+ packageSelectorMatches(packageName, version, pattern),
562
+ );
563
+ } catch {
564
+ return false;
565
+ }
566
+ }
567
+
487
568
  private async getPnpmListProjects(): Promise<IPnpmListProject[]> {
488
569
  try {
489
570
  const result = await this.execPnpmSilent(
@@ -710,6 +791,17 @@ export class PackageManagerUtil {
710
791
  registry: string,
711
792
  packageName: string,
712
793
  ): Promise<string | null> {
794
+ const data = await this.getPackageMetadataFromRegistry(
795
+ registry,
796
+ packageName,
797
+ );
798
+ return data ? getLatestVersionFromMetadata(data) : null;
799
+ }
800
+
801
+ private async getPackageMetadataFromRegistry(
802
+ registry: string,
803
+ packageName: string,
804
+ ): Promise<any | null> {
713
805
  const encodedName = packageName.replace("/", "%2f");
714
806
  const controller = new AbortController();
715
807
  const timeout = setTimeout(() => controller.abort(), 8000);
@@ -724,9 +816,7 @@ export class PackageManagerUtil {
724
816
  if (!response.ok) {
725
817
  return null;
726
818
  }
727
- const data = await response.json();
728
- const latest = (data as any)["dist-tags"]?.latest;
729
- return typeof latest === "string" && latest.length > 0 ? latest : null;
819
+ return await response.json();
730
820
  } catch {
731
821
  return null;
732
822
  } finally {
@@ -767,6 +857,163 @@ function getDependencyPackagePath(info: any): string | undefined {
767
857
  : undefined;
768
858
  }
769
859
 
860
+ function getLatestVersionFromMetadata(metadata: any): string | null {
861
+ const latest = metadata?.["dist-tags"]?.latest;
862
+ return typeof latest === "string" && latest.length > 0 ? latest : null;
863
+ }
864
+
865
+ function getDefaultMinimumReleaseAgeMinutes(currentPnpmVersion: string): number {
866
+ const majorVersion = normalizeSemver(currentPnpmVersion)[0] || 0;
867
+ return majorVersion >= 11 ? 1440 : 0;
868
+ }
869
+
870
+ function getLatestMatureVersionFromMetadata(
871
+ metadata: any,
872
+ minimumReleaseAgeMinutes: number,
873
+ ): string | null {
874
+ const versions = metadata?.versions;
875
+ const versionTimes = metadata?.time;
876
+ if (
877
+ !versions ||
878
+ typeof versions !== "object" ||
879
+ !versionTimes ||
880
+ typeof versionTimes !== "object"
881
+ ) {
882
+ return null;
883
+ }
884
+
885
+ const cutoffTimestamp = Date.now() - minimumReleaseAgeMinutes * 60 * 1000;
886
+ return Object.keys(versions)
887
+ .filter((version) => /^\d+\.\d+\.\d+$/.test(version))
888
+ .filter((version) => {
889
+ const publishedAt = Date.parse(versionTimes[version]);
890
+ return Number.isFinite(publishedAt) && publishedAt <= cutoffTimestamp;
891
+ })
892
+ .sort(compareSemverDescending)[0] || null;
893
+ }
894
+
895
+ function compareSemverDescending(versionA: string, versionB: string): number {
896
+ const versionAParts = normalizeSemver(versionA);
897
+ const versionBParts = normalizeSemver(versionB);
898
+
899
+ for (
900
+ let i = 0;
901
+ i < Math.max(versionAParts.length, versionBParts.length);
902
+ i++
903
+ ) {
904
+ const versionAPart = versionAParts[i] || 0;
905
+ const versionBPart = versionBParts[i] || 0;
906
+ if (versionAPart !== versionBPart) {
907
+ return versionBPart - versionAPart;
908
+ }
909
+ }
910
+
911
+ return 0;
912
+ }
913
+
914
+ function parsePnpmConfigStringList(rawValue: string): string[] {
915
+ const trimmedValue = rawValue.trim();
916
+ if (
917
+ !trimmedValue ||
918
+ trimmedValue === "undefined" ||
919
+ trimmedValue === "null"
920
+ ) {
921
+ return [];
922
+ }
923
+
924
+ try {
925
+ const parsedValue = JSON.parse(trimmedValue);
926
+ if (Array.isArray(parsedValue)) {
927
+ return parsedValue
928
+ .filter((item) => typeof item === "string")
929
+ .map((item) => normalizePnpmConfigListItem(item))
930
+ .filter((item) => item.length > 0);
931
+ }
932
+ if (typeof parsedValue === "string") {
933
+ return [normalizePnpmConfigListItem(parsedValue)].filter(
934
+ (item) => item.length > 0,
935
+ );
936
+ }
937
+ } catch {
938
+ // pnpm commonly prints string arrays as comma-delimited text.
939
+ }
940
+
941
+ return trimmedValue
942
+ .split(/[\n,]/)
943
+ .map((item) => normalizePnpmConfigListItem(item))
944
+ .filter((item) => item.length > 0);
945
+ }
946
+
947
+ function normalizePnpmConfigListItem(rawItem: string): string {
948
+ return rawItem
949
+ .trim()
950
+ .replace(/^-\s*/, "")
951
+ .replace(/^\[/, "")
952
+ .replace(/\]$/, "")
953
+ .trim()
954
+ .replace(/^['"]/, "")
955
+ .replace(/['"]$/, "")
956
+ .trim();
957
+ }
958
+
959
+ function packageSelectorMatches(
960
+ packageName: string,
961
+ version: string,
962
+ pattern: string,
963
+ ): boolean {
964
+ const selectorParts = splitPackageSelector(pattern);
965
+ if (!globPatternMatches(packageName, selectorParts.packagePattern)) {
966
+ return false;
967
+ }
968
+ if (!selectorParts.versionSelector) {
969
+ return true;
970
+ }
971
+ return selectorParts.versionSelector
972
+ .split("||")
973
+ .map((versionSelector) => versionSelector.trim())
974
+ .some((versionSelector) => {
975
+ const packageVersionPrefix = `${packageName}@`;
976
+ const normalizedVersionSelector = versionSelector.startsWith(
977
+ packageVersionPrefix,
978
+ )
979
+ ? versionSelector.slice(packageVersionPrefix.length)
980
+ : versionSelector;
981
+ return normalizedVersionSelector === version;
982
+ });
983
+ }
984
+
985
+ function splitPackageSelector(pattern: string): {
986
+ packagePattern: string;
987
+ versionSelector: string | null;
988
+ } {
989
+ const versionSeparatorIndex = pattern.startsWith("@")
990
+ ? pattern.indexOf("@", 1)
991
+ : pattern.indexOf("@");
992
+ if (versionSeparatorIndex <= 0) {
993
+ return {
994
+ packagePattern: pattern,
995
+ versionSelector: null,
996
+ };
997
+ }
998
+
999
+ return {
1000
+ packagePattern: pattern.slice(0, versionSeparatorIndex),
1001
+ versionSelector: pattern.slice(versionSeparatorIndex + 1).trim() || null,
1002
+ };
1003
+ }
1004
+
1005
+ function globPatternMatches(value: string, pattern: string): boolean {
1006
+ const escapedPattern = pattern
1007
+ .split("*")
1008
+ .map((patternPart) => escapeRegExp(patternPart))
1009
+ .join(".*");
1010
+ return new RegExp(`^${escapedPattern}$`).test(value);
1011
+ }
1012
+
1013
+ function escapeRegExp(value: string): string {
1014
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1015
+ }
1016
+
770
1017
  function getPackagePath(globalDir: string, packageName: string): string {
771
1018
  return plugins.path.join(
772
1019
  globalDir,