@changesets/get-dependents-graph
Advanced tools
| import { Packages } from "@changesets/types"; | ||
| //#region src/index.d.ts | ||
| declare function getDependentsGraph(packages: Packages, opts?: { | ||
| ignoreDevDependencies?: boolean; | ||
| bumpVersionsWithWorkspaceProtocolOnly?: boolean; | ||
| }): Map<any, any>; | ||
| //#endregion | ||
| export { getDependentsGraph }; |
+128
| import path from "node:path"; | ||
| import { styleText } from "node:util"; | ||
| import Range from "semver/classes/range.js"; | ||
| //#region ../color/src/index.ts | ||
| var src_default = new Proxy({}, { get(target, color) { | ||
| target[color] ??= (text) => styleText(color, text); | ||
| return target[color]; | ||
| } }); | ||
| //#endregion | ||
| //#region src/get-dependency-graph.ts | ||
| const DEPENDENCY_TYPES = [ | ||
| "dependencies", | ||
| "devDependencies", | ||
| "peerDependencies", | ||
| "optionalDependencies" | ||
| ]; | ||
| const getAllDependencies = (config, ignoreDevDependencies) => { | ||
| const allDependencies = /* @__PURE__ */ 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) => { | ||
| if (isProtocolRange(potentialRange)) return null; | ||
| try { | ||
| return new Range(potentialRange); | ||
| } catch { | ||
| return null; | ||
| } | ||
| }; | ||
| function getDependencyGraph(packages, rootPackage, { ignoreDevDependencies = false, bumpVersionsWithWorkspaceProtocolOnly = false } = {}) { | ||
| const graph = /* @__PURE__ */ new Map(); | ||
| let valid = true; | ||
| const packagesByName = { [rootPackage.packageJson.name]: rootPackage }; | ||
| const relativePathsByName = { [rootPackage.packageJson.name]: "." }; | ||
| const queue = [rootPackage]; | ||
| for (const pkg of packages.packages) { | ||
| queue.push(pkg); | ||
| packagesByName[pkg.packageJson.name] = pkg; | ||
| relativePathsByName[pkg.packageJson.name] = path.relative(rootPackage.dir, pkg.dir).replace(/\\/g, "/"); | ||
| } | ||
| for (const pkg of queue) { | ||
| const { name } = pkg.packageJson; | ||
| const dependencies = []; | ||
| const allDependencies = getAllDependencies(pkg.packageJson, ignoreDevDependencies); | ||
| for (let [depName, depRange] of allDependencies) { | ||
| const match = packagesByName[depName]; | ||
| if (!match) continue; | ||
| const expected = match.packageJson.version; | ||
| const rawDepRange = depRange; | ||
| if (depRange.startsWith("workspace:")) { | ||
| depRange = depRange.replace(/^workspace:/, ""); | ||
| if (depRange === "*" || depRange === "^" || depRange === "~") { | ||
| dependencies.push(depName); | ||
| continue; | ||
| } | ||
| if (path.posix.normalize(depRange) === relativePathsByName[depName]) { | ||
| dependencies.push(depName); | ||
| continue; | ||
| } | ||
| if (!getValidRange(depRange)) { | ||
| valid = false; | ||
| console.error(`Package ${src_default.blue(name)} must depend on the current version of ${src_default.blue(depName)}: ${src_default.green(expected)} vs ${src_default.red(rawDepRange)}`); | ||
| continue; | ||
| } | ||
| } else if (bumpVersionsWithWorkspaceProtocolOnly) continue; | ||
| const range = getValidRange(depRange); | ||
| if (range && !range.test(expected) || isProtocolRange(depRange)) { | ||
| valid = false; | ||
| console.error(`Package ${src_default.blue(name)} must depend on the current version of ${src_default.blue(depName)}: ${src_default.green(expected)} vs ${src_default.red(rawDepRange)}`); | ||
| continue; | ||
| } | ||
| if (!range) continue; | ||
| dependencies.push(depName); | ||
| } | ||
| graph.set(name, { | ||
| pkg, | ||
| dependencies | ||
| }); | ||
| } | ||
| return { | ||
| graph, | ||
| valid | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/index.ts | ||
| function getDependentsGraph(packages, opts) { | ||
| const graph = /* @__PURE__ */ new Map(); | ||
| const rootPackage = packages.rootPackage; | ||
| if (rootPackage == null) return /* @__PURE__ */ new Map(); | ||
| const { graph: dependencyGraph } = getDependencyGraph(packages, rootPackage, opts); | ||
| const dependentsLookup = { [rootPackage.packageJson.name]: { | ||
| pkg: rootPackage, | ||
| dependents: [] | ||
| } }; | ||
| packages.packages.forEach((pkg) => { | ||
| dependentsLookup[pkg.packageJson.name] = { | ||
| pkg, | ||
| dependents: [] | ||
| }; | ||
| }); | ||
| packages.packages.forEach((pkg) => { | ||
| const dependent = pkg.packageJson.name; | ||
| const valFromDependencyGraph = dependencyGraph.get(dependent); | ||
| if (valFromDependencyGraph) valFromDependencyGraph.dependencies.forEach((dependency) => { | ||
| dependentsLookup[dependency].dependents.push(dependent); | ||
| }); | ||
| }); | ||
| Object.keys(dependentsLookup).forEach((key) => { | ||
| graph.set(key, dependentsLookup[key]); | ||
| }); | ||
| const simplifiedDependentsGraph = /* @__PURE__ */ new Map(); | ||
| graph.forEach((pkgInfo, pkgName) => { | ||
| simplifiedDependentsGraph.set(pkgName, pkgInfo.dependents); | ||
| }); | ||
| return simplifiedDependentsGraph; | ||
| } | ||
| //#endregion | ||
| export { getDependentsGraph }; |
| import { defineConfig } from "tsdown/config"; | ||
| import { baseConfig } from "../../tsdown.config.ts"; | ||
| export default defineConfig(baseConfig); |
+9
-0
| # @changesets/get-dependents-graph | ||
| ## 3.0.0-next.4 | ||
| ### Patch Changes | ||
| - [#2004](https://github.com/changesets/changesets/pull/2004) [`169b128`](https://github.com/changesets/changesets/commit/169b128522f0e53ef228f3acd8118709b0f72156) Thanks [@ghostdevv](https://github.com/ghostdevv)! - Replace `picocolors` with `node:util`'s `styleText` | ||
| - Updated dependencies [[`062530b`](https://github.com/changesets/changesets/commit/062530b825d53abc9d8934f3a50cc61ff3ff82b8)]: | ||
| - @changesets/types@7.0.0-next.4 | ||
| ## 3.0.0-next.3 | ||
@@ -4,0 +13,0 @@ |
+10
-6
| { | ||
| "name": "@changesets/get-dependents-graph", | ||
| "version": "3.0.0-next.3", | ||
| "version": "3.0.0-next.4", | ||
| "description": "Get the graph of dependents in a monorepo", | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/changesets/changesets.git", | ||
| "directory": "packages/get-dependents-graph" | ||
| }, | ||
| "type": "module", | ||
| "exports": { | ||
| ".": "./dist/changesets-get-dependents-graph.js", | ||
| ".": "./dist/index.mjs", | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "license": "MIT", | ||
| "repository": "https://github.com/changesets/changesets/tree/main/packages/get-dependents-graph", | ||
| "dependencies": { | ||
| "picocolors": "^1.1.0", | ||
| "semver": "^7.5.3", | ||
| "@changesets/types": "^7.0.0-next.3" | ||
| "@changesets/types": "^7.0.0-next.4" | ||
| }, | ||
| "devDependencies": { | ||
| "vitest": "^4.1.5", | ||
| "@changesets/color": "^1.0.0", | ||
| "@changesets/test-utils": "0.0.9-next.3" | ||
@@ -20,0 +24,0 @@ }, |
@@ -132,3 +132,3 @@ import path from "node:path"; | ||
| ).toBe( | ||
| `Package "foo" must depend on the current version of "bar": "1.0.0" vs "link:../bar"`, | ||
| `Package foo must depend on the current version of bar: 1.0.0 vs link:../bar`, | ||
| ); | ||
@@ -176,3 +176,3 @@ }), | ||
| ).toMatchInlineSnapshot( | ||
| `"Package "foo" must depend on the current version of "bar": "1.0.0" vs "0.9.0""`, | ||
| `"Package foo must depend on the current version of bar: 1.0.0 vs 0.9.0"`, | ||
| ); | ||
@@ -302,3 +302,3 @@ }), | ||
| ).toMatchInlineSnapshot( | ||
| `"Package "foo" must depend on the current version of "bar": "1.0.0" vs "workspace:packages/not-bar""`, | ||
| `"Package foo must depend on the current version of bar: 1.0.0 vs workspace:packages/not-bar"`, | ||
| ); | ||
@@ -305,0 +305,0 @@ }), |
| import path from "node:path"; | ||
| import c from "@changesets/color"; | ||
| import type { Package, Packages, PackageJSON } from "@changesets/types"; | ||
| import pc from "picocolors"; | ||
| import Range from "semver/classes/range.js"; | ||
@@ -124,8 +124,5 @@ | ||
| valid = false; | ||
| // TODO: replace with returning errors/warnings | ||
| console.error( | ||
| `Package ${pc.cyan( | ||
| `"${name}"`, | ||
| )} must depend on the current version of ${pc.cyan( | ||
| `"${depName}"`, | ||
| )}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${rawDepRange}"`)}`, | ||
| `Package ${c.blue(name)} must depend on the current version of ${c.blue(depName)}: ${c.green(expected)} vs ${c.red(rawDepRange)}`, | ||
| ); | ||
@@ -142,8 +139,5 @@ continue; | ||
| valid = false; | ||
| // TODO: replace with returning errors/warnings | ||
| console.error( | ||
| `Package ${pc.cyan( | ||
| `"${name}"`, | ||
| )} must depend on the current version of ${pc.cyan( | ||
| `"${depName}"`, | ||
| )}: ${pc.green(`"${expected}"`)} vs ${pc.red(`"${rawDepRange}"`)}`, | ||
| `Package ${c.blue(name)} must depend on the current version of ${c.blue(depName)}: ${c.green(expected)} vs ${c.red(rawDepRange)}`, | ||
| ); | ||
@@ -150,0 +144,0 @@ continue; |
| export * from "./declarations/src/index.js"; | ||
| //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hhbmdlc2V0cy1nZXQtZGVwZW5kZW50cy1ncmFwaC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi9kZWNsYXJhdGlvbnMvc3JjL2luZGV4LmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEifQ== |
| import path from 'node:path'; | ||
| import pc from 'picocolors'; | ||
| import Range from 'semver/classes/range.js'; | ||
| 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 => { | ||
| if (isProtocolRange(potentialRange)) { | ||
| return null; | ||
| } | ||
| try { | ||
| return new Range(potentialRange); | ||
| } catch { | ||
| return null; | ||
| } | ||
| }; | ||
| function getDependencyGraph(packages, rootPackage, { | ||
| ignoreDevDependencies = false, | ||
| bumpVersionsWithWorkspaceProtocolOnly = false | ||
| } = {}) { | ||
| const graph = new Map(); | ||
| let valid = true; | ||
| const packagesByName = { | ||
| [rootPackage.packageJson.name]: rootPackage | ||
| }; | ||
| const relativePathsByName = { | ||
| [rootPackage.packageJson.name]: "." | ||
| }; | ||
| const queue = [rootPackage]; | ||
| for (const pkg of packages.packages) { | ||
| queue.push(pkg); | ||
| packagesByName[pkg.packageJson.name] = pkg; | ||
| relativePathsByName[pkg.packageJson.name] = path.relative(rootPackage.dir, pkg.dir).replace(/\\/g, "/"); | ||
| } | ||
| for (const pkg of queue) { | ||
| const { | ||
| name | ||
| } = pkg.packageJson; | ||
| const dependencies = []; | ||
| const allDependencies = getAllDependencies(pkg.packageJson, ignoreDevDependencies); | ||
| // eslint-disable-next-line prefer-const | ||
| for (let [depName, depRange] of allDependencies) { | ||
| const match = packagesByName[depName]; | ||
| if (!match) continue; | ||
| const expected = match.packageJson.version; | ||
| const rawDepRange = depRange; | ||
| const usesWorkspaceRange = depRange.startsWith("workspace:"); | ||
| if (usesWorkspaceRange) { | ||
| depRange = depRange.replace(/^workspace:/, ""); | ||
| if (depRange === "*" || depRange === "^" || depRange === "~") { | ||
| dependencies.push(depName); | ||
| continue; | ||
| } | ||
| 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(`"${rawDepRange}"`)}`); | ||
| continue; | ||
| } | ||
| // `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, { | ||
| pkg, | ||
| dependencies | ||
| }); | ||
| } | ||
| return { | ||
| graph, | ||
| valid | ||
| }; | ||
| } | ||
| function getDependentsGraph(packages, opts) { | ||
| const graph = new Map(); | ||
| const rootPackage = packages.rootPackage; | ||
| if (rootPackage == null) { | ||
| return new Map(); | ||
| } | ||
| const { | ||
| graph: dependencyGraph | ||
| } = getDependencyGraph(packages, rootPackage, opts); | ||
| const dependentsLookup = { | ||
| [rootPackage.packageJson.name]: { | ||
| pkg: rootPackage, | ||
| dependents: [] | ||
| } | ||
| }; | ||
| packages.packages.forEach(pkg => { | ||
| dependentsLookup[pkg.packageJson.name] = { | ||
| pkg, | ||
| dependents: [] | ||
| }; | ||
| }); | ||
| packages.packages.forEach(pkg => { | ||
| const dependent = pkg.packageJson.name; | ||
| const valFromDependencyGraph = dependencyGraph.get(dependent); | ||
| if (valFromDependencyGraph) { | ||
| const dependencies = valFromDependencyGraph.dependencies; | ||
| dependencies.forEach(dependency => { | ||
| dependentsLookup[dependency].dependents.push(dependent); | ||
| }); | ||
| } | ||
| }); | ||
| Object.keys(dependentsLookup).forEach(key => { | ||
| graph.set(key, dependentsLookup[key]); | ||
| }); | ||
| const simplifiedDependentsGraph = new Map(); | ||
| graph.forEach((pkgInfo, pkgName) => { | ||
| simplifiedDependentsGraph.set(pkgName, pkgInfo.dependents); | ||
| }); | ||
| return simplifiedDependentsGraph; | ||
| } | ||
| export { getDependentsGraph }; |
| import type { Packages } from "@changesets/types"; | ||
| export declare function getDependentsGraph(packages: Packages, opts?: { | ||
| ignoreDevDependencies?: boolean; | ||
| bumpVersionsWithWorkspaceProtocolOnly?: boolean; | ||
| }): Map<any, any>; |
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
34321
0.09%2
-33.33%0
-100%3
50%607
-4.11%- Removed
- Removed