Skip to content

Commit

Permalink
fix lint
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardfoyle committed Apr 5, 2024
1 parent 2480023 commit cce9e9e
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 248 deletions.
69 changes: 69 additions & 0 deletions scripts/components/dist_tag_mover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { EOL } from 'os';
import { NpmClient } from './npm_client.js';
import { releaseTagToNameAndVersion } from './release_tag_to_name_and_version.js';

type DistTagMoveAction = {
/**
* An NPM dist-tag
*/
distTag: string;
/**
* This is a string of the form <packageName>@<version>
*/
releaseTag: string;
};

/**
* Handles moving npm dist-tags from one package version to another
*/
export class DistTagMover {
/**
* Initialize with an npmClient
*/
constructor(private readonly npmClient: NpmClient) {}

/**
* Given a list of sourceReleaseTags and destReleaseTags,
* any npm dist-tags that are pointing to a sourceReleaseTag will be moved to point to the corresponding destReleaseTag
*/
moveDistTags = async (
sourceReleaseTags: string[],
destReleaseTags: string[]
) => {
const moveActions: DistTagMoveAction[] = [];

for (const sourceReleaseTag of sourceReleaseTags) {
const { packageName, version: sourceVersion } =
releaseTagToNameAndVersion(sourceReleaseTag);

const { 'dist-tags': distTags } = await this.npmClient.getPackageInfo(
sourceReleaseTag
);

Object.entries(distTags).forEach(([tagName, versionAtTag]) => {
if (versionAtTag !== sourceVersion) {
return;
}
const destReleaseTag = destReleaseTags.find((releaseTag) =>
releaseTag.includes(packageName)
);
if (!destReleaseTag) {
// this should never happen because of the upstream logic that resolves the corresponding versions
throw new Error(
`No corresponding destination release tag found for ${sourceReleaseTag}`
);
}
moveActions.push({
releaseTag: destReleaseTag,
distTag: tagName,
});
});
}

for (const { distTag, releaseTag } of moveActions) {
console.log(`Moving dist tag "${distTag}" to release tag ${releaseTag}`);
await this.npmClient.setDistTag(releaseTag, distTag);
console.log(`Done!${EOL}`);
}
};
}
11 changes: 8 additions & 3 deletions scripts/components/git_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ export class GitClient {
};

/**
* Returns true if the git tree is clean and false if it is dirty
* Throws if there are uncommitted changes in the repo
*/
isWorkingTreeClean = async () => {
ensureWorkingTreeIsClean = async () => {
const { stdout } = await this.exec`git status --porcelain`;
return !stdout.trim();
const isDirty = stdout.trim();
if (isDirty) {
throw new Error(
'Dirty working tree detected. Commit or stash changes to continue.'
);
}
};

getCurrentBranch = async () => {
Expand Down
91 changes: 91 additions & 0 deletions scripts/components/release_deprecator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { EOL } from 'os';
import { GitClient } from './git_client.js';
import { NpmClient } from './npm_client.js';
import { GithubClient } from './github_client.js';
import { DistTagMover } from './dist_tag_mover.js';

/**
*
*/
export class ReleaseDeprecator {
/**
* Initialize with deprecation config and necessary clients
*/
constructor(
private readonly gitRefToStartReleaseSearchFrom: string,
private readonly deprecationMessage: string,
private readonly githubClient: GithubClient,
private readonly gitClient: GitClient,
private readonly npmClient: NpmClient,
private readonly distTagMover: DistTagMover
) {}

/**
* This method deprecates a set of package versions that were released by a single release commit.
*
* The steps that it takes are
* 1. Given a starting commit, find the most recent release commit (this could be the commit itself)
* 2. Find the git tags associated with that commit. These are the package versions that need to be deprecated
* 3. Find the git tags associated with the previous versions of the packages that are being deprecated. These are the package versions that need to be marked as "latest" (or whatever the dist-tag for the release is)
* 5. Creates a rollback PR that resets the .changeset directory to its state before the release
* 6. Resets the dist-tags to the previous package versions
* 7. Marks the current package versions as deprecated
*/
deprecateRelease = async () => {
await this.gitClient.ensureWorkingTreeIsClean();

const releaseCommitHashToDeprecate =
await this.gitClient.getNearestReleaseCommit(
this.gitRefToStartReleaseSearchFrom
);

const releaseTagsToDeprecate = await this.gitClient.getTagsAtCommit(
releaseCommitHashToDeprecate
);

const previousReleaseTags = await this.gitClient.getPreviousReleaseTags(
releaseCommitHashToDeprecate
);

// Create the changeset revert PR
// This PR restores the changeset files that were part of the release but does NOT revert the package.json and changelog changes
const prBranch = `revert_changeset/${releaseCommitHashToDeprecate}`;

await this.gitClient.switchToBranch(prBranch);
await this.gitClient.checkout(`${releaseCommitHashToDeprecate}^`, [
'.changeset',
]);
await this.gitClient.status();
await this.gitClient.commitAllChanges(
`Reverting updates to the .changeset directory made by release commit ${releaseCommitHashToDeprecate}`
);
await this.gitClient.push({ force: true });

console.log(EOL);

const { prUrl } = await this.githubClient.createPr({
head: prBranch,
title: `Deprecate release ${releaseCommitHashToDeprecate}`,
body: `Reverting updates to the .changeset directory made by release commit ${releaseCommitHashToDeprecate}`,
});

console.log(`Created deprecation PR at ${prUrl}`);

// if anything fails before this point, we haven't actually modified anything on NPM yet.
// now we actually update the npm dist tags and mark the packages as deprecated

await this.distTagMover.moveDistTags(
releaseTagsToDeprecate,
previousReleaseTags
);

for (const releaseTag of releaseTagsToDeprecate) {
console.log(`Deprecating package version ${releaseTag}`);
await this.npmClient.deprecatePackage(
releaseTag,
this.deprecationMessage
);
console.log(`Done!${EOL}`);
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,24 @@ import {
} from './package-json/package_json.js';
import { runVersion } from '../version_runner.js';
import { runPublish } from '../publish_runner.js';
import { ReleaseLifecycleManager } from './release_lifecycle_manager.js';
import { GithubClient } from './github_client.js';
import assert from 'node:assert';
import { ReleaseDeprecator } from './release_deprecator.js';
import { DistTagMover } from './dist_tag_mover.js';

/**
* This test suite is more of an integration test than a unit test.
* It uses the real file system and git repo but mocks the GitHub API client
* It spins up verdaccio to test updating package metadata locally
*
* Since all of these tests are sharing the same git tree, we're running in serial to avoid conflicts (mostly around duplicate tag names)
*/
void describe('ReleaseLifecycleManager', async () => {
let gitClient: GitClient;
let npmClient: NpmClient;

let cantaloupePackageName: string;
let platypusPackageName: string;

// TODO uncomment before merging
// before(async () => {
// await import('../start_npm_proxy.js');
// });
Expand Down Expand Up @@ -136,13 +137,15 @@ void describe('ReleaseLifecycleManager', async () => {
const githubClient = new GithubClient('garbage');
mock.method(githubClient, 'createPr', async () => ({ prUrl: 'testPrUrl' }));
mock.method(gitClient, 'push', async () => {});
const releaseLifecycleManager = new ReleaseLifecycleManager(
const releaseDeprecator = new ReleaseDeprecator(
'HEAD',
'the cantaloupe is rotten',
githubClient,
gitClient,
npmClient
npmClient,
new DistTagMover(npmClient)
);
await releaseLifecycleManager.deprecateRelease('cantaloupe is rotten');
await releaseDeprecator.deprecateRelease();
// switch back to the original branch
await gitClient.switchToBranch('main');

Expand All @@ -151,7 +154,7 @@ void describe('ReleaseLifecycleManager', async () => {
const { 'dist-tags': distTags, deprecated } =
await npmClient.getPackageInfo(`${cantaloupePackageName}@1.3.0`);
assert.equal(distTags.latest, '1.2.0');
assert.equal(deprecated, 'cantaloupe is rotten');
assert.equal(deprecated, 'the cantaloupe is rotten');
});
});

Expand Down
Loading

0 comments on commit cce9e9e

Please sign in to comment.