From 234a5cdd17a13ede941e0424306cd79a8e2959a2 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Tue, 18 Feb 2025 23:24:44 +0800 Subject: [PATCH 01/10] feat!: use a giant multiselect instead of individual toggles for features As we are adding more features, the number of toggles is getting out of hand. A multiselect could save a few keystrokes for most users. I also take this opportunity to change the prompts library to `@clack/prompts`. The color library is changed to `picocolors`. IMO `kleur` is still the smaller library because of treeshaking. But `@clack/prompts` already comes with `picocolors` as a dependency, so by not adding `kleur`, we save a few bytes. This is a proof-of-concept change. I haven't put much consideration into the code style yet. Nor have I updated the i18n messages. I will do that if this change is accepted. See the result in a screen recording: [![asciicast](https://asciinema.org/a/lPaZSGz9LTd7mcWZeJAZxerhx.svg)](https://asciinema.org/a/lPaZSGz9LTd7mcWZeJAZxerhx) --- LICENSE | 103 +++++++++-------- index.ts | 267 +++++++++++++++++++++------------------------ locales/en-US.json | 22 ++-- package.json | 4 +- pnpm-lock.yaml | 58 +++++----- 5 files changed, 220 insertions(+), 234 deletions(-) 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 744a66702..c1b84c2f7 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 } from '@clack/prompts' +import { red, green, cyan, bold, dim } from 'picocolors' import ejs from 'ejs' @@ -207,6 +206,7 @@ async function init() { needsEslint?: false | 'eslintOnly' | 'speedUpWithOxlint' needsOxlint?: boolean needsPrettier?: boolean + features?: string[] } = {} console.log() @@ -218,167 +218,148 @@ async function init() { 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) => [ - { - title: language.needsE2eTesting.selectOptions.negative.title, - value: false, - }, + intro() + + if (!targetDir) { + const projectNameInput = await text({ + message: language.projectName.message, + placeholder: defaultProjectName, + validate: (value) => (value.length > 0 ? undefined : 'Should not be empty'), + }) + + if (isCancel(projectNameInput)) { + throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) + } + + targetDir = projectNameInput + } + + if (!canSkipEmptying(targetDir) && !forceOverwrite) { + const shouldOverwriteInput = await confirm({ + message: `${ + targetDir === '.' + ? language.shouldOverwrite.dirForPrompts.current + : `${language.shouldOverwrite.dirForPrompts.target} "${targetDir}"` + } ${language.shouldOverwrite.message}`, + }) + + if (isCancel(shouldOverwriteInput) || !shouldOverwriteInput) { + throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) + } + + result.shouldOverwrite = shouldOverwriteInput + } + + if (!isValidPackageName(targetDir)) { + const packageNameInput = await text({ + message: language.packageName.message, + initialValue: toValidPackageName(targetDir), + validate: (value) => + isValidPackageName(value) ? undefined : language.packageName.invalidMessage, + }) + + if (isCancel(packageNameInput)) { + throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) + } + + result.packageName = packageNameInput + } + + if (!isFeatureFlagsUsed) { + const features = await multiselect({ + message: `Select the features you want to enable: ${dim('(↑/↓ to navigate, space to select, a to select/unselect all, return to submit)')}`, + 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 }, + ], + required: false, + }) + + if (isCancel(features)) { + throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) + } + + result.features = features + + if (features.includes('e2e')) { + const e2eTestingInput = await select({ + message: `${language.needsE2eTesting.message}: ${dim('(↑/↓ to navigate, return to submit)')}`, + options: [ { - title: language.needsE2eTesting.selectOptions.cypress.title, - description: answers.needsVitest + value: 'cypress', + label: language.needsE2eTesting.selectOptions.cypress.title, + hint: features.includes('vitest') ? undefined : language.needsE2eTesting.selectOptions.cypress.desc, - value: 'cypress', }, { - title: language.needsE2eTesting.selectOptions.nightwatch.title, - description: answers.needsVitest + value: 'nightwatch', + label: language.needsE2eTesting.selectOptions.nightwatch.title, + hint: features.includes('vitest') ? undefined : language.needsE2eTesting.selectOptions.nightwatch.desc, - value: 'nightwatch', }, { - title: language.needsE2eTesting.selectOptions.playwright.title, value: 'playwright', + label: language.needsE2eTesting.selectOptions.playwright.title, }, ], - }, - { - name: 'needsEslint', - type: () => (isFeatureFlagsUsed ? null : 'select'), + }) + + if (isCancel(e2eTestingInput)) { + throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) + } + + result.needsE2eTesting = e2eTestingInput + } + + if (features.includes('eslint')) { + const eslintInput = await select({ message: language.needsEslint.message, - initial: 0, - choices: [ - { - title: language.needsEslint.selectOptions.negative.title, - value: false, - }, + options: [ { - title: language.needsEslint.selectOptions.eslintOnly.title, value: 'eslintOnly', + label: language.needsEslint.selectOptions.eslintOnly.title, }, { - title: language.needsEslint.selectOptions.speedUpWithOxlint.title, value: 'speedUpWithOxlint', + label: language.needsEslint.selectOptions.speedUpWithOxlint.title, }, ], - }, - { - name: 'needsPrettier', - type: () => (isFeatureFlagsUsed ? null : 'toggle'), - message: language.needsPrettier.message, - initial: false, - active: language.defaultToggleOptions.active, - inactive: language.defaultToggleOptions.inactive, - }, - ], - { - onCancel: () => { + }) + + if (isCancel(eslintInput)) { throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - }, - }, - ) + } + + result.needsEslint = eslintInput + } + } + + // Convert multiselect results to individual flags + if (result.features) { + result.needsTypeScript = result.features.includes('typescript') + result.needsJsx = result.features.includes('jsx') + result.needsRouter = result.features.includes('router') + result.needsPinia = result.features.includes('pinia') + result.needsVitest = result.features.includes('vitest') + result.needsPrettier = result.features.includes('prettier') + // E2E and ESLint are handled by their respective follow-up prompts + if (!result.features.includes('e2e')) { + result.needsE2eTesting = false + } + if (!result.features.includes('eslint')) { + result.needsEslint = false + } + } } catch (cancelled) { - console.log(cancelled.message) + outro(cancelled.message) process.exit(1) } @@ -690,7 +671,7 @@ async function init() { }), ) - console.log(`\n${language.infos.done}\n`) + outro(`${language.infos.done}\n`) if (root !== cwd) { const cdProjectName = path.relative(cwd, root) console.log( diff --git a/locales/en-US.json b/locales/en-US.json index d1f8a1698..20f13e253 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -1,35 +1,35 @@ { "projectName": { - "message": "Project name:" + "message": "Project name (target directory):" }, "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" }, "needsTypeScript": { - "message": "Add TypeScript?" + "message": "Add TypeScript" }, "needsJsx": { - "message": "Add JSX Support?" + "message": "Add JSX Support" }, "needsRouter": { - "message": "Add Vue Router for Single Page Application development?" + "message": "Add Vue Router for Single Page Application development" }, "needsPinia": { - "message": "Add Pinia for state management?" + "message": "Add Pinia for state management" }, "needsVitest": { - "message": "Add Vitest for Unit Testing?" + "message": "Add Vitest for Unit Testing" }, "needsE2eTesting": { - "message": "Add an End-to-End Testing Solution?", + "message": "Add an End-to-End Testing Solution", "hint": "- Use arrow-keys. Return to submit.", "selectOptions": { "negative": { "title": "No" }, @@ -45,11 +45,11 @@ } }, "needsEslint": { - "message": "Add ESLint for code quality?", + "message": "Add ESLint for code quality", "selectOptions": { "negative": { "title": "No" }, "eslintOnly": { - "title": "Yes" + "title": "Yes, just ESLint" }, "speedUpWithOxlint": { "title": "Yes, and speed up with Oxlint (experimental)" @@ -57,7 +57,7 @@ } }, "needsPrettier": { - "message": "Add Prettier for code formatting?" + "message": "Add Prettier for code formatting" }, "errors": { "operationCancelled": "Operation cancelled" diff --git a/package.json b/package.json index c0eaf7937..41e6218d9 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.4", @@ -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.5", "zx": "^8.3.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3fe0fb57..59f18caf8 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.5 version: 3.0.5(@types/node@22.13.4)(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'} @@ -2805,10 +2811,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==} @@ -3295,10 +3297,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==} @@ -4308,6 +4306,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 @@ -5650,6 +5659,10 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + debug@4.4.0: + dependencies: + ms: 2.1.3 + debug@4.4.0(supports-color@8.1.1): dependencies: ms: 2.1.3 @@ -6312,7 +6325,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -6325,7 +6338,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -6644,8 +6657,6 @@ snapshots: kleur@3.0.3: {} - kleur@4.1.5: {} - kolorist@1.8.0: {} lazy-ass@1.6.0: {} @@ -6664,7 +6675,7 @@ snapshots: dependencies: chalk: 5.4.1 commander: 13.1.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 execa: 8.0.1 lilconfig: 3.1.3 listr2: 8.2.5 @@ -7143,11 +7154,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: @@ -7690,7 +7696,7 @@ snapshots: vite-node@3.0.5(@types/node@22.13.4)(yaml@2.7.0): dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.2 vite: 6.1.0(@types/node@22.13.4)(yaml@2.7.0) @@ -7798,7 +7804,7 @@ snapshots: '@vitest/spy': 3.0.5 '@vitest/utils': 3.0.5 chai: 5.1.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 expect-type: 1.1.0 magic-string: 0.30.17 pathe: 2.0.2 From 43ba4b5e7e18836534dc267a971ca465bb6adabb Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Tue, 18 Feb 2025 23:57:34 +0800 Subject: [PATCH 02/10] chore: small formatting improvements to the intro/outro message [skip ci] Still much work to do. But it's acdceptable for now. --- index.ts | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/index.ts b/index.ts index c1b84c2f7..1b944483d 100755 --- a/index.ts +++ b/index.ts @@ -209,16 +209,12 @@ async function init() { features?: string[] } = {} - console.log() - console.log( - process.stdout.isTTY && process.stdout.getColorDepth() > 8 - ? banners.gradientBanner - : banners.defaultBanner, - ) - console.log() - try { - intro() + intro( + process.stdout.isTTY && process.stdout.getColorDepth() > 8 + ? banners.gradientBanner + : banners.defaultBanner, + ) if (!targetDir) { const projectNameInput = await text({ @@ -671,19 +667,18 @@ async function init() { }), ) - outro(`${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')))}` + + outro(outroMessage) } init().catch((e) => { From 70048266bb753f7975667c404e750d0f825c4e3f Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Thu, 27 Feb 2025 00:04:11 +0800 Subject: [PATCH 03/10] chore: unselect -> deselect --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index 1b944483d..a4ccac7fc 100755 --- a/index.ts +++ b/index.ts @@ -263,7 +263,7 @@ async function init() { if (!isFeatureFlagsUsed) { const features = await multiselect({ - message: `Select the features you want to enable: ${dim('(↑/↓ to navigate, space to select, a to select/unselect all, return to submit)')}`, + message: `Select the features you want to enable: ${dim('(↑/↓ to navigate, space to select, a to select/deselect all, return to submit)')}`, options: [ { value: 'typescript', label: language.needsTypeScript.message }, { value: 'jsx', label: language.needsJsx.message }, From a142f80be6a4bc71926fbdaffe6b2af5f785f616 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 28 Feb 2025 00:30:05 +0800 Subject: [PATCH 04/10] refactor: update English prompts --- index.ts | 95 ++++++++++++++++++++++++++++---------------- locales/en-US.json | 52 ++++++++++++------------ utils/getLanguage.ts | 7 ++++ 3 files changed, 94 insertions(+), 60 deletions(-) diff --git a/index.ts b/index.ts index a4ccac7fc..72fc3063c 100755 --- a/index.ts +++ b/index.ts @@ -263,16 +263,44 @@ async function init() { if (!isFeatureFlagsUsed) { const features = await multiselect({ - message: `Select the features you want to enable: ${dim('(↑/↓ to navigate, space to select, a to select/deselect all, return to submit)')}`, + message: `${language.featureSelection.message} ${dim(language.featureSelection.hint)}`, 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 }, + { + value: 'typescript', + label: language.needsTypeScript.message, + hint: language.needsTypeScript.hint, + }, + { value: 'jsx', label: language.needsJsx.message, hint: language.needsJsx.hint }, + { + value: 'router', + label: language.needsRouter.message, + hint: language.needsRouter.hint, + }, + { + value: 'pinia', + label: language.needsPinia.message, + hint: language.needsPinia.hint, + }, + { + value: 'vitest', + label: language.needsVitest.message, + hint: language.needsVitest.hint, + }, + { + value: 'e2e', + label: language.needsE2eTesting.message, + hint: language.needsE2eTesting.hint, + }, + { + value: 'eslint', + label: language.needsEslint.message, + hint: language.needsEslint.hint, + }, + { + value: 'prettier', + label: language.needsPrettier.message, + hint: language.needsPrettier.hint, + }, ], required: false, }) @@ -285,25 +313,26 @@ async function init() { if (features.includes('e2e')) { const e2eTestingInput = await select({ - message: `${language.needsE2eTesting.message}: ${dim('(↑/↓ to navigate, return to submit)')}`, + message: `${language.e2eSelection.message} ${dim(language.e2eSelection.hint)}`, options: [ + { + value: 'playwright', + label: language.e2eSelection.selectOptions.playwright.title, + hint: language.e2eSelection.selectOptions.playwright.desc, + }, { value: 'cypress', - label: language.needsE2eTesting.selectOptions.cypress.title, + label: language.e2eSelection.selectOptions.cypress.title, hint: features.includes('vitest') - ? undefined - : language.needsE2eTesting.selectOptions.cypress.desc, + ? language.e2eSelection.selectOptions.cypress.desc + : language.e2eSelection.selectOptions.cypress.hintOnComponentTesting!, }, { value: 'nightwatch', - label: language.needsE2eTesting.selectOptions.nightwatch.title, + label: language.e2eSelection.selectOptions.nightwatch.title, hint: features.includes('vitest') - ? undefined - : language.needsE2eTesting.selectOptions.nightwatch.desc, - }, - { - value: 'playwright', - label: language.needsE2eTesting.selectOptions.playwright.title, + ? language.e2eSelection.selectOptions.nightwatch.desc + : language.e2eSelection.selectOptions.nightwatch.hintOnComponentTesting!, }, ], }) @@ -316,25 +345,16 @@ async function init() { } if (features.includes('eslint')) { - const eslintInput = await select({ - message: language.needsEslint.message, - options: [ - { - value: 'eslintOnly', - label: language.needsEslint.selectOptions.eslintOnly.title, - }, - { - value: 'speedUpWithOxlint', - label: language.needsEslint.selectOptions.speedUpWithOxlint.title, - }, - ], + const oxlintInput = await confirm({ + message: language.needsOxlint.message, + initialValue: false, }) - if (isCancel(eslintInput)) { + if (isCancel(oxlintInput)) { throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) } - result.needsEslint = eslintInput + result.needsOxlint = oxlintInput } } @@ -676,7 +696,12 @@ async function init() { if (needsPrettier) { outroMessage += ` ${bold(green(getCommand(packageManager, 'format')))}\n` } - outroMessage += ` ${bold(green(getCommand(packageManager, 'dev')))}` + outroMessage += ` ${bold(green(getCommand(packageManager, 'dev')))}\n` + + outroMessage += ` +${dim('|')} Optional: Initialize Git in your project directory with: + + ${bold(green('git init && git add -A && git commit -m "initial commit"'))}` outro(outroMessage) } diff --git a/locales/en-US.json b/locales/en-US.json index 20f13e253..c13840b77 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -13,51 +13,53 @@ "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, just ESLint" - }, - "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" diff --git a/utils/getLanguage.ts b/utils/getLanguage.ts index 6a1e12d72..0ce0d8064 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 } From b333568d29cec1f083fcb1da25e7e9ef916737b4 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 28 Feb 2025 00:47:51 +0800 Subject: [PATCH 05/10] docs: update all the locales with the new prompts --- index.ts | 2 +- locales/en-US.json | 3 ++- locales/fr-FR.json | 55 ++++++++++++++++++++------------------- locales/tr-TR.json | 55 ++++++++++++++++++++------------------- locales/zh-Hans.json | 55 ++++++++++++++++++++------------------- locales/zh-Hant.json | 61 ++++++++++++++++++++++---------------------- utils/getLanguage.ts | 1 + 7 files changed, 121 insertions(+), 111 deletions(-) diff --git a/index.ts b/index.ts index 72fc3063c..9a348379a 100755 --- a/index.ts +++ b/index.ts @@ -699,7 +699,7 @@ async function init() { outroMessage += ` ${bold(green(getCommand(packageManager, 'dev')))}\n` outroMessage += ` -${dim('|')} Optional: Initialize Git in your project directory with: +${dim('|')} ${language.infos.optionalGitCommand} ${bold(green('git init && git add -A && git commit -m "initial commit"'))}` diff --git a/locales/en-US.json b/locales/en-US.json index c13840b77..1371219e1 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -70,6 +70,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..622b417ba 100644 --- a/locales/fr-FR.json +++ b/locales/fr-FR.json @@ -13,51 +13,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 basculer, 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 +70,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..edbce0000 100644 --- a/locales/tr-TR.json +++ b/locales/tr-TR.json @@ -13,51 +13,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 +70,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..127fb2b2b 100644 --- a/locales/zh-Hans.json +++ b/locales/zh-Hans.json @@ -13,51 +13,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 +70,7 @@ }, "infos": { "scaffolding": "正在初始化项目", - "done": "项目初始化完成,可执行以下命令:" + "done": "项目初始化完成,可执行以下命令:", + "optionalGitCommand": "可选:使用以下命令在项目目录中初始化 Git:" } } diff --git a/locales/zh-Hant.json b/locales/zh-Hant.json index 4837230e7..f5f9cbad3 100644 --- a/locales/zh-Hant.json +++ b/locales/zh-Hant.json @@ -5,7 +5,7 @@ "shouldOverwrite": { "dirForPrompts": { "current": "當前資料夾", - "target": "目標資料夾:" + "target": "目標資料夾" }, "message": "非空,是否覆蓋?" }, @@ -13,55 +13,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 +70,7 @@ }, "infos": { "scaffolding": "正在建置專案", - "done": "專案建置完成,可執行以下命令:" + "done": "專案建置完成,可執行以下命令:", + "optionalGitCommand": "可選:使用以下命令在專案目錄中初始化 Git:" } } diff --git a/utils/getLanguage.ts b/utils/getLanguage.ts index 0ce0d8064..ee130843f 100644 --- a/utils/getLanguage.ts +++ b/utils/getLanguage.ts @@ -47,6 +47,7 @@ interface Language { infos: { scaffolding: string done: string + optionalGitCommand: string } } From 1aad47008aa8f0952b9624fcb75e7f9a5bc3f852 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 28 Feb 2025 13:33:52 +0800 Subject: [PATCH 06/10] refactor: remove some duplicated or redundant code --- index.ts | 219 +++++++++++++++++++++++++------------------------------ 1 file changed, 101 insertions(+), 118 deletions(-) diff --git a/index.ts b/index.ts index 9a348379a..8e1458a7f 100755 --- a/index.ts +++ b/index.ts @@ -4,7 +4,7 @@ import * as fs from 'node:fs' import * as path from 'node:path' import { parseArgs } from 'node:util' -import { intro, outro, text, confirm, multiselect, select, isCancel } from '@clack/prompts' +import { intro, outro, text, confirm, multiselect, select, isCancel, cancel } from '@clack/prompts' import { red, green, cyan, bold, dim } from 'picocolors' import ejs from 'ejs' @@ -192,6 +192,15 @@ async function init() { const forceOverwrite = argv.force const language = getLanguage() + async function unwrapPrompt(maybeCancelPromise: Promise): Promise { + const result = await maybeCancelPromise + + if (isCancel(result)) { + cancel(red('✖') + ` ${language.errors.operationCancelled}`) + process.exit(0) + } + return result + } let result: { projectName?: string @@ -203,7 +212,7 @@ async function init() { needsPinia?: boolean needsVitest?: boolean needsE2eTesting?: false | 'cypress' | 'nightwatch' | 'playwright' - needsEslint?: false | 'eslintOnly' | 'speedUpWithOxlint' + needsEslint?: boolean needsOxlint?: boolean needsPrettier?: boolean features?: string[] @@ -217,144 +226,120 @@ async function init() { ) if (!targetDir) { - const projectNameInput = await text({ - message: language.projectName.message, - placeholder: defaultProjectName, - validate: (value) => (value.length > 0 ? undefined : 'Should not be empty'), - }) - - if (isCancel(projectNameInput)) { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - } - - targetDir = projectNameInput + targetDir = await unwrapPrompt( + text({ + message: language.projectName.message, + placeholder: defaultProjectName, + validate: (value) => (value.length > 0 ? undefined : 'Should not be empty'), + }), + ) } if (!canSkipEmptying(targetDir) && !forceOverwrite) { - const shouldOverwriteInput = await confirm({ - message: `${ - targetDir === '.' - ? language.shouldOverwrite.dirForPrompts.current - : `${language.shouldOverwrite.dirForPrompts.target} "${targetDir}"` - } ${language.shouldOverwrite.message}`, - }) + result.shouldOverwrite = await unwrapPrompt( + confirm({ + message: `${ + targetDir === '.' + ? language.shouldOverwrite.dirForPrompts.current + : `${language.shouldOverwrite.dirForPrompts.target} "${targetDir}"` + } ${language.shouldOverwrite.message}`, + initialValue: false, + }), + ) - if (isCancel(shouldOverwriteInput) || !shouldOverwriteInput) { + if (!result.shouldOverwrite) { throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) } - - result.shouldOverwrite = shouldOverwriteInput } if (!isValidPackageName(targetDir)) { - const packageNameInput = await text({ - message: language.packageName.message, - initialValue: toValidPackageName(targetDir), - validate: (value) => - isValidPackageName(value) ? undefined : language.packageName.invalidMessage, - }) - - if (isCancel(packageNameInput)) { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - } - - result.packageName = packageNameInput + result.packageName = await unwrapPrompt( + text({ + message: language.packageName.message, + initialValue: toValidPackageName(targetDir), + validate: (value) => + isValidPackageName(value) ? undefined : language.packageName.invalidMessage, + }), + ) } if (!isFeatureFlagsUsed) { - const features = await multiselect({ - message: `${language.featureSelection.message} ${dim(language.featureSelection.hint)}`, - options: [ - { - value: 'typescript', - label: language.needsTypeScript.message, - hint: language.needsTypeScript.hint, - }, - { value: 'jsx', label: language.needsJsx.message, hint: language.needsJsx.hint }, - { - value: 'router', - label: language.needsRouter.message, - hint: language.needsRouter.hint, - }, - { - value: 'pinia', - label: language.needsPinia.message, - hint: language.needsPinia.hint, - }, - { - value: 'vitest', - label: language.needsVitest.message, - hint: language.needsVitest.hint, - }, - { - value: 'e2e', - label: language.needsE2eTesting.message, - hint: language.needsE2eTesting.hint, - }, - { - value: 'eslint', - label: language.needsEslint.message, - hint: language.needsEslint.hint, - }, - { - value: 'prettier', - label: language.needsPrettier.message, - hint: language.needsPrettier.hint, - }, - ], - required: false, - }) - - if (isCancel(features)) { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - } - - result.features = features - - if (features.includes('e2e')) { - const e2eTestingInput = await select({ - message: `${language.e2eSelection.message} ${dim(language.e2eSelection.hint)}`, + const features = await unwrapPrompt( + multiselect({ + message: `${language.featureSelection.message} ${dim(language.featureSelection.hint)}`, options: [ { - value: 'playwright', - label: language.e2eSelection.selectOptions.playwright.title, - hint: language.e2eSelection.selectOptions.playwright.desc, + 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: 'cypress', - label: language.e2eSelection.selectOptions.cypress.title, - hint: features.includes('vitest') - ? language.e2eSelection.selectOptions.cypress.desc - : language.e2eSelection.selectOptions.cypress.hintOnComponentTesting!, + value: 'e2e', + label: language.needsE2eTesting.message, }, { - value: 'nightwatch', - label: language.e2eSelection.selectOptions.nightwatch.title, - hint: features.includes('vitest') - ? language.e2eSelection.selectOptions.nightwatch.desc - : language.e2eSelection.selectOptions.nightwatch.hintOnComponentTesting!, + value: 'eslint', + label: language.needsEslint.message, + }, + { + value: 'prettier', + label: language.needsPrettier.message, }, ], - }) + required: false, + }), + ) - if (isCancel(e2eTestingInput)) { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - } + result.features = features - result.needsE2eTesting = e2eTestingInput + if (features.includes('e2e')) { + result.needsE2eTesting = await unwrapPrompt( + select({ + message: `${language.e2eSelection.message} ${dim(language.e2eSelection.hint)}`, + options: [ + { + value: 'playwright', + label: language.e2eSelection.selectOptions.playwright.title, + hint: language.e2eSelection.selectOptions.playwright.desc, + }, + { + value: 'cypress', + label: language.e2eSelection.selectOptions.cypress.title, + hint: features.includes('vitest') + ? language.e2eSelection.selectOptions.cypress.desc + : language.e2eSelection.selectOptions.cypress.hintOnComponentTesting!, + }, + { + value: 'nightwatch', + label: language.e2eSelection.selectOptions.nightwatch.title, + hint: features.includes('vitest') + ? language.e2eSelection.selectOptions.nightwatch.desc + : language.e2eSelection.selectOptions.nightwatch.hintOnComponentTesting!, + }, + ], + }), + ) } if (features.includes('eslint')) { - const oxlintInput = await confirm({ - message: language.needsOxlint.message, - initialValue: false, - }) - - if (isCancel(oxlintInput)) { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - } - - result.needsOxlint = oxlintInput + result.needsOxlint = await unwrapPrompt( + confirm({ + message: language.needsOxlint.message, + initialValue: false, + }), + ) } } @@ -396,9 +381,7 @@ async function init() { 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', - ) + const needsOxlint = Boolean(argv['eslint-with-oxlint'] || result.needsOxlint) const { needsE2eTesting } = result const needsCypress = argv.cypress || argv.tests || needsE2eTesting === 'cypress' From 509ccfdcb0c35cf2fdedd4d2c0652444de23910e Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 28 Feb 2025 14:44:30 +0800 Subject: [PATCH 07/10] refactor: further cleanup of codebase --- index.ts | 406 +++++++++++++++++++++++++------------------------------ 1 file changed, 186 insertions(+), 220 deletions(-) diff --git a/index.ts b/index.ts index 8e1458a7f..6ffd040b0 100755 --- a/index.ts +++ b/index.ts @@ -21,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) } @@ -62,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] @@ -119,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' } } @@ -165,234 +219,146 @@ 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 forceOverwrite = argv.force - const language = getLanguage() - async function unwrapPrompt(maybeCancelPromise: Promise): Promise { - const result = await maybeCancelPromise + const result: PromptResult = { + projectName: defaultProjectName, + shouldOverwrite: forceOverwrite, + packageName: defaultProjectName, + features: [], + e2eFramework: undefined, + experimentOxlint: false, + } - if (isCancel(result)) { - cancel(red('✖') + ` ${language.errors.operationCancelled}`) - process.exit(0) - } - return result - } - - let result: { - projectName?: string - shouldOverwrite?: boolean - packageName?: string - needsTypeScript?: boolean - needsJsx?: boolean - needsRouter?: boolean - needsPinia?: boolean - needsVitest?: boolean - needsE2eTesting?: false | 'cypress' | 'nightwatch' | 'playwright' - needsEslint?: boolean - needsOxlint?: boolean - needsPrettier?: boolean - features?: string[] - } = {} - - try { - intro( - process.stdout.isTTY && process.stdout.getColorDepth() > 8 - ? banners.gradientBanner - : banners.defaultBanner, - ) + intro( + process.stdout.isTTY && process.stdout.getColorDepth() > 8 + ? banners.gradientBanner + : banners.defaultBanner, + ) - if (!targetDir) { - targetDir = await unwrapPrompt( - text({ - message: language.projectName.message, - placeholder: defaultProjectName, - validate: (value) => (value.length > 0 ? undefined : 'Should not be empty'), - }), - ) - } + if (!targetDir) { + targetDir = + result.projectName = + result.packageName = + await unwrapPrompt( + text({ + message: language.projectName.message, + placeholder: defaultProjectName, + validate: (value) => (value.length > 0 ? undefined : 'Should not be empty'), + }), + ) + } - 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 (!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) { - throw new Error(red('✖') + ` ${language.errors.operationCancelled}`) - } + 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 (!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 (!isFeatureFlagsUsed) { - const features = await unwrapPrompt( - multiselect({ - message: `${language.featureSelection.message} ${dim(language.featureSelection.hint)}`, + 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: [ { - value: 'typescript', - label: language.needsTypeScript.message, - }, - { value: 'jsx', label: language.needsJsx.message }, - { - value: 'router', - label: language.needsRouter.message, + value: 'playwright', + label: language.e2eSelection.selectOptions.playwright.title, + hint: language.e2eSelection.selectOptions.playwright.desc, }, { - value: 'pinia', - label: language.needsPinia.message, + value: 'cypress', + label: language.e2eSelection.selectOptions.cypress.title, + hint: hasVitest + ? language.e2eSelection.selectOptions.cypress.desc + : language.e2eSelection.selectOptions.cypress.hintOnComponentTesting!, }, { - value: 'vitest', - label: language.needsVitest.message, - }, - { - value: 'e2e', - label: language.needsE2eTesting.message, - }, - { - value: 'eslint', - label: language.needsEslint.message, - }, - { - value: 'prettier', - label: language.needsPrettier.message, + value: 'nightwatch', + label: language.e2eSelection.selectOptions.nightwatch.title, + hint: hasVitest + ? language.e2eSelection.selectOptions.nightwatch.desc + : language.e2eSelection.selectOptions.nightwatch.hintOnComponentTesting!, }, ], - required: false, }), ) - - result.features = features - - if (features.includes('e2e')) { - result.needsE2eTesting = await unwrapPrompt( - select({ - message: `${language.e2eSelection.message} ${dim(language.e2eSelection.hint)}`, - options: [ - { - value: 'playwright', - label: language.e2eSelection.selectOptions.playwright.title, - hint: language.e2eSelection.selectOptions.playwright.desc, - }, - { - value: 'cypress', - label: language.e2eSelection.selectOptions.cypress.title, - hint: features.includes('vitest') - ? language.e2eSelection.selectOptions.cypress.desc - : language.e2eSelection.selectOptions.cypress.hintOnComponentTesting!, - }, - { - value: 'nightwatch', - label: language.e2eSelection.selectOptions.nightwatch.title, - hint: features.includes('vitest') - ? language.e2eSelection.selectOptions.nightwatch.desc - : language.e2eSelection.selectOptions.nightwatch.hintOnComponentTesting!, - }, - ], - }), - ) - } - - if (features.includes('eslint')) { - result.needsOxlint = await unwrapPrompt( - confirm({ - message: language.needsOxlint.message, - initialValue: false, - }), - ) - } } - // Convert multiselect results to individual flags - if (result.features) { - result.needsTypeScript = result.features.includes('typescript') - result.needsJsx = result.features.includes('jsx') - result.needsRouter = result.features.includes('router') - result.needsPinia = result.features.includes('pinia') - result.needsVitest = result.features.includes('vitest') - result.needsPrettier = result.features.includes('prettier') - // E2E and ESLint are handled by their respective follow-up prompts - if (!result.features.includes('e2e')) { - result.needsE2eTesting = false - } - if (!result.features.includes('eslint')) { - result.needsEslint = false - } + if (result.features.includes('eslint')) { + result.experimentOxlint = await unwrapPrompt( + confirm({ + message: language.needsOxlint.message, + initialValue: false, + }), + ) } - } catch (cancelled) { - outro(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 ?? 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.needsOxlint) + } - 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) @@ -400,7 +366,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: From 7ea2b9bbdd422579561f253254526b75794d1d32 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 28 Feb 2025 22:56:35 +0800 Subject: [PATCH 08/10] build: remove alias for `prompts` package --- scripts/build.mjs | 15 --------------- 1 file changed, 15 deletions(-) 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) { From 922237fc292e1ba81b5f7aefa15dd089e638a5b7 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Wed, 5 Mar 2025 01:21:37 +0800 Subject: [PATCH 09/10] docs: Update fr-FR.json [skip ci] --- locales/fr-FR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr-FR.json b/locales/fr-FR.json index 622b417ba..d63641680 100644 --- a/locales/fr-FR.json +++ b/locales/fr-FR.json @@ -15,7 +15,7 @@ }, "featureSelection": { "message": "Sélectionnez les fonctionnalités à inclure dans votre projet\u00a0:", - "hint": "(↑/↓ pour naviguer, espace pour sélectionner, a pour tout basculer, entrée pour confirmer)" + "hint": "(↑/↓ pour naviguer, espace pour sélectionner, a pour tout sélectionner, entrée pour confirmer)" }, "needsTypeScript": { "message": "TypeScript" From 1bc81069f0635a90fc822c9664aecc33c4504dd8 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Thu, 6 Mar 2025 02:37:15 +0800 Subject: [PATCH 10/10] i18n: add translations for the "should not be empty" message --- index.ts | 3 ++- locales/en-US.json | 3 ++- locales/fr-FR.json | 3 ++- locales/tr-TR.json | 3 ++- locales/zh-Hans.json | 3 ++- locales/zh-Hant.json | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/index.ts b/index.ts index 3dc7fd008..6ef6c51dd 100755 --- a/index.ts +++ b/index.ts @@ -249,7 +249,8 @@ async function init() { text({ message: language.projectName.message, placeholder: defaultProjectName, - validate: (value) => (value.trim().length > 0 ? undefined : 'Should not be empty'), + validate: (value) => + value.trim().length > 0 ? undefined : language.projectName.invalidMessage, }), ) } diff --git a/locales/en-US.json b/locales/en-US.json index 1371219e1..24c335dc8 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -1,6 +1,7 @@ { "projectName": { - "message": "Project name (target directory):" + "message": "Project name (target directory):", + "invalidMessage": "Should not be empty" }, "shouldOverwrite": { "dirForPrompts": { diff --git a/locales/fr-FR.json b/locales/fr-FR.json index d63641680..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": { diff --git a/locales/tr-TR.json b/locales/tr-TR.json index edbce0000..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": { diff --git a/locales/zh-Hans.json b/locales/zh-Hans.json index 127fb2b2b..92e367adc 100644 --- a/locales/zh-Hans.json +++ b/locales/zh-Hans.json @@ -1,6 +1,7 @@ { "projectName": { - "message": "请输入项目名称:" + "message": "请输入项目名称:", + "invalidMessage": "不能为空" }, "shouldOverwrite": { "dirForPrompts": { diff --git a/locales/zh-Hant.json b/locales/zh-Hant.json index f5f9cbad3..d7b80b958 100644 --- a/locales/zh-Hant.json +++ b/locales/zh-Hant.json @@ -1,6 +1,7 @@ { "projectName": { - "message": "請輸入專案名稱:" + "message": "請輸入專案名稱:", + "invalidMessage": "不能為空" }, "shouldOverwrite": { "dirForPrompts": {