Skip to content

Commit

Permalink
fix(version): create correct independent tags when using --sign-git-t…
Browse files Browse the repository at this point in the history
…ag (#3917)
  • Loading branch information
ivml committed Dec 15, 2023
1 parent f5fdcba commit 8f7a32b
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 1 deletion.
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ jobs:
git config --global user.email test@example.com
git config --global user.name "Tester McPerson"
- name: Generate and configure GPG for signing commits and tags in E2E tests
run: |
# Generate a GPG key for test@example.com and store the output from stderr
GPG_OUTPUT=$(echo "Key-Type: default
Key-Length: 2048
Subkey-Type: default
Subkey-Length: 2048
Name-Real: Tester McPerson
Name-Email: test@example.com
Expire-Date: 0
%no-protection" | gpg --pinentry-mode loopback --batch --generate-key 2>&1)
# Find and extract the revocation file path from sdterr
REVOCATION_FILE=$(echo "$GPG_OUTPUT" | grep '.rev' | tr '\n' ' ' | awk -F "'" '{print $4}')
# Get the GPG key ID and the full fingerprint
export GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
export GPG_FULL_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep "$GPG_KEY_ID" | grep -v "sec" | awk '{print $1}' | cut -d'/' -f2)
# Export fingerprint and the path to the revocation file to GITHUB_ENV
# This allows the last step in this job to revoke and delete the key
echo "GPG_FULL_KEY_ID=$GPG_FULL_KEY_ID" >> $GITHUB_ENV
echo "REVOCATION_FILE=$REVOCATION_FILE" >> $GITHUB_ENV
# Setup git signing for commits and tags
git config commit.gpgsign true
git config tag.gpgsign true
git config --global user.signingkey $GPG_KEY_ID
- name: Install primary node version (see volta config in package.json) and dependencies
uses: ./.github/actions/install-node-and-dependencies

Expand All @@ -74,6 +103,17 @@ jobs:
env:
NX_AGENT_NAME: ${{ matrix.agent }}

- name: Revoke and delete GPG key
# It's important that we always run this step, otherwise the key will remain active if any of the steps above fail
if: ${{ always() }}
run: |
# As instructed in the text of revocation file, there is a colon that needs to be removed manually
sed -i "s/:-----BEGIN PGP PUBLIC KEY BLOCK-----/-----BEGIN PGP PUBLIC KEY BLOCK-----/" $REVOCATION_FILE
# Revoke the key and delete it
gpg --yes --import $REVOCATION_FILE
gpg --batch --yes --delete-secret-and-public-key $GPG_FULL_KEY_ID
windows-main:
name: Nx Cloud - Windows Main Job
runs-on: windows-latest
Expand Down
292 changes: 292 additions & 0 deletions e2e/version/src/sign-git-tag.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
import { Fixture, normalizeCommitSHAs, normalizeEnvironment } from "@lerna/e2e-utils";

expect.addSnapshotSerializer({
serialize(str: string) {
return normalizeCommitSHAs(normalizeEnvironment(str));
},
test(val: string) {
return val != null && typeof val === "string";
},
});

/**
* NOTE: This test suite should only execute in CI/CD.
* The reason is that using the `--sign-git-tag` flag requires a GPG key to be present on the machine,
* which is not a guarantee in a local development environment.
*/
const describeFunc = process.env.CI === "true" ? describe : describe.skip;

describeFunc("lerna-version-sign-git-tag", () => {
describe("single package", () => {
let fixture: Fixture;

beforeEach(async () => {
fixture = await Fixture.create({
e2eRoot: process.env.E2E_ROOT,
name: "lerna-version-sign-git-tag",
packageManager: "npm",
initializeGit: true,
lernaInit: { args: [`--packages="packages/*"`] },
installDependencies: true,
});
await fixture.lerna("create package-a -y");
await fixture.createInitialGitCommit();
await fixture.exec("git push origin test-main");
});
afterEach(() => fixture.destroy());

it("should create tags that match version when not using --sign-git-tag", async () => {
const output = await fixture.lerna("version 3.3.3 -y");

expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info current version 0.0.0
lerna info Assuming all packages changed
Changes:
- package-a: 0.0.0 => 3.3.3
lerna info auto-confirmed
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished
`);

const checkTagIsPresentLocally = await fixture.exec("git describe --abbrev=0");

expect(checkTagIsPresentLocally.combinedOutput).toMatchInlineSnapshot(`
v3.3.3
`);

const checkTagIsPresentOnRemote = await fixture.exec("git ls-remote origin refs/tags/v3.3.3");

expect(checkTagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/v3.3.3
`);
});

it("should create tags that match version when using --sign-git-tag", async () => {
const output = await fixture.lerna("version 3.4.5 --sign-git-tag -y");
expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info current version 0.0.0
lerna info Assuming all packages changed
Changes:
- package-a: 0.0.0 => 3.4.5
lerna info auto-confirmed
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished
`);

const checkTagIsPresentLocally = await fixture.exec("git describe --abbrev=0");
expect(checkTagIsPresentLocally.combinedOutput).toMatchInlineSnapshot(`
v3.4.5
`);

const checkTagIsPresentOnRemote = await fixture.exec("git ls-remote origin refs/tags/v3.4.5");
expect(checkTagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/v3.4.5
`);
});
});

describe("multiple packages", () => {
let fixture: Fixture;

beforeEach(async () => {
fixture = await Fixture.create({
e2eRoot: process.env.E2E_ROOT,
name: "lerna-version-sign-git-tag-multiple-packages",
packageManager: "npm",
initializeGit: true,
lernaInit: { args: [`--packages="packages/*"`] },
installDependencies: true,
});
await fixture.lerna("create package-a -y");
await fixture.lerna("create package-b -y");
await fixture.createInitialGitCommit();
await fixture.exec("git push origin test-main");
});
afterEach(() => fixture.destroy());

it("should create tags that match version when not using --sign-git-tag", async () => {
const output = await fixture.lerna("version 3.3.3 -y");

expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info current version 0.0.0
lerna info Assuming all packages changed
Changes:
- package-a: 0.0.0 => 3.3.3
- package-b: 0.0.0 => 3.3.3
lerna info auto-confirmed
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished
`);

const checkTagIsPresentLocally = await fixture.exec("git describe --abbrev=0");

expect(checkTagIsPresentLocally.combinedOutput).toMatchInlineSnapshot(`
v3.3.3
`);

const checkTagIsPresentOnRemote = await fixture.exec("git ls-remote origin refs/tags/v3.3.3");

expect(checkTagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/v3.3.3
`);
});

it("should create tags that match version when using --sign-git-tag", async () => {
const output = await fixture.lerna("version 3.4.5 --sign-git-tag -y");
expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info current version 0.0.0
lerna info Assuming all packages changed
Changes:
- package-a: 0.0.0 => 3.4.5
- package-b: 0.0.0 => 3.4.5
lerna info auto-confirmed
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished
`);

const checkTagIsPresentLocally = await fixture.exec("git describe --abbrev=0");
expect(checkTagIsPresentLocally.combinedOutput).toMatchInlineSnapshot(`
v3.4.5
`);

const checkTagIsPresentOnRemote = await fixture.exec("git ls-remote origin refs/tags/v3.4.5");
expect(checkTagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/v3.4.5
`);
});
});

describe("independent packages", () => {
let fixture: Fixture;

beforeEach(async () => {
fixture = await Fixture.create({
e2eRoot: process.env.E2E_ROOT,
name: "lerna-version-sign-git-tag-multiple-packages",
packageManager: "npm",
initializeGit: true,
lernaInit: { args: [`--packages="packages/*" --independent`] },
installDependencies: true,
});
await fixture.lerna("create package-a -y");
await fixture.lerna("create package-b -y");
await fixture.createInitialGitCommit();
await fixture.exec("git push origin test-main");
});
afterEach(() => fixture.destroy());

it("should create tags that match version when not using --sign-git-tag", async () => {
const output = await fixture.lerna("version 3.3.3 -y");

// NOTE: In the independent case, lerna started with version 1.0.0 as its assumed baseline (not 0.0.0 as in the fixed mode case)
expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info versioning independent
lerna info Assuming all packages changed
Changes:
- package-a: 1.0.0 => 3.3.3
- package-b: 1.0.0 => 3.3.3
lerna info auto-confirmed
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished
`);

// It should create one tag for each independently versioned package
const checkPackageTagsArePresentLocally = await fixture.exec("git describe --abbrev=0");
expect(checkPackageTagsArePresentLocally.combinedOutput).toMatchInlineSnapshot(`
package-a@3.3.3
`);

const checkPackageATagIsPresentOnRemote = await fixture.exec(
"git ls-remote origin refs/tags/package-a@3.3.3"
);
expect(checkPackageATagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/package-a@3.3.3
`);
const checkPackageBTagIsPresentOnRemote = await fixture.exec(
"git ls-remote origin refs/tags/package-b@3.3.3"
);
expect(checkPackageBTagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/package-b@3.3.3
`);
});

it("should create tags that match version when using --sign-git-tag", async () => {
const output = await fixture.lerna("version 3.4.5 --sign-git-tag -y");

// NOTE: In the independent case, lerna started with version 1.0.0 as its assumed baseline (not 0.0.0 as in the fixed mode case)
expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info versioning independent
lerna info Assuming all packages changed
Changes:
- package-a: 1.0.0 => 3.4.5
- package-b: 1.0.0 => 3.4.5
lerna info auto-confirmed
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished
`);

// It should create one tag for each independently versioned package
const checkPackageTagsArePresentLocally = await fixture.exec("git describe --abbrev=0");
expect(checkPackageTagsArePresentLocally.combinedOutput).toMatchInlineSnapshot(`
package-a@3.4.5
`);

const checkPackageATagIsPresentOnRemote = await fixture.exec(
"git ls-remote origin refs/tags/package-a@3.4.5"
);
expect(checkPackageATagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/package-a@3.4.5
`);
const checkPackageBTagIsPresentOnRemote = await fixture.exec(
"git ls-remote origin refs/tags/package-b@3.4.5"
);
expect(checkPackageBTagIsPresentOnRemote.combinedOutput).toMatchInlineSnapshot(`
{FULL_COMMIT_SHA} refs/tags/package-b@3.4.5
`);
});
});
});
2 changes: 1 addition & 1 deletion libs/commands/version/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ class VersionCommand extends Command {
await gitCommit(message, this.gitOpts, this.execOpts);
}
if (this.gitOpts.signGitTag) {
for (const tag in tags) await gitTag(tag, this.gitOpts, this.execOpts, this.options.gitTagCommand);
for (const tag of tags) await gitTag(tag, this.gitOpts, this.execOpts, this.options.gitTagCommand);
} else {
await Promise.all(
tags.map((tag) => gitTag(tag, this.gitOpts, this.execOpts, this.options.gitTagCommand))
Expand Down

0 comments on commit 8f7a32b

Please sign in to comment.