Giter Club home page Giter Club logo

syncpack's Introduction

syncpack


Consistent dependency versions in large JavaScript Monorepos.
https://jamiemason.github.io/syncpack

Installation

npm install --save-dev syncpack

Commands

Ensure that multiple packages requiring the same dependency define the same version, so that every package requires eg. [email protected], instead of a combination of [email protected], [email protected], and [email protected].

Organise package.json files according to a conventional format, where fields appear in a predictable order and nested fields are ordered alphabetically. Shorthand properties are used where available, such as the "repository" and "bugs" fields.

Lint all versions and ranges and exit with 0 or 1 based on whether all files match your Syncpack configuration file.

Check whether dependency versions used within "dependencies", "devDependencies", etc follow a consistent format.

List all dependencies required by your packages.

List dependencies which are required by multiple packages, where the version is not the same across every package.

Displays a series of prompts to fix mismatches which syncpack cannot fix automatically.

Ensure dependency versions used within "dependencies", "devDependencies" etc follow a consistent format.

Interactively update packages to the latest versions from the npm registry, wherever they are in your monorepo. You can update every dependency, just dev/peer/prod dependencies, just packages which match a name filter, and more.

Badges

  • support on ko-fi
  • NPM version
  • NPM downloads
  • Build Status
  • Maintainability

syncpack's People

Contributors

adnjoo avatar alexhayton avatar altaywtf avatar aparajita avatar arturwierzbicki avatar auto200 avatar beeequeue avatar dependabot-preview[bot] avatar dependabot[bot] avatar dsilvasc avatar jamiemason avatar jodyheavener avatar keyz avatar luisvieiragmr avatar maraisr avatar mxro avatar nsaunders avatar syhner avatar tom-fletcher avatar wtchnm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

syncpack's Issues

Add resolutions field to package.json dependency checking

Description

I can probably help with this (amongst my other issues, I swear I haven't forgotten). Essentially, right now we check devDependencies, dependencies, and peerDependencies. But there is also resolutions which can override versions all the way down the stack. It would be useful to check that too to make sure it doesn't drift either

Suggested Solution

Support overrides for NPM (https://github.com/npm/rfcs/blob/main/accepted/0036-overrides.md) and resolutions for yarn (https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/)

Add anything listed there to the list of things to compare across package.json files

Help Needed

I think this can be done in list-mismatches command, but I'm not sure if there is somewhere else I should be looking. Other than that, I can't imagine this is a massive lift.

Blows away line endings in every package.json file with CRLF

Hello! And thanks for the excellent tool.

From a Windows machine, running syncpack fix-mismatches on my monorepo replaces the line endings in every package.json file with CRLF. (They were LF before.)

This is not good, as it breaks prettier --check and subsequently breaks CI.

Can the tool just copy the existing line endings that are used in the file? Or failing that, always just use LF or something?

feat: find mismatches in internal monorepo packages

Description

  1. @this-monorepo/atoms and @this-monorepo/molecules are developed in your monorepo.
  2. packages/atoms/package.json contains
    {
      "version": "1.2.3"
    }
  3. packages/molecules/package.json contains
    {
      "dependencies": {
        "@this-monorepo/atoms": "1.0.4"
      }
    }
  4. syncpack does not report any mismatches because nothing else in the monorepo depends on @this-monorepo/atoms.

Suggested Solution

Add --workspace to the existing list of locations to check for mismatches:

  -p, --prod              include dependencies
  -d, --dev               include devDependencies
  -P, --peer              include peerDependencies
  -R, --resolutions       include resolutions (yarn)
  -o, --overrides         include overrides (pnpm)
+ -w, --workspace         include locally developed package versions

feat(versionGroups): mark specific dependencies for removal

Description

There are some cases were you may not want certain dependencies introduced into the codebase (for example, say your company uses Typescript, you wouldn't want people trying to introduce Flow). Having a way to have a disallow list would be nice.

Suggested Solution

Have a list in the config of disallowed dependencies to prevent them being added anywhere in the repo.

Help Needed

Mostly want to know if this fits the goal of this project before looking to add this feature, or if it's not the job of this project

Feature request: Manage .npmignore files

Thanks for the good work. A feature I've been missing is the ability to manage the .npmignore files in each package.

NPM doesn't walk upwards in the folder structure, so it is more or less mandatory to have an .npmignore file in every package. Being able to check, sync, or update them would make the situation feel less insecure.

I hope you will consider this added feature. Thanks again.

Use workspace's dependencies versions as source of truth in fix-mismatches

Description

When running fix-mismatches in a folder containing a pnpm workspace, it always uses the highest version found in any of the packages, instead of the version specified in the workspace root, which I personally use as the source of truth for shared external dependencies.

Suggested Solution

I'd like a setting to prefer the dependencies versions listed in the workspace's root package.json, instead of the highest version found in any of the workspace projects. This allows quick dependencies / devDependencies / peerDependencies version downgrades across an entire workspace by simply updating the root package.json and then running fix-mismatches.

versionGroups not working with multiple elements

Description

I'm not sure if I'm using this feature correctly, but versionGroups is an array type which seems to imply it supports multiple entries. It works fine when I have one entry, but as soon as I add a second entry, it actually seems to break BOTH entries.

  1. Clone https://github.com/JasonGore/repro-syncpack
  2. yarn
  3. yarn repro

Expected:

No syncpack errors.

Actual:

Syncpack seems to ignore versionGroups and errors:

λ yarn repro
yarn run v1.22.4
$ syncpack list-mismatches --prod --dev
✕ fs-extra
- 9.0.1 in dependencies of syncpack-repro-a
- 9.0.1 in dependencies of syncpack-repro-c
- 9.0.1 in dependencies of syncpack-repro-a
- 8.0.1 in dependencies of syncpack-repro-b
- 9.0.1 in dependencies of syncpack-repro-c
✕ node-fetch
- 2.6.1 in dependencies of syncpack-repro-a
- 2.6.1 in dependencies of syncpack-repro-c
- 2.6.1 in dependencies of syncpack-repro-a
- 2.4.1 in dependencies of syncpack-repro-b
- 2.6.1 in dependencies of syncpack-repro-c
✕ uuid
- 6.0.0 in dependencies of syncpack-repro-a
- 8.3.0 in dependencies of syncpack-repro-c
- 6.0.0 in dependencies of syncpack-repro-a
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

If you just delete the second entry versionGroups in package.json from:

    "versionGroups": [
      {
        "dependencies": ["fs-extra", "node-fetch"],
        "packages": ["syncpack-repro-b"]
      },
      {
        "dependencies": ["uuid"],
        "packages": ["syncpack-repro-c"]
      }
    ]

to:

    "versionGroups": [
      {
        "dependencies": ["fs-extra", "node-fetch"],
        "packages": ["syncpack-repro-b"]
      }
    ]

Then you actually see some of the errors resolving:

λ yarn repro
yarn run v1.22.4
$ syncpack list-mismatches --prod --dev
✕ uuid
- 6.0.0 in dependencies of syncpack-repro-a
- 8.3.0 in dependencies of syncpack-repro-c
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I would expect both entries to work based on the type, but beyond that, it actually seems like the second entry breaks both entries.

Use workspace's packages version as source of truth in fix-mismatches

Description

When doing fix-mismatches it would be cool to sync dependencies version to use the local version, taken from the package.json version field instead of using the higher version found in the deps tree

Suggested Solution

Use the package.json version field of workspace packages as a range present in the dependency tree

Help Needed

feat: add config path option

Description

Currently, syncpack only locates the config file (if present) with cosmiconfig.

Other tools, such as ESLint and Prettier, allow the user to specific a configuration path other than the normal one auto-detected. This can be useful for i.e. moving the config files out of the package root and into a config/ folder. Allowing a custom config path would bring syncpack into parity with these tools.

Suggested Solution

Add -c / --config option to CLI:

  -i, --indent [value]    override indentation. defaults to "  "
+ -c, --config <path>     path to a syncpack config file, overriding automatic detection
  -h, --help              display help for command

When specified, use that path to locate the config file. If the file is not present, exit with an error.

When not specified, locate the config settings with cosmiconfig as normal.

PR

Happy to look at submitting a PR on this.

feat: format - option to opt out of sorting specific properties

Description

Sometime an objects properties need to remain in the order they are set.
for example when using npm-rull-all in sequential mode it runs the scripts in the order that they are defined

I do understand that this could be achieved with the configuration file, so its a "nice to have"

"scripts": {
    "build": "run-s build:* ",
    "build:clean": "...",
    "build:anotherStep": "...",
 },

when you run yarn build, the scripts are executed in the order

  1. build:clean
  2. build:anotherStep

after running format the order is changed to

  1. build:anotherStep
  2. build:clean

Suggested Solution

add a --skip-sort flag that takes a property path which turns off sorting of the sub-properties of the specified object

example:
syncpack format --skip-sort scripts
syncpack format --skip-sort some.custom.prop

Yarn workspaces support

It would be great if syncpack could work with yarn workspaces to help synchronize package versions across workspaces. A syncpack command specifying the root package.json could automatically use globs for workspaces.

bug: Running Tests on Windows doesn't work

Description

Steps:

  • Clone the repository
  • Run yarn ==> Runs successfully
  • Run yarn test

Received the following result (full log below)

image

Environment

  • Windows 10 with mingw64
  • Node v14.19.1

Complete Log

$ yarn test
yarn run v1.22.18
$ jest
 PASS  src/lib/set-semver-range.spec.ts (8.309 s)
 FAIL  src/bin-format/format.spec.ts (8.628 s)
  ● format › sorts array properties alphabetically by value

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: StringMatching /✓/, StringMatching /\/some\/package.json/  
    Received: "✓", "..\\..\\..\\..\\some\\package.json"

    Number of calls: 1

      24 |     );
      25 |     expect(before).toEqual(after);
    > 26 |     expect(log).toHaveBeenCalledWith(
         |                 ^
      27 |       expect.stringMatching(/✓/),
      28 |       expect.stringMatching('/some/package.json'),
      29 |     );

      at Object.<anonymous> (src/bin-format/format.spec.ts:26:17)        

  ● format › sorts object properties alphabetically by key

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: StringMatching /✓/, StringMatching /\/some\/package.json/  
    Received
           1: "✓", "..\\..\\..\\..\\some\\package.json"
           2: "✓", "..\\..\\..\\..\\some\\package.json"

    Number of calls: 2

      46 |     );
      47 |     expect(before).toEqual(after);
    > 48 |     expect(log).toHaveBeenCalledWith(
         |                 ^
      49 |       expect.stringMatching(/✓/),
      50 |       expect.stringMatching('/some/package.json'),
      51 |     );

      at Object.<anonymous> (src/bin-format/format.spec.ts:48:17)        

  ● format › sorts named properties first, then the rest alphabetically  

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: StringMatching /✓/, StringMatching /\/some\/package.json/  
    Received
           1: "✓", "..\\..\\..\\..\\some\\package.json"
           2: "✓", "..\\..\\..\\..\\some\\package.json"
           3: "✓", "..\\..\\..\\..\\some\\package.json"

    Number of calls: 3

      68 |     );
      69 |     expect(before).toEqual(after);
    > 70 |     expect(log).toHaveBeenCalledWith(
         |                 ^
      71 |       expect.stringMatching(/✓/),
      72 |       expect.stringMatching('/some/package.json'),
      73 |     );

      at Object.<anonymous> (src/bin-format/format.spec.ts:70:17)        

  ● format › uses shorthand format for "bugs"

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: StringMatching /✓/, StringMatching /\/some\/package.json/  
    Received
           1: "✓", "..\\..\\..\\..\\some\\package.json"
           2: "✓", "..\\..\\..\\..\\some\\package.json"
           3: "✓", "..\\..\\..\\..\\some\\package.json"

    Number of calls: 4

      89 |     );
      90 |     expect(before).toEqual(after);
    > 91 |     expect(log).toHaveBeenCalledWith(
         |                 ^
      92 |       expect.stringMatching(/✓/),
      93 |       expect.stringMatching('/some/package.json'),
      94 |     );

      at Object.<anonymous> (src/bin-format/format.spec.ts:91:17)        

  ● format › uses shorthand format for "repository"

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: StringMatching /✓/, StringMatching /\/some\/package.json/  
    Received
           1: "✓", "..\\..\\..\\..\\some\\package.json"
           2: "✓", "..\\..\\..\\..\\some\\package.json"
           3: "✓", "..\\..\\..\\..\\some\\package.json"

    Number of calls: 5

      112 |     );
      113 |     expect(before).toEqual(after);
    > 114 |     expect(log).toHaveBeenCalledWith(
          |                 ^
      115 |       expect.stringMatching(/✓/),
      116 |       expect.stringMatching('/some/package.json'),
      117 |     );

      at Object.<anonymous> (src/bin-format/format.spec.ts:114:17)       

  ● format › uses github shorthand format for "repository"

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: StringMatching /✓/, StringMatching /\/some\/package.json/  
    Received
           1: "✓", "..\\..\\..\\..\\some\\package.json"
           2: "✓", "..\\..\\..\\..\\some\\package.json"
           3: "✓", "..\\..\\..\\..\\some\\package.json"

    Number of calls: 6

      135 |     );
      136 |     expect(before).toEqual(after);
    > 137 |     expect(log).toHaveBeenCalledWith(
          |                 ^
      138 |       expect.stringMatching(/✓/),
      139 |       expect.stringMatching('/some/package.json'),
      140 |     );

      at Object.<anonymous> (src/bin-format/format.spec.ts:137:17)       

 PASS  src/bin-fix-mismatches/get-expected-version/get-highest-version.spec.ts (8.733 s)
 PASS  src/bin-fix-mismatches/get-expected-version/get-expected-version.spec.ts
 PASS  src/lib/disk.spec.ts
 PASS  src/bin-list/list-version-groups.spec.ts
 PASS  src/bin-lint-semver-ranges/list-semver-group-mismatches.spec.ts
 FAIL  src/lib/get-input/get-input.spec.ts (11.147 s)
  ● getInput › wrappers › when no --source cli options are given › when yarn workspaces are defined › as an array › resolves yarn workspace packages

    expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Array [
        Array [
          "package.json",
        ],
        Array [
    -     "as-array/*/package.json",
    +     "as-array\\*\\package.json",
        ],
      ]

      179 |             disk.readFileSync.mockReturnValue(json);
      180 |             getInput(disk, {});
    > 181 |             expect(disk.globSync.mock.calls).toEqual([       
          |                                              ^
      182 |               ['package.json'],
      183 |               ['as-array/*/package.json'],
      184 |             ]);

      at Object.<anonymous> (src/lib/get-input/get-input.spec.ts:181:46) 

  ● getInput › wrappers › when no --source cli options are given › when yarn workspaces are defined › as an object › resolves yarn workspace packages

    expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Array [
        Array [
          "package.json",
        ],
        Array [
    -     "as-object/*/package.json",
    +     "as-object\\*\\package.json",
        ],
      ]

      195 |             disk.readFileSync.mockReturnValue(json);
      196 |             getInput(disk, {});
    > 197 |             expect(disk.globSync.mock.calls).toEqual([       
          |                                              ^
      198 |               ['package.json'],
      199 |               ['as-object/*/package.json'],
      200 |             ]);

      at Object.<anonymous> (src/lib/get-input/get-input.spec.ts:197:46) 

  ● getInput › wrappers › when no --source cli options are given › when yarn workspaces are not defined › when lerna.json is defined › resolves lerna packages

    expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Array [
        Array [
          "package.json",
        ],
        Array [
    -     "lerna/*/package.json",
    +     "lerna\\*\\package.json",
        ],
      ]

      217 |             });
      218 |             getInput(disk, {});
    > 219 |             expect(disk.globSync.mock.calls).toEqual([       
          |                                              ^
      220 |               ['package.json'],
      221 |               ['lerna/*/package.json'],
      222 |             ]);

      at Object.<anonymous> (src/lib/get-input/get-input.spec.ts:219:46) 

  ● getInput › wrappers › when no --source cli options are given › when yarn workspaces are not defined › when lerna.json is not defined › when pnpm workspaces are defined › resolves pnpm packages

    expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Array [
        Array [
          "package.json",
        ],
        Array [
    -     "from-pnpm/*/package.json",
    +     "from-pnpm\\*\\package.json",
        ],
      ]

      234 |               });
      235 |               getInput(disk, {});
    > 236 |               expect(disk.globSync.mock.calls).toEqual([     
          |                                                ^
      237 |                 ['package.json'],
      238 |                 ['from-pnpm/*/package.json'],
      239 |               ]);

      at Object.<anonymous> (src/lib/get-input/get-input.spec.ts:236:48) 

 FAIL  src/bin-list/list.spec.ts
  ● list › when dependencies are installed with different versions › when the dependency is a package maintained in this workspace › warns ab the 
workspace version

    expect(received).toEqual(expected) // deep equality

    - Expected  - 5
    + Received  + 1

    - Array [
    -   Array [
    -     "✕ c 0.1.0, 0.2.0",
    -   ],
    - ]
    + Array []

      14 |         const scenario = scenarios.dependentDoesNotMatchWorkspaceVersion();
      15 |         list(getInput(scenario.disk, scenario.config), scenario.disk);
    > 16 |         expect(scenario.log.mock.calls).toEqual([['✕ c 0.1.0, 
0.2.0']]);
         |                                         ^
      17 |         expect(scenario.disk.process.exit).toHaveBeenCalledWith(1);
      18 |       });
      19 |     });

      at Object.<anonymous> (src/bin-list/list.spec.ts:16:41)

  ● list › when dependencies are installed with different versions › replaces non-semver dependencies with valid semver dependencies

    expect(received).toEqual(expected) // deep equality

    - Expected  - 5
    + Received  + 1

    - Array [
    -   Array [
    -     "✕ foo 0.2.0, 0.3.0, link:vendor/foo-0.1.0, link:vendor/foo-0.2.0",
    -   ],
    - ]
    + Array []

      22 |       const scenario = scenarios.mismatchesIncludeNonSemverVersions();
      23 |       list(getInput(scenario.disk, scenario.config), scenario.disk);
    > 24 |       expect(scenario.log.mock.calls).toEqual([
         |                                       ^
      25 |         ['✕ foo 0.2.0, 0.3.0, link:vendor/foo-0.1.0, link:vendor/foo-0.2.0'],
      26 |       ]);
      27 |       expect(scenario.disk.process.exit).toHaveBeenCalledWith(1);

      at Object.<anonymous> (src/bin-list/list.spec.ts:24:39)

  ● list › when dependencies are installed with different versions › removes banned/disallowed dependencies

    expect(received).toEqual(expected) // deep equality

    - Expected  - 7
    + Received  + 1

      Array [
        Array [
    -     "- foo 0.1.0",
    -   ],
    -   Array [
    -     StringMatching /Version Group 1/,
    -   ],
    -   Array [
    -     "✕ bar remove this dependency",
    +     "= Version Group 1 ===============================================================",
        ],
      ]

      31 |       const scenario = scenarios.dependencyIsBanned();        
      32 |       list(getInput(scenario.disk, scenario.config), scenario.disk);
    > 33 |       expect(scenario.log.mock.calls).toEqual([
         |                                       ^
      34 |         ['- foo 0.1.0'],
      35 |         [expect.stringMatching(/Version Group 1/)],
      36 |         ['✕ bar remove this dependency'],

      at Object.<anonymous> (src/bin-list/list.spec.ts:33:39)

 FAIL  src/bin-fix-mismatches/fix-mismatches.spec.ts (11.408 s)
  ● fixMismatches › when dependencies are installed with different versions › when the dependency is a package maintained in this workspace › uses the workspace version

    expect(received).toEqual(expected) // deep equality

    - Expected  - 22
    + Received  +  1

    - Array [
    -   Array [
    -     StringContaining "packages/a/package.json",
    -     "{
    -   \"name\": \"a\",
    -   \"dependencies\": {
    -     \"c\": \"0.0.1\"
    -   }
    - }
    - ",
    -   ],
    -   Array [
    -     StringContaining "packages/b/package.json",
    -     "{
    -   \"name\": \"b\",
    -   \"devDependencies\": {
    -     \"c\": \"0.0.1\"
    -   }
    - }
    - ",
    -   ],
    - ]
    + Array []

      14 |         const scenario = scenarios.dependentDoesNotMatchWorkspaceVersion();
      15 |         fixMismatches(getInput(scenario.disk, scenario.config), scenario.disk);
    > 16 |         expect(scenario.disk.writeFileSync.mock.calls).toEqual([
         |                                                        ^      
      17 |           scenario.files['packages/a/package.json'].diskWriteWhenChanged,
      18 |           scenario.files['packages/b/package.json'].diskWriteWhenChanged,
      19 |         ]);

      at Object.<anonymous> (src/bin-fix-mismatches/fix-mismatches.spec.ts:16:56)

  ● fixMismatches › when dependencies are installed with different versions › replaces non-semver dependencies with valid semver dependencies     

    expect(received).toEqual(expected) // deep equality

    - Expected  - 32
    + Received  +  1

    - Array [
    -   Array [
    -     StringContaining "packages/a/package.json",
    -     "{
    -   \"name\": \"a\",
    -   \"dependencies\": {
    -     \"foo\": \"0.3.0\"
    -   }
    - }
    - ",
    -   ],
    -   Array [
    -     StringContaining "packages/b/package.json",
    -     "{
    -   \"name\": \"b\",
    -   \"dependencies\": {
    -     \"foo\": \"0.3.0\"
    -   }
    - }
    - ",
    -   ],
    -   Array [
    -     StringContaining "packages/d/package.json",
    -     "{
    -   \"name\": \"d\",
    -   \"dependencies\": {
    -     \"foo\": \"0.3.0\"
    -   }
    - }
    - ",
    -   ],
    - ]
    + Array []

      29 |       const scenario = scenarios.mismatchesIncludeNonSemverVersions();
      30 |       fixMismatches(getInput(scenario.disk, scenario.config), 
scenario.disk);
    > 31 |       expect(scenario.disk.writeFileSync.mock.calls).toEqual([         |                                                      ^        
      32 |         scenario.files['packages/a/package.json'].diskWriteWhenChanged,
      33 |         scenario.files['packages/b/package.json'].diskWriteWhenChanged,
      34 |         scenario.files['packages/d/package.json'].diskWriteWhenChanged,

      at Object.<anonymous> (src/bin-fix-mismatches/fix-mismatches.spec.ts:31:54)

  ● fixMismatches › when dependencies are installed with different versions › removes banned/disallowed dependencies

    expect(received).toEqual(expected) // deep equality

    - Expected  - 9
    + Received  + 1

    - Array [
    -   Array [
    -     StringContaining "packages/b/package.json",
    -     "{
    -   \"name\": \"b\"
    - }
    - ",
    -   ],
    - ]
    + Array []

      45 |       const scenario = scenarios.dependencyIsBanned();        
      46 |       fixMismatches(getInput(scenario.disk, scenario.config), 
scenario.disk);
    > 47 |       expect(scenario.disk.writeFileSync.mock.calls).toEqual([         |                                                      ^        
      48 |         scenario.files['packages/b/package.json'].diskWriteWhenChanged,
      49 |       ]);
      50 |       expect(scenario.log.mock.calls).toEqual([

      at Object.<anonymous> (src/bin-fix-mismatches/fix-mismatches.spec.ts:47:54)

 FAIL  src/bin-list-mismatches/list-mismatches.spec.ts      
  ● listMismatches › when dependencies are installed with different versions › when the dependency is a package maintained in this workspace › warns ab the workspace version

    expect(received).toEqual(expected) // deep equality

    - Expected  - 11
    + Received  +  1

    - Array [
    -   Array [
    -     "- c 0.0.1",
    -   ],
    -   Array [
    -     "  0.1.0 in dependencies of a",
    -   ],
    -   Array [
    -     "  0.2.0 in devDependencies of b",
    -   ],
    - ]
    + Array []

      14 |         const scenario = scenarios.dependentDoesNotMatchWorkspaceVersion();
      15 |         listMismatches(getInput(scenario.disk, scenario.config), scenario.disk);
    > 16 |         expect(scenario.log.mock.calls).toEqual([
         |                                         ^
      17 |           ['- c 0.0.1'],
      18 |           ['  0.1.0 in dependencies of a'],
      19 |           ['  0.2.0 in devDependencies of b'],

      at Object.<anonymous> (src/bin-list-mismatches/list-mismatches.spec.ts:16:41)

  ● listMismatches › when dependencies are installed with different versions › replaces non-semver dependencies with valid semver dependencies    

    expect(received).toEqual(expected) // deep equality

    - Expected  - 17
    + Received  +  1

    - Array [
    -   Array [
    -     "- foo 0.3.0",
    -   ],
    -   Array [
    -     "  link:vendor/foo-0.1.0 in dependencies of a",
    -   ],
    -   Array [
    -     "  link:vendor/foo-0.2.0 in dependencies of b",
    -   ],
    -   Array [
    -     "  0.3.0 in dependencies of c",
    -   ],
    -   Array [
    -     "  0.2.0 in dependencies of d",
    -   ],
    - ]
    + Array []

      26 |       const scenario = scenarios.mismatchesIncludeNonSemverVersions();
      27 |       listMismatches(getInput(scenario.disk, scenario.config), scenario.disk);
    > 28 |       expect(scenario.log.mock.calls).toEqual([
         |                                       ^
      29 |         ['- foo 0.3.0'],
      30 |         ['  link:vendor/foo-0.1.0 in dependencies of a'],     
      31 |         ['  link:vendor/foo-0.2.0 in dependencies of b'],     

      at Object.<anonymous> (src/bin-list-mismatches/list-mismatches.spec.ts:28:39)

  ● listMismatches › when dependencies are installed with different versions › removes banned/disallowed dependencies

    expect(received).toEqual(expected) // deep equality

    - Expected  - 11
    + Received  +  1

    - Array [
    -   Array [
    -     StringMatching /Version Group 1/,
    -   ],
    -   Array [
    -     "✕ bar remove this dependency",
    -   ],
    -   Array [
    -     "  0.2.0 in dependencies of b",
    -   ],
    - ]
    + Array []

      39 |       const scenario = scenarios.dependencyIsBanned();        
      40 |       listMismatches(getInput(scenario.disk, scenario.config), scenario.disk);
    > 41 |       expect(scenario.log.mock.calls).toEqual([
         |                                       ^
      42 |         [expect.stringMatching(/Version Group 1/)],
      43 |         ['✕ bar remove this dependency'],
      44 |         ['  0.2.0 in dependencies of b'],

      at Object.<anonymous> (src/bin-list-mismatches/list-mismatches.spec.ts:41:39)

 PASS  src/lib/is-semver.spec.ts
 PASS  src/lib/collect.spec.ts
 PASS  src/lib/sort-by-name.spec.ts
 FAIL  src/bin-lint-semver-ranges/lint-semver-ranges.spec.ts
  ● lintSemverRanges › lists versions with ranges which do not match the 
project config

    expect(received).toEqual(expected) // deep equality

    - Expected  - 8
    + Received  + 1

    - Array [
    -   Array [
    -     "✕ bar 2.0.0 in dependencies of a should be ~2.0.0",
    -   ],
    -   Array [
    -     "✕ foo 0.1.0 in dependencies of a should be ~0.1.0",
    -   ],
    - ]
    + Array []

      12 |     const scenario = scenarios.semverRangesDoNotMatchConfig();      13 |     lintSemverRanges(getInput(scenario.disk, scenario.config), scenario.disk);
    > 14 |     expect(scenario.log.mock.calls).toEqual([
         |                                     ^
      15 |       ['✕ bar 2.0.0 in dependencies of a should be ~2.0.0'],  
      16 |       ['✕ foo 0.1.0 in dependencies of a should be ~0.1.0'],  
      17 |     ]);

      at Object.<anonymous> (src/bin-lint-semver-ranges/lint-semver-ranges.spec.ts:14:37)

 FAIL  src/bin-set-semver-ranges/set-semver-ranges.spec.ts
  ● setSemverRanges › sets all versions to use the supplied range

    expect(received).toEqual(expected) // deep equality

    - Expected  - 13
    + Received  +  1

    - Array [
    -   Array [
    -     StringContaining "packages/a/package.json",
    -     "{
    -   \"name\": \"a\",
    -   \"dependencies\": {
    -     \"foo\": \"~0.1.0\",
    -     \"bar\": \"~2.0.0\"
    -   }
    - }
    - ",
    -   ],
    - ]
    + Array []

      12 |     const scenario = scenarios.semverRangesDoNotMatchConfig();      13 |     setSemverRanges(getInput(scenario.disk, scenario.config), scenario.disk);
    > 14 |     expect(scenario.disk.writeFileSync.mock.calls).toEqual([
         |                                                    ^
      15 |       scenario.files['packages/a/package.json'].diskWriteWhenChanged,
      16 |     ]);
      17 |     expect(scenario.log.mock.calls).toEqual([

      at Object.<anonymous> (src/bin-set-semver-ranges/set-semver-ranges.spec.ts:14:52)

Jest: "global" coverage threshold for statements (92%) not met: 77.29%
Jest: "global" coverage threshold for branches (79%) not met: 63.07%
Jest: "global" coverage threshold for lines (93%) not met: 82.27%
Jest: "global" coverage threshold for functions (93%) not met: 80.16%
Test Suites: 7 failed, 9 passed, 16 total
Tests:       21 failed, 33 passed, 54 total
Snapshots:   0 total
Time:        16.402 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Add "set-range" command

Usage: syncpack set-range [options]

Options:

  -p, --packages <glob...>  location of packages. defaults to 'package.json' 'packages/*/package.json'
  -r, --range <value>       semver range such as ^, ~, >= etc. defaults to none (like npm install --save-exact)
  -h, --help                output usage information

The chosen ranges would result in the following formats:

Range Result
< <1.0.0
<= <=1.0.0
'' 1.0.0
~ ^1.0.0
^ ^1.0.0
>= >=1.0.0
> >1.0.0
* *
.x 1.x.x and 0.1.x

Sort resolutions a-z

Description

syncpack format does not sort the properties of resolutions alphabetically, this property should behave the same as dependencies or devDependencies.

ignore file: and link: dependencies

Description

When project uses local dependencies, such as

"dependencies": {
    "my-library": "link:./../my-library"
}

syncpack will fail with message:

Error: Failed to find highest version of link:./../my-library

Suggested Solution

This kind of dependency should be simply ignored.
This also applies to file:, https: and git repos.

doesn't work on node 11.6

trying to run a command getting this

syncpack list-mismatches

(node:74583) UnhandledPromiseRejectionWarning: TypeError: Expected `cwd` to be of type `string` but received type `undefined`
    at module.exports.sync (/usr/local/lib/node_modules/syncpack/node_modules/dir-glob/index.js:56:9)
    at globDirs (/usr/local/lib/node_modules/syncpack/node_modules/globby/index.js:58:9)
    at getPattern (/usr/local/lib/node_modules/syncpack/node_modules/globby/index.js:61:64)
    at globTasks.reduce (/usr/local/lib/node_modules/syncpack/node_modules/globby/index.js:107:19)
    at Array.reduce (<anonymous>)
    at Function.module.exports.sync (/usr/local/lib/node_modules/syncpack/node_modules/globby/index.js:106:26)
    at Object.exports.getPackages (/usr/local/lib/node_modules/syncpack/dist/lib/get-packages.js:19:19)
    at Object.<anonymous> (/usr/local/lib/node_modules/syncpack/dist/list-mismatches.js:56:31)
    at step (/usr/local/lib/node_modules/syncpack/dist/list-mismatches.js:32:23)
    at Object.next (/usr/local/lib/node_modules/syncpack/dist/list-mismatches.js:13:53)

Overrides not checked with pnpm

Description

In a pnpm monorepo, I have a pnpm.overrieds field in the root package.json in order to override a an indirect dependency (a dependency of a dependency).

And then, in the same package.json devDependencies field or in one of the workspaces, is have an explicit dependency on eslint@^7.0.0.

// package.json
{
  // ...
  "devDependencies": {
    "eslint": "^7.0.0"
  },
  "pnpm:" {
    "overrides": {
      "eslint": "^8.16.0"
    }
  }
}

When I run syncpack list-mismatches, this discrepancy to be reported, but it isn't, even if I specifically add the --overrides flag.

A minimal reproduction repo is available here: https://github.com/TxHawks/syncpack-overrides-repro

Suggested Solution

Since overrides are mostly used to deal with indirect dependencies, where you have no direct control over the content of the package.json files, I'd expect list-mismatches to report this discrepancy like any other version mismatch.

Help Needed

If this is indeed a bug, I'd be happy to take a stab at this, and would appreciate pointers for where in the code to start looking at and maybe hash out the best way to do this.

docs(website): add examples cookbook

Description

There are some examples scattered around:

Suggested Solution

Add a Cookbook section of examples to README.md, something roughly like this:

👨🏻‍🍳 Cookbook

Use loose semver ranges for `devDependencies` only, with the rest of the repo using the default setting.

Some explanation here

{
  "semverGroups": [
    {
      "dependencies": ["**"],
      "dependencyTypes": ["devDependencies"],
      "packages": ["**"],
      "semverRange": "^"
    }
  ]
}
Force the version of `jest` to `*` (latest) for the whole monorepo, but only within `peerDependencies`

Some explanation here

{
  "versionGroups": [
    {
      "dependencies": ["jest"],
      "dependencyTypes": ["peerDependencies"],
      "packages": ["**"],
      "pinVersion": "*"
    }
  ]
}

--indent option does not work if changes are only to indentation

Description

  1. Execute syncpack format --source "PATH-TO-PACKAGE-JSON" to make sure properties are now in the correct order
  2. Execute syncpack format --indent '....' --source "PATH-TO-PACKAGE-JSON"
  3. Review package.json - notice that .... is not used as indentation.

The expected output is that indentation will be four periods (this is purely for demonstration purposes).

Suggested Solution

The issue appear to be here: https://github.com/JamieMason/syncpack/blob/master/src/commands/lib/write-if-changed.ts#L11
The comparison is the trimmed input line (meaning: no leading white space) with the indent added in front. When you compare that to the "after", there are no differences because the order of the keys is not changing at all.

Based on the fact that the parser does not take white-space into account, there are two options:

  1. use a parser other than JSON.parse which will take the white-space into account
  2. update the documentation to indicate that "--indent" only applies when other modifications are made; so from -i, --indent [value] override indentation. defaults to " " to -i, --indent [value] the indentation to use if other modifications are made. defaults to " "

Review default value for --packages

The default value for --packages is currently "./packages/*/package.json", this works well for list-mismatches for example but for format it should also include the root package.json. Check the default values for each command.

feat: add a monorepo-wide `npm update`

Description

Hi, in regular npm projects, i often use npm update which does:

  1. update the package.json file: change the package versions to the highest possible
  2. actually update all the packages in node_modules based on this

Inside lerna projects, this is sadly not possible, because npm cannot install stuff in subfolders.
And afaik there's no good equivalent to npm update in lerna:
lerna/lerna#2142
(issue been open for ages)

Suggested Solution

if syncpack could offer to do step 1 above:

  1. update the package.json file: change the package versions to the highest possible

then afterwards I could run lerna bootstrap to actually install those.

Keep up the good work!!

feat: apply constraints to versions used when fixing mismatches

I have a bunch of packages using webpack@4 and webpack@5. I basically want all packages to pin to the same version of either 4 or 5 depending on what they currently depend on.

Some packages depend on [email protected] for example. This is because it was the only package available at the time, but now I want them all to use the latest.

I guess its similar to versionGroups, but instead of listing packages, I want to list the semver range for the group.

Something like:

{
  "versionGroups": [
    {
      "dependencies": ["webpack"],
      "versions": ["webpack@4<5"],
    },
    {
      "dependencies": ["webpack"],
      "versions": ["webpack@>5"],
    }
  ]
}

Allow multiple versions of a particular dependency

Description

This request pertains to the list-mismatches and fix-mismatches commands.

In some cases, it's necessary to temporarily have different versions of a particular dependency present in the same repo, for example when experimenting with new things or moving code into a monorepo.

Example:

  • foo is a package within the monorepo
  • Several packages in the monorepo (including foo) depend on typescript and react
  • For most packages, their typescript and react versions must match, but foo is experimental and needs newer versions (it may also need newer versions of other packages)
  • Variant: foo could have a few related packages (like foo-utilities) which should share these different dependency versions

Existing solutions

  • Ignore all dependencies of foo by using a custom --source glob which excludes it: e.g. --source "{apps/*,packages/!(foo),packages/sub-group/*,scripts,.}/package.json" (from the real monorepo where I want this feature)
    • Disadvantages: allows accidental variation of other deps
  • Ignore all dependencies on typescript with --filter "^(?!^typescript$).*$"
    • Disadvantages: removes all restrictions on typescript

These options also share the following disadvantages:

  • Not terribly intuitive, at least in the exclusion/negation case
  • Awkward to type on the command line and/or must be duplicated across multiple package.json scripts
  • Doesn't scale well if there are related packages which share alternate versions

Suggested Solutions

There are multiple possible ways to solve this, most simply by adding another command line option, but a package.json and/or config file option would be easier to use and more capable.

1. Command line option

Simplest implementation would be to add a command line option listing packages for which dependencies don't need to match, something like --ignore <package-name-regex>.

This is not my preference (at least as the only solution) because:

  • It's too permissive:
    • Allows any deps to vary, not just the (probably) one or two that you care about
    • If ignoring multiple package names, there's no enforcement that the deps match within them, which is likely what would be desired
  • Typing potentially-complicated regexes on the command line (or duplicating them across multiple scripts in package.json) is annoying.

2. package.json (and/or config file) options

A better approach would be to add support for reading configuration from the monorepo root package.json (and/or a config file, but only using package.json would be easier). This allows easily specifying alternates xin the form of key/value mappings, which I think is the most logical form but would be very awkward to specify on the command line.

There are various ways the option could be set up. Alternatives can be found in the collapsed section below, but here's what I'd suggest (though I'm not at all set on the names).

Using the same example from above, this would allow foo and foo-utilities to depend on any version of typescript and react, but all other packages must still depend on a matching version. variationGroups takes an array in case there are multiple groups of packages which should share alternate versions.

{
  "syncpack": {
    "variationGroups": [
      {
        "packages": ["foo", "foo-utilities"],
        "dependencies": ["typescript", "react"]
      }
    ]
  }
}

Other solutions considered

Click to expand

These were other options I considered but discarded for various reasons. (Again in all these cases, naming is not final.)

2a. List groups of monorepo packages to consider separately

Essentially, do two sub-runs: against foo plus foo-utilities, and against everything else.

{
  "syncpack": {
    "variationGroups": [
      ["foo", "foo-utilities"]
    ]
  }
}

Pros: Probably the simplest to set up and to implement. (Could potentially even be set up as a command line option.)

Cons: It's likely that the variation group and the main group should still share versions of some dependencies, and this does nothing to enforce that.

2b. Mapping between monorepo package and deps which may vary

foo and foo-utilities may depend on any version of typescript and react, but all other packages must still depend on a matching version.

{
  "syncpack": {
    // Mapping from monorepo package to dep list
    "allowedVariations": {
      "foo": ["typescript", "react"],
      "foo-utilities": ["typescript", "react"]
    },
    // OR the inverse:
    // Mapping from dep to monorepo package list
    "allowedVariations": {
      "typescript": ["foo", "foo-utilities"],
      "react": ["foo", "foo-utilities"]
    }
  }
}

Pros: Simple to set up in some ways.

Cons: Requires duplication if there are additional packages which are related to foo; no notion of groups which should share dep versions

2c. Mapping from monorepo package to allowed semver specs

For particular packages, list the semver specs which are allowed. This is similar to allowedAlternateVersions from rush (which my team's monorepo previously used).

{
  "syncpack": {
    "allowedVersions": {
      "typescript": ["3.7.2", "^3.9.0"]
    }
  }
}

Pros: No duplication required for multiple packages to share an alternate version; very specific

Cons: Additional place to manually change when upgrading versions; no notion of grouping; seems a bit incongruous with syncpack's other options (which don't rely on explicitly set semver specs)

Note for any package.json/config file-based solution

If any file-based config support is added for version alternates, it would probably make sense to also add support for overrideable defaults for the other options. For example, instead of having to specify --prod --dev --source "{apps/*,packages/!(foo),packages/sub-group/*,scripts,.}/package.json" for every syncpack command, you could do this:

{
  "syncpack": {
    "prod": true,
    "dev": true,
    "source": "{apps/*,packages/!(foo),packages/sub-group/*,scripts,.}/package.json"
  }
}

(Granted the long source glob would not be needed once this new config setting is added, but it would still be nice to have the other common options specified in a single place.)

Help Needed

I can probably help implement this.

feat: sync versions between dependencies such as @aws-cdk/*

Description

I would expect version group to match packages in the devDependencies, dependencies and peerDependencies field alltogether.

Suggested Solution

Fix mismatches for packages across dev, peer and prod dependencies.

Help Needed

It seems package.json files are written even if not changed

Description

When running syncpack git seems to pick up that all package.json files have been changed. Even if there are no changes made to the files:

image

Suggested Solution

Ensure that package.json files are only written when they are actually changed.

Help Needed

syncpack list: support --json

Hey,

What do you think about support a json format instead of what the list method has at the moment?

It can be useful for automatic tools to run the script and collect the result much easily.
I saw there's also this, which suggests a node API, which is great too :)

WDYT? I'd be willing to contribute if you find this useful.

Thanks

Add "format" command

Usage: syncpack format [options]

Options:

  -p, --packages <glob...>  location of packages. defaults to 'package.json' 'packages/*/package.json'
  -h, --help             output usage information

Runs fixpack over each package.json.

feature: allow for custom sorting for fix-mismatches

Description

Expected usecase:
using prerelease / experimental react versions should sync correctly (0.0.0-experimental-b53ea6ca0), i.e, imagine going from 16.12.0 to an experimental version, you now want to sync the experimental version across all your package versions in your monorepo when using the fix-mismatches command.

Actual:
package changes are marked as mismatched, stable version is taken (16.12.0)

+        "react": "^16.12.0",
+       "react-dom": "^16.12.0",
-        "react": "^0.0.0-experimental-b53ea6ca0",
-        "react-dom": "^0.0.0-experimental-b53ea6ca0",

Peeking at the source code, i see there is some logic for taking the newest version, which uses the semver package,
which I guess could be an issue since React doesn't use semantic versioning, and instead uses 0.0.0-sha.

Suggested Solution

Admittedly, not too sure on what the API would look like. Would like to have some discussion on the issue first.

One possible approach is to accept a flag to take the lowest version for a specific package, e.g. --take-lowest react. As we iterate through the synced packages, we take the reversed ordering.

Another one is to provide a way to specify a custom comparator to the sorting functions.

Help Needed

  • Mostly seeking discussion on whether this is something syncpack would be open to a PR for
  • If so, iterate on the API.

Question: is there a way to use this with custom package directories?

Both Lerna and Yarn Workspaces allow you to override their package directories. In our project, we are using multiple package directories. Is there a way to pass in this pattern? Alternatively would you agree to this library detecting Lerna / Yarn Workspaces and using their configuration by default to find this?

fix: globs starting with ./ resolve to undefined name in output

Description

I'm using the syncpack.config.js as my rc file for one reason: our monorepo doesn't have a standard packages/ folder, so I'm listing paths by hand. As such, my source array looks like this:

  source: [
    // Root package.json
    '.',
    'path/to/package,
  ].map(entry => `${entry}/package.json`),

When an error is found against the path ./package.json (root package.json) the name assigned to it is undefined. I assume the code is stripping out /package.json and trying to use what comes before it, which in this case has no name.

Example:

Error, package mismatch found:
x webpack-cli

3.3.12 in dependencies of undefined
^3.3.11 in dependencies of package

Suggested Solution

If no path before /package.json, just return package.json or root.

Help Needed

Knowing where the paths are sanitized in the code, other than that I can probably do the fix.

Seems to be unable to handle non-semver based version strings

09:58 $ npx syncpack fix-mismatches --dev --indent "    "
(node:26719) UnhandledPromiseRejectionWarning: TypeError: Invalid Version: d
    at new SemVer (/home/user/Repos/project-fe/node_modules/semver/semver.js:312:11)
    at compare (/home/user/Repos/project-fe/node_modules/semver/semver.js:585:10)
    at Function.gt (/home/user/Repos/project-fe/node_modules/semver/semver.js:614:10)
    at /home/user/Repos/project-fe/node_modules/syncpack/dist/lib/version.js:41:20
    at Array.sort (native)
    at Object.exports.sortBySemver (/home/user/Repos/project-fe/node_modules/syncpack/dist/lib/version.js:19:10)
    at Object.exports.getNewest (/home/user/Repos/project-fe/node_modules/syncpack/dist/lib/version.js:6:26)
    at /home/user/Repos/project-fe/node_modules/syncpack/dist/fix-mismatches.js:68:44
    at Array.forEach (<anonymous>)
    at Object.<anonymous> (/home/user/Repos/project-fe/node_modules/syncpack/dist/fix-mismatches.js:66:58)
(node:26719) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:26719) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I have a lot of "file:" protocol version strings, and some strings are links to github repos.. I suspect this is the issue, because I can run the command just fine without devDependencies.

I would really like to be able to run this tool on all my lerna packages.

Allow specifying the desired version per dependency

Description

In some cases, it's useful to align all the dependencies of a package to a pre-defined dependency, e.g. set all lodash versions across the monorepo to be ~14.17.0.

The current process is unclear in regards to how the version to set is obtained - I see it can be either the latest version of the dependency, or some other version declared by one of the packages (which I assume if picked to be such that falls within all the semver ranges defined in all the monorepo packages).

Suggested Solution

Allow providing an enforceVersions property in the syncpack file (or as a standalone syncpack-versions file, for instance), that will hold the desired versions to set for each dependency. Based on a new flag or the mere existence of the file, the dependencies across the repo would be all set to the desired versions.

For example:

...
enforceVersion: {
   production: {
     lodash: "^14.17.0",
     "fs-extra": "1.0.0"
   },
   dev: {
     lodash: "~14.0.0."
   }
}
...

The examples separates dev fro production dependencies, if for some reason they need to be set to different semver ranges, but this should probably be optional (by allowing to nest all the dependencies directly under the enforceVersion property).

This will allow both an easy alignment of dependencies in an existing repo, and also provide a clearer process of version resolution that can be reasoned about.

Help Needed

@JamieMason If you found this proposal useful, I'll be glad to be pointed to the relevant parts in the code that needs to be changed and contribute a PR.

feat: add hoist command

hoist

Take a property from multiple package.json files and merge them into the target package.json. When
hoisting dependencies and multiple sources contain the same dependency, the newest version will be
used. When hoisting scripts with the same name, they will be namespaced on the target like so
"build:packagename".

Options

-s, --source [pattern]  glob pattern for package.json files to read from
-t, --target [pattern]  glob pattern for package.json files to write to
-p, --prop [name]       path to property to hoist
-e, --exclude [name]    path to property to exclude from hoisting
-i, --indent [value]    override indentation. defaults to "  "
-h, --help              output usage information

Examples

# uses packages defined in lerna.json as default sources and ./package.json as default target
syncpack hoist --prop "devDependencies"
# nested properties can be hoisted like this
syncpack hoist --prop "devDependencies.gulp"
# properties can be excluded like this
syncpack hoist --prop "devDependencies" --exclude "devDependencies.jest"
# uses packages defined by --source and --target when provided
syncpack hoist --prop "devDependencies" --source "apps/*/package.json" --target "package.json"
# multiple sources can be provided like this
syncpack hoist --prop "devDependencies" --source "apps/*/package.json" --source "core/*/package.json"
# multiple targets can be provided like this
syncpack hoist --prop "devDependencies" --source "packages/foo/package.json" --target "packages/bar/package.json" --target "packages/baz/package.json"
# multiple properties can be provided like this
syncpack hoist --prop "devDependencies" --prop "scripts"
# indent package.json with 4 spaces instead of 2
syncpack hoist --prop "devDependencies" --indent "    "

yarn workspaces: packages found in node_modules are not excluded when using list/fix-mismatches

Description

Hi,
We're using Yarn Workspaces with the glob pattern packages/**, since we have a deep package tree.
Some of the packages have their own node_modules folder with packages inside.
Yarn is ok with it and automatically doesn't search for packages in node_modules.

However, syncpack has its own resolving mechanism and does include packages from node_modules.

Suggested Solution

Automatically exclude node_modules when searching for packages.

Is there a way maybe for a user to exclude it via config? I couldn't find one.

Thanks!!

Handle multiple --packages globs

I think currently only one glob can be passed, ensure all commands can accept multiple globs.

$ syncpack format --help

  Usage: syncpack-format [options]


  Options:

    -p, --packages <glob>  location of packages, defaults to "./packages/*/package.json"
    -h, --help             output usage information

discuss: how to fix non-semver dependencies

Description

I have a monorepo that contains 3 react-native apps.
Before running fix-mismatches

// apps/a/package.json
"dependencies": {
  "@react-native-community/cameraroll": "https://github.com/my-fork/react-native-cameraroll.git#fix-a-bug",
}

// apps/b/package.json
"dependencies": {
  "@react-native-community/cameraroll": "https://github.com/my-fork/react-native-cameraroll.git#fix-another-bug",
}

After

// apps/a/package.json
"dependencies": {
  "@react-native-community/cameraroll": "1.3.1",
}

// apps/b/package.json
"dependencies": {
  "@react-native-community/cameraroll": "1.3.1",
}

It changes the custom URL dependency back to the original one, which is not expected.

Not sure if anyone else has the same situation like this.

Suggested Solution

The situation above is a little bit complicated and we just don't have a clue to fix it automatically. Only the developer can solve it.
My suggestion is that syncpack skips those custom URL dependencies and give warnings in the console to let developers know that there are problems to solve, like:

// console output

Warnings: the dependencies below need sync manually.
- apps/a/package.json -> "@react-native-community/cameraroll": "https://github.com/my-fork/react-native-cameraroll.git#fix-a-bug"
- apps/b/package.json -> "@react-native-community/cameraroll": "https://github.com/my-fork/react-native-cameraroll.git#fix-another-bug"

feat(node): add a Node.js API

Description

This package is really great, but it would be nice for it to run sort of like a linter in that it should report to you the lines in the package.json that are impacted. This is mostly so that there may be a way to more easily identify which files need editing or updating for a given dependency.

Suggested Solution

For a given scan:

  • Return an array of objects
  • Each object has a package name
  • Each object also has an array of paths that represent the files that are mis aligned
  • Each object contains a message with the version that are skewd

Help Needed

At this point I just want to know if you are open to this feature, or if this is not the intent of the tool.

format command sorts "files" property and breaks packages

Description

syncpack is reordering the contents of the "files" property which changes the contents of the tarball when using "npm publish" or "npm pack".

example package.json:

{
    files: [
        "dist/",
        "!*.test.*",
        "!__mocks__"
   ]
}

command:
npx syncpack format

result:

{
    files: [
        "!*.test.*",
        "!__mocks__",
        "dist/"
   ]
}

expected result:

{
    files: [
        "dist/",
        "!*.test.*",
        "!__mocks__"
   ]
}

The only work-around is to use .npmignore and remove the files property, but this becomes unmanageable.

Suggested Solution

Do not sort the "files" property.

Let user override dependency types

Description

DEPENDENCY_TYPES is a constant used by gatherDependencies to get every name/version pair for the commands list, list-mismatches, fix-mismatches, and set-semver-ranges.

  • Make DEPENDENCY_TYPES the default.
  • Add CLI options to let you override which types are used.

Possible Solutions

Scenario: Only list devDependencies and peerDependencies:

  1. syncpack list --type devDependencies --type peerDependencies
  2. syncpack list --type dev --type peer
  3. syncpack list --types dev,peer
  4. syncpack list --dev --peer

Suggested Solution

Use option 4 from above, where each dependency type is a boolean option:

  • '-p, --prod'
  • '-d, --dev'
  • '-P, --peer'

Usage

  • If none are set, use all.
  • If any are set, use only those which are set.

Related Issues

  • Will help enable #7.
  • Might help #4, as 'engines' could be included.

feature: show path to package.json instead of package.json name field

Description

When there are multiple offenders, it can be hard to align offending packages with their name field
because that is not an easily searchable.

Suggested Solution

Show the path to the offending package.json instead of the name field

Help Needed

Happy to help contribute this. I haven't looked into it yet. I just keep hitting it and getting annoyed :)

Support Yarn's `workspaces` being an object with a `packages` array

Problem

Currently, syncpack only understands the following syntax for Yarn Workspaces:

{
  "workspaces": ["packages/*"]
}

Desired behavior

But is should also support:

{
  "workspaces": {
    "packages": ["packages/*"]
  }
}

Proposed solution

AFAICT, the getYarnPatterns function in the get-wrappers.ts file should be modified to something like:

const getYarnPatterns = () => {
  const workspaces = getPatternsFromConfig('package.json', 'workspaces');
  return Array.isArray(workspaces) ? workspaces : workspaces.packages;
};

feature: Custom formating

Description

I like the format command, but I would like more control on what it does. For example, my package.json has a unpkg and jsdelivr field that I would like to stay close together, or I would like to have the main field at the top.
Another way of putting this is; maybe some wants to only reorder nested field like dependencies without reordering the whole package.json.

Suggested Solution

Managing all the possibilities through CLI options would be too much. Maybe some kind of configuration file (.syncpackrc ?) could solve this. Something like what fixpack is doing is good.
Alternatively, I wonder if a template file could be a good idea.

Help Needed

Is this something you would consider adding or is this out of scope for this CLI ?

Thanks for your time.

Improve isBanned messaging

Description

Make messaging clearer when an instance of a dependency matches versionGroup.isBanned.

Current output is like so:

x  react remove this dependency

Suggested Solution

From #65 (comment):

I would just change the error to be something more useful. I think it would be nice to say react is ban from use within the code base and then in the config isBanned can take a reason that we can customize and surface to the user so we can say "go to this url to learn more why"

feat(cli): add lint and fix commands

Based on some configuration and a glob for package.json files, exits with 0 or 1 based on whether the files match the configuration. Could be used in CI and/or hooks.

EDIT: create a lint command and a fix command which each combine the existing commands which either list issues or fix them.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.