ChromaticHQ / calliope

An opinionated yet extensible toolset to handle common front-end tasks
3 stars 0 forks source link

Consider shrink-wrapping dependencies #8

Open agarzola opened 2 years ago

agarzola commented 2 years ago

Description

Downstream projects currently install Calliope’s dependencies (i.e. nested dependencies) whenever Calliope itself is installed. This is the way most nested dependencies are handled. However, Calliope being an internal tool designed to meet our needs as a team, not just the needs of individual projects, there is an opportunity here to limit maintenance overhead by reducing the number of dependency update PRs that downstream projects have to contend with.

Details of the Problem

Say Calliope has dependencies dep1, dep2, and dep3. That means that Downstream project project installs @chromatichq/calliope, dep1, dep2, and dep3. Say next week there are regular minor releases for dep1 and dep2, plus a security patch for dep3. After all three of those Dependabot PRs are merged in Calliope, we publish a new patch release for it that updates those dependencies. In the meantime, developers working on project will at some point be reviewing PRs for dep1, dep2, and dep3, and eventually will need to also review the PR for @chromatichq/calliope’s own update.

It is possible that the devs on project won’t get to one or all of the nested dependency updates in project before the new Calliope release is published, but chances are they will get to at least some of them. Worst case, our team will collectively review, approve, and merge 7 Dependabot PRs in the scenario above:

  1. dep1 minor release in Calliope.
  2. dep2 minor release in Calliope.
  3. dep3 security patch in Calliope.
  4. dep1 minor release in project.
  5. dep2 minor release in project.
  6. dep3 security patch in project.
  7. @chromatichq/calliope patch release in project.

Now consider that this scenario portrays a week during which only three nested dependencies published new releases, and only considers a single downstream project. Given the volume of front-end dependency releases we typically see in a given week and the number of active projects we maintain at any given moment, it is easy to see how there is an opportunity for reduction of overhead.

Here’s some fun math — Each week, our team may collectively handle this many Dependabot PRs:

// The +1 represents an additional round of reviews for those
// dependencies in the Calliope repo.
(dep_updates * (downstream_projects + 1)) + (calliope_releases * downstream_projects)

Say one week we have three dependency updates, across 5 projects, and we publish one calliope release updating those dependencies: (3 * (5 + 1)) + (1 * 5) = 23. Say we hit a really nice milestone of 10 active support projects all converted to use Calliope plus 5 regular projects also using Calliope. That same math for three dependency updates and one Calliope release gets us 63 Dependabot PRs.

That is in addition to Dependabot PRs for each project’s own direct dependencies (and their nested dependencies).

Proposed Solution

Research dependency shrink-wrapping strategies. The end result should be:

  1. Dependencies are still tracked in package.json and yarn.lock as usual.
  2. Every time we publish a new release, a distribution of Calliope is generated that includes all dependencies necessary for its operation.
  3. This generated distribution of Calliope is published to npm.
  4. Downstream projects will no longer have to install Calliope’s dependencies, as they will be packaged with Calliope itself.
  5. Calliope’s dependencies are kept up-to-date and new releases of Calliope are published regularly.
  6. Downstream projects get automated dependency update PRs every time Calliope publishes a new version.

Since downstream projects no would no longer receive Dependabot PRs for Calliope’s dependencies, this simplifies the overhead equation like so:

(dep_updates + (calliope_releases * downstream_projects))

At 3 dependencies, 5 projects and one Calliope release: our team will review 8 total Dependabot PRs. At 3 dependencies and 15 projects, we will review a total of 18 PRs. That is ~2/3 reduction in overhead.

Caveat: Increased Risk

There is a slight increase in risk here, but not one I believe to be very significant: Since a single Calliope release will (in the hypothetical scenarios above) include updates to three of its dependencies, there is an increased risk for a downstream project to run into regressions borne out of that release. The reason I don’t think this is a significant increase in risk is that this is already the case when any direct dependency update bumps up its own dependencies’ versions resulting a single Dependabot PR updates a handful (often many) dependencies in yarn.lock. In addition to this, even if each Dependabot PR only concerns itself with a single dependency (nested or otherwise), our developers typically review and merge these in batches anyway. So any time a regression makes its way to production, there’s a good chance we’ll be inspecting a handful of dependency updates to figure out the problem (that is, unless the regression somehow breaks a build, which is the best case scenario and relatively rare).

If anything, the slightly increased risk should encourage us to use the significant time savings of this strategy to develop strategies for testing PRs (visually or otherwise) across all of our projects in the interest of empowering developers to make well-informed, confident decisions when reviewing any PRs, not just Dependabot’s and not just for Calliope.

Alternatives Considered

There may be a way to configure Dependabot to ignore nested dependencies of a given package in downstream projects. That is: in CHQ’s Dependabot config, say: update all package, except for package that are nested dependencies of @chromatichq/calliope. If that is possible, then we would get the same effect: dep1, dep2, and dep3 only need to be updated in Calliope, and when a new release is published, then CHQ would get bumped on all three of those plus @chromatichq/calliope, since Calliope’s own pakcage.json would bump the minimum versions for the three dependencies updated that week.

I have no idea if this is possible at the moment, although a cursory search online did not yield anything promising.

Additional Context

n/a