From 69276f3f77f839dca0f35f14c8a463f0bba6e2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Mon, 1 Jul 2024 22:15:27 +0200 Subject: [PATCH 01/10] refactor: move prompts to the beginning of the command --- eslint.config.js | 6 +-- src/commands/init.ts | 91 ++++++++++++++++++++++++-------------------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 0512a302..dc12cea3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,12 +4,10 @@ import { createConfigForNuxt } from '@nuxt/eslint-config/flat' export default createConfigForNuxt({ features: { tooling: true, - stylistic: true, + stylistic: false, }, dirs: { - src: [ - './playground', - ], + src: ['./playground'], }, }).append({ rules: { diff --git a/src/commands/init.ts b/src/commands/init.ts index 4c90c4e8..58751b91 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -8,8 +8,8 @@ import { defineCommand } from 'citty' import { sharedArgs } from './_shared' -const DEFAULT_REGISTRY - = 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' +const DEFAULT_REGISTRY = + 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' const DEFAULT_TEMPLATE_NAME = 'v3' export default defineCommand({ @@ -63,9 +63,18 @@ export default defineCommand({ async run(ctx) { const cwd = resolve(ctx.args.cwd || '.') - // Get template name const templateName = ctx.args.template || DEFAULT_TEMPLATE_NAME + const selectedPackageManager = await resolvePackageManager( + ctx.args.packageManager as PackageManagerName + ) + + if (ctx.args.gitInit === undefined) { + ctx.args.gitInit = await consola.prompt('Initialize git repository?', { + type: 'confirm', + }) + } + if (typeof templateName !== 'string') { consola.error('Please specify a template!') process.exit(1) @@ -83,8 +92,7 @@ export default defineCommand({ preferOffline: Boolean(ctx.args.preferOffline), registry: process.env.NUXI_INIT_REGISTRY || DEFAULT_REGISTRY, }) - } - catch (err) { + } catch (err) { if (process.env.DEBUG) { throw err } @@ -92,29 +100,11 @@ export default defineCommand({ process.exit(1) } - // Resolve package manager - const packageManagerOptions: PackageManagerName[] = [ - 'npm', - 'pnpm', - 'yarn', - 'bun', - ] - const packageManagerArg = ctx.args.packageManager as PackageManagerName - const selectedPackageManager = packageManagerOptions.includes( - packageManagerArg, - ) - ? packageManagerArg - : await consola.prompt('Which package manager would you like to use?', { - type: 'select', - options: packageManagerOptions, - }) - // Install project dependencies // or skip installation based on the '--no-install' flag if (ctx.args.install === false) { consola.info('Skipping install dependencies step.') - } - else { + } else { consola.start('Installing dependencies...') try { @@ -125,8 +115,7 @@ export default defineCommand({ command: selectedPackageManager, }, }) - } - catch (err) { + } catch (err) { if (process.env.DEBUG) { throw err } @@ -137,30 +126,19 @@ export default defineCommand({ consola.success('Installation completed.') } - if (ctx.args.gitInit === undefined) { - ctx.args.gitInit = await consola.prompt('Initialize git repository?', { - type: 'confirm', - }) - } if (ctx.args.gitInit) { - consola.info('Initializing git repository...\n') - const { execa } = await import('execa') - await execa('git', ['init', template.dir], { - stdio: 'inherit', - }).catch((err) => { - consola.warn(`Failed to initialize git repository: ${err}`) - }) + await initializeGitRepository(template.dir) } // Display next steps consola.log( - `\n✨ Nuxt project has been created with the \`${template.name}\` template. Next steps:`, + `\n✨ Nuxt project has been created with the \`${template.name}\` template. Next steps:` ) const relativeTemplateDir = relative(process.cwd(), template.dir) || '.' const nextSteps = [ - !ctx.args.shell - && relativeTemplateDir.length > 1 - && `\`cd ${relativeTemplateDir}\``, + !ctx.args.shell && + relativeTemplateDir.length > 1 && + `\`cd ${relativeTemplateDir}\``, `Start development server with \`${selectedPackageManager} run dev\``, ].filter(Boolean) @@ -173,3 +151,32 @@ export default defineCommand({ } }, }) + +async function resolvePackageManager(packageManager: PackageManagerName) { + const packageManagerOptions: PackageManagerName[] = [ + 'npm', + 'pnpm', + 'yarn', + 'bun', + ] + + const isSupportedPackageManager = + packageManagerOptions.includes(packageManager) + + return isSupportedPackageManager + ? packageManager + : await consola.prompt('Which package manager would you like to use?', { + type: 'select', + options: packageManagerOptions, + }) +} + +async function initializeGitRepository(templateDir: string) { + consola.info('Initializing git repository...\n') + const { execa } = await import('execa') + await execa('git', ['init', templateDir], { + stdio: 'inherit', + }).catch((err) => { + consola.warn(`Failed to initialize git repository: ${err}`) + }) +} From c2942de6c685615fa7f431f0720af446e2dcda64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Thu, 4 Jul 2024 18:56:58 +0200 Subject: [PATCH 02/10] create templates for prettier and eslint --- .gitignore | 1 + package.json | 4 + pnpm-lock.yaml | 242 +++++++++++++----- src/commands/init.ts | 176 +++++++++++-- src/commands/module/_utils.ts | 7 +- src/commands/module/add.ts | 10 +- .../eslint/eslint.config.mjs | 3 + .../prettier/.prettierignore.njk | 3 + .../prettier/.prettierrc.mjs | 5 + src/utils/packageJson.ts | 12 + 10 files changed, 364 insertions(+), 99 deletions(-) create mode 100644 src/partial-templates/eslint/eslint.config.mjs create mode 100644 src/partial-templates/prettier/.prettierignore.njk create mode 100644 src/partial-templates/prettier/.prettierrc.mjs create mode 100644 src/utils/packageJson.ts diff --git a/.gitignore b/.gitignore index bace9cdd..2379c916 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist .nuxt nuxt-app .pnpm-store +.idea diff --git a/package.json b/package.json index 82fce807..4cda84f9 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@nuxt/test-utils": "^3.13.1", "@types/http-proxy": "^1.17.14", "@types/node": "^20.14.9", + "@types/nunjucks": "^3.2.6", "@types/semver": "^7.5.8", "@types/ws": "^8.5.10", "c12": "^1.11.1", @@ -88,5 +89,8 @@ "packageManager": "pnpm@9.4.0", "engines": { "node": "^16.10.0 || >=18.0.0" + }, + "dependencies": { + "nunjucks": "^3.2.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15cf060c..65d24756 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,10 @@ overrides: importers: .: + dependencies: + nunjucks: + specifier: ^3.2.4 + version: 3.2.4(chokidar@3.6.0) optionalDependencies: fsevents: specifier: ~2.3.3 @@ -35,6 +39,9 @@ importers: '@types/node': specifier: ^20.14.9 version: 20.14.9 + '@types/nunjucks': + specifier: ^3.2.6 + version: 3.2.6 '@types/semver': specifier: ^7.5.8 version: 7.5.8 @@ -776,10 +783,6 @@ packages: resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - '@floating-ui/core@1.6.0': resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} @@ -1151,6 +1154,16 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@redocly/ajv@8.11.0': + resolution: {integrity: sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==} + + '@redocly/config@0.6.2': + resolution: {integrity: sha512-c3K5u64eMnr2ootPcpEI0ioIRLE8QP8ptvLxG9MwAmb2sU8HMRfVwXDU3AZiMVY2w4Ts0mDc+Xv4HTIk8DRqFw==} + + '@redocly/openapi-core@1.17.0': + resolution: {integrity: sha512-XoNIuksnOGAzAcfpyJkHrMxwurXaQfglnovNE7/pTx4OEjik3OT91+tKAyRCkklVCdMtAA3YokGMZzdhjViUWA==} + engines: {node: '>=14.19.0', npm: '>=7.0.0'} + '@rollup/plugin-alias@5.1.0': resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==} engines: {node: '>=14.0.0'} @@ -1425,6 +1438,9 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/nunjucks@3.2.6': + resolution: {integrity: sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1783,6 +1799,9 @@ packages: '@vueuse/shared@10.9.0': resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} + a-sync-waterfall@1.0.1: + resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -1888,6 +1907,9 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + ast-kit@0.11.3: resolution: {integrity: sha512-qdwwKEhckRk0XE22/xDdmU3v/60E8Edu4qFhgTLIhGGDs/PAJwLw9pQn8Rj99PitlbBZbYpx0k/lbir4kg0SuA==} engines: {node: '>=16.14.0'} @@ -2097,12 +2119,19 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -2932,6 +2961,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + index-to-position@0.1.2: + resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==} + engines: {node: '>=18'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -3076,6 +3109,10 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3120,6 +3157,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -3203,6 +3243,9 @@ packages: lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -3402,8 +3445,8 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - nitropack-nightly@2.10.0-28656653.33fb776f: - resolution: {integrity: sha512-GjFFz3Gu8jPMR7RYXbHGEhTUkTQVKnDdlDDXt+Cg4oHfQAGDO6Wdz7qWkSyHWh/jWuKbQJ9ymOXkijaFv98Q+A==} + nitropack-nightly@2.10.0-28663815.3a2e6cc4: + resolution: {integrity: sha512-AYNlvxjYaTAInLvCtyXAitrO/EM443LQa/+XtTthdrQ/oE42iQctzLkPUEWZ+RKQIVPEIXcbu8XW5Nmbfpk/7A==} engines: {node: ^16.11.0 || >=17.0.0} hasBin: true peerDependencies: @@ -3515,6 +3558,16 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nunjucks@3.2.4: + resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==} + engines: {node: '>= 6.9.0'} + hasBin: true + peerDependencies: + chokidar: ^3.3.0 + peerDependenciesMeta: + chokidar: + optional: true + nuxi@3.12.0: resolution: {integrity: sha512-6vRdiXTw9SajEQOUi6Ze/XaIXzy1q/sD5UqHQSv3yqTu7Pot5S7fEihNXV8LpcgLz+9HzjVt70r7jYe7R99c2w==} engines: {node: ^16.10.0 || >=18.0.0} @@ -3575,9 +3628,11 @@ packages: resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} engines: {node: '>=14.16'} - openapi-typescript@6.7.6: - resolution: {integrity: sha512-c/hfooPx+RBIOPM09GSxABOZhYPblDoyaGhqBkD/59vtpN21jEuWKDlM0KYTvqJVlSYjKs0tBcIdeXKChlSPtw==} + openapi-typescript@7.0.1: + resolution: {integrity: sha512-RrjgCRTIpJzJGKZ2Fg7ERuOVXvmLcSAC8KRi165QyueW+0u3HSOpAhZDUpAPNlJ0ubd+bkjFTMWIwoXn0hHbtg==} hasBin: true + peerDependencies: + typescript: ^5.x optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} @@ -3636,6 +3691,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.1.0: + resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} + engines: {node: '>=18'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -4184,6 +4243,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4631,10 +4694,6 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@5.28.4: - resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} - engines: {node: '>=14.0'} - unenv@1.9.0: resolution: {integrity: sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==} @@ -5016,6 +5075,9 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml@2.4.1: resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} engines: {node: '>= 14'} @@ -5091,7 +5153,7 @@ snapshots: '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 convert-source-map: 2.0.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5111,7 +5173,7 @@ snapshots: '@babel/traverse': 7.24.7 '@babel/types': 7.24.7 convert-source-map: 2.0.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5463,7 +5525,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.4 '@babel/types': 7.24.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5478,7 +5540,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5656,7 +5718,7 @@ snapshots: '@eslint/config-array@0.16.0': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5664,7 +5726,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) espree: 10.0.1 globals: 14.0.0 ignore: 5.3.1 @@ -5681,8 +5743,6 @@ snapshots: '@eslint/object-schema@2.1.4': {} - '@fastify/busboy@2.1.1': {} - '@floating-ui/core@1.6.0': dependencies: '@floating-ui/utils': 0.2.1 @@ -5704,7 +5764,7 @@ snapshots: '@antfu/install-pkg': 0.1.1 '@antfu/utils': 0.7.8 '@iconify/types': 2.0.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) kolorist: 1.8.0 local-pkg: 0.5.0 mlly: 1.7.1 @@ -5746,7 +5806,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) transitivePeerDependencies: - supports-color @@ -5801,9 +5861,9 @@ snapshots: '@npmcli/agent@2.2.1': dependencies: - agent-base: 7.1.0 + agent-base: 7.1.0(supports-color@9.4.0) http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.4 + https-proxy-agent: 7.0.4(supports-color@9.4.0) lru-cache: 10.2.0 socks-proxy-agent: 8.0.2 transitivePeerDependencies: @@ -6285,6 +6345,32 @@ snapshots: '@polka/url@1.0.0-next.25': {} + '@redocly/ajv@8.11.0': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + '@redocly/config@0.6.2': {} + + '@redocly/openapi-core@1.17.0(encoding@0.1.13)(supports-color@9.4.0)': + dependencies: + '@redocly/ajv': 8.11.0 + '@redocly/config': 0.6.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.4(supports-color@9.4.0) + js-levenshtein: 1.1.6 + js-yaml: 4.1.0 + lodash.isequal: 4.5.0 + minimatch: 5.1.6 + node-fetch: 2.7.0(encoding@0.1.13) + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - encoding + - supports-color + '@rollup/plugin-alias@5.1.0(rollup@3.29.4)': dependencies: slash: 4.0.0 @@ -6574,6 +6660,8 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/nunjucks@3.2.6': {} + '@types/resolve@1.20.2': {} '@types/semver@7.5.8': {} @@ -6608,7 +6696,7 @@ snapshots: '@typescript-eslint/types': 7.9.0 '@typescript-eslint/typescript-estree': 7.9.0(typescript@5.4.2) '@typescript-eslint/visitor-keys': 7.9.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) eslint: 9.5.0 optionalDependencies: typescript: 5.4.2 @@ -6624,7 +6712,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.9.0(typescript@5.4.2) '@typescript-eslint/utils': 7.9.0(eslint@9.5.0)(typescript@5.4.2) - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) eslint: 9.5.0 ts-api-utils: 1.3.0(typescript@5.4.2) optionalDependencies: @@ -6638,7 +6726,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.9.0 '@typescript-eslint/visitor-keys': 7.9.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 @@ -7181,6 +7269,8 @@ snapshots: - '@vue/composition-api' - vue + a-sync-waterfall@1.0.1: {} + abbrev@1.1.1: {} abbrev@2.0.0: {} @@ -7203,13 +7293,13 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) transitivePeerDependencies: - supports-color - agent-base@7.1.0: + agent-base@7.1.0(supports-color@9.4.0): dependencies: - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) transitivePeerDependencies: - supports-color @@ -7283,6 +7373,8 @@ snapshots: array-union@2.1.0: {} + asap@2.0.6: {} + ast-kit@0.11.3(rollup@3.29.4): dependencies: '@babel/parser': 7.24.4 @@ -7536,10 +7628,14 @@ snapshots: colord@2.9.3: {} + colorette@1.4.0: {} + colorette@2.0.20: {} commander@2.20.3: {} + commander@5.1.0: {} + commander@7.2.0: {} commander@8.3.0: {} @@ -7737,9 +7833,11 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.5: + debug@4.3.5(supports-color@9.4.0): dependencies: ms: 2.1.2 + optionalDependencies: + supports-color: 9.4.0 deep-is@0.1.4: {} @@ -7939,7 +8037,7 @@ snapshots: eslint-plugin-import-x@0.5.0(eslint@9.5.0)(typescript@5.4.2): dependencies: '@typescript-eslint/utils': 7.9.0(eslint@9.5.0)(typescript@5.4.2) - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) doctrine: 3.0.0 eslint: 9.5.0 eslint-import-resolver-node: 0.3.9 @@ -7956,7 +8054,7 @@ snapshots: '@es-joy/jsdoccomment': 0.43.1 are-docs-informative: 0.0.2 comment-parser: 1.4.1 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) escape-string-regexp: 4.0.0 eslint: 9.5.0 esquery: 1.5.0 @@ -8039,7 +8137,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) escape-string-regexp: 4.0.0 eslint-scope: 8.0.1 eslint-visitor-keys: 4.0.0 @@ -8453,8 +8551,8 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.0 - debug: 4.3.5 + agent-base: 7.1.0(supports-color@9.4.0) + debug: 4.3.5(supports-color@9.4.0) transitivePeerDependencies: - supports-color @@ -8463,14 +8561,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.4: + https-proxy-agent@7.0.4(supports-color@9.4.0): dependencies: - agent-base: 7.1.0 - debug: 4.3.5 + agent-base: 7.1.0(supports-color@9.4.0) + debug: 4.3.5(supports-color@9.4.0) transitivePeerDependencies: - supports-color @@ -8508,6 +8606,8 @@ snapshots: indent-string@4.0.0: {} + index-to-position@0.1.2: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -8523,7 +8623,7 @@ snapshots: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -8629,6 +8729,8 @@ snapshots: jiti@1.21.6: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} js-tokens@9.0.0: {} @@ -8655,6 +8757,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -8745,6 +8849,8 @@ snapshots: lodash.isarguments@3.1.0: {} + lodash.isequal@4.5.0: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} @@ -8924,7 +9030,7 @@ snapshots: negotiator@0.6.3: {} - nitropack-nightly@2.10.0-28656653.33fb776f(@opentelemetry/api@1.8.0)(encoding@0.1.13): + nitropack-nightly@2.10.0-28663815.3a2e6cc4(@opentelemetry/api@1.8.0)(encoding@0.1.13)(typescript@5.4.2): dependencies: '@cloudflare/kv-asset-handler': 0.3.4 '@netlify/functions': 2.7.0(@opentelemetry/api@1.8.0) @@ -8940,7 +9046,6 @@ snapshots: '@vercel/nft': 0.27.2(encoding@0.1.13) archiver: 7.0.1 c12: 1.11.1(magicast@0.3.4) - chalk: 5.3.0 chokidar: 3.6.0 citty: 0.1.6 compatx: 0.1.8 @@ -8975,7 +9080,7 @@ snapshots: node-fetch-native: 1.6.4 ofetch: 1.3.4 ohash: 1.1.3 - openapi-typescript: 6.7.6 + openapi-typescript: 7.0.1(encoding@0.1.13)(typescript@5.4.2) pathe: 1.1.2 perfect-debounce: 1.0.0 pkg-types: 1.1.1 @@ -9015,6 +9120,7 @@ snapshots: - encoding - idb-keyval - supports-color + - typescript - uWebSockets.js node-addon-api@7.1.0: {} @@ -9136,6 +9242,14 @@ snapshots: dependencies: boolbase: 1.0.0 + nunjucks@3.2.4(chokidar@3.6.0): + dependencies: + a-sync-waterfall: 1.0.1 + asap: 2.0.6 + commander: 5.1.0 + optionalDependencies: + chokidar: 3.6.0 + nuxi@3.12.0: optionalDependencies: fsevents: 2.3.3 @@ -9172,7 +9286,7 @@ snapshots: knitwork: 1.1.0 magic-string: 0.30.10 mlly: 1.7.1 - nitropack: nitropack-nightly@2.10.0-28656653.33fb776f(@opentelemetry/api@1.8.0)(encoding@0.1.13) + nitropack: nitropack-nightly@2.10.0-28663815.3a2e6cc4(@opentelemetry/api@1.8.0)(encoding@0.1.13)(typescript@5.4.2) nuxi: 3.12.0 nypm: 0.3.9 ofetch: 1.3.4 @@ -9314,14 +9428,16 @@ snapshots: is-inside-container: 1.0.0 is-wsl: 2.2.0 - openapi-typescript@6.7.6: + openapi-typescript@7.0.1(encoding@0.1.13)(typescript@5.4.2): dependencies: + '@redocly/openapi-core': 1.17.0(encoding@0.1.13)(supports-color@9.4.0) ansi-colors: 4.1.3 - fast-glob: 3.3.2 - js-yaml: 4.1.0 + parse-json: 8.1.0 supports-color: 9.4.0 - undici: 5.28.4 + typescript: 5.4.2 yargs-parser: 21.1.1 + transitivePeerDependencies: + - encoding optionator@0.9.3: dependencies: @@ -9403,6 +9519,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.1.0: + dependencies: + '@babel/code-frame': 7.24.7 + index-to-position: 0.1.2 + type-fest: 4.18.2 + parse-ms@4.0.0: {} parse-path@7.0.0: @@ -9896,6 +10018,8 @@ snapshots: require-directory@2.1.1: {} + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -10071,7 +10195,7 @@ snapshots: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) transitivePeerDependencies: - supports-color @@ -10095,8 +10219,8 @@ snapshots: socks-proxy-agent@8.0.2: dependencies: - agent-base: 7.1.0 - debug: 4.3.5 + agent-base: 7.1.0(supports-color@9.4.0) + debug: 4.3.5(supports-color@9.4.0) socks: 2.8.1 transitivePeerDependencies: - supports-color @@ -10304,7 +10428,7 @@ snapshots: tuf-js@2.2.0: dependencies: '@tufjs/models': 2.0.0 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) make-fetch-happen: 13.0.0 transitivePeerDependencies: - supports-color @@ -10381,10 +10505,6 @@ snapshots: undici-types@5.26.5: {} - undici@5.28.4: - dependencies: - '@fastify/busboy': 2.1.1 - unenv@1.9.0: dependencies: consola: 3.2.3 @@ -10594,7 +10714,7 @@ snapshots: vite-node@1.6.0(@types/node@20.14.9)(terser@5.29.2): dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) pathe: 1.1.2 picocolors: 1.0.0 vite: 5.3.1(@types/node@20.14.9)(terser@5.29.2) @@ -10636,7 +10756,7 @@ snapshots: dependencies: '@antfu/utils': 0.7.8 '@rollup/pluginutils': 5.1.0(rollup@3.29.4) - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) error-stack-parser-es: 0.1.1 fs-extra: 11.2.0 open: 10.1.0 @@ -10733,7 +10853,7 @@ snapshots: vue-eslint-parser@9.4.2(eslint@9.5.0): dependencies: - debug: 4.3.5 + debug: 4.3.5(supports-color@9.4.0) eslint: 9.5.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 @@ -10837,6 +10957,8 @@ snapshots: yallist@4.0.0: {} + yaml-ast-parser@0.0.43: {} + yaml@2.4.1: {} yargs-parser@21.1.1: {} diff --git a/src/commands/init.ts b/src/commands/init.ts index 58751b91..73512806 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,17 +1,24 @@ import { downloadTemplate, startShell } from 'giget' import type { DownloadTemplateResult } from 'giget' -import { relative, resolve } from 'pathe' +import { relative, resolve, dirname, join } from 'pathe' import { consola } from 'consola' -import { installDependencies } from 'nypm' import type { PackageManagerName } from 'nypm' import { defineCommand } from 'citty' +import nunjucks from 'nunjucks' import { sharedArgs } from './_shared' +import { fileURLToPath } from 'node:url' +import { writeFileSync } from 'node:fs' +import { updateConfig } from 'c12/update' +import { readPackageJson, writePackageJson } from '../utils/packageJson' const DEFAULT_REGISTRY = 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' const DEFAULT_TEMPLATE_NAME = 'v3' +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + export default defineCommand({ meta: { name: 'init', @@ -69,6 +76,35 @@ export default defineCommand({ ctx.args.packageManager as PackageManagerName ) + const selectedFeatures = (await consola.prompt( + 'Select additional features', + { + type: 'multiselect', + required: false, + options: [ + { + value: 'eslint', + label: 'Add ESLint for code linting', + }, + { + value: 'prettier', + label: 'Add Prettier for code formatting', + }, + { + value: 'playwright', + label: 'Add Playwright for browser testing', + }, + { + value: 'vitest', + label: 'Add Vitest for unit testing', + }, + ], + } + )) as any as string[] + const features = Object.fromEntries( + selectedFeatures.map((value) => [value, true]) + ) + if (ctx.args.gitInit === undefined) { ctx.args.gitInit = await consola.prompt('Initialize git repository?', { type: 'confirm', @@ -100,35 +136,53 @@ export default defineCommand({ process.exit(1) } + const templateDir = join(__dirname, '..', 'partial-templates') + const engine = nunjucks.configure(templateDir, { + autoescape: false, + trimBlocks: true, + }) + const templateCtx = { + ...features, + packageManager: selectedPackageManager, + } + + if (features.prettier) { + renderPrettierFiles(engine, template.dir, templateCtx) + } + if (features.eslint) { + renderEslintFiles(engine, template.dir, templateCtx) + } + await renderPackageJson(template.dir, features) + // Install project dependencies // or skip installation based on the '--no-install' flag - if (ctx.args.install === false) { - consola.info('Skipping install dependencies step.') - } else { - consola.start('Installing dependencies...') - - try { - await installDependencies({ - cwd: template.dir, - packageManager: { - name: selectedPackageManager, - command: selectedPackageManager, - }, - }) - } catch (err) { - if (process.env.DEBUG) { - throw err - } - consola.error((err as Error).toString()) - process.exit(1) - } + // if (ctx.args.install === false) { + // consola.info('Skipping install dependencies step.') + // } else { + // consola.start('Installing dependencies...') - consola.success('Installation completed.') - } + // try { + // await installDependencies({ + // cwd: template.dir, + // packageManager: { + // name: selectedPackageManager, + // command: selectedPackageManager, + // }, + // }) + // } catch (err) { + // if (process.env.DEBUG) { + // throw err + // } + // consola.error((err as Error).toString()) + // process.exit(1) + // } - if (ctx.args.gitInit) { - await initializeGitRepository(template.dir) - } + // consola.success('Installation completed.') + // } + + // if (ctx.args.gitInit) { + // await initializeGitRepository(template.dir) + // } // Display next steps consola.log( @@ -171,6 +225,74 @@ async function resolvePackageManager(packageManager: PackageManagerName) { }) } +function renderPrettierFiles( + engine: nunjucks.Environment, + dir: string, + ctx: any +) { + writeFileSync( + join(dir, '.prettierrc.mjs'), + engine.render('prettier/.prettierrc.mjs', { ctx }) + ) + writeFileSync( + join(dir, '.prettierignore'), + engine.render('prettier/.prettierignore.njk', { ctx }) + ) +} + +function renderEslintFiles( + engine: nunjucks.Environment, + dir: string, + ctx: any +) { + writeFileSync( + join(dir, 'eslint.config.mjs'), + engine.render('eslint/eslint.config.mjs', { ctx }) + ) +} + +async function renderPackageJson( + dir: string, + features: Record +) { + const pkgJson = await readPackageJson(dir) + if (features.prettier) { + pkgJson.devDependencies ??= {} + pkgJson.devDependencies['prettier'] = 'latest' + + if (features.eslint) { + pkgJson.scripts!['lint'] = 'prettier --check . && eslint .' + } else { + pkgJson.scripts!['lint'] = 'prettier --check .' + } + pkgJson.scripts!['format'] = 'prettier --write .' + } + if (features.eslint) { + pkgJson.devDependencies ??= {} + pkgJson.devDependencies['eslint'] = 'latest' + pkgJson.devDependencies['@nuxt/eslint'] = 'latest' + + if (!features.prettier) { + pkgJson.scripts!['lint'] = 'eslint .' + } + + await updateConfig({ + cwd: dir, + configFile: 'nuxt.config', + async onUpdate(config) { + if (!config.modules) { + config.modules = [] + } + if (config.modules.includes('@nuxt/eslint')) { + return + } + config.modules.push('@nuxt/eslint') + }, + }) + } + writePackageJson(dir, pkgJson) +} + async function initializeGitRepository(templateDir: string) { consola.info('Initializing git repository...\n') const { execa } = await import('execa') diff --git a/src/commands/module/_utils.ts b/src/commands/module/_utils.ts index 06f2a806..e08911b9 100644 --- a/src/commands/module/_utils.ts +++ b/src/commands/module/_utils.ts @@ -1,6 +1,7 @@ import { $fetch } from 'ofetch' import { satisfies, coerce } from 'semver' import { tryRequireModule } from '../../utils/cjs' +import { readPackageJson } from '../../utils/packageJson' export const categories = [ 'Analytics', @@ -121,11 +122,7 @@ export async function getNuxtVersion(cwd: string) { if (nuxtPkg) { return nuxtPkg.version } - const pkg = await getProjectPackage(cwd) + const pkg = await readPackageJson(cwd) const pkgDep = pkg?.dependencies?.['nuxt'] || pkg?.devDependencies?.['nuxt'] return (pkgDep && coerce(pkgDep)?.version) || '3.0.0' } - -export async function getProjectPackage(cwd: string) { - return await tryRequireModule('./package.json', cwd) -} diff --git a/src/commands/module/add.ts b/src/commands/module/add.ts index 440487d1..472f130a 100644 --- a/src/commands/module/add.ts +++ b/src/commands/module/add.ts @@ -7,13 +7,9 @@ import { satisfies } from 'semver' import { updateConfig } from 'c12/update' import { colors } from 'consola/utils' import { sharedArgs } from '../_shared' -import { - checkNuxtCompatibility, - fetchModules, - getNuxtVersion, - getProjectPackage, -} from './_utils' +import { checkNuxtCompatibility, fetchModules, getNuxtVersion } from './_utils' import type { NuxtModule } from './_utils' +import { readPackageJson } from '../../utils/packageJson' export default defineCommand({ meta: { @@ -37,7 +33,7 @@ export default defineCommand({ }, async setup(ctx) { const cwd = resolve(ctx.args.cwd || '.') - const projectPkg = await getProjectPackage(cwd) + const projectPkg = await readPackageJson(cwd) if (!projectPkg.dependencies?.nuxt && !projectPkg.devDependencies?.nuxt) { consola.warn(`No \`nuxt\` dependency detected in \`${cwd}\`.`) diff --git a/src/partial-templates/eslint/eslint.config.mjs b/src/partial-templates/eslint/eslint.config.mjs new file mode 100644 index 00000000..b62c1406 --- /dev/null +++ b/src/partial-templates/eslint/eslint.config.mjs @@ -0,0 +1,3 @@ +import withNuxt from './.nuxt/eslint.config.mjs' + +export default withNuxt() diff --git a/src/partial-templates/prettier/.prettierignore.njk b/src/partial-templates/prettier/.prettierignore.njk new file mode 100644 index 00000000..21e4690e --- /dev/null +++ b/src/partial-templates/prettier/.prettierignore.njk @@ -0,0 +1,3 @@ +{% if ctx.packageManager == 'pnpm' %} +pnpm-lock.yaml +{% endif %} diff --git a/src/partial-templates/prettier/.prettierrc.mjs b/src/partial-templates/prettier/.prettierrc.mjs new file mode 100644 index 00000000..4e3d5deb --- /dev/null +++ b/src/partial-templates/prettier/.prettierrc.mjs @@ -0,0 +1,5 @@ +/** @type {import("prettier").Options} */ +export default { + printWidth: 80, + semi: false, +} diff --git a/src/utils/packageJson.ts b/src/utils/packageJson.ts new file mode 100644 index 00000000..5c3e4496 --- /dev/null +++ b/src/utils/packageJson.ts @@ -0,0 +1,12 @@ +import type { PackageJson } from 'pkg-types' +import { tryRequireModule } from './cjs' +import { writeFileSync } from 'node:fs' +import { join } from 'pathe' + +export async function readPackageJson(dir: string): Promise { + return await tryRequireModule('./package.json', dir) +} + +export function writePackageJson(dir: string, content: unknown) { + writeFileSync(join(dir, 'package.json'), JSON.stringify(content, null, 2)) +} From 9dafcd537e911d3f9782e2877f3706d70ba5baec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Thu, 4 Jul 2024 20:42:49 +0200 Subject: [PATCH 03/10] add playwright support --- src/commands/init.ts | 119 +++++++++++++----- .../eslint/eslint.config.mjs | 3 - .../eslint/eslint.config.mjs.njk | 12 ++ .../playwright/e2e/index.spec.ts | 8 ++ .../playwright/playwright.config.ts | 78 ++++++++++++ 5 files changed, 184 insertions(+), 36 deletions(-) delete mode 100644 src/partial-templates/eslint/eslint.config.mjs create mode 100644 src/partial-templates/eslint/eslint.config.mjs.njk create mode 100644 src/partial-templates/playwright/e2e/index.spec.ts create mode 100644 src/partial-templates/playwright/playwright.config.ts diff --git a/src/commands/init.ts b/src/commands/init.ts index 73512806..ac120f8a 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -2,13 +2,13 @@ import { downloadTemplate, startShell } from 'giget' import type { DownloadTemplateResult } from 'giget' import { relative, resolve, dirname, join } from 'pathe' import { consola } from 'consola' -import type { PackageManagerName } from 'nypm' +import { installDependencies, type PackageManagerName } from 'nypm' import { defineCommand } from 'citty' import nunjucks from 'nunjucks' import { sharedArgs } from './_shared' import { fileURLToPath } from 'node:url' -import { writeFileSync } from 'node:fs' +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { updateConfig } from 'c12/update' import { readPackageJson, writePackageJson } from '../utils/packageJson' @@ -152,37 +152,24 @@ export default defineCommand({ if (features.eslint) { renderEslintFiles(engine, template.dir, templateCtx) } + if (features.playwright) { + renderPlaywrightFiles(engine, template.dir, templateCtx) + } await renderPackageJson(template.dir, features) // Install project dependencies // or skip installation based on the '--no-install' flag - // if (ctx.args.install === false) { - // consola.info('Skipping install dependencies step.') - // } else { - // consola.start('Installing dependencies...') - - // try { - // await installDependencies({ - // cwd: template.dir, - // packageManager: { - // name: selectedPackageManager, - // command: selectedPackageManager, - // }, - // }) - // } catch (err) { - // if (process.env.DEBUG) { - // throw err - // } - // consola.error((err as Error).toString()) - // process.exit(1) - // } - - // consola.success('Installation completed.') - // } - - // if (ctx.args.gitInit) { - // await initializeGitRepository(template.dir) - // } + if (ctx.args.install === false) { + consola.info('Skipping install dependencies step.') + } else { + consola.start('Installing dependencies...') + await installProjectDependencies(template.dir, selectedPackageManager) + consola.success('Installation completed.') + } + + if (ctx.args.gitInit) { + await initializeGitRepository(template.dir) + } // Display next steps consola.log( @@ -247,10 +234,40 @@ function renderEslintFiles( ) { writeFileSync( join(dir, 'eslint.config.mjs'), - engine.render('eslint/eslint.config.mjs', { ctx }) + engine.render('eslint/eslint.config.mjs.njk', { ctx }) ) } +function renderPlaywrightFiles( + engine: nunjucks.Environment, + dir: string, + ctx: any +) { + writeFileSync( + join(dir, 'playwright.config.ts'), + engine.render('playwright/playwright.config.ts', { ctx }) + ) + mkdirSync(join(dir, 'e2e'), { recursive: true }) + writeFileSync( + join(dir, 'e2e', 'index.spec.ts'), + engine.render('playwright/e2e/index.spec.ts', { ctx }) + ) + + const ignorePathPatterns = [ + '/test-results/', + '/playwright-report/', + '/blob-report/', + '/playwright/.cache/', + ] + const ignorePaths = `\n#Playwright\n${ignorePathPatterns.join('\n')}\n` + writeFileSync(join(dir, '.nuxtignore'), ignorePaths) + + const gitignoreContents = readFileSync(join(dir, '.gitignore'), { + encoding: 'utf8', + }) + writeFileSync(join(dir, '.gitignore'), gitignoreContents + ignorePaths) +} + async function renderPackageJson( dir: string, features: Record @@ -258,7 +275,7 @@ async function renderPackageJson( const pkgJson = await readPackageJson(dir) if (features.prettier) { pkgJson.devDependencies ??= {} - pkgJson.devDependencies['prettier'] = 'latest' + pkgJson.devDependencies['prettier'] = '^3.1.1' if (features.eslint) { pkgJson.scripts!['lint'] = 'prettier --check . && eslint .' @@ -269,8 +286,8 @@ async function renderPackageJson( } if (features.eslint) { pkgJson.devDependencies ??= {} - pkgJson.devDependencies['eslint'] = 'latest' - pkgJson.devDependencies['@nuxt/eslint'] = 'latest' + pkgJson.devDependencies['eslint'] = '^9.0.0' + pkgJson.devDependencies['@nuxt/eslint'] = '^0.3.13' if (!features.prettier) { pkgJson.scripts!['lint'] = 'eslint .' @@ -290,9 +307,45 @@ async function renderPackageJson( }, }) } + if (features.playwright) { + pkgJson.devDependencies ??= {} + pkgJson.devDependencies['@playwright/test'] = '^1.28.1' + if (features.eslint) { + pkgJson.devDependencies['eslint-plugin-playwright'] = '^1.6.2' + } + + if (features.vitest) { + pkgJson.scripts!['test:e2e'] = 'playwright test' + pkgJson.scripts!['test:unit'] = 'vitest' + pkgJson.scripts!['test'] = 'npm run test:unit && npm run test:e2e' + } else { + pkgJson.scripts!['test'] = 'playwright test' + } + } writePackageJson(dir, pkgJson) } +async function installProjectDependencies( + dir: string, + packageManager: PackageManagerName +) { + try { + await installDependencies({ + cwd: dir, + packageManager: { + name: packageManager, + command: packageManager, + }, + }) + } catch (err) { + if (process.env.DEBUG) { + throw err + } + consola.error((err as Error).toString()) + process.exit(1) + } +} + async function initializeGitRepository(templateDir: string) { consola.info('Initializing git repository...\n') const { execa } = await import('execa') diff --git a/src/partial-templates/eslint/eslint.config.mjs b/src/partial-templates/eslint/eslint.config.mjs deleted file mode 100644 index b62c1406..00000000 --- a/src/partial-templates/eslint/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import withNuxt from './.nuxt/eslint.config.mjs' - -export default withNuxt() diff --git a/src/partial-templates/eslint/eslint.config.mjs.njk b/src/partial-templates/eslint/eslint.config.mjs.njk new file mode 100644 index 00000000..6a98158c --- /dev/null +++ b/src/partial-templates/eslint/eslint.config.mjs.njk @@ -0,0 +1,12 @@ +import withNuxt from './.nuxt/eslint.config.mjs' +{% if ctx.playwright %} +import playwright from "eslint-plugin-playwright" +{% endif %} + +export default withNuxt() +{% if ctx.playwright %} +.append({ + ...playwright.configs["flat/recommended"], + files: ["e2e/**"], +}) +{% endif %} \ No newline at end of file diff --git a/src/partial-templates/playwright/e2e/index.spec.ts b/src/partial-templates/playwright/e2e/index.spec.ts new file mode 100644 index 00000000..b1c5fae4 --- /dev/null +++ b/src/partial-templates/playwright/e2e/index.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test' + +test('has title', async ({ page }) => { + await page.goto('http://localhost:3000/') + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Welcome to Nuxt!/) +}) diff --git a/src/partial-templates/playwright/playwright.config.ts b/src/partial-templates/playwright/playwright.config.ts new file mode 100644 index 00000000..8ee5bbc9 --- /dev/null +++ b/src/partial-templates/playwright/playwright.config.ts @@ -0,0 +1,78 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run dev', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}) From 4f8134a8f627b3ab159ccef66cddb4a8493f39a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Thu, 4 Jul 2024 22:04:17 +0200 Subject: [PATCH 04/10] add vitest support --- src/commands/init.ts | 64 +++++++++++++++---- .../eslint/eslint.config.mjs.njk | 14 ++++ src/partial-templates/vitest/app.spec.ts | 10 +++ .../vitest/vitest.config.mts | 7 ++ 4 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 src/partial-templates/vitest/app.spec.ts create mode 100644 src/partial-templates/vitest/vitest.config.mts diff --git a/src/commands/init.ts b/src/commands/init.ts index ac120f8a..3954aa40 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -155,6 +155,10 @@ export default defineCommand({ if (features.playwright) { renderPlaywrightFiles(engine, template.dir, templateCtx) } + if (features.vitest) { + renderVitestFiles(engine, template.dir, templateCtx) + } + await renderPackageJson(template.dir, features) // Install project dependencies @@ -268,6 +272,21 @@ function renderPlaywrightFiles( writeFileSync(join(dir, '.gitignore'), gitignoreContents + ignorePaths) } +function renderVitestFiles( + engine: nunjucks.Environment, + dir: string, + ctx: any +) { + writeFileSync( + join(dir, 'app.spec.ts'), + engine.render('vitest/vitest.config.mts', { ctx }) + ) + writeFileSync( + join(dir, 'app.spec.ts'), + engine.render('vitest/app.spec.ts', { ctx }) + ) +} + async function renderPackageJson( dir: string, features: Record @@ -293,19 +312,7 @@ async function renderPackageJson( pkgJson.scripts!['lint'] = 'eslint .' } - await updateConfig({ - cwd: dir, - configFile: 'nuxt.config', - async onUpdate(config) { - if (!config.modules) { - config.modules = [] - } - if (config.modules.includes('@nuxt/eslint')) { - return - } - config.modules.push('@nuxt/eslint') - }, - }) + await addModuleToNuxtConfig(dir, '@nuxt/eslint') } if (features.playwright) { pkgJson.devDependencies ??= {} @@ -316,12 +323,26 @@ async function renderPackageJson( if (features.vitest) { pkgJson.scripts!['test:e2e'] = 'playwright test' - pkgJson.scripts!['test:unit'] = 'vitest' + pkgJson.scripts!['test:unit'] = 'vitest run' pkgJson.scripts!['test'] = 'npm run test:unit && npm run test:e2e' } else { pkgJson.scripts!['test'] = 'playwright test' } } + if (features.vitest) { + pkgJson.devDependencies ??= {} + pkgJson.devDependencies['@nuxt/test-utils'] = '^3.13.1' + pkgJson.devDependencies['@testing-library/vue'] = '^8.1.0' + pkgJson.devDependencies['@vue/test-utils'] = '^2.4.6' + pkgJson.devDependencies['happy-dom'] = '^14.12.3' + pkgJson.devDependencies['vitest'] = '^1.6.0' + + await addModuleToNuxtConfig(dir, '@nuxt/test-utils') + + if (!features.playwright) { + pkgJson.scripts!['test'] = 'vitest run' + } + } writePackageJson(dir, pkgJson) } @@ -346,6 +367,21 @@ async function installProjectDependencies( } } +async function addModuleToNuxtConfig(dir: string, moduleName: string) { + return await updateConfig({ + cwd: dir, + configFile: 'nuxt.config', + async onUpdate(config) { + if (!config.modules) { + config.modules = [] + } + if (!config.modules.includes(moduleName)) { + config.modules.push(moduleName) + } + }, + }) +} + async function initializeGitRepository(templateDir: string) { consola.info('Initializing git repository...\n') const { execa } = await import('execa') diff --git a/src/partial-templates/eslint/eslint.config.mjs.njk b/src/partial-templates/eslint/eslint.config.mjs.njk index 6a98158c..3ef5a22e 100644 --- a/src/partial-templates/eslint/eslint.config.mjs.njk +++ b/src/partial-templates/eslint/eslint.config.mjs.njk @@ -2,6 +2,9 @@ import withNuxt from './.nuxt/eslint.config.mjs' {% if ctx.playwright %} import playwright from "eslint-plugin-playwright" {% endif %} +{% if ctx.playwright %} +import vitest from "eslint-plugin-vitest" +{% endif %} export default withNuxt() {% if ctx.playwright %} @@ -9,4 +12,15 @@ export default withNuxt() ...playwright.configs["flat/recommended"], files: ["e2e/**"], }) +{% endif %} +{% if ctx.vitest %} +.append({ + files: ["!e2e2/**", "**/*.spec.ts"], + plugins: { + vitest, + }, + rules: { + ...vitest.configs.recommended.rules, + }, +}) {% endif %} \ No newline at end of file diff --git a/src/partial-templates/vitest/app.spec.ts b/src/partial-templates/vitest/app.spec.ts new file mode 100644 index 00000000..c17a63eb --- /dev/null +++ b/src/partial-templates/vitest/app.spec.ts @@ -0,0 +1,10 @@ +// @vitest-environment nuxt +import { expect, test } from 'vitest' +import { renderSuspended } from '@nuxt/test-utils/runtime' +import { screen } from '@testing-library/vue' +import App from '~/app.vue' + +test('my test', async () => { + await renderSuspended(App) + expect(screen.getByText('Get started')).toBeDefined() +}) diff --git a/src/partial-templates/vitest/vitest.config.mts b/src/partial-templates/vitest/vitest.config.mts new file mode 100644 index 00000000..f1d27bba --- /dev/null +++ b/src/partial-templates/vitest/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineVitestConfig } from '@nuxt/test-utils/config' + +export default defineVitestConfig({ + test: { + exclude: ['node_modules', 'e2e'], + }, +}) From 8e0143e5441daed8399a056f20cd44bff9e97339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Thu, 4 Jul 2024 22:56:02 +0200 Subject: [PATCH 05/10] refactor code --- src/commands/init.ts | 189 +++++++++++------- ...itest.config.mts => vitest.config.mts.njk} | 2 + 2 files changed, 117 insertions(+), 74 deletions(-) rename src/partial-templates/vitest/{vitest.config.mts => vitest.config.mts.njk} (75%) diff --git a/src/commands/init.ts b/src/commands/init.ts index 3954aa40..0a183a52 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -4,7 +4,7 @@ import { relative, resolve, dirname, join } from 'pathe' import { consola } from 'consola' import { installDependencies, type PackageManagerName } from 'nypm' import { defineCommand } from 'citty' -import nunjucks from 'nunjucks' +import type nunjucks from 'nunjucks' import { sharedArgs } from './_shared' import { fileURLToPath } from 'node:url' @@ -16,9 +16,6 @@ const DEFAULT_REGISTRY = 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' const DEFAULT_TEMPLATE_NAME = 'v3' -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) - export default defineCommand({ meta: { name: 'init', @@ -66,6 +63,10 @@ export default defineCommand({ type: 'string', description: 'Package manager choice (npm, pnpm, yarn, bun)', }, + skipExtras: { + type: 'boolean', + description: 'Skip setting up any extra technology', + }, }, async run(ctx) { const cwd = resolve(ctx.args.cwd || '.') @@ -76,34 +77,7 @@ export default defineCommand({ ctx.args.packageManager as PackageManagerName ) - const selectedFeatures = (await consola.prompt( - 'Select additional features', - { - type: 'multiselect', - required: false, - options: [ - { - value: 'eslint', - label: 'Add ESLint for code linting', - }, - { - value: 'prettier', - label: 'Add Prettier for code formatting', - }, - { - value: 'playwright', - label: 'Add Playwright for browser testing', - }, - { - value: 'vitest', - label: 'Add Vitest for unit testing', - }, - ], - } - )) as any as string[] - const features = Object.fromEntries( - selectedFeatures.map((value) => [value, true]) - ) + const extraFeatures = await resolveExtraFeatures(ctx.args.skipExtras) if (ctx.args.gitInit === undefined) { ctx.args.gitInit = await consola.prompt('Initialize git repository?', { @@ -136,31 +110,10 @@ export default defineCommand({ process.exit(1) } - const templateDir = join(__dirname, '..', 'partial-templates') - const engine = nunjucks.configure(templateDir, { - autoescape: false, - trimBlocks: true, - }) - const templateCtx = { - ...features, - packageManager: selectedPackageManager, - } - - if (features.prettier) { - renderPrettierFiles(engine, template.dir, templateCtx) - } - if (features.eslint) { - renderEslintFiles(engine, template.dir, templateCtx) - } - if (features.playwright) { - renderPlaywrightFiles(engine, template.dir, templateCtx) - } - if (features.vitest) { - renderVitestFiles(engine, template.dir, templateCtx) + if (anyExtraSelected(extraFeatures)) { + setupExtras(template, selectedPackageManager, extraFeatures) } - await renderPackageJson(template.dir, features) - // Install project dependencies // or skip installation based on the '--no-install' flag if (ctx.args.install === false) { @@ -197,6 +150,62 @@ export default defineCommand({ }, }) +interface ExtraFeatures { + eslint: boolean + prettier: boolean + playwright: boolean + vitest: boolean +} + +async function resolveExtraFeatures( + skipExtras: boolean +): Promise { + const DEFAULT = { + eslint: false, + prettier: false, + playwright: false, + vitest: false, + } + if (skipExtras) { + return DEFAULT + } + + const selectedExtras = await consola.prompt('Select extra features', { + type: 'multiselect', + required: false, + options: [ + { + value: 'eslint', + label: 'Add ESLint for code linting', + }, + { + value: 'prettier', + label: 'Add Prettier for code formatting', + }, + { + value: 'playwright', + label: 'Add Playwright for browser testing', + }, + { + value: 'vitest', + label: 'Add Vitest for unit testing', + }, + ], + }) + + const selectedExtrasObject = Object.fromEntries( + selectedExtras.map((extra) => [extra, true]) + ) + return { + ...DEFAULT, + ...selectedExtrasObject, + } +} + +function anyExtraSelected(extras: ExtraFeatures) { + return Object.values(extras).some((extra) => !!extra) +} + async function resolvePackageManager(packageManager: PackageManagerName) { const packageManagerOptions: PackageManagerName[] = [ 'npm', @@ -216,6 +225,39 @@ async function resolvePackageManager(packageManager: PackageManagerName) { }) } +async function setupExtras( + template: DownloadTemplateResult, + packageManager: PackageManagerName, + extras: ExtraFeatures +) { + const __dirname = dirname(fileURLToPath(import.meta.url)) + const templateDir = join(__dirname, '..', 'partial-templates') + const nunjucks = await import('nunjucks') + const engine = nunjucks.configure(templateDir, { + autoescape: false, + lstripBlocks: true, + trimBlocks: true, + }) + const templateCtx = { + ...extras, + packageManager, + } + + if (extras.prettier) { + renderPrettierFiles(engine, template.dir, templateCtx) + } + if (extras.eslint) { + renderEslintFiles(engine, template.dir, templateCtx) + } + if (extras.playwright) { + renderPlaywrightFiles(engine, template.dir, templateCtx) + } + if (extras.vitest) { + renderVitestFiles(engine, template.dir, templateCtx) + } + await renderPackageJson(template.dir, extras) +} + function renderPrettierFiles( engine: nunjucks.Environment, dir: string, @@ -278,8 +320,8 @@ function renderVitestFiles( ctx: any ) { writeFileSync( - join(dir, 'app.spec.ts'), - engine.render('vitest/vitest.config.mts', { ctx }) + join(dir, 'vitest.config.mts'), + engine.render('vitest/vitest.config.mts.njk', { ctx }) ) writeFileSync( join(dir, 'app.spec.ts'), @@ -287,60 +329,59 @@ function renderVitestFiles( ) } -async function renderPackageJson( - dir: string, - features: Record -) { +async function renderPackageJson(dir: string, features: ExtraFeatures) { const pkgJson = await readPackageJson(dir) + pkgJson.scripts ??= {} + pkgJson.devDependencies ??= {} + if (features.prettier) { - pkgJson.devDependencies ??= {} pkgJson.devDependencies['prettier'] = '^3.1.1' if (features.eslint) { - pkgJson.scripts!['lint'] = 'prettier --check . && eslint .' + pkgJson.scripts['lint'] = 'prettier --check . && eslint .' } else { - pkgJson.scripts!['lint'] = 'prettier --check .' + pkgJson.scripts['lint'] = 'prettier --check .' } - pkgJson.scripts!['format'] = 'prettier --write .' + pkgJson.scripts['format'] = 'prettier --write .' } if (features.eslint) { - pkgJson.devDependencies ??= {} pkgJson.devDependencies['eslint'] = '^9.0.0' pkgJson.devDependencies['@nuxt/eslint'] = '^0.3.13' if (!features.prettier) { - pkgJson.scripts!['lint'] = 'eslint .' + pkgJson.scripts['lint'] = 'eslint .' } await addModuleToNuxtConfig(dir, '@nuxt/eslint') } if (features.playwright) { - pkgJson.devDependencies ??= {} pkgJson.devDependencies['@playwright/test'] = '^1.28.1' if (features.eslint) { pkgJson.devDependencies['eslint-plugin-playwright'] = '^1.6.2' } if (features.vitest) { - pkgJson.scripts!['test:e2e'] = 'playwright test' - pkgJson.scripts!['test:unit'] = 'vitest run' - pkgJson.scripts!['test'] = 'npm run test:unit && npm run test:e2e' + pkgJson.scripts['test:e2e'] = 'playwright test' + pkgJson.scripts['test:unit'] = 'vitest run' + pkgJson.scripts['test'] = 'npm run test:unit && npm run test:e2e' } else { - pkgJson.scripts!['test'] = 'playwright test' + pkgJson.scripts['test'] = 'playwright test' } } if (features.vitest) { - pkgJson.devDependencies ??= {} pkgJson.devDependencies['@nuxt/test-utils'] = '^3.13.1' pkgJson.devDependencies['@testing-library/vue'] = '^8.1.0' pkgJson.devDependencies['@vue/test-utils'] = '^2.4.6' pkgJson.devDependencies['happy-dom'] = '^14.12.3' pkgJson.devDependencies['vitest'] = '^1.6.0' + if (features.eslint) { + pkgJson.devDependencies['eslint-plugin-vitest'] = '^0.5.4' + } await addModuleToNuxtConfig(dir, '@nuxt/test-utils') if (!features.playwright) { - pkgJson.scripts!['test'] = 'vitest run' + pkgJson.scripts['test'] = 'vitest run' } } writePackageJson(dir, pkgJson) diff --git a/src/partial-templates/vitest/vitest.config.mts b/src/partial-templates/vitest/vitest.config.mts.njk similarity index 75% rename from src/partial-templates/vitest/vitest.config.mts rename to src/partial-templates/vitest/vitest.config.mts.njk index f1d27bba..28c60f53 100644 --- a/src/partial-templates/vitest/vitest.config.mts +++ b/src/partial-templates/vitest/vitest.config.mts.njk @@ -1,7 +1,9 @@ import { defineVitestConfig } from '@nuxt/test-utils/config' export default defineVitestConfig({ + {% if ctx.playwright %} test: { exclude: ['node_modules', 'e2e'], }, + {% endif %} }) From 8494244757427559fb563de4c4da2c3d9b347951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Fri, 5 Jul 2024 13:27:07 +0200 Subject: [PATCH 06/10] add vscode config --- src/commands/init.ts | 39 ++++++++++++++++--- src/partial-templates/vitest/app.spec.ts | 2 +- .../vscode/extensions.json.njk | 18 +++++++++ .../vscode/settings.json.njk | 12 ++++++ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/partial-templates/vscode/extensions.json.njk create mode 100644 src/partial-templates/vscode/settings.json.njk diff --git a/src/commands/init.ts b/src/commands/init.ts index 0a183a52..209b6f6a 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -155,6 +155,7 @@ interface ExtraFeatures { prettier: boolean playwright: boolean vitest: boolean + vscode: boolean } async function resolveExtraFeatures( @@ -165,6 +166,7 @@ async function resolveExtraFeatures( prettier: false, playwright: false, vitest: false, + vscode: false, } if (skipExtras) { return DEFAULT @@ -190,6 +192,10 @@ async function resolveExtraFeatures( value: 'vitest', label: 'Add Vitest for unit testing', }, + { + value: 'vscode', + label: 'Setup configuration for VSCode', + }, ], }) @@ -238,7 +244,7 @@ async function setupExtras( lstripBlocks: true, trimBlocks: true, }) - const templateCtx = { + const templateCtx: TemplateContext = { ...extras, packageManager, } @@ -255,13 +261,20 @@ async function setupExtras( if (extras.vitest) { renderVitestFiles(engine, template.dir, templateCtx) } + if (extras.vscode) { + renderVSCodeFiles(engine, template.dir, templateCtx) + } await renderPackageJson(template.dir, extras) } +interface TemplateContext extends ExtraFeatures { + packageManager: PackageManagerName +} + function renderPrettierFiles( engine: nunjucks.Environment, dir: string, - ctx: any + ctx: TemplateContext ) { writeFileSync( join(dir, '.prettierrc.mjs'), @@ -276,7 +289,7 @@ function renderPrettierFiles( function renderEslintFiles( engine: nunjucks.Environment, dir: string, - ctx: any + ctx: TemplateContext ) { writeFileSync( join(dir, 'eslint.config.mjs'), @@ -287,7 +300,7 @@ function renderEslintFiles( function renderPlaywrightFiles( engine: nunjucks.Environment, dir: string, - ctx: any + ctx: TemplateContext ) { writeFileSync( join(dir, 'playwright.config.ts'), @@ -317,7 +330,7 @@ function renderPlaywrightFiles( function renderVitestFiles( engine: nunjucks.Environment, dir: string, - ctx: any + ctx: TemplateContext ) { writeFileSync( join(dir, 'vitest.config.mts'), @@ -329,6 +342,22 @@ function renderVitestFiles( ) } +function renderVSCodeFiles( + engine: nunjucks.Environment, + dir: string, + ctx: TemplateContext +) { + mkdirSync(join(dir, '.vscode'), { recursive: true }) + writeFileSync( + join(dir, '.vscode', 'extensions.json'), + engine.render('vscode/extensions.json.njk', { ctx }) + ) + writeFileSync( + join(dir, '.vscode', 'settings.json'), + engine.render('vscode/settings.json.njk', { ctx }) + ) +} + async function renderPackageJson(dir: string, features: ExtraFeatures) { const pkgJson = await readPackageJson(dir) pkgJson.scripts ??= {} diff --git a/src/partial-templates/vitest/app.spec.ts b/src/partial-templates/vitest/app.spec.ts index c17a63eb..6203e446 100644 --- a/src/partial-templates/vitest/app.spec.ts +++ b/src/partial-templates/vitest/app.spec.ts @@ -4,7 +4,7 @@ import { renderSuspended } from '@nuxt/test-utils/runtime' import { screen } from '@testing-library/vue' import App from '~/app.vue' -test('my test', async () => { +test('app has getting started section', async () => { await renderSuspended(App) expect(screen.getByText('Get started')).toBeDefined() }) diff --git a/src/partial-templates/vscode/extensions.json.njk b/src/partial-templates/vscode/extensions.json.njk new file mode 100644 index 00000000..3f1c0462 --- /dev/null +++ b/src/partial-templates/vscode/extensions.json.njk @@ -0,0 +1,18 @@ +{ + "recommendations": [ + "vue.volar", + "antfu.goto-alias", + {% if ctx.prettier %} + "esbenp.prettier-vscode", + {% endif %} + {% if ctx.eslint %} + "dbaeumer.vscode-eslint", + {% endif %} + {% if ctx.playwright %} + "ms-playwright.playwright", + {% endif %} + {% if ctx.vitest %} + "vitest.explorer" + {% endif %} + ] +} diff --git a/src/partial-templates/vscode/settings.json.njk b/src/partial-templates/vscode/settings.json.njk new file mode 100644 index 00000000..aedaaa93 --- /dev/null +++ b/src/partial-templates/vscode/settings.json.njk @@ -0,0 +1,12 @@ +{ + {% if ctx.prettier %} + "editor.defaultFormatter": "esbenp.prettier-vscode", + {% endif %} + {% if ctx.eslint %} + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + {% endif %} + "vue.complete.casing.props": "kebab", + "vue.complete.casing.tags": "pascal" +} From 23104c64d7cf2d3e9e5ffeb126896cd2af50d85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Fri, 5 Jul 2024 13:31:29 +0200 Subject: [PATCH 07/10] fix eslint config --- eslint.config.js | 6 ++++-- src/partial-templates/eslint/eslint.config.mjs.njk | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index dc12cea3..0512a302 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,10 +4,12 @@ import { createConfigForNuxt } from '@nuxt/eslint-config/flat' export default createConfigForNuxt({ features: { tooling: true, - stylistic: false, + stylistic: true, }, dirs: { - src: ['./playground'], + src: [ + './playground', + ], }, }).append({ rules: { diff --git a/src/partial-templates/eslint/eslint.config.mjs.njk b/src/partial-templates/eslint/eslint.config.mjs.njk index 3ef5a22e..c7d6c95b 100644 --- a/src/partial-templates/eslint/eslint.config.mjs.njk +++ b/src/partial-templates/eslint/eslint.config.mjs.njk @@ -2,7 +2,7 @@ import withNuxt from './.nuxt/eslint.config.mjs' {% if ctx.playwright %} import playwright from "eslint-plugin-playwright" {% endif %} -{% if ctx.playwright %} +{% if ctx.vitest %} import vitest from "eslint-plugin-vitest" {% endif %} From 86a9b70729dd3857b0d12bf3b252e7cff0780e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Fri, 5 Jul 2024 13:35:19 +0200 Subject: [PATCH 08/10] lint fix --- .gitignore | 1 - .prettierrc | 5 --- .vscode/settings.json | 3 ++ src/commands/init.ts | 83 ++++++++++++++++++++------------------ src/commands/module/add.ts | 2 +- src/utils/packageJson.ts | 4 +- 6 files changed, 50 insertions(+), 48 deletions(-) delete mode 100644 .prettierrc create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 2379c916..e69cd11d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ node_modules dist -.vscode .nuxt nuxt-app .pnpm-store diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 6caa81c4..00000000 --- a/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "semi": false, - "singleQuote": true, - "vueIndentScriptAndStyle": false -} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..12b5db15 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "prettier.enable": false +} diff --git a/src/commands/init.ts b/src/commands/init.ts index 209b6f6a..cd4bc24b 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,3 +1,5 @@ +import { fileURLToPath } from 'node:url' +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { downloadTemplate, startShell } from 'giget' import type { DownloadTemplateResult } from 'giget' import { relative, resolve, dirname, join } from 'pathe' @@ -6,14 +8,12 @@ import { installDependencies, type PackageManagerName } from 'nypm' import { defineCommand } from 'citty' import type nunjucks from 'nunjucks' -import { sharedArgs } from './_shared' -import { fileURLToPath } from 'node:url' -import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { updateConfig } from 'c12/update' import { readPackageJson, writePackageJson } from '../utils/packageJson' +import { sharedArgs } from './_shared' -const DEFAULT_REGISTRY = - 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' +const DEFAULT_REGISTRY + = 'https://raw.githubusercontent.com/nuxt/starter/templates/templates' const DEFAULT_TEMPLATE_NAME = 'v3' export default defineCommand({ @@ -74,7 +74,7 @@ export default defineCommand({ const templateName = ctx.args.template || DEFAULT_TEMPLATE_NAME const selectedPackageManager = await resolvePackageManager( - ctx.args.packageManager as PackageManagerName + ctx.args.packageManager as PackageManagerName, ) const extraFeatures = await resolveExtraFeatures(ctx.args.skipExtras) @@ -102,7 +102,8 @@ export default defineCommand({ preferOffline: Boolean(ctx.args.preferOffline), registry: process.env.NUXI_INIT_REGISTRY || DEFAULT_REGISTRY, }) - } catch (err) { + } + catch (err) { if (process.env.DEBUG) { throw err } @@ -118,7 +119,8 @@ export default defineCommand({ // or skip installation based on the '--no-install' flag if (ctx.args.install === false) { consola.info('Skipping install dependencies step.') - } else { + } + else { consola.start('Installing dependencies...') await installProjectDependencies(template.dir, selectedPackageManager) consola.success('Installation completed.') @@ -130,13 +132,13 @@ export default defineCommand({ // Display next steps consola.log( - `\n✨ Nuxt project has been created with the \`${template.name}\` template. Next steps:` + `\n✨ Nuxt project has been created with the \`${template.name}\` template. Next steps:`, ) const relativeTemplateDir = relative(process.cwd(), template.dir) || '.' const nextSteps = [ - !ctx.args.shell && - relativeTemplateDir.length > 1 && - `\`cd ${relativeTemplateDir}\``, + !ctx.args.shell + && relativeTemplateDir.length > 1 + && `\`cd ${relativeTemplateDir}\``, `Start development server with \`${selectedPackageManager} run dev\``, ].filter(Boolean) @@ -159,7 +161,7 @@ interface ExtraFeatures { } async function resolveExtraFeatures( - skipExtras: boolean + skipExtras: boolean, ): Promise { const DEFAULT = { eslint: false, @@ -200,7 +202,7 @@ async function resolveExtraFeatures( }) const selectedExtrasObject = Object.fromEntries( - selectedExtras.map((extra) => [extra, true]) + selectedExtras.map(extra => [extra, true]), ) return { ...DEFAULT, @@ -209,7 +211,7 @@ async function resolveExtraFeatures( } function anyExtraSelected(extras: ExtraFeatures) { - return Object.values(extras).some((extra) => !!extra) + return Object.values(extras).some(extra => !!extra) } async function resolvePackageManager(packageManager: PackageManagerName) { @@ -220,21 +222,21 @@ async function resolvePackageManager(packageManager: PackageManagerName) { 'bun', ] - const isSupportedPackageManager = - packageManagerOptions.includes(packageManager) + const isSupportedPackageManager + = packageManagerOptions.includes(packageManager) return isSupportedPackageManager ? packageManager : await consola.prompt('Which package manager would you like to use?', { - type: 'select', - options: packageManagerOptions, - }) + type: 'select', + options: packageManagerOptions, + }) } async function setupExtras( template: DownloadTemplateResult, packageManager: PackageManagerName, - extras: ExtraFeatures + extras: ExtraFeatures, ) { const __dirname = dirname(fileURLToPath(import.meta.url)) const templateDir = join(__dirname, '..', 'partial-templates') @@ -274,42 +276,42 @@ interface TemplateContext extends ExtraFeatures { function renderPrettierFiles( engine: nunjucks.Environment, dir: string, - ctx: TemplateContext + ctx: TemplateContext, ) { writeFileSync( join(dir, '.prettierrc.mjs'), - engine.render('prettier/.prettierrc.mjs', { ctx }) + engine.render('prettier/.prettierrc.mjs', { ctx }), ) writeFileSync( join(dir, '.prettierignore'), - engine.render('prettier/.prettierignore.njk', { ctx }) + engine.render('prettier/.prettierignore.njk', { ctx }), ) } function renderEslintFiles( engine: nunjucks.Environment, dir: string, - ctx: TemplateContext + ctx: TemplateContext, ) { writeFileSync( join(dir, 'eslint.config.mjs'), - engine.render('eslint/eslint.config.mjs.njk', { ctx }) + engine.render('eslint/eslint.config.mjs.njk', { ctx }), ) } function renderPlaywrightFiles( engine: nunjucks.Environment, dir: string, - ctx: TemplateContext + ctx: TemplateContext, ) { writeFileSync( join(dir, 'playwright.config.ts'), - engine.render('playwright/playwright.config.ts', { ctx }) + engine.render('playwright/playwright.config.ts', { ctx }), ) mkdirSync(join(dir, 'e2e'), { recursive: true }) writeFileSync( join(dir, 'e2e', 'index.spec.ts'), - engine.render('playwright/e2e/index.spec.ts', { ctx }) + engine.render('playwright/e2e/index.spec.ts', { ctx }), ) const ignorePathPatterns = [ @@ -330,31 +332,31 @@ function renderPlaywrightFiles( function renderVitestFiles( engine: nunjucks.Environment, dir: string, - ctx: TemplateContext + ctx: TemplateContext, ) { writeFileSync( join(dir, 'vitest.config.mts'), - engine.render('vitest/vitest.config.mts.njk', { ctx }) + engine.render('vitest/vitest.config.mts.njk', { ctx }), ) writeFileSync( join(dir, 'app.spec.ts'), - engine.render('vitest/app.spec.ts', { ctx }) + engine.render('vitest/app.spec.ts', { ctx }), ) } function renderVSCodeFiles( engine: nunjucks.Environment, dir: string, - ctx: TemplateContext + ctx: TemplateContext, ) { mkdirSync(join(dir, '.vscode'), { recursive: true }) writeFileSync( join(dir, '.vscode', 'extensions.json'), - engine.render('vscode/extensions.json.njk', { ctx }) + engine.render('vscode/extensions.json.njk', { ctx }), ) writeFileSync( join(dir, '.vscode', 'settings.json'), - engine.render('vscode/settings.json.njk', { ctx }) + engine.render('vscode/settings.json.njk', { ctx }), ) } @@ -368,7 +370,8 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { if (features.eslint) { pkgJson.scripts['lint'] = 'prettier --check . && eslint .' - } else { + } + else { pkgJson.scripts['lint'] = 'prettier --check .' } pkgJson.scripts['format'] = 'prettier --write .' @@ -393,7 +396,8 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { pkgJson.scripts['test:e2e'] = 'playwright test' pkgJson.scripts['test:unit'] = 'vitest run' pkgJson.scripts['test'] = 'npm run test:unit && npm run test:e2e' - } else { + } + else { pkgJson.scripts['test'] = 'playwright test' } } @@ -418,7 +422,7 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { async function installProjectDependencies( dir: string, - packageManager: PackageManagerName + packageManager: PackageManagerName, ) { try { await installDependencies({ @@ -428,7 +432,8 @@ async function installProjectDependencies( command: packageManager, }, }) - } catch (err) { + } + catch (err) { if (process.env.DEBUG) { throw err } diff --git a/src/commands/module/add.ts b/src/commands/module/add.ts index 472f130a..6c6ec424 100644 --- a/src/commands/module/add.ts +++ b/src/commands/module/add.ts @@ -7,9 +7,9 @@ import { satisfies } from 'semver' import { updateConfig } from 'c12/update' import { colors } from 'consola/utils' import { sharedArgs } from '../_shared' +import { readPackageJson } from '../../utils/packageJson' import { checkNuxtCompatibility, fetchModules, getNuxtVersion } from './_utils' import type { NuxtModule } from './_utils' -import { readPackageJson } from '../../utils/packageJson' export default defineCommand({ meta: { diff --git a/src/utils/packageJson.ts b/src/utils/packageJson.ts index 5c3e4496..1a461690 100644 --- a/src/utils/packageJson.ts +++ b/src/utils/packageJson.ts @@ -1,7 +1,7 @@ -import type { PackageJson } from 'pkg-types' -import { tryRequireModule } from './cjs' import { writeFileSync } from 'node:fs' +import type { PackageJson } from 'pkg-types' import { join } from 'pathe' +import { tryRequireModule } from './cjs' export async function readPackageJson(dir: string): Promise { return await tryRequireModule('./package.json', dir) From 9bfb128651bc04387b78fd188976190ed44b61eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Fri, 5 Jul 2024 15:21:10 +0200 Subject: [PATCH 09/10] add lint fix command --- src/commands/init.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/commands/init.ts b/src/commands/init.ts index cd4bc24b..f2f33f9b 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -381,8 +381,12 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { pkgJson.devDependencies['@nuxt/eslint'] = '^0.3.13' if (!features.prettier) { + pkgJson.scripts['lint:fix'] = 'eslint . --fix' pkgJson.scripts['lint'] = 'eslint .' } + else { + pkgJson.scripts['eslint:fix'] = 'eslint . --fix' + } await addModuleToNuxtConfig(dir, '@nuxt/eslint') } From 8b1c996a869eb8a393f9c3874134db27592b21ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Fri, 16 Aug 2024 17:38:53 +0200 Subject: [PATCH 10/10] update dependencies --- src/commands/init.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index f2f33f9b..e66b08ae 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -366,7 +366,7 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { pkgJson.devDependencies ??= {} if (features.prettier) { - pkgJson.devDependencies['prettier'] = '^3.1.1' + pkgJson.devDependencies['prettier'] = '^3.3.3' if (features.eslint) { pkgJson.scripts['lint'] = 'prettier --check . && eslint .' @@ -377,8 +377,8 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { pkgJson.scripts['format'] = 'prettier --write .' } if (features.eslint) { - pkgJson.devDependencies['eslint'] = '^9.0.0' - pkgJson.devDependencies['@nuxt/eslint'] = '^0.3.13' + pkgJson.devDependencies['eslint'] = '^9.9.0' + pkgJson.devDependencies['@nuxt/eslint'] = '^0.5.0' if (!features.prettier) { pkgJson.scripts['lint:fix'] = 'eslint . --fix' @@ -391,7 +391,7 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { await addModuleToNuxtConfig(dir, '@nuxt/eslint') } if (features.playwright) { - pkgJson.devDependencies['@playwright/test'] = '^1.28.1' + pkgJson.devDependencies['@playwright/test'] = '^1.46.0' if (features.eslint) { pkgJson.devDependencies['eslint-plugin-playwright'] = '^1.6.2' } @@ -406,16 +406,16 @@ async function renderPackageJson(dir: string, features: ExtraFeatures) { } } if (features.vitest) { - pkgJson.devDependencies['@nuxt/test-utils'] = '^3.13.1' + pkgJson.devDependencies['@nuxt/test-utils'] = '^3.14.1' pkgJson.devDependencies['@testing-library/vue'] = '^8.1.0' pkgJson.devDependencies['@vue/test-utils'] = '^2.4.6' pkgJson.devDependencies['happy-dom'] = '^14.12.3' - pkgJson.devDependencies['vitest'] = '^1.6.0' + pkgJson.devDependencies['vitest'] = '^2.0.5' if (features.eslint) { pkgJson.devDependencies['eslint-plugin-vitest'] = '^0.5.4' } - await addModuleToNuxtConfig(dir, '@nuxt/test-utils') + await addModuleToNuxtConfig(dir, '@nuxt/test-utils/module') if (!features.playwright) { pkgJson.scripts['test'] = 'vitest run'