mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat(graph-sequencer): add package @pnpm/graph-sequencer (#7168)
--------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
10
.changeset/proud-oranges-talk.md
Normal file
10
.changeset/proud-oranges-talk.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-rebuild": patch
|
||||
"@pnpm/deps.graph-sequencer": major
|
||||
"@pnpm/sort-packages": patch
|
||||
"@pnpm/build-modules": patch
|
||||
"@pnpm/core": patch
|
||||
"@pnpm-private/typings": patch
|
||||
---
|
||||
|
||||
Add package @pnpm/deps.graph-sequencer for better topological sort [#7168](https://github.com/pnpm/pnpm/pull/7168).
|
||||
23
__typings__/local.d.ts
vendored
23
__typings__/local.d.ts
vendored
@@ -79,29 +79,6 @@ declare module 'graceful-git' {
|
||||
export = anything
|
||||
}
|
||||
|
||||
declare module '@pnpm/graph-sequencer' {
|
||||
namespace graphSequencer {
|
||||
type Graph<T> = Map<T, T[]>
|
||||
type Groups<T> = T[][]
|
||||
|
||||
interface Options<T> {
|
||||
graph: Graph<T>
|
||||
groups: Groups<T>
|
||||
}
|
||||
|
||||
interface Result<T> {
|
||||
safe: boolean
|
||||
chunks: Groups<T>
|
||||
cycles: Groups<T>
|
||||
}
|
||||
|
||||
type GraphSequencer = <T>(opts: Options<T>) => Result<T>
|
||||
}
|
||||
|
||||
const graphSequencer: graphSequencer.GraphSequencer
|
||||
export = graphSequencer
|
||||
}
|
||||
|
||||
declare module 'is-inner-link' {
|
||||
const anything: any
|
||||
export = anything
|
||||
|
||||
30
deps/graph-sequencer/README.md
vendored
Normal file
30
deps/graph-sequencer/README.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# @pnpm/deps.graph-sequencer
|
||||
|
||||
> Sort items in a graph using a topological sort
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
pnpm add @pnpm/deps.graph-sequencer
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
expect(graphSequencer(new Map([
|
||||
[0, [1]],
|
||||
[1, [2]],
|
||||
[2, [3]],
|
||||
[3, [0]],
|
||||
]), [0, 1, 2, 3])).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [[0, 1, 2, 3]],
|
||||
cycles: [[0, 1, 2, 3]],
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
1
deps/graph-sequencer/jest.config.js
vendored
Normal file
1
deps/graph-sequencer/jest.config.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('../../jest.config.js')
|
||||
41
deps/graph-sequencer/package.json
vendored
Normal file
41
deps/graph-sequencer/package.json
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@pnpm/deps.graph-sequencer",
|
||||
"version": "0.0.0",
|
||||
"description": "Sort items in a graph using a topological sort",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"_test": "jest",
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"compile": "tsc --build && pnpm run lint --fix"
|
||||
},
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/main/deps/graph-sequencer",
|
||||
"keywords": [
|
||||
"pnpm8",
|
||||
"pnpm",
|
||||
"graph"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/main/deps/graph-sequencer#readme",
|
||||
"dependencies": {
|
||||
},
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"devDependencies": {
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
}
|
||||
}
|
||||
116
deps/graph-sequencer/src/index.ts
vendored
Normal file
116
deps/graph-sequencer/src/index.ts
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
export type Graph<T> = Map<T, T[]>
|
||||
export type Groups<T> = T[][]
|
||||
|
||||
export interface Options<T> {
|
||||
graph: Graph<T>
|
||||
groups: Groups<T>
|
||||
}
|
||||
|
||||
export interface Result<T> {
|
||||
safe: boolean
|
||||
chunks: Groups<T>
|
||||
cycles: Groups<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs topological sorting on a graph while supporting node restrictions.
|
||||
*
|
||||
* @param {Graph<T>} graph - The graph represented as a Map where keys are nodes and values are their outgoing edges.
|
||||
* @param {T[]} includedNodes - An array of nodes that should be included in the sorting process. Other nodes will be ignored.
|
||||
* @returns {Result<T>} An object containing the result of the sorting, including safe, chunks, and cycles.
|
||||
*/
|
||||
export function graphSequencer<T> (graph: Graph<T>, includedNodes: T[] = [...graph.keys()]): Result<T> {
|
||||
// Initialize reverseGraph with empty arrays for all nodes.
|
||||
const reverseGraph = new Map<T, T[]>()
|
||||
for (const key of graph.keys()) {
|
||||
reverseGraph.set(key, [])
|
||||
}
|
||||
|
||||
// Calculate outDegree and reverseGraph for the included nodes.
|
||||
const nodes = new Set<T>(includedNodes)
|
||||
const visited = new Set<T>()
|
||||
const outDegree = new Map<T, number>()
|
||||
for (const [from, edges] of graph.entries()) {
|
||||
outDegree.set(from, 0)
|
||||
for (const to of edges) {
|
||||
if (nodes.has(from) && nodes.has(to)) {
|
||||
changeOutDegree(from, 1)
|
||||
reverseGraph.get(to)!.push(from)
|
||||
}
|
||||
}
|
||||
if (!nodes.has(from)) {
|
||||
visited.add(from)
|
||||
}
|
||||
}
|
||||
|
||||
const chunks: T[][] = []
|
||||
const cycles: T[][] = []
|
||||
let safe = true
|
||||
while (nodes.size) {
|
||||
const chunk: T[] = []
|
||||
let minDegree = Number.MAX_SAFE_INTEGER
|
||||
for (const node of nodes) {
|
||||
const degree = outDegree.get(node)!
|
||||
if (degree === 0) {
|
||||
chunk.push(node)
|
||||
}
|
||||
minDegree = Math.min(minDegree, degree)
|
||||
}
|
||||
|
||||
if (minDegree === 0) {
|
||||
chunk.forEach(removeNode)
|
||||
chunks.push(chunk)
|
||||
} else {
|
||||
const cycleNodes: T[] = []
|
||||
for (const node of nodes) {
|
||||
const cycle = findCycle(node)
|
||||
if (cycle.length) {
|
||||
cycles.push(cycle)
|
||||
cycle.forEach(removeNode)
|
||||
cycleNodes.push(...cycle)
|
||||
|
||||
if (cycle.length > 1) {
|
||||
safe = false
|
||||
}
|
||||
}
|
||||
}
|
||||
chunks.push(cycleNodes)
|
||||
}
|
||||
}
|
||||
|
||||
return { safe, chunks, cycles }
|
||||
|
||||
// Function to update the outDegree of a node.
|
||||
function changeOutDegree (node: T, value: number) {
|
||||
const degree = outDegree.get(node) ?? 0
|
||||
outDegree.set(node, degree + value)
|
||||
}
|
||||
|
||||
// Function to remove a node from the graph.
|
||||
function removeNode (node: T) {
|
||||
for (const from of reverseGraph.get(node)!) {
|
||||
changeOutDegree(from, -1)
|
||||
}
|
||||
visited.add(node)
|
||||
nodes.delete(node)
|
||||
}
|
||||
|
||||
function findCycle (startNode: T): T[] {
|
||||
const queue: Array<[T, T[]]> = [[startNode, [startNode]]]
|
||||
const cycleVisited = new Set<T>()
|
||||
while (queue.length) {
|
||||
const [id, cycle] = queue.shift()!
|
||||
for (const to of graph.get(id)!) {
|
||||
if (to === startNode) {
|
||||
return cycle
|
||||
}
|
||||
if (visited.has(to) || cycleVisited.has(to)) {
|
||||
continue
|
||||
}
|
||||
cycleVisited.add(to)
|
||||
queue.push([to, [...cycle, to]])
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
351
deps/graph-sequencer/test/index.ts
vendored
Normal file
351
deps/graph-sequencer/test/index.ts
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
import { graphSequencer } from '../src'
|
||||
|
||||
test('graph with three independent self-cycles', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['a']],
|
||||
['b', ['b']],
|
||||
['c', ['c']],
|
||||
]
|
||||
))).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['a', 'b', 'c']],
|
||||
cycles: [
|
||||
['a'], ['b'], ['c'],
|
||||
],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with self-cycle. Sequencing a subgraph', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['a']],
|
||||
['b', ['b']],
|
||||
['c', ['c']],
|
||||
|
||||
]), ['a', 'b'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['a', 'b']],
|
||||
cycles: [['a'], ['b']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with two self-cycles and an edge linking them', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', ['b']],
|
||||
['c', ['b', 'c']]]
|
||||
))).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['b', 'c'], ['a']],
|
||||
cycles: [
|
||||
['b'], ['c'],
|
||||
],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with nodes connected to each other sequentially without forming a cycle', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', []],
|
||||
['c', ['b']]]
|
||||
))).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['b'], ['c'], ['a']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph sequencing with a subset of 3 nodes, ignoring 2 nodes, in a 5-node graph', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', []],
|
||||
['c', []],
|
||||
['d', ['a']],
|
||||
['e', ['a', 'b', 'c']]]
|
||||
), ['a', 'd', 'e'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['a'], ['d', 'e']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with no edges', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', []],
|
||||
['b', []],
|
||||
['c', []],
|
||||
['d', []],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['a', 'b', 'c', 'd']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph of isolated nodes with no edges, sequencing a subgraph of selected nodes', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', []],
|
||||
['b', []],
|
||||
['c', []],
|
||||
['d', []],
|
||||
]), ['a', 'b', 'c'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['a', 'b', 'c']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with multiple dependencies on one item', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['d']],
|
||||
['b', ['d']],
|
||||
['c', []],
|
||||
['d', []],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['c', 'd'], ['a', 'b']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with resolved cycle', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b']],
|
||||
['b', ['c']],
|
||||
['c', ['d']],
|
||||
['d', ['a']],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [['a', 'b', 'c', 'd']],
|
||||
cycles: [['a', 'b', 'c', 'd']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with a cycle, but sequencing a subgraph that avoids the cycle', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b']],
|
||||
['b', ['c']],
|
||||
['c', ['d']],
|
||||
['d', ['a']],
|
||||
]), ['a', 'b', 'c'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['c'], ['b'], ['a']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with resolved cycle with multiple unblocked deps', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['d']],
|
||||
['b', ['d']],
|
||||
['c', ['d']],
|
||||
['d', ['a']],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [
|
||||
['a', 'd'],
|
||||
['b', 'c'],
|
||||
],
|
||||
cycles: [['a', 'd']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with resolved cycle with multiple unblocked deps subgraph', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['d']],
|
||||
['b', ['d']],
|
||||
['c', ['d']],
|
||||
['d', ['a']],
|
||||
]), ['a', 'b', 'c'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [
|
||||
['a', 'b', 'c'],
|
||||
],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with two cycles', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b']],
|
||||
['b', ['a']],
|
||||
['c', ['d']],
|
||||
['d', ['c']],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [['a', 'b', 'c', 'd']],
|
||||
cycles: [
|
||||
['a', 'b'],
|
||||
['c', 'd'],
|
||||
],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with multiple cycles. case 1', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['c']],
|
||||
['b', ['a', 'd']],
|
||||
['c', ['b']],
|
||||
['d', ['c', 'e']],
|
||||
['e', []],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [['e'], ['a', 'c', 'b'], ['d']],
|
||||
cycles: [['a', 'c', 'b']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with multiple cycles. case 2', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b']],
|
||||
['b', ['d']],
|
||||
['c', []],
|
||||
['d', ['b', 'c']],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [['c'], ['b', 'd'], ['a']],
|
||||
cycles: [['b', 'd']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with fully connected subgraph and additional connected node', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c', 'd']],
|
||||
['b', ['a', 'c', 'd']],
|
||||
['c', ['a', 'b', 'd']],
|
||||
['d', ['a', 'b', 'c']],
|
||||
['e', ['b']],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [['a', 'b', 'c', 'd'], ['e']],
|
||||
cycles: [
|
||||
['a', 'b'],
|
||||
['c', 'd'],
|
||||
],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with fully connected subgraph. case 1', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c', 'd']],
|
||||
['b', ['a', 'c', 'd']],
|
||||
['c', ['a', 'b', 'd']],
|
||||
['d', ['a', 'b', 'c']],
|
||||
['e', ['b']],
|
||||
]), ['b', 'e'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['b'], ['e']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with fully connected subgraph. case 2', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c', 'd']],
|
||||
['b', ['a', 'c', 'd']],
|
||||
['c', ['a', 'b', 'd']],
|
||||
['d', ['a', 'b', 'c']],
|
||||
['e', ['b']],
|
||||
]), ['a', 'b', 'e'])).toStrictEqual(
|
||||
{
|
||||
safe: false,
|
||||
chunks: [['a', 'b'], ['e']],
|
||||
cycles: [['a', 'b']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with two self-cycles', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', ['b']],
|
||||
['c', ['c']],
|
||||
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['b', 'c'], ['a']],
|
||||
cycles: [['b'], ['c']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with two self-cycles. Sequencing a subgraph', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', ['b']],
|
||||
['c', ['c']],
|
||||
|
||||
]), ['b', 'c'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['b', 'c']],
|
||||
cycles: [['b'], ['c']],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with many nodes', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', []],
|
||||
['c', []],
|
||||
['d', ['a']],
|
||||
['e', ['a', 'b', 'c']],
|
||||
]))).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['b', 'c'], ['a'], ['d', 'e']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('graph with many nodes. Sequencing a subgraph', () => {
|
||||
expect(graphSequencer(new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', []],
|
||||
['c', []],
|
||||
['d', ['a']],
|
||||
['e', ['a', 'b', 'c']],
|
||||
]), ['a', 'd', 'e'])).toStrictEqual(
|
||||
{
|
||||
safe: true,
|
||||
chunks: [['a'], ['d', 'e']],
|
||||
cycles: [],
|
||||
}
|
||||
)
|
||||
})
|
||||
14
deps/graph-sequencer/tsconfig.json
vendored
Normal file
14
deps/graph-sequencer/tsconfig.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
],
|
||||
"composite": true
|
||||
}
|
||||
8
deps/graph-sequencer/tsconfig.lint.json
vendored
Normal file
8
deps/graph-sequencer/tsconfig.lint.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
"@pnpm/calc-dep-state": "workspace:*",
|
||||
"@pnpm/core-loggers": "workspace:*",
|
||||
"@pnpm/fs.hard-link-dir": "workspace:*",
|
||||
"@pnpm/graph-sequencer": "1.1.1",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/lifecycle": "workspace:*",
|
||||
"@pnpm/link-bins": "workspace:*",
|
||||
"@pnpm/patching.apply-patch": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import graphSequencer from '@pnpm/graph-sequencer'
|
||||
import { graphSequencer } from '@pnpm/deps.graph-sequencer'
|
||||
import { type PackageManifest, type PatchFile } from '@pnpm/types'
|
||||
import filter from 'ramda/src/filter'
|
||||
|
||||
@@ -36,10 +36,7 @@ export function buildSequence (
|
||||
nodesToBuildArray
|
||||
.map((depPath) => [depPath, onlyFromBuildGraph(Object.values(depGraph[depPath].children))])
|
||||
)
|
||||
const graphSequencerResult = graphSequencer({
|
||||
graph,
|
||||
groups: [nodesToBuildArray],
|
||||
})
|
||||
const graphSequencerResult = graphSequencer(graph, nodesToBuildArray)
|
||||
const chunks = graphSequencerResult.chunks as string[][]
|
||||
return chunks
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../deps/graph-sequencer"
|
||||
},
|
||||
{
|
||||
"path": "../../fs/hard-link-dir"
|
||||
},
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/fs.hard-link-dir": "workspace:*",
|
||||
"@pnpm/get-context": "workspace:*",
|
||||
"@pnpm/graph-sequencer": "1.1.1",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/lifecycle": "workspace:*",
|
||||
"@pnpm/link-bins": "workspace:*",
|
||||
"@pnpm/lockfile-types": "workspace:*",
|
||||
|
||||
@@ -30,7 +30,7 @@ import * as dp from '@pnpm/dependency-path'
|
||||
import { hardLinkDir } from '@pnpm/fs.hard-link-dir'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import runGroups from 'run-groups'
|
||||
import graphSequencer from '@pnpm/graph-sequencer'
|
||||
import { graphSequencer } from '@pnpm/deps.graph-sequencer'
|
||||
import npa from '@pnpm/npm-package-arg'
|
||||
import pLimit from 'p-limit'
|
||||
import semver from 'semver'
|
||||
@@ -275,10 +275,10 @@ async function _rebuild (
|
||||
.map(([pkgName, reference]) => dp.refToRelative(reference, pkgName))
|
||||
.filter((childRelDepPath) => childRelDepPath && nodesToBuildAndTransitive.has(childRelDepPath)))
|
||||
}
|
||||
const graphSequencerResult = graphSequencer({
|
||||
const graphSequencerResult = graphSequencer(
|
||||
graph,
|
||||
groups: [nodesToBuildAndTransitiveArray],
|
||||
})
|
||||
nodesToBuildAndTransitiveArray
|
||||
)
|
||||
const chunks = graphSequencerResult.chunks as string[][]
|
||||
const warn = (message: string) => {
|
||||
logger.info({ message, prefix: opts.dir })
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
{
|
||||
"path": "../../config/normalize-registries"
|
||||
},
|
||||
{
|
||||
"path": "../../deps/graph-sequencer"
|
||||
},
|
||||
{
|
||||
"path": "../../fs/hard-link-dir"
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/filter-lockfile": "workspace:*",
|
||||
"@pnpm/get-context": "workspace:*",
|
||||
"@pnpm/graph-sequencer": "1.1.1",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/headless": "workspace:*",
|
||||
"@pnpm/hoist": "workspace:*",
|
||||
"@pnpm/hooks.read-package-hook": "workspace:*",
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
{
|
||||
"path": "../../config/normalize-registries"
|
||||
},
|
||||
{
|
||||
"path": "../../deps/graph-sequencer"
|
||||
},
|
||||
{
|
||||
"path": "../../exec/build-modules"
|
||||
},
|
||||
@@ -158,4 +161,4 @@
|
||||
}
|
||||
],
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@@ -884,6 +884,12 @@ importers:
|
||||
specifier: 0.28.20
|
||||
version: 0.28.20
|
||||
|
||||
deps/graph-sequencer:
|
||||
devDependencies:
|
||||
'@pnpm/deps.graph-sequencer':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
|
||||
env/node.fetcher:
|
||||
dependencies:
|
||||
'@pnpm/create-cafs-store':
|
||||
@@ -1057,12 +1063,12 @@ importers:
|
||||
'@pnpm/core-loggers':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core-loggers
|
||||
'@pnpm/deps.graph-sequencer':
|
||||
specifier: workspace:*
|
||||
version: link:../../deps/graph-sequencer
|
||||
'@pnpm/fs.hard-link-dir':
|
||||
specifier: workspace:*
|
||||
version: link:../../fs/hard-link-dir
|
||||
'@pnpm/graph-sequencer':
|
||||
specifier: 1.1.1
|
||||
version: 1.1.1
|
||||
'@pnpm/lifecycle':
|
||||
specifier: workspace:*
|
||||
version: link:../lifecycle
|
||||
@@ -1182,6 +1188,9 @@ importers:
|
||||
'@pnpm/dependency-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/dependency-path
|
||||
'@pnpm/deps.graph-sequencer':
|
||||
specifier: workspace:*
|
||||
version: link:../../deps/graph-sequencer
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
@@ -1191,9 +1200,6 @@ importers:
|
||||
'@pnpm/get-context':
|
||||
specifier: workspace:*
|
||||
version: link:../../pkg-manager/get-context
|
||||
'@pnpm/graph-sequencer':
|
||||
specifier: 1.1.1
|
||||
version: 1.1.1
|
||||
'@pnpm/lifecycle':
|
||||
specifier: workspace:*
|
||||
version: link:../lifecycle
|
||||
@@ -2886,6 +2892,9 @@ importers:
|
||||
'@pnpm/dependency-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/dependency-path
|
||||
'@pnpm/deps.graph-sequencer':
|
||||
specifier: workspace:*
|
||||
version: link:../../deps/graph-sequencer
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
@@ -2895,9 +2904,6 @@ importers:
|
||||
'@pnpm/get-context':
|
||||
specifier: workspace:*
|
||||
version: link:../get-context
|
||||
'@pnpm/graph-sequencer':
|
||||
specifier: 1.1.1
|
||||
version: 1.1.1
|
||||
'@pnpm/headless':
|
||||
specifier: workspace:*
|
||||
version: link:../headless
|
||||
@@ -6195,9 +6201,9 @@ importers:
|
||||
|
||||
workspace/sort-packages:
|
||||
dependencies:
|
||||
'@pnpm/graph-sequencer':
|
||||
specifier: 1.1.1
|
||||
version: 1.1.1
|
||||
'@pnpm/deps.graph-sequencer':
|
||||
specifier: workspace:*
|
||||
version: link:../../deps/graph-sequencer
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -7932,10 +7938,6 @@ packages:
|
||||
graceful-fs: 4.2.11(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q)
|
||||
dev: true
|
||||
|
||||
/@pnpm/graph-sequencer@1.1.1:
|
||||
resolution: {integrity: sha512-nlLogZV9i8J2z9vw1cHtKAX8Caj3WeYUw63G1ni2ULLwvb+FfRAhdIfDsuce0gUHdOClF/gsKN+7H28yryNlAw==}
|
||||
dev: false
|
||||
|
||||
/@pnpm/hooks.types@1.0.1:
|
||||
resolution: {integrity: sha512-Zx2hzwxBKv1RmFzyu4pEVY7QeIGUb54smSSYt8GcJgByn+uMXgwJ7ydv9t2Koc90QTqk8J3P2J+RDrZVIQpVQw==}
|
||||
engines: {node: '>=16.14'}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/main/workspace/sort-packages#readme",
|
||||
"dependencies": {
|
||||
"@pnpm/graph-sequencer": "1.1.1",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/types": "workspace:*"
|
||||
},
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
@@ -38,4 +38,4 @@
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ProjectsGraph } from '@pnpm/types'
|
||||
import graphSequencer from '@pnpm/graph-sequencer'
|
||||
import type { Result as GraphSequencerResult } from '@pnpm/graph-sequencer'
|
||||
import { graphSequencer } from '@pnpm/deps.graph-sequencer'
|
||||
import type { Result as GraphSequencerResult } from '@pnpm/deps.graph-sequencer'
|
||||
|
||||
export function sequenceGraph (pkgGraph: ProjectsGraph): GraphSequencerResult<string> {
|
||||
const keys = Object.keys(pkgGraph)
|
||||
@@ -9,62 +9,10 @@ export function sequenceGraph (pkgGraph: ProjectsGraph): GraphSequencerResult<st
|
||||
keys.map((pkgPath) => [
|
||||
pkgPath,
|
||||
pkgGraph[pkgPath].dependencies.filter(
|
||||
/* remove cycles of length 1 (ie., package 'a' depends on 'a'). They
|
||||
confuse the graph-sequencer, but can be ignored when ordering packages
|
||||
topologically.
|
||||
|
||||
See the following example where 'b' and 'c' depend on themselves:
|
||||
|
||||
graphSequencer({graph: new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', ['b']],
|
||||
['c', ['b', 'c']]]
|
||||
),
|
||||
groups: [['a', 'b', 'c']]})
|
||||
|
||||
returns chunks:
|
||||
|
||||
[['b'],['a'],['c']]
|
||||
|
||||
But both 'b' and 'c' should be executed _before_ 'a', because 'a' depends on
|
||||
them. It works (and is considered 'safe' if we run:)
|
||||
|
||||
graphSequencer({graph: new Map([
|
||||
['a', ['b', 'c']],
|
||||
['b', []],
|
||||
['c', ['b']]]
|
||||
), groups: [['a', 'b', 'c']]})
|
||||
|
||||
returning:
|
||||
|
||||
[['b'], ['c'], ['a']]
|
||||
|
||||
*/
|
||||
d => d !== pkgPath &&
|
||||
/* remove unused dependencies that we can ignore due to a filter expression.
|
||||
|
||||
Again, the graph sequencer used to behave weirdly in the following edge case:
|
||||
|
||||
graphSequencer({graph: new Map([
|
||||
['a', ['b', 'c']],
|
||||
['d', ['a']],
|
||||
['e', ['a', 'b', 'c']]]
|
||||
),
|
||||
groups: [['a', 'e', 'e']]})
|
||||
|
||||
returns chunks:
|
||||
|
||||
[['d'],['a'],['e']]
|
||||
|
||||
But we really want 'a' to be executed first.
|
||||
*/
|
||||
setOfKeys.has(d))]
|
||||
d => d !== pkgPath && setOfKeys.has(d))]
|
||||
)
|
||||
)
|
||||
return graphSequencer({
|
||||
graph,
|
||||
groups: [keys],
|
||||
})
|
||||
return graphSequencer(graph, keys)
|
||||
}
|
||||
|
||||
export function sortPackages (pkgGraph: ProjectsGraph): string[][] {
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../deps/graph-sequencer"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/types"
|
||||
}
|
||||
],
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user