Skip to content

Commit 31ffcfe

Browse files
authored
feat: new release strategy (#3020)
## Summary - Keep package minors in sync (`lerna publish --force-publish` when releasing a new minor) - Patch on demand (`lerna publish` fixed mode) - `eslint-config-*` packages are published manually ## Longer Description 1. We used to force publish every package till v3.1, this is due to the unbalanced update frequency among different plugins. In practice, some of them, like the pwa plugin, are quite stable and rarely need updating. So force publishing is overkill now. 2. We have switched to independent mode then, for two major benefits: 1) patching on demand and only on demand 2) eslint config packages needs a more aggressive update strategy in practice, due to the frequent breaking changes in the upstream packages. However, this also causes maintenance burden and confusion to end users. So it's far from ideal. 3. So now we choose to go back fixed versioning. By default lerna would skip untouched packages when publishing a new version. This might be confusing as we think. So to reduce the cognitive load, we still keep each package's minor field in sync. ## Implementation Detail 1. To selectively publish packages, we have to disable `useWorkspaces` in lerna so that these packages can be linked by yarn while being unrecognizable to lerna. 2. Due to an oversight in the old version checking code, version fields in `vue-cli-version-marker` needs to be exact versions, otherwise cli will throw on startup. So we have to publish `vue-cli-version-marker` manually after other package updates. This logic can be removed once all users have been upgrade to 3.2.0+ and we can then add it back to `packages` in `lerna.json` 3. We now use `~/.vuerc` to cache version check results. This also fixes #2956
1 parent d7ff752 commit 31ffcfe

File tree

8 files changed

+703
-626
lines changed

8 files changed

+703
-626
lines changed

lerna.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"npmClient": "yarn",
3-
"useWorkspaces": true,
4-
"version": "independent",
3+
"useWorkspaces": false,
4+
"version": "3.1.5",
55
"packages": [
6-
"packages/@vue/*",
7-
"packages/vue-cli-version-marker"
6+
"packages/@vue/babel-preset-app",
7+
"packages/@vue/cli*"
88
]
99
}

packages/@vue/cli/lib/Creator.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const chalk = require('chalk')
33
const debug = require('debug')
44
const execa = require('execa')
55
const inquirer = require('inquirer')
6+
const semver = require('semver')
67
const EventEmitter = require('events')
78
const Generator = require('./Generator')
89
const cloneDeep = require('lodash.clonedeep')
@@ -105,6 +106,7 @@ module.exports = class Creator extends EventEmitter {
105106

106107
// get latest CLI version
107108
const { latest } = await getVersions()
109+
const latestMinor = `${semver.major(latest)}.${semver.minor(latest)}.0`
108110
// generate package.json with plugin dependencies
109111
const pkg = {
110112
name,
@@ -117,9 +119,13 @@ module.exports = class Creator extends EventEmitter {
117119
if (preset.plugins[dep]._isPreset) {
118120
return
119121
}
122+
123+
// Note: the default creator includes no more than `@vue/cli-*` & `@vue/babel-preset-env`,
124+
// so it is fine to only test `@vue` prefix.
125+
// Other `@vue/*` packages' version may not be in sync with the cli itself.
120126
pkg.devDependencies[dep] = (
121127
preset.plugins[dep].version ||
122-
((/^@vue/.test(dep) && latest[dep]) ? `^${latest[dep]}` : `latest`)
128+
((/^@vue/.test(dep)) ? `^${latestMinor}` : `latest`)
123129
)
124130
})
125131
// write package.json

packages/@vue/cli/lib/options.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const presetSchema = createSchema(joi => joi.object().keys({
1919
}))
2020

2121
const schema = createSchema(joi => joi.object().keys({
22+
latestVersion: joi.string().regex(/^\d+\.\d+\.\d+$/),
23+
lastChecked: joi.date().timestamp(),
2224
packageManager: joi.string().only(['yarn', 'npm']),
2325
useTaobaoRegistry: joi.boolean(),
2426
presets: joi.object().pattern(/^/, presetSchema)

packages/@vue/cli/lib/util/clearConsole.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@ const { clearConsole } = require('@vue/cli-shared-utils')
66
exports.generateTitle = async function (checkUpdate) {
77
const { current, latest } = await getVersions()
88

9-
let title = chalk.bold.blue(`Vue CLI v${current['@vue/cli']}`)
9+
let title = chalk.bold.blue(`Vue CLI v${current}`)
1010

1111
if (process.env.VUE_CLI_TEST) {
1212
title += ' ' + chalk.blue.bold('TEST')
1313
}
1414
if (process.env.VUE_CLI_DEBUG) {
1515
title += ' ' + chalk.magenta.bold('DEBUG')
1616
}
17-
if (checkUpdate && semver.gt(latest['@vue/cli'], current['@vue/cli'])) {
17+
if (checkUpdate && semver.gt(latest, current)) {
1818
if (process.env.VUE_CLI_API_MODE) {
1919
title += chalk.green(` 🌟️ Update available: ${latest}`)
2020
} else {
2121
title += chalk.green(`
22-
┌────────────────────${`─`.repeat(latest['@vue/cli'].length)}──┐
23-
│ Update available: ${latest['@vue/cli']}
24-
└────────────────────${`─`.repeat(latest['@vue/cli'].length)}──┘`)
22+
┌────────────────────${`─`.repeat(latest.length)}──┐
23+
│ Update available: ${latest}
24+
└────────────────────${`─`.repeat(latest.length)}──┘`)
2525
}
2626
}
2727

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
const fs = require('fs-extra')
2-
const path = require('path')
3-
const fsCachePath = path.resolve(__dirname, '.versions')
1+
const semver = require('semver')
2+
const { loadOptions, saveOptions } = require('../options')
43

54
let sessionCached
65

@@ -10,31 +9,26 @@ module.exports = async function getVersions () {
109
}
1110

1211
let latest
13-
const local = require('vue-cli-version-marker').devDependencies
12+
const local = require(`../../package.json`).version
1413
if (process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG) {
1514
return (sessionCached = {
1615
current: local,
1716
latest: local
1817
})
1918
}
2019

21-
if (!fs.existsSync(fsCachePath)) {
22-
// if the cache file doesn't exist, this is likely a fresh install
23-
// then create a cache file with the bundled version map
24-
await fs.writeFile(fsCachePath, JSON.stringify(local))
25-
}
26-
27-
const cached = JSON.parse(await fs.readFile(fsCachePath, 'utf-8'))
28-
const lastChecked = (await fs.stat(fsCachePath)).mtimeMs
20+
const { latestVersion = local, lastChecked = 0 } = loadOptions()
21+
const cached = latestVersion
2922
const daysPassed = (Date.now() - lastChecked) / (60 * 60 * 1000 * 24)
23+
3024
if (daysPassed > 1) {
3125
// if we haven't check for a new version in a day, wait for the check
3226
// before proceeding
33-
latest = await getAndCacheLatestVersions(cached)
27+
latest = await getAndCacheLatestVersion(cached)
3428
} else {
3529
// Otherwise, do a check in the background. If the result was updated,
3630
// it will be used for the next 24 hours.
37-
getAndCacheLatestVersions(cached)
31+
getAndCacheLatestVersion(cached)
3832
latest = cached
3933
}
4034

@@ -46,13 +40,15 @@ module.exports = async function getVersions () {
4640

4741
// fetch the latest version and save it on disk
4842
// so that it is available immediately next time
49-
async function getAndCacheLatestVersions (cached) {
43+
async function getAndCacheLatestVersion (cached) {
5044
const getPackageVersion = require('./getPackageVersion')
5145
const res = await getPackageVersion('vue-cli-version-marker', 'latest')
5246
if (res.statusCode === 200) {
53-
const versions = res.body.devDependencies
54-
await fs.writeFile(fsCachePath, JSON.stringify(versions))
55-
return versions
47+
const { version } = res.body
48+
if (semver.valid(version) && version !== cached) {
49+
saveOptions({ lastestVersion: version, lastChecked: Date.now() })
50+
return version
51+
}
5652
}
5753
return cached
5854
}

packages/@vue/cli/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"shortid": "^2.2.11",
5555
"slash": "^2.0.0",
5656
"validate-npm-package-name": "^3.0.0",
57-
"vue-cli-version-marker": "3.1.2",
5857
"yaml-front-matter": "^3.4.1"
5958
},
6059
"engines": {

scripts/release.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,19 @@ How to do a release:
2727
6. Go to GitHub releases page and publish the release (this is required for
2828
the release to show up in the issue helper)
2929
30+
Note: eslint-config-* packages should be released separately & manually.
31+
3032
*/
3133

3234
process.env.VUE_CLI_RELEASE = true
3335

36+
const fs = require('fs')
37+
const path = require('path')
3438
const execa = require('execa')
3539
const semver = require('semver')
3640
const inquirer = require('inquirer')
3741
const { syncDeps } = require('./syncDeps')
38-
const { buildEditorConfig } = require('./buildEditorConfig')
42+
// const { buildEditorConfig } = require('./buildEditorConfig')
3943

4044
const curVersion = require('../lerna.json').version
4145

@@ -81,21 +85,40 @@ const release = async () => {
8185
})
8286
delete process.env.PREFIX
8387

84-
buildEditorConfig()
88+
// buildEditorConfig()
8589

8690
await execa('git', ['add', '-A'], { stdio: 'inherit' })
8791
await execa('git', ['commit', '-m', 'chore: pre release sync'], { stdio: 'inherit' })
8892
}
8993

90-
await execa(require.resolve('lerna/bin/lerna'), [
94+
const lernaArgs = [
9195
'publish',
92-
'--repo-version',
93-
version,
94-
'--force-publish',
95-
'*'
96-
], { stdio: 'inherit' })
96+
version
97+
]
98+
const releaseType = semver.diff(curVersion, version)
99+
100+
// keep packages' minor version in sync
101+
if (releaseType !== 'patch') {
102+
lernaArgs.push('--force-publish')
103+
}
104+
await execa(require.resolve('lerna/cli'), lernaArgs, { stdio: 'inherit' })
97105

98106
require('./genChangelog')(version)
107+
108+
const packages = JSON.parse(
109+
(await execa(require.resolve('lerna/cli'), ['list', '--json'])).stdout
110+
).filter(p => !p.private)
111+
const versionMarkerPath = path.resolve(__dirname, '../packages/vue-cli-version-marker/package.json')
112+
const versionMarkerPkg = JSON.parse(fs.readFileSync(versionMarkerPath))
113+
114+
versionMarkerPkg.version = semver.inc(versionMarkerPkg.version, releaseType)
115+
versionMarkerPkg.devDependencies = packages.reduce((prev, pkg) => {
116+
prev[pkg.name] = pkg.version
117+
return prev
118+
}, {})
119+
fs.writeFileSync(versionMarkerPath, JSON.stringify(versionMarkerPkg, null, 2))
120+
121+
await execa('npm', ['publish'], { stdio: 'inherit', cwd: path.dirname(versionMarkerPath) })
99122
}
100123

101124
release().catch(err => {

0 commit comments

Comments
 (0)