Skip to content

Commit

Permalink
feat: parse monorepo project readme (#1001)
Browse files Browse the repository at this point in the history
  • Loading branch information
achingbrain committed Jun 14, 2022
1 parent fc4d7b9 commit ae76db7
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 97 deletions.
137 changes: 137 additions & 0 deletions src/check-project/check-monorepo-readme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@

/* eslint-disable no-console */

import fs from 'fs'
import path from 'path'
import {
ensureFileHasContents
} from './utils.js'
import { toc as makeToc } from 'mdast-util-toc'
import { parseMarkdown, writeMarkdown } from './readme/utils.js'
import { HEADER } from './readme/header.js'
import { LICENSE } from './readme/license.js'
import { STRUCTURE } from './readme/structure.js'

/**
* @param {string} projectDir
* @param {string} repoUrl
* @param {string} defaultBranch
* @param {string[]} projectDirs
*/
export async function checkMonorepoReadme (projectDir, repoUrl, defaultBranch, projectDirs) {
const repoParts = repoUrl.split('/')
const repoName = repoParts.pop()
const repoOwner = repoParts.pop()

if (repoName == null || repoOwner == null) {
throw new Error(`Could not parse repo owner & name from ${repoUrl}`)
}

console.info('Check README files')

const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), {
encoding: 'utf-8'
}))

const readmePath = path.join(projectDir, 'README.md')
let readmeContents = ''

if (fs.existsSync(readmePath)) {
readmeContents = fs.readFileSync(path.join(projectDir, 'README.md'), {
encoding: 'utf-8'
})
}

// replace the magic OPTION+SPACE character that messes up headers
readmeContents = readmeContents.replaceAll(' ', ' ')

// parse the project's readme file
const file = parseMarkdown(readmeContents)

// create basic readme with heading, CI link, etc
const readme = parseMarkdown(HEADER(pkg, repoOwner, repoName, defaultBranch))

// remove existing header, CI link, etc
/** @type {import('mdast').Root} */
const parsedReadme = {
type: 'root',
children: []
}

let structureIndex = -1
let tocIndex = -1
let licenseFound = false

file.children.forEach((child, index) => {
const rendered = writeMarkdown(child).toLowerCase()

if (child.type === 'heading' && index === 0) {
// skip heading
return
}

if (child.type === 'paragraph' && index === 1) {
// skip badges
return
}

if (child.type === 'blockquote' && tocIndex === -1 && tocIndex === -1) {
// skip project overview
return
}

if (rendered.includes('## table of')) {
// skip toc header
tocIndex = index
return
}

if (tocIndex !== -1 && index === tocIndex + 1) {
// skip toc
return
}

if (child.type === 'heading' && rendered.includes('structure')) {
// skip structure header
structureIndex = index
return
}

if (structureIndex !== -1 && index === structureIndex + 1) {
// skip structure
return
}

if ((child.type === 'heading' && rendered.includes('license')) || licenseFound) {
licenseFound = true
return
}

parsedReadme.children.push(child)
})

const license = parseMarkdown(LICENSE[repoOwner])
const structure = parseMarkdown(STRUCTURE(projectDir, projectDirs))

parsedReadme.children = [
...structure.children,
...parsedReadme.children,
...license.children
]

const toc = makeToc(parsedReadme, {
tight: true
})

if (toc.map == null) {
throw new Error('Could not create TOC for README.md')
}

readme.children = [
...readme.children,
toc.map,
...parsedReadme.children
]

await ensureFileHasContents(projectDir, 'README.md', writeMarkdown(readme))
}
111 changes: 14 additions & 97 deletions src/check-project/check-readme.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,109 +6,26 @@ import path from 'path'
import {
ensureFileHasContents
} from './utils.js'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { toMarkdown } from 'mdast-util-to-markdown'
import { toc as makeToc } from 'mdast-util-toc'
import { gfm } from 'micromark-extension-gfm'
import { gfmFromMarkdown, gfmToMarkdown } from 'mdast-util-gfm'
import { gfmTable } from 'micromark-extension-gfm-table'
import { gfmTableFromMarkdown, gfmTableToMarkdown } from 'mdast-util-gfm-table'
import { gfmFootnote } from 'micromark-extension-gfm-footnote'
import { gfmFootnoteFromMarkdown, gfmFootnoteToMarkdown } from 'mdast-util-gfm-footnote'
import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'
import { gfmStrikethroughFromMarkdown, gfmStrikethroughToMarkdown } from 'mdast-util-gfm-strikethrough'
import { gfmTaskListItem } from 'micromark-extension-gfm-task-list-item'
import { gfmTaskListItemFromMarkdown, gfmTaskListItemToMarkdown } from 'mdast-util-gfm-task-list-item'

/**
* @param {*} pkg
* @param {string} repoUrl
* @param {string} defaultBranch
*/
const HEADER = (pkg, repoUrl, defaultBranch) => {
return `
# ${pkg.name} <!-- omit in toc -->
[![test & maybe release](${repoUrl}/actions/workflows/js-test-and-release.yml/badge.svg?branch=${defaultBranch})](${repoUrl}/actions/workflows/js-test-and-release.yml)
> ${pkg.description}
## Table of contents <!-- omit in toc -->
`
}

/**
* @param {*} pkg
*/
const INSTALL = (pkg) => {
return `
## Install
\`\`\`console
$ npm i ${pkg.name}
\`\`\`
`
}

const LICENSE = `
## License
Licensed under either of
* Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0)
* MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT)
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
`

/**
* @param {string} md
*/
function parseMarkdown (md) {
return fromMarkdown(md, {
extensions: [
gfm(),
gfmTable,
gfmFootnote(),
gfmStrikethrough(),
gfmTaskListItem
],
mdastExtensions: [
gfmFromMarkdown(),
gfmTableFromMarkdown,
gfmFootnoteFromMarkdown(),
gfmStrikethroughFromMarkdown,
gfmTaskListItemFromMarkdown
]
})
}

/**
*
* @param {import('mdast').Root | import('mdast').Content} tree
*/
function writeMarkdown (tree) {
return toMarkdown(tree, {
extensions: [
gfmToMarkdown(),
gfmTableToMarkdown(),
gfmFootnoteToMarkdown(),
gfmStrikethroughToMarkdown,
gfmTaskListItemToMarkdown
],
bullet: '-',
listItemIndent: 'one'
})
}
import { parseMarkdown, writeMarkdown } from './readme/utils.js'
import { HEADER } from './readme/header.js'
import { LICENSE } from './readme/license.js'
import { INSTALL } from './readme/install.js'

/**
* @param {string} projectDir
* @param {string} repoUrl
* @param {string} defaultBranch
*/
export async function checkReadme (projectDir, repoUrl, defaultBranch) {
const repoParts = repoUrl.split('/')
const repoName = repoParts.pop()
const repoOwner = repoParts.pop()

if (repoName == null || repoOwner == null) {
throw new Error(`Could not parse repo owner & name from ${repoUrl}`)
}

console.info('Check README files')

const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), {
Expand All @@ -131,7 +48,7 @@ export async function checkReadme (projectDir, repoUrl, defaultBranch) {
const file = parseMarkdown(readmeContents)

// create basic readme with heading, CI link, etc
const readme = parseMarkdown(HEADER(pkg, repoUrl, defaultBranch))
const readme = parseMarkdown(HEADER(pkg, repoOwner, repoName, defaultBranch))

// remove existing header, CI link, etc
/** @type {import('mdast').Root} */
Expand Down Expand Up @@ -193,7 +110,7 @@ export async function checkReadme (projectDir, repoUrl, defaultBranch) {
})

const installation = parseMarkdown(INSTALL(pkg))
const license = parseMarkdown(LICENSE)
const license = parseMarkdown(LICENSE[repoOwner])

parsedReadme.children = [
...installation.children,
Expand Down
2 changes: 2 additions & 0 deletions src/check-project/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { checkLicenseFiles } from './check-licence-files.js'
import { checkBuildFiles } from './check-build-files.js'
import { checkMonorepoFiles } from './check-monorepo-files.js'
import { checkReadme } from './check-readme.js'
import { checkMonorepoReadme } from './check-monorepo-readme.js'
import {
sortManifest,
ensureFileHasContents
Expand Down Expand Up @@ -127,6 +128,7 @@ async function processMonorepo (projectDir, manifest, branchName, repoUrl) {
await ensureFileHasContents(projectDir, 'package.json', JSON.stringify(proposedManifest, null, 2))
await checkLicenseFiles(projectDir)
await checkBuildFiles(projectDir, branchName, repoUrl)
await checkMonorepoReadme(projectDir, repoUrl, branchName, projectDirs)
await checkMonorepoFiles(projectDir)
}

Expand Down
37 changes: 37 additions & 0 deletions src/check-project/readme/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @type {Record<string, (repoOwner: string, repoName: string, defaultBranch: string) => string>}
*/
const BADGES = {
libp2p: (repoOwner, repoName, defaultBranch) => `
[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
[![IRC](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io)
[![codecov](https://img.shields.io/codecov/c/github/${repoOwner}/${repoName}.svg?style=flat-square)](https://codecov.io/gh/${repoOwner}/${repoName})
[![CI](https://img.shields.io/github/workflow/status/libp2p/js-libp2p-interfaces/test%20&%20maybe%20release/${defaultBranch}?style=flat-square)](https://github.com/${repoOwner}/${repoName}/actions/workflows/js-test-and-release.yml)
`,
ipfs: (repoOwner, repoName, defaultBranch) => `
[![ipfs.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io)
[![IRC](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![Discord](https://img.shields.io/discord/806902334369824788?style=flat-square)](https://discord.gg/ipfs)
[![codecov](https://img.shields.io/codecov/c/github/${repoOwner}/${repoName}.svg?style=flat-square)](https://codecov.io/gh/${repoOwner}/${repoName})
[![CI](https://img.shields.io/github/workflow/status/libp2p/js-libp2p-interfaces/test%20&%20maybe%20release/${defaultBranch}?style=flat-square)](https://github.com/${repoOwner}/${repoName}/actions/workflows/js-test-and-release.yml)
`
}

/**
* @param {*} pkg
* @param {string} repoOwner
* @param {string} repoName
* @param {string} defaultBranch
*/
export const HEADER = (pkg, repoOwner, repoName, defaultBranch) => {
return `
# ${pkg.name} <!-- omit in toc -->
${BADGES[repoOwner](repoOwner, repoName, defaultBranch).trim()}
> ${pkg.description}
## Table of contents <!-- omit in toc -->
`
}
12 changes: 12 additions & 0 deletions src/check-project/readme/install.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @param {*} pkg
*/
export const INSTALL = (pkg) => {
return `
## Install
\`\`\`console
$ npm i ${pkg.name}
\`\`\`
`
}
32 changes: 32 additions & 0 deletions src/check-project/readme/license.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

/** @type {Record<string, string>} */
export const LICENSE = {
libp2p: `
## License
Licensed under either of
* Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0)
* MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT)
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
`,
ipfs: `
## License
Licensed under either of
* Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0)
* MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT)
## Contribute
Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-ipfs-unixfs-importer/issues)!
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)
`
}

0 comments on commit ae76db7

Please sign in to comment.