Skip to content

Commit

Permalink
toposort
Browse files Browse the repository at this point in the history
  • Loading branch information
mmkal committed Mar 9, 2024
1 parent b88e660 commit d72b314
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 5 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Convert jsdoc for an es export from a javascript/typescript file to markdown.
<!-- codegen:end -->
<!-- codegen:start {preset: markdownFromJsdoc, source: src/presets/monorepo-toc.ts, export: monorepoTOC} -->
#### [monorepoTOC](./src/presets/monorepo-toc.ts#L37)
#### [monorepoTOC](./src/presets/monorepo-toc.ts#L41)
Generate a table of contents for a monorepo.
Expand All @@ -241,7 +241,7 @@ Generate a table of contents for a monorepo.
|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|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". |
|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")|
|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` |
|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) |
<!-- codegen:end -->
##### Demo
Expand All @@ -250,7 +250,7 @@ Generate a table of contents for a monorepo.
#### [markdownFromJsdoc](./src/presets/markdown-from-jsdoc.ts#L17)
Convert jsdoc for an es export from a javascript/typescript file to markdown.
Convert jsdoc to an es export from a javascript/typescript file to markdown.
##### Example
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@babel/generator": "~7.12.0",
"@babel/parser": "^7.11.5",
"@babel/traverse": "^7.11.5",
"@pnpm/deps.graph-sequencer": "^1.0.0",
"@types/dedent": "0.7.0",
"@types/eslint": "^8.44.7",
"@types/glob": "7.1.3",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 37 additions & 2 deletions src/presets/monorepo-toc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {Preset} from '.'
import {graphSequencer} from '@pnpm/deps.graph-sequencer'
import * as fs from 'fs'
import * as lodash from 'lodash'
import * as os from 'os'
Expand Down Expand Up @@ -32,7 +33,10 @@ import {relative} from './util/path'
* - `filter: { readme: 'This is production-ready' }` (match packages whose readme contains the string "This is production-ready")
* @param sort
* [optional] sort based on package properties (see `filter`), or readme length. Use `-` as a prefix to sort descending.
* e.g. `sort: -readme.length`
* examples:
* - `sort: package.name` (sort by package name)
* - `sort: -readme.length` (sort by readme length, descending)
* - `sort: toplogical` (sort by toplogical dependencies, starting with the most depended-on packages)
*/
export const monorepoTOC: Preset<{
repoRoot?: string
Expand All @@ -41,14 +45,38 @@ export const monorepoTOC: Preset<{
}> = ({options, context}) => {
const packages = getLeafPackages(options.repoRoot, context.physicalFilename)

const packageNames = new Set(packages.map(({packageJson}) => packageJson.name))
const toposorted = toposort(
Object.fromEntries(
packages
.map(({packageJson}) => {
const dependencies = Object.keys({...packageJson.dependencies, ...packageJson.devDependencies}).filter(dep =>
packageNames.has(dep),
)
return [packageJson.name!, dependencies] as const
})
.sort(([a], [b]) => a.localeCompare(b)),
),
)
const toposortIndexes = Object.fromEntries(
toposorted.chunks.flatMap((chunk, i) => {
return chunk.map(pkg => [pkg, i] as const)
}),
)

const leafPackages = packages
.map(({path: leafPath, packageJson: leafPkg}) => {
const dirname = path.dirname(leafPath)
const readmePath = [path.join(dirname, 'readme.md'), path.join(dirname, 'README.md')].find(p => fs.existsSync(p))
const readme = [readmePath && fs.readFileSync(readmePath).toString(), leafPkg.description]
.filter(Boolean)
.join(os.EOL + os.EOL)
return {package: leafPkg, path: leafPath, readme}
return {
package: leafPkg,
path: leafPath,
readme,
topological: toposortIndexes[leafPkg.name!] ?? Number.POSITIVE_INFINITY,
}
})
.filter(props => {
const filter = typeof options.filter === 'object' ? options.filter : {'package.name': options.filter!}
Expand Down Expand Up @@ -84,3 +112,10 @@ export const monorepoTOC: Preset<{

return leafPackages.join(os.EOL)
}

export const toposort = <K extends string, Deps extends K>(graph: Record<K, Deps[]>) => {
return graphSequencer<K>(
new Map(Object.entries(graph) as Array<[K, Array<K | Deps>]>),
Object.keys(graph).sort() as K[],
)
}
70 changes: 70 additions & 0 deletions test/presets/monorepo-toc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,73 @@ test('invalid workspaces', () => {
}),
).toThrow(/Expected to find workspaces array, got 'package.json - not an array'/)
})

test('toplogical sort', () => {
Object.keys(mockFs)
.filter(k => k.includes('packages/'))
.forEach(k => {
// eslint-disable-next-line mmkal/@typescript-eslint/no-dynamic-delete
delete mockFs[k]
})
Object.assign(mockFs, {
'packages/client/package.json': '{ "name": "client" }',
'packages/schemainspect/package.json': '{ "name": "schemainspect", "dependencies": {"client": "*"} }',
'packages/migra/package.json': '{ "name": "migra", "dependencies": {"client": "*", "schemainspect": "*"} }',
'packages/migrator/package.json': '{ "name": "migrator", "dependencies": {"migra": "*", "client": "*"} }',
'packages/typegen/package.json': '{ "name": "typegen", "dependencies": {"client": "*"} }',
'packages/admin/package.json':
'{ "name": "admin", "dependencies": {"client": "*", "schemainspect": "*", "migrator": "*"} }',
})

expect(
preset.monorepoTOC({
...params,
options: {
sort: 'topological',
},
}),
).toMatchInlineSnapshot(`
"- [client](./packages/client)
- [schemainspect](./packages/schemainspect)
- [typegen](./packages/typegen)
- [migra](./packages/migra)
- [migrator](./packages/migrator)
- [admin](./packages/admin)"
`)
})

test('toposort helper', () => {
expect(
preset.toposort({
client: [],
schemainspect: ['client'],
migra: ['client', 'schemainspect'],
migrator: ['migra', 'client'],
typegen: ['client'],
admin: ['client', 'schemainspect', 'migrator'],
}),
).toMatchInlineSnapshot(`
{
"chunks": [
[
"client",
],
[
"schemainspect",
"typegen",
],
[
"migra",
],
[
"migrator",
],
[
"admin",
],
],
"cycles": [],
"safe": true,
}
`)
})

0 comments on commit d72b314

Please sign in to comment.