🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@changesets/get-dependents-graph

Package Overview
Dependencies
Maintainers
4
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@changesets/get-dependents-graph - npm Package Compare versions

Comparing version
2.1.3
to
2.1.4
+6
-0
CHANGELOG.md
# @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 @@

+1
-1
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;

{
"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 "foo" must depend on the current version of "bar": "1.0.0" vs "0.9.0"",
],
]
`);
})
);
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 "foo" must depend on the current version of "bar": "1.0.0" vs "workspace:packages/not-bar"",
],
]
`);
})
);
});

@@ -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"}