Skip to content

Commit ff66ffb

Browse files
authored
feat: add a new experimental option to try out Vue 3.6 beta (#913)
1 parent 651b7a5 commit ff66ffb

File tree

10 files changed

+189
-11
lines changed

10 files changed

+189
-11
lines changed

index.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import generateReadme from './utils/generateReadme'
2121
import getCommand from './utils/getCommand'
2222
import getLanguage from './utils/getLanguage'
2323
import { trimBoilerplate, removeCSSImport, emptyRouterConfig } from './utils/trimBoilerplate'
24+
import applyVueBeta from './utils/applyVueBeta'
25+
import {
26+
inferPackageManager,
27+
getPackageManagerOptions,
28+
type PackageManager,
29+
} from './utils/packageManager'
2430

2531
import cliPackageJson from './package.json' with { type: 'json' }
2632

@@ -45,6 +51,7 @@ const FEATURE_FLAGS = [
4551
'eslint-with-prettier',
4652
'oxlint',
4753
'vite-beta',
54+
'vue-beta',
4855
] as const
4956

5057
const FEATURE_OPTIONS = [
@@ -90,6 +97,10 @@ const EXPERIMENTAL_FEATURE_OPTIONS = [
9097
value: 'vite-beta',
9198
label: language.needsViteBeta.message,
9299
},
100+
{
101+
value: 'vue-beta',
102+
label: language.needsVueBeta.message,
103+
},
93104
] as const
94105

95106
type PromptResult = {
@@ -100,6 +111,7 @@ type PromptResult = {
100111
e2eFramework?: 'cypress' | 'nightwatch' | 'playwright'
101112
experimentFeatures?: (typeof EXPERIMENTAL_FEATURE_OPTIONS)[number]['value'][]
102113
needsBareboneTemplates?: boolean
114+
packageManager?: PackageManager
103115
}
104116

105117
function isValidPackageName(projectName) {
@@ -199,6 +211,8 @@ Available feature flags:
199211
Add Oxfmt for code formatting.
200212
--vite-beta
201213
Use Vite 8 Beta instead of Vite for building the project.
214+
--vue-beta
215+
Use Vue 3.6 Beta. Requires specifying a package manager in interactive mode.
202216
203217
Unstable feature flags:
204218
--tests, --with-tests
@@ -250,6 +264,9 @@ async function init() {
250264

251265
const forceOverwrite = argv.force
252266

267+
// Infer package manager from user agent early so we can use it in prompts
268+
const inferredPackageManager = inferPackageManager()
269+
253270
const result: PromptResult = {
254271
projectName: defaultProjectName,
255272
shouldOverwrite: forceOverwrite,
@@ -359,6 +376,21 @@ async function init() {
359376
required: false,
360377
}),
361378
)
379+
380+
// Ask for package manager if Vue 3.6 beta is selected (needed for correct overrides)
381+
if (result.experimentFeatures.includes('vue-beta')) {
382+
const packageManagerOptions = getPackageManagerOptions(inferredPackageManager).map((pm) => ({
383+
value: pm,
384+
label: pm,
385+
}))
386+
387+
result.packageManager = await unwrapPrompt(
388+
select({
389+
message: `${language.packageManagerSelection.message} ${dim(language.packageManagerSelection.hint)}`,
390+
options: packageManagerOptions,
391+
}),
392+
)
393+
}
362394
}
363395

364396
if (argv.bare) {
@@ -386,6 +418,7 @@ async function init() {
386418
const needsOxfmt = experimentFeatures.includes('oxfmt') || argv['oxfmt']
387419
const needsViteBeta =
388420
experimentFeatures.includes('vite-beta') || argv['vite-beta'] || argv['rolldown-vite'] // keep `rolldown-vite` for backward compatibility
421+
const needsVueBeta = experimentFeatures.includes('vue-beta') || argv['vue-beta']
389422

390423
const { e2eFramework } = result
391424
const needsCypress = argv.cypress || argv.tests || e2eFramework === 'cypress'
@@ -677,16 +710,16 @@ async function init() {
677710
}
678711
}
679712

680-
// Instructions:
681-
// Supported package managers: pnpm > yarn > bun > npm
682-
const userAgent = process.env.npm_config_user_agent ?? ''
683-
const packageManager = /pnpm/.test(userAgent)
684-
? 'pnpm'
685-
: /yarn/.test(userAgent)
686-
? 'yarn'
687-
: /bun/.test(userAgent)
688-
? 'bun'
689-
: 'npm'
713+
// Use the package manager selected by user for Vue 3.6 beta, or inferred from user agent
714+
const packageManager = result.packageManager ?? inferredPackageManager
715+
716+
// Apply Vue 3.6 Beta overrides if the feature is enabled
717+
if (needsVueBeta) {
718+
const pkgPath = path.resolve(root, 'package.json')
719+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
720+
applyVueBeta(root, packageManager, pkg)
721+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
722+
}
690723

691724
// README generation
692725
fs.writeFileSync(

locales/en-US.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,19 @@
6969
"needsViteBeta": {
7070
"message": "Vite 8 (beta)"
7171
},
72+
"needsVueBeta": {
73+
"message": "Vue 3.6 (beta)"
74+
},
7275
"needsOxfmt": {
7376
"message": "Replace Prettier with Oxfmt"
7477
},
7578
"needsBareboneTemplates": {
7679
"message": "Skip all example code and start with a blank Vue project?"
7780
},
81+
"packageManagerSelection": {
82+
"message": "Which package manager will you use?",
83+
"hint": "(Vue 3.6 beta requires version overrides that differ per package manager)"
84+
},
7885
"errors": {
7986
"operationCancelled": "Operation cancelled"
8087
},

locales/fr-FR.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,19 @@
6969
"needsViteBeta": {
7070
"message": "Vite 8 (beta)"
7171
},
72+
"needsVueBeta": {
73+
"message": "Vue 3.6 (beta)"
74+
},
7275
"needsOxfmt": {
7376
"message": "Remplacer Prettier par Oxfmt"
7477
},
7578
"needsBareboneTemplates": {
7679
"message": "Ignorer tout le code d'exemple et commencer avec un projet Vue vierge\u00a0?"
7780
},
81+
"packageManagerSelection": {
82+
"message": "Quel gestionnaire de paquets allez-vous utiliser\u00a0?",
83+
"hint": "(Vue 3.6 beta nécessite des surcharges de version spécifiques au gestionnaire de paquets)"
84+
},
7885
"errors": {
7986
"operationCancelled": "Operation annulée"
8087
},

locales/tr-TR.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,19 @@
6969
"needsViteBeta": {
7070
"message": "Vite 8 (beta)"
7171
},
72+
"needsVueBeta": {
73+
"message": "Vue 3.6 (beta)"
74+
},
7275
"needsOxfmt": {
7376
"message": "Prettier'ı Oxfmt ile değiştir"
7477
},
7578
"needsBareboneTemplates": {
7679
"message": "Tüm örnek kodları atlayıp boş bir Vue projesi ile başlansın mı?"
7780
},
81+
"packageManagerSelection": {
82+
"message": "Hangi paket yöneticisini kullanacaksınız?",
83+
"hint": "(Vue 3.6 beta, paket yöneticisine özgü sürüm geçersiz kılmaları gerektirir)"
84+
},
7885
"errors": {
7986
"operationCancelled": "İşlem iptal edildi"
8087
},

locales/zh-Hans.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,19 @@
6969
"needsViteBeta": {
7070
"message": "Vite 8(测试版)"
7171
},
72+
"needsVueBeta": {
73+
"message": "Vue 3.6(测试版)"
74+
},
7275
"needsOxfmt": {
7376
"message": "使用 Oxfmt 替代 Prettier"
7477
},
7578
"needsBareboneTemplates": {
7679
"message": "跳过所有示例代码,创建一个空白的 Vue 项目?"
7780
},
81+
"packageManagerSelection": {
82+
"message": "项目将使用哪个包管理器?",
83+
"hint": "(需要根据包管理器生成对应的 Vue 3.6 测试版配置)"
84+
},
7885
"errors": {
7986
"operationCancelled": "操作取消"
8087
},

locales/zh-Hant.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,19 @@
6969
"needsViteBeta": {
7070
"message": "Vite 8(測試版)"
7171
},
72+
"needsVueBeta": {
73+
"message": "Vue 3.6(測試版)"
74+
},
7275
"needsOxfmt": {
7376
"message": "使用 Oxfmt 替代 Prettier"
7477
},
7578
"needsBareboneTemplates": {
7679
"message": "跳過所有範例程式碼,建立一個空白的 Vue 專案?"
7780
},
81+
"packageManagerSelection": {
82+
"message": "專案將使用哪個套件管理器?",
83+
"hint": "(需要根據套件管理器生成對應的 Vue 3.6 測試版配置)"
84+
},
7885
"errors": {
7986
"operationCancelled": "操作取消"
8087
},

utils/applyVueBeta.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as fs from 'node:fs'
2+
import * as path from 'node:path'
3+
import type { PackageManager } from './packageManager'
4+
5+
// Core Vue packages that need to be overridden
6+
// Based on https://github.com/haoqunjiang/install-vue/blob/main/src/constants.ts
7+
const CORE_VUE_PACKAGES = [
8+
'vue',
9+
'@vue/compiler-core',
10+
'@vue/compiler-dom',
11+
'@vue/compiler-sfc',
12+
'@vue/compiler-ssr',
13+
'@vue/compiler-vapor',
14+
'@vue/reactivity',
15+
'@vue/runtime-core',
16+
'@vue/runtime-dom',
17+
'@vue/runtime-vapor',
18+
'@vue/server-renderer',
19+
'@vue/shared',
20+
'@vue/compat',
21+
] as const
22+
23+
function generateOverridesMap(): Record<string, string> {
24+
return Object.fromEntries(CORE_VUE_PACKAGES.map((name) => [name, 'beta']))
25+
}
26+
27+
/**
28+
* Apply Vue 3.6 beta overrides to the project based on the package manager.
29+
* Different package managers have different mechanisms for version overrides:
30+
* - npm/bun: uses `overrides` field in package.json
31+
* - yarn: uses `resolutions` field in package.json
32+
* - pnpm: uses `overrides` and `peerDependencyRules` in pnpm-workspace.yaml
33+
*/
34+
export default function applyVueBeta(
35+
root: string,
36+
packageManager: PackageManager,
37+
pkg: Record<string, any>,
38+
): void {
39+
const overrides = generateOverridesMap()
40+
41+
if (packageManager === 'npm' || packageManager === 'bun') {
42+
// https://github.com/npm/rfcs/blob/main/accepted/0036-overrides.md
43+
// NPM overrides require exact versions for resolution, but "beta" dist-tag works too
44+
// Bun also supports the same `overrides` field
45+
pkg.overrides = {
46+
...pkg.overrides,
47+
...overrides,
48+
}
49+
50+
// NPM requires direct dependencies to be rewritten to match overrides
51+
for (const dependencyName of CORE_VUE_PACKAGES) {
52+
for (const dependencyType of ['dependencies', 'devDependencies', 'optionalDependencies']) {
53+
if (pkg[dependencyType]?.[dependencyName]) {
54+
pkg[dependencyType][dependencyName] = overrides[dependencyName]
55+
}
56+
}
57+
}
58+
} else if (packageManager === 'yarn') {
59+
// https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-selective-versions-resolutions.md
60+
pkg.resolutions = {
61+
...pkg.resolutions,
62+
...overrides,
63+
}
64+
} else if (packageManager === 'pnpm') {
65+
// pnpm now recommends putting overrides in pnpm-workspace.yaml
66+
// https://pnpm.io/pnpm-workspace_yaml
67+
const yamlContent = `overrides:
68+
${Object.entries(overrides)
69+
.map(([key, value]) => ` '${key}': '${value}'`)
70+
.join('\n')}
71+
72+
peerDependencyRules:
73+
allowAny:
74+
- 'vue'
75+
`
76+
77+
fs.writeFileSync(path.resolve(root, 'pnpm-workspace.yaml'), yamlContent, 'utf-8')
78+
}
79+
}

utils/getCommand.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
export default function getCommand(packageManager: string, scriptName: string, args?: string) {
1+
import type { PackageManager } from './packageManager'
2+
3+
export default function getCommand(
4+
packageManager: PackageManager,
5+
scriptName: string,
6+
args?: string,
7+
) {
28
if (scriptName === 'install') {
39
return packageManager === 'yarn' ? 'yarn' : `${packageManager} install`
410
}

utils/getLanguage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ interface Language {
3939
needsExperimental: LanguageItem
4040
needsExperimentalFeatures: LanguageItem
4141
needsViteBeta: LanguageItem
42+
needsVueBeta: LanguageItem
4243
needsOxfmt: LanguageItem
4344
needsBareboneTemplates: LanguageItem
45+
packageManagerSelection: LanguageItem
4446
errors: {
4547
operationCancelled: string
4648
}

utils/packageManager.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun'
2+
3+
/**
4+
* Infers the package manager from the user agent string.
5+
* Falls back to npm if unable to detect.
6+
*/
7+
export function inferPackageManager(): PackageManager {
8+
const userAgent = process.env.npm_config_user_agent ?? ''
9+
10+
if (/pnpm/.test(userAgent)) return 'pnpm'
11+
if (/yarn/.test(userAgent)) return 'yarn'
12+
if (/bun/.test(userAgent)) return 'bun'
13+
14+
return 'npm'
15+
}
16+
17+
/**
18+
* Creates an ordered list of package managers with the preferred one first.
19+
*/
20+
export function getPackageManagerOptions(preferred: PackageManager) {
21+
const all: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun']
22+
return [preferred, ...all.filter((pm) => pm !== preferred)]
23+
}

0 commit comments

Comments
 (0)