-
Notifications
You must be signed in to change notification settings - Fork 771
RFC: Packages extensibility #849
Description
Problem
The tagling for changesets is A way to manage your versioning and changelogs with a focus on monorepos, currently it is A way to manage your versioning and changelogs for NPM packages with a focus on monorepos
The goal of this RFC is to suggest how we could realise the dream while not only supporting NPM.
There are also a bunch of issues talking about changing behavior around NPM publish.
Related discussions / PRs
#171
#218
#399
#425
#580
#654
#778
#800
#801
Proposed solution
Packages have a few basic components:
- A name
- A version
- Dependencies to other packages in the repo
- Publish status with version
- Ability to be published
If changesets allows the concept of 'packages' to be extensible then a repository could have multiple types of packages registered. For example in an NX repo you could expose NX projects via the plugin.
It would also allow non-node projects to be tracked by creating a plugin to support Ruby Gems, or .net NuGet packages etc.
Package interface
type PublishedState = "never" | "published" | "only-pre";
interface ChangesetPackage {
name: string
version: string
kind: string
directory: string
dependencies: string[]
getPublishInfo(): Promise<{
publishedState: PublishedState
publishedVersions: string[]
}>
/** assumes updateVersion has run */
publish(): Promise<{}>
updateVersion(version: string): Promise<{}>
}Config
"packages": [
"@changesets/packages-nx",
[
"@changesets/packages-npm",
{ "repository": "npm", ignore: ['some-package'] }
],
[
"@changesets/packages-nuget",
{ }
]
]
Core workflows
Some of these diagrams could be improved, but is a first pass at visualising the workflows. Also have left out things like pre-release to keep things simple.
Get Packages
sequenceDiagram
cli ->>+ getPackages:
getPackages ->> config: getPackagesPlugins
config ->> getPackages: PackagePlugin[]
loop map each plugin
getPackages ->> plugin: getPackages
plugin ->> getPackages: ChangesetPackage[]
end
note over getPackages: Input ChangesetPackage[][]
loop flatMap packages
alt !exists in map
getPackages ->> getPackages: add to map
end
end
getPackages ->>- cli: ChangesetPackage[]
Version
sequenceDiagram
note over getPackages: *Changed
version ->> getPackages:
getPackages ->> version: ChangesetPackage[]
version ->> readChangeset:
readChangeset ->> version: NewChangeset[]
version ->> assembleReleasePlan: changesets, packages
note over assembleReleasePlan: Filters changesets from ignored packages
note over assembleReleasePlan: Flatten changesets into package releases
note over assembleReleasePlan: Add changes due to dependencies into releases
assembleReleasePlan ->> version: { changesets: NewChangeset[], releases: ComprehensiveRelease[] }
version ->> applyReleasePlan:
loop for each release
note over package: *Changed from directly updating package.json
applyReleasePlan ->> package: updateVersion()
applyReleasePlan ->> config: Get changelog plugin
note over applyReleasePlan: Gets commit for each change
note over applyReleasePlan: Uses plugin to format lines
applyReleasePlan ->> changeset file: Updates CHANGELOG.md for project
end
Publish
This function gets rejigged a fair bit. Packages marked as private never call the publish() function on the package, they are just tagged (assuming the config allows that).
sequenceDiagram
publish ->> getPackages:
getPackages ->> version: ChangesetPackage[]
loop parallel map for each package
alt is private package
publish ->> git: get tag for current version
alt tag exists
publish -x publish: do nothing
note over publish: return { published: false }
else
publish ->> git: tag release
note over publish: return { published: true }
end
else
publish ->> package: publish()
alt success
publish ->> git: tag release
note over publish: return { published: true }
end
end
end
Challenges
Source of truth?
While building #662 one of the challenges of tracking private packages is that Changeset uses the published NPM info as the source of truth but this isn't possible for the private packages. Those instead use the git tags in the repo.
I think we should use tags in the git repo as our state, then we can put checks in place to 'refresh' if things get out of sync, ie publish succeeds but then tagging fails. We can detect and fix this state.
This approach will not work if you can publish without tagging though, or not tag per repo. Either that or only private packages rely on git tags being the source of truth?
Duplicate discovery
Package discovery will be done by multiple plugins, if multiple plugins discover a project I think we simply take the one from the plugin registered first (so ordering of the plugins matters).