Skip to content

Commit d72b314

Browse files
committedMar 9, 2024
toposort
1 parent b88e660 commit d72b314

File tree

5 files changed

+119
-5
lines changed

5 files changed

+119
-5
lines changed
 

‎README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ Convert jsdoc for an es export from a javascript/typescript file to markdown.
223223
<!-- codegen:end -->
224224
225225
<!-- codegen:start {preset: markdownFromJsdoc, source: src/presets/monorepo-toc.ts, export: monorepoTOC} -->
226-
#### [monorepoTOC](./src/presets/monorepo-toc.ts#L37)
226+
#### [monorepoTOC](./src/presets/monorepo-toc.ts#L41)
227227
228228
Generate a table of contents for a monorepo.
229229
@@ -241,7 +241,7 @@ Generate a table of contents for a monorepo.
241241
|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
242242
|repoRoot|[optional] the relative path to the root of the git repository. By default, searches parent directories for a package.json to find the "root". |
243243
|filter |[optional] a dictionary of filter rules to whitelist packages. Filters can be applied based on package.json keys,<br /><br />examples:<br />- `filter: '@myorg/.*-lib'` (match packages with names matching this regex)<br />- `filter: { package.name: '@myorg/.*-lib' }` (equivalent to the above)<br />- `filter: { package.version: '^[1-9].*' }` (match packages with versions starting with a non-zero digit, i.e. 1.0.0+)<br />- `filter: '^(?!.*(internal$))'` (match packages that do not contain "internal" anywhere (using [negative lookahead](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookahead_assertion)))<br />- `filter: { package.name: '@myorg', path: 'libraries' }` (match packages whose name contains "@myorg" and whose path matches "libraries")<br />- `filter: { readme: 'This is production-ready' }` (match packages whose readme contains the string "This is production-ready")|
244-
|sort |[optional] sort based on package properties (see `filter`), or readme length. Use `-` as a prefix to sort descending.<br />e.g. `sort: -readme.length` |
244+
|sort |[optional] sort based on package properties (see `filter`), or readme length. Use `-` as a prefix to sort descending.<br />examples:<br />- `sort: package.name` (sort by package name)<br />- `sort: -readme.length` (sort by readme length, descending)<br />- `sort: toplogical` (sort by toplogical dependencies, starting with the most depended-on packages) |
245245
<!-- codegen:end -->
246246
247247
##### Demo
@@ -250,7 +250,7 @@ Generate a table of contents for a monorepo.
250250
251251
#### [markdownFromJsdoc](./src/presets/markdown-from-jsdoc.ts#L17)
252252
253-
Convert jsdoc for an es export from a javascript/typescript file to markdown.
253+
Convert jsdoc to an es export from a javascript/typescript file to markdown.
254254
255255
##### Example
256256

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@babel/generator": "~7.12.0",
4040
"@babel/parser": "^7.11.5",
4141
"@babel/traverse": "^7.11.5",
42+
"@pnpm/deps.graph-sequencer": "^1.0.0",
4243
"@types/dedent": "0.7.0",
4344
"@types/eslint": "^8.44.7",
4445
"@types/glob": "7.1.3",

‎pnpm-lock.yaml

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/presets/monorepo-toc.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {Preset} from '.'
2+
import {graphSequencer} from '@pnpm/deps.graph-sequencer'
23
import * as fs from 'fs'
34
import * as lodash from 'lodash'
45
import * as os from 'os'
@@ -32,7 +33,10 @@ import {relative} from './util/path'
3233
* - `filter: { readme: 'This is production-ready' }` (match packages whose readme contains the string "This is production-ready")
3334
* @param sort
3435
* [optional] sort based on package properties (see `filter`), or readme length. Use `-` as a prefix to sort descending.
35-
* e.g. `sort: -readme.length`
36+
* examples:
37+
* - `sort: package.name` (sort by package name)
38+
* - `sort: -readme.length` (sort by readme length, descending)
39+
* - `sort: toplogical` (sort by toplogical dependencies, starting with the most depended-on packages)
3640
*/
3741
export const monorepoTOC: Preset<{
3842
repoRoot?: string
@@ -41,14 +45,38 @@ export const monorepoTOC: Preset<{
4145
}> = ({options, context}) => {
4246
const packages = getLeafPackages(options.repoRoot, context.physicalFilename)
4347

48+
const packageNames = new Set(packages.map(({packageJson}) => packageJson.name))
49+
const toposorted = toposort(
50+
Object.fromEntries(
51+
packages
52+
.map(({packageJson}) => {
53+
const dependencies = Object.keys({...packageJson.dependencies, ...packageJson.devDependencies}).filter(dep =>
54+
packageNames.has(dep),
55+
)
56+
return [packageJson.name!, dependencies] as const
57+
})
58+
.sort(([a], [b]) => a.localeCompare(b)),
59+
),
60+
)
61+
const toposortIndexes = Object.fromEntries(
62+
toposorted.chunks.flatMap((chunk, i) => {
63+
return chunk.map(pkg => [pkg, i] as const)
64+
}),
65+
)
66+
4467
const leafPackages = packages
4568
.map(({path: leafPath, packageJson: leafPkg}) => {
4669
const dirname = path.dirname(leafPath)
4770
const readmePath = [path.join(dirname, 'readme.md'), path.join(dirname, 'README.md')].find(p => fs.existsSync(p))
4871
const readme = [readmePath && fs.readFileSync(readmePath).toString(), leafPkg.description]
4972
.filter(Boolean)
5073
.join(os.EOL + os.EOL)
51-
return {package: leafPkg, path: leafPath, readme}
74+
return {
75+
package: leafPkg,
76+
path: leafPath,
77+
readme,
78+
topological: toposortIndexes[leafPkg.name!] ?? Number.POSITIVE_INFINITY,
79+
}
5280
})
5381
.filter(props => {
5482
const filter = typeof options.filter === 'object' ? options.filter : {'package.name': options.filter!}
@@ -84,3 +112,10 @@ export const monorepoTOC: Preset<{
84112

85113
return leafPackages.join(os.EOL)
86114
}
115+
116+
export const toposort = <K extends string, Deps extends K>(graph: Record<K, Deps[]>) => {
117+
return graphSequencer<K>(
118+
new Map(Object.entries(graph) as Array<[K, Array<K | Deps>]>),
119+
Object.keys(graph).sort() as K[],
120+
)
121+
}

‎test/presets/monorepo-toc.test.ts

+70
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,73 @@ test('invalid workspaces', () => {
193193
}),
194194
).toThrow(/Expected to find workspaces array, got 'package.json - not an array'/)
195195
})
196+
197+
test('toplogical sort', () => {
198+
Object.keys(mockFs)
199+
.filter(k => k.includes('packages/'))
200+
.forEach(k => {
201+
// eslint-disable-next-line mmkal/@typescript-eslint/no-dynamic-delete
202+
delete mockFs[k]
203+
})
204+
Object.assign(mockFs, {
205+
'packages/client/package.json': '{ "name": "client" }',
206+
'packages/schemainspect/package.json': '{ "name": "schemainspect", "dependencies": {"client": "*"} }',
207+
'packages/migra/package.json': '{ "name": "migra", "dependencies": {"client": "*", "schemainspect": "*"} }',
208+
'packages/migrator/package.json': '{ "name": "migrator", "dependencies": {"migra": "*", "client": "*"} }',
209+
'packages/typegen/package.json': '{ "name": "typegen", "dependencies": {"client": "*"} }',
210+
'packages/admin/package.json':
211+
'{ "name": "admin", "dependencies": {"client": "*", "schemainspect": "*", "migrator": "*"} }',
212+
})
213+
214+
expect(
215+
preset.monorepoTOC({
216+
...params,
217+
options: {
218+
sort: 'topological',
219+
},
220+
}),
221+
).toMatchInlineSnapshot(`
222+
"- [client](./packages/client)
223+
- [schemainspect](./packages/schemainspect)
224+
- [typegen](./packages/typegen)
225+
- [migra](./packages/migra)
226+
- [migrator](./packages/migrator)
227+
- [admin](./packages/admin)"
228+
`)
229+
})
230+
231+
test('toposort helper', () => {
232+
expect(
233+
preset.toposort({
234+
client: [],
235+
schemainspect: ['client'],
236+
migra: ['client', 'schemainspect'],
237+
migrator: ['migra', 'client'],
238+
typegen: ['client'],
239+
admin: ['client', 'schemainspect', 'migrator'],
240+
}),
241+
).toMatchInlineSnapshot(`
242+
{
243+
"chunks": [
244+
[
245+
"client",
246+
],
247+
[
248+
"schemainspect",
249+
"typegen",
250+
],
251+
[
252+
"migra",
253+
],
254+
[
255+
"migrator",
256+
],
257+
[
258+
"admin",
259+
],
260+
],
261+
"cycles": [],
262+
"safe": true,
263+
}
264+
`)
265+
})

0 commit comments

Comments
 (0)
Please sign in to comment.