syncpack 12.4.0 → 13.0.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 (101) hide show
  1. package/README.md +3 -0
  2. package/dist/bin-fix-mismatches/fix-mismatches.d.ts +2 -2
  3. package/dist/bin-fix-mismatches/fix-mismatches.js +13 -8
  4. package/dist/bin-fix-mismatches/index.js +1 -32
  5. package/dist/bin-format/format.d.ts +2 -2
  6. package/dist/bin-format/format.js +7 -5
  7. package/dist/bin-format/index.js +1 -26
  8. package/dist/bin-lint/index.js +1 -22
  9. package/dist/bin-lint/lint.d.ts +1 -1
  10. package/dist/bin-lint/lint.js +23 -23
  11. package/dist/bin-lint-semver-ranges/index.js +1 -36
  12. package/dist/bin-lint-semver-ranges/lint-semver-ranges.d.ts +2 -2
  13. package/dist/bin-lint-semver-ranges/lint-semver-ranges.js +5 -4
  14. package/dist/bin-list/index.js +1 -31
  15. package/dist/bin-list/list.d.ts +1 -1
  16. package/dist/bin-list/list.js +9 -5
  17. package/dist/bin-list-mismatches/index.js +1 -31
  18. package/dist/bin-list-mismatches/list-mismatches.d.ts +2 -2
  19. package/dist/bin-list-mismatches/list-mismatches.js +15 -9
  20. package/dist/bin-prompt/index.js +1 -33
  21. package/dist/bin-prompt/prompt.d.ts +2 -2
  22. package/dist/bin-prompt/prompt.js +14 -12
  23. package/dist/bin-set-semver-ranges/index.js +1 -38
  24. package/dist/bin-set-semver-ranges/set-semver-ranges.d.ts +2 -2
  25. package/dist/bin-set-semver-ranges/set-semver-ranges.js +8 -5
  26. package/dist/bin-update/effects.js +43 -28
  27. package/dist/bin-update/index.js +1 -31
  28. package/dist/bin-update/update.d.ts +1 -1
  29. package/dist/bin-update/update.js +4 -4
  30. package/dist/bin.js +3 -3
  31. package/dist/config/get-custom-types.js +20 -13
  32. package/dist/config/get-enabled-types.js +27 -12
  33. package/dist/config/get-sort-exports.js +2 -1
  34. package/dist/config/types.d.ts +3 -1
  35. package/dist/error-handlers/default-error-handlers.js +2 -2
  36. package/dist/get-context/index.d.ts +2 -2
  37. package/dist/get-context/index.js +1 -1
  38. package/dist/get-instances/index.js +6 -6
  39. package/dist/get-instances/instance.js +6 -2
  40. package/dist/get-package-json-files/get-patterns/get-lerna-patterns.js +4 -2
  41. package/dist/get-package-json-files/get-patterns/get-pnpm-patterns.d.ts +1 -1
  42. package/dist/get-package-json-files/get-patterns/get-pnpm-patterns.js +2 -2
  43. package/dist/get-package-json-files/get-patterns/get-yarn-patterns.js +2 -2
  44. package/dist/get-package-json-files/get-patterns/index.js +3 -3
  45. package/dist/get-package-json-files/index.js +1 -1
  46. package/dist/get-package-json-files/package-json-file.d.ts +7 -0
  47. package/dist/get-package-json-files/package-json-file.js +18 -3
  48. package/dist/guards/can-add-to-group.js +15 -9
  49. package/dist/guards/is-semver.js +3 -1
  50. package/dist/io/ask-for-choice.js +3 -3
  51. package/dist/io/ask-for-input.js +2 -2
  52. package/dist/io/exit-if-invalid.js +1 -1
  53. package/dist/io/glob-sync.js +1 -1
  54. package/dist/io/index.d.ts +1 -1
  55. package/dist/io/index.js +1 -1
  56. package/dist/io/read-config-file.js +5 -3
  57. package/dist/io/read-file-sync.js +1 -1
  58. package/dist/io/read-json-file-sync.d.ts +2 -1
  59. package/dist/io/read-json-file-sync.js +9 -5
  60. package/dist/io/read-yaml-file-sync.js +1 -1
  61. package/dist/io/{to-json.d.ts → to-formatted-json.d.ts} +1 -1
  62. package/dist/io/{to-json.js → to-formatted-json.js} +11 -8
  63. package/dist/io/write-file-sync.js +2 -2
  64. package/dist/io/write-if-changed.js +3 -4
  65. package/dist/lib/format-repository-url.js +8 -5
  66. package/dist/lib/get-group-header.js +3 -1
  67. package/dist/lib/get.js +20 -12
  68. package/dist/lib/ring-buffer.js +1 -1
  69. package/dist/lib/set-semver-range.js +8 -4
  70. package/dist/lib/with-logger.js +1 -2
  71. package/dist/option.js +4 -1
  72. package/dist/schema.json +75 -5
  73. package/dist/semver-group/create-semver-groups.js +7 -3
  74. package/dist/semver-group/disabled.js +1 -1
  75. package/dist/semver-group/filtered-out.js +1 -1
  76. package/dist/semver-group/ignored.js +1 -1
  77. package/dist/semver-group/with-range.js +3 -3
  78. package/dist/specifier/alias.js +3 -2
  79. package/dist/specifier/exact.js +1 -1
  80. package/dist/specifier/hosted-git.js +5 -3
  81. package/dist/specifier/index.js +27 -15
  82. package/dist/specifier/latest.js +1 -1
  83. package/dist/specifier/lib/parse-specifier.js +3 -1
  84. package/dist/specifier/range.js +1 -1
  85. package/dist/specifier/workspace-protocol.js +2 -1
  86. package/dist/strategy/lib/get-non-empty-string-prop.js +1 -1
  87. package/dist/strategy/name-and-version-props.js +7 -7
  88. package/dist/strategy/named-version-string.js +11 -8
  89. package/dist/strategy/unnamed-version-string.js +6 -6
  90. package/dist/strategy/versions-by-name.js +4 -2
  91. package/dist/version-group/banned.js +1 -1
  92. package/dist/version-group/create-version-groups.js +11 -7
  93. package/dist/version-group/filtered-out.js +1 -1
  94. package/dist/version-group/ignored.js +1 -1
  95. package/dist/version-group/lib/get-preferred-version.js +22 -13
  96. package/dist/version-group/lib/get-range-score.js +3 -1
  97. package/dist/version-group/pinned.js +2 -2
  98. package/dist/version-group/same-range.js +6 -6
  99. package/dist/version-group/snapped-to.js +7 -5
  100. package/dist/version-group/standard.js +19 -16
  101. package/package.json +33 -35
@@ -95,7 +95,9 @@ export interface RcConfig {
95
95
  /** @see https://jamiemason.github.io/syncpack/integrations/json-schema */
96
96
  $schema?: string;
97
97
  /** @see https://jamiemason.github.io/syncpack/config/custom-types */
98
- customTypes: Record<string, CustomTypeConfig.Any>;
98
+ customTypes: {
99
+ [name: string]: CustomTypeConfig.Any;
100
+ };
99
101
  /** @see https://jamiemason.github.io/syncpack/config/dependency-types */
100
102
  dependencyTypes: DependencyType[];
101
103
  /** @see https://jamiemason.github.io/syncpack/config/filter */
@@ -1,6 +1,6 @@
1
+ import { EOL } from 'node:os';
1
2
  import chalk from 'chalk';
2
3
  import { Effect } from 'effect';
3
- import { EOL } from 'os';
4
4
  export const defaultErrorHandlers = {
5
5
  // getContext
6
6
  GlobError(err) {
@@ -13,7 +13,7 @@ export const defaultErrorHandlers = {
13
13
  return Effect.logError([
14
14
  chalk.red('An error was found when parsing a JSON file'),
15
15
  chalk.red(' File:', err.filePath),
16
- chalk.red(' Error:', err.error),
16
+ chalk.red(' Error:', err.errors),
17
17
  ].join(EOL));
18
18
  },
19
19
  NoSourcesFoundError(err) {
@@ -1,6 +1,6 @@
1
1
  import { Effect } from 'effect';
2
2
  import type { O } from 'ts-toolbelt';
3
- import { type CliConfig, type RcConfig } from '../config/types.js';
3
+ import type { CliConfig, RcConfig } from '../config/types.js';
4
4
  import type { ErrorHandlers } from '../error-handlers/default-error-handlers.js';
5
5
  import type { PackageJsonFile } from '../get-package-json-files/package-json-file.js';
6
6
  import type { Io } from '../io/index.js';
@@ -19,5 +19,5 @@ interface Input {
19
19
  cli: Partial<CliConfig>;
20
20
  errorHandlers: ErrorHandlers;
21
21
  }
22
- export declare function getContext({ io, cli, errorHandlers }: Input): Effect.Effect<Ctx>;
22
+ export declare function getContext({ io, cli, errorHandlers, }: Input): Effect.Effect<Ctx>;
23
23
  export {};
@@ -2,7 +2,7 @@ import { Effect, flow, pipe } from 'effect';
2
2
  import { getPackageJsonFiles } from '../get-package-json-files/index.js';
3
3
  import { readConfigFile } from '../io/read-config-file.js';
4
4
  import { keyBy } from './lib/key-by.js';
5
- export function getContext({ io, cli, errorHandlers }) {
5
+ export function getContext({ io, cli, errorHandlers, }) {
6
6
  const exitOnError = Effect.flatMap(() => Effect.failSync(() => io.process.exit(1)));
7
7
  return pipe(Effect.Do, Effect.bind('rcFile', () => readConfigFile(io, cli.configPath)), Effect.bind('packageJsonFiles', ({ rcFile }) => getPackageJsonFiles(io, { cli, rcFile })), Effect.map(({ rcFile, packageJsonFiles }) => ({
8
8
  config: { cli, rcFile },
@@ -6,22 +6,22 @@ import { createSemverGroups } from '../semver-group/create-semver-groups.js';
6
6
  import { createVersionGroups } from '../version-group/create-version-groups.js';
7
7
  export function getInstances(ctx, io, errorHandlers) {
8
8
  const exitOnError = Effect.flatMap(() => Effect.failSync(() => io.process.exit(1)));
9
- return pipe(Effect.Do, Effect.bind('enabledTypes', () => getEnabledTypes(ctx.config)), Effect.bind('semverGroups', () => createSemverGroups(ctx)), Effect.bind('versionGroups', () => createVersionGroups(ctx)), Effect.bind('instances', (acc) => pipe(ctx.packageJsonFiles, Effect.forEach((file) => file.getInstances(acc.enabledTypes)), Effect.map((instancesByFile) => instancesByFile.flat()))), Effect.tap(({ instances, semverGroups, versionGroups }) => Effect.sync(() => {
9
+ return pipe(Effect.Do, Effect.bind('enabledTypes', () => getEnabledTypes(ctx.config)), Effect.bind('semverGroups', () => createSemverGroups(ctx)), Effect.bind('versionGroups', () => createVersionGroups(ctx)), Effect.bind('instances', acc => pipe(ctx.packageJsonFiles, Effect.forEach(file => file.getInstances(acc.enabledTypes)), Effect.map(instancesByFile => instancesByFile.flat()))), Effect.tap(({ instances, semverGroups, versionGroups }) => Effect.sync(() => {
10
10
  for (const instance of instances) {
11
11
  // assign each instance to its semver group, first match wins
12
- assignToSemverGroup: for (const group of semverGroups) {
12
+ for (const group of semverGroups) {
13
13
  if (canAddToGroup(ctx.packageJsonFilesByName, group, instance)) {
14
14
  instance.semverGroup = group;
15
15
  group.instances.push(instance);
16
- break assignToSemverGroup;
16
+ break;
17
17
  }
18
18
  }
19
19
  // assign each instance to its version group, first match wins
20
- assignToVersionGroup: for (const group of versionGroups) {
20
+ for (const group of versionGroups) {
21
21
  if (canAddToGroup(ctx.packageJsonFilesByName, group, instance)) {
22
22
  instance.versionGroup = group;
23
23
  group.instances.push(instance);
24
- break assignToVersionGroup;
24
+ break;
25
25
  }
26
26
  }
27
27
  }
@@ -37,6 +37,6 @@ export function getInstances(ctx, io, errorHandlers) {
37
37
  VersionGroupConfigError: flow(errorHandlers.VersionGroupConfigError, exitOnError),
38
38
  }));
39
39
  function getSortedAndFiltered(groups) {
40
- return groups.filter((group) => group.instances.sort(sortByName).length > 0);
40
+ return groups.filter(group => group.instances.sort(sortByName).length > 0);
41
41
  }
42
42
  }
@@ -16,7 +16,8 @@ export class Instance {
16
16
  versionGroup;
17
17
  constructor(name, rawSpecifier, packageJsonFile, strategy) {
18
18
  this.name = name;
19
- this.pkgName = packageJsonFile.jsonFile.contents.name || 'PACKAGE_JSON_HAS_NO_NAME';
19
+ this.pkgName =
20
+ packageJsonFile.jsonFile.contents.name || 'PACKAGE_JSON_HAS_NO_NAME';
20
21
  this.packageJsonFile = packageJsonFile;
21
22
  this.strategy = strategy;
22
23
  this.semverGroup = null;
@@ -26,6 +27,9 @@ export class Instance {
26
27
  /** Mutate the package.json file in memory with the latest version specifier */
27
28
  write(rawSpecifier) {
28
29
  this.rawSpecifier = Specifier.create(this, rawSpecifier);
29
- return this.strategy.write(this.packageJsonFile, [this.name, this.rawSpecifier.raw]);
30
+ return this.strategy.write(this.packageJsonFile, [
31
+ this.name,
32
+ this.rawSpecifier.raw,
33
+ ]);
30
34
  }
31
35
  }
@@ -1,9 +1,11 @@
1
+ import { join } from 'node:path';
1
2
  import { Effect, Option as O, pipe } from 'effect';
2
- import { join } from 'path';
3
3
  import { isArrayOfStrings } from 'tightrope/guard/is-array-of-strings.js';
4
4
  import { readJsonFileSync } from '../../io/read-json-file-sync.js';
5
5
  export function getLernaPatterns(io) {
6
- return pipe(readJsonFileSync(io, join(io.process.cwd(), 'lerna.json')), Effect.map((file) => isArrayOfStrings(file.contents.packages) ? O.some(file.contents.packages) : O.none()), Effect.catchTags({
6
+ return pipe(readJsonFileSync(io, join(io.process.cwd(), 'lerna.json')), Effect.map(file => isArrayOfStrings(file.contents.packages)
7
+ ? O.some(file.contents.packages)
8
+ : O.none()), Effect.catchTags({
7
9
  ReadFileError: () => Effect.succeed(O.none()),
8
10
  JsonParseError: () => Effect.succeed(O.none()),
9
11
  }));
@@ -1,3 +1,3 @@
1
1
  import { Effect, Option as O } from 'effect';
2
- import { type Io } from '../../io/index.js';
2
+ import type { Io } from '../../io/index.js';
3
3
  export declare function getPnpmPatterns(io: Io): Effect.Effect<O.Option<string[]>>;
@@ -1,5 +1,5 @@
1
+ import { join } from 'node:path';
1
2
  import { Effect, Option as O, pipe } from 'effect';
2
- import { join } from 'path';
3
3
  import { isArrayOfStrings } from 'tightrope/guard/is-array-of-strings.js';
4
4
  import { readYamlFileSync } from '../../io/read-yaml-file-sync.js';
5
5
  export function getPnpmPatterns(io) {
@@ -8,7 +8,7 @@ export function getPnpmPatterns(io) {
8
8
  // - "packages/**"
9
9
  // - "components/**"
10
10
  // - "!**/test/**"
11
- readYamlFileSync(io, join(io.process.cwd(), 'pnpm-workspace.yaml')), Effect.map((file) => (isArrayOfStrings(file?.packages) ? O.some(file.packages) : O.none())), Effect.catchTags({
11
+ readYamlFileSync(io, join(io.process.cwd(), 'pnpm-workspace.yaml')), Effect.map(file => isArrayOfStrings(file?.packages) ? O.some(file.packages) : O.none()), Effect.catchTags({
12
12
  ReadYamlFileError: () => Effect.succeed(O.none()),
13
13
  }));
14
14
  }
@@ -1,10 +1,10 @@
1
+ import { join } from 'node:path';
1
2
  import { Effect, Option as O, pipe } from 'effect';
2
- import { join } from 'path';
3
3
  import { isArrayOfStrings } from 'tightrope/guard/is-array-of-strings.js';
4
4
  import { isNonEmptyObject } from 'tightrope/guard/is-non-empty-object.js';
5
5
  import { readJsonFileSync } from '../../io/read-json-file-sync.js';
6
6
  export function getYarnPatterns(io) {
7
- return pipe(readJsonFileSync(io, join(io.process.cwd(), 'package.json')), Effect.map((file) => isNonEmptyObject(file.contents.workspaces) &&
7
+ return pipe(readJsonFileSync(io, join(io.process.cwd(), 'package.json')), Effect.map(file => isNonEmptyObject(file.contents.workspaces) &&
8
8
  isArrayOfStrings(file.contents.workspaces.packages)
9
9
  ? O.some(file.contents.workspaces.packages)
10
10
  : isArrayOfStrings(file.contents.workspaces)
@@ -10,14 +10,14 @@ import { getYarnPatterns } from './get-yarn-patterns.js';
10
10
  * this monorepo.
11
11
  */
12
12
  export function getPatterns(io, config) {
13
- return pipe(getCliPatterns(), Effect.flatMap((opt) => (O.isSome(opt) ? Effect.succeed(opt) : getWorkspacePatterns())), Effect.map(O.map(limitToPackageJson)), Effect.map(O.getOrElse(() => [...DEFAULT_CONFIG.source])));
13
+ return pipe(getCliPatterns(), Effect.flatMap(opt => O.isSome(opt) ? Effect.succeed(opt) : getWorkspacePatterns()), Effect.map(O.map(limitToPackageJson)), Effect.map(O.getOrElse(() => [...DEFAULT_CONFIG.source])));
14
14
  function getCliPatterns() {
15
15
  return pipe(O.some(getSource(config)), O.filter(isArrayOfStrings), Effect.succeed);
16
16
  }
17
17
  function getWorkspacePatterns() {
18
- return pipe(getYarnPatterns(io), Effect.flatMap((opt) => (O.isSome(opt) ? Effect.succeed(opt) : getPnpmPatterns(io))), Effect.flatMap((opt) => (O.isSome(opt) ? Effect.succeed(opt) : getLernaPatterns(io))), Effect.map(O.map((patterns) => ['package.json', ...patterns])));
18
+ return pipe(getYarnPatterns(io), Effect.flatMap(opt => O.isSome(opt) ? Effect.succeed(opt) : getPnpmPatterns(io)), Effect.flatMap(opt => O.isSome(opt) ? Effect.succeed(opt) : getLernaPatterns(io)), Effect.map(O.map(patterns => ['package.json', ...patterns])));
19
19
  }
20
20
  function limitToPackageJson(patterns) {
21
- return patterns.map((pattern) => pattern.includes('package.json') ? pattern : `${pattern}/package.json`);
21
+ return patterns.map(pattern => pattern.includes('package.json') ? pattern : `${pattern}/package.json`);
22
22
  }
23
23
  }
@@ -4,5 +4,5 @@ import { getFilePaths } from './get-file-paths.js';
4
4
  import { PackageJsonFile } from './package-json-file.js';
5
5
  /** Create an API for every package.json file needed. */
6
6
  export function getPackageJsonFiles(io, config) {
7
- return pipe(getFilePaths(io, config), Effect.flatMap((filePaths) => Effect.all(filePaths.map((filePath) => readJsonFileSync(io, filePath)))), Effect.map((files) => files.map((file) => new PackageJsonFile(file, config))), Effect.tap((files) => Effect.logDebug(`${files.length} package.json files found`)));
7
+ return pipe(getFilePaths(io, config), Effect.flatMap(filePaths => Effect.all(filePaths.map(filePath => readJsonFileSync(io, filePath)))), Effect.map(files => files.map(file => new PackageJsonFile(file, config))), Effect.tap(files => Effect.logDebug(`${files.length} package.json files found`)));
8
8
  }
@@ -42,6 +42,13 @@ export declare class PackageJsonFile {
42
42
  jsonFile: JsonFile<PackageJson>;
43
43
  /** the .name property from the package.json file */
44
44
  name: string | undefined;
45
+ /** the next package.json file contents after modification, with formatting preserved */
46
+ nextJson: string;
45
47
  constructor(jsonFile: JsonFile<PackageJson>, config: Ctx['config']);
46
48
  getInstances(enabledTypes: Strategy.Any[]): Effect.Effect<Instance[]>;
49
+ /**
50
+ * Apply an edit to the raw JSON string which will be written to disk. This string preserves the
51
+ * original formatting of the file.
52
+ */
53
+ applyEdit(fullPath: string[], value: string | undefined): void;
47
54
  }
@@ -1,4 +1,5 @@
1
1
  import { Effect, pipe } from 'effect';
2
+ import { applyEdits, modify } from 'jsonc-parser';
2
3
  import { Instance } from '../get-instances/instance.js';
3
4
  export class PackageJsonFile {
4
5
  /** resolved configuration */
@@ -9,21 +10,35 @@ export class PackageJsonFile {
9
10
  jsonFile;
10
11
  /** the .name property from the package.json file */
11
12
  name;
13
+ /** the next package.json file contents after modification, with formatting preserved */
14
+ nextJson;
12
15
  constructor(jsonFile, config) {
13
16
  this._instances = null;
14
17
  this.config = config;
15
18
  this.jsonFile = jsonFile;
16
19
  this.name = jsonFile.contents.name;
20
+ this.nextJson = jsonFile.json;
17
21
  }
18
22
  getInstances(enabledTypes) {
19
23
  if (!this._instances) {
20
- return pipe(Effect.all(enabledTypes.map((strategy) => pipe(strategy.read(this), Effect.map((entries) => entries.map(([name, rawSpecifier]) => new Instance(name, rawSpecifier, this, strategy)))))), Effect.map((array) => array.flat()), Effect.tapBoth({
21
- onSuccess: (instances) => Effect.logDebug(`found ${instances.length} instances in <${this.jsonFile.shortPath}>`),
24
+ return pipe(Effect.all(enabledTypes.map(strategy => pipe(strategy.read(this), Effect.map(entries => entries.map(([name, rawSpecifier]) => new Instance(name, rawSpecifier, this, strategy)))))), Effect.map(array => array.flat()), Effect.tapBoth({
25
+ onSuccess: instances => Effect.logDebug(`found ${instances.length} instances in <${this.jsonFile.shortPath}>`),
22
26
  onFailure: () => Effect.logError(`failed to get instances from <${this.jsonFile.shortPath}>`),
23
- }), Effect.catchAll(() => Effect.succeed([])), Effect.tap((instances) => Effect.sync(() => {
27
+ }), Effect.catchAll(() => Effect.succeed([])), Effect.tap(instances => Effect.sync(() => {
24
28
  this._instances = instances;
25
29
  })));
26
30
  }
27
31
  return Effect.succeed(this._instances);
28
32
  }
33
+ /**
34
+ * Apply an edit to the raw JSON string which will be written to disk. This string preserves the
35
+ * original formatting of the file.
36
+ */
37
+ applyEdit(fullPath, value) {
38
+ const edits = modify(this.nextJson, fullPath.map(parseNumericStrings), value, {});
39
+ this.nextJson = applyEdits(this.nextJson, edits);
40
+ }
41
+ }
42
+ function parseNumericStrings(key) {
43
+ return /[^0-9]/.test(key) ? key : Number(key);
29
44
  }
@@ -10,12 +10,15 @@ export function canAddToGroup(packageJsonFilesByName, group, instance) {
10
10
  }
11
11
  function matchesDependencies(packageJsonFilesByName, group, dependencies, instance) {
12
12
  // matches if not defined
13
- if (!isNonEmptyArray(dependencies))
13
+ if (!isNonEmptyArray(dependencies)) {
14
14
  return true;
15
- return dependencies.some((pattern) => (pattern === '$LOCAL' &&
15
+ }
16
+ return dependencies.some(pattern => (pattern === '$LOCAL' &&
16
17
  instance.name in packageJsonFilesByName &&
17
- ((group.groupType === 'versionGroup' && instance.versionGroup === null) ||
18
- (group.groupType === 'semverGroup' && instance.semverGroup === null))) ||
18
+ ((group.groupType === 'versionGroup' &&
19
+ instance.versionGroup === null) ||
20
+ (group.groupType === 'semverGroup' &&
21
+ instance.semverGroup === null))) ||
19
22
  minimatch(instance.name, pattern));
20
23
  }
21
24
  function matchesPackages(packages, instance) {
@@ -29,13 +32,15 @@ function matchesSpecifierTypes(specifierTypes, instance) {
29
32
  }
30
33
  function matchesKnownList(values, value) {
31
34
  // matches if not defined
32
- if (!isNonEmptyArray(values))
35
+ if (!isNonEmptyArray(values)) {
33
36
  return true;
34
- if (values.join('') === '**')
37
+ }
38
+ if (values.join('') === '**') {
35
39
  return true;
40
+ }
36
41
  const negative = [];
37
42
  const positive = [];
38
- values.forEach((name) => {
43
+ values.forEach(name => {
39
44
  if (name.startsWith('!')) {
40
45
  negative.push(name.replace('!', ''));
41
46
  }
@@ -43,10 +48,11 @@ function matchesKnownList(values, value) {
43
48
  positive.push(name);
44
49
  }
45
50
  });
46
- if (isNonEmptyArray(negative) && !someMinimatch(value, negative))
51
+ if (isNonEmptyArray(negative) && !someMinimatch(value, negative)) {
47
52
  return true;
53
+ }
48
54
  return isNonEmptyArray(positive) && someMinimatch(value, positive);
49
55
  }
50
56
  function someMinimatch(value, patterns) {
51
- return patterns.some((pattern) => minimatch(value, pattern));
57
+ return patterns.some(pattern => minimatch(value, pattern));
52
58
  }
@@ -9,5 +9,7 @@ export function isSemver(version) {
9
9
  const minor = new RegExp(`^${range}${ints}${dot}${intsOrX}$`);
10
10
  const patch = new RegExp(`^${range}${ints}${dot}${intsOrX}${dot}${intsOrX}$`);
11
11
  return (isString(version) &&
12
- (version.search(major) !== -1 || version.search(minor) !== -1 || version.search(patch) !== -1));
12
+ (version.search(major) !== -1 ||
13
+ version.search(minor) !== -1 ||
14
+ version.search(patch) !== -1));
13
15
  }
@@ -3,7 +3,7 @@ import { IoTag } from './index.js';
3
3
  class AskForChoiceError extends Data.TaggedClass('AskForChoiceError') {
4
4
  }
5
5
  export function askForChoice(opts) {
6
- return pipe(IoTag, Effect.flatMap((io) => Effect.tryPromise({
6
+ return pipe(IoTag, Effect.flatMap(io => Effect.tryPromise({
7
7
  try: () => io.enquirer
8
8
  .prompt({
9
9
  type: 'select',
@@ -11,7 +11,7 @@ export function askForChoice(opts) {
11
11
  message: opts.message,
12
12
  choices: opts.choices,
13
13
  })
14
- .then((res) => res.choice),
15
- catch: (err) => new AskForChoiceError({ error: String(err) }),
14
+ .then(res => res.choice),
15
+ catch: err => new AskForChoiceError({ error: String(err) }),
16
16
  })));
17
17
  }
@@ -3,12 +3,12 @@ import { IoTag } from './index.js';
3
3
  class AskForInputError extends Data.TaggedClass('AskForInputError') {
4
4
  }
5
5
  export function askForInput(opts) {
6
- return pipe(IoTag, Effect.flatMap((io) => Effect.tryPromise({
6
+ return pipe(IoTag, Effect.flatMap(io => Effect.tryPromise({
7
7
  try: () => io.enquirer.prompt({
8
8
  name: 'version',
9
9
  type: 'input',
10
10
  message: opts.message,
11
11
  }),
12
- catch: (err) => new AskForInputError({ error: String(err) }),
12
+ catch: err => new AskForInputError({ error: String(err) }),
13
13
  })));
14
14
  }
@@ -1,7 +1,7 @@
1
1
  import { Effect, pipe } from 'effect';
2
2
  import { IoTag } from './index.js';
3
3
  export function exitIfInvalid(ctx) {
4
- return pipe(IoTag, Effect.tap((io) => Effect.sync(() => {
4
+ return pipe(IoTag, Effect.tap(io => Effect.sync(() => {
5
5
  if (ctx.isInvalid) {
6
6
  io.process.exit(1);
7
7
  }
@@ -9,6 +9,6 @@ export function globSync(io, patterns) {
9
9
  fs: io.fs,
10
10
  ignore: ['**/node_modules/**'],
11
11
  }),
12
- catch: (err) => new GlobError({ error: String(err) }),
12
+ catch: err => new GlobError({ error: String(err) }),
13
13
  });
14
14
  }
@@ -1,7 +1,7 @@
1
+ import fs from 'node:fs';
1
2
  import { cosmiconfig } from 'cosmiconfig';
2
3
  import { Context } from 'effect';
3
4
  import enquirer from 'enquirer';
4
- import fs from 'fs';
5
5
  import { globbySync } from 'globby';
6
6
  import { sync as readYamlFileSync } from 'read-yaml-file';
7
7
  export interface Io {
package/dist/io/index.js CHANGED
@@ -1,7 +1,7 @@
1
+ import fs from 'node:fs';
1
2
  import { cosmiconfig } from 'cosmiconfig';
2
3
  import { Context } from 'effect';
3
4
  import enquirer from 'enquirer';
4
- import fs from 'fs';
5
5
  import { globbySync } from 'globby';
6
6
  import { sync as readYamlFileSync } from 'read-yaml-file';
7
7
  export const IoTag = Context.GenericTag('@services/IoTag');
@@ -1,10 +1,12 @@
1
+ import { join } from 'node:path';
1
2
  import { Effect, Option, pipe } from 'effect';
2
- import { join } from 'path';
3
3
  import { isNonEmptyObject } from 'tightrope/guard/is-non-empty-object.js';
4
4
  import { readJsonFileSync } from './read-json-file-sync.js';
5
5
  const getOptionOfNonEmptyObject = Option.liftPredicate((isNonEmptyObject));
6
6
  export function readConfigFile(io, configPath) {
7
- return pipe(Effect.try(() => io.cosmiconfig.cosmiconfig('syncpack')), Effect.flatMap((client) => Effect.tryPromise(() => (configPath ? client.load(configPath) : client.search()))), Effect.flatMap((result) => result !== null ? getValueFromCosmiconfig(result) : findConfigInPackageJson(io)), Effect.tap((config) => Effect.logDebug(`config file found: ${JSON.stringify(config)}`)), Effect.tapError(() => Effect.logDebug('no config file found, will use defaults')), Effect.catchAll(() => Effect.succeed({})));
7
+ return pipe(Effect.try(() => io.cosmiconfig.cosmiconfig('syncpack')), Effect.flatMap(client => Effect.tryPromise(() => configPath ? client.load(configPath) : client.search())), Effect.flatMap(result => result !== null
8
+ ? getValueFromCosmiconfig(result)
9
+ : findConfigInPackageJson(io)), Effect.tap(config => Effect.logDebug(`config file found: ${JSON.stringify(config)}`)), Effect.tapError(() => Effect.logDebug('no config file found, will use defaults')), Effect.catchAll(() => Effect.succeed({})));
8
10
  }
9
11
  /**
10
12
  * Look for a .config.syncpack property in the root package.json.
@@ -18,5 +20,5 @@ function findConfigInPackageJson(io) {
18
20
  }
19
21
  /** Extract the value from a successful search by cosmiconfig */
20
22
  function getValueFromCosmiconfig(result) {
21
- return pipe(Effect.succeed(result), Effect.tap((result) => Effect.logDebug(`cosmiconfig found ${result.filepath}`)), Effect.flatMap((result) => getOptionOfNonEmptyObject(result.config)));
23
+ return pipe(Effect.succeed(result), Effect.tap(result => Effect.logDebug(`cosmiconfig found ${result.filepath}`)), Effect.flatMap(result => getOptionOfNonEmptyObject(result.config)));
22
24
  }
@@ -4,6 +4,6 @@ export class ReadFileError extends Data.TaggedClass('ReadFileError') {
4
4
  export function readFileSync(io, filePath) {
5
5
  return Effect.try({
6
6
  try: () => io.fs.readFileSync(filePath, { encoding: 'utf8' }),
7
- catch: (err) => new ReadFileError({ filePath, error: String(err) }),
7
+ catch: err => new ReadFileError({ filePath, error: String(err) }),
8
8
  });
9
9
  }
@@ -1,11 +1,12 @@
1
1
  import { Effect } from 'effect';
2
+ import { type ParseError } from 'jsonc-parser';
2
3
  import type { Io } from './index.js';
3
4
  import type { ReadFileError } from './read-file-sync.js';
4
5
  declare const JsonParseError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Readonly<A> & {
5
6
  readonly _tag: "JsonParseError";
6
7
  };
7
8
  export declare class JsonParseError extends JsonParseError_base<{
8
- readonly error: unknown;
9
+ readonly errors: ParseError[];
9
10
  readonly filePath: string;
10
11
  readonly json: string;
11
12
  }> {
@@ -1,15 +1,19 @@
1
+ import { dirname, relative } from 'node:path';
1
2
  import { Data, Effect, pipe } from 'effect';
2
- import { dirname, relative } from 'path';
3
+ import { parse } from 'jsonc-parser';
3
4
  import { readFileSync } from './read-file-sync.js';
4
5
  export class JsonParseError extends Data.TaggedClass('JsonParseError') {
5
6
  }
6
7
  export class JsonFile extends Data.TaggedClass('JsonFile') {
7
8
  }
8
9
  export function readJsonFileSync(io, filePath) {
9
- return pipe(Effect.Do, Effect.bind('json', () => readFileSync(io, filePath)), Effect.bind('contents', ({ json }) => Effect.try({
10
- try: () => JSON.parse(json),
11
- catch: (error) => new JsonParseError({ error, filePath, json }),
12
- })), Effect.map(({ contents, json }) => new JsonFile({
10
+ return pipe(Effect.Do, Effect.bind('json', () => readFileSync(io, filePath)), Effect.bind('contents', ({ json }) => {
11
+ const errors = [];
12
+ const data = parse(json, errors);
13
+ return errors.length === 0
14
+ ? Effect.succeed(data)
15
+ : Effect.fail(new JsonParseError({ errors, filePath, json }));
16
+ }), Effect.map(({ contents, json }) => new JsonFile({
13
17
  contents,
14
18
  dirPath: dirname(filePath),
15
19
  filePath,
@@ -4,6 +4,6 @@ class ReadYamlFileError extends Data.TaggedClass('ReadYamlFileError') {
4
4
  export function readYamlFileSync(io, filePath) {
5
5
  return Effect.try({
6
6
  try: () => io.readYamlFile.sync(filePath),
7
- catch: (err) => new ReadYamlFileError({ filePath, error: String(err) }),
7
+ catch: err => new ReadYamlFileError({ filePath, error: String(err) }),
8
8
  });
9
9
  }
@@ -5,5 +5,5 @@ export declare const newlines: {
5
5
  detect(source: string): Ending;
6
6
  fix(source: string, lineEnding: Ending): string;
7
7
  };
8
- export declare function toJson(ctx: Ctx, file: PackageJsonFile): string;
8
+ export declare function toFormattedJson(ctx: Ctx, file: PackageJsonFile): string;
9
9
  export {};
@@ -1,4 +1,4 @@
1
- import { EOL } from 'os';
1
+ import { EOL } from 'node:os';
2
2
  import { getIndent } from '../config/get-indent.js';
3
3
  const CR = '\r';
4
4
  const CRLF = '\r\n';
@@ -8,22 +8,25 @@ export const newlines = {
8
8
  const cr = source.split(CR).length;
9
9
  const lf = source.split(LF).length;
10
10
  const crlf = source.split(CRLF).length;
11
- if (cr + lf === 0)
11
+ if (cr + lf === 0) {
12
12
  return EOL;
13
- if (crlf === cr && crlf === lf)
13
+ }
14
+ if (crlf === cr && crlf === lf) {
14
15
  return CRLF;
15
- if (cr > lf)
16
+ }
17
+ if (cr > lf) {
16
18
  return CR;
19
+ }
17
20
  return LF;
18
21
  },
19
22
  fix(source, lineEnding) {
20
23
  return source.replace(/\r\n|\n|\r/g, lineEnding);
21
24
  },
22
25
  };
23
- export function toJson(ctx, file) {
26
+ export function toFormattedJson(ctx, file) {
24
27
  const contents = file.jsonFile.contents;
25
28
  const indent = getIndent(ctx.config);
26
- const EOL = newlines.detect(file.jsonFile.json);
27
- const source = `${JSON.stringify(contents, null, indent)}${EOL}`;
28
- return newlines.fix(source, EOL);
29
+ const eol = newlines.detect(file.jsonFile.json);
30
+ const source = `${JSON.stringify(contents, null, indent)}${eol}`;
31
+ return newlines.fix(source, eol);
29
32
  }
@@ -3,8 +3,8 @@ import { IoTag } from './index.js';
3
3
  export class WriteFileError extends Data.TaggedClass('WriteFileError') {
4
4
  }
5
5
  export function writeFileSync(filePath, contents) {
6
- return pipe(IoTag, Effect.flatMap((io) => Effect.try({
6
+ return pipe(IoTag, Effect.flatMap(io => Effect.try({
7
7
  try: () => io.fs.writeFileSync(filePath, contents),
8
- catch: (err) => new WriteFileError({ filePath, error: String(err) }),
8
+ catch: err => new WriteFileError({ filePath, error: String(err) }),
9
9
  })));
10
10
  }
@@ -1,10 +1,9 @@
1
1
  import chalk from 'chalk-template';
2
2
  import { Effect, pipe } from 'effect';
3
3
  import { ICON } from '../constants.js';
4
- import { toJson } from './to-json.js';
5
4
  import { writeFileSync } from './write-file-sync.js';
6
5
  export function writeIfChanged(ctx) {
7
- return pipe(Effect.all(ctx.packageJsonFiles.map((file) => pipe(Effect.Do, Effect.bind('nextJson', () => Effect.succeed(toJson(ctx, file))), Effect.bind('hasChanged', ({ nextJson }) => Effect.succeed(file.jsonFile.json !== nextJson)), Effect.flatMap(({ hasChanged, nextJson }) => hasChanged
8
- ? pipe(writeFileSync(file.jsonFile.filePath, nextJson), Effect.flatMap(() => Effect.logInfo(chalk `{green ${ICON.tick}} ${file.jsonFile.shortPath}`)))
9
- : Effect.logInfo(chalk `{dim ${ICON.skip} ${file.jsonFile.shortPath}}`))))), Effect.map(() => ctx));
6
+ return pipe(Effect.all(ctx.packageJsonFiles.map((file) => file.jsonFile.json !== file.nextJson
7
+ ? pipe(writeFileSync(file.jsonFile.filePath, file.nextJson), Effect.flatMap(() => Effect.logInfo(chalk `{green ${ICON.tick}} ${file.jsonFile.shortPath}`)))
8
+ : Effect.logInfo(chalk `{dim ${ICON.skip} ${file.jsonFile.shortPath}}`))), Effect.map(() => ctx));
10
9
  }
@@ -1,22 +1,25 @@
1
1
  export function formatRepositoryUrl(input) {
2
- if (!input)
2
+ if (!input) {
3
3
  return undefined;
4
+ }
4
5
  const extractedUrl = input.match(/https?:\/\/[^\s]+/)?.[0];
5
6
  if (extractedUrl) {
6
7
  const withoutSuffix = removeSuffix(extractedUrl);
7
8
  return withoutSuffix;
8
9
  }
9
- const isSSHProtocol = input.startsWith('git@');
10
- if (isSSHProtocol) {
10
+ const isSshProtocol = input.startsWith('git@');
11
+ if (isSshProtocol) {
11
12
  const withoutAffix = removeSuffix(removePrefix(input, 'git@'));
12
13
  const [origin, path] = withoutAffix.split(':');
13
- if (!origin || !path)
14
+ if (!(origin && path)) {
14
15
  return undefined;
16
+ }
15
17
  return `https://${origin}/${path}`;
16
18
  }
17
19
  const isShortcut = input.split('/').length === 2;
18
- if (isShortcut)
20
+ if (isShortcut) {
19
21
  return `https://github.com/${input}`;
22
+ }
20
23
  const isGitProtocol = input.startsWith('git://');
21
24
  if (isGitProtocol) {
22
25
  const withoutAffix = removeSuffix(removePrefix(input));
@@ -8,7 +8,9 @@ export function getVersionGroupHeader(input) {
8
8
  function getGroupHeader(label) {
9
9
  const trimmed = label.trim();
10
10
  const hasNewLines = trimmed.search(/[\r\n]/) !== -1;
11
- const header = hasNewLines ? formatMultiLine(trimmed) : formatSingleLine(trimmed);
11
+ const header = hasNewLines
12
+ ? formatMultiLine(trimmed)
13
+ : formatSingleLine(trimmed);
12
14
  return chalk.blue(header);
13
15
  }
14
16
  function formatSingleLine(label) {