Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
feat: support grouping packages similar to lerna fixed mode #415 (#453)
Browse files Browse the repository at this point in the history
This introduces the 'packageGroupManifestField' config option to create groups of packages based on a common field in each workspace manifest (package.json). All packages within a group will have the same version strategy and version number at publish time, and will be grouped under a single git tag and single release (under the GitHub plugin).
  • Loading branch information
Noah authored Dec 25, 2021
1 parent 559de51 commit 1e8711a
Show file tree
Hide file tree
Showing 42 changed files with 3,218 additions and 4,451 deletions.
5 changes: 2 additions & 3 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
codecov:
require_ci_to_pass: true

notify:
wait_for_ci: true
notify:
wait_for_ci: true

coverage:
status:
Expand Down
40 changes: 20 additions & 20 deletions gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-markdown": "^6.0.2",
"remark-external-links": "^8.0.0",
"sanitize.css": "^12.0.1"
"react-markdown": "^7.1.1",
"remark-external-links": "^9.0.1",
"sanitize.css": "^13.0.0"
},
"devDependencies": {
"@mdx-js/loader": "^1.6.22",
"@mdx-js/mdx": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@types/loader-utils": "^2",
"@types/mdx-js__react": "^1",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.8",
"@types/react-helmet": "^6.1.1",
"gatsby": "^3.8.1",
"gatsby-plugin-gatsby-cloud": "^2.8.1",
"gatsby-plugin-image": "^1.8.0",
"gatsby-plugin-manifest": "^3.8.0",
"gatsby-plugin-mdx": "^2.9.0",
"gatsby-plugin-offline": "^4.8.0",
"gatsby-plugin-react-helmet": "^4.8.0",
"gatsby-plugin-sharp": "^3.8.0",
"gatsby-remark-autolink-headers": "^4.6.0",
"gatsby-remark-images": "^5.6.0",
"gatsby-source-filesystem": "^3.8.0",
"gatsby-transformer-sharp": "^3.8.0",
"loader-utils": "^2.0.0",
"prop-types": "^15.7.2"
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-helmet": "^6.1.5",
"gatsby": "^4.4.0",
"gatsby-plugin-gatsby-cloud": "^4.4.0",
"gatsby-plugin-image": "^2.4.0",
"gatsby-plugin-manifest": "^4.4.0",
"gatsby-plugin-mdx": "^3.4.0",
"gatsby-plugin-offline": "^5.4.0",
"gatsby-plugin-react-helmet": "^5.4.0",
"gatsby-plugin-sharp": "^4.4.0",
"gatsby-remark-autolink-headers": "^5.4.0",
"gatsby-remark-images": "^6.4.0",
"gatsby-source-filesystem": "^4.4.0",
"gatsby-transformer-sharp": "^4.4.0",
"loader-utils": "^3.2.0",
"prop-types": "^15.8.0"
},
"scripts": {
"build:gatsby": "rm -rf .cache public && gatsby build --prefix-paths \"$@\" && rsync -rtuc --delete public/ ../docs/",
Expand Down
4 changes: 3 additions & 1 deletion gatsby/src/components/TypeDocInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ const stringifyType = (data: any): string => {
}

const stringifyComment = (data: any): string => {
return `${data?.shortText ?? ''}\n\n${data?.text ?? ''}`.trim()
const defaultValue = data?.tags?.find((tag) => tag.tag === 'default')?.text
const body = `${data?.shortText ?? ''}\n\n${data?.text ?? ''}`.trim()
return `Default: ${defaultValue?.trim() ?? '_No Default_'}\n\n${body}`
}

const InterfaceChildRow: React.FC<{ data: any }> = ({ data }) => {
Expand Down
10 changes: 6 additions & 4 deletions gatsby/src/pages/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ In the first stage of the pipeline, after initialization, we use the loaded conv

The determination of the version strategies by package is the beginning of the creation of Monodeploy's "changeset". This changeset is enhanced a few times throughout the pipeline before ultimately being written out to the changeset file.

Once we have the explicit version strategies, we determine the implicit strategies. This is accomplished by traversing the dependencies graph and finding all dependents of the explicitly bumped packages, excluding dependents which themselves are explicitly bumped. These dependent packages are automatically given a patch version strategy. This is to ensure downstream consumers of the explicitly bumped packages receive updates.
Once we have the explicit version strategies, we determine the implicit strategies. This is accomplished by traversing the dependencies graph and finding all dependents of the explicitly bumped packages, excluding dependents which themselves are explicitly bumped. These dependent packages are automatically given a patch version strategy. This is to ensure downstream consumers of the explicitly bumped packages receive updates. If using package groups (similar to Lerna's fixed mode), the greatest strategy is assumed for each group. Note that if no groups are specified, monodeploy defaults to treat each package as its own independent group.

With a collection of version strategies associated with the individual packages, are now read to apply the version strategy (e.g. "minor") to the latest version of each package. Package manifests (`package.json` files) are updated so the `version` of the manifest, and the versions listed in the `dependencies`, `peerDependencies`, and `devDependencies` fields reflect the latest versions we are about to publish.
The collection of version strategies associated with the individual packages are now read to apply the version strategy (e.g. "minor") to the latest version of each package. Each package group resolves to use the largest version number among the group for each of its members. Package manifests (`package.json` files) are updated so the `version` of the manifest, and the versions listed in the `dependencies`, `peerDependencies`, and `devDependencies` fields reflect the latest versions we are about to publish.

Once the manifests of all packages we will be publishing are updated, we trigger a publish to the specified NPM registry. Before and after publishing each package, the appropriate npm lifecycle hooks are executed. These hooks are guaranteed to be executed in topological order, if the topological configuration option is enabled. After updating the remote registry, the release git tags are created.
After manifest files are updated, Yarn triggers an install to update the Yarn lockfile, as well as any other install state files.

At this point, the changeset information for each published package is piped through the loaded conventional changelog config, and changelog files are written. Depending on configuration options, the created git tags, the changelog files, and the modified package manifests are committed and pushed to the remote git repository.
Once the manifests of all packages we will be publishing are updated, we trigger a publish to the specified NPM-like registry. Before and after publishing each package, the appropriate npm lifecycle hooks are executed. These hooks are guaranteed to be executed in topological order, if the topological configuration option is enabled.

At this point, the changeset information for each published package is piped through the loaded conventional changelog config, and changelog files are written. Depending on configuration options, the changelog files, and the modified package manifests are committed. If enabled, release git tags are created then created, and the tagged commit is pushed to the remote git repository.

At various steps throughout the pipepline, Monodeploy plugin hooks will be triggered.
4 changes: 4 additions & 0 deletions gatsby/src/pages/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ As an added benefit, downstream projects using systems like Renovate or Dependab

If there is a use case where you believe this behaviour is unwarranted, please open an issue and we'll be glad to discuss.

### How can I achieve behaviour similar to Lerna's fixed version mode?

Starting in Monodeploy v3, fixed version mode can be achieved by setting the `packageGroupManifestField` in your monodeploy configuration, or via the CLI argument. This config option should be set to some field in each workspace's manifest file to indicate fixed version grouping. For example, you can add a "group" field set to "components" for some workspaces and "utils" for other workspaces. Monodeploy will then ensure all workspaces with changes (or dependent changes) in the "components" group have the same version bump. To achieve fixed versioning of all workspaces, you need to specify a field that all workspaces have in common. You can also enforce this with the use of the Yarn Constraints plugin.

### How does Monodeploy differ from:

#### Lerna
Expand Down
1 change: 1 addition & 0 deletions packages/changelog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"registry": "https://registry.npmjs.org/",
"types": "./lib/index.d.ts"
},
"group": "monodeploy",
"files": [
"lib"
],
Expand Down
44 changes: 25 additions & 19 deletions packages/changelog/src/prependChangelogFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
setupContext,
setupTestRepository,
} from '@monodeploy/test-utils'
import { YarnContext } from '@monodeploy/types'
import { ChangesetSchema, YarnContext } from '@monodeploy/types'
import { Workspace, structUtils } from '@yarnpkg/core'
import { PortablePath } from '@yarnpkg/fslib'

Expand Down Expand Up @@ -41,8 +41,8 @@ describe('prependChangelogFile', () => {
changelogFilename: undefined,
})
const context = await setupContext(cwd)
const changeset = {
'1.0.0': { version: '1.0.0', changelog: 'wowchanges', tag: null },
const changeset: ChangesetSchema = {
'pkg-1': { version: '1.0.0', changelog: 'wowchanges', tag: null, group: 'pkg-1' },
}

// TODO: Better assertion.
Expand All @@ -69,8 +69,8 @@ describe('prependChangelogFile', () => {
changelogFilename: mockChangelogFilename,
})
const context = await setupContext(cwd)
const changeset = {
'1.0.0': { version: '1.0.0', changelog: 'wowchanges', tag: null },
const changeset: ChangesetSchema = {
'pkg-1': { version: '1.0.0', changelog: 'wowchanges', tag: null, group: 'pkg-1' },
}

// We'll grab a handle so prepend won't be able to write
Expand Down Expand Up @@ -98,8 +98,8 @@ describe('prependChangelogFile', () => {
changelogFilename: 'changelog',
})
const context = await setupContext(cwd)
const changeset = {
'1.0.0': { version: '1.0.0', changelog: 'wowchanges', tag: null },
const changeset: ChangesetSchema = {
'pkg-1': { version: '1.0.0', changelog: 'wowchanges', tag: null, group: 'pkg-1' },
}
await createFile({ filePath: 'changelog', cwd, content: 'wonomarker' })
await expect(async () =>
Expand Down Expand Up @@ -128,8 +128,8 @@ describe('prependChangelogFile', () => {
dryRun: true,
})
const context = await setupContext(cwd)
const changeset = {
'1.0.0': { version: '1.0.0', changelog: 'wowchanges', tag: null },
const changeset: ChangesetSchema = {
'pkg-1': { version: '1.0.0', changelog: 'wowchanges', tag: null, group: 'pkg-1' },
}

// TODO: Better assertion.
Expand Down Expand Up @@ -159,16 +159,18 @@ describe('prependChangelogFile', () => {
cwd: workspacePath,
content: '<!-- MONODEPLOY:BELOW -->',
})
const changeset = {
const changeset: ChangesetSchema = {
'pkg-1': {
version: '1.0.0',
changelog: 'wowchanges\nthisisachangelog',
tag: null,
group: 'pkg-1',
},
'pkg-2': {
version: '1.1.0',
changelog: 'just a version bump',
tag: null,
group: 'pkg-2',
},
}

Expand All @@ -183,7 +185,7 @@ describe('prependChangelogFile', () => {
encoding: 'utf8',
})

expect(changelogContents).toEqual(expect.stringContaining(changeset['pkg-1'].changelog))
expect(changelogContents).toEqual(expect.stringContaining(changeset['pkg-1'].changelog!))
})

it('creates the changelog file if it does not exist', async () => {
Expand All @@ -196,16 +198,18 @@ describe('prependChangelogFile', () => {
changelogFilename: mockChangelogFilename,
})
const context = await setupContext(cwd)
const changeset = {
const changeset: ChangesetSchema = {
'pkg-1': {
version: '1.0.0',
changelog: 'wowchanges\nthisisachangelog',
tag: null,
group: 'pkg-1',
},
'pkg-2': {
version: '1.1.0',
changelog: 'just a version bump',
tag: null,
group: 'pkg-2',
},
}

Expand All @@ -220,7 +224,7 @@ describe('prependChangelogFile', () => {
encoding: 'utf8',
})

expect(changelogContents).toEqual(expect.stringContaining(changeset['pkg-1'].changelog))
expect(changelogContents).toEqual(expect.stringContaining(changeset['pkg-1'].changelog!))
})

it('writes changelogs for each package if token present', async () => {
Expand All @@ -232,16 +236,18 @@ describe('prependChangelogFile', () => {
changelogFilename: '<packageDir>/CHANGELOG.md',
})
const context = await setupContext(cwd)
const changeset = {
const changeset: ChangesetSchema = {
'pkg-1': {
version: '1.0.0',
changelog: 'wowchanges\nthisisachangelog',
tag: null,
group: 'pkg-1',
},
'pkg-2': {
version: '1.1.0',
changelog: 'just a version bump',
tag: null,
group: 'pkg-2',
},
}
const workspaces = new Set([getWorkspace(context, 'pkg-1'), getWorkspace(context, 'pkg-2')])
Expand All @@ -253,19 +259,19 @@ describe('prependChangelogFile', () => {
{ encoding: 'utf8' },
)

expect(onDiskChangelogPkg1).toEqual(expect.stringContaining(changeset['pkg-1'].changelog))
expect(onDiskChangelogPkg1).toEqual(expect.stringContaining(changeset['pkg-1'].changelog!))
expect(onDiskChangelogPkg1).not.toEqual(
expect.stringContaining(changeset['pkg-2'].changelog),
expect.stringContaining(changeset['pkg-2'].changelog!),
)

const onDiskChangelogPkg2 = await fs.readFile(
path.join(cwd, 'packages', 'pkg-2', 'CHANGELOG.md'),
{ encoding: 'utf8' },
)

expect(onDiskChangelogPkg2).toEqual(expect.stringContaining(changeset['pkg-2'].changelog))
expect(onDiskChangelogPkg2).toEqual(expect.stringContaining(changeset['pkg-2'].changelog!))
expect(onDiskChangelogPkg2).not.toEqual(
expect.stringContaining(changeset['pkg-1'].changelog),
expect.stringContaining(changeset['pkg-1'].changelog!),
)
})

Expand All @@ -284,7 +290,7 @@ describe('prependChangelogFile', () => {
cwd: workspacePath,
content: '<!-- MONODEPLOY:BELOW -->',
})
const changeset = {}
const changeset: ChangesetSchema = {}

const contentsBefore = await fs.readFile(path.join(cwd, mockChangelogFilename), {
encoding: 'utf8',
Expand Down
6 changes: 6 additions & 0 deletions packages/changelog/src/writeChangesetFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ describe('writeChangesetFile', () => {
['pkg-1', '[email protected]'],
['pkg-2', '[email protected]'],
]),
workspaceGroups: new Map([
['pkg-1', new Set(['pkg-1'])],
['pkg-2', new Set(['pkg-2'])],
]),
})

expect(changeset).toEqual(
Expand All @@ -83,13 +87,15 @@ describe('writeChangesetFile', () => {
strategy: 'major',
tag: '[email protected]',
version: '2.0.0',
group: 'pkg-1',
},
'pkg-2': {
changelog: expect.stringContaining('fancy change'),
previousVersion: '4.5.2',
strategy: 'minor',
tag: '[email protected]',
version: '4.6.0',
group: 'pkg-2',
},
}),
)
Expand Down
10 changes: 10 additions & 0 deletions packages/changelog/src/writeChangesetFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ const writeChangesetFile = async ({
nextTags,
versionStrategies,
gitTags,
workspaceGroups,
}: {
config: MonodeployConfiguration
context: YarnContext
previousTags: PackageVersionMap
nextTags: PackageVersionMap
versionStrategies: PackageStrategyMap
gitTags?: Map<string, string>
workspaceGroups: Map<string, Set<string>>
}): Promise<ChangesetSchema> => {
const changesetData: ChangesetSchema = {}

Expand All @@ -47,6 +49,14 @@ const writeChangesetFile = async ({
changelog,
tag: gitTags?.get(packageName) ?? null,
strategy: versionStrategy?.type ?? null,
group: packageName, // overwritten below
}
}

for (const [groupKey, group] of workspaceGroups.entries()) {
for (const packageName of group) {
if (!changesetData[packageName]) continue
changesetData[packageName].group = groupKey ?? packageName
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"bin": "./lib/cli.js",
"main": "./lib/cli.js"
},
"group": "monodeploy",
"bin": "./src/cli.ts",
"main": "./src/cli.ts",
"files": [
Expand Down
Loading

0 comments on commit 1e8711a

Please sign in to comment.