1 const {posix} = require('path')
2 const fs = require('fs').promises
3 const yaml = require('yaml')
4 const semver = require('semver')
5 const pacote = require('pacote')
6 const extractRelease = require('./extract')
7 const log = require('./log')
8
9 const DOCS_PATH = 'cli'
10
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,
16 default: release.default,
17 type: release.type,
18 children: release.nav,
19 }))
20
21 const index = nav.contents.items.findIndex(n => posix.basename(n.get('url')) === DOCS_PATH)
22 const key = [index, 'variants']
23 const current = nav.getIn(key)
24
25 if (!current) {
26 nav.setIn(key, nav.createNode(variants))
27 } else {
28 for (const variant of variants) {
29 const vIndex = current.items.findIndex(n => n.get('url') === variant.url)
30 if (vIndex === -1) {
31 nav.addIn(key, nav.createNode(variant))
32 } else {
33 nav.setIn([...key, vIndex], nav.createNode(variant))
34 }
35 }
36 }
37
38 return fs.writeFile(path, nav.toString(), 'utf-8')
39 }
40
41 const getCurrentVersions = nav => {
42 // the only place the current versions are stored is in the nav
43 const currentSections = nav.find(s => s.url === `/${DOCS_PATH}`).variants
44
45 const currentVersions = currentSections
46 .map(v => {
47 const version = v.title?.match(/^Version\s(.*?)$/)[1]
48 return version
49 })
50 .sort(semver.compare)
51
52 return {
53 versions: currentVersions,
54 latest: currentVersions[currentVersions.length - 1],
55 }
56 }
57
58 const main = async ({loglevel, releases: rawReleases, useCurrent, navPath, contentPath, prerelease}) => {
59 /* istanbul ignore next */
60 if (loglevel) {
61 log.on(loglevel)
62 }
63
64 const baseNav = await fs.readFile(navPath, 'utf-8')
65 const navData = yaml.parse(baseNav)
66 const navDoc = yaml.parseDocument(baseNav)
67
68 const pack = useCurrent
69 ? getCurrentVersions(navData)
70 : await pacote.packument('npm', {preferOnline: true}).then(p => ({
71 versions: Object.keys(p.versions),
72 latest: p['dist-tags'].latest,
73 }))
74
75 const releaseVersions = rawReleases
76 .map(release => {
77 const major = Number(release.id.replace(/^v/, ''))
78 const range = `>=${major}.0.0-a <${major + 1}.0.0` // include all prereleases
79 const version = semver.parse(semver.maxSatisfying(pack.versions, range))
80
81 return (
82 version && {
83 ...release,
84 version: version.toString(),
85 // the default release is always controlled by the latest dist-tag
86 default: semver.eq(version, pack.latest),
87 prerelease: version.prerelease.length > 0,
88 }
89 )
90 })
91 .filter(Boolean)
92
93 const latestRelease =
94 releaseVersions.find(r => r.default) ??
95 releaseVersions.slice(0).sort((a, b) => semver.compare(b.version, a.version))[0]
96
97 const releases = releaseVersions.map(release => {
98 const type = release.default
99 ? 'latest'
100 : release.prerelease
101 ? 'prerelease'
102 : semver.gt(release.version, latestRelease.version)
103 ? 'current'
104 : 'legacy'
105
106 return {
107 ...release,
108 type,
109 title: `Version ${release.version}`,
110 url: `/${DOCS_PATH}/${release.id}`,
111 urlPrefix: DOCS_PATH,
112 urlPrefixes: [DOCS_PATH, `${DOCS_PATH}-documentation`],
113 }
114 })
115
116 const updates = await Promise.all(
117 releases.map(r => extractRelease(r, {contentPath, baseNav: navData, prerelease})),
118 ).then(r => r.filter(Boolean))
119
120 await updateNav(updates, {nav: navDoc, path: navPath})
121 }
122
123 module.exports = main