fix: always rebuild cli source on command
Luke Karrys committed
Nov 9, 2022 at 23:37 UTC
7416290a2e3ec7e2a9bd59b875fe6511b5e2f232
6 files changed
+112
-171
cli/bin/build.js
-1
@@ -3,7 +3,6 @@ const build = require('../lib/build.js')
3
4
build({
5
loglevel: process.argv.includes('--debug') || process.env.CI ? 'verbose' : 'info',
6
- force: process.argv.includes('--force'),
6
prerelease: false,
7
contentPath: resolve(__dirname, '..', '..', 'content', 'cli'),
8
releasesPath: resolve(__dirname, '..', 'releases.json'),
cli/lib/build.js
+20
-37
@@ -3,7 +3,6 @@ const fs = require('fs').promises
3
const yaml = require('yaml')
4
const semver = require('semver')
5
const pacote = require('pacote')
6
-const gh = require('./gh')
6
const extractRelease = require('./extract')
7
const log = require('./log')
8
@@ -39,20 +38,8 @@ const updateNav = async (updates, { nav, path }) => {
38
return fs.writeFile(path, nav.toString(), 'utf-8')
39
}
40
42
-const updateReleases = async (updates, path) => {
43
- const data = JSON.parse(await fs.readFile(path, 'utf-8'))
44
-
45
- for (const release of updates) {
46
- const index = data.findIndex((item) => item.id === release.id)
47
- data[index].resolved = release.resolved
48
- }
49
-
50
- return fs.writeFile(path, JSON.stringify(data, null, 2) + '\n', 'utf-8')
51
-}
52
-
41
const main = async ({
42
loglevel,
55
- force,
43
releasesPath,
44
navPath,
45
contentPath,
@@ -63,29 +50,28 @@ const main = async ({
50
log.on(loglevel)
51
}
52
66
- const rawReleases = require(releasesPath)
67
-
68
- const releaseManifests = await Promise.all(rawReleases.map(async release => {
69
- const manifest = await pacote.manifest(`${gh.owner}@${release.spec}`, {
70
- preferOnline: true,
71
- })
72
- // the default release is always controlled by the latest dist-tag
73
- release.default = release.spec === 'latest'
74
- release.manifest = manifest
75
- release.version = manifest.version
76
- const sVersion = semver.parse(release.version)
77
- release.semver = sVersion
78
- release.prerelease = sVersion.prerelease.length > 0
79
- return release
53
+ const pack = await pacote.packument('npm', { preferOnline: true }).then(p => ({
54
+ versions: Object.keys(p.versions),
55
+ latest: p['dist-tags'].latest,
56
}))
57
82
- const latestRelease = releaseManifests.find(r => r.spec === 'latest')
58
+ const releaseVersions = require(releasesPath).map(release => {
59
+ const major = Number(release.id.replace(/^v/, ''))
60
+ const range = `>=${major}.0.0-a <${major + 1}.0.0` // include all prereleases
61
+ const version = semver.parse(semver.maxSatisfying(pack.versions, range))
62
84
- if (!latestRelease) {
85
- throw new Error(`One of the CLI releases must have \`spec: 'latest'\``)
86
- }
63
+ return {
64
+ ...release,
65
+ version: version.toString(),
66
+ // the default release is always controlled by the latest dist-tag
67
+ default: semver.eq(version, pack.latest),
68
+ prerelease: version.prerelease.length > 0,
69
+ }
70
+ })
71
+
72
+ const latestRelease = releaseVersions.find(r => r.default)
73
88
- const releases = releaseManifests.map((release) => {
74
+ const releases = releaseVersions.map((release) => {
75
const type = release.default ? 'Latest Release'
76
: release.prerelease ? 'Prerelease'
77
: semver.gt(release.version, latestRelease.version) ? 'Current Release'
@@ -104,14 +90,11 @@ const main = async ({
90
91
const updates = await Promise.all(
92
releases.map((r) =>
107
- extractRelease(r, { contentPath, baseNav: yaml.parse(baseNav), force, prerelease })
93
+ extractRelease(r, { contentPath, baseNav: yaml.parse(baseNav), prerelease })
94
)
95
).then((r) => r.filter(Boolean))
96
111
- await Promise.all([
112
- updateNav(updates, { nav: yaml.parseDocument(baseNav), path: navPath }),
113
- updateReleases(updates, releasesPath),
114
- ])
97
+ await updateNav(updates, { nav: yaml.parseDocument(baseNav), path: navPath })
98
}
99
100
module.exports = main
cli/lib/extract.js
+10
-45
@@ -33,16 +33,12 @@ const unpackTarball = async ({ release, cwd, dir }) => {
33
},
34
})
35
36
- await pacote.tarball.stream(
37
- release.spec,
38
- (stream) =>
39
- new Promise((res, rej) => {
40
- stream.on('end', res)
41
- stream.on('error', rej)
42
- stream.pipe(extract())
43
- }),
44
- { resolved: release.resolved }
45
- )
36
+ await pacote.tarball.stream(`npm@${release.version}`, (stream) =>
37
+ new Promise((res, rej) => {
38
+ stream.on('end', res)
39
+ stream.on('error', rej)
40
+ stream.pipe(extract())
41
+ }))
42
43
return result
44
}
@@ -122,47 +118,16 @@ const writeChangelog = async ({ release, nav, cwd, srcPath, contentPath }) => {
118
})
119
}
120
125
-const resolveRelease = async (
126
- { resolved: current, ...release },
127
- { force, prerelease }
121
+const unpackRelease = async (
122
+ release,
123
+ { contentPath, baseNav, prerelease = false }
124
) => {
125
if (release.prerelease && !prerelease) {
126
log.info(`Skipping ${release.id} due to prerelease ${release.version}`)
131
- return null
132
- }
133
-
134
- // The legacy v6 release has updated docs in GitHub that were never
135
- // published. So in this case we skip cloning the repo with pacote
136
- // and get the latest commit on the branch. Later we will use the
137
- // GitHub api to fetch just the docs files we need since that is
138
- // much faster than cloning and preparing with pacote
139
- if (release.useBranch) {
140
- release.resolved = await gh.getLatestSha(release.branch)
141
- } else {
142
- release.resolved = release.manifest._resolved
143
- release.spec = release.manifest._from
144
- }
145
-
146
- log.info(release.id, release.version, release.resolved)
147
-
148
- if (release.resolved === current && !force) {
149
- log.info(`Skipping ${release.id} due to resolved fields matching`)
150
- return null
151
- }
152
-
153
- return release
154
-}
155
-
156
-const unpackRelease = async (
157
- _release,
158
- { contentPath, baseNav, force = false, prerelease = false }
159
-) => {
160
- const release = await resolveRelease(_release, { force, prerelease })
161
- if (!release) {
127
return
128
}
129
165
- log.verbose(release)
130
+ log.info(release.id, release)
131
132
const cwd = join(contentPath, release.id)
133
await fs
cli/lib/gh.js
-12
@@ -20,17 +20,6 @@ const getFile = async ({ sha, ref, path }) => {
20
return Buffer.from(data.content, data.encoding)
21
}
22
23
-const getLatestSha = async (ref) => {
24
- const {
25
- data: [commit],
26
- } = await octokit.repos.listCommits({
27
- ...opts,
28
- sha: ref,
29
- per_page: 1,
30
- })
31
- return commit.sha
32
-}
33
-
23
const getAllFiles = async (sha) => {
24
const {
25
data: { tree },
@@ -79,7 +68,6 @@ const pathExists = async (ref, path) => {
68
module.exports = {
69
octokit,
70
getFile,
82
- getLatestSha,
71
getAllFiles,
72
getDirectory,
73
pathExists,
cli/releases.json
+4
-12
@@ -2,26 +2,18 @@
2
{
3
"id": "v6",
4
"branch": "release/v6",
5
- "spec": "^6",
6
- "useBranch": true,
7
- "resolved": "898c0496406e29865b5ae736ed541ca41895e484"
5
+ "useBranch": true
6
},
7
{
8
"id": "v7",
11
- "branch": "release/v7",
12
- "spec": "^7",
13
- "resolved": "https://registry.npmjs.org/npm/-/npm-7.24.2.tgz"
9
+ "branch": "release/v7"
10
},
11
{
12
"id": "v8",
17
- "branch": "release/v8",
18
- "spec": "^8",
19
- "resolved": "https://registry.npmjs.org/npm/-/npm-8.19.3.tgz"
13
+ "branch": "release/v8"
14
},
15
{
16
"id": "v9",
23
- "branch": "latest",
24
- "spec": "latest",
25
- "resolved": "https://registry.npmjs.org/npm/-/npm-9.1.1.tgz"
17
+ "branch": "latest"
18
}
19
]
cli/test/index.js
+78
-64
@@ -12,11 +12,27 @@ const navPath = resolve(
12
'nav.yml'
13
)
14
15
-const getReleases = () => {
16
- return JSON.parse(JSON.stringify(require('../releases.json')))
17
-}
18
-
19
-const mockBuild = async ({ releases, testdir: testdirOpts }) => {
15
+const getReleases = () => [
16
+ {
17
+ id: 'v6',
18
+ branch: 'release/v6',
19
+ useBranch: true,
20
+ },
21
+ {
22
+ id: 'v7',
23
+ branch: 'release/v7',
24
+ },
25
+ {
26
+ id: 'v8',
27
+ branch: 'release/v8',
28
+ },
29
+ {
30
+ id: 'v9',
31
+ branch: 'latest',
32
+ },
33
+]
34
+
35
+const mockBuild = async ({ releases, packument = {}, testdir: testdirOpts }) => {
36
const testdir = t.testdir({
37
'releases.json': JSON.stringify(releases),
38
'nav.yml': await fs.readFile(navPath, 'utf-8'),
@@ -24,51 +40,58 @@ const mockBuild = async ({ releases, testdir: testdirOpts }) => {
40
...testdirOpts,
41
})
42
43
+ if (!packument.versions) {
44
+ packument.versions = releases.map(r => {
45
+ // real tarball requests are made for these verions
46
+ // so by default they all need to exist
47
+ switch (r.id.slice(1)) {
48
+ case '6':
49
+ return '6.14.17'
50
+ case '7':
51
+ return '7.24.2'
52
+ case '8':
53
+ return '8.19.3'
54
+ case '9':
55
+ return '9.0.0'
56
+ }
57
+ })
58
+ }
59
+
60
+ if (!packument.latest) {
61
+ packument.latest = packument.versions[packument.versions.length - 1]
62
+ }
63
+
64
const build = t.mock('../lib/build', {
65
pacote: {
66
...pacote,
30
- manifest: async (spec) => {
31
- let version = spec.split('@')[1]
32
- const release = releases.find(r => r.spec === version)
33
-
34
- if (version.match(/^\^\d+$/)) {
35
- version = version.slice(1) + '.0.0'
36
- } else if (version === 'latest') {
37
- version = release.id.slice(1) + '.0.0'
38
- } else if (version.startsWith('next-')) {
39
- version = version.replace('next-', '') + '.0.0-pre.4'
40
- }
41
-
67
+ packument: async () => {
68
return {
43
- version,
44
- _resolved: release.resolved,
45
- _from: spec,
69
+ 'dist-tags': {
70
+ latest: packument.latest,
71
+ },
72
+ versions: packument.versions.reduce((acc, v) => {
73
+ acc[v] = null
74
+ return acc
75
+ }, {}),
76
}
77
},
78
},
79
})
80
51
- return (opts) => build({
52
- contentPath: join(testdir, 'content'),
53
- releasesPath: join(testdir, 'releases.json'),
54
- navPath: join(testdir, 'nav.yml'),
55
- ...opts,
56
- })
81
+ return {
82
+ testdir,
83
+ build: (opts) => build({
84
+ contentPath: join(testdir, 'content'),
85
+ releasesPath: join(testdir, 'releases.json'),
86
+ navPath: join(testdir, 'nav.yml'),
87
+ ...opts,
88
+ }),
89
+ }
90
}
91
59
-t.test('builds successfully', async (t) => {
60
- const releases = getReleases()
61
- const build = await mockBuild({ releases })
62
-
63
- await build({
64
- force: true,
65
- prerelease: true,
66
- })
67
-})
68
-
69
-t.test('no force', async (t) => {
92
+t.test('basic', async (t) => {
93
const releases = getReleases()
71
- const build = await mockBuild({
94
+ const { build, testdir } = await mockBuild({
95
releases,
96
testdir: {
97
'nav.yml': '- title: cli\n url: /cli',
@@ -76,45 +99,39 @@ t.test('no force', async (t) => {
99
})
100
101
await build()
102
+ t.strictSame(await fs.readdir(join(testdir, 'content')), releases.map(r => r.id))
103
})
104
105
t.test('no default release', async (t) => {
82
- const releases = getReleases().filter(r => r.spec !== 'latest')
83
- const build = await mockBuild({
106
+ const releases = getReleases()
107
+ const { build } = await mockBuild({
108
releases,
85
- testdir: {
86
- 'nav.yml': '- title: cli\n url: /cli',
87
- },
109
+ packument: { latest: '9999.99999.99999' },
110
})
111
112
await t.rejects(() => build())
113
})
114
93
-t.test('prerelease', async (t) => {
115
+t.test('prereleases', async (t) => {
116
const releases = getReleases()
95
- releases[1].spec = 'next-8'
96
- releases[2].spec = '^8'
97
-
98
- const build = await mockBuild({
117
+ const { build, testdir } = await mockBuild({
118
releases,
100
- testdir: {
101
- 'nav.yml': '- title: cli\n url: /cli',
102
- },
119
+ packument: { versions: ['6.14.17', '7.24.2', '8.19.3', '9.0.0-pre.2'], latest: '8.19.3' },
120
})
121
105
- await build()
122
+ await build({ prerelease: false })
123
+ const expectedReleases = releases.map(r => r.id).filter(r => r !== 'v9')
124
+ t.strictSame(await fs.readdir(join(testdir, 'content')), expectedReleases)
125
+
126
+ await build({ prerelease: true })
127
+ t.strictSame(await fs.readdir(join(testdir, 'content')), releases.map(r => r.id))
128
})
129
130
t.test('earlier release is latest', async (t) => {
131
const releases = getReleases()
110
- releases[1].spec = 'latest'
111
- releases[2].spec = '^8'
112
-
113
- const build = await mockBuild({
132
+ const { build } = await mockBuild({
133
releases,
115
- testdir: {
116
- 'nav.yml': '- title: cli\n url: /cli',
117
- },
134
+ packument: { latest: '8.19.3' },
135
})
136
137
await build()
@@ -122,15 +139,12 @@ t.test('earlier release is latest', async (t) => {
139
140
t.test('add variant to nav', async (t) => {
141
const releases = getReleases()
125
- releases[1].spec = 'latest'
126
- releases[2].spec = '^8'
127
-
128
- const build = await mockBuild({
142
+ const { build } = await mockBuild({
143
releases,
144
testdir: {
145
'nav.yml': '- title: cli\n url: /cli\n variants:\n - url: /cli/v0',
146
},
147
})
148
135
- await build({ force: true })
149
+ await build()
150
})