diff --git a/LICENSE b/LICENSE index 636ecdd30..27ac08fe2 100644 --- a/LICENSE +++ b/LICENSE @@ -128,10 +128,44 @@ For more information, please see ## Licenses of bundled dependencies The published create-vue artifact additionally contains code with the following licenses: -MIT, Apache-2.0 +MIT, Apache-2.0, ISC ## Bundled dependencies +## @clack/core + +License: MIT +By: Nate Moore +Repository: git+https://github.com/natemoo-re/clack.git + +> MIT License +> +> Copyright (c) Nate Moore +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> + +## @clack/prompts + +License: MIT +By: Nate Moore +Repository: git+https://github.com/natemoo-re/clack.git + +> MIT License +> +> Copyright (c) Nate Moore +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> + ## @vue/create-eslint-config License: MIT @@ -371,62 +405,27 @@ Repository: git://github.com/mde/ejs.git > limitations under the License. > -## kleur - -License: MIT -By: Luke Edwards -Repository: git+https://github.com/lukeed/kleur.git - -> The MIT License (MIT) -> -> Copyright (c) Luke Edwards (lukeed.com) -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -> THE SOFTWARE. -> - -## prompts +## picocolors -License: MIT -By: Terkel Gjervig -Repository: git+https://github.com/terkelg/prompts.git +License: ISC +By: Alexey Raspopov +Repository: git+https://github.com/alexeyraspopov/picocolors.git -> MIT License -> -> Copyright (c) 2018 Terkel Gjervig Nielsen +> ISC License > -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: +> Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov > -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. > -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > ## sisteransi diff --git a/index.ts b/index.ts index 0f0e60871..6ef6c51dd 100755 --- a/index.ts +++ b/index.ts @@ -4,9 +4,8 @@ import * as fs from 'node:fs' import * as path from 'node:path' import { parseArgs } from 'node:util' - -import prompts from 'prompts' -import { red, green, cyan, bold } from 'kleur/colors' +import { intro, outro, text, confirm, multiselect, select, isCancel, cancel } from '@clack/prompts' +import { red, green, cyan, bold, dim } from 'picocolors' import ejs from 'ejs' @@ -22,6 +21,72 @@ import { trimBoilerplate, removeCSSImport, emptyRouterConfig } from './utils/tri import cliPackageJson from './package.json' +const language = getLanguage() + +const FEATURE_FLAGS = [ + 'default', + 'ts', + 'typescript', + 'jsx', + 'router', + 'vue-router', + 'pinia', + 'tests', + 'with-tests', + 'vitest', + 'cypress', + 'nightwatch', + 'playwright', + 'eslint', + 'prettier', + 'eslint-with-oxlint', + 'eslint-with-prettier', +] as const + +const FEATURE_OPTIONS = [ + { + value: 'typescript', + label: language.needsTypeScript.message, + }, + { + value: 'jsx', + label: language.needsJsx.message, + }, + { + value: 'router', + label: language.needsRouter.message, + }, + { + value: 'pinia', + label: language.needsPinia.message, + }, + { + value: 'vitest', + label: language.needsVitest.message, + }, + { + value: 'e2e', + label: language.needsE2eTesting.message, + }, + { + value: 'eslint', + label: language.needsEslint.message, + }, + { + value: 'prettier', + label: language.needsPrettier.message, + }, +] as const + +type PromptResult = { + projectName?: string + shouldOverwrite?: boolean + packageName?: string + features?: (typeof FEATURE_OPTIONS)[number]['value'][] + e2eFramework?: 'cypress' | 'nightwatch' | 'playwright' + experimentOxlint?: boolean +} + function isValidPackageName(projectName) { return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName) } @@ -63,6 +128,16 @@ function emptyDir(dir) { ) } +async function unwrapPrompt(maybeCancelPromise: Promise): Promise { + const result = await maybeCancelPromise + + if (isCancel(result)) { + cancel(red('✖') + ` ${language.errors.operationCancelled}`) + process.exit(0) + } + return result +} + const helpMessage = `\ Usage: create-vue [FEATURE_FLAGS...] [OPTIONS...] [DIRECTORY] @@ -120,29 +195,7 @@ async function init() { const args = process.argv.slice(2) // // alias is not supported by parseArgs so we declare all the flags altogether - const flags = [ - 'default', - 'typescript', - 'ts', - 'jsx', - 'router', - 'vue-router', - 'pinia', - 'vitest', - 'cypress', - 'playwright', - 'nightwatch', - 'eslint', - 'eslint-with-oxlint', - 'eslint-with-prettier', - 'prettier', - 'tests', - 'with-tests', - 'force', - 'bare', - 'help', - 'version', - ] as const + const flags = [...FEATURE_FLAGS, 'force', 'bare', 'help', 'version'] as const type CLIOptions = { [key in (typeof flags)[number]]: { readonly type: 'boolean' } } @@ -166,253 +219,147 @@ async function init() { } // if any of the feature flags is set, we would skip the feature prompts - const isFeatureFlagsUsed = - typeof ( - argv.default ?? - argv.ts ?? - argv.typescript ?? - argv.jsx ?? - argv.router ?? - argv['vue-router'] ?? - argv.pinia ?? - argv.tests ?? - argv['with-tests'] ?? - argv.vitest ?? - argv.cypress ?? - argv.nightwatch ?? - argv.playwright ?? - argv.eslint ?? - argv.prettier ?? - argv['eslint-with-oxlint'] ?? - argv['eslint-with-prettier'] - ) === 'boolean' + const isFeatureFlagsUsed = FEATURE_FLAGS.some((flag) => typeof argv[flag] === 'boolean') let targetDir = positionals[0] - const defaultProjectName = !targetDir ? 'vue-project' : targetDir + const defaultProjectName = targetDir || 'vue-project' const forceOverwrite = argv.force - const language = getLanguage() - - let result: { - projectName?: string - shouldOverwrite?: boolean - packageName?: string - needsTypeScript?: boolean - needsJsx?: boolean - needsRouter?: boolean - needsPinia?: boolean - needsVitest?: boolean - needsE2eTesting?: false | 'cypress' | 'nightwatch' | 'playwright' - needsEslint?: false | 'eslintOnly' | 'speedUpWithOxlint' - needsOxlint?: boolean - needsPrettier?: boolean - } = {} - - console.log() - console.log( + const result: PromptResult = { + projectName: defaultProjectName, + shouldOverwrite: forceOverwrite, + packageName: defaultProjectName, + features: [], + e2eFramework: undefined, + experimentOxlint: false, + } + + intro( process.stdout.isTTY && process.stdout.getColorDepth() > 8 ? banners.gradientBanner : banners.defaultBanner, ) - console.log() - - try { - // Prompts: - // - Project name: - // - whether to overwrite the existing directory or not? - // - enter a valid package name for package.json - // - Project language: JavaScript / TypeScript - // - Add JSX Support? - // - Install Vue Router for SPA development? - // - Install Pinia for state management? - // - Add Cypress for testing? - // - Add Nightwatch for testing? - // - Add Playwright for end-to-end testing? - // - Add ESLint for code quality? - // - Add Prettier for code formatting? - result = await prompts( - [ - { - name: 'projectName', - type: targetDir ? null : 'text', - message: language.projectName.message, - initial: defaultProjectName, - onState: (state) => (targetDir = String(state.value).trim() || defaultProjectName), - }, - { - name: 'shouldOverwrite', - type: () => (canSkipEmptying(targetDir) || forceOverwrite ? null : 'toggle'), - message: () => { - const dirForPrompt = - targetDir === '.' - ? language.shouldOverwrite.dirForPrompts.current - : `${language.shouldOverwrite.dirForPrompts.target} "${targetDir}"` - - return `${dirForPrompt} ${language.shouldOverwrite.message}` - }, - initial: true, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - { - name: 'overwriteChecker', - type: (prev, values) => { - if (values.shouldOverwrite === false) { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - } - return null - }, - }, - { - name: 'packageName', - type: () => (isValidPackageName(targetDir) ? null : 'text'), - message: language.packageName.message, - initial: () => toValidPackageName(targetDir), - validate: (dir) => isValidPackageName(dir) || language.packageName.invalidMessage, - }, - { - name: 'needsTypeScript', - type: () => (isFeatureFlagsUsed ? null : 'toggle'), - message: language.needsTypeScript.message, - initial: false, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - { - name: 'needsJsx', - type: () => (isFeatureFlagsUsed ? null : 'toggle'), - message: language.needsJsx.message, - initial: false, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - { - name: 'needsRouter', - type: () => (isFeatureFlagsUsed ? null : 'toggle'), - message: language.needsRouter.message, - initial: false, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - { - name: 'needsPinia', - type: () => (isFeatureFlagsUsed ? null : 'toggle'), - message: language.needsPinia.message, - initial: false, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - { - name: 'needsVitest', - type: () => (isFeatureFlagsUsed ? null : 'toggle'), - message: language.needsVitest.message, - initial: false, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - { - name: 'needsE2eTesting', - type: () => (isFeatureFlagsUsed ? null : 'select'), - hint: language.needsE2eTesting.hint, - message: language.needsE2eTesting.message, - initial: 0, - choices: (prev, answers) => [ + + if (!targetDir) { + targetDir = + result.projectName = + result.packageName = + await unwrapPrompt( + text({ + message: language.projectName.message, + placeholder: defaultProjectName, + validate: (value) => + value.trim().length > 0 ? undefined : language.projectName.invalidMessage, + }), + ) + } + + if (!canSkipEmptying(targetDir) && !forceOverwrite) { + result.shouldOverwrite = await unwrapPrompt( + confirm({ + message: `${ + targetDir === '.' + ? language.shouldOverwrite.dirForPrompts.current + : `${language.shouldOverwrite.dirForPrompts.target} "${targetDir}"` + } ${language.shouldOverwrite.message}`, + initialValue: false, + }), + ) + + if (!result.shouldOverwrite) { + cancel(red('✖') + ` ${language.errors.operationCancelled}`) + process.exit(0) + } + } + + if (!isValidPackageName(targetDir)) { + result.packageName = await unwrapPrompt( + text({ + message: language.packageName.message, + initialValue: toValidPackageName(targetDir), + validate: (value) => + isValidPackageName(value) ? undefined : language.packageName.invalidMessage, + }), + ) + } + + if (!isFeatureFlagsUsed) { + result.features = await unwrapPrompt( + multiselect({ + message: `${language.featureSelection.message} ${dim(language.featureSelection.hint)}`, + // @ts-expect-error @clack/prompt's type doesn't support readonly array yet + options: FEATURE_OPTIONS, + required: false, + }), + ) + + if (result.features.includes('e2e')) { + const hasVitest = result.features.includes('vitest') + result.e2eFramework = await unwrapPrompt( + select({ + message: `${language.e2eSelection.message} ${dim(language.e2eSelection.hint)}`, + options: [ { - title: language.needsE2eTesting.selectOptions.negative.title, - value: false, + value: 'playwright', + label: language.e2eSelection.selectOptions.playwright.title, + hint: language.e2eSelection.selectOptions.playwright.desc, }, { - title: language.needsE2eTesting.selectOptions.cypress.title, - description: answers.needsVitest - ? undefined - : language.needsE2eTesting.selectOptions.cypress.desc, value: 'cypress', + label: language.e2eSelection.selectOptions.cypress.title, + hint: hasVitest + ? language.e2eSelection.selectOptions.cypress.desc + : language.e2eSelection.selectOptions.cypress.hintOnComponentTesting!, }, { - title: language.needsE2eTesting.selectOptions.nightwatch.title, - description: answers.needsVitest - ? undefined - : language.needsE2eTesting.selectOptions.nightwatch.desc, value: 'nightwatch', - }, - { - title: language.needsE2eTesting.selectOptions.playwright.title, - value: 'playwright', + label: language.e2eSelection.selectOptions.nightwatch.title, + hint: hasVitest + ? language.e2eSelection.selectOptions.nightwatch.desc + : language.e2eSelection.selectOptions.nightwatch.hintOnComponentTesting!, }, ], - }, - { - name: 'needsEslint', - type: () => (isFeatureFlagsUsed ? null : 'select'), - message: language.needsEslint.message, - initial: 0, - choices: [ - { - title: language.needsEslint.selectOptions.negative.title, - value: false, - }, - { - title: language.needsEslint.selectOptions.eslintOnly.title, - value: 'eslintOnly', - }, - { - title: language.needsEslint.selectOptions.speedUpWithOxlint.title, - value: 'speedUpWithOxlint', - }, - ], - }, - { - name: 'needsPrettier', - type: () => (isFeatureFlagsUsed ? null : 'toggle'), - message: language.needsPrettier.message, - initial: false, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - ], - { - onCancel: () => { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - }, - }, - ) - } catch (cancelled) { - console.log(cancelled.message) - process.exit(1) - } - - // `initial` won't take effect if the prompt type is null - // so we still have to assign the default values here - const { - projectName, - packageName = projectName?.trim() || defaultProjectName, - shouldOverwrite = argv.force as boolean, - needsJsx = argv.jsx as boolean, - needsTypeScript = (argv.ts || argv.typescript) as boolean, - needsRouter = (argv.router || argv['vue-router']) as boolean, - needsPinia = argv.pinia as boolean, - needsVitest = (argv.vitest || argv.tests) as boolean, - needsPrettier = (argv.prettier || argv['eslint-with-prettier']) as boolean, - } = result - - const needsEslint = Boolean( - argv.eslint || argv['eslint-with-oxlint'] || argv['eslint-with-prettier'] || result.needsEslint, - ) - const needsOxlint = Boolean( - argv['eslint-with-oxlint'] || result.needsEslint === 'speedUpWithOxlint', - ) + }), + ) + } + + if (result.features.includes('eslint')) { + result.experimentOxlint = await unwrapPrompt( + confirm({ + message: language.needsOxlint.message, + initialValue: false, + }), + ) + } + } - const { needsE2eTesting } = result - const needsCypress = argv.cypress || argv.tests || needsE2eTesting === 'cypress' + const { features } = result + + const needsTypeScript = argv.ts || argv.typescript || features.includes('typescript') + const needsJsx = argv.jsx || features.includes('jsx') + const needsRouter = argv.router || argv['vue-router'] || features.includes('router') + const needsPinia = argv.pinia || features.includes('pinia') + const needsVitest = argv.vitest || argv.tests || features.includes('vitest') + const needsEslint = + argv.eslint || + argv['eslint-with-oxlint'] || + argv['eslint-with-prettier'] || + features.includes('eslint') + const needsPrettier = + argv.prettier || argv['eslint-with-prettier'] || features.includes('prettier') + const needsOxlint = argv['eslint-with-oxlint'] || result.experimentOxlint + + const { e2eFramework } = result + const needsCypress = argv.cypress || argv.tests || e2eFramework === 'cypress' const needsCypressCT = needsCypress && !needsVitest - const needsNightwatch = argv.nightwatch || needsE2eTesting === 'nightwatch' + const needsNightwatch = argv.nightwatch || e2eFramework === 'nightwatch' const needsNightwatchCT = needsNightwatch && !needsVitest - const needsPlaywright = argv.playwright || needsE2eTesting === 'playwright' + const needsPlaywright = argv.playwright || e2eFramework === 'playwright' const root = path.join(cwd, targetDir) - if (fs.existsSync(root) && shouldOverwrite) { + if (fs.existsSync(root) && result.shouldOverwrite) { emptyDir(root) } else if (!fs.existsSync(root)) { fs.mkdirSync(root) @@ -420,7 +367,7 @@ async function init() { console.log(`\n${language.infos.scaffolding} ${root}...`) - const pkg = { name: packageName, version: '0.0.0' } + const pkg = { name: result.packageName, version: '0.0.0' } fs.writeFileSync(path.resolve(root, 'package.json'), JSON.stringify(pkg, null, 2)) // todo: @@ -690,19 +637,23 @@ async function init() { }), ) - console.log(`\n${language.infos.done}\n`) + let outroMessage = `${language.infos.done}\n\n` if (root !== cwd) { const cdProjectName = path.relative(cwd, root) - console.log( - ` ${bold(green(`cd ${cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName}`))}`, - ) + outroMessage += ` ${bold(green(`cd ${cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName}`))}\n` } - console.log(` ${bold(green(getCommand(packageManager, 'install')))}`) + outroMessage += ` ${bold(green(getCommand(packageManager, 'install')))}\n` if (needsPrettier) { - console.log(` ${bold(green(getCommand(packageManager, 'format')))}`) + outroMessage += ` ${bold(green(getCommand(packageManager, 'format')))}\n` } - console.log(` ${bold(green(getCommand(packageManager, 'dev')))}`) - console.log() + outroMessage += ` ${bold(green(getCommand(packageManager, 'dev')))}\n` + + outroMessage += ` +${dim('|')} ${language.infos.optionalGitCommand} + + ${bold(green('git init && git add -A && git commit -m "initial commit"'))}` + + outro(outroMessage) } init().catch((e) => { diff --git a/locales/en-US.json b/locales/en-US.json index d1f8a1698..24c335dc8 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -1,63 +1,66 @@ { "projectName": { - "message": "Project name:" + "message": "Project name (target directory):", + "invalidMessage": "Should not be empty" }, "shouldOverwrite": { "dirForPrompts": { "current": "Current directory", "target": "Target directory" }, - "message": "is not empty. Remove existing files and continue?" + "message": "is not empty. Remove existing files and continue" }, "packageName": { "message": "Package name:", "invalidMessage": "Invalid package.json name" }, + "featureSelection": { + "message": "Select features to include in your project:", + "hint": "(↑/↓ to navigate, space to select, a to toggle all, enter to confirm)" + }, "needsTypeScript": { - "message": "Add TypeScript?" + "message": "TypeScript" }, "needsJsx": { - "message": "Add JSX Support?" + "message": "JSX Support" }, "needsRouter": { - "message": "Add Vue Router for Single Page Application development?" + "message": "Router (SPA development)" }, "needsPinia": { - "message": "Add Pinia for state management?" + "message": "Pinia (state management)" }, "needsVitest": { - "message": "Add Vitest for Unit Testing?" + "message": "Vitest (unit testing)" }, "needsE2eTesting": { - "message": "Add an End-to-End Testing Solution?", - "hint": "- Use arrow-keys. Return to submit.", + "message": "End-to-End Testing" + }, + "needsEslint": { + "message": "ESLint (error prevention)" + }, + "needsPrettier": { + "message": "Prettier (code formatting)" + }, + "e2eSelection": { + "message": "Select an End-to-End testing framework:", + "hint": "(↑/↓ to navigate, enter to confirm)", "selectOptions": { - "negative": { "title": "No" }, + "playwright": { "title": "Playwright", "desc": "https://playwright.dev/" }, "cypress": { "title": "Cypress", - "desc": "also supports unit testing with Cypress Component Testing" + "desc": "https://www.cypress.io/", + "hintOnComponentTesting": "also supports unit testing with Cypress Component Testing - https://www.cypress.io/" }, "nightwatch": { "title": "Nightwatch", - "desc": "also supports unit testing with Nightwatch Component Testing" - }, - "playwright": { "title": "Playwright" } - } - }, - "needsEslint": { - "message": "Add ESLint for code quality?", - "selectOptions": { - "negative": { "title": "No" }, - "eslintOnly": { - "title": "Yes" - }, - "speedUpWithOxlint": { - "title": "Yes, and speed up with Oxlint (experimental)" + "desc": "https://nightwatchjs.org/", + "hintOnComponentTesting": "also supports unit testing with Nightwatch Component Testing - https://nightwatchjs.org/" } } }, - "needsPrettier": { - "message": "Add Prettier for code formatting?" + "needsOxlint": { + "message": "Install Oxlint for faster linting? (experimental)" }, "errors": { "operationCancelled": "Operation cancelled" @@ -68,6 +71,7 @@ }, "infos": { "scaffolding": "Scaffolding project in", - "done": "Done. Now run:" + "done": "Done. Now run:", + "optionalGitCommand": "Optional: Initialize Git in your project directory with:" } } diff --git a/locales/fr-FR.json b/locales/fr-FR.json index 9ff6d1313..480df3050 100644 --- a/locales/fr-FR.json +++ b/locales/fr-FR.json @@ -1,6 +1,7 @@ { "projectName": { - "message": "Nom du projet\u00a0:" + "message": "Nom du projet\u00a0:", + "invalidMessage": "Ne doit pas être vide" }, "shouldOverwrite": { "dirForPrompts": { @@ -13,51 +14,53 @@ "message": "Nom du package\u00a0:", "invalidMessage": "Le nom du package.json est invalide" }, + "featureSelection": { + "message": "Sélectionnez les fonctionnalités à inclure dans votre projet\u00a0:", + "hint": "(↑/↓ pour naviguer, espace pour sélectionner, a pour tout sélectionner, entrée pour confirmer)" + }, "needsTypeScript": { - "message": "Ajouter TypeScript\u00a0?" + "message": "TypeScript" }, "needsJsx": { - "message": "Ajouter le support de JSX\u00a0?" + "message": "Support de JSX" }, "needsRouter": { - "message": "Ajouter Vue Router pour le développement d'applications _single page_\u00a0?" + "message": "Router (développement SPA)" }, "needsPinia": { - "message": "Ajouter Pinia pour la gestion de l'état\u00a0?" + "message": "Pinia (gestion de l'état)" }, "needsVitest": { - "message": "Ajouter Vitest pour les tests unitaires\u00a0?" + "message": "Vitest (tests unitaires)" }, "needsE2eTesting": { - "message": "Ajouter une solution de test de bout en bout (e2e)\u00a0?", - "hint": "- Utilisez les flèches et appuyez sur la touche Entrée pour valider", + "message": "Tests de bout en bout" + }, + "needsEslint": { + "message": "ESLint (prévention des erreurs)" + }, + "needsPrettier": { + "message": "Prettier (formatage du code)" + }, + "e2eSelection": { + "message": "Sélectionnez un framework de test de bout en bout\u00a0:", + "hint": "(↑/↓ pour naviguer, entrée pour confirmer)", "selectOptions": { - "negative": { "title": "Non" }, + "playwright": { "title": "Playwright", "desc": "https://playwright.dev/" }, "cypress": { "title": "Cypress", - "desc": "prend également en charge les tests unitaires avec Cypress Component Testing" + "desc": "https://www.cypress.io/", + "hintOnComponentTesting": "prend également en charge les tests unitaires avec Cypress Component Testing - https://www.cypress.io/" }, "nightwatch": { "title": "Nightwatch", - "desc": "prend également en charge les tests unitaires avec Nightwatch Component Testing" - }, - "playwright": { "title": "Playwright" } - } - }, - "needsEslint": { - "message": "Ajouter ESLint pour la qualité du code\u00a0?", - "selectOptions": { - "negative": { "title": "Non" }, - "eslintOnly": { - "title": "Oui" - }, - "speedUpWithOxlint": { - "title": "Oui, et accélérer avec Oxlint (expérimental)" + "desc": "https://nightwatchjs.org/", + "hintOnComponentTesting": "prend également en charge les tests unitaires avec Nightwatch Component Testing - https://nightwatchjs.org/" } } }, - "needsPrettier": { - "message": "Ajouter Prettier pour le formatage du code\u00a0?" + "needsOxlint": { + "message": "Installer Oxlint pour un linting plus rapide\u00a0? (expérimental)" }, "errors": { "operationCancelled": "Operation annulée" @@ -68,6 +71,7 @@ }, "infos": { "scaffolding": "Génération du projet dans", - "done": "Terminé. Exécutez maintenant\u00a0:" + "done": "Terminé. Exécutez maintenant\u00a0:", + "optionalGitCommand": "Optionnel\u00a0: Initialisez Git dans votre répertoire de projet avec\u00a0:" } } diff --git a/locales/tr-TR.json b/locales/tr-TR.json index 323acac1d..60c71a2cc 100644 --- a/locales/tr-TR.json +++ b/locales/tr-TR.json @@ -1,6 +1,7 @@ { "projectName": { - "message": "Proje adı:" + "message": "Proje adı:", + "invalidMessage": "Boş bırakılamaz" }, "shouldOverwrite": { "dirForPrompts": { @@ -13,51 +14,53 @@ "message": "Paket adı:", "invalidMessage": "Geçersiz package.json adı" }, + "featureSelection": { + "message": "Projenize eklenecek özellikleri seçin:", + "hint": "(↑/↓ gezinmek için, boşluk seçmek için, a tümünü seçmek için, enter onaylamak için)" + }, "needsTypeScript": { - "message": "TypeScript Eklensin mi?" + "message": "TypeScript" }, "needsJsx": { - "message": "JSX Desteği Eklensin mi?" + "message": "JSX Desteği" }, "needsRouter": { - "message": "Tek Sayfa Uygulama geliştirilmesi için Vue Router eklensin mi?" + "message": "Router (SPA geliştirme)" }, "needsPinia": { - "message": "Durum yönetimi için Pinia eklensin mi?" + "message": "Pinia (durum yönetimi)" }, "needsVitest": { - "message": "Birim Testi için Vitest eklensin mi?" + "message": "Vitest (birim testi)" }, "needsE2eTesting": { - "message": "Uçtan Uca Test Çözümü Eklensin mi?", - "hint": "- Ok tuşlarını kullan. Gönderime geri dön.", + "message": "Uçtan Uca Test" + }, + "needsEslint": { + "message": "ESLint (hata önleme)" + }, + "needsPrettier": { + "message": "Prettier (kod formatlama)" + }, + "e2eSelection": { + "message": "Bir Uçtan Uca test çerçevesi seçin:", + "hint": "(↑/↓ gezinmek için, enter onaylamak için)", "selectOptions": { - "negative": { "title": "Hayır" }, + "playwright": { "title": "Playwright", "desc": "https://playwright.dev/" }, "cypress": { "title": "Cypress", - "desc": "ayrıca Cypress Bileşen Testi ile birim testini de destekler" + "desc": "https://www.cypress.io/", + "hintOnComponentTesting": "ayrıca Cypress Bileşen Testi ile birim testini de destekler - https://www.cypress.io/" }, "nightwatch": { "title": "Nightwatch", - "desc": "ayrıca Nightwatch Bileşen Testi ile birim testini de destekler" - }, - "playwright": { "title": "Playwright" } - } - }, - "needsEslint": { - "message": "Kod kalitesi için ESLint eklensin mi?", - "selectOptions": { - "negative": { "title": "Hayır" }, - "eslintOnly": { - "title": "Evet" - }, - "speedUpWithOxlint": { - "title": "Evet ve Oxlint ile hızlanın (deneysel)" + "desc": "https://nightwatchjs.org/", + "hintOnComponentTesting": "ayrıca Nightwatch Bileşen Testi ile birim testini de destekler - https://nightwatchjs.org/" } } }, - "needsPrettier": { - "message": "Kod formatlama için Prettier eklensin mi?" + "needsOxlint": { + "message": "Daha hızlı linting için Oxlint eklensin mi? (deneysel)" }, "errors": { "operationCancelled": "İşlem iptal edildi" @@ -68,6 +71,7 @@ }, "infos": { "scaffolding": "İskele projesi", - "done": "Tamamlandı. Şimdi bunu çalıştır:" + "done": "Tamamlandı. Şimdi bunu çalıştır:", + "optionalGitCommand": "İsteğe bağlı: Proje dizininizde Git'i şununla başlatın:" } } diff --git a/locales/zh-Hans.json b/locales/zh-Hans.json index c74002f01..92e367adc 100644 --- a/locales/zh-Hans.json +++ b/locales/zh-Hans.json @@ -1,6 +1,7 @@ { "projectName": { - "message": "请输入项目名称:" + "message": "请输入项目名称:", + "invalidMessage": "不能为空" }, "shouldOverwrite": { "dirForPrompts": { @@ -13,51 +14,53 @@ "message": "请输入包名称:", "invalidMessage": "无效的 package.json 名称" }, + "featureSelection": { + "message": "请选择要包含的功能:", + "hint": "(↑/↓ 切换,空格选择,a 全选,回车确认)" + }, "needsTypeScript": { - "message": "是否使用 TypeScript 语法?" + "message": "TypeScript" }, "needsJsx": { - "message": "是否启用 JSX 支持?" + "message": "JSX 支持" }, "needsRouter": { - "message": "是否引入 Vue Router 进行单页面应用开发?" + "message": "Router(单页面应用开发)" }, "needsPinia": { - "message": "是否引入 Pinia 用于状态管理?" + "message": "Pinia(状态管理)" }, "needsVitest": { - "message": "是否引入 Vitest 用于单元测试?" + "message": "Vitest(单元测试)" }, "needsE2eTesting": { - "message": "是否要引入一款端到端(End to End)测试工具?", - "hint": "- 使用箭头切换按Enter确认。", + "message": "端到端测试" + }, + "needsEslint": { + "message": "ESLint(错误预防)" + }, + "needsPrettier": { + "message": "Prettier(代码格式化)" + }, + "e2eSelection": { + "message": "选择一个端到端测试框架:", + "hint": "(↑/↓ 切换,回车确认)", "selectOptions": { - "negative": { "title": "不需要" }, + "playwright": { "title": "Playwright", "desc": "https://playwright.dev/" }, "cypress": { "title": "Cypress", - "desc": "同时支持基于 Cypress Component Testing 的单元测试" + "desc": "https://www.cypress.io/", + "hintOnComponentTesting": "同时支持基于 Cypress Component Testing 的单元测试 - https://www.cypress.io/" }, "nightwatch": { "title": "Nightwatch", - "desc": "同时支持基于 Nightwatch Component Testing 的单元测试" - }, - "playwright": { "title": "Playwright" } - } - }, - "needsEslint": { - "message": "是否引入 ESLint 用于代码质量检测?", - "selectOptions": { - "negative": { "title": "否" }, - "eslintOnly": { - "title": "是" - }, - "speedUpWithOxlint": { - "title": "是,并同时引入 Oxlint 以加快检测(试验阶段)" + "desc": "https://nightwatchjs.org/", + "hintOnComponentTesting": "同时支持基于 Nightwatch Component Testing 的单元测试 - https://nightwatchjs.org/" } } }, - "needsPrettier": { - "message": "是否引入 Prettier 用于代码格式化?" + "needsOxlint": { + "message": "是否引入 Oxlint 以加快检测?(试验阶段)" }, "errors": { "operationCancelled": "操作取消" @@ -68,6 +71,7 @@ }, "infos": { "scaffolding": "正在初始化项目", - "done": "项目初始化完成,可执行以下命令:" + "done": "项目初始化完成,可执行以下命令:", + "optionalGitCommand": "可选:使用以下命令在项目目录中初始化 Git:" } } diff --git a/locales/zh-Hant.json b/locales/zh-Hant.json index 4837230e7..d7b80b958 100644 --- a/locales/zh-Hant.json +++ b/locales/zh-Hant.json @@ -1,11 +1,12 @@ { "projectName": { - "message": "請輸入專案名稱:" + "message": "請輸入專案名稱:", + "invalidMessage": "不能為空" }, "shouldOverwrite": { "dirForPrompts": { "current": "當前資料夾", - "target": "目標資料夾:" + "target": "目標資料夾" }, "message": "非空,是否覆蓋?" }, @@ -13,55 +14,53 @@ "message": "請輸入套件名稱:", "invalidMessage": "無效的 package.json 名稱" }, + "featureSelection": { + "message": "請選擇要包含的功能:", + "hint": "(↑/↓ 切換,空格選擇,a 全選,enter 確認)" + }, "needsTypeScript": { - "message": "是否使用 TypeScript?" + "message": "TypeScript" }, "needsJsx": { - "message": "是否啟用 JSX 支援?" + "message": "JSX 支援" }, "needsRouter": { - "message": "是否引入 Vue Router 進行單頁應用程式開發?" + "message": "Router(單頁應用程式開發)" }, "needsPinia": { - "message": "是否引入 Pinia 用於狀態管理?" + "message": "Pinia(狀態管理)" }, "needsVitest": { - "message": "是否引入 Vitest 用於單元測試" + "message": "Vitest(單元測試)" }, "needsE2eTesting": { - "message": "是否要引入一款端對端(End to End)測試工具?", - "hint": "- 使用箭頭切換按 Enter 確認。", + "message": "端對端測試" + }, + "needsEslint": { + "message": "ESLint(錯誤預防)" + }, + "needsPrettier": { + "message": "Prettier(程式碼格式化)" + }, + "e2eSelection": { + "message": "選擇一個端對端測試框架:", + "hint": "(↑/↓ 切換,enter 確認)", "selectOptions": { - "negative": { - "title": "不需要" - }, + "playwright": { "title": "Playwright", "desc": "https://playwright.dev/" }, "cypress": { "title": "Cypress", - "desc": "同時支援基於 Cypress Component Testing 的單元測試" + "desc": "https://www.cypress.io/", + "hintOnComponentTesting": "同時支援基於 Cypress Component Testing 的單元測試 - https://www.cypress.io/" }, "nightwatch": { "title": "Nightwatch", - "desc": "同時支援基於 Nightwatch Component Testing 的單元測試" - }, - "playwright": { - "title": "Playwright" + "desc": "https://nightwatchjs.org/", + "hintOnComponentTesting": "同時支援基於 Nightwatch Component Testing 的單元測試 - https://nightwatchjs.org/" } } }, - "needsEslint": { - "message": "是否引入 ESLint 用於程式碼品質檢測?", - "selectOptions": { - "negative": { "title": "否" }, - "eslintOnly": { - "title": "是" - }, - "speedUpWithOxlint": { - "title": "是,並同時引入 Oxlint 以加快檢測(試驗性功能)" - } - } - }, - "needsPrettier": { - "message": "是否引入 Prettier 用於程式碼格式化?" + "needsOxlint": { + "message": "是否引入 Oxlint 以加快檢測?(試驗性功能)" }, "errors": { "operationCancelled": "操作取消" @@ -72,6 +71,7 @@ }, "infos": { "scaffolding": "正在建置專案", - "done": "專案建置完成,可執行以下命令:" + "done": "專案建置完成,可執行以下命令:", + "optionalGitCommand": "可選:使用以下命令在專案目錄中初始化 Git:" } } diff --git a/package.json b/package.json index 0e2d523d0..e009326d0 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ }, "homepage": "https://github.com/vuejs/create-vue#readme", "devDependencies": { + "@clack/prompts": "^0.10.0", "@tsconfig/node22": "^22.0.0", "@types/eslint": "^9.6.1", "@types/node": "^22.13.5", @@ -49,10 +50,9 @@ "esbuild": "^0.25.0", "esbuild-plugin-license": "^1.2.3", "husky": "^9.1.7", - "kleur": "^4.1.5", "lint-staged": "^15.4.3", + "picocolors": "^1.1.1", "prettier": "3.5.1", - "prompts": "^2.4.2", "vitest": "^3.0.7", "zx": "^8.3.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05e0b9532..73c6a888a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@clack/prompts': + specifier: ^0.10.0 + version: 0.10.0 '@tsconfig/node22': specifier: ^22.0.0 version: 22.0.0 @@ -38,18 +41,15 @@ importers: husky: specifier: ^9.1.7 version: 9.1.7 - kleur: - specifier: ^4.1.5 - version: 4.1.5 lint-staged: specifier: ^15.4.3 version: 15.4.3 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 prettier: specifier: 3.5.1 version: 3.5.1 - prompts: - specifier: ^2.4.2 - version: 2.4.2 vitest: specifier: ^3.0.7 version: 3.0.7(@types/node@22.13.5)(jsdom@26.0.0)(yaml@2.7.0) @@ -369,6 +369,12 @@ packages: '@bazel/runfiles@6.3.1': resolution: {integrity: sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==} + '@clack/core@0.4.1': + resolution: {integrity: sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==} + + '@clack/prompts@0.10.0': + resolution: {integrity: sha512-H3rCl6CwW1NdQt9rE3n373t7o5cthPv7yUoxF2ytZvyvlJv89C5RYMJu83Hed8ODgys5vpBU0GKxIRG83jd8NQ==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -2650,10 +2656,6 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -3150,10 +3152,6 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -4163,6 +4161,17 @@ snapshots: '@bazel/runfiles@6.3.1': {} + '@clack/core@0.4.1': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.10.0': + dependencies: + '@clack/core': 0.4.1 + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@colors/colors@1.5.0': optional: true @@ -6396,8 +6405,6 @@ snapshots: kleur@3.0.3: {} - kleur@4.1.5: {} - kolorist@1.8.0: {} lazy-ass@1.6.0: {} @@ -6905,11 +6912,6 @@ snapshots: process@0.11.10: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - proto-list@1.2.4: {} proxy-agent@6.4.0: diff --git a/scripts/build.mjs b/scripts/build.mjs index 6238a31bf..6f1805f07 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -135,21 +135,6 @@ await esbuild.build({ target: 'node14', plugins: [ - { - name: 'alias', - setup({ onResolve, resolve }) { - onResolve({ filter: /^prompts$/, namespace: 'file' }, async ({ importer, resolveDir }) => { - // we can always use non-transpiled code since we support 14.16.0+ - const result = await resolve('prompts/lib/index.js', { - importer, - resolveDir, - kind: 'import-statement', - }) - return result - }) - }, - }, - { name: '@vue/create-eslint-config fix', setup(build) { diff --git a/utils/getLanguage.ts b/utils/getLanguage.ts index 6a1e12d72..ee130843f 100644 --- a/utils/getLanguage.ts +++ b/utils/getLanguage.ts @@ -22,6 +22,7 @@ interface Language { projectName: LanguageItem shouldOverwrite: LanguageItem packageName: LanguageItem + featureSelection: LanguageItem needsTypeScript: LanguageItem needsJsx: LanguageItem needsRouter: LanguageItem @@ -30,6 +31,12 @@ interface Language { needsE2eTesting: LanguageItem needsEslint: LanguageItem needsPrettier: LanguageItem + e2eSelection: LanguageItem & { + selectOptions?: { + [key: string]: { title: string; desc?: string; hintOnComponentTesting?: string } + } + } + needsOxlint: LanguageItem errors: { operationCancelled: string } @@ -40,6 +47,7 @@ interface Language { infos: { scaffolding: string done: string + optionalGitCommand: string } }