Skip to content

Commit

Permalink
feat(release-workflow): call a reusable release workflow rather than …
Browse files Browse the repository at this point in the history
  • Loading branch information
travi committed Nov 11, 2022
1 parent 721b460 commit 00e6e22
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 249 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import {scaffold, test, lift} from '@form8ion/commit-convention';
await scaffold({projectRoot, configs: {}});

if (await test({projectRoot})) {
await lift({projectRoot, packageManager: packageManagers.NPM, vcs: {owner: 'foo', name: 'bar'}});
await lift({projectRoot, packageManager: packageManagers.NPM});
}
})();
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"pregenerate:md": "run-s build",
"lint:sensitive": "ban",
"prepare": "husky install",
"pretest:integration": "run-s build",
"pretest:integration:base": "run-s build",
"lint:gherkin": "gherkin-lint",
"test:integration": "run-s 'test:integration:base -- --profile noWip'",
"test:integration:base": "NODE_OPTIONS=--enable-source-maps DEBUG=any cucumber-js test/integration --profile base",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export {default as scaffold} from './scaffolder';
export {default as scaffold} from './release-workflow-for-alpha/scaffolder';
export {default as test} from './tester';
export {default as lift} from './lifter';
216 changes: 99 additions & 117 deletions src/semantic-release/ci-providers/github-workflows/lifter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,34 @@ import {assert} from 'chai';
import sinon from 'sinon';

import * as releaseTriggerNeeds from './release-trigger-needs';
import * as scaffolder from './scaffolder';
import * as releaseWorkflowLifter from './release-workflow-for-alpha/lifter';
import lift from './lifter';

suite('github-workflows lifter for semantic-release', () => {
let sandbox;
const projectRoot = any.string();
const workflowsDirectory = `${projectRoot}/.github/workflows`;
const vcsName = any.word();
const vcsOwner = any.word();
const vcsDetails = {name: vcsName, owner: vcsOwner};
const verificationWorkflowContents = any.string();
const parsedVerificationWorkflowContents = any.simpleObject();
const jobs = any.objectWithKeys(any.listOf(any.word), {factory: () => ({steps: any.listOf(any.simpleObject)})});
const legacyReleaseJob = any.simpleObject();
const updatedVerificationWorkflowContents = any.string();
const legacyReleaseJob = {...any.simpleObject(), steps: any.listOf(any.simpleObject)};
const branchTriggers = any.listOf(any.word);
const moreBranchTriggers = any.listOf(any.word);
const neededJobsToTriggerRelease = any.listOf(any.word);
const modernReleaseJobDefinition = {
needs: neededJobsToTriggerRelease,
uses: 'form8ion/.github/.github/workflows/release-package.yml@master',
// eslint-disable-next-line no-template-curly-in-string
secrets: {NPM_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}'}
};

setup(() => {
sandbox = sinon.createSandbox();

sandbox.stub(fs, 'readFile');
sandbox.stub(fs, 'writeFile');
sandbox.stub(jsYaml, 'load');
sandbox.stub(jsYaml, 'dump');
sandbox.stub(core, 'fileExists');
sandbox.stub(scaffolder, 'default');
sandbox.stub(core, 'writeConfigFile');
sandbox.stub(releaseWorkflowLifter, 'default');
sandbox.stub(releaseTriggerNeeds, 'default');

const commonVerificationWorkflowContents = any.string();
Expand All @@ -44,25 +44,8 @@ suite('github-workflows lifter for semantic-release', () => {

teardown(() => sandbox.restore());

test('that the release workflow is added if it doesnt already exist', async () => {
core.fileExists.resolves(false);

await lift({projectRoot, vcs: vcsDetails});

assert.calledWith(scaffolder.default, {projectRoot});
});

test('that the release workflow is not added if it already exists', async () => {
core.fileExists.withArgs(`${workflowsDirectory}/release.yml`).resolves(true);

await lift({projectRoot, vcs: vcsDetails});

assert.notCalled(scaffolder.default);
});

test('that the legacy release job is removed', async () => {
test('that the legacy release job is replaced', async () => {
const existingJobs = {...jobs, release: legacyReleaseJob};
core.fileExists.resolves(true);
fs.readFile.withArgs(`${workflowsDirectory}/node-ci.yml`, 'utf-8').resolves(verificationWorkflowContents);
releaseTriggerNeeds.default.withArgs(existingJobs).returns(neededJobsToTriggerRelease);
jsYaml.load
Expand All @@ -72,37 +55,52 @@ suite('github-workflows lifter for semantic-release', () => {
on: {push: {branches: [...branchTriggers, 'alpha', 'beta', ...moreBranchTriggers]}},
jobs: existingJobs
});
jsYaml.dump
.withArgs({
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, 'beta', ...moreBranchTriggers]}},
jobs: {
...jobs,
'trigger-release': {
'runs-on': 'ubuntu-latest',
if: "github.event_name == 'push'",
needs: neededJobsToTriggerRelease,
steps: [{
uses: 'octokit/request-action@v2.x',
with: {
route: 'POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches',
owner: vcsOwner,
repo: vcsName,
ref: '${{ github.ref }}', // eslint-disable-line no-template-curly-in-string
workflow_id: 'release.yml'
},
env: {
GITHUB_TOKEN: '${{ secrets.GH_PAT }}' // eslint-disable-line no-template-curly-in-string
}
}]
}

await lift({projectRoot});

assert.calledWith(releaseWorkflowLifter.default, {projectRoot});
assert.calledWith(
core.writeConfigFile,
{
format: core.fileTypes.YAML,
name: 'node-ci',
path: workflowsDirectory,
config: {
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, 'beta', ...moreBranchTriggers]}},
jobs: {...jobs, release: modernReleaseJobDefinition}
}
})
.returns(updatedVerificationWorkflowContents);
}
);
});

await lift({projectRoot, vcs: vcsDetails});
test('that a modern release job is left as-is', async () => {
const existingJobs = {...jobs, release: modernReleaseJobDefinition};
releaseTriggerNeeds.default.withArgs(existingJobs).returns(neededJobsToTriggerRelease);
fs.readFile.withArgs(`${workflowsDirectory}/node-ci.yml`, 'utf-8').resolves(verificationWorkflowContents);
jsYaml.load
.withArgs(verificationWorkflowContents)
.returns({
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, 'beta', ...moreBranchTriggers]}},
jobs: existingJobs
});

assert.calledWith(fs.writeFile, `${workflowsDirectory}/node-ci.yml`, updatedVerificationWorkflowContents);
await lift({projectRoot});

assert.calledWith(
core.writeConfigFile,
{
format: core.fileTypes.YAML,
name: 'node-ci',
path: workflowsDirectory,
config: {
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, 'beta', ...moreBranchTriggers]}},
jobs: {...jobs, release: modernReleaseJobDefinition}
}
}
);
});

test('that the cycjimmy action is removed', async () => {
Expand All @@ -117,7 +115,6 @@ suite('github-workflows lifter for semantic-release', () => {
]
}
};
core.fileExists.resolves(true);
fs.readFile.withArgs(`${workflowsDirectory}/node-ci.yml`, 'utf-8').resolves(verificationWorkflowContents);
releaseTriggerNeeds.default.withArgs(existingJobs).returns(neededJobsToTriggerRelease);
jsYaml.load
Expand All @@ -127,42 +124,34 @@ suite('github-workflows lifter for semantic-release', () => {
on: {push: {branches: [...branchTriggers, 'alpha', 'beta', ...moreBranchTriggers]}},
jobs: existingJobs
});
jsYaml.dump
.withArgs({
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, 'beta', ...moreBranchTriggers]}},
jobs: {
...jobs,
[jobNameContainingCycjimmyAction]: {steps: otherStepsInJobContainingCycJimmyAction},
'trigger-release': {
'runs-on': 'ubuntu-latest',
if: "github.event_name == 'push'",
needs: neededJobsToTriggerRelease,
steps: [{
uses: 'octokit/request-action@v2.x',
with: {
route: 'POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches',
owner: vcsOwner,
repo: vcsName,
ref: '${{ github.ref }}', // eslint-disable-line no-template-curly-in-string
workflow_id: 'release.yml'
},
env: {
GITHUB_TOKEN: '${{ secrets.GH_PAT }}' // eslint-disable-line no-template-curly-in-string
}
}]

await lift({projectRoot});

assert.calledWith(
core.writeConfigFile,
{
format: core.fileTypes.YAML,
name: 'node-ci',
path: workflowsDirectory,
config: {
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, 'beta', ...moreBranchTriggers]}},
jobs: {
...jobs,
[jobNameContainingCycjimmyAction]: {steps: otherStepsInJobContainingCycJimmyAction},
release: {
needs: neededJobsToTriggerRelease,
uses: 'form8ion/.github/.github/workflows/release-package.yml@master',
// eslint-disable-next-line no-template-curly-in-string
secrets: {NPM_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}'}
}
}
}
})
.returns(updatedVerificationWorkflowContents);

await lift({projectRoot, vcs: vcsDetails});

assert.calledWith(fs.writeFile, `${workflowsDirectory}/node-ci.yml`, updatedVerificationWorkflowContents);
}
);
});

test('that the the release trigger is added when no release is configured yet', async () => {
core.fileExists.resolves(true);
fs.readFile.withArgs(`${workflowsDirectory}/node-ci.yml`, 'utf-8').resolves(verificationWorkflowContents);
releaseTriggerNeeds.default.withArgs(jobs).returns(neededJobsToTriggerRelease);
jsYaml.load
Expand All @@ -172,36 +161,29 @@ suite('github-workflows lifter for semantic-release', () => {
on: {push: {branches: [...branchTriggers, ...moreBranchTriggers]}},
jobs
});
jsYaml.dump
.withArgs({
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, ...moreBranchTriggers, 'beta']}},
jobs: {
...jobs,
'trigger-release': {
'runs-on': 'ubuntu-latest',
if: "github.event_name == 'push'",
needs: neededJobsToTriggerRelease,
steps: [{
uses: 'octokit/request-action@v2.x',
with: {
route: 'POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches',
owner: vcsOwner,
repo: vcsName,
ref: '${{ github.ref }}', // eslint-disable-line no-template-curly-in-string
workflow_id: 'release.yml'
},
env: {
GITHUB_TOKEN: '${{ secrets.GH_PAT }}' // eslint-disable-line no-template-curly-in-string
}
}]

await lift({projectRoot});

assert.calledWith(
core.writeConfigFile,
{
format: core.fileTypes.YAML,
name: 'node-ci',
path: workflowsDirectory,
config: {
...parsedVerificationWorkflowContents,
on: {push: {branches: [...branchTriggers, ...moreBranchTriggers, 'beta']}},
jobs: {
...jobs,
release: {
needs: neededJobsToTriggerRelease,
uses: 'form8ion/.github/.github/workflows/release-package.yml@master',
// eslint-disable-next-line no-template-curly-in-string
secrets: {NPM_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}'}
}
}
}
})
.returns(updatedVerificationWorkflowContents);

await lift({projectRoot, vcs: vcsDetails});

assert.calledWith(fs.writeFile, `${workflowsDirectory}/node-ci.yml`, updatedVerificationWorkflowContents);
}
);
});
});
41 changes: 15 additions & 26 deletions src/semantic-release/ci-providers/github-workflows/lifter.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import {promises as fs} from 'fs';
import {load} from 'js-yaml';
import {fileExists, fileTypes, writeConfigFile} from '@form8ion/core';
import {fileTypes, writeConfigFile} from '@form8ion/core';

import determineTriggerNeedsFrom from './release-trigger-needs';
import scaffoldReleaseWorkflow from './scaffolder';
import {lift as liftReleaseWorkflow} from './release-workflow-for-alpha';

function removeCycjimmyActionFrom(otherJobs) {
return Object.fromEntries(Object.entries(otherJobs).map(([jobName, job]) => [
jobName,
{...job, steps: job.steps.filter(step => !step.uses?.includes('cycjimmy/semantic-release-action'))}
{
...job,
...job.steps && {steps: job.steps.filter(step => !step.uses?.includes('cycjimmy/semantic-release-action'))}
}
]));
}

export default async function ({projectRoot, vcs: {name: vcsProjectName, owner: vcsOwner}}) {
export default async function ({projectRoot}) {
const workflowsDirectory = `${projectRoot}/.github/workflows`;

if (!await fileExists(`${workflowsDirectory}/release.yml`)) {
await scaffoldReleaseWorkflow({projectRoot});
}
await liftReleaseWorkflow({projectRoot});

const parsedVerificationWorkflowDetails = load(await fs.readFile(`${workflowsDirectory}/node-ci.yml`, 'utf-8'));

Expand All @@ -26,27 +27,15 @@ export default async function ({projectRoot, vcs: {name: vcsProjectName, owner:
...!parsedVerificationWorkflowDetails.on.push.branches.includes('beta') ? ['beta'] : []
];

const {release, ...otherJobs} = parsedVerificationWorkflowDetails.jobs;
const {jobs} = parsedVerificationWorkflowDetails;

parsedVerificationWorkflowDetails.jobs = {
...removeCycjimmyActionFrom(otherJobs),
'trigger-release': {
'runs-on': 'ubuntu-latest',
if: "github.event_name == 'push'",
needs: determineTriggerNeedsFrom(parsedVerificationWorkflowDetails.jobs),
steps: [{
uses: 'octokit/request-action@v2.x',
with: {
route: 'POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches',
owner: vcsOwner,
repo: vcsProjectName,
ref: '${{ github.ref }}', // eslint-disable-line no-template-curly-in-string
workflow_id: 'release.yml'
},
env: {
GITHUB_TOKEN: '${{ secrets.GH_PAT }}' // eslint-disable-line no-template-curly-in-string
}
}]
...removeCycjimmyActionFrom(jobs),
release: {
needs: determineTriggerNeedsFrom(jobs),
uses: 'form8ion/.github/.github/workflows/release-package.yml@master',
// eslint-disable-next-line no-template-curly-in-string
secrets: {NPM_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}'}
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {default as scaffold} from './scaffolder';
export {default as lift} from './lifter';
Loading

0 comments on commit 00e6e22

Please sign in to comment.