Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build(client): Add syncpack to find and fix mismatched dependency versions #14042

Merged
merged 13 commits into from
Feb 17, 2023

Conversation

tylerbutler
Copy link
Member

@tylerbutler tylerbutler commented Feb 7, 2023

pnpm enables us to use different versions of dependencies in different projects within the same release group (workspace in pnpm terms). While there are legitimate uses for this capability, we prefer to use the same version of dependencies across the repo whenever possible.

This PR uses syncpack to lint and fix version differences and enforce dependency range types (e.g. tilde vs. caret deps). I did not add these scripts to CI; they can only be run manually using the following scripts:

  • syncpack:deps
  • syncpack:deps:fix
  • syncpack:versions
  • syncpack:versions:fix

The scripts already revealed some inconsistencies which I have fixed in this PR so you can see what it found.

A future PR will add this to CI in some way; details TBD.

@github-actions github-actions bot added area: dds Issues related to distributed data structures area: dds: propertydds area: dds: tree area: examples Changes that focus on our examples area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct base: main PRs targeted against main branch labels Feb 7, 2023
@Josmithr
Copy link
Contributor

Josmithr commented Feb 7, 2023

Does lerna not enforce matching deps via the --strict flag when run under pnpm, I take it?

With regards to number 2: I don't know how well it fits into the current CI infra, but it seems like the check should run at the root of each release group on a per-group basis if possible (rather than policy-check) IMO. But I don't know how reasonable a direction that is in the short term.

Update: Remembered that the strict flag is a bootstrap argument, and we no longer run that script. I wonder if there is a lerna config option to do something comperable?

@tylerbutler
Copy link
Member Author

Does lerna not enforce matching deps via the --strict flag when run under pnpm, I take it?

Update: Remembered that the strict flag is a bootstrap argument, and we no longer run that script. I wonder if there is a lerna config option to do something comperable?

Lerna is (appropriately, IMO) deprecating its package-management features:

Lerna has historically its own dependency management solution: lerna bootstrap. This was required because at the time when Lerna was first released, there were no native solutions available. Nowadays the modern package managers come with a built-in "workspaces" solution, so it is highly recommended to go with that instead. lerna bootstrap and other related commands will be officially deprecated in Lerna v7. See lerna/lerna#3410

Lerna also blocks bootstrap and similar commands when used with pnpm:

...block usage of bootstrap, link, and add commands. Instead, you should use pnpm commands directly to manage dependencies

Since they're getting out of the package management game altogether, and pnpm deliberately allows different versions per package, I think we have to look elsewhere. pnpm does have some good features for overriding dependencies, but it doesn't seem to fit our needs as well: https://pnpm.io/package_json#pnpmoverrides

@Josmithr
Copy link
Contributor

Josmithr commented Feb 8, 2023

Does lerna not enforce matching deps via the --strict flag when run under pnpm, I take it?
Update: Remembered that the strict flag is a bootstrap argument, and we no longer run that script. I wonder if there is a lerna config option to do something comperable?

Lerna is (appropriately, IMO) deprecating its package-management features:

Lerna has historically its own dependency management solution: lerna bootstrap. This was required because at the time when Lerna was first released, there were no native solutions available. Nowadays the modern package managers come with a built-in "workspaces" solution, so it is highly recommended to go with that instead. lerna bootstrap and other related commands will be officially deprecated in Lerna v7. See lerna/lerna#3410

Lerna also blocks bootstrap and similar commands when used with pnpm:

...block usage of bootstrap, link, and add commands. Instead, you should use pnpm commands directly to manage dependencies

Since they're getting out of the package management game altogether, and pnpm deliberately allows different versions per package, I think we have to look elsewhere. pnpm does have some good features for overriding dependencies, but it doesn't seem to fit our needs as well: https://pnpm.io/package_json#pnpmoverrides

100% agree with Lerna getting out of package management. And I agree that the pnpm feature doesn't look like the right answer. Linting seems like a reasonable choice to me. And it looks like syncpack supports both pnpm and yarn, so there shouldn't be an issue if/when we migrate.

@Abe27342
Copy link
Contributor

Abe27342 commented Feb 8, 2023

I agree with previous feedback that linting is a reasonable approach. It definitely seems a lot cleaner than hooking into pnpm behavior.

@github-actions github-actions bot removed the area: dds Issues related to distributed data structures label Feb 15, 2023
"webpack-dev-server",
],
packages: ["**"],
range: "~",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this line won't do anything since this entry is under versionGroups and not semverGroups, you might actually be able to remove this versionGroup and the behaviour will be the same as far as I can tell. Shout if I can help with anything confusing about syncpack 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @JamieMason, thanks for stopping by! 👋🏻 And thanks for writing/maintaining a great infrastructure tool!

this line won't do anything since this entry is under versionGroups and not semverGroups

Thank you, I missed that! I removed this and also started using the new custom types feature. Very useful! I may have found a bug which I'll point out in a separate comment.

Shout if I can help with anything confusing about syncpack

Is my understanding of the groups terminal output correct? Namely that they're numbered, but that the numeric order is the inverse of the array order in the config? When building the syncpack config, I found it very confusing to map the output to the group/rule (I think of them as "rules", like linting). Because a given package/dep can only fit into one group, it was difficult to debug why a given combo winds up in a group I didn't expect.

When you don't know what the state of your repo is -- what packages have mismatched deps that you want to ignore, etc. -- then it helps tremendously if it's easy to map syncpack's output -- this package has this dep that is mismatched -- to the "rule" that is being applied. A feature that may help is to add an optional identifier/name field to the semverGroups and versionGroups, and use those names in the terminal output/logs.

One minor note: we use an unusual semver range for our in-workspace dependencies. For example, ">=2.0.0-internal.3.0.0 <2.0.0-internal.3.1.0". Unless I ignore these dependencies with an ignored versionGroup, syncpack overrwrites the versions with "". Maybe a bug, but could also be expected/undefined behavior since the version range is unusual.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is my understanding of the groups terminal output correct? Namely that they're numbered, but that the numeric order is the inverse of the array order in the config? When building the syncpack config, I found it very confusing to map the output to the group/rule (I think of them as "rules", like linting). Because a given package/dep can only fit into one group, it was difficult to debug why a given combo winds up in a group I didn't expect.

When you don't know what the state of your repo is -- what packages have mismatched deps that you want to ignore, etc. -- then it helps tremendously if it's easy to map syncpack's output -- this package has this dep that is mismatched -- to the "rule" that is being applied.

Yeah 100% agree, it's weird how it is at the moment – I've created JamieMason/syncpack#120 to update that.

A feature that may help is to add an optional identifier/name field to the semverGroups and versionGroups, and use those names in the terminal output/logs.

Good idea, I've created JamieMason/syncpack#118 for that.

One minor note: we use an unusual semver range for our in-workspace dependencies. For example, ">=2.0.0-internal.3.0.0 <2.0.0-internal.3.1.0". Unless I ignore these dependencies with an ignored versionGroup, syncpack overrwrites the versions with "". Maybe a bug, but could also be expected/undefined behavior since the version range is unusual.

That sounds like a bug, I've created JamieMason/syncpack#119.

Thanks a lot for this feedback @tylerbutler, lots of good stuff has come out of it.

Comment on lines +44 to +50
// engines.npm should always use ^ range
{
dependencyTypes: ["enginesNpm"],
dependencies: ["**"],
packages: ["**"],
range: "^",
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JamieMason This is the possible bug in custom types. I've defined engines.node and engines.npm custom types, but in testing I could only get one to trigger at a time. I can do more testing and give more details if it's helpful. I'm also happy to file a proper issue. Thanks for taking the time to help!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably a bug, I'll take a look 👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to reproduce this in this commit but the tests I expected to fail are actually passing – could you create an issue with anything more you can think of? thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a bit more testing on this and found several inconsistencies. I wrote them up a readme on my branch along with how to reproduce them with our config. Details are at https://github.com/tylerbutler/FluidFramework/tree/syncpack-issues-repro

Copy link

@JamieMason JamieMason Feb 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @tylerbutler, I wasn't sure where best to reply but I've put some comments to what's in the readme in this gist

EDIT: Some of these issues have since been fixed in https://github.com/JamieMason/syncpack/releases/tag/9.7.4

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JamieMason Thanks for looking into those issues and resolving them so quickly! I definitely had some config bugs on my end so thanks for looking over them. I updated to 9.8.4 and verified that all the issues I noticed were fixed. And the new group headers are extremely helpful for me! Cheers!

@tylerbutler
Copy link
Member Author

I did some more thinking about this and I think policy-check is a better phase of the development cycle to run this check in, at least when running locally. It could even be a policy handler, but since policy-check is designed to work on a per-file basis, it would be hacky to do so.

In CI, I think we need to split out the tasks from lint and policy so we can run them separately both for efficiency and so it's clearer in CI what's failing and how to fix it. But that's a longer conversation that I'll write up a formal proposal for.

@tylerbutler tylerbutler changed the title feedback wanted: syncpack for enforcing dependency versions build(client): Add syncpack to find and fix mismatched dependency versions Feb 17, 2023
@tylerbutler tylerbutler marked this pull request as ready for review February 17, 2023 19:26
@tylerbutler tylerbutler requested review from msfluid-bot and a team as code owners February 17, 2023 19:26
Comment on lines 117 to 121
"@fluid-experimental/property-binder": ">=2.0.0-internal.3.1.0 <2.0.0-internal.4.0.0",
"@fluid-experimental/property-changeset": ">=2.0.0-internal.3.2.0 <2.0.0-internal.4.0.0",
"@fluid-experimental/property-dds": ">=2.0.0-internal.3.2.0 <2.0.0-internal.4.0.0",
"@fluid-experimental/property-properties": ">=2.0.0-internal.3.2.0 <2.0.0-internal.4.0.0",
"@fluid-experimental/property-proxy": ">=2.0.0-internal.3.2.0 <2.0.0-internal.4.0.0",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JamieMason This is a possible issue I missed earlier. I expected the mismatched @fluid-experimental/property-binder version to be caught, but it was not. Now that I think about it, if the version isn't understandable by syncpack, as these presumably are, then it likely just ignores it completely? I originally thought that it would be doing a string comparison. Feel free to ignore if this is working as intended.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 9.3.2 it should tell you that it can't fix them (where before it silently ignored them) but it should know when they mismatch and tell you there's a mismatch it cannot fix. Thanks for the repro branch, I'll see what I can find on this and the other problems.

"@tiny-calc/*",
"@graphql-codegen/cli",
"@graphql-codegen/typescript",
"@types/chrome",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious as to the motivation for pinning the @types packages here, but not their corresponding runtime packages. Don't we want them to "match" / get updated together?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to follow the conventions we have in place - which may have no reasoning. Updating these deps should be done separately from this PR, and probably individually because lots of things broke when I tested it.

All the deps updates in this PR narrow the dep range, with the exception of re-resizable, which went from an exact version to a ^ But everything else went from ^ to ~ or exact versions. That's one reason I feel safer about them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any plans to follow up on this later? This definitely feels like something we should address. But since these are existing patterns, I have no worries about deferring it outside of this PR.

@tylerbutler tylerbutler merged commit c010387 into microsoft:main Feb 17, 2023
@tylerbutler tylerbutler deleted the syncpack branch February 17, 2023 23:07
@tylerbutler
Copy link
Member Author

Thanks for the review, @Josmithr!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: dds: propertydds area: examples Changes that focus on our examples area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct base: main PRs targeted against main branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants