├── .gitignore ├── .gitmodules ├── .prettierignore ├── .vscode └── settings.json ├── README.md ├── index.ts ├── package.json ├── pnpm-lock.yaml ├── prettier.config.js ├── prompts ├── index.json └── types.ts ├── scripts └── build.js ├── template ├── default-ejs │ └── vite │ │ └── plugins.ts.ejs └── vue-devtools │ ├── package.json │ └── vite │ └── plugins.ts.data.mjs ├── tsconfig.json └── utils ├── banner.ts ├── deepMerge.ts ├── directoryTraverse.ts ├── emptyDir.ts ├── packageName.ts ├── renderTemplate.ts └── sortDependencies.ts /.gitignore: -------------------------------------------------------------------------------- 1 | outfile.cjs 2 | my-awesome-site/ 3 | 4 | TODOs 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "template/base"] 2 | path = template/base 3 | url = https://github.com/xiaoyan13/vue-templete 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.enable": true, 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "[javascript]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-vue-xy 2 | 3 | An Out-of-the-Box Scaffolding with Vue.js. 4 | 5 | ## 使用方式 6 | 7 | 打开终端,使用如下命令: 8 | 9 | ```sh 10 | pnpm create vue-xy 11 | ``` 12 | 13 | > [!NOTE] 14 | > 项目只支持一种包管理器 pnpm。这主要是为了保证 lock.json 锁定依赖版本。当然,使用 `corepack` 也可以。 15 | 16 | 选择需要的选项,之后就可以运行了: 17 | 18 | ```sh 19 | pnpm dev 20 | pnpm lint:inspect 21 | git init . 22 | ``` 23 | 24 | ## 基础配置 25 | 26 | 默认的配置包含了: 27 | 28 | - vue + vite + ts, tailwindcss 29 | - 从 0 配置的必要语法检查和格式化。 30 | - 清除了所有默认的 css 样式;只提供了干净的唯一组件 `MainView`, 空的 `router` 和 `pinia` 状态库. 31 | - 提供了一些有用的 `.vscode` 工程配置: `tailwindcss` 的 `@` 指令语法提示支持和 `vue` 的 `code-snippets`. 32 | - 一些(自认为)通用的 `vite` 插件. 33 | 34 | ## 可选配置 35 | 36 | 有几个可选项:`utils`, `axios`, `devtools/vue-devtools`。 37 | 38 | `utils` 提供了一些可能有用的封装: 39 | 40 | - 实现了极小的 _eventBus_ 41 | - 对 vue 本身提供的 _provide_ 和 _inject_ 进行了二次封装,使用局部上下文来代替全局状态管理; 42 | 43 | `axios` 选项配置了通用的 `axios` 请求封装: 44 | 45 | - 开箱即用的 `axios` 的请求封装, 并支持多环境切换: `mock`, `dev`, `prod`。 46 | 47 | `devtools/vue-devtools` 选项,会引入 `vue-devtools` 的 vite 插件。 48 | 49 | 以上这些可选配置也可以通过命令行参数来选择,如: 50 | 51 | ```sh 52 | pnpm create vue-xy --axios # 启用 axios 53 | ``` 54 | 55 | > [!WARNING] 56 | > 需要注意,一旦传入 `--` 的命令行参数,将不会再提供这些可选配置的交互行为。 57 | 58 | 其他还有一些配置是默认的: 59 | 60 | - 项目提供了开箱即用的多语言支持, 内置 [`vue-i18n@9`](https://github.com/intlify/vue-i18n) 的配置。 61 | 62 | ## VSCode 63 | 64 | 在正式开始之前,可能还需要调整一下 vscode: 65 | 66 | **代码库适用于 vscode**. 必需安装的 `vsc` 插件: 67 | 68 | - vue 官方插件 69 | - eslint 官方插件 70 | - tailwind 官方插件 71 | - prettier 官方插件 72 | 73 | > [!WARNING] 74 | > 注意本地已经存在的插件可能会覆盖掉上述插件某些功能,从而影响语法提示,进而影响食用体验。 75 | 76 | - 可选的插件:`i18n` 77 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { parseArgs } from 'util'; 2 | import fs from 'fs'; 3 | import * as banners from './utils/banner'; 4 | import { red, yellow, green, bold } from 'kolorist'; 5 | import path from 'path'; 6 | import prompts from 'prompts'; 7 | import emptyDir from './utils/emptyDir'; 8 | import { isValidPackageName, toValidPackageName } from './utils/packageName'; 9 | import promptsJSON from './prompts/index.json'; 10 | import { renderTemplate } from './utils/renderTemplate'; 11 | import { preOrderDirectoryTraverse } from './utils/directoryTraverse'; 12 | import ejs from 'ejs'; 13 | 14 | // 处理命令行参数 15 | const args = process.argv.slice(2); 16 | const options = { 17 | axios: { type: 'boolean' }, 18 | utils: { type: 'boolean' }, 19 | 'vue-devtools': { type: 'boolean' }, 20 | devtools: { type: 'boolean' }, 21 | } as const; 22 | const { values: argv, positionals } = parseArgs({ 23 | args, 24 | options, 25 | strict: false, 26 | }); 27 | /** 28 | * 方便起见,如果 isFlagUsed 为 true,我们将在下面的交互中跳过 _所有_ 可选的安装选项 29 | * 这意味着只要用户通过命令行传参的方式指定了一个安装项,那么接下来的交互就不会出现任何安装项提供选择 30 | */ 31 | const isFlagUsed: boolean = 32 | typeof ( 33 | argv.axios ?? 34 | argv.utils ?? 35 | (argv.devtools || argv['vue-devtools']) 36 | ) === 'boolean'; 37 | /** 38 | * 方便起见,将仅处理接收 _相对路径_ 作为 targetDir 39 | * 这意味着用户如果使用命令行参数,将只能传入相对路径 40 | */ 41 | let targetDir = positionals[0]; 42 | /** 43 | * 我们一开始有一个 defaultProjectName, 44 | * 他的值取决于一开始用户传入的命令行参数是否为空 45 | */ 46 | const defaultProjectName = targetDir ?? 'my-awesome-site'; 47 | 48 | async function setup() { 49 | console.log(); 50 | console.log( 51 | process.stdout.isTTY && process.stdout.getColorDepth() > 8 52 | ? banners.gradientBanner 53 | : banners.defaultBanner, 54 | ); 55 | console.log(); 56 | 57 | let result: { 58 | projectName?: string; 59 | shouldOverwrite?: string; 60 | packageName?: string; 61 | needsAxios?: boolean; 62 | needsUtils?: boolean; 63 | needsDevTools?: boolean; 64 | } = {}; 65 | 66 | try { 67 | result = await getResult(); 68 | } catch (cancelled) { 69 | console.log(cancelled.toString().split('\n')[0]); 70 | process.exit(1); 71 | } 72 | 73 | // 我们从这里解构出来的是用户命令行交互的结果 74 | // 我们仍然需要将该结果与用户首次运行命令的时候传入的参数选项结合 75 | // 也就是说,解构的时候仍然需要对应的 argv 参数作为默认值 76 | const { 77 | projectName, 78 | // 包名如果解构不出,则默认值取 projectName 79 | packageName = projectName, 80 | shouldOverwrite = argv.force, 81 | needsAxios = argv.axios, 82 | needsUtils = argv.utils, 83 | needsDevTools = argv.devtools || argv['vue-devtools'], 84 | } = result; 85 | 86 | const cwd = process.cwd(); 87 | // 目标目录 88 | const root = path.join(cwd, targetDir); 89 | 90 | console.log(`\n${promptsJSON.infos.scaffolding} ${root}...`); 91 | 92 | // 准备好空的文件夹,准备填充 93 | if (fs.existsSync(root) && shouldOverwrite) { 94 | emptyDir(root); 95 | } else if (!fs.existsSync(root)) { 96 | fs.mkdirSync(root); 97 | } 98 | 99 | // 渲染 100 | const templateRoot = path.resolve(__dirname, 'template'); 101 | const callbacks = []; 102 | 103 | const render = (templateName) => { 104 | const templateDir = path.resolve(templateRoot, templateName); 105 | renderTemplate(templateDir, root, callbacks); 106 | }; 107 | 108 | // 首先渲染 template/base 109 | render('base'); 110 | 111 | // 处理 package.json 的主字段,这里暂时只需要处理 package.json 的 name 字段 112 | const pkgFile = path.resolve(root, 'package.json'); 113 | const packageJSON = JSON.parse(fs.readFileSync(pkgFile, 'utf-8')); 114 | 115 | packageJSON.name = packageName; 116 | 117 | fs.writeFileSync(pkgFile, JSON.stringify(packageJSON, null, 2)); 118 | 119 | // 处理 axios 配置和 utils 配置 120 | if (!needsAxios) { 121 | const targetDir = path.resolve(root, 'src/api'); 122 | if (fs.existsSync(targetDir)) { 123 | emptyDir(targetDir); 124 | fs.rmdirSync(targetDir); 125 | } 126 | } 127 | if (!needsUtils) { 128 | const targetDir = path.resolve(root, 'src/common'); 129 | if (fs.existsSync(targetDir)) { 130 | emptyDir(targetDir); 131 | fs.rmdirSync(targetDir); 132 | } 133 | } 134 | 135 | /** 136 | * 处理 vite 插件。 137 | * 我们使用 ejs 库来拼接出 js/ts 文件。我们约定, 138 | * 某个目录下的 .data.mjs 用来渲染同级目录中的同名 .ejs 文件。 139 | */ 140 | if (needsDevTools) { 141 | // 删除默认的 plugins.ts 142 | fs.unlinkSync(path.resolve(root, 'vite/plugins.ts')); 143 | // 添加初始的 ejs 代码,他们最终将被渲染为对应的同级目录下的同名文件 144 | render('default-ejs'); 145 | // render vue-devtools 需要的 .data.mjs 146 | render('vue-devtools'); 147 | } 148 | 149 | // 收集 150 | const dataStore = {}; 151 | for (const cb of callbacks) { 152 | await cb(dataStore); 153 | } 154 | 155 | // render ejs 156 | preOrderDirectoryTraverse( 157 | root, 158 | () => {}, 159 | (filepath: string) => { 160 | if (filepath.endsWith('.ejs')) { 161 | const dest = filepath.replace(/\.ejs$/, ''); 162 | const ejsTemplate = fs.readFileSync(filepath, 'utf8'); 163 | 164 | const res = ejs.render(ejsTemplate, dataStore[dest]); 165 | 166 | fs.writeFileSync(dest, res); 167 | // 记得删除 ejs 168 | fs.unlinkSync(filepath); 169 | } 170 | }, 171 | ); 172 | 173 | // 包管理器检测 174 | const userAgent = process.env.npm_config_user_agent ?? ''; 175 | const packageManager = /pnpm/.test(userAgent) 176 | ? 'pnpm' 177 | : /yarn/.test(userAgent) 178 | ? 'yarn' 179 | : /bun/.test(userAgent) 180 | ? 'bun' 181 | : 'npm'; 182 | if (packageManager !== 'pnpm') { 183 | console.log( 184 | yellow('本项目使用 pnpm 作为包管理器: 请留意,您使用的并不是 pnpm.'), 185 | ); 186 | } 187 | console.log(`\n${promptsJSON.infos.done}\n`); 188 | if (root !== cwd) { 189 | const cdProjString = path.relative(cwd, root); 190 | console.log( 191 | ` ${bold( 192 | green( 193 | `cd ${cdProjString.includes(' ') ? `"${cdProjString}"` : `${cdProjString}`}`, 194 | ), 195 | )}`, 196 | ); 197 | } 198 | console.log(` ${bold(green('pnpm i'))}`); 199 | console.log(` ${bold(green('pnpm lint'))}`); 200 | console.log(` ${bold(green('pnpm dev'))}`); 201 | console.log(); 202 | } 203 | 204 | async function getResult() { 205 | const res = await prompts( 206 | [ 207 | { 208 | name: 'projectName', 209 | // 如果已经通过命令行参数传入了,则不再需要输入 projectName 210 | // 反之,如果需要用户交互,则此时 defaultProjectName 为 'my-awesome-site' 211 | type: targetDir ? null : 'text', 212 | message: promptsJSON.projectName.message, 213 | initial: defaultProjectName, 214 | onState: (state) => 215 | (targetDir = String(state.value.trim()) || defaultProjectName), 216 | }, 217 | { 218 | name: 'shouldOverwrite', 219 | // 如果传参 --force 或者 targetDir 为空,则跳过 220 | type: () => (argv.force || !fs.existsSync(targetDir) ? null : 'toggle'), 221 | message: () => { 222 | // 我们判断目标目录是否是 '.' 来拼接将要打印的内容 223 | const prompt = 224 | targetDir === '.' 225 | ? promptsJSON.shouldOverwrite.dirForPrompts.current 226 | : `${promptsJSON.shouldOverwrite.dirForPrompts.target} "${targetDir}"`; 227 | 228 | return `${prompt} ${promptsJSON.shouldOverwrite.message}`; 229 | }, 230 | initial: true, 231 | active: promptsJSON.defaultToggleOptions.active, 232 | inactive: promptsJSON.defaultToggleOptions.inactive, 233 | format: (val) => { 234 | // 回车后,拿到 val,我们判断是否是 false. 如果为 false, 则直接终止程序 235 | if (val === false) 236 | throw new Error( 237 | red('✖') + ` ${promptsJSON.errors.operationCancelled}`, 238 | ); 239 | return val; 240 | }, 241 | }, 242 | { 243 | name: 'packageName', 244 | // 如果 targetDir 可以作为 packageName 则跳过 245 | type: () => (isValidPackageName(targetDir) ? null : 'text'), 246 | message: promptsJSON.projectName.message, 247 | initial: () => toValidPackageName(targetDir), 248 | validate: (dir) => 249 | isValidPackageName(dir) || promptsJSON.packageName.invalidMessage, 250 | }, 251 | { 252 | name: 'needsAxios', 253 | type: () => (isFlagUsed ? null : 'toggle'), 254 | message: promptsJSON.needsAxios.message, 255 | initial: true, 256 | active: promptsJSON.defaultToggleOptions.active, 257 | inactive: promptsJSON.defaultToggleOptions.inactive, 258 | }, 259 | { 260 | name: 'needsUtils', 261 | type: () => (isFlagUsed ? null : 'toggle'), 262 | message: promptsJSON.needsUtils.message, 263 | initial: true, 264 | active: promptsJSON.defaultToggleOptions.active, 265 | inactive: promptsJSON.defaultToggleOptions.inactive, 266 | }, 267 | { 268 | name: 'needsDevTools', 269 | type: () => (isFlagUsed ? null : 'toggle'), 270 | message: promptsJSON.needsDevTools.message, 271 | initial: false, 272 | active: promptsJSON.defaultToggleOptions.active, 273 | inactive: promptsJSON.defaultToggleOptions.inactive, 274 | }, 275 | ], 276 | { 277 | onCancel: () => { 278 | throw new Error(red('✖' + ` ${promptsJSON.errors.operationCancelled}`)); 279 | }, 280 | }, 281 | ); 282 | 283 | return res; 284 | } 285 | 286 | setup().catch((err) => { 287 | console.error(err); 288 | }); 289 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-vue-xy", 3 | "version": "1.0.9", 4 | "description": "An Out-of-the-Box Scaffolding with Vue.js", 5 | "packageManager": "pnpm@9.4.0", 6 | "type": "module", 7 | "engines": { 8 | "node": ">=v18.3.0" 9 | }, 10 | "files": [ 11 | "./outfile.cjs", 12 | "template/" 13 | ], 14 | "bin": { 15 | "create-vue": "outfile.cjs" 16 | }, 17 | "scripts": { 18 | "build": "zx ./scripts/build.js", 19 | "test": "pnpm build && node ./outfile.cjs", 20 | "pub": "pnpm run build && pnpm publish" 21 | }, 22 | "keywords": [], 23 | "author": "xiaoyan13 ", 24 | "license": "MIT", 25 | "homepage": "https://github.com/xiaoyan13/create-vue-xy", 26 | "dependencies": { 27 | "@tsconfig/node20": "^20.1.4", 28 | "@types/ejs": "^3.1.5", 29 | "@types/node": "^20.14.10", 30 | "@types/prompts": "^2.4.9", 31 | "ejs": "^3.1.10", 32 | "esbuild": "^0.23.0", 33 | "kolorist": "^1.8.0", 34 | "npm-run-all2": "^6.2.2", 35 | "prettier": "^3.3.2", 36 | "prompts": "^2.4.2", 37 | "zx": "^8.1.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@tsconfig/node20': 12 | specifier: ^20.1.4 13 | version: 20.1.4 14 | '@types/ejs': 15 | specifier: ^3.1.5 16 | version: 3.1.5 17 | '@types/node': 18 | specifier: ^20.14.10 19 | version: 20.14.10 20 | '@types/prompts': 21 | specifier: ^2.4.9 22 | version: 2.4.9 23 | ejs: 24 | specifier: ^3.1.10 25 | version: 3.1.10 26 | esbuild: 27 | specifier: ^0.23.0 28 | version: 0.23.0 29 | kolorist: 30 | specifier: ^1.8.0 31 | version: 1.8.0 32 | npm-run-all2: 33 | specifier: ^6.2.2 34 | version: 6.2.2 35 | prettier: 36 | specifier: ^3.3.2 37 | version: 3.3.2 38 | prompts: 39 | specifier: ^2.4.2 40 | version: 2.4.2 41 | zx: 42 | specifier: ^8.1.4 43 | version: 8.1.4 44 | 45 | packages: 46 | 47 | '@esbuild/aix-ppc64@0.23.0': 48 | resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} 49 | engines: {node: '>=18'} 50 | cpu: [ppc64] 51 | os: [aix] 52 | 53 | '@esbuild/android-arm64@0.23.0': 54 | resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} 55 | engines: {node: '>=18'} 56 | cpu: [arm64] 57 | os: [android] 58 | 59 | '@esbuild/android-arm@0.23.0': 60 | resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} 61 | engines: {node: '>=18'} 62 | cpu: [arm] 63 | os: [android] 64 | 65 | '@esbuild/android-x64@0.23.0': 66 | resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} 67 | engines: {node: '>=18'} 68 | cpu: [x64] 69 | os: [android] 70 | 71 | '@esbuild/darwin-arm64@0.23.0': 72 | resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} 73 | engines: {node: '>=18'} 74 | cpu: [arm64] 75 | os: [darwin] 76 | 77 | '@esbuild/darwin-x64@0.23.0': 78 | resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} 79 | engines: {node: '>=18'} 80 | cpu: [x64] 81 | os: [darwin] 82 | 83 | '@esbuild/freebsd-arm64@0.23.0': 84 | resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} 85 | engines: {node: '>=18'} 86 | cpu: [arm64] 87 | os: [freebsd] 88 | 89 | '@esbuild/freebsd-x64@0.23.0': 90 | resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} 91 | engines: {node: '>=18'} 92 | cpu: [x64] 93 | os: [freebsd] 94 | 95 | '@esbuild/linux-arm64@0.23.0': 96 | resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} 97 | engines: {node: '>=18'} 98 | cpu: [arm64] 99 | os: [linux] 100 | 101 | '@esbuild/linux-arm@0.23.0': 102 | resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} 103 | engines: {node: '>=18'} 104 | cpu: [arm] 105 | os: [linux] 106 | 107 | '@esbuild/linux-ia32@0.23.0': 108 | resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} 109 | engines: {node: '>=18'} 110 | cpu: [ia32] 111 | os: [linux] 112 | 113 | '@esbuild/linux-loong64@0.23.0': 114 | resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} 115 | engines: {node: '>=18'} 116 | cpu: [loong64] 117 | os: [linux] 118 | 119 | '@esbuild/linux-mips64el@0.23.0': 120 | resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} 121 | engines: {node: '>=18'} 122 | cpu: [mips64el] 123 | os: [linux] 124 | 125 | '@esbuild/linux-ppc64@0.23.0': 126 | resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} 127 | engines: {node: '>=18'} 128 | cpu: [ppc64] 129 | os: [linux] 130 | 131 | '@esbuild/linux-riscv64@0.23.0': 132 | resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} 133 | engines: {node: '>=18'} 134 | cpu: [riscv64] 135 | os: [linux] 136 | 137 | '@esbuild/linux-s390x@0.23.0': 138 | resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} 139 | engines: {node: '>=18'} 140 | cpu: [s390x] 141 | os: [linux] 142 | 143 | '@esbuild/linux-x64@0.23.0': 144 | resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} 145 | engines: {node: '>=18'} 146 | cpu: [x64] 147 | os: [linux] 148 | 149 | '@esbuild/netbsd-x64@0.23.0': 150 | resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} 151 | engines: {node: '>=18'} 152 | cpu: [x64] 153 | os: [netbsd] 154 | 155 | '@esbuild/openbsd-arm64@0.23.0': 156 | resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} 157 | engines: {node: '>=18'} 158 | cpu: [arm64] 159 | os: [openbsd] 160 | 161 | '@esbuild/openbsd-x64@0.23.0': 162 | resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} 163 | engines: {node: '>=18'} 164 | cpu: [x64] 165 | os: [openbsd] 166 | 167 | '@esbuild/sunos-x64@0.23.0': 168 | resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} 169 | engines: {node: '>=18'} 170 | cpu: [x64] 171 | os: [sunos] 172 | 173 | '@esbuild/win32-arm64@0.23.0': 174 | resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} 175 | engines: {node: '>=18'} 176 | cpu: [arm64] 177 | os: [win32] 178 | 179 | '@esbuild/win32-ia32@0.23.0': 180 | resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} 181 | engines: {node: '>=18'} 182 | cpu: [ia32] 183 | os: [win32] 184 | 185 | '@esbuild/win32-x64@0.23.0': 186 | resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} 187 | engines: {node: '>=18'} 188 | cpu: [x64] 189 | os: [win32] 190 | 191 | '@tsconfig/node20@20.1.4': 192 | resolution: {integrity: sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==} 193 | 194 | '@types/ejs@3.1.5': 195 | resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} 196 | 197 | '@types/fs-extra@11.0.4': 198 | resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} 199 | 200 | '@types/jsonfile@6.1.4': 201 | resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} 202 | 203 | '@types/node@20.14.10': 204 | resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} 205 | 206 | '@types/prompts@2.4.9': 207 | resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} 208 | 209 | ansi-styles@4.3.0: 210 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 211 | engines: {node: '>=8'} 212 | 213 | ansi-styles@6.2.1: 214 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 215 | engines: {node: '>=12'} 216 | 217 | async@3.2.5: 218 | resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} 219 | 220 | balanced-match@1.0.2: 221 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 222 | 223 | brace-expansion@1.1.11: 224 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 225 | 226 | brace-expansion@2.0.1: 227 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 228 | 229 | chalk@4.1.2: 230 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 231 | engines: {node: '>=10'} 232 | 233 | color-convert@2.0.1: 234 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 235 | engines: {node: '>=7.0.0'} 236 | 237 | color-name@1.1.4: 238 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 239 | 240 | concat-map@0.0.1: 241 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 242 | 243 | cross-spawn@7.0.3: 244 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 245 | engines: {node: '>= 8'} 246 | 247 | ejs@3.1.10: 248 | resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} 249 | engines: {node: '>=0.10.0'} 250 | hasBin: true 251 | 252 | esbuild@0.23.0: 253 | resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} 254 | engines: {node: '>=18'} 255 | hasBin: true 256 | 257 | filelist@1.0.4: 258 | resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} 259 | 260 | has-flag@4.0.0: 261 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 262 | engines: {node: '>=8'} 263 | 264 | isexe@2.0.0: 265 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 266 | 267 | jake@10.9.1: 268 | resolution: {integrity: sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==} 269 | engines: {node: '>=10'} 270 | hasBin: true 271 | 272 | json-parse-even-better-errors@3.0.2: 273 | resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} 274 | engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 275 | 276 | kleur@3.0.3: 277 | resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} 278 | engines: {node: '>=6'} 279 | 280 | kolorist@1.8.0: 281 | resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} 282 | 283 | memorystream@0.3.1: 284 | resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} 285 | engines: {node: '>= 0.10.0'} 286 | 287 | minimatch@3.1.2: 288 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 289 | 290 | minimatch@5.1.6: 291 | resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} 292 | engines: {node: '>=10'} 293 | 294 | minimatch@9.0.5: 295 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 296 | engines: {node: '>=16 || 14 >=14.17'} 297 | 298 | npm-normalize-package-bin@3.0.1: 299 | resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} 300 | engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 301 | 302 | npm-run-all2@6.2.2: 303 | resolution: {integrity: sha512-Q+alQAGIW7ZhKcxLt8GcSi3h3ryheD6xnmXahkMRVM5LYmajcUrSITm8h+OPC9RYWMV2GR0Q1ntTUCfxaNoOJw==} 304 | engines: {node: ^14.18.0 || ^16.13.0 || >=18.0.0, npm: '>= 8'} 305 | hasBin: true 306 | 307 | path-key@3.1.1: 308 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 309 | engines: {node: '>=8'} 310 | 311 | pidtree@0.6.0: 312 | resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} 313 | engines: {node: '>=0.10'} 314 | hasBin: true 315 | 316 | prettier@3.3.2: 317 | resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} 318 | engines: {node: '>=14'} 319 | hasBin: true 320 | 321 | prompts@2.4.2: 322 | resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} 323 | engines: {node: '>= 6'} 324 | 325 | read-package-json-fast@3.0.2: 326 | resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} 327 | engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 328 | 329 | shebang-command@2.0.0: 330 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 331 | engines: {node: '>=8'} 332 | 333 | shebang-regex@3.0.0: 334 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 335 | engines: {node: '>=8'} 336 | 337 | shell-quote@1.8.1: 338 | resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} 339 | 340 | sisteransi@1.0.5: 341 | resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} 342 | 343 | supports-color@7.2.0: 344 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 345 | engines: {node: '>=8'} 346 | 347 | undici-types@5.26.5: 348 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 349 | 350 | which@2.0.2: 351 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 352 | engines: {node: '>= 8'} 353 | hasBin: true 354 | 355 | zx@8.1.4: 356 | resolution: {integrity: sha512-QFDYYpnzdpRiJ3dL2102Cw26FpXpWshW4QLTGxiYfIcwdAqg084jRCkK/kuP/NOSkxOjydRwNFG81qzA5r1a6w==} 357 | engines: {node: '>= 12.17.0'} 358 | hasBin: true 359 | 360 | snapshots: 361 | 362 | '@esbuild/aix-ppc64@0.23.0': 363 | optional: true 364 | 365 | '@esbuild/android-arm64@0.23.0': 366 | optional: true 367 | 368 | '@esbuild/android-arm@0.23.0': 369 | optional: true 370 | 371 | '@esbuild/android-x64@0.23.0': 372 | optional: true 373 | 374 | '@esbuild/darwin-arm64@0.23.0': 375 | optional: true 376 | 377 | '@esbuild/darwin-x64@0.23.0': 378 | optional: true 379 | 380 | '@esbuild/freebsd-arm64@0.23.0': 381 | optional: true 382 | 383 | '@esbuild/freebsd-x64@0.23.0': 384 | optional: true 385 | 386 | '@esbuild/linux-arm64@0.23.0': 387 | optional: true 388 | 389 | '@esbuild/linux-arm@0.23.0': 390 | optional: true 391 | 392 | '@esbuild/linux-ia32@0.23.0': 393 | optional: true 394 | 395 | '@esbuild/linux-loong64@0.23.0': 396 | optional: true 397 | 398 | '@esbuild/linux-mips64el@0.23.0': 399 | optional: true 400 | 401 | '@esbuild/linux-ppc64@0.23.0': 402 | optional: true 403 | 404 | '@esbuild/linux-riscv64@0.23.0': 405 | optional: true 406 | 407 | '@esbuild/linux-s390x@0.23.0': 408 | optional: true 409 | 410 | '@esbuild/linux-x64@0.23.0': 411 | optional: true 412 | 413 | '@esbuild/netbsd-x64@0.23.0': 414 | optional: true 415 | 416 | '@esbuild/openbsd-arm64@0.23.0': 417 | optional: true 418 | 419 | '@esbuild/openbsd-x64@0.23.0': 420 | optional: true 421 | 422 | '@esbuild/sunos-x64@0.23.0': 423 | optional: true 424 | 425 | '@esbuild/win32-arm64@0.23.0': 426 | optional: true 427 | 428 | '@esbuild/win32-ia32@0.23.0': 429 | optional: true 430 | 431 | '@esbuild/win32-x64@0.23.0': 432 | optional: true 433 | 434 | '@tsconfig/node20@20.1.4': {} 435 | 436 | '@types/ejs@3.1.5': {} 437 | 438 | '@types/fs-extra@11.0.4': 439 | dependencies: 440 | '@types/jsonfile': 6.1.4 441 | '@types/node': 20.14.10 442 | optional: true 443 | 444 | '@types/jsonfile@6.1.4': 445 | dependencies: 446 | '@types/node': 20.14.10 447 | optional: true 448 | 449 | '@types/node@20.14.10': 450 | dependencies: 451 | undici-types: 5.26.5 452 | 453 | '@types/prompts@2.4.9': 454 | dependencies: 455 | '@types/node': 20.14.10 456 | kleur: 3.0.3 457 | 458 | ansi-styles@4.3.0: 459 | dependencies: 460 | color-convert: 2.0.1 461 | 462 | ansi-styles@6.2.1: {} 463 | 464 | async@3.2.5: {} 465 | 466 | balanced-match@1.0.2: {} 467 | 468 | brace-expansion@1.1.11: 469 | dependencies: 470 | balanced-match: 1.0.2 471 | concat-map: 0.0.1 472 | 473 | brace-expansion@2.0.1: 474 | dependencies: 475 | balanced-match: 1.0.2 476 | 477 | chalk@4.1.2: 478 | dependencies: 479 | ansi-styles: 4.3.0 480 | supports-color: 7.2.0 481 | 482 | color-convert@2.0.1: 483 | dependencies: 484 | color-name: 1.1.4 485 | 486 | color-name@1.1.4: {} 487 | 488 | concat-map@0.0.1: {} 489 | 490 | cross-spawn@7.0.3: 491 | dependencies: 492 | path-key: 3.1.1 493 | shebang-command: 2.0.0 494 | which: 2.0.2 495 | 496 | ejs@3.1.10: 497 | dependencies: 498 | jake: 10.9.1 499 | 500 | esbuild@0.23.0: 501 | optionalDependencies: 502 | '@esbuild/aix-ppc64': 0.23.0 503 | '@esbuild/android-arm': 0.23.0 504 | '@esbuild/android-arm64': 0.23.0 505 | '@esbuild/android-x64': 0.23.0 506 | '@esbuild/darwin-arm64': 0.23.0 507 | '@esbuild/darwin-x64': 0.23.0 508 | '@esbuild/freebsd-arm64': 0.23.0 509 | '@esbuild/freebsd-x64': 0.23.0 510 | '@esbuild/linux-arm': 0.23.0 511 | '@esbuild/linux-arm64': 0.23.0 512 | '@esbuild/linux-ia32': 0.23.0 513 | '@esbuild/linux-loong64': 0.23.0 514 | '@esbuild/linux-mips64el': 0.23.0 515 | '@esbuild/linux-ppc64': 0.23.0 516 | '@esbuild/linux-riscv64': 0.23.0 517 | '@esbuild/linux-s390x': 0.23.0 518 | '@esbuild/linux-x64': 0.23.0 519 | '@esbuild/netbsd-x64': 0.23.0 520 | '@esbuild/openbsd-arm64': 0.23.0 521 | '@esbuild/openbsd-x64': 0.23.0 522 | '@esbuild/sunos-x64': 0.23.0 523 | '@esbuild/win32-arm64': 0.23.0 524 | '@esbuild/win32-ia32': 0.23.0 525 | '@esbuild/win32-x64': 0.23.0 526 | 527 | filelist@1.0.4: 528 | dependencies: 529 | minimatch: 5.1.6 530 | 531 | has-flag@4.0.0: {} 532 | 533 | isexe@2.0.0: {} 534 | 535 | jake@10.9.1: 536 | dependencies: 537 | async: 3.2.5 538 | chalk: 4.1.2 539 | filelist: 1.0.4 540 | minimatch: 3.1.2 541 | 542 | json-parse-even-better-errors@3.0.2: {} 543 | 544 | kleur@3.0.3: {} 545 | 546 | kolorist@1.8.0: {} 547 | 548 | memorystream@0.3.1: {} 549 | 550 | minimatch@3.1.2: 551 | dependencies: 552 | brace-expansion: 1.1.11 553 | 554 | minimatch@5.1.6: 555 | dependencies: 556 | brace-expansion: 2.0.1 557 | 558 | minimatch@9.0.5: 559 | dependencies: 560 | brace-expansion: 2.0.1 561 | 562 | npm-normalize-package-bin@3.0.1: {} 563 | 564 | npm-run-all2@6.2.2: 565 | dependencies: 566 | ansi-styles: 6.2.1 567 | cross-spawn: 7.0.3 568 | memorystream: 0.3.1 569 | minimatch: 9.0.5 570 | pidtree: 0.6.0 571 | read-package-json-fast: 3.0.2 572 | shell-quote: 1.8.1 573 | 574 | path-key@3.1.1: {} 575 | 576 | pidtree@0.6.0: {} 577 | 578 | prettier@3.3.2: {} 579 | 580 | prompts@2.4.2: 581 | dependencies: 582 | kleur: 3.0.3 583 | sisteransi: 1.0.5 584 | 585 | read-package-json-fast@3.0.2: 586 | dependencies: 587 | json-parse-even-better-errors: 3.0.2 588 | npm-normalize-package-bin: 3.0.1 589 | 590 | shebang-command@2.0.0: 591 | dependencies: 592 | shebang-regex: 3.0.0 593 | 594 | shebang-regex@3.0.0: {} 595 | 596 | shell-quote@1.8.1: {} 597 | 598 | sisteransi@1.0.5: {} 599 | 600 | supports-color@7.2.0: 601 | dependencies: 602 | has-flag: 4.0.0 603 | 604 | undici-types@5.26.5: {} 605 | 606 | which@2.0.2: 607 | dependencies: 608 | isexe: 2.0.0 609 | 610 | zx@8.1.4: 611 | optionalDependencies: 612 | '@types/fs-extra': 11.0.4 613 | '@types/node': 20.14.10 614 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://prettier.io/docs/en/configuration.html 3 | * @type {import("prettier").Config} 4 | */ 5 | const config = { 6 | tabWidth: 2, 7 | semi: true, 8 | singleQuote: true, 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /prompts/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": { 3 | "message": "请输入项目名称:" 4 | }, 5 | "shouldOverwrite": { 6 | "dirForPrompts": { 7 | "current": "当前目录", 8 | "target": "目标文件夹" 9 | }, 10 | "message": "非空,是否覆盖?" 11 | }, 12 | "packageName": { 13 | "message": "请输入包名称:", 14 | "invalidMessage": "无效的 package.json 名称" 15 | }, 16 | "needsAxios": { 17 | "message": "是否需要 axios 的配置封装?" 18 | }, 19 | "needsUtils": { 20 | "message": "是否需要一些开箱即用的 utils?(详情请查看文档)" 21 | }, 22 | "needsDevTools": { 23 | "message": "是否要引入 Vue DevTools ?" 24 | }, 25 | "errors": { 26 | "operationCancelled": "操作取消" 27 | }, 28 | "defaultToggleOptions": { 29 | "active": "是", 30 | "inactive": "否" 31 | }, 32 | "infos": { 33 | "scaffolding": "正在初始化项目", 34 | "done": "项目初始化完成,可执行以下命令: " 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /prompts/types.ts: -------------------------------------------------------------------------------- 1 | export interface PromptItem { 2 | message: string; 3 | invalidMessage?: string; 4 | dirForPrompts?: { 5 | current: string; 6 | target: string; 7 | }; 8 | toggleOptions?: { 9 | active: string; 10 | inactive: string; 11 | }; 12 | } 13 | 14 | export interface Prompts { 15 | projectName: PromptItem; 16 | shouldOverwrite: PromptItem; 17 | packageName: PromptItem; 18 | needsAxios: PromptItem; 19 | needsUtils: PromptItem; 20 | needsDevTools: PromptItem; 21 | errors: { 22 | operationCancelled: string; 23 | }; 24 | defaultToggleOptions: { 25 | active: string; 26 | inactive: string; 27 | }; 28 | infos: { 29 | scaffolding: string; 30 | done: string; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild'; 2 | 3 | await esbuild.build({ 4 | // 开启打包 5 | bundle: true, 6 | // 目标文件的格式, 名称 7 | format: 'cjs', 8 | outfile: 'outfile.cjs', 9 | target: 'node16', 10 | // 目标运行环境 11 | platform: 'node', 12 | // 入口 13 | entryPoints: ['index.ts'], 14 | banner: { 15 | js: '#!/usr/bin/env node', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /template/default-ejs/vite/plugins.ts.ejs: -------------------------------------------------------------------------------- 1 | import { PluginOption } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import vueJsx from '@vitejs/plugin-vue-jsx'; 4 | import AutoImport from 'unplugin-auto-import/vite'; 5 | import { visualizer } from 'rollup-plugin-visualizer'; 6 | import { compression } from 'vite-plugin-compression2'; 7 | <%_ for (const { importer } of plugins) { _%> 8 | <%- importer %> 9 | <%_ } _%> 10 | 11 | const plugins: PluginOption[] = [ 12 | vue(), 13 | vueJsx(), 14 | // @see https://github.com/unplugin/unplugin-auto-import 15 | AutoImport({ 16 | include: [ 17 | /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx 18 | /\.vue$/, // .vue 19 | ], 20 | imports: ['vue', 'vue-router'], 21 | dts: 'src/typings/auto-imports.d.ts', 22 | }), 23 | 24 | // @see https://github.com/nonzzz/vite-plugin-compression 25 | compression(), 26 | // @see https://github.com/btd/rollup-plugin-visualizer 27 | visualizer({ open: true }), // 自动开启分析页面 28 | <%_ for (const { initializer } of plugins) { _%> 29 | <%- initializer _%>, 30 | <%_ } _%> 31 | ]; 32 | 33 | export default plugins; 34 | -------------------------------------------------------------------------------- /template/vue-devtools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "vite-plugin-vue-devtools": "^7.3.5" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /template/vue-devtools/vite/plugins.ts.data.mjs: -------------------------------------------------------------------------------- 1 | export default function getData({ oldData }) { 2 | const vueDevtoolsPlugin = { 3 | id: 'vite-plugin-vue-devtools', 4 | importer: "import vueDevTools from 'vite-plugin-vue-devtools'", 5 | initializer: 'vueDevTools()', 6 | }; 7 | 8 | return { 9 | ...oldData, 10 | plugins: oldData.plugins 11 | ? [...oldData.plugins, vueDevtoolsPlugin] 12 | : [vueDevtoolsPlugin], 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": ["index.ts", "utils/**/*"], 4 | "compilerOptions": { 5 | // any script, 启动 6 | "strict": false, 7 | // ts 编译器器开启 json 文件的识别 8 | "resolveJsonModule": true, 9 | // 告诉编译器所有 ts 模块语法均严格遵循 ESM 规范标准, 即禁用掉 ts require() 10 | "module": "ESNext", 11 | // 模块最终解析策略指定受构建工具影响,即 ts 编译器将尽可能的查找模块,这包括开启 package.json 的 exports/imports 识别 12 | "moduleResolution": "Bundler", 13 | // 开启此选项后,模块文件的编译将考虑 import cjs 库文件的行为,并编译出该行为能正常工作的代码 14 | "esModuleInterop": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/banner.ts: -------------------------------------------------------------------------------- 1 | const defaultBanner = 2 | 'create-vue-xy: An Out-of-the-Box Scaffolding with Vue.js'; 3 | 4 | // gradientBanner 的来源: 5 | // import gradient from 'gradient-string' 6 | // const gradientBanner = gradient([ 7 | // { color: '#42d392', pos: 0 }, 8 | // { color: '#42d392', pos: 0.1 }, 9 | // { color: '#647eff', pos: 1 }, 10 | // ])(defaultBanner); 11 | // 我们直接使用其输出结果, 这样打包的时候就可以少一个库的体积了. 12 | const gradientBanner = 13 | 'create-vue-xy: An Out-of-the-Box Scaffolding with Vue.js'; 14 | 15 | export { defaultBanner, gradientBanner }; 16 | -------------------------------------------------------------------------------- /utils/deepMerge.ts: -------------------------------------------------------------------------------- 1 | const isObject = (val) => val && typeof val === 'object'; 2 | const mergeArrayWithDedupe = (a, b) => Array.from(new Set([...a, ...b])); 3 | 4 | /** 5 | * 递归的合并 obj 中字段合并到 target 6 | */ 7 | export default function deepMerge(target, obj) { 8 | for (const key of Object.keys(obj)) { 9 | const oldVal = target[key]; 10 | const newVal = obj[key]; 11 | 12 | // 如果均是数组/对象则 merge 13 | if (Array.isArray(oldVal) && Array.isArray(newVal)) { 14 | target[key] = mergeArrayWithDedupe(oldVal, newVal); 15 | } else if (isObject(oldVal) && isObject(newVal)) { 16 | target[key] = deepMerge(oldVal, newVal); 17 | } else { 18 | // 如果 target 中原值为原始值或者根本不存在,则覆盖 19 | target[key] = newVal; 20 | } 21 | 22 | return target; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /utils/directoryTraverse.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | 4 | export function preOrderDirectoryTraverse(dir, dirCb, fileCb) { 5 | for (const filename of fs.readdirSync(dir)) { 6 | const fullpath = path.resolve(dir, filename); 7 | if (fs.lstatSync(fullpath).isDirectory()) { 8 | dirCb(fullpath); 9 | fs.existsSync(fullpath) && 10 | preOrderDirectoryTraverse(fullpath, dirCb, fileCb); 11 | } else { 12 | fileCb(fullpath); 13 | } 14 | } 15 | } 16 | 17 | export function postOrderDirectoryTraverse(dir, dirCb, fileCb) { 18 | for (const filename of fs.readdirSync(dir)) { 19 | const fullpath = path.resolve(dir, filename); 20 | if (fs.lstatSync(fullpath).isDirectory()) { 21 | postOrderDirectoryTraverse(fullpath, dirCb, fileCb); 22 | dirCb(fullpath); 23 | } else { 24 | fileCb(fullpath); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /utils/emptyDir.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { postOrderDirectoryTraverse } from './directoryTraverse'; 3 | export default function emptyDir(dir) { 4 | if (!fs.existsSync(dir)) { 5 | return; 6 | } 7 | 8 | postOrderDirectoryTraverse( 9 | dir, 10 | (dir) => fs.rmdirSync(dir), 11 | (file) => fs.unlinkSync(file), 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /utils/packageName.ts: -------------------------------------------------------------------------------- 1 | export function isValidPackageName(projectName) { 2 | return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test( 3 | projectName, 4 | ); 5 | } 6 | 7 | export function toValidPackageName(projectName) { 8 | return projectName 9 | .trim() 10 | .toLowerCase() 11 | .replace(/\s+/g, '-') 12 | .replace(/^[._]/, '') 13 | .replace(/[^a-z0-9-~]+/g, '-'); 14 | } 15 | -------------------------------------------------------------------------------- /utils/renderTemplate.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import sortDependencies from './sortDependencies'; 4 | import deepMerge from './deepMerge'; 5 | import { pathToFileURL } from 'url'; 6 | 7 | /** 8 | * `package.json` 下会被递归合并 9 | * `.git` 将被忽略 10 | * 其他文件会被覆盖 11 | * @param {string} src 将要被拷贝的目录 12 | * @param {string} dest 目标目录 13 | */ 14 | export function renderTemplate(src, dest, callbacks) { 15 | // 我们不需要拷贝源仓库的 git 历史记录 16 | if (path.basename(src) === '.git') return; 17 | 18 | const stats = fs.statSync(src); 19 | 20 | if (stats.isDirectory()) { 21 | fs.mkdirSync(dest, { recursive: true }); 22 | 23 | for (const file of fs.readdirSync(src)) { 24 | renderTemplate( 25 | path.resolve(src, file), 26 | path.resolve(dest, file), 27 | callbacks, 28 | ); 29 | } 30 | return; 31 | } 32 | 33 | const filename = path.basename(src); 34 | 35 | // 合并 package.json 36 | if (filename === 'package.json') { 37 | // 如果目标目录还没有,则创建 38 | if (!fs.existsSync(dest)) { 39 | fs.copyFileSync(src, dest); 40 | } else { 41 | const existing = JSON.parse(fs.readFileSync(dest, 'utf8')); 42 | const news = JSON.parse(fs.readFileSync(src, 'utf8')); 43 | const pkg = sortDependencies(deepMerge(existing, news)); 44 | fs.writeFileSync(dest, JSON.stringify(pkg, null, 2) + '\n'); 45 | return; 46 | } 47 | } 48 | 49 | // 处理 ejs 模板的 .data.mjs 50 | if (filename.endsWith('.data.mjs')) { 51 | dest = dest.replace(/\.data.mjs$/, ''); 52 | 53 | callbacks.push(async (dataStore) => { 54 | const getData = (await import(pathToFileURL(src).toString())).default; 55 | 56 | dataStore[dest] = await getData({ 57 | oldData: dataStore[dest] || {}, 58 | }); 59 | }); 60 | } 61 | 62 | // 其他普通文件:直接拷贝覆盖 63 | fs.copyFileSync(src, dest); 64 | } 65 | -------------------------------------------------------------------------------- /utils/sortDependencies.ts: -------------------------------------------------------------------------------- 1 | export default function sortDependencies(packageJSON) { 2 | const depTypes = [ 3 | 'dependencies', 4 | 'devDependencies', 5 | 'peerDependencies', 6 | 'optionalDependencies', 7 | ]; 8 | const sortedResult = {}; 9 | 10 | for (const depType of depTypes) { 11 | if (packageJSON[depType]) { 12 | sortedResult[depType] = {}; 13 | 14 | Object.keys(packageJSON[depType]) 15 | .sort() 16 | .forEach((name) => { 17 | sortedResult[depType][name] = packageJSON[depType][name]; 18 | }); 19 | } 20 | } 21 | 22 | return { 23 | ...packageJSON, 24 | ...sortedResult, 25 | }; 26 | } 27 | --------------------------------------------------------------------------------