From e77905dbcad2e6e1c1d9014ae093d2006b5aa738 Mon Sep 17 00:00:00 2001 From: Davi Kawasaki Date: Sun, 7 Mar 2021 10:08:27 -0300 Subject: [PATCH] feat: provide ignore packages CLI directive --- .gitignore | 2 +- README.md | 37 +++++ bin/cli.js | 12 +- bin/runner.js | 2 +- lib/createInlinePluginCreator.js | 1 + lib/getWorkspacesYarn.js | 10 +- lib/git.js | 30 ++++ lib/glob.js | 9 +- lib/updateDeps.js | 147 +++++++++++++++++- lib/utils.js | 60 +++++++ package.json | 2 +- test/bin/cli.test.js | 16 ++ .../yarnWorkspacesIgnore/package.json | 21 +++ .../packages/a/package.json | 8 + .../packages/b/package.json | 12 ++ .../packages/c/.releaserc.json | 3 + .../packages/c/package.json | 8 + .../packages/d/package.json | 4 + .../yarnWorkspacesIgnoreSplit/package.json | 23 +++ .../packages/a/package.json | 8 + .../packages/b/package.json | 11 ++ .../packages/c/.releaserc.json | 3 + .../packages/c/package.json | 8 + .../packages/d/package.json | 4 + test/lib/getWorkspacesYarn.test.js | 46 ++++-- test/lib/git.test.js | 90 +++++++++++ test/lib/multiSemanticRelease.test.js | 94 ++++++++++- test/lib/updateDeps.test.js | 106 +++++++++++-- test/lib/utils.test.js | 69 ++++++++ yarn.lock | 42 ++--- 30 files changed, 817 insertions(+), 71 deletions(-) create mode 100644 lib/git.js create mode 100644 lib/utils.js create mode 100644 test/fixtures/yarnWorkspacesIgnore/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnore/packages/a/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnore/packages/b/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnore/packages/c/.releaserc.json create mode 100644 test/fixtures/yarnWorkspacesIgnore/packages/c/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnore/packages/d/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnoreSplit/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnoreSplit/packages/a/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnoreSplit/packages/b/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/.releaserc.json create mode 100644 test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/package.json create mode 100644 test/fixtures/yarnWorkspacesIgnoreSplit/packages/d/package.json create mode 100644 test/lib/git.test.js create mode 100644 test/lib/utils.test.js diff --git a/.gitignore b/.gitignore index 5793926..2cf3958 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/node_modules/ +**/node_modules/** /coverage/ *.log diff --git a/README.md b/README.md index b08edc1..35f2067 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,32 @@ _But_ in multi-semantic-release this configuration can be done globally (in your multi-semantic-release does not support any command line arguments (this wasn't possible without duplicating files from semantic-release, which I've tried to avoid). +Make sure to have a `workspaces` attribute inside your `package.json` project file. In there, you can set a list of packages that you might want to process in the msr process, as well as ignore others. For example, let's say your project has 4 packages (i.e. a, b, c and d) and you want to process only a and d (ignore b and c). You can set the following structure in your `package.json` file: +```json +{ + "name": "msr-test-yarn", + "author": "Dave Houlbrooke =8.3" + }, + "workspaces": [ + "packages/*", + "!packages/b/**", + "!packages/c/**" + ], + "release": { + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator" + ], + "noCi": true + } +} +``` + ## CLI There are several tweaks to adapt **msr** to some corner cases: @@ -43,6 +69,17 @@ There are several tweaks to adapt **msr** to some corner cases: |`--deps.bump`|string| Define deps version update rule. `override` — replace any prev version with the next one, `satisfy` — check the next pkg version against its current references. If it matches (`*` matches to any, `1.1.0` matches `1.1.x`, `1.5.0` matches to `^1.0.0` and so on) release will not be triggered, if not `override` strategy will be applied instead; `inherit` will try to follow the current declaration version/range. `~1.0.0` + `minor` turns into `~1.1.0`, `1.x` + `major` gives `2.x`, but `1.x` + `minor` gives `1.x` so there will be no release, etc. + **Experimental feat** | `override` |`--deps.release`|string| Define release type for dependent package if any of its deps changes. `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated; `inherit` — applies the "highest" release of updated deps to the package. For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain. **Experimental feat** | `patch` |`--dry-run`|bool |Dry run mode| `false` +|`--ignore-packages`|string|Packages list to be ignored on bumping process (append to the ones that already exist at package.json workspaces)|`null` + +Examples: + +``` +$ multi-semantic-release --debug +$ multi-semantic-release --deps.bump=satisfy --deps.release=patch +$ multi-semantic-release --ignore-packages=packages/a/**,packages/b/** +``` + +You can also combine the CLI `--ignore-packages` options with the `!` operator at each package inside `package.json.workspaces` attribute. Even though you can use the CLI to ignore options, you can't use it to set which packages to be released – i.e. you still need to set the `workspaces` attribute inside the `package.json`. ## API diff --git a/bin/cli.js b/bin/cli.js index eb26b6c..7e063ab 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -15,11 +15,13 @@ const cli = meow( --first-parent Apply commit filtering to current branch only. --deps.bump Define deps version updating rule. Allowed: override, satisfy, inherit. --deps.release Define release type for dependent package if any of its deps changes. Supported values: patch, minor, major, inherit. + --ignore-packages Packages' list to be ignored on bumping process --help Help info. Examples $ multi-semantic-release --debug $ multi-semantic-release --deps.bump=satisfy --deps.release=patch + $ multi-semantic-release --ignore-packages=packages/a/**,packages/b/** `, { flags: { @@ -40,6 +42,9 @@ const cli = meow( type: "string", default: "patch", }, + ignorePackages: { + type: "string", + }, dryRun: { type: "boolean", }, @@ -47,6 +52,11 @@ const cli = meow( } ); -const processFlags = (flags) => toPairs(flags).reduce((m, [k, v]) => set(m, k, v), {}); +const processFlags = (flags) => { + return toPairs(flags).reduce((m, [k, v]) => { + if (k === "ignorePackages" && v) return set(m, k, v.split(",")); + return set(m, k, v); + }, {}); +}; runner(processFlags(cli.flags)); diff --git a/bin/runner.js b/bin/runner.js index 30ee54b..74009ea 100644 --- a/bin/runner.js +++ b/bin/runner.js @@ -19,7 +19,7 @@ module.exports = (flags) => { console.log(`flags: ${JSON.stringify(flags, null, 2)}`); // Get list of package.json paths according to Yarn workspaces. - const paths = getWorkspacesYarn(cwd); + const paths = getWorkspacesYarn(cwd, flags.ignorePackages); console.log("yarn paths", paths); // Do multirelease (log out any errors). diff --git a/lib/createInlinePluginCreator.js b/lib/createInlinePluginCreator.js index 2bedf77..5fb9e34 100644 --- a/lib/createInlinePluginCreator.js +++ b/lib/createInlinePluginCreator.js @@ -87,6 +87,7 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags) const analyzeCommits = async (pluginOptions, context) => { const firstParentBranch = flags.firstParent ? context.branch.name : undefined; pkg._preRelease = context.branch.prerelease || null; + pkg._branch = context.branch.name; // Filter commits by directory. commits = await getCommitsFiltered(cwd, dir, context.lastRelease.gitHead, firstParentBranch); diff --git a/lib/getWorkspacesYarn.js b/lib/getWorkspacesYarn.js index 4b10d76..f98727d 100644 --- a/lib/getWorkspacesYarn.js +++ b/lib/getWorkspacesYarn.js @@ -6,9 +6,10 @@ const { checker } = require("./blork"); * Return array of package.json for Yarn workspaces. * * @param {string} cwd The current working directory where a package.json file can be found. + * @param {string[]|null} ignorePackages (Optional) Packages to be ignored passed via cli. * @returns {string[]} An array of package.json files corresponding to the workspaces setting in package.json */ -function getWorkspacesYarn(cwd) { +function getWorkspacesYarn(cwd, ignorePackages = null) { // Load package.json const manifest = getManifest(`${cwd}/package.json`); @@ -22,13 +23,16 @@ function getWorkspacesYarn(cwd) { throw new TypeError("package.json: workspaces or workspaces.packages: Must be non-empty array of string"); } + // If packages to be ignored come from CLI, we need to combine them with the ones from manifest workspaces + if (Array.isArray(ignorePackages)) packages.push(...ignorePackages.map((p) => `!${p}`)); + // Turn workspaces into list of package.json files. const workspaces = glob( packages.map((p) => p.replace(/\/?$/, "/package.json")), { cwd: cwd, - realpath: true, - ignore: "**/node_modules/**", + absolute: true, + gitignore: true, } ); diff --git a/lib/git.js b/lib/git.js new file mode 100644 index 0000000..d86eb66 --- /dev/null +++ b/lib/git.js @@ -0,0 +1,30 @@ +const execa = require("execa"); + +/** + * Get all the tags for a given branch. + * + * @param {String} branch The branch for which to retrieve the tags. + * @param {Object} [execaOptions] Options to pass to `execa`. + * @param {Array} filters List of prefixes/sufixes to be checked inside tags. + * + * @return {Array} List of git tags. + * @throws {Error} If the `git` command fails. + * @internal + */ +function getTags(branch, execaOptions, filters) { + let tags = execa.sync("git", ["tag", "--merged", branch], execaOptions).stdout; + tags = tags + .split("\n") + .map((tag) => tag.trim()) + .filter(Boolean); + + if (!filters || !filters.length) return tags; + + const validateSubstr = (t, f) => !!f.find((v) => t.includes(v)); + + return tags.filter((tag) => validateSubstr(tag, filters)); +} + +module.exports = { + getTags, +}; diff --git a/lib/glob.js b/lib/glob.js index c9bc256..1dfe023 100644 --- a/lib/glob.js +++ b/lib/glob.js @@ -1,10 +1,7 @@ -const bashGlob = require("bash-glob"); -const bashPath = require("bash-path"); +const globby = require("globby"); module.exports = (...args) => { - if (!bashPath) { - throw new TypeError("`bash` must be installed"); // TODO move this check to bash-glob - } + const [pattern, ...options] = args; - return bashGlob.sync(...args); + return globby.sync(pattern, ...options); }; diff --git a/lib/updateDeps.js b/lib/updateDeps.js index 212490e..9f44d0a 100644 --- a/lib/updateDeps.js +++ b/lib/updateDeps.js @@ -1,6 +1,11 @@ const { writeFileSync } = require("fs"); -const recognizeFormat = require("./recognizeFormat"); const semver = require("semver"); +const { isObject, isEqual, transform } = require("lodash"); +const recognizeFormat = require("./recognizeFormat"); +const getManifest = require("./getManifest"); +const { getHighestVersion, getLatestVersion } = require("./utils"); +const { getTags } = require("./git"); +const debug = require("debug")("msr:updateDeps"); /** * Resolve next package version. @@ -17,17 +22,106 @@ const getNextVersion = (pkg) => { : lastVersion || "1.0.0"; }; +/** + * Resolve the package version from a tag + * + * @param {Package} pkg Package object. + * @param {string} tag The tag containing the version to resolve + * @returns {string} The version of the package + * @returns {string|null} The version of the package or null if no tag was passed + * @internal + */ +const getVersionFromTag = (pkg, tag) => { + if (!pkg.name) return tag || null; + if (!tag) return null; + + const strMatch = tag.match(/[0-9].[0-9].[0-9].*/); + return strMatch && strMatch[0] && semver.valid(strMatch[0]) ? strMatch[0] : null; +}; + /** * Resolve next package version on prereleases. * * @param {Package} pkg Package object. + * @param {Array} tags Override list of tags from specific pkg and branch. * @returns {string|undefined} Next pkg version. * @internal */ -const getNextPreVersion = (pkg) => { +const getNextPreVersion = (pkg, tags) => { + const tagFilters = [pkg._preRelease]; const lastVersion = pkg._lastRelease && pkg._lastRelease.version; + // Extract tags: + // 1. Set filter to extract only package tags + // 2. Get tags from a branch considering the filters established + // 3. Resolve the versions from the tags + // TODO: replace {cwd: '.'} with multiContext.cwd + if (pkg.name) tagFilters.push(pkg.name); + if (!tags || !tags.length) { + tags = getTags(pkg._branch, { cwd: "." }, tagFilters); + } + const lastPreRelTag = getPreReleaseTag(lastVersion); + const isNewPreRelTag = lastPreRelTag && lastPreRelTag !== pkg._preRelease; + const versionToSet = + isNewPreRelTag || !lastVersion + ? `1.0.0-${pkg._preRelease}.1` + : _nextPreVersionCases( + tags.map((tag) => getVersionFromTag(pkg, tag)).filter((tag) => tag), + lastVersion, + pkg._nextType, + pkg._preRelease + ); + return versionToSet; +}; + +/** + * Parse the prerelease tag from a semver version. + * + * @param {string} version Semver version in a string format. + * @returns {string|null} preReleaseTag Version prerelease tag or null. + * @internal + */ +const getPreReleaseTag = (version) => { + const parsed = semver.parse(version); + if (!parsed) return null; + return parsed.prerelease[0] || null; +}; + +/** + * Resolve next prerelease special cases: highest version from tags or major/minor/patch. + * + * @param {Array} tags List of all released tags from package. + * @param {string} lastVersion Last package version released. + * @param {string} pkgNextType Next type evaluated for the next package type. + * @param {string} pkgPreRelease Package prerelease suffix. + * @returns {string|undefined} Next pkg version. + * @internal + */ +const _nextPreVersionCases = (tags, lastVersion, pkgNextType, pkgPreRelease) => { + // Case 1: Normal release on last version and is now converted to a prerelease + if (!semver.prerelease(lastVersion)) { + const { major, minor, patch } = semver.parse(lastVersion); + return `${semver.inc(`${major}.${minor}.${patch}`, pkgNextType || "patch")}-${pkgPreRelease}.1`; + } - return lastVersion ? semver.inc(lastVersion, "prerelease", pkg._preRelease) : `1.0.0-${pkg._preRelease}.1`; + // Case 2: Validates version with tags + const latestTag = getLatestVersion(tags, { withPrerelease: true }); + return _nextPreHighestVersion(latestTag, lastVersion, pkgPreRelease); +}; + +/** + * Resolve next prerelease comparing bumped tags versions with last version. + * + * @param {string|null} latestTag Last released tag from branch or null if non-existent. + * @param {string} lastVersion Last version released. + * @param {string} pkgPreRelease Prerelease tag from package to-be-released. + * @returns {string} Next pkg version. + * @internal + */ +const _nextPreHighestVersion = (latestTag, lastVersion, pkgPreRelease) => { + const bumpFromTags = latestTag ? semver.inc(latestTag, "prerelease", pkgPreRelease) : null; + const bumpFromLast = semver.inc(lastVersion, "prerelease", pkgPreRelease); + + return bumpFromTags ? getHighestVersion(bumpFromLast, bumpFromTags) : bumpFromLast; }; /** @@ -181,14 +275,61 @@ const updateManifestDeps = (pkg) => { throw Error(`Cannot release because dependency ${d.name} has not been released`); }); + if (!auditManifestChanges(manifest, path)) { + return; + } + // Write package.json back out. writeFileSync(path, JSON.stringify(manifest, null, indent) + trailingWhitespace); }; +// https://gist.github.com/Yimiprod/7ee176597fef230d1451 +const difference = (object, base) => + transform(object, (result, value, key) => { + if (!isEqual(value, base[key])) { + result[key] = + isObject(value) && isObject(base[key]) ? difference(value, base[key]) : `${base[key]} → ${value}`; + } + }); + +/** + * Clarify what exactly was changed in manifest file. + * @param {object} actualManifest manifest object + * @param {string} path manifest path + * @returns {boolean} has changed or not + * @internal + */ +const auditManifestChanges = (actualManifest, path) => { + const debugPrefix = `[${actualManifest.name}]`; + const oldManifest = getManifest(path); + const depScopes = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; + const changes = depScopes.reduce((res, scope) => { + const diff = difference(actualManifest[scope], oldManifest[scope]); + + if (Object.keys(diff).length) { + res[scope] = diff; + } + + return res; + }, {}); + + debug(debugPrefix, "package.json path=", path); + + if (Object.keys(changes).length) { + debug(debugPrefix, "changes=", changes); + return true; + } + + debug(debugPrefix, "no deps changes"); + return false; +}; + module.exports = { getNextVersion, getNextPreVersion, + getPreReleaseTag, updateManifestDeps, resolveReleaseType, resolveNextVersion, + getVersionFromTag, }; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..d027405 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,60 @@ +/** + * Lifted and tweaked from semantic-release because we follow how they bump their packages/dependencies. + * https://github.com/semantic-release/semantic-release/blob/master/lib/utils.js + */ + +const { gt, lt, prerelease, rcompare } = require("semver"); + +/** + * Get tag objects and convert them to a list of stringified versions. + * @param {array} tags Tags as object list. + * @returns {array} Tags as string list. + * @internal + */ +function tagsToVersions(tags) { + if (!tags) return []; + return tags.map(({ version }) => version); +} + +/** + * HOC that applies highest/lowest semver function. + * @param {Function} predicate High order function to be called. + * @param {string|undefined} version1 Version 1 to be compared with. + * @param {string|undefined} version2 Version 2 to be compared with. + * @returns {string|undefined} Highest or lowest version. + * @internal + */ +const _selectVersionBy = (predicate, version1, version2) => { + if (predicate && version1 && version2) { + return predicate(version1, version2) ? version1 : version2; + } + return version1 || version2; +}; + +/** + * Gets highest semver function binding gt to the HOC selectVersionBy. + */ +const getHighestVersion = _selectVersionBy.bind(null, gt); + +/** + * Gets lowest semver function binding gt to the HOC selectVersionBy. + */ +const getLowestVersion = _selectVersionBy.bind(null, lt); + +/** + * Retrieve the latest version from a list of versions. + * @param {array} versions Versions as string list. + * @param {bool|undefined} withPrerelease Prerelease flag. + * @returns {string|undefined} Latest version. + * @internal + */ +function getLatestVersion(versions, withPrerelease) { + return versions.filter((version) => withPrerelease || !prerelease(version)).sort(rcompare)[0]; +} + +module.exports = { + tagsToVersions, + getHighestVersion, + getLowestVersion, + getLatestVersion, +}; diff --git a/package.json b/package.json index 876f629..ad2aca1 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ ] }, "dependencies": { - "bash-glob": "^2.0.0", "blork": "^9.2.2", "cosmiconfig": "^7.0.0", "debug": "^4.3.1", @@ -55,6 +54,7 @@ "execa": "^4.1.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", + "globby": "11.0.2", "lodash": "^4.17.20", "meow": "^8.0.0", "promise-events": "^0.2.1", diff --git a/test/bin/cli.test.js b/test/bin/cli.test.js index 136d121..ebe7d85 100644 --- a/test/bin/cli.test.js +++ b/test/bin/cli.test.js @@ -29,4 +29,20 @@ describe("multi-semantic-release CLI", () => { expect(out).toMatch("Started multirelease! Loading 4 packages..."); expect(out).toMatch("Released 4 of 4 packages, semantically!"); }); + test("Initial commit (changes in 2 packages, 2 filtered out)", async () => { + // Create Git repo with copy of Yarn workspaces fixture. + const cwd = gitInit(); + copyDirectory(`test/fixtures/yarnWorkspaces/`, cwd); + const sha = gitCommitAll(cwd, "feat: Initial release"); + const url = gitInitOrigin(cwd); + gitPush(cwd); + + // Path to CLI command. + const filepath = `${__dirname}/../../bin/cli.js`; + + // Run via command line. + const out = (await execa("node", [filepath, "--ignore-packages=packages/c/**,packages/d/**"], { cwd })).stdout; + expect(out).toMatch("Started multirelease! Loading 2 packages..."); + expect(out).toMatch("Released 2 of 2 packages, semantically!"); + }); }); diff --git a/test/fixtures/yarnWorkspacesIgnore/package.json b/test/fixtures/yarnWorkspacesIgnore/package.json new file mode 100644 index 0000000..1ecbf8d --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnore/package.json @@ -0,0 +1,21 @@ +{ + "name": "msr-test-yarn", + "author": "Dave Houlbrooke =8.3" + }, + "workspaces": [ + "packages/*", + "!packages/d/**" + ], + "release": { + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator" + ], + "noCi": true + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnore/packages/a/package.json b/test/fixtures/yarnWorkspacesIgnore/packages/a/package.json new file mode 100644 index 0000000..92d8046 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnore/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "msr-test-a", + "version": "0.0.0", + "peerDependencies": { + "msr-test-c": "*", + "left-pad": "latest" + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnore/packages/b/package.json b/test/fixtures/yarnWorkspacesIgnore/packages/b/package.json new file mode 100644 index 0000000..d4b1ec7 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnore/packages/b/package.json @@ -0,0 +1,12 @@ + +{ + "name": "msr-test-b", + "version": "0.0.0", + "dependencies": { + "msr-test-a": "*" + }, + "devDependencies": { + "msr-test-c": "*", + "left-pad": "latest" + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnore/packages/c/.releaserc.json b/test/fixtures/yarnWorkspacesIgnore/packages/c/.releaserc.json new file mode 100644 index 0000000..64f41b1 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnore/packages/c/.releaserc.json @@ -0,0 +1,3 @@ +{ + "tagFormat": "multi-semantic-release-test-c@v${version}" +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnore/packages/c/package.json b/test/fixtures/yarnWorkspacesIgnore/packages/c/package.json new file mode 100644 index 0000000..0bdb127 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnore/packages/c/package.json @@ -0,0 +1,8 @@ +{ + "name": "msr-test-c", + "version": "0.0.0", + "devDependencies": { + "msr-test-b": "*", + "msr-test-d": "*" + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnore/packages/d/package.json b/test/fixtures/yarnWorkspacesIgnore/packages/d/package.json new file mode 100644 index 0000000..fa64f23 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnore/packages/d/package.json @@ -0,0 +1,4 @@ +{ + "name": "msr-test-d", + "version": "0.0.0" +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnoreSplit/package.json b/test/fixtures/yarnWorkspacesIgnoreSplit/package.json new file mode 100644 index 0000000..f37304b --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnoreSplit/package.json @@ -0,0 +1,23 @@ +{ + "name": "msr-test-yarn", + "author": "Dave Houlbrooke =8.3" + }, + "workspaces": [ + "packages/a", + "!packages/b", + "packages/c", + "!packages/d" + ], + "release": { + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator" + ], + "noCi": true + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnoreSplit/packages/a/package.json b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/a/package.json new file mode 100644 index 0000000..92d8046 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "msr-test-a", + "version": "0.0.0", + "peerDependencies": { + "msr-test-c": "*", + "left-pad": "latest" + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnoreSplit/packages/b/package.json b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/b/package.json new file mode 100644 index 0000000..1812781 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/b/package.json @@ -0,0 +1,11 @@ +{ + "name": "msr-test-b", + "version": "0.0.0", + "dependencies": { + "msr-test-a": "*" + }, + "devDependencies": { + "msr-test-c": "*", + "left-pad": "latest" + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/.releaserc.json b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/.releaserc.json new file mode 100644 index 0000000..64f41b1 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/.releaserc.json @@ -0,0 +1,3 @@ +{ + "tagFormat": "multi-semantic-release-test-c@v${version}" +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/package.json b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/package.json new file mode 100644 index 0000000..0bdb127 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/c/package.json @@ -0,0 +1,8 @@ +{ + "name": "msr-test-c", + "version": "0.0.0", + "devDependencies": { + "msr-test-b": "*", + "msr-test-d": "*" + } +} \ No newline at end of file diff --git a/test/fixtures/yarnWorkspacesIgnoreSplit/packages/d/package.json b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/d/package.json new file mode 100644 index 0000000..fa64f23 --- /dev/null +++ b/test/fixtures/yarnWorkspacesIgnoreSplit/packages/d/package.json @@ -0,0 +1,4 @@ +{ + "name": "msr-test-d", + "version": "0.0.0" +} \ No newline at end of file diff --git a/test/lib/getWorkspacesYarn.test.js b/test/lib/getWorkspacesYarn.test.js index 8361e78..1aa34cf 100644 --- a/test/lib/getWorkspacesYarn.test.js +++ b/test/lib/getWorkspacesYarn.test.js @@ -12,6 +12,41 @@ describe("getWorkspacesYarn()", () => { `${resolved}/packages/d/package.json`, ]); }); + test("Should ignore some packages", () => { + const resolved = resolve(`${__dirname}/../fixtures/yarnWorkspacesIgnore`); + expect(getWorkspacesYarn(resolved)).toEqual([ + `${resolved}/packages/a/package.json`, + `${resolved}/packages/b/package.json`, + `${resolved}/packages/c/package.json`, + ]); + + const resolvedSplit = resolve(`${__dirname}/../fixtures/yarnWorkspacesIgnoreSplit`); + expect(getWorkspacesYarn(resolvedSplit)).toEqual([ + `${resolvedSplit}/packages/a/package.json`, + `${resolvedSplit}/packages/c/package.json`, + ]); + }); + test("Should ignore some packages via CLI", () => { + const resolved = resolve(`${__dirname}/../fixtures/yarnWorkspacesIgnore`); + expect(getWorkspacesYarn(resolved, ["packages/a/**", "packages/b/**"])).toEqual([ + `${resolved}/packages/c/package.json`, + ]); + + const resolvedSplit = resolve(`${__dirname}/../fixtures/yarnWorkspacesIgnoreSplit`); + expect(getWorkspacesYarn(resolvedSplit, ["packages/b", "packages/d"])).toEqual([ + `${resolvedSplit}/packages/a/package.json`, + `${resolvedSplit}/packages/c/package.json`, + ]); + }); + test("Should throw when ignored packages from CLI and workspaces sets an empty workspace list to be processed", () => { + const resolved = resolve(`${__dirname}/../fixtures/yarnWorkspacesIgnore`); + expect(() => getWorkspacesYarn(resolved, ["packages/a/**", "packages/b/**", "packages/c/**"])).toThrow( + TypeError + ); + expect(() => getWorkspacesYarn(resolved, ["packages/a/**", "packages/b/**", "packages/c/**"])).toThrow( + "package.json: workspaces: Must contain one or more workspaces" + ); + }); test("TypeError if bad workspaces setting", () => { const resolved = resolve(`${__dirname}/../fixtures/badYarnWorkspaces`); expect(() => getWorkspacesYarn(resolved)).toThrow(TypeError); @@ -36,15 +71,4 @@ describe("getWorkspacesYarn()", () => { `${resolved}/packages/d/package.json`, ]); }); - test("Checks `bash` to be installed", () => { - jest.isolateModules(() => { - jest.resetModules(); - jest.mock("bash-path", () => undefined); - - const resolved = resolve(`${__dirname}/../fixtures/yarnWorkspaces`); - const getWorkspaces = require("../../lib/getWorkspacesYarn"); - - expect(() => getWorkspaces(resolved)).toThrowError("`bash` must be installed"); - }); - }); }); diff --git a/test/lib/git.test.js b/test/lib/git.test.js new file mode 100644 index 0000000..d437d09 --- /dev/null +++ b/test/lib/git.test.js @@ -0,0 +1,90 @@ +const tempy = require("tempy"); +const { WritableStreamBuffer } = require("stream-buffers"); +const { copyDirectory, createNewTestingFiles } = require("../helpers/file"); +const { gitInit, gitCommitAll, gitInitOrigin, gitPush } = require("../helpers/git"); +const { getTags } = require("../../lib/git"); + +test("Fetch all tags on master after two package release", async () => { + const packages = ["packages/c/", "packages/d/"]; + + // Create Git repo with copy of Yarn workspaces fixture. + const cwd = gitInit("master", "release"); + copyDirectory(`test/fixtures/yarnWorkspaces2Packages/`, cwd); + const sha1 = gitCommitAll(cwd, "feat: Initial release"); + gitInitOrigin(cwd, "release"); + gitPush(cwd); + + const stdout = new WritableStreamBuffer(); + const stderr = new WritableStreamBuffer(); + + // Call multiSemanticRelease() + // Doesn't include plugins that actually publish. + const multiSemanticRelease = require("../../"); + await multiSemanticRelease( + packages.map((folder) => `${folder}package.json`), + { + branches: [{ name: "master" }, { name: "release" }], + }, + { cwd, stdout, stderr } + ); + + const tags = getTags("master", { cwd }).sort(); + expect(tags).toEqual(["msr-test-d@1.0.0", "msr-test-c@1.0.0"].sort()); +}); + +test("Fetch only prerelease tags", async () => { + const packages = ["packages/c/", "packages/d/"]; + + // Create Git repo with copy of Yarn workspaces fixture. + const cwd = gitInit("master", "release"); + copyDirectory(`test/fixtures/yarnWorkspaces2Packages/`, cwd); + const sha1 = gitCommitAll(cwd, "feat: Initial release"); + gitInitOrigin(cwd, "release"); + gitPush(cwd); + + let stdout = new WritableStreamBuffer(); + let stderr = new WritableStreamBuffer(); + + // Call multiSemanticRelease() + // Doesn't include plugins that actually publish. + const multiSemanticRelease = require("../../"); + await multiSemanticRelease( + packages.map((folder) => `${folder}package.json`), + { + branches: [{ name: "master" }, { name: "release" }], + }, + { cwd, stdout, stderr } + ); + + // Add new testing files for a new release. + createNewTestingFiles(packages, cwd); + const sha = gitCommitAll(cwd, "feat: New prerelease\n\nBREAKING CHANGE: bump to bigger value"); + gitPush(cwd); + + // Capture output. + stdout = new WritableStreamBuffer(); + stderr = new WritableStreamBuffer(); + + // Call multiSemanticRelease() for a second release + // Doesn't include plugins that actually publish. + // Change the master branch from release to prerelease to test bumping. + await multiSemanticRelease( + packages.map((folder) => `${folder}package.json`), + { + branches: [{ name: "master", prerelease: "beta" }, { name: "release" }], + }, + { cwd, stdout, stderr } + ); + + const tags = getTags("master", { cwd }, ["beta"]).sort(); + expect(tags).toEqual(["msr-test-d@2.0.0-beta.1", "msr-test-c@2.0.0-beta.1"].sort()); +}); + +test("Throws error if obtaining the tags fails", () => { + const cwd = tempy.directory(); + + const t = () => { + getTags("master", { cwd }); + }; + expect(t).toThrow(Error); +}); diff --git a/test/lib/multiSemanticRelease.test.js b/test/lib/multiSemanticRelease.test.js index 33ce263..a08da33 100644 --- a/test/lib/multiSemanticRelease.test.js +++ b/test/lib/multiSemanticRelease.test.js @@ -352,6 +352,98 @@ describe("multiSemanticRelease()", () => { }, }); }); + test("Two separate releases (release to prerelease)", async () => { + const packages = ["packages/c/", "packages/d/"]; + + // Create Git repo with copy of Yarn workspaces fixture. + const cwd = gitInit("master", "release"); + copyDirectory(`test/fixtures/yarnWorkspaces2Packages/`, cwd); + const sha1 = gitCommitAll(cwd, "feat: Initial release"); + gitInitOrigin(cwd, "release"); + gitPush(cwd); + + let stdout = new WritableStreamBuffer(); + let stderr = new WritableStreamBuffer(); + + // Call multiSemanticRelease() + // Doesn't include plugins that actually publish. + const multiSemanticRelease = require("../../"); + let result = await multiSemanticRelease( + packages.map((folder) => `${folder}package.json`), + { + branches: [{ name: "master" }, { name: "release" }], + }, + { cwd, stdout, stderr } + ); + + // Add new testing files for a new release. + createNewTestingFiles(packages, cwd); + const sha = gitCommitAll(cwd, "feat: New prerelease\n\nBREAKING CHANGE: bump to bigger value"); + gitPush(cwd); + + // Capture output. + stdout = new WritableStreamBuffer(); + stderr = new WritableStreamBuffer(); + + // Call multiSemanticRelease() for a second release + // Doesn't include plugins that actually publish. + // Change the master branch from release to prerelease to test bumping. + result = await multiSemanticRelease( + packages.map((folder) => `${folder}package.json`), + { + branches: [{ name: "master", prerelease: "beta" }, { name: "release" }], + }, + { cwd, stdout, stderr } + ); + + // Get stdout and stderr output. + const err = stderr.getContentsAsString("utf8"); + expect(err).toBe(false); + const out = stdout.getContentsAsString("utf8"); + expect(out).toMatch("Started multirelease! Loading 2 packages..."); + expect(out).toMatch("Loaded package msr-test-c"); + expect(out).toMatch("Loaded package msr-test-d"); + expect(out).toMatch("Queued 2 packages! Starting release..."); + expect(out).toMatch("Created tag msr-test-c@2.0.0-beta.1"); + expect(out).toMatch("Created tag msr-test-d@2.0.0-beta.1"); + expect(out).toMatch("Released 2 of 2 packages, semantically!"); + + // D. + expect(result[0].name).toBe("msr-test-c"); + expect(result[0].result.lastRelease).toEqual({ + channels: [null], + gitHead: sha1, + gitTag: "msr-test-c@1.0.0", + name: "msr-test-c@1.0.0", + version: "1.0.0", + }); + expect(result[0].result.nextRelease).toMatchObject({ + gitHead: sha, + gitTag: "msr-test-c@2.0.0-beta.1", + type: "major", + version: "2.0.0-beta.1", + }); + + expect(result[0].result.nextRelease.notes).toMatch("# msr-test-c [2.0.0-beta.1]"); + expect(result[0].result.nextRelease.notes).toMatch("### Features\n\n* New prerelease"); + expect(result[0].result.nextRelease.notes).toMatch( + "### Dependencies\n\n* **msr-test-d:** upgraded to 2.0.0-beta.1" + ); + + expect(result[1].result.nextRelease.notes).toMatch("# msr-test-d [2.0.0-beta.1]"); + expect(result[1].result.nextRelease.notes).toMatch("### Features\n\n* New prerelease"); + expect(result[1].result.nextRelease.notes).not.toMatch("### Dependencies"); + + // ONLY 1 time. + expect(result).toHaveLength(2); + + // Check manifests. + expect(require(`${cwd}/packages/c/package.json`)).toMatchObject({ + dependencies: { + "msr-test-d": "2.0.0-beta.1", + }, + }); + }, 10000); test("Two separate releases (changes in all packages with prereleases)", async () => { const packages = ["packages/a/", "packages/b/", "packages/c/", "packages/d/"]; @@ -516,7 +608,7 @@ describe("multiSemanticRelease()", () => { "msr-test-d": "1.0.0-dev.2", }, }); - }); + }, 10000); test("No changes in any packages", async () => { // Create Git repo with copy of Yarn workspaces fixture. const cwd = gitInit(); diff --git a/test/lib/updateDeps.test.js b/test/lib/updateDeps.test.js index f4d1b95..1b6f89d 100644 --- a/test/lib/updateDeps.test.js +++ b/test/lib/updateDeps.test.js @@ -1,4 +1,11 @@ -const { resolveReleaseType, resolveNextVersion, getNextVersion, getNextPreVersion } = require("../../lib/updateDeps"); +const { + resolveReleaseType, + resolveNextVersion, + getNextVersion, + getNextPreVersion, + getPreReleaseTag, + getVersionFromTag, +} = require("../../lib/updateDeps"); describe("resolveNextVersion()", () => { // prettier-ignore @@ -153,12 +160,13 @@ describe("getNextVersion()", () => { ["1.0.0-dev.1", "patch", "1.0.0"], ] - cases.forEach(([lastVersion, releaseType, nextVersion]) => { + cases.forEach(([lastVersion, releaseType, nextVersion, preRelease]) => { it(`${lastVersion} and ${releaseType} gives ${nextVersion}`, () => { // prettier-ignore expect(getNextVersion({ _nextType: releaseType, - _lastRelease: {version: lastVersion} + _lastRelease: {version: lastVersion}, + _preRelease: preRelease })).toBe(nextVersion); }); }); @@ -167,21 +175,89 @@ describe("getNextVersion()", () => { describe("getNextPreVersion()", () => { // prettier-ignore const cases = [ - [undefined, "patch", "rc", "1.0.0-rc.1"], - [undefined, "patch", "rc", "1.0.0-rc.1"], - ["1.0.0-rc.0", "minor", "dev", "1.0.0-dev.0"], - ["1.0.0-dev.0", "major", "dev", "1.0.0-dev.1"], - + [undefined, "patch", "rc", [], "1.0.0-rc.1"], + [undefined, "patch", "rc", [], "1.0.0-rc.1"], + [null, "patch", "rc", [], "1.0.0-rc.1"], + [null, "patch", "rc", [], "1.0.0-rc.1"], + ["1.0.0-rc.0", "minor", "dev", [], "1.0.0-dev.1"], + ["1.0.0-dev.0", "major", "dev", [], "1.0.0-dev.1"], + ["1.0.0-dev.0", "major", "dev", ["1.0.0-dev.1"], "1.0.0-dev.2"], + ["1.0.0-dev.0", "major", "dev", ["1.0.0-dev.1", "1.0.1-dev.0"], "1.0.1-dev.1"], + ["11.0.0", "major", "beta", [], "12.0.0-beta.1"], + ["1.0.0", "minor", "beta", [], "1.1.0-beta.1"], + ["1.0.0", "patch", "beta", [], "1.0.1-beta.1"], ] - cases.forEach(([lastVersion, releaseType, preRelease, nextVersion]) => { - it(`${lastVersion} and ${releaseType} gives ${nextVersion}`, () => { + cases.forEach(([lastVersion, releaseType, preRelease, lastTags, nextVersion]) => { + it(`${lastVersion} and ${releaseType} ${ + lastTags.length ? "with existent tags " : "" + }gives ${nextVersion}`, () => { // prettier-ignore - expect(getNextPreVersion({ - _nextType: releaseType, - _lastRelease: {version: lastVersion}, - _preRelease: preRelease - })).toBe(nextVersion); + expect(getNextPreVersion( + { + _nextType: releaseType, + _lastRelease: {version: lastVersion}, + _preRelease: preRelease, + _branch: "master", + }, + lastTags + )).toBe(nextVersion); + }); + }); +}); + +describe("getPreReleaseTag()", () => { + // prettier-ignore + const cases = [ + [undefined, null], + [null, null], + ["1.0.0-rc.0", "rc"], + ["1.0.0-dev.0", "dev"], + ["1.0.0-dev.2", "dev"], + ["1.1.0-beta.0", "beta"], + ["11.0.0", null], + ["11.1.0", null], + ["11.0.1", null], + ] + + cases.forEach(([version, preReleaseTag]) => { + it(`${version} gives ${preReleaseTag}`, () => { + // prettier-ignore + expect(getPreReleaseTag(version)).toBe(preReleaseTag); + }); + }); +}); + +describe("getVersionFromTag()", () => { + // prettier-ignore + const cases = [ + [{}, undefined, null], + [{ name: undefined }, undefined, null], + [{}, null, null], + [{ name: null }, null, null], + [{ name: undefined }, '1.0.0', '1.0.0'], + [{ name: null }, '1.0.0', '1.0.0'], + [{ name: 'abc' }, undefined, null], + [{ name: 'abc' }, null, null], + [{ name: 'abc' }, '1.0.0', '1.0.0'], + [{ name: 'dev' }, '1.0.0-dev.1', '1.0.0-dev.1'], + [{ name: 'app' }, 'app@1.0.0-dev.1', '1.0.0-dev.1'], + [{ name: 'app' }, 'app@1.0.0-devapp@.1', null], + [{ name: 'msr-test-a' }, 'msr-test-a@1.0.0-rc.1', '1.0.0-rc.1'], + [{ name: 'msr.test.a' }, 'msr.test.a@1.0.0', '1.0.0'], + [{ name: 'msr_test_a' }, 'msr_test_a@1.0.0', '1.0.0'], + [{ name: 'msr@test@a' }, 'msr@test@a@1.0.0', '1.0.0'], + [{ name: 'abc' }, 'a.b.c-rc.0', null], + [{ name: 'abc' }, '1-rc.0', null], + [{ name: 'abc' }, '1.0.x-rc.0', null], + [{ name: 'abc' }, '1.x.0-rc.0', null], + [{ name: 'abc' }, 'x.1.0-rc.0', null], + ] + + cases.forEach(([pkg, tag, versionFromTag]) => { + it(`${JSON.stringify(pkg)} pkg with tag ${tag} gives ${versionFromTag}`, () => { + // prettier-ignore + expect(getVersionFromTag(pkg, tag)).toBe(versionFromTag); }); }); }); diff --git a/test/lib/utils.test.js b/test/lib/utils.test.js new file mode 100644 index 0000000..1990261 --- /dev/null +++ b/test/lib/utils.test.js @@ -0,0 +1,69 @@ +const { tag } = require("semantic-release/lib/git"); +const { getHighestVersion, getLowestVersion, getLatestVersion, tagsToVersions } = require("../../lib/utils"); + +describe("tagsToVersions()", () => { + // prettier-ignore + const cases = [ + [[{version: "1.0.0"}, {version: "1.1.0"}, {version: "1.2.0"}], ["1.0.0", "1.1.0", "1.2.0"]], + [[],[]], + [undefined, []], + [null, []], + ] + + cases.forEach(([tags, versions]) => { + it(`${tags} gives versions as ${versions}`, () => { + expect(tagsToVersions(tags)).toStrictEqual(versions); + }); + }); +}); + +describe("getHighestVersion()", () => { + // prettier-ignore + const cases = [ + ["1.0.0", "2.0.0", "2.0.0"], + ["1.1.1", "1.0.0", "1.1.1"], + [null, "1.0.0", "1.0.0"], + ["1.0.0", undefined, "1.0.0"], + [undefined, undefined, undefined], + ] + + cases.forEach(([version1, version2, high]) => { + it(`${version1}/${version2} gives highest as ${high}`, () => { + expect(getHighestVersion(version1, version2)).toBe(high); + }); + }); +}); + +describe("getLowestVersion()", () => { + // prettier-ignore + const cases = [ + ["1.0.0", "2.0.0", "1.0.0"], + ["1.1.1", "1.0.0", "1.0.0"], + [null, "1.0.0", "1.0.0"], + ["1.0.0", undefined, "1.0.0"], + [undefined, undefined, undefined], + ] + + cases.forEach(([version1, version2, low]) => { + it(`${version1}/${version2} gives lowest as ${low}`, () => { + expect(getLowestVersion(version1, version2, 0)).toBe(low); + }); + }); +}); + +describe("getLatestVersion()", () => { + // prettier-ignore + const cases = [ + [["1.2.3-alpha.3", "1.2.0", "1.0.1", "1.0.0-alpha.1"], null, "1.2.0"], + [["1.2.3-alpha.3", "1.2.3-alpha.2"], null, undefined], + [["1.2.3-alpha.3", "1.2.0", "1.0.1", "1.0.0-alpha.1"], true, "1.2.3-alpha.3"], + [["1.2.3-alpha.3", "1.2.3-alpha.2"], true, "1.2.3-alpha.3"], + [[], {}, undefined] + ] + + cases.forEach(([versions, withPrerelease, latest]) => { + it(`${versions}/${withPrerelease} gives latest as ${latest}`, () => { + expect(getLatestVersion(versions, withPrerelease)).toBe(latest); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index b6c2481..7b75f10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1326,27 +1326,6 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -bash-glob@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bash-glob/-/bash-glob-2.0.0.tgz#a8ef19450783403ed93fccca2dbe09f2cf6320dc" - integrity sha512-53/NJ+t2UAkEYgQPO6aFjbx1Ue8vNNXCYaA4EljNKP1SR8A9dSQQoBmYWR8BLXO0/NDRJEMSJ4BxWihi//m3Kw== - dependencies: - bash-path "^1.0.1" - component-emitter "^1.2.1" - cross-spawn "^5.1.0" - each-parallel-async "^1.0.0" - extend-shallow "^2.0.1" - is-extglob "^2.1.1" - is-glob "^4.0.0" - -bash-path@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/bash-path/-/bash-path-1.0.3.tgz#dbc9efbdf18b1c11413dcb59b960e6aa56c84258" - integrity sha512-mGrYvOa6yTY/qNCiZkPFJqWmODK68y6kmVRAJ1NNbWlNoJrUrsFxu7FU2EKg7gbrer6ttrKkF2s/E/lhRy7/OA== - dependencies: - arr-union "^3.1.0" - is-windows "^1.0.1" - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -1964,7 +1943,7 @@ create-error-class@^3.0.0: dependencies: capture-stack-trace "^1.0.0" -cross-spawn@^5.0.1, cross-spawn@^5.1.0: +cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= @@ -2289,11 +2268,6 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -each-parallel-async@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/each-parallel-async/-/each-parallel-async-1.0.0.tgz#91783e190000c7dd588336b2d468ebaf71980f7b" - integrity sha512-P/9kLQiQj0vZNzphvKKTgRgMnlqs5cJsxeAiuog1jrUnwv0Z3hVUwJDQiP7MnLb2I9S15nR9SRUceFT9IxtqRg== - ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -3126,6 +3100,18 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globby@11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" + integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^11.0.0, globby@^11.0.1: version "11.0.1" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" @@ -3766,7 +3752,7 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-windows@^1.0.1, is-windows@^1.0.2: +is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==