Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import generateReadme from './utils/generateReadme'
import getCommand from './utils/getCommand'
import getLanguage from './utils/getLanguage'
import { trimBoilerplate, removeCSSImport, emptyRouterConfig } from './utils/trimBoilerplate'
import applyVueBeta from './utils/applyVueBeta'
import {
inferPackageManager,
getPackageManagerOptions,
type PackageManager,
} from './utils/packageManager'

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

Expand All @@ -45,6 +51,7 @@ const FEATURE_FLAGS = [
'eslint-with-prettier',
'oxlint',
'vite-beta',
'vue-beta',
] as const

const FEATURE_OPTIONS = [
Expand Down Expand Up @@ -90,6 +97,10 @@ const EXPERIMENTAL_FEATURE_OPTIONS = [
value: 'vite-beta',
label: language.needsViteBeta.message,
},
{
value: 'vue-beta',
label: language.needsVueBeta.message,
},
] as const

type PromptResult = {
Expand All @@ -100,6 +111,7 @@ type PromptResult = {
e2eFramework?: 'cypress' | 'nightwatch' | 'playwright'
experimentFeatures?: (typeof EXPERIMENTAL_FEATURE_OPTIONS)[number]['value'][]
needsBareboneTemplates?: boolean
packageManager?: PackageManager
}

function isValidPackageName(projectName) {
Expand Down Expand Up @@ -199,6 +211,8 @@ Available feature flags:
Add Oxfmt for code formatting.
--vite-beta
Use Vite 8 Beta instead of Vite for building the project.
--vue-beta
Use Vue 3.6 Beta. Requires specifying a package manager in interactive mode.

Unstable feature flags:
--tests, --with-tests
Expand Down Expand Up @@ -250,6 +264,9 @@ async function init() {

const forceOverwrite = argv.force

// Infer package manager from user agent early so we can use it in prompts
const inferredPackageManager = inferPackageManager()

const result: PromptResult = {
projectName: defaultProjectName,
shouldOverwrite: forceOverwrite,
Expand Down Expand Up @@ -359,6 +376,21 @@ async function init() {
required: false,
}),
)

// Ask for package manager if Vue 3.6 beta is selected (needed for correct overrides)
if (result.experimentFeatures.includes('vue-beta')) {
const packageManagerOptions = getPackageManagerOptions(inferredPackageManager).map((pm) => ({
value: pm,
label: pm,
}))

result.packageManager = await unwrapPrompt(
select({
message: `${language.packageManagerSelection.message} ${dim(language.packageManagerSelection.hint)}`,
options: packageManagerOptions,
}),
)
}
}

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

const { e2eFramework } = result
const needsCypress = argv.cypress || argv.tests || e2eFramework === 'cypress'
Expand Down Expand Up @@ -672,16 +705,16 @@ async function init() {
}
}

// Instructions:
// Supported package managers: pnpm > yarn > bun > npm
const userAgent = process.env.npm_config_user_agent ?? ''
const packageManager = /pnpm/.test(userAgent)
? 'pnpm'
: /yarn/.test(userAgent)
? 'yarn'
: /bun/.test(userAgent)
? 'bun'
: 'npm'
// Use the package manager selected by user for Vue 3.6 beta, or inferred from user agent
const packageManager = result.packageManager ?? inferredPackageManager

// Apply Vue 3.6 Beta overrides if the feature is enabled
if (needsVueBeta) {
const pkgPath = path.resolve(root, 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
applyVueBeta(root, packageManager, pkg)
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
}

// README generation
fs.writeFileSync(
Expand Down
7 changes: 7 additions & 0 deletions locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@
"needsViteBeta": {
"message": "Vite 8 (beta)"
},
"needsVueBeta": {
"message": "Vue 3.6 (beta)"
},
"needsOxfmt": {
"message": "Replace Prettier with Oxfmt"
},
"needsBareboneTemplates": {
"message": "Skip all example code and start with a blank Vue project?"
},
"packageManagerSelection": {
"message": "Which package manager will you use?",
"hint": "(Vue 3.6 beta requires version overrides that differ per package manager)"
},
"errors": {
"operationCancelled": "Operation cancelled"
},
Expand Down
7 changes: 7 additions & 0 deletions locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@
"needsViteBeta": {
"message": "Vite 8 (beta)"
},
"needsVueBeta": {
"message": "Vue 3.6 (beta)"
},
"needsOxfmt": {
"message": "Remplacer Prettier par Oxfmt"
},
"needsBareboneTemplates": {
"message": "Ignorer tout le code d'exemple et commencer avec un projet Vue vierge\u00a0?"
},
"packageManagerSelection": {
"message": "Quel gestionnaire de paquets allez-vous utiliser\u00a0?",
"hint": "(Vue 3.6 beta nécessite des surcharges de version spécifiques au gestionnaire de paquets)"
},
"errors": {
"operationCancelled": "Operation annulée"
},
Expand Down
7 changes: 7 additions & 0 deletions locales/tr-TR.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@
"needsViteBeta": {
"message": "Vite 8 (beta)"
},
"needsVueBeta": {
"message": "Vue 3.6 (beta)"
},
"needsOxfmt": {
"message": "Prettier'ı Oxfmt ile değiştir"
},
"needsBareboneTemplates": {
"message": "Tüm örnek kodları atlayıp boş bir Vue projesi ile başlansın mı?"
},
"packageManagerSelection": {
"message": "Hangi paket yöneticisini kullanacaksınız?",
"hint": "(Vue 3.6 beta, paket yöneticisine özgü sürüm geçersiz kılmaları gerektirir)"
},
"errors": {
"operationCancelled": "İşlem iptal edildi"
},
Expand Down
7 changes: 7 additions & 0 deletions locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@
"needsViteBeta": {
"message": "Vite 8(测试版)"
},
"needsVueBeta": {
"message": "Vue 3.6(测试版)"
},
"needsOxfmt": {
"message": "使用 Oxfmt 替代 Prettier"
},
"needsBareboneTemplates": {
"message": "跳过所有示例代码,创建一个空白的 Vue 项目?"
},
"packageManagerSelection": {
"message": "项目将使用哪个包管理器?",
"hint": "(需要根据包管理器生成对应的 Vue 3.6 测试版配置)"
},
"errors": {
"operationCancelled": "操作取消"
},
Expand Down
7 changes: 7 additions & 0 deletions locales/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@
"needsViteBeta": {
"message": "Vite 8(測試版)"
},
"needsVueBeta": {
"message": "Vue 3.6(測試版)"
},
"needsOxfmt": {
"message": "使用 Oxfmt 替代 Prettier"
},
"needsBareboneTemplates": {
"message": "跳過所有範例程式碼,建立一個空白的 Vue 專案?"
},
"packageManagerSelection": {
"message": "專案將使用哪個套件管理器?",
"hint": "(需要根據套件管理器生成對應的 Vue 3.6 測試版配置)"
},
"errors": {
"operationCancelled": "操作取消"
},
Expand Down
79 changes: 79 additions & 0 deletions utils/applyVueBeta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as fs from 'node:fs'
import * as path from 'node:path'
import type { PackageManager } from './packageManager'

// Core Vue packages that need to be overridden
// Based on https://github.com/haoqunjiang/install-vue/blob/main/src/constants.ts
const CORE_VUE_PACKAGES = [
'vue',
'@vue/compiler-core',
'@vue/compiler-dom',
'@vue/compiler-sfc',
'@vue/compiler-ssr',
'@vue/compiler-vapor',
'@vue/reactivity',
'@vue/runtime-core',
'@vue/runtime-dom',
'@vue/runtime-vapor',
'@vue/server-renderer',
'@vue/shared',
'@vue/compat',
] as const

function generateOverridesMap(): Record<string, string> {
return Object.fromEntries(CORE_VUE_PACKAGES.map((name) => [name, 'beta']))
}

/**
* Apply Vue 3.6 beta overrides to the project based on the package manager.
* Different package managers have different mechanisms for version overrides:
* - npm/bun: uses `overrides` field in package.json
* - yarn: uses `resolutions` field in package.json
* - pnpm: uses `overrides` and `peerDependencyRules` in pnpm-workspace.yaml
*/
export default function applyVueBeta(
root: string,
packageManager: PackageManager,
pkg: Record<string, any>,
): void {
const overrides = generateOverridesMap()

if (packageManager === 'npm' || packageManager === 'bun') {
// https://github.com/npm/rfcs/blob/main/accepted/0036-overrides.md
// NPM overrides require exact versions for resolution, but "beta" dist-tag works too
// Bun also supports the same `overrides` field
pkg.overrides = {
...pkg.overrides,
...overrides,
}

// NPM requires direct dependencies to be rewritten to match overrides
for (const dependencyName of CORE_VUE_PACKAGES) {
for (const dependencyType of ['dependencies', 'devDependencies', 'optionalDependencies']) {
if (pkg[dependencyType]?.[dependencyName]) {
pkg[dependencyType][dependencyName] = overrides[dependencyName]
}
}
}
} else if (packageManager === 'yarn') {
// https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-selective-versions-resolutions.md
pkg.resolutions = {
...pkg.resolutions,
...overrides,
}
} else if (packageManager === 'pnpm') {
// pnpm now recommends putting overrides in pnpm-workspace.yaml
// https://pnpm.io/pnpm-workspace_yaml
const yamlContent = `overrides:
${Object.entries(overrides)
.map(([key, value]) => ` '${key}': '${value}'`)
.join('\n')}

peerDependencyRules:
allowAny:
- 'vue'
`

fs.writeFileSync(path.resolve(root, 'pnpm-workspace.yaml'), yamlContent, 'utf-8')
}
}
8 changes: 7 additions & 1 deletion utils/getCommand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export default function getCommand(packageManager: string, scriptName: string, args?: string) {
import type { PackageManager } from './packageManager'

export default function getCommand(
packageManager: PackageManager,
scriptName: string,
args?: string,
) {
if (scriptName === 'install') {
return packageManager === 'yarn' ? 'yarn' : `${packageManager} install`
}
Expand Down
2 changes: 2 additions & 0 deletions utils/getLanguage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ interface Language {
needsExperimental: LanguageItem
needsExperimentalFeatures: LanguageItem
needsViteBeta: LanguageItem
needsVueBeta: LanguageItem
needsOxfmt: LanguageItem
needsBareboneTemplates: LanguageItem
packageManagerSelection: LanguageItem
errors: {
operationCancelled: string
}
Expand Down
23 changes: 23 additions & 0 deletions utils/packageManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun'

/**
* Infers the package manager from the user agent string.
* Falls back to npm if unable to detect.
*/
export function inferPackageManager(): PackageManager {
const userAgent = process.env.npm_config_user_agent ?? ''

if (/pnpm/.test(userAgent)) return 'pnpm'
if (/yarn/.test(userAgent)) return 'yarn'
if (/bun/.test(userAgent)) return 'bun'

return 'npm'
}

/**
* Creates an ordered list of package managers with the preferred one first.
*/
export function getPackageManagerOptions(preferred: PackageManager) {
const all: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun']
return [preferred, ...all.filter((pm) => pm !== preferred)]
}