Lint and format all code with same configs

Luke Karrys committed Oct 20, 2023 at 15:11 UTC 8b3b27d769fc1491b6de7934e71328f7ea637503
20 files changed +275 -315
.eslintrc.js
+3 -1
@@ -1,7 +1,8 @@
1 module.exports = {
2 root: true,
3 - ignorePatterns: ['cli/', '.cache/', 'public/'],
3 + ignorePatterns: ['.cache/', 'public/'],
4 extends: [
5 + '@npmcli',
6 'react-app',
7 // 'react-app/jest',
8 'eslint:recommended',
@@ -13,6 +14,7 @@ module.exports = {
14 'prettier',
15 ],
16 rules: {
17 + 'max-len': 'off',
18 'react/prop-types': 'off',
19 'primer-react/no-system-props': ['error', {includeUtilityComponents: true}],
20 },
.prettierignore
+7 -6
@@ -1,8 +1,9 @@
1 **/template-oss/*.json
2 **/template-oss/*.yml
3 -content/
4 -cli/
5 -.github/
6 -package-lock.json
7 -SECURITY.md
8 -.reuse/
3 +/content/
4 +/.github/
5 +/package-lock.json
6 +/SECURITY.md
7 +/.reuse/
8 +.nyc_output/
9 +coverage/
cli/.eslintrc.js deleted
-17
@@ -1,17 +0,0 @@
1 -/* This file is automatically added by @npmcli/template-oss. Do not edit. */
2 -
3 -'use strict'
4 -
5 -const { readdirSync: readdir } = require('fs')
6 -
7 -const localConfigs = readdir(__dirname)
8 - .filter((file) => file.startsWith('.eslintrc.local.'))
9 - .map((file) => `./${file}`)
10 -
11 -module.exports = {
12 - root: true,
13 - extends: [
14 - '@npmcli',
15 - ...localConfigs,
16 - ],
17 -}
cli/bin/build.js
+5 -5
@@ -1,7 +1,7 @@
1 -const { resolve, relative, join } = require('path')
2 -const { spawnSync } = require('child_process')
1 +const {resolve, relative, join} = require('path')
2 +const {spawnSync} = require('child_process')
3 const build = require('../lib/build.js')
4 -const { nwo } = require('../lib/gh')
4 +const {nwo} = require('../lib/gh')
5
6 // check only build with the current versions instead of checking the registry
7 // and also fails if any changes are detected. this is used in CI to make sure
@@ -13,7 +13,7 @@ const contentPath = join(ROOT, 'content/cli')
13 const navPath = join(ROOT, 'content/nav.yml')
14
15 const checkContent = () => {
16 - const status = spawnSync('git', ['status', '--porcelain', contentPath], { encoding: 'utf-8' })
16 + const status = spawnSync('git', ['status', '--porcelain', contentPath], {encoding: 'utf-8'})
17 if (status.stdout) {
18 const msg = [
19 `The following untracked changes to ${relative(process.cwd(), contentPath)} were found:`,
@@ -38,7 +38,7 @@ build({
38 }
39 return console.log('DONE')
40 })
41 - .catch((e) => {
41 + .catch(e => {
42 console.error(e)
43 process.exit(1)
44 })
cli/lib/build.js
+48 -48
@@ -1,4 +1,4 @@
1 -const { posix } = require('path')
1 +const {posix} = require('path')
2 const fs = require('fs').promises
3 const yaml = require('yaml')
4 const semver = require('semver')
@@ -8,8 +8,8 @@ const log = require('./log')
8
9 const DOCS_PATH = 'cli'
10
11 -const updateNav = async (updates, { nav, path }) => {
12 - const variants = updates.map((release) => ({
11 +const updateNav = async (updates, {nav, path}) => {
12 + const variants = updates.map(release => ({
13 title: release.title,
14 shortName: release.id,
15 url: release.url,
@@ -17,8 +17,7 @@ const updateNav = async (updates, { nav, path }) => {
17 children: release.nav,
18 }))
19
20 - const index = nav.contents.items
21 - .findIndex(n => posix.basename(n.get('url')) === DOCS_PATH)
20 + const index = nav.contents.items.findIndex(n => posix.basename(n.get('url')) === DOCS_PATH)
21 const key = [index, 'variants']
22 const current = nav.getIn(key)
23
@@ -26,7 +25,7 @@ const updateNav = async (updates, { nav, path }) => {
25 nav.setIn(key, nav.createNode(variants))
26 } else {
27 for (const variant of variants) {
29 - const vIndex = current.items.findIndex((n) => n.get('url') === variant.url)
28 + const vIndex = current.items.findIndex(n => n.get('url') === variant.url)
29 if (vIndex === -1) {
30 nav.addIn(key, nav.createNode(variant))
31 } else {
@@ -38,14 +37,16 @@ const updateNav = async (updates, { nav, path }) => {
37 return fs.writeFile(path, nav.toString(), 'utf-8')
38 }
39
41 -const getCurrentVersions = (nav) => {
40 +const getCurrentVersions = nav => {
41 // the only place the current versions are stored is in the nav
42 const currentSections = nav.find(s => s.url === `/${DOCS_PATH}`).variants
43
45 - const currentVersions = currentSections.map((v) => {
46 - const version = v.title?.match(/^Version\s(.*?)\s/)[1]
47 - return version
48 - }).sort(semver.compare)
44 + const currentVersions = currentSections
45 + .map(v => {
46 + const version = v.title?.match(/^Version\s(.*?)\s/)[1]
47 + return version
48 + })
49 + .sort(semver.compare)
50
51 return {
52 versions: currentVersions,
@@ -53,14 +54,7 @@ const getCurrentVersions = (nav) => {
54 }
55 }
56
56 -const main = async ({
57 - loglevel,
58 - releases: rawReleases,
59 - useCurrent,
60 - navPath,
61 - contentPath,
62 - prerelease,
63 -}) => {
57 +const main = async ({loglevel, releases: rawReleases, useCurrent, navPath, contentPath, prerelease}) => {
58 /* istanbul ignore next */
59 if (loglevel) {
60 log.on(loglevel)
@@ -72,32 +66,40 @@ const main = async ({
66
67 const pack = useCurrent
68 ? getCurrentVersions(navData)
75 - : await pacote.packument('npm', { preferOnline: true }).then(p => ({
76 - versions: Object.keys(p.versions),
77 - latest: p['dist-tags'].latest,
78 - }))
79 -
80 - const releaseVersions = rawReleases.map(release => {
81 - const major = Number(release.id.replace(/^v/, ''))
82 - const range = `>=${major}.0.0-a <${major + 1}.0.0` // include all prereleases
83 - const version = semver.parse(semver.maxSatisfying(pack.versions, range))
84 -
85 - return version && {
86 - ...release,
87 - version: version.toString(),
88 - // the default release is always controlled by the latest dist-tag
89 - default: semver.eq(version, pack.latest),
90 - prerelease: version.prerelease.length > 0,
91 - }
92 - }).filter(Boolean)
93 -
94 - const latestRelease = releaseVersions.find(r => r.default) ??
69 + : await pacote.packument('npm', {preferOnline: true}).then(p => ({
70 + versions: Object.keys(p.versions),
71 + latest: p['dist-tags'].latest,
72 + }))
73 +
74 + const releaseVersions = rawReleases
75 + .map(release => {
76 + const major = Number(release.id.replace(/^v/, ''))
77 + const range = `>=${major}.0.0-a <${major + 1}.0.0` // include all prereleases
78 + const version = semver.parse(semver.maxSatisfying(pack.versions, range))
79 +
80 + return (
81 + version && {
82 + ...release,
83 + version: version.toString(),
84 + // the default release is always controlled by the latest dist-tag
85 + default: semver.eq(version, pack.latest),
86 + prerelease: version.prerelease.length > 0,
87 + }
88 + )
89 + })
90 + .filter(Boolean)
91 +
92 + const latestRelease =
93 + releaseVersions.find(r => r.default) ??
94 releaseVersions.slice(0).sort((a, b) => semver.compare(b.version, a.version))[0]
95
97 - const releases = releaseVersions.map((release) => {
98 - const type = release.default ? 'Latest Release'
99 - : release.prerelease ? 'Prerelease'
100 - : semver.gt(release.version, latestRelease.version) ? 'Current Release'
96 + const releases = releaseVersions.map(release => {
97 + const type = release.default
98 + ? 'Latest Release'
99 + : release.prerelease
100 + ? 'Prerelease'
101 + : semver.gt(release.version, latestRelease.version)
102 + ? 'Current Release'
103 : 'Legacy Release'
104
105 return {
@@ -110,12 +112,10 @@ const main = async ({
112 })
113
114 const updates = await Promise.all(
113 - releases.map((r) =>
114 - extractRelease(r, { contentPath, baseNav: navData, prerelease })
115 - )
116 - ).then((r) => r.filter(Boolean))
115 + releases.map(r => extractRelease(r, {contentPath, baseNav: navData, prerelease})),
116 + ).then(r => r.filter(Boolean))
117
118 - await updateNav(updates, { nav: navDoc, path: navPath })
118 + await updateNav(updates, {nav: navDoc, path: navPath})
119 }
120
121 module.exports = main
cli/lib/extract.js
+58 -56
@@ -1,6 +1,6 @@
1 const pacote = require('pacote')
2 const tar = require('tar')
3 -const { join, sep, dirname, posix } = require('path')
3 +const {join, sep, dirname, posix} = require('path')
4 const fs = require('fs/promises')
5 const yaml = require('yaml')
6 const Transform = require('./transform')
@@ -17,31 +17,33 @@ const whackAMoleReplace = (s, replacements) => {
17 return s
18 }
19
20 -const unpackTarball = async ({ release, cwd, dir }) => {
20 +const unpackTarball = async ({release, cwd, dir}) => {
21 const strip = 1
22 const result = []
23 const dirParts = dir.split(sep)
24
25 - log.verbose('tarball', release.resolved, { cwd, dir })
25 + log.verbose('tarball', release.resolved, {cwd, dir})
26
27 - const format = (s) => {
27 + const format = s => {
28 // a few specific replacements that cause problems for mdx
29 - return whackAMoleReplace(s, [
30 - '{npm-version} node/{node-version} {platform} {arch} workspaces/{workspaces} {ci}',
31 - 'Default: {prefix}/etc/npmrc',
32 - ])
33 - // we cant remove all emails since all except this one are in code blocks
34 - .replace(/(:: )<(i@izs\.me)>/g, '$1[$2](mailto:$2)')
35 - // the v6 version of the funding page has json not inside a code
36 - .replace(/(:\n\n)(\s{4}"funding": {)/g, '$1```json\n$2')
37 - .replace(/^(\s{4}]$)(\n\n)/gm, '$1\n```$2')
29 + return (
30 + whackAMoleReplace(s, [
31 + '{npm-version} node/{node-version} {platform} {arch} workspaces/{workspaces} {ci}',
32 + 'Default: {prefix}/etc/npmrc',
33 + ])
34 + // we cant remove all emails since all except this one are in code blocks
35 + .replace(/(:: )<(i@izs\.me)>/g, '$1[$2](mailto:$2)')
36 + // the v6 version of the funding page has json not inside a code
37 + .replace(/(:\n\n)(\s{4}"funding": {)/g, '$1```json\n$2')
38 + .replace(/^(\s{4}]$)(\n\n)/gm, '$1\n```$2')
39 + )
40 }
41
42 const extract = () =>
43 tar.x({
44 cwd,
45 strip: dirParts.length + strip,
44 - transform: ({ path }) => {
46 + transform: ({path}) => {
47 result.push(path)
48 log.verbose(release.id, path)
49 return new Transform({
@@ -50,28 +52,31 @@ const unpackTarball = async ({ release, cwd, dir }) => {
52 format,
53 })
54 },
53 - filter: (path) => {
55 + filter: path => {
56 const pathParts = path.split(posix.sep)
57 const prefixParts = pathParts.slice(strip, dirParts.length + strip)
58 return join(...prefixParts) === join(...dirParts)
59 },
60 })
61
60 - await pacote.tarball.stream(`npm@${release.version}`, (stream) =>
61 - new Promise((res, rej) => {
62 - stream.on('end', res)
63 - stream.on('error', rej)
64 - stream.pipe(extract())
65 - }))
62 + await pacote.tarball.stream(
63 + `npm@${release.version}`,
64 + stream =>
65 + new Promise((res, rej) => {
66 + stream.on('end', res)
67 + stream.on('error', rej)
68 + stream.pipe(extract())
69 + }),
70 + )
71
72 return result
73 }
74
70 -const getNav = async ({ path, release }) => {
71 - const nav = await gh.getFile({ ref: release.branch, path })
75 +const getNav = async ({path, release}) => {
76 + const nav = await gh.getFile({ref: release.branch, path})
77
73 - const rewriteUrls = (nodes) =>
74 - nodes?.map((n) => {
78 + const rewriteUrls = nodes =>
79 + nodes?.map(n => {
80 n.url = release.url + n.url
81 n.children = rewriteUrls(n.children)
82 return n
@@ -83,9 +88,9 @@ const getNav = async ({ path, release }) => {
88 }
89 }
90
86 -const writeChangelog = async ({ release, nav, cwd, srcPath, contentPath }) => {
91 +const writeChangelog = async ({release, nav, cwd, srcPath, contentPath}) => {
92 const title = 'Changelog'
88 - const changelog = await gh.getFile({ ref: release.branch, path: srcPath })
93 + const changelog = await gh.getFile({ref: release.branch, path: srcPath})
94
95 await fs.writeFile(
96 join(cwd, contentPath + '.md'),
@@ -96,23 +101,25 @@ const writeChangelog = async ({ release, nav, cwd, srcPath, contentPath }) => {
101 github_path: srcPath,
102 title,
103 },
99 - format: (s) => {
104 + format: s => {
105 // some known content in changelogs that is problematic for mdx this is
106 // a bit of whack-a-mole but is necessary since markdown in the CLI is
107 // different from the mdx v2 we parse for the docs site
103 - return whackAMoleReplace(s, [
104 - ' support for node <=16.13 ',
105 - '<->',
106 - ' npm install <folder> ',
107 - ' --replace-registry-host=<npmjs|always|never> ',
108 - 'bundledDependencies -> bundleDependencies ',
109 - ' bump knownBroken to <12.5.0 ',
110 - ])
111 - // remove changelog h1 so it doesnt double render the title
112 - .replace(/^#\s+Changelog\s+$\n/gm, '')
108 + return (
109 + whackAMoleReplace(s, [
110 + ' support for node <=16.13 ',
111 + '<->',
112 + ' npm install <folder> ',
113 + ' --replace-registry-host=<npmjs|always|never> ',
114 + 'bundledDependencies -> bundleDependencies ',
115 + ' bump knownBroken to <12.5.0 ',
116 + ])
117 + // remove changelog h1 so it doesnt double render the title
118 + .replace(/^#\s+Changelog\s+$\n/gm, '')
119 + )
120 },
121 }),
115 - 'utf-8'
122 + 'utf-8',
123 )
124
125 nav.children[nav.children.length - 1].children.push({
@@ -122,10 +129,7 @@ const writeChangelog = async ({ release, nav, cwd, srcPath, contentPath }) => {
129 })
130 }
131
125 -const unpackRelease = async (
126 - release,
127 - { contentPath, baseNav, prerelease = false }
128 -) => {
132 +const unpackRelease = async (release, {contentPath, baseNav, prerelease = false}) => {
133 if (release.prerelease && !prerelease) {
134 log.info(`Skipping ${release.id} due to prerelease ${release.version}`)
135 return
@@ -139,8 +143,7 @@ const unpackRelease = async (
143 const srcPath = join('docs', 'lib', 'content')
144
145 // this is the src dir for the docs that we link to for the edit links
142 - release.src = await gh.pathExists(release.branch, srcPath)
143 - ?? await gh.pathExists(release.branch, builtPath)
146 + release.src = (await gh.pathExists(release.branch, srcPath)) ?? (await gh.pathExists(release.branch, builtPath))
147
148 /* istanbul ignore next */
149 if (!release.src) {
@@ -150,13 +153,12 @@ const unpackRelease = async (
153 const nav = await getNav({
154 release,
155 // the nav file can also be in a few different places
153 - path: await gh.pathExists(release.branch, join(srcPath, 'nav.yml'))
154 - ?? await gh.pathExists(release.branch, join('docs', 'nav.yml')),
156 + path:
157 + (await gh.pathExists(release.branch, join(srcPath, 'nav.yml'))) ??
158 + (await gh.pathExists(release.branch, join('docs', 'nav.yml'))),
159 })
160
157 - await fs
158 - .rm(cwd, { force: true, recursive: true })
159 - .then(() => fs.mkdir(cwd, { recursive: true }))
161 + await fs.rm(cwd, {force: true, recursive: true}).then(() => fs.mkdir(cwd, {recursive: true}))
162
163 const files = await unpackTarball({
164 release,
@@ -164,18 +166,18 @@ const unpackRelease = async (
166 dir: builtPath,
167 })
168
167 - const dirs = ['', ...new Set(files.map((f) => dirname(f)))]
169 + const dirs = ['', ...new Set(files.map(f => dirname(f)))]
170
171 // The docs in the cli contains all the content pagess and the nav
172 // but no index pages. So this builts empty index pages for each
173 // directory with the correct frontmatter. The context is an mdx
174 // component which will end up showing the nav for this directory.
175 const indexes = await Promise.all(
174 - dirs.map(async (dir) => {
176 + dirs.map(async dir => {
177 const path = join(dir, 'index.mdx')
178 const navSection = dir
177 - ? nav.children.find((c) => posix.basename(c.url) === dir)
178 - : baseNav.find((c) => posix.basename(c.url) === release.urlPrefix)
179 + ? nav.children.find(c => posix.basename(c.url) === dir)
180 + : baseNav.find(c => posix.basename(c.url) === release.urlPrefix)
181
182 await fs.writeFile(
183 join(cwd, path),
@@ -188,11 +190,11 @@ const unpackRelease = async (
190 shortName: navSection.shortName,
191 },
192 }),
191 - 'utf-8'
193 + 'utf-8',
194 )
195
196 return path
195 - })
197 + }),
198 )
199
200 await writeChangelog({
cli/lib/gh.js
+13 -13
@@ -1,26 +1,26 @@
1 -const { Octokit } = require('@octokit/rest')
2 -const { posix, sep } = require('path')
1 +const {Octokit} = require('@octokit/rest')
2 +const {posix, sep} = require('path')
3
4 if (!process.env.GITHUB_TOKEN) {
5 throw new Error('GITHUB_TOKEN env var is required to build CLI docs')
6 }
7
8 -const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
8 +const octokit = new Octokit({auth: process.env.GITHUB_TOKEN})
9 const owner = 'npm'
10 const repo = 'cli'
11 -const opts = { owner, repo }
11 +const opts = {owner, repo}
12
13 -const getFile = async ({ sha, ref, path }) => {
14 - const { data } = await (sha
13 +const getFile = async ({sha, ref, path}) => {
14 + const {data} = await (sha
15 ? octokit.git.getBlob({
16 - ...opts,
17 - file_sha: sha,
18 - })
16 + ...opts,
17 + file_sha: sha,
18 + })
19 : octokit.repos.getContent({
20 - ...opts,
21 - ref,
22 - path: path.split(sep).join(posix.sep),
23 - }))
20 + ...opts,
21 + ref,
22 + path: path.split(sep).join(posix.sep),
23 + }))
24 return Buffer.from(data.content, data.encoding)
25 }
26
cli/lib/log.js
+10 -11
@@ -1,16 +1,15 @@
1 const log = require('proc-log')
2 const SYMBOL = Symbol('doc-log')
3
4 -module.exports = Object.fromEntries(
5 - Object.entries(log).map(([k, v]) => [k, (...a) => v(SYMBOL, ...a)])
6 -)
4 +module.exports = Object.fromEntries(Object.entries(log).map(([k, v]) => [k, (...a) => v(SYMBOL, ...a)]))
5
6 /* istanbul ignore next */
9 -module.exports.on = (loglevel) => process.on('log', (l, s, ...args) => {
10 - // If loglevel is verbose, show everything. Otherwise only show things
11 - // that are not verbose. The script only uses verbose and info, so this
12 - // approach works for now.
13 - if (s === SYMBOL && (loglevel === 'verbose' || l !== 'verbose')) {
14 - console.error(l, ...args)
15 - }
16 -})
7 +module.exports.on = loglevel =>
8 + process.on('log', (l, s, ...args) => {
9 + // If loglevel is verbose, show everything. Otherwise only show things
10 + // that are not verbose. The script only uses verbose and info, so this
11 + // approach works for now.
12 + if (s === SYMBOL && (loglevel === 'verbose' || l !== 'verbose')) {
13 + console.error(l, ...args)
14 + }
15 + })
cli/lib/redirects.js
+19 -50
@@ -1,4 +1,6 @@
1 -const { posix: { join } } = require('path')
1 +const {
2 + posix: {join},
3 +} = require('path')
4
5 // An object to generate redirects for a given path.
6 // The key is a glob path that is matched by minimatch,
@@ -31,64 +33,31 @@ const { posix: { join } } = require('path')
33
34 module.exports = {
35 index: () => ['.'],
34 - '*/!(index)': ({ section, page }) => [
35 - join(section, page),
36 - join('/', section, page),
37 - ],
38 - '*/index': ({ section, page }) => [
39 - join(section),
40 - join('/', section),
41 - join(section, page),
42 - join('/', section, page),
43 - ],
36 + '*/!(index)': ({section, page}) => [join(section, page), join('/', section, page)],
37 + '*/index': ({section, page}) => [join(section), join('/', section), join(section, page), join('/', section, page)],
38
39 // any page with `-json` -> `.json`
46 - '**/*-json*': ({ section, page }) => [
47 - join(section, page.replace(/-json/g, '.json')),
48 - ],
40 + '**/*-json*': ({section, page}) => [join(section, page.replace(/-json/g, '.json'))],
41
42 // commands
51 - 'commands/!(index)': ({ page }) => [
52 - page,
53 - ],
54 - 'commands/index': () => [
55 - '/cli-documentation/cli',
56 - ],
57 - 'commands/*': ({ page }) => [
58 - join('cli-commands', page),
59 - join('/', 'cli-commands', page),
60 - ],
61 - 'commands/npm-*': ({ section, page }) => [
62 - join(section, page.replace(/^npm-/, '')),
63 - ],
43 + 'commands/!(index)': ({page}) => [page],
44 + 'commands/index': () => ['/cli-documentation/cli'],
45 + 'commands/*': ({page}) => [join('cli-commands', page), join('/', 'cli-commands', page)],
46 + 'commands/npm-*': ({section, page}) => [join(section, page.replace(/^npm-/, ''))],
47
48 // configuring npm
66 - 'configuring-npm/*': ({ page }) => [
67 - join('files', page),
68 - join('/', 'files', page),
69 - ],
70 - 'configuring-npm/package-locks': ({ section, page, release }) => [
49 + 'configuring-npm/*': ({page}) => [join('files', page), join('/', 'files', page)],
50 + 'configuring-npm/package-locks': ({section, page, release}) => [
51 // A special case for a path that was deleted in v7, but we still
52 // want the old v6 default url to resolve.
73 - { path: join('/', section, page), default: release.id === 'v6' },
74 - { path: join(section, page), default: release.id === 'v6' },
53 + {path: join('/', section, page), default: release.id === 'v6'},
54 + {path: join(section, page), default: release.id === 'v6'},
55 ],
56
57 // using npm
78 - 'using-npm/*': ({ page }) => [
79 - join('misc', page),
80 - join('/', 'misc', page),
81 - ],
82 - 'using-npm/removal': ({ section }) => [
83 - join(section, 'removing-npm'),
84 - ],
85 - 'using-npm/scope': ({ section }) => [
86 - join(section, 'npm-scope'),
87 - ],
88 - 'about-pgp-signatures-for-packages-in-the-public-registry': () => [
89 - '/about-registry-signatures',
90 - ],
91 - 'verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry': () => [
92 - '/verifying-registry-signatures',
93 - ],
58 + 'using-npm/*': ({page}) => [join('misc', page), join('/', 'misc', page)],
59 + 'using-npm/removal': ({section}) => [join(section, 'removing-npm')],
60 + 'using-npm/scope': ({section}) => [join(section, 'npm-scope')],
61 + 'about-pgp-signatures-for-packages-in-the-public-registry': () => ['/about-registry-signatures'],
62 + 'verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry': () => ['/verifying-registry-signatures'],
63 }
cli/lib/transform.js
+21 -25
@@ -1,13 +1,13 @@
1 const parseFm = require('front-matter')
2 -const { Minipass } = require('minipass')
2 +const {Minipass} = require('minipass')
3 const yaml = require('yaml')
4 -const { minimatch } = require('minimatch')
5 -const { sep, join, posix, isAbsolute } = require('path')
4 +const {minimatch} = require('minimatch')
5 +const {sep, join, posix, isAbsolute} = require('path')
6 const gh = require('./gh')
7 const prettier = require('@prettier/sync')
8 const rawRedirects = require('./redirects')
9
10 -const getPathParts = (path) => {
10 +const getPathParts = path => {
11 const abs = isAbsolute(path)
12 const paths = path.replace(/\.mdx?$/, '').split(sep)
13
@@ -22,23 +22,20 @@ const getPathParts = (path) => {
22 }
23 }
24
25 -const getRedirects = ({ path, release }) => {
25 +const getRedirects = ({path, release}) => {
26 const redirects = [getPathParts(path)]
27 const [pagePath] = redirects
28 - const canonical = posix.join(
29 - release.url,
30 - pagePath.section,
31 - pagePath.page === 'index' ? '' : pagePath.page
32 - )
28 + const canonical = posix.join(release.url, pagePath.section, pagePath.page === 'index' ? '' : pagePath.page)
29
30 for (const [k, v] of Object.entries(rawRedirects).reverse()) {
31 const pageRedirects = redirects.flatMap(redirect => {
32 if (minimatch(redirect.path, k)) {
37 - return v({ ...getPathParts(redirect.path), release }).map(p => ({
33 + return v({...getPathParts(redirect.path), release}).map(p => ({
34 ...redirect,
39 - ...typeof p === 'object' ? p : { path: p },
35 + ...(typeof p === 'object' ? p : {path: p}),
36 }))
37 }
38 + return []
39 })
40 redirects.push(...pageRedirects.filter(Boolean))
41 }
@@ -58,18 +55,17 @@ const getRedirects = ({ path, release }) => {
55 .filter(Boolean)
56 .filter(r => r !== canonical)
57
61 - return [...new Set(prefixRedirects)]
62 - .sort((a, b) => a.localeCompare(b, 'en'))
58 + return [...new Set(prefixRedirects)].sort((a, b) => a.localeCompare(b, 'en'))
59 }
60
65 -const transform = (data, { release, path, frontmatter, format = s => s }) => {
66 - let { attributes, body } = parseFm(data.toString())
61 +const transform = (data, {release, path, frontmatter, format = s => s}) => {
62 + let {attributes, body} = parseFm(data.toString())
63
64 /* istanbul ignore next */
65 if (!attributes.redirect_from) {
66 attributes.redirect_from = []
67 }
72 - attributes.redirect_from.push(...getRedirects({ path, release }))
68 + attributes.redirect_from.push(...getRedirects({path, release}))
69
70 const ghFrontmatter = {
71 github_repo: gh.nwo,
@@ -91,7 +87,7 @@ const transform = (data, { release, path, frontmatter, format = s => s }) => {
87 const sortRedirects = (a, b) => a.localeCompare(b, 'en')
88
89 attributes = Object.fromEntries(
94 - Object.entries({ ...attributes, ...ghFrontmatter, ...frontmatter })
90 + Object.entries({...attributes, ...ghFrontmatter, ...frontmatter})
91 .filter(([, v]) => (Array.isArray(v) ? v.length : true))
92 .map(([k, v]) => (Array.isArray(v) ? [k, v.sort(sortRedirects)] : [k, v]))
93 .sort(([a], [b]) => {
@@ -100,12 +96,12 @@ const transform = (data, { release, path, frontmatter, format = s => s }) => {
96 /* istanbul ignore next */
97 const bIndex = order.includes(b) ? order.indexOf(b) : order.length
98 return aIndex - bIndex
103 - })
99 + }),
100 )
101
102 // first format with prettier, this helps so other replacements don't have to
103 // worry about newlines vs spaces
108 - body = prettier.format(body, { parser: 'markdown', proseWrap: 'never' })
104 + body = prettier.format(body, {parser: 'markdown', proseWrap: 'never'})
105 // then do replacements for all cli makdown files
106 body = body
107 // some legacy versions of the docs did not get this replaced
@@ -115,7 +111,7 @@ const transform = (data, { release, path, frontmatter, format = s => s }) => {
111 // specific version
112 .replace(
113 /\[([^\]]+)\]\(\/((?:commands|configuring-npm|using-npm)\/[^)]+)\)/g,
118 - (_, p1, p2) => `[${p1}](${release.url}/${p2})`
114 + (_, p1, p2) => `[${p1}](${release.url}/${p2})`,
115 )
116 // remove html comments which are not mdx compatible
117 .replace(/^<!--\s.*?\s-->$\n/gm, '')
@@ -137,18 +133,18 @@ class Transform extends Minipass {
133
134 static sync = transform
135
140 - constructor (transformOpts) {
141 - super({ encoding: 'utf-8' })
136 + constructor(transformOpts) {
137 + super({encoding: 'utf-8'})
138 this.#opts = transformOpts
139 }
140
145 - write (c) {
141 + write(c) {
142 this.#data.push(c)
143 this.#length += c.length
144 return true
145 }
146
151 - end () {
147 + end() {
148 super.write(transform(Buffer.concat(this.#data, this.#length), this.#opts))
149 return super.end()
150 }
cli/package.json
-2
@@ -31,8 +31,6 @@
31 "devDependencies": {
32 "@npmcli/eslint-config": "^4.0.2",
33 "@npmcli/template-oss": "4.19.0",
34 - "eslint-plugin-node": "^11.1.0",
35 - "eslint-plugin-promise": "^6.1.1",
34 "tap": "^16.3.9"
35 },
36 "author": "GitHub Inc.",
cli/scripts/template-oss/index.js
+1 -3
@@ -5,7 +5,5 @@ module.exports = {
5 '.github/workflows/update-cli.yml': 'update-cli.yml',
6 },
7 },
8 - allowPaths: [
9 - '/releases.json',
10 - ],
8 + allowPaths: ['/releases.json'],
9 }
cli/test/index.js
+40 -41
@@ -1,17 +1,11 @@
1 const t = require('tap')
2 -const { resolve, join, posix } = require('path')
2 +const {resolve, join, posix} = require('path')
3 const fs = require('fs/promises')
4 const pacote = require('pacote')
5 const yaml = require('yaml')
6 const semver = require('semver')
7
8 -const navPath = resolve(
9 - __dirname,
10 - '..',
11 - '..',
12 - 'content',
13 - 'nav.yml'
14 -)
8 +const navPath = resolve(__dirname, '..', '..', 'content', 'nav.yml')
9
10 const getReleases = () => [
11 {
@@ -32,11 +26,7 @@ const getReleases = () => [
26 },
27 ]
28
35 -const mockBuild = async (t, {
36 - releases = getReleases(),
37 - packument = {},
38 - testdir: testdirOpts,
39 -} = {}) => {
29 +const mockBuild = async (t, {releases = getReleases(), packument = {}, testdir: testdirOpts} = {}) => {
30 const rawNav = await fs.readFile(navPath, 'utf-8')
31 const nav = yaml.parse(rawNav)
32
@@ -59,6 +49,8 @@ const mockBuild = async (t, {
49 return '8.19.3'
50 case '9':
51 return '9.0.0'
52 + default:
53 + throw new Error(`Unknown packument version: ${JSON.stringify(r)}`)
54 }
55 })
56 }
@@ -67,10 +59,10 @@ const mockBuild = async (t, {
59 packument.latest = packument.versions[packument.versions.length - 1]
60 }
61
70 - const navSection = (ref) => {
62 + const navSection = ref => {
63 const id = ref === 'latest' ? `v${semver.major(packument.latest)}` : posix.basename(ref)
72 - const { variants } = nav.find(c => c.url === '/cli')
73 - const { children } = variants.find(v => posix.basename(v.url) === id)
64 + const {variants} = nav.find(c => c.url === '/cli')
65 + const {children} = variants.find(v => posix.basename(v.url) === id)
66 return yaml.stringify(children).replace(new RegExp(`/cli/${id}/`, 'g'), '/')
67 }
68
@@ -89,9 +81,9 @@ const mockBuild = async (t, {
81 }
82 },
83 },
92 - '@prettier/sync': { format: s => s },
84 + '@prettier/sync': {format: s => s},
85 '../lib/gh.js': {
94 - getFile: async ({ ref }) => navSection(ref),
86 + getFile: async ({ref}) => navSection(ref),
87 pathExists: async (ref, p) => {
88 if (ref.includes('v6') && p.includes('docs/lib/content')) {
89 return null
@@ -105,55 +97,62 @@ const mockBuild = async (t, {
97 return {
98 testdir,
99 releases,
108 - build: (opts) => build({
109 - releases,
110 - contentPath: join(testdir, 'content'),
111 - navPath: join(testdir, 'nav.yml'),
112 - ...opts,
113 - }),
100 + build: opts =>
101 + build({
102 + releases,
103 + contentPath: join(testdir, 'content'),
104 + navPath: join(testdir, 'nav.yml'),
105 + ...opts,
106 + }),
107 }
108 }
109
117 -t.test('basic', async (t) => {
118 - const { releases, build, testdir } = await mockBuild(t, {
110 +t.test('basic', async t => {
111 + const {releases, build, testdir} = await mockBuild(t, {
112 testdir: {
113 'nav.yml': '- title: cli\n url: /cli',
114 },
115 })
116
117 await build()
125 - t.strictSame(await fs.readdir(join(testdir, 'content')), releases.map(r => r.id))
118 + t.strictSame(
119 + await fs.readdir(join(testdir, 'content')),
120 + releases.map(r => r.id),
121 + )
122 })
123
128 -t.test('prereleases', async (t) => {
129 - const { build, releases, testdir } = await mockBuild(t, {
130 - packument: { versions: ['6.14.18', '7.24.2', '8.19.3', '9.0.0-pre.2'], latest: '8.19.3' },
124 +t.test('prereleases', async t => {
125 + const {build, releases, testdir} = await mockBuild(t, {
126 + packument: {versions: ['6.14.18', '7.24.2', '8.19.3', '9.0.0-pre.2'], latest: '8.19.3'},
127 })
128
133 - await build({ prerelease: false })
129 + await build({prerelease: false})
130 const expectedReleases = releases.map(r => r.id).filter(r => r !== 'v9')
131 t.strictSame(await fs.readdir(join(testdir, 'content')), expectedReleases)
132
137 - await build({ prerelease: true })
138 - t.strictSame(await fs.readdir(join(testdir, 'content')), releases.map(r => r.id))
133 + await build({prerelease: true})
134 + t.strictSame(
135 + await fs.readdir(join(testdir, 'content')),
136 + releases.map(r => r.id),
137 + )
138 })
139
141 -t.test('earlier release is latest', async (t) => {
142 - const { build } = await mockBuild(t, {
143 - packument: { latest: '8.19.3' },
140 +t.test('earlier release is latest', async t => {
141 + const {build} = await mockBuild(t, {
142 + packument: {latest: '8.19.3'},
143 })
144
145 await build()
146 })
147
149 -t.test('can skip fetching latest', async (t) => {
150 - const { build } = await mockBuild(t)
148 +t.test('can skip fetching latest', async t => {
149 + const {build} = await mockBuild(t)
150
152 - await build({ useCurrent: true })
151 + await build({useCurrent: true})
152 })
153
155 -t.test('add variant to nav', async (t) => {
156 - const { build } = await mockBuild(t, {
154 +t.test('add variant to nav', async t => {
155 + const {build} = await mockBuild(t, {
156 testdir: {
157 'nav.yml': '- title: cli\n url: /cli\n variants:\n - url: /cli/v0',
158 },
cli/test/transform.js
+31 -28
@@ -1,9 +1,9 @@
1 const t = require('tap')
2 const fm = require('front-matter')
3
4 -const transform = ({ id, path }) => {
4 +const transform = ({id, path}) => {
5 const Transform = t.mock('../lib/transform', {
6 - '../lib/gh.js': { nwo: 'npm/cli' },
6 + '../lib/gh.js': {nwo: 'npm/cli'},
7 })
8 const transformed = Transform.sync('---\n---\n', {
9 release: {
@@ -19,8 +19,8 @@ const transform = ({ id, path }) => {
19 }
20
21 t.test('v6 default page', async t => {
22 - const v6 = transform({ id: 'v6', path: 'configuring-npm/package-locks' })
23 - const v7 = transform({ id: 'v7', path: 'configuring-npm/package-locks' })
22 + const v6 = transform({id: 'v6', path: 'configuring-npm/package-locks'})
23 + const v7 = transform({id: 'v7', path: 'configuring-npm/package-locks'})
24
25 t.strictSame(v6.redirect_from, [
26 '/cli/configuring-npm/package-locks',
@@ -30,15 +30,12 @@ t.test('v6 default page', async t => {
30 '/configuring-npm/package-locks',
31 '/files/package-locks',
32 ])
33 - t.strictSame(v7.redirect_from, [
34 - '/cli/v7/configuring-npm/package-locks',
35 - '/cli/v7/files/package-locks',
36 - ])
33 + t.strictSame(v7.redirect_from, ['/cli/v7/configuring-npm/package-locks', '/cli/v7/files/package-locks'])
34 })
35
36 t.test('command', async t => {
40 - const v7 = transform({ id: 'v7', path: 'commands/npm-bin' })
41 - const v8 = transform({ id: 'v8', path: 'commands/npm-bin' })
37 + const v7 = transform({id: 'v7', path: 'commands/npm-bin'})
38 + const v8 = transform({id: 'v8', path: 'commands/npm-bin'})
39
40 t.strictSame(v7.redirect_from, [
41 '/cli/v7/bin',
@@ -69,8 +66,8 @@ t.test('command', async t => {
66 })
67
68 t.test('package-json files', async t => {
72 - const v7 = transform({ id: 'v7', path: 'configuring-npm/package-json' })
73 - const v8 = transform({ id: 'v8', path: 'configuring-npm/package-json' })
69 + const v7 = transform({id: 'v7', path: 'configuring-npm/package-json'})
70 + const v8 = transform({id: 'v8', path: 'configuring-npm/package-json'})
71
72 t.strictSame(v7.redirect_from, [
73 '/cli/v7/configuring-npm/package-json',
@@ -95,20 +92,26 @@ t.test('package-json files', async t => {
92 })
93
94 t.test('registry signatures', async t => {
98 - t.strictSame(transform({
99 - id: 'v8',
100 - path: 'about-pgp-signatures-for-packages-in-the-public-registry',
101 - }).redirect_from, [
102 - '/about-registry-signatures',
103 - '/cli/about-pgp-signatures-for-packages-in-the-public-registry',
104 - '/cli/v8/about-pgp-signatures-for-packages-in-the-public-registry',
105 - ])
106 - t.strictSame(transform({
107 - id: 'v8',
108 - path: 'verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry',
109 - }).redirect_from, [
110 - '/cli/v8/verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry',
111 - '/cli/verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry',
112 - '/verifying-registry-signatures',
113 - ])
95 + t.strictSame(
96 + transform({
97 + id: 'v8',
98 + path: 'about-pgp-signatures-for-packages-in-the-public-registry',
99 + }).redirect_from,
100 + [
101 + '/about-registry-signatures',
102 + '/cli/about-pgp-signatures-for-packages-in-the-public-registry',
103 + '/cli/v8/about-pgp-signatures-for-packages-in-the-public-registry',
104 + ],
105 + )
106 + t.strictSame(
107 + transform({
108 + id: 'v8',
109 + path: 'verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry',
110 + }).redirect_from,
111 + [
112 + '/cli/v8/verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry',
113 + '/cli/verifying-the-pgp-signature-for-a-package-from-the-npm-public-registry',
114 + '/verifying-registry-signatures',
115 + ],
116 + )
117 })
gatsby-node.js
-1
@@ -289,6 +289,5 @@ const fetchContributors = async (path, fm, {reporter, octokit}) => {
289 }
290 } catch (err) {
291 reporter[CI ? 'panic' : 'error'](`Error fetching contributors for ${path}`, err)
292 - return
292 }
293 }
package-lock.json
+5 -3
@@ -42,11 +42,13 @@
42 "react-dom": "^18.2.0",
43 "react-focus-on": "^3.9.1",
44 "react-helmet": "^6.1.0",
45 - "styled-components": "^5.3.11"
45 + "styled-components": "^5.3.11",
46 + "styled-system": "^5.1.5"
47 },
48 "devDependencies": {
49 "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
50 "@github/prettier-config": "^0.0.6",
51 + "@npmcli/eslint-config": "^4.0.2",
52 "@npmcli/template-oss": "4.19.0",
53 "@testing-library/jest-dom": "^6.1.4",
54 "@testing-library/react": "^14.0.0",
@@ -56,7 +58,9 @@
58 "eslint-plugin-github": "^4.10.1",
59 "eslint-plugin-import": "^2.28.1",
60 "eslint-plugin-jsx-a11y": "^6.7.1",
61 + "eslint-plugin-node": "^11.1.0",
62 "eslint-plugin-primer-react": "^4.0.3",
63 + "eslint-plugin-promise": "^6.1.1",
64 "eslint-plugin-react": "^7.33.2",
65 "eslint-plugin-react-hooks": "^4.6.0",
66 "jest": "^29.7.0",
@@ -83,8 +87,6 @@
87 "devDependencies": {
88 "@npmcli/eslint-config": "^4.0.2",
89 "@npmcli/template-oss": "4.19.0",
86 - "eslint-plugin-node": "^11.1.0",
87 - "eslint-plugin-promise": "^6.1.1",
90 "tap": "^16.3.9"
91 },
92 "engines": {
package.json
+5 -1
@@ -57,11 +57,13 @@
57 "react-dom": "^18.2.0",
58 "react-focus-on": "^3.9.1",
59 "react-helmet": "^6.1.0",
60 - "styled-components": "^5.3.11"
60 + "styled-components": "^5.3.11",
61 + "styled-system": "^5.1.5"
62 },
63 "devDependencies": {
64 "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
65 "@github/prettier-config": "^0.0.6",
66 + "@npmcli/eslint-config": "^4.0.2",
67 "@npmcli/template-oss": "4.19.0",
68 "@testing-library/jest-dom": "^6.1.4",
69 "@testing-library/react": "^14.0.0",
@@ -71,7 +73,9 @@
73 "eslint-plugin-github": "^4.10.1",
74 "eslint-plugin-import": "^2.28.1",
75 "eslint-plugin-jsx-a11y": "^6.7.1",
76 + "eslint-plugin-node": "^11.1.0",
77 "eslint-plugin-primer-react": "^4.0.3",
78 + "eslint-plugin-promise": "^6.1.1",
79 "eslint-plugin-react": "^7.33.2",
80 "eslint-plugin-react-hooks": "^4.6.0",
81 "jest": "^29.7.0",
scripts/template-oss/index.js
+5
@@ -22,6 +22,11 @@ module.exports = {
22 '.github/settings.yml': false,
23 },
24 },
25 + workspaceModule: {
26 + add: {
27 + '.eslintrc.js': false,
28 + },
29 + },
30 ciVersions: 'latest',
31 latestCiVersion: 18,
32 macCI: false,
src/mdx/code.js
+2 -2
@@ -54,7 +54,7 @@ function Code({className = '', children}) {
54
55 return (
56 <Highlight code={code} language={className.replace(/language-/, '') || 'bash'} theme={themes.github}>
57 - {({className, style, tokens, getLineProps, getTokenProps}) => (
57 + {({className: highlightClassName, style, tokens, getLineProps, getTokenProps}) => (
58 <Box
59 sx={{
60 // Make <pre> adjust to the width of the container
@@ -91,7 +91,7 @@ function Code({className = '', children}) {
91 }}
92 />
93 <Box sx={{m: 0, p: 3, overflowX: 'auto'}}>
94 - <Box as="pre" className={className} tabIndex={0} sx={{m: 0}}>
94 + <Box as="pre" className={highlightClassName} tabIndex={0} sx={{m: 0}}>
95 {tokens.map((line, i) => (
96 <div key={i} {...getLineProps({line, key: i})}>
97 {line.map((token, key) => (
src/mdx/index.js
+2 -2
@@ -2,7 +2,7 @@ import React from 'react'
2 import {Box, Heading, themeGet, Text, Octicon} from '@primer/react'
3 import {withPrefix} from 'gatsby'
4 import styled from 'styled-components'
5 -import {variant} from 'styled-system'
5 +import {variant as styledVariant} from 'styled-system'
6 import {LinkIcon} from '@primer/octicons-react'
7 import textContent from 'react-addons-text-content'
8 import {FULL_HEADER_HEIGHT} from '../constants'
@@ -268,7 +268,7 @@ const StyledNote = styled.div`
268 margin-bottom: 0;
269 }
270
271 - ${variant({
271 + ${styledVariant({
272 variants: {
273 info: {
274 borderColor: 'accent.muted',