@changesets/get-dependents-graph
Advanced tools
+6
-0
| # @changesets/get-dependents-graph | ||
| ## 2.1.4 | ||
| ### Patch Changes | ||
| - [#1888](https://github.com/changesets/changesets/pull/1888) [`036fdd4`](https://github.com/changesets/changesets/commit/036fdd451367226d0f2cd8af1e0a7f37a65e3464) Thanks [@mixelburg](https://github.com/mixelburg)! - Fix dependency graph validation for workspace path references. Valid `workspace:packages/pkg` specifiers are now treated as local dependencies instead of being rejected as invalid ranges. | ||
| ## 2.1.3 | ||
@@ -4,0 +10,0 @@ |
| export * from "./declarations/src/index.js"; | ||
| //# sourceMappingURL=changesets-get-dependents-graph.cjs.d.mts.map | ||
| //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hhbmdlc2V0cy1nZXQtZGVwZW5kZW50cy1ncmFwaC5janMuZC5tdHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuL2RlY2xhcmF0aW9ucy9zcmMvaW5kZXguZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9 |
@@ -1,2 +0,2 @@ | ||
| export * from "./declarations/src/index"; | ||
| //# sourceMappingURL=changesets-get-dependents-graph.cjs.d.ts.map | ||
| export * from "./declarations/src/index.js"; | ||
| //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hhbmdlc2V0cy1nZXQtZGVwZW5kZW50cy1ncmFwaC5janMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4vZGVjbGFyYXRpb25zL3NyYy9pbmRleC5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0= |
@@ -7,2 +7,3 @@ 'use strict'; | ||
| var pc = require('picocolors'); | ||
| var path = require('node:path'); | ||
@@ -13,29 +14,22 @@ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } | ||
| var pc__default = /*#__PURE__*/_interopDefault(pc); | ||
| var path__default = /*#__PURE__*/_interopDefault(path); | ||
| // This is a modified version of the graph-getting in bolt | ||
| const DEPENDENCY_TYPES = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; | ||
| const getAllDependencies = (config, ignoreDevDependencies) => { | ||
| const allDependencies = new Map(); | ||
| for (const type of DEPENDENCY_TYPES) { | ||
| const deps = config[type]; | ||
| if (!deps) continue; | ||
| for (const name of Object.keys(deps)) { | ||
| const depRange = deps[name]; | ||
| if (type === "devDependencies" && (ignoreDevDependencies || depRange.startsWith("link:") || depRange.startsWith("file:"))) { | ||
| continue; | ||
| } | ||
| allDependencies.set(name, depRange); | ||
| } | ||
| } | ||
| return allDependencies; | ||
| }; | ||
| const isProtocolRange = range => range.indexOf(":") !== -1; | ||
| const getValidRange = potentialRange => { | ||
@@ -45,3 +39,2 @@ if (isProtocolRange(potentialRange)) { | ||
| } | ||
| try { | ||
@@ -53,3 +46,2 @@ return new Range__default["default"](potentialRange); | ||
| }; | ||
| function getDependencyGraph(packages, { | ||
@@ -64,9 +56,11 @@ ignoreDevDependencies = false, | ||
| }; | ||
| const relativePathsByName = { | ||
| [packages.root.packageJson.name]: "." | ||
| }; | ||
| const queue = [packages.root]; | ||
| for (const pkg of packages.packages) { | ||
| queue.push(pkg); | ||
| packagesByName[pkg.packageJson.name] = pkg; | ||
| relativePathsByName[pkg.packageJson.name] = path__default["default"].relative(packages.root.dir, pkg.dir).replace(/\\/g, "/"); | ||
| } | ||
| for (const pkg of queue) { | ||
@@ -78,3 +72,2 @@ const { | ||
| const allDependencies = getAllDependencies(pkg.packageJson, ignoreDevDependencies); | ||
| for (let [depName, depRange] of allDependencies) { | ||
@@ -84,7 +77,6 @@ const match = packagesByName[depName]; | ||
| const expected = match.packageJson.version; | ||
| const rawDepRange = depRange; | ||
| const usesWorkspaceRange = depRange.startsWith("workspace:"); | ||
| if (usesWorkspaceRange) { | ||
| depRange = depRange.replace(/^workspace:/, ""); | ||
| if (depRange === "*" || depRange === "^" || depRange === "~") { | ||
@@ -94,23 +86,28 @@ dependencies.push(depName); | ||
| } | ||
| if (path__default["default"].posix.normalize(depRange) === relativePathsByName[depName]) { | ||
| dependencies.push(depName); | ||
| continue; | ||
| } | ||
| if (!getValidRange(depRange)) { | ||
| valid = false; | ||
| console.error(`Package ${pc__default["default"].cyan(`"${name}"`)} must depend on the current version of ${pc__default["default"].cyan(`"${depName}"`)}: ${pc__default["default"].green(`"${expected}"`)} vs ${pc__default["default"].red(`"${rawDepRange}"`)}`); | ||
| continue; | ||
| } | ||
| } else if (bumpVersionsWithWorkspaceProtocolOnly) { | ||
| continue; | ||
| } | ||
| const range = getValidRange(depRange); | ||
| if (range && !range.test(expected) || isProtocolRange(depRange)) { | ||
| valid = false; | ||
| console.error(`Package ${pc__default["default"].cyan(`"${name}"`)} must depend on the current version of ${pc__default["default"].cyan(`"${depName}"`)}: ${pc__default["default"].green(`"${expected}"`)} vs ${pc__default["default"].red(`"${depRange}"`)}`); | ||
| console.error(`Package ${pc__default["default"].cyan(`"${name}"`)} must depend on the current version of ${pc__default["default"].cyan(`"${depName}"`)}: ${pc__default["default"].green(`"${expected}"`)} vs ${pc__default["default"].red(`"${rawDepRange}"`)}`); | ||
| continue; | ||
| } // `depRange` could have been a tag and if a tag has been used there might have been a reason for that | ||
| } | ||
| // `depRange` could have been a tag and if a tag has been used there might have been a reason for that | ||
| // we should not count this as a local monorepro dependant | ||
| if (!range) { | ||
| continue; | ||
| } | ||
| dependencies.push(depName); | ||
| } | ||
| graph.set(name, { | ||
@@ -121,3 +118,2 @@ pkg, | ||
| } | ||
| return { | ||
@@ -149,3 +145,2 @@ graph, | ||
| const valFromDependencyGraph = dependencyGraph.get(dependent); | ||
| if (valFromDependencyGraph) { | ||
@@ -152,0 +147,0 @@ const dependencies = valFromDependencyGraph.dependencies; |
| import Range from 'semver/classes/range'; | ||
| import pc from 'picocolors'; | ||
| import path from 'node:path'; | ||
| // This is a modified version of the graph-getting in bolt | ||
| const DEPENDENCY_TYPES = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]; | ||
| const getAllDependencies = (config, ignoreDevDependencies) => { | ||
| const allDependencies = new Map(); | ||
| for (const type of DEPENDENCY_TYPES) { | ||
| const deps = config[type]; | ||
| if (!deps) continue; | ||
| for (const name of Object.keys(deps)) { | ||
| const depRange = deps[name]; | ||
| if (type === "devDependencies" && (ignoreDevDependencies || depRange.startsWith("link:") || depRange.startsWith("file:"))) { | ||
| continue; | ||
| } | ||
| allDependencies.set(name, depRange); | ||
| } | ||
| } | ||
| return allDependencies; | ||
| }; | ||
| const isProtocolRange = range => range.indexOf(":") !== -1; | ||
| const getValidRange = potentialRange => { | ||
@@ -34,3 +27,2 @@ if (isProtocolRange(potentialRange)) { | ||
| } | ||
| try { | ||
@@ -42,3 +34,2 @@ return new Range(potentialRange); | ||
| }; | ||
| function getDependencyGraph(packages, { | ||
@@ -53,9 +44,11 @@ ignoreDevDependencies = false, | ||
| }; | ||
| const relativePathsByName = { | ||
| [packages.root.packageJson.name]: "." | ||
| }; | ||
| const queue = [packages.root]; | ||
| for (const pkg of packages.packages) { | ||
| queue.push(pkg); | ||
| packagesByName[pkg.packageJson.name] = pkg; | ||
| relativePathsByName[pkg.packageJson.name] = path.relative(packages.root.dir, pkg.dir).replace(/\\/g, "/"); | ||
| } | ||
| for (const pkg of queue) { | ||
@@ -67,3 +60,2 @@ const { | ||
| const allDependencies = getAllDependencies(pkg.packageJson, ignoreDevDependencies); | ||
| for (let [depName, depRange] of allDependencies) { | ||
@@ -73,7 +65,6 @@ const match = packagesByName[depName]; | ||
| const expected = match.packageJson.version; | ||
| const rawDepRange = depRange; | ||
| const usesWorkspaceRange = depRange.startsWith("workspace:"); | ||
| if (usesWorkspaceRange) { | ||
| depRange = depRange.replace(/^workspace:/, ""); | ||
| if (depRange === "*" || depRange === "^" || depRange === "~") { | ||
@@ -83,23 +74,28 @@ dependencies.push(depName); | ||
| } | ||
| if (path.posix.normalize(depRange) === relativePathsByName[depName]) { | ||
| dependencies.push(depName); | ||
| continue; | ||
| } | ||
| if (!getValidRange(depRange)) { | ||
| valid = false; | ||
| console.error(`Package ${pc.cyan(`"${name}"`)} must depend on the current version of ${pc.cyan(`"${depName}"`)}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${rawDepRange}"`)}`); | ||
| continue; | ||
| } | ||
| } else if (bumpVersionsWithWorkspaceProtocolOnly) { | ||
| continue; | ||
| } | ||
| const range = getValidRange(depRange); | ||
| if (range && !range.test(expected) || isProtocolRange(depRange)) { | ||
| valid = false; | ||
| console.error(`Package ${pc.cyan(`"${name}"`)} must depend on the current version of ${pc.cyan(`"${depName}"`)}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${depRange}"`)}`); | ||
| console.error(`Package ${pc.cyan(`"${name}"`)} must depend on the current version of ${pc.cyan(`"${depName}"`)}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${rawDepRange}"`)}`); | ||
| continue; | ||
| } // `depRange` could have been a tag and if a tag has been used there might have been a reason for that | ||
| } | ||
| // `depRange` could have been a tag and if a tag has been used there might have been a reason for that | ||
| // we should not count this as a local monorepro dependant | ||
| if (!range) { | ||
| continue; | ||
| } | ||
| dependencies.push(depName); | ||
| } | ||
| graph.set(name, { | ||
@@ -110,3 +106,2 @@ pkg, | ||
| } | ||
| return { | ||
@@ -138,3 +133,2 @@ graph, | ||
| const valFromDependencyGraph = dependencyGraph.get(dependent); | ||
| if (valFromDependencyGraph) { | ||
@@ -141,0 +135,0 @@ const dependencies = valFromDependencyGraph.dependencies; |
+1
-1
| { | ||
| "name": "@changesets/get-dependents-graph", | ||
| "version": "2.1.3", | ||
| "version": "2.1.4", | ||
| "description": "Get the graph of dependents in a monorepo", | ||
@@ -5,0 +5,0 @@ "main": "dist/changesets-get-dependents-graph.cjs.js", |
@@ -118,2 +118,156 @@ import { temporarilySilenceLogs } from "@changesets/test-utils"; | ||
| ); | ||
| it( | ||
| "should error on dependencies not specified using workspace protocol when bumpVersionsWithWorkspaceProtocolOnly is false", | ||
| temporarilySilenceLogs(() => { | ||
| const { valid } = getDependencyGraph({ | ||
| root: { | ||
| dir: ".", | ||
| packageJson: { name: "root", version: "1.0.0" }, | ||
| }, | ||
| packages: [ | ||
| { | ||
| dir: "foo", | ||
| packageJson: { | ||
| name: "foo", | ||
| version: "1.0.0", | ||
| dependencies: { | ||
| bar: "0.9.0", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| dir: "bar", | ||
| packageJson: { | ||
| name: "bar", | ||
| version: "1.0.0", | ||
| }, | ||
| }, | ||
| ], | ||
| tool: "pnpm", | ||
| }); | ||
| expect(valid).toBe(false); | ||
| expect((console.error as any).mock.calls).toMatchInlineSnapshot(` | ||
| [ | ||
| [ | ||
| "Package [36m"foo"[39m must depend on the current version of [36m"bar"[39m: [32m"1.0.0"[39m vs [31m"0.9.0"[39m", | ||
| ], | ||
| ] | ||
| `); | ||
| }) | ||
| ); | ||
| it( | ||
| "should skip dependencies not specified using workspace protocol when bumpVersionsWithWorkspaceProtocolOnly is true", | ||
| temporarilySilenceLogs(() => { | ||
| const { valid } = getDependencyGraph( | ||
| { | ||
| root: { | ||
| dir: ".", | ||
| packageJson: { name: "root", version: "1.0.0" }, | ||
| }, | ||
| packages: [ | ||
| { | ||
| dir: "foo", | ||
| packageJson: { | ||
| name: "foo", | ||
| version: "1.0.0", | ||
| dependencies: { | ||
| bar: "0.9.0", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| dir: "bar", | ||
| packageJson: { | ||
| name: "bar", | ||
| version: "1.0.0", | ||
| }, | ||
| }, | ||
| ], | ||
| tool: "pnpm", | ||
| }, | ||
| { | ||
| bumpVersionsWithWorkspaceProtocolOnly: true, | ||
| } | ||
| ); | ||
| expect(valid).toBe(true); | ||
| expect((console.error as any).mock.calls).toMatchInlineSnapshot(`[]`); | ||
| }) | ||
| ); | ||
| it("should treat workspace path dependencies as valid local dependencies", () => { | ||
| const { graph, valid } = getDependencyGraph({ | ||
| root: { | ||
| dir: ".", | ||
| packageJson: { name: "root", version: "1.0.0" }, | ||
| }, | ||
| packages: [ | ||
| { | ||
| dir: "packages/foo", | ||
| packageJson: { | ||
| name: "foo", | ||
| version: "1.0.0", | ||
| dependencies: { | ||
| bar: "workspace:packages/bar", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| dir: "packages/bar", | ||
| packageJson: { | ||
| name: "bar", | ||
| version: "1.0.0", | ||
| }, | ||
| }, | ||
| ], | ||
| tool: "pnpm", | ||
| }); | ||
| expect(graph.get("foo")!.dependencies).toStrictEqual(["bar"]); | ||
| expect(valid).toBeTruthy(); | ||
| expect((console.error as any).mock.calls).toMatchInlineSnapshot(`[]`); | ||
| }); | ||
| it( | ||
| "should error on mismatched workspace path dependencies", | ||
| temporarilySilenceLogs(() => { | ||
| const { graph, valid } = getDependencyGraph({ | ||
| root: { | ||
| dir: ".", | ||
| packageJson: { name: "root", version: "1.0.0" }, | ||
| }, | ||
| packages: [ | ||
| { | ||
| dir: "packages/foo", | ||
| packageJson: { | ||
| name: "foo", | ||
| version: "1.0.0", | ||
| dependencies: { | ||
| bar: "workspace:packages/not-bar", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| dir: "packages/bar", | ||
| packageJson: { | ||
| name: "bar", | ||
| version: "1.0.0", | ||
| }, | ||
| }, | ||
| ], | ||
| tool: "pnpm", | ||
| }); | ||
| expect(graph.get("foo")!.dependencies).toStrictEqual([]); | ||
| expect(valid).toBe(false); | ||
| expect((console.error as any).mock.calls).toMatchInlineSnapshot(` | ||
| [ | ||
| [ | ||
| "Package [36m"foo"[39m must depend on the current version of [36m"bar"[39m: [32m"1.0.0"[39m vs [31m"workspace:packages/not-bar"[39m", | ||
| ], | ||
| ] | ||
| `); | ||
| }) | ||
| ); | ||
| }); |
@@ -6,2 +6,3 @@ // This is a modified version of the graph-getting in bolt | ||
| import { PackageJSON } from "@changesets/types"; | ||
| import path from "node:path"; | ||
@@ -79,2 +80,5 @@ const DEPENDENCY_TYPES = [ | ||
| }; | ||
| const relativePathsByName: { [key: string]: string } = { | ||
| [packages.root.packageJson.name]: ".", | ||
| }; | ||
@@ -86,2 +90,5 @@ const queue = [packages.root]; | ||
| packagesByName[pkg.packageJson.name] = pkg; | ||
| relativePathsByName[pkg.packageJson.name] = path | ||
| .relative(packages.root.dir, pkg.dir) | ||
| .replace(/\\/g, "/"); | ||
| } | ||
@@ -102,2 +109,3 @@ | ||
| const expected = match.packageJson.version; | ||
| const rawDepRange = depRange; | ||
| const usesWorkspaceRange = depRange.startsWith("workspace:"); | ||
@@ -112,2 +120,19 @@ | ||
| } | ||
| if (path.posix.normalize(depRange) === relativePathsByName[depName]) { | ||
| dependencies.push(depName); | ||
| continue; | ||
| } | ||
| if (!getValidRange(depRange)) { | ||
| valid = false; | ||
| console.error( | ||
| `Package ${pc.cyan( | ||
| `"${name}"` | ||
| )} must depend on the current version of ${pc.cyan( | ||
| `"${depName}"` | ||
| )}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${rawDepRange}"`)}` | ||
| ); | ||
| continue; | ||
| } | ||
| } else if (bumpVersionsWithWorkspaceProtocolOnly) { | ||
@@ -126,3 +151,3 @@ continue; | ||
| `"${depName}"` | ||
| )}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${depRange}"`)}` | ||
| )}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${rawDepRange}"`)}` | ||
| ); | ||
@@ -129,0 +154,0 @@ continue; |
| {"version":3,"file":"changesets-get-dependents-graph.cjs.d.mts","sourceRoot":"","sources":["./declarations/src/index.d.ts"],"names":[],"mappings":"AAAA"} |
| {"version":3,"file":"changesets-get-dependents-graph.cjs.d.ts","sourceRoot":"","sources":["./declarations/src/index.d.ts"],"names":[],"mappings":"AAAA"} |
34732
24.96%737
38.53%12
-14.29%