diff --git a/.changeset/config.json b/.changeset/config.json index 1d726b249d1..126fd4175be 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,7 +1,7 @@ { "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": [ - "@changesets/changelog-github", + "@spectrum-tools/changesets-changelog-github", { "repo": "adobe/spectrum-css" } diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 9392f6f44b7..5cd453f4104 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -183,6 +183,8 @@ jobs: ## --- Run plugins test suites --- ## - name: Run plugin tests run: yarn test:plugins + env: + GITHUB_TOKEN: ${{ secrets.GH_ACCESS_FOR_CHANGESETS }} # ------------------------------------------------------------- # RUN VISUAL REGRESSION TESTS --- # diff --git a/plugins/changesets-changelog-github/index.js b/plugins/changesets-changelog-github/index.js new file mode 100644 index 00000000000..d5bc09375d1 --- /dev/null +++ b/plugins/changesets-changelog-github/index.js @@ -0,0 +1,119 @@ +import { getInfo, getInfoFromPullRequest } from "@changesets/get-github-info"; +import { config } from "dotenv"; + +config(); + +/** + * @type {import("@changesets/types").ChangelogFunctions} + */ +const changelogFunctions = { + getDependencyReleaseLine: async ( + changesets, + dependenciesUpdated, + options, + ) => { + if (!options.repo) { + throw new Error( + "Please provide a repo to this changelog generator like this:\n\"changelog\": [\"@changesets/changelog-github\", { \"repo\": \"org/repo\" }]", + ); + } + if (dependenciesUpdated.length === 0) return ""; + + const changesetLink = `Updated dependencies [${( + await Promise.all( + changesets.map(async (cs) => { + if (cs.commit) { + let { links } = await getInfo({ + repo: options.repo, + commit: cs.commit, + }); + return links.commit; + } + }), + ) + ) + .filter((_) => _) + .join(", ")}]:`; + + const updatedDepenenciesList = dependenciesUpdated.map( + (dependency) => ` - ${dependency.name}@${dependency.newVersion}`, + ); + + return [changesetLink, ...updatedDepenenciesList].join("\n"); + }, + getReleaseLine: async (changeset, type, options) => { + if (!options || !options.repo) { + throw new Error( + "Please provide a repo to this changelog generator like this:\n\"changelog\": [\"@changesets/changelog-github\", { \"repo\": \"org/repo\" }]", + ); + } + + /** @type {number | undefined} */ + let prFromSummary; + /** @type {string | undefined} */ + let commitFromSummary; + /** @type {string[]} */ + let usersFromSummary = []; + + const replacedChangelog = changeset.summary + .replace(/^\s*(?:pr|pull|pull\s+request):\s*#?(\d+)/im, (_, pr) => { + let num = Number(pr); + if (!isNaN(num)) prFromSummary = num; + return ""; + }) + .replace(/^\s*commit:\s*([^\s]+)/im, (_, commit) => { + commitFromSummary = commit; + return ""; + }) + .replace(/^\s*(?:author|user):\s*@?([^\s]+)/gim, (_, user) => { + usersFromSummary.push(user); + return ""; + }) + .trim(); + + const changelogLines = replacedChangelog + .split("\n") + .map((l) => l.trimRight()); + + const links = await (async () => { + if (prFromSummary !== undefined) { + let { links } = await getInfoFromPullRequest({ + repo: options.repo, + pull: prFromSummary, + }); + if (commitFromSummary) { + links.commit = `[\`${commitFromSummary.slice(0, 7)}\`](https://github.com/${options.repo}/commit/${commitFromSummary})`; + } + return links; + } + const commitToFetchFrom = commitFromSummary || changeset.commit; + if (commitToFetchFrom) { + let { links } = await getInfo({ + repo: options.repo, + commit: commitToFetchFrom, + }); + return links; + } + return { commit: null, pull: null, user: null }; + })(); + + const users = usersFromSummary.length + ? usersFromSummary + .map( + (userFromSummary) => + `[@${userFromSummary}](https://github.com/${userFromSummary})`, + ) + .join(", ") + : links.user; + + const prefix = [ + links.pull === null ? "" : ` ${links.pull}`, + links.commit === null ? "" : ` ${links.commit}`, + users === null ? "" : ` Thanks ${users}!`, + ].join(""); + + return `${prefix ? `\n\nšŸ“ ${prefix}` : ""}\n\n${changelogLines.join("\n")}\n`; + }, +}; + +export default changelogFunctions; diff --git a/plugins/changesets-changelog-github/package.json b/plugins/changesets-changelog-github/package.json new file mode 100644 index 00000000000..6f51a641d42 --- /dev/null +++ b/plugins/changesets-changelog-github/package.json @@ -0,0 +1,35 @@ +{ + "name": "@spectrum-tools/changesets-changelog-github", + "version": "0.0.0", + "description": "A changelog entry generator for GitHub that links to commits, PRs and users", + "license": "Apache-2.0", + "author": "Adobe", + "homepage": "https://opensource.adobe.com/spectrum-css/", + "repository": { + "type": "git", + "url": "https://github.com/adobe/spectrum-css.git", + "directory": "plugins/changesets-changelog-github" + }, + "bugs": { + "url": "https://github.com/adobe/spectrum-css/issues" + }, + "type": "module", + "module": "index.js", + "dependencies": { + "@changesets/get-github-info": "^0.6.0", + "@changesets/types": "^6.1.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "@changesets/parse": "^0.4.1", + "ava": "^6.4.0", + "sinon": "^20.0.0" + }, + "keywords": [ + "design-system", + "spectrum", + "spectrum-css", + "adobe", + "adobe-spectrum" + ] +} diff --git a/plugins/changesets-changelog-github/project.json b/plugins/changesets-changelog-github/project.json new file mode 100644 index 00000000000..c491db120d0 --- /dev/null +++ b/plugins/changesets-changelog-github/project.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "changesets-changelog-github", + "tags": ["tooling", "changesets", "plugin"], + "targets": { + "format": { "defaultConfiguration": "plugins" }, + "lint": { "defaultConfiguration": "plugins" }, + "test": { "defaultConfiguration": "plugins" } + } +} diff --git a/plugins/changesets-changelog-github/test.js b/plugins/changesets-changelog-github/test.js new file mode 100644 index 00000000000..4a2e2179733 --- /dev/null +++ b/plugins/changesets-changelog-github/test.js @@ -0,0 +1,144 @@ +import parse from "@changesets/parse"; +import test from "ava"; +import sinon from "sinon"; +import changelogFunctions from "./index.js"; + +/** @type {sinon.SinonSandbox} */ +let sandbox = sinon.createSandbox(); + +const data = { + commit: "a085003d4c8ca284c116668d7217fb747802ed85", + user: "Andarist", + pull: 1613, + repo: "emotion-js/emotion", +}; + +test.beforeEach((t) => { + sandbox.stub({ + getInfo: () => ({ + pull: data.pull, + user: data.user, + links: { + user: `[@${data.user}](https://github.com/${data.user})`, + pull: `[#${data.pull}](https://github.com/${data.repo}/pull/${data.pull})`, + commit: `[\`${data.commit.slice(0, 7)}\`](https://github.com/${data.repo}/commit/${data.commit})`, + }, + }) + }, "getInfo"); + sandbox.stub({ + getInfoFromPullRequest: () => ({ + commit: data.commit, + user: data.user, + links: { + user: `[@${data.user}](https://github.com/${data.user})`, + pull: `[#${data.pull}](https://github.com/${data.repo}/pull/${data.pull})`, + commit: `[\`${data.commit.slice(0, 7)}\`](https://github.com/${data.repo}/commit/${data.commit})`, + }, + }), + }, "getInfoFromPullRequest"); +}); + +test.afterEach.always(() => { + sandbox.restore(); +}); + +/** + * + * @param {string} content + * @param {string|undefined} commit + * @returns + */ +const getChangeset = (content, commit) => { + return [ + { + ...parse( + `--- + pkg: "minor" + --- + + something + ${content} + ` + ), + id: "some-id", + commit, + }, + "minor", + { repo: data.repo }, + ]; +}; + +[data.commit, "wrongcommit", undefined].forEach((commitFromChangeset) => { + ["pr", "pull request", "pull"].forEach((keyword) => { + test(`with commit from changeset of ${commitFromChangeset} override pr with ${keyword} keyword with #`, async (t) => { + t.is( + await changelogFunctions.getReleaseLine( + ...getChangeset( + `${keyword}: #${data.pull}`, + commitFromChangeset + ) + ), + "\n\nšŸ“ [#1613](https://github.com/emotion-js/emotion/pull/1613) [`a085003`](https://github.com/emotion-js/emotion/commit/a085003d4c8ca284c116668d7217fb747802ed85) Thanks [@Andarist](https://github.com/Andarist)!\n\nsomething\n" + ); + }); + + test(`with commit from changeset of ${commitFromChangeset} override pr with pr ${keyword} without #`, async (t) => { + t.is( + await changelogFunctions.getReleaseLine( + ...getChangeset( + `pr: ${data.pull}`, + commitFromChangeset + ) + ), + "\n\nšŸ“ [#1613](https://github.com/emotion-js/emotion/pull/1613) [`a085003`](https://github.com/emotion-js/emotion/commit/a085003d4c8ca284c116668d7217fb747802ed85) Thanks [@Andarist](https://github.com/Andarist)!\n\nsomething\n" + ); + }); + }); + + test(`override commit ${commitFromChangeset} with commit keyword`, async (t) => { + t.is( + await changelogFunctions.getReleaseLine( + ...getChangeset(`commit: ${data.commit}`, commitFromChangeset) + ), + "\n\nšŸ“ [#1613](https://github.com/emotion-js/emotion/pull/1613) [`a085003`](https://github.com/emotion-js/emotion/commit/a085003d4c8ca284c116668d7217fb747802ed85) Thanks [@Andarist](https://github.com/Andarist)!\n\nsomething\n" + ); + }); +}); + +["author", "user"].forEach((keyword) => { + test(`override author with ${keyword} keyword with @`, async (t) => { + t.is( + await changelogFunctions.getReleaseLine( + ...getChangeset( + `${keyword}: @other`, + data.commit + ) + ), + "\n\nšŸ“ [#1613](https://github.com/emotion-js/emotion/pull/1613) [`a085003`](https://github.com/emotion-js/emotion/commit/a085003d4c8ca284c116668d7217fb747802ed85) Thanks [@other](https://github.com/other)!\n\nsomething\n" + ); + }); + + test(`override author with ${keyword} keyword without @`, async (t) => { + t.is( + await changelogFunctions.getReleaseLine( + ...getChangeset( + `${keyword}: other`, + data.commit + ) + ), + "\n\nšŸ“ [#1613](https://github.com/emotion-js/emotion/pull/1613) [`a085003`](https://github.com/emotion-js/emotion/commit/a085003d4c8ca284c116668d7217fb747802ed85) Thanks [@other](https://github.com/other)!\n\nsomething\n" + ); + }); +}); + +test("with multiple authors", async (t) => { + t.is( + await changelogFunctions.getReleaseLine( + ...getChangeset( + ["author: @Andarist", "author: @mitchellhamilton"].join("\n"), + data.commit + ) + ), + `\n\nšŸ“ [#1613](https://github.com/emotion-js/emotion/pull/1613) [\`a085003\`](https://github.com/emotion-js/emotion/commit/a085003d4c8ca284c116668d7217fb747802ed85) Thanks [@Andarist](https://github.com/Andarist), [@mitchellhamilton](https://github.com/mitchellhamilton)!\n\nsomething\n` + ); +}); diff --git a/yarn.lock b/yarn.lock index 06b322bb6a2..28ff60efdd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3873,7 +3873,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^3.0.0": +"@sinonjs/commons@npm:^3.0.0, @sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" dependencies: @@ -3891,6 +3891,26 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^13.0.5": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + checksum: 10c0/a707476efd523d2138ef6bba916c83c4a377a8372ef04fad87499458af9f01afc58f4f245c5fd062793d6d70587309330c6f96947b5bd5697961c18004dc3e26 + languageName: node + linkType: hard + +"@sinonjs/samsam@npm:^8.0.1": + version: 8.0.2 + resolution: "@sinonjs/samsam@npm:8.0.2" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + lodash.get: "npm:^4.4.2" + type-detect: "npm:^4.1.0" + checksum: 10c0/31d74c415040161f2963a202d7f866bedbb5a9b522a74b08a17086c15a75c3ef2893eecebb0c65a7b1603ef4ebdf83fa73cbe384b4cd679944918ed833200443 + languageName: node + linkType: hard + "@spectrum-css/accordion@npm:7.1.0, @spectrum-css/accordion@workspace:components/accordion": version: 0.0.0-use.local resolution: "@spectrum-css/accordion@workspace:components/accordion" @@ -5759,6 +5779,19 @@ __metadata: languageName: unknown linkType: soft +"@spectrum-tools/changesets-changelog-github@workspace:plugins/changesets-changelog-github": + version: 0.0.0-use.local + resolution: "@spectrum-tools/changesets-changelog-github@workspace:plugins/changesets-changelog-github" + dependencies: + "@changesets/get-github-info": "npm:^0.6.0" + "@changesets/parse": "npm:^0.4.1" + "@changesets/types": "npm:^6.1.0" + ava: "npm:^6.4.0" + dotenv: "npm:^16.5.0" + sinon: "npm:^20.0.0" + languageName: unknown + linkType: soft + "@spectrum-tools/gh-action-file-diff@workspace:.github/actions/file-diff": version: 0.0.0-use.local resolution: "@spectrum-tools/gh-action-file-diff@workspace:.github/actions/file-diff" @@ -9917,6 +9950,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.5.0": + version: 16.5.0 + resolution: "dotenv@npm:16.5.0" + checksum: 10c0/5bc94c919fbd955bf0ba44d33922a1e93d1078e64a1db5c30faeded1d996e7a83c55332cb8ea4fae5a9ca4d0be44cbceb95c5811e70f9f095298df09d1997dd9 + languageName: node + linkType: hard + "dotenv@npm:^8.1.0": version: 8.6.0 resolution: "dotenv@npm:8.6.0" @@ -11656,7 +11696,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.7, glob@npm:^10.4.1, glob@npm:^10.4.2, glob@npm:^10.4.5": +"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1, glob@npm:^10.4.2, glob@npm:^10.4.5": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -15638,7 +15678,7 @@ __metadata: languageName: node linkType: hard -"node-gyp@npm:^11.2.0, node-gyp@npm:latest": +"node-gyp@npm:^11.2.0": version: 11.2.0 resolution: "node-gyp@npm:11.2.0" dependencies: @@ -15658,6 +15698,26 @@ __metadata: languageName: node linkType: hard +"node-gyp@npm:latest": + version: 11.1.0 + resolution: "node-gyp@npm:11.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/c38977ce502f1ea41ba2b8721bd5b49bc3d5b3f813eabfac8414082faf0620ccb5211e15c4daecc23ed9f5e3e9cc4da00e575a0bcfc2a95a069294f2afa1e0cd + languageName: node + linkType: hard + "node-int64@npm:^0.4.0": version: 0.4.0 resolution: "node-int64@npm:0.4.0" @@ -18845,6 +18905,19 @@ __metadata: languageName: node linkType: hard +"sinon@npm:^20.0.0": + version: 20.0.0 + resolution: "sinon@npm:20.0.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:^13.0.5" + "@sinonjs/samsam": "npm:^8.0.1" + diff: "npm:^7.0.0" + supports-color: "npm:^7.2.0" + checksum: 10c0/98198e9608f9844a00d2dc733bc8d75cfd1826b2214ed07bbfcd05cdfa24eb0ac0866be116079febd9f20c715a41fe62db62f70961f941750b24281c97f04136 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -19577,7 +19650,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -20038,6 +20111,13 @@ __metadata: languageName: node linkType: hard +"type-detect@npm:^4.1.0": + version: 4.1.0 + resolution: "type-detect@npm:4.1.0" + checksum: 10c0/df8157ca3f5d311edc22885abc134e18ff8ffbc93d6a9848af5b682730ca6a5a44499259750197250479c5331a8a75b5537529df5ec410622041650a7f293e2a + languageName: node + linkType: hard + "type-fest@npm:^0.13.1": version: 0.13.1 resolution: "type-fest@npm:0.13.1"