aboutsummaryrefslogtreecommitdiffstats
path: root/packages/monorepo-scripts/src/utils/github_release_utils.ts
blob: 48704f3aadef82c96469bfedf33edbe5e752a1c6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import * as promisify from 'es6-promisify';
import { readFileSync } from 'fs';
import * as _ from 'lodash';
import * as path from 'path';
import { exec as execAsync } from 'promisify-child-process';
import * as publishRelease from 'publish-release';

import { constants } from '../constants';
import { Package } from '../types';

import { utils } from './utils';

const publishReleaseAsync = promisify(publishRelease);
// tslint:disable-next-line:completed-docs
export async function publishReleaseNotesAsync(
    packagesToPublish: Package[],
    isDryRun: boolean,
): Promise<string | undefined> {
    // Git push a tag representing this publish (publish-{commit-hash}) (truncate hash)
    const result = await execAsync('git log -n 1 --pretty=format:"%H"', { cwd: constants.monorepoRootPath });
    const latestGitCommit = result.stdout;
    const prefixLength = 7;
    const shortenedGitCommit = latestGitCommit.slice(0, prefixLength);
    const tagName = `monorepo@${shortenedGitCommit}`;

    if (!isDryRun) {
        try {
            await execAsync(`git tag ${tagName}`);
        } catch (err) {
            if (_.includes(err.message, 'already exists')) {
                // Noop tag creation since already exists
            } else {
                throw err;
            }
        }
        const { stdout } = await execAsync(`git ls-remote --tags origin refs/tags/${tagName}`);
        if (_.isEmpty(stdout)) {
            await execAsync(`git push origin ${tagName}`);
        }
    }

    const releaseName = `0x monorepo - ${shortenedGitCommit}`;

    let assets: string[] = [];
    let aggregateNotes = '';
    _.each(packagesToPublish, pkg => {
        aggregateNotes += getReleaseNotesForPackage(pkg.location, pkg.packageJson.name);

        const packageAssets = _.get(pkg.packageJson, 'config.postpublish.assets');
        if (!_.isUndefined(packageAssets)) {
            assets = [...assets, ...packageAssets];
        }
    });
    const finalAssets = adjustAssetPaths(assets);

    const publishReleaseConfigs = {
        token: constants.githubPersonalAccessToken,
        owner: '0xProject',
        tag: tagName,
        repo: '0x-monorepo',
        name: releaseName,
        notes: aggregateNotes,
        draft: false,
        prerelease: false,
        reuseRelease: true,
        reuseDraftOnly: false,
        // TODO: Currently publish-release doesn't let you specify the labels for each asset uploaded
        // Ideally we would like to name the assets after the package they are from
        // Source: https://github.com/remixz/publish-release/issues/39
        assets: finalAssets,
    };

    if (isDryRun) {
        utils.log(`Dry run: stopping short of publishing release notes to github`);
        utils.log(`Would publish with configs:\n${JSON.stringify(publishReleaseConfigs, null, '\t')}`);
        return;
    }

    utils.log('Publishing release notes ', releaseName, '...');
    await publishReleaseAsync(publishReleaseConfigs);

    return aggregateNotes;
}

// Asset paths should described from the monorepo root. This method prefixes
// the supplied path with the absolute path to the monorepo root.
function adjustAssetPaths(assets: string[]): string[] {
    const finalAssets: string[] = [];
    _.each(assets, (asset: string) => {
        const finalAsset = `${constants.monorepoRootPath}/${asset}`;
        finalAssets.push(finalAsset);
    });
    return finalAssets;
}

function getReleaseNotesForPackage(packageLocation: string, packageName: string): string {
    const changelogJSONPath = path.join(packageLocation, 'CHANGELOG.json');
    const changelogJSON = readFileSync(changelogJSONPath, 'utf-8');
    const changelogs = JSON.parse(changelogJSON);
    const latestLog = changelogs[0];
    // If only has a `Dependencies updated` changelog, we don't include it in release notes
    if (latestLog.changes.length === 1 && latestLog.changes[0].note === constants.dependenciesUpdatedMessage) {
        return '';
    }
    let notes = '';
    _.each(latestLog.changes, change => {
        notes += `* ${change.note}`;
        if (change.pr) {
            notes += ` (#${change.pr})`;
        }
        notes += `\n`;
    });
    if (_.isEmpty(notes)) {
        return ''; // don't include it
    }
    const releaseNotesSection = `### ${packageName}@${latestLog.version}\n${notes}\n\n`;
    return releaseNotesSection;
}