Skip to content

Commit

Permalink
Fix constraints and update yarn lock at the end of release process (#145
Browse files Browse the repository at this point in the history
)
  • Loading branch information
cryptodev-2s authored Jun 25, 2024
1 parent 3fb952e commit 03b300a
Show file tree
Hide file tree
Showing 6 changed files with 411 additions and 1 deletion.
260 changes: 260 additions & 0 deletions src/functional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('create-release-branch (functional)', () => {
private: true,
workspaces: ['packages/*'],
scripts: { foo: 'bar' },
packageManager: '[email protected]',
});
expect(
await environment.readJsonFileWithinPackage('a', 'package.json'),
Expand Down Expand Up @@ -195,6 +196,7 @@ describe('create-release-branch (functional)', () => {
private: true,
workspaces: ['packages/*'],
scripts: { foo: 'bar' },
packageManager: '[email protected]',
});
expect(
await environment.readJsonFileWithinPackage('a', 'package.json'),
Expand Down Expand Up @@ -640,6 +642,263 @@ describe('create-release-branch (functional)', () => {
);
});

it('updates the dependency version in package "b" when package "a" version is bumped', async () => {
await withMonorepoProjectEnvironment(
{
packages: {
$root$: {
name: '@scope/monorepo',
version: '1.0.0',
directoryPath: '.',
},
a: {
name: '@scope/a',
version: '1.0.0',
directoryPath: 'packages/a',
},
b: {
name: '@scope/b',
version: '2.0.0',
directoryPath: 'packages/b',
},
},
workspaces: {
'.': ['packages/*'],
},
},
async (environment) => {
await environment.updateJsonFileWithinPackage('b', 'package.json', {
dependencies: {
'@scope/a': '1.0.0',
},
});
const constraintsProContent = `
% All packages must have a name and version defined.
\\+ gen_enforced_field(_, 'name', null).
\\+ gen_enforced_field(_, 'version', null).
% All version ranges used to reference one workspace package in another workspace package's \`dependencies\` or \`devDependencies\` must match the current version of that package.
gen_enforced_dependency(Pkg, DependencyIdent, CorrectDependencyRange, DependencyType) :-
DependencyType \\= 'peerDependencies',
workspace_has_dependency(Pkg, DependencyIdent, _, DependencyType),
workspace_ident(DepPkg, DependencyIdent),
workspace_version(DepPkg, DependencyVersion),
atomic_list_concat(['^', DependencyVersion], CorrectDependencyRange),
Pkg \\= DepPkg. % Ensure we do not add self-dependency
% Entry point to check all constraints.
workspace_package(Pkg) :-
package_json(Pkg, _, _).
enforce_all :-
workspace_package(Pkg),
enforce_has_name(Pkg),
enforce_has_version(Pkg),
(package_json(Pkg, 'dependencies', Deps) -> enforce_dependencies(Pkg, Deps) ; true).
enforce_has_name(Pkg) :-
package_json(Pkg, 'name', _).
enforce_has_version(Pkg) :-
package_json(Pkg, 'version', _).
enforce_dependencies(_, []).
enforce_dependencies(Pkg, [DepPkg-DepVersion | Rest]) :-
workspace_package(DepPkg),
package_json(DepPkg, 'version', DepVersion),
enforce_dependency_version(Pkg, DepPkg),
enforce_dependencies(Pkg, Rest).
enforce_dependency_version(Pkg, DepPkg) :-
package_json(Pkg, 'dependencies', Deps),
package_json(DepPkg, 'version', DepVersion),
member(DepPkg-DepVersion, Deps).
update_dependency_version(Pkg, DepPkg) :-
package_json(Pkg, 'dependencies', Deps),
package_json(DepPkg, 'version', DepVersion),
\\+ member(DepPkg-DepVersion, Deps),
Pkg \\= DepPkg, % Ensure we do not add self-dependency
set_package_json(Pkg, 'dependencies', DepPkg, DepVersion).
`;

await environment.writeFile('constraints.pro', constraintsProContent);
await environment.runTool({
releaseSpecification: {
packages: {
a: 'major',
b: 'intentionally-skip',
},
},
});

expect(
await environment.readJsonFileWithinPackage('a', 'package.json'),
).toStrictEqual({
name: '@scope/a',
version: '2.0.0',
});
expect(
await environment.readJsonFileWithinPackage('b', 'package.json'),
).toStrictEqual({
name: '@scope/b',
version: '2.0.0',
dependencies: { '@scope/a': '^2.0.0' },
});
},
);
});

it('updates the yarn lock file', async () => {
await withMonorepoProjectEnvironment(
{
packages: {
$root$: {
name: '@scope/monorepo',
version: '1.0.0',
directoryPath: '.',
},
a: {
name: '@scope/a',
version: '1.0.0',
directoryPath: 'packages/a',
},
b: {
name: '@scope/b',
version: '2.0.0',
directoryPath: 'packages/b',
},
},
workspaces: {
'.': ['packages/*'],
},
},
async (environment) => {
await environment.updateJsonFileWithinPackage('b', 'package.json', {
dependencies: {
'@scope/a': '1.0.0',
},
});
const constraintsProContent = `
% All packages must have a name and version defined.
\\+ gen_enforced_field(_, 'name', null).
\\+ gen_enforced_field(_, 'version', null).
% All version ranges used to reference one workspace package in another workspace package's \`dependencies\` or \`devDependencies\` must match the current version of that package.
gen_enforced_dependency(Pkg, DependencyIdent, CorrectDependencyRange, DependencyType) :-
DependencyType \\= 'peerDependencies',
workspace_has_dependency(Pkg, DependencyIdent, _, DependencyType),
workspace_ident(DepPkg, DependencyIdent),
workspace_version(DepPkg, DependencyVersion),
atomic_list_concat(['^', DependencyVersion], CorrectDependencyRange),
Pkg \\= DepPkg. % Ensure we do not add self-dependency
% Entry point to check all constraints.
workspace_package(Pkg) :-
package_json(Pkg, _, _).
enforce_all :-
workspace_package(Pkg),
enforce_has_name(Pkg),
enforce_has_version(Pkg),
(package_json(Pkg, 'dependencies', Deps) -> enforce_dependencies(Pkg, Deps) ; true).
enforce_has_name(Pkg) :-
package_json(Pkg, 'name', _).
enforce_has_version(Pkg) :-
package_json(Pkg, 'version', _).
enforce_dependencies(_, []).
enforce_dependencies(Pkg, [DepPkg-DepVersion | Rest]) :-
workspace_package(DepPkg),
package_json(DepPkg, 'version', DepVersion),
enforce_dependency_version(Pkg, DepPkg),
enforce_dependencies(Pkg, Rest).
enforce_dependency_version(Pkg, DepPkg) :-
package_json(Pkg, 'dependencies', Deps),
package_json(DepPkg, 'version', DepVersion),
member(DepPkg-DepVersion, Deps).
update_dependency_version(Pkg, DepPkg) :-
package_json(Pkg, 'dependencies', Deps),
package_json(DepPkg, 'version', DepVersion),
\\+ member(DepPkg-DepVersion, Deps),
Pkg \\= DepPkg, % Ensure we do not add self-dependency
set_package_json(Pkg, 'dependencies', DepPkg, DepVersion).
`;
await environment.writeFile('constraints.pro', constraintsProContent);
const outdatedLockfile = `
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 6
"@scope/a@^1.0.0, @scope/a@workspace:packages/a":
version: 0.0.0-use.local
resolution: "@scope/a@workspace:packages/a"
languageName: unknown
linkType: soft
"@scope/b@workspace:packages/b":
version: 0.0.0-use.local
resolution: "@scope/b@workspace:packages/b"
dependencies:
"@scope/a": ^1.0.0
languageName: unknown
linkType: soft
"@scope/monorepo@workspace:.":
version: 0.0.0-use.local
resolution: "@scope/monorepo@workspace:."
languageName: unknown
linkType: soft`;
await environment.writeFile('yarn.lock', outdatedLockfile);
await environment.runTool({
releaseSpecification: {
packages: {
a: 'major',
b: 'intentionally-skip',
},
},
});

const updatedLockfile = `# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 6
"@scope/a@^2.0.0, @scope/a@workspace:packages/a":
version: 0.0.0-use.local
resolution: "@scope/a@workspace:packages/a"
languageName: unknown
linkType: soft
"@scope/b@workspace:packages/b":
version: 0.0.0-use.local
resolution: "@scope/b@workspace:packages/b"
dependencies:
"@scope/a": ^2.0.0
languageName: unknown
linkType: soft
"@scope/monorepo@workspace:.":
version: 0.0.0-use.local
resolution: "@scope/monorepo@workspace:."
languageName: unknown
linkType: soft
`;

expect(await environment.readFile('yarn.lock')).toStrictEqual(
updatedLockfile,
);
},
);
});

it('does not update the versions of any packages that have been tagged with intentionally-skip', async () => {
await withMonorepoProjectEnvironment(
{
Expand Down Expand Up @@ -691,6 +950,7 @@ describe('create-release-branch (functional)', () => {
version: '2.0.0',
private: true,
workspaces: ['packages/*'],
packageManager: '[email protected]',
});
expect(
await environment.readJsonFileWithinPackage('a', 'package.json'),
Expand Down
30 changes: 30 additions & 0 deletions src/monorepo-workflow-operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import type { ReleaseSpecification } from './release-specification.js';
import * as releasePlanModule from './release-plan.js';
import type { ReleasePlan } from './release-plan.js';
import * as repoModule from './repo.js';
import * as yarnCommands from './yarn-commands.js';
import * as workflowOperations from './workflow-operations.js';

jest.mock('./editor');
jest.mock('./release-plan');
jest.mock('./release-specification');
jest.mock('./repo');
jest.mock('./yarn-commands.js');

/**
* Tests the given path to determine whether it represents a file.
Expand Down Expand Up @@ -65,6 +67,12 @@ function getDependencySpies() {
planReleaseSpy: jest.spyOn(releasePlanModule, 'planRelease'),
executeReleasePlanSpy: jest.spyOn(releasePlanModule, 'executeReleasePlan'),
commitAllChangesSpy: jest.spyOn(repoModule, 'commitAllChanges'),
fixConstraintsSpy: jest.spyOn(yarnCommands, 'fixConstraints'),
updateYarnLockfileSpy: jest.spyOn(yarnCommands, 'updateYarnLockfile'),
deduplicateDependenciesSpy: jest.spyOn(
yarnCommands,
'deduplicateDependencies',
),
};
}

Expand Down Expand Up @@ -180,6 +188,9 @@ async function setupFollowMonorepoWorkflow({
planReleaseSpy,
executeReleasePlanSpy,
commitAllChangesSpy,
fixConstraintsSpy,
updateYarnLockfileSpy,
deduplicateDependenciesSpy,
} = getDependencySpies();
const editor = buildMockEditor();
const releaseSpecificationPath = path.join(
Expand Down Expand Up @@ -273,6 +284,9 @@ async function setupFollowMonorepoWorkflow({
releasePlan,
releaseVersion,
releaseSpecificationPath,
fixConstraintsSpy,
updateYarnLockfileSpy,
deduplicateDependenciesSpy,
};
}

Expand Down Expand Up @@ -405,6 +419,9 @@ describe('monorepo-workflow-operations', () => {
createReleaseBranchSpy,
commitAllChangesSpy,
projectDirectoryPath,
fixConstraintsSpy,
updateYarnLockfileSpy,
deduplicateDependenciesSpy,
} = await setupFollowMonorepoWorkflow({
sandbox,
releaseVersion,
Expand Down Expand Up @@ -445,6 +462,19 @@ describe('monorepo-workflow-operations', () => {
`Update Release ${releaseVersion}`,
);

expect(fixConstraintsSpy).toHaveBeenCalledTimes(1);
expect(fixConstraintsSpy).toHaveBeenCalledWith(projectDirectoryPath);

expect(updateYarnLockfileSpy).toHaveBeenCalledTimes(1);
expect(updateYarnLockfileSpy).toHaveBeenCalledWith(
projectDirectoryPath,
);

expect(deduplicateDependenciesSpy).toHaveBeenCalledTimes(1);
expect(deduplicateDependenciesSpy).toHaveBeenCalledWith(
projectDirectoryPath,
);

// Second call of followMonorepoWorkflow

createReleaseBranchSpy.mockResolvedValueOnce({
Expand Down
8 changes: 8 additions & 0 deletions src/monorepo-workflow-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import {
validateReleaseSpecification,
} from './release-specification.js';
import { createReleaseBranch } from './workflow-operations.js';
import {
deduplicateDependencies,
fixConstraints,
updateYarnLockfile,
} from './yarn-commands.js';

/**
* For a monorepo, the process works like this:
Expand Down Expand Up @@ -147,6 +152,9 @@ export async function followMonorepoWorkflow({
});
await executeReleasePlan(project, releasePlan, stderr);
await removeFile(releaseSpecificationPath);
await fixConstraints(project.directoryPath);
await updateYarnLockfile(project.directoryPath);
await deduplicateDependencies(project.directoryPath);
await commitAllChanges(
project.directoryPath,
`Update Release ${newReleaseVersion}`,
Expand Down
Loading

0 comments on commit 03b300a

Please sign in to comment.