├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── auto-pr.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .releaserc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── pnpm-lock.yaml ├── scripts ├── build │ ├── dts.ts │ └── options.ts └── verifyCommit.ts ├── src ├── core │ ├── configure.ts │ ├── init.ts │ ├── proxy.ts │ └── upgrade.ts ├── data │ └── index.ts ├── global.d.ts ├── index.ts ├── libs │ ├── argv.ts │ ├── cmd.ts │ ├── config.ts │ ├── download.ts │ ├── local.ts │ ├── pkg.ts │ ├── tips.ts │ └── validator.ts └── types │ └── index.ts ├── test ├── download.test.ts ├── getDownloadUrl.test.ts ├── uniqueTechConfig.test.ts └── upgrade.test.ts ├── tsconfig.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | bin/* 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | commonjs: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: ['eslint:recommended', 'prettier'], 9 | parser: '@typescript-eslint/parser', 10 | parserOptions: { 11 | ecmaVersion: 13, 12 | }, 13 | plugins: ['@typescript-eslint', 'prettier'], 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 17 | 'prettier/prettier': 'warn', 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/auto-pr.yml: -------------------------------------------------------------------------------- 1 | name: Auto PR 2 | 3 | on: 4 | push: 5 | branches: [develop] 6 | 7 | jobs: 8 | auto-pr: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | ref: main 15 | - name: Reset develop branch 16 | run: | 17 | git fetch origin develop:develop 18 | git reset --hard develop 19 | - name: Create pull request 20 | uses: peter-evans/create-pull-request@v3 21 | with: 22 | branch: develop 23 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 24 | title: 'chore: release next version' 25 | commit-message: 'chore: release next version' 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | types: [closed] 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: 18 17 | registry-url: https://registry.npmjs.org/ 18 | - name: Install and build 19 | run: | 20 | npm i -g pnpm 21 | pnpm install 22 | pnpm build 23 | - name: Release 24 | env: 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 27 | run: pnpm run release 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | bin 6 | *.local 7 | yarn.lock 8 | package-lock.json 9 | .test-download 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | bin 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": "main", 3 | "ci": false, 4 | "plugins": [ 5 | "@semantic-release/commit-analyzer", 6 | "@semantic-release/release-notes-generator", 7 | "@semantic-release/changelog", 8 | "@semantic-release/npm", 9 | [ 10 | "@semantic-release/git", 11 | { 12 | "assets": [ 13 | "package.json", 14 | "CHANGELOG.md" 15 | ], 16 | "message": "release: v${nextRelease.version}\n\n${nextRelease.notes}" 17 | } 18 | ], 19 | "@semantic-release/github" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.13.1](https://github.com/awesome-starter/create-preset/compare/v0.13.0...v0.13.1) (2023-03-14) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * the `-v` option get the wrong version ([227ab09](https://github.com/awesome-starter/create-preset/commit/227ab09ae68467c9f3330244988b2f9c139ba660)) 7 | 8 | # [0.13.0](https://github.com/awesome-starter/create-preset/compare/v0.12.2...v0.13.0) (2023-01-30) 9 | 10 | 11 | ### Features 12 | 13 | * add a tips to enable proxy ([e7f60f0](https://github.com/awesome-starter/create-preset/commit/e7f60f0fcc86269c751d2199c9d8b4c8c141ffae)) 14 | * add mirror config ([5cf4e14](https://github.com/awesome-starter/create-preset/commit/5cf4e14924d11e735d20904d1d66e91aaf088145)) 15 | 16 | ## [0.12.2](https://github.com/awesome-starter/create-preset/compare/v0.12.1...v0.12.2) (2023-01-27) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * remove invalid mirror proxy service ([3a7fc7b](https://github.com/awesome-starter/create-preset/commit/3a7fc7b65c3fded7799e9c3821b916e1f342d530)) 22 | 23 | ## [0.12.1](https://github.com/awesome-starter/create-preset/compare/v0.12.0...v0.12.1) (2022-05-12) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * change the proxy domain for download ([9bd0b90](https://github.com/awesome-starter/create-preset/commit/9bd0b90ce94b9e94bb3bbc200358b2a2ba511cf9)) 29 | 30 | # [0.12.0](https://github.com/awesome-starter/create-preset/compare/v0.11.0...v0.12.0) (2022-05-03) 31 | 32 | 33 | ### Features 34 | 35 | * make the init command to be default ([f7754bd](https://github.com/awesome-starter/create-preset/commit/f7754bd515d55e047cdadab88651b4704760610d)) 36 | * support specify a template name when init ([c0fa9ea](https://github.com/awesome-starter/create-preset/commit/c0fa9ea47d3245c7d9a13c350eda4d4b501a2d2a)) 37 | 38 | # [0.11.0](https://github.com/awesome-starter/create-preset/compare/v0.10.0...v0.11.0) (2022-02-11) 39 | 40 | 41 | ### Features 42 | 43 | * change download proxy source ([1aa0975](https://github.com/awesome-starter/create-preset/commit/1aa0975352880f8379bb1cb551617b8d90216743)) 44 | 45 | # [0.10.0](https://github.com/awesome-starter/create-preset/compare/v0.9.0...v0.10.0) (2022-01-27) 46 | 47 | 48 | ### Features 49 | 50 | * community templates randomly sorted ([9e02764](https://github.com/awesome-starter/create-preset/commit/9e02764dbfee4b60768e283ee8f7118edca7716f)) 51 | * hide tech stack without templates on interactive interface ([cabbe4c](https://github.com/awesome-starter/create-preset/commit/cabbe4c9cad0ce7e4db7aad318d3f4772dc70fc4)) 52 | 53 | # [0.9.0](https://github.com/awesome-starter/create-preset/compare/v0.8.0...v0.9.0) (2022-01-25) 54 | 55 | 56 | ### Features 57 | 58 | * make the local list first ([34b7998](https://github.com/awesome-starter/create-preset/commit/34b7998311510ac7e54d9a84ad928b8fc2f876ac)) 59 | * **pkg:** add assign version to test ([97270ad](https://github.com/awesome-starter/create-preset/commit/97270adbe0c174db8ae2703fae59068e21b1f8cc)) 60 | * unique tech stacks ([367f7f8](https://github.com/awesome-starter/create-preset/commit/367f7f86ceeb420031d70dbd3e67abe00b88d620)) 61 | * unique template list ([b92a6f8](https://github.com/awesome-starter/create-preset/commit/b92a6f86aeeef288c402ce07e64c2aaf00f6b4b7)) 62 | 63 | # [0.8.0](https://github.com/awesome-starter/create-preset/compare/v0.7.0...v0.8.0) (2022-01-24) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | - after removing the local config, the get command reads the wrong path 69 | 70 | ### Features 71 | 72 | - support manage your local tech list 73 | - support use proxy to download templates 74 | 75 | ```bash 76 | Usage: preset [options] 77 | 78 | Options: 79 | -v, --version output the version number 80 | -h, --help output usage information 81 | 82 | Commands: 83 | init|i [app-name] generate a project from a preset template 84 | config|c [options] use the local preset config 85 | proxy|p use proxy to download template 86 | upgrade|u updated version 87 | help [command] display help for command 88 | 89 | Run preset init to initialize your project. 90 | ``` 91 | 92 | # [0.7.0](https://github.com/awesome-starter/create-preset/compare/v0.6.0...v0.7.0) (2022-01-21) 93 | 94 | 95 | rewrite in vite with typescript 96 | 97 | 98 | # [0.6.0](https://github.com/awesome-starter/create-preset/compare/v0.5.0...v0.6.0) (2022-01-13) 99 | 100 | 101 | ### Features 102 | 103 | - the configuration file is split into 3 parts: `official`, `community` and `local` 104 | - fetch [configuration files](https://github.com/awesome-starter/website/tree/main/docs/public/config) from the official website, future template additions and updates will be handed over to the official website 105 | - add `config` command, can manage local configuration 106 | - if you create a local configuration, you can download templates for your own private repository 107 | - add `upgrade` command, In the case of global installation, you can check whether the CLI needs to be upgraded 108 | - temporarily remove fastgit acceleration, because it is found that it is only valid for Chinese networks, and networks in other regions cannot download templates. Later versions will be changed to custom configuration functions to provide acceleration 109 | 110 | please pay attention to [the official website](https://preset.js.org/) documentation for the instructions of the new version 111 | 112 | 113 | # [0.5.0](https://github.com/awesome-starter/create-preset/compare/v0.4.0...v0.5.0) (2022-01-10) 114 | 115 | ### Features 116 | 117 | - add `rollup` starter 118 | - update colors for tech stacks 119 | 120 | 121 | # [0.4.0](https://github.com/awesome-starter/create-preset/compare/v0.3.0...v0.4.0) (2022-01-07) 122 | 123 | ### Features 124 | 125 | - add a alias i for init command 126 | - add electron starter 127 | - add pkg starter 128 | 129 | # [0.3.0](https://github.com/awesome-starter/create-preset/compare/v0.2.0...v0.3.0) (2022-01-06) 130 | 131 | ### Features 132 | 133 | - remove local template 134 | - support download remote repo starter to create project 135 | - add `node-basic`, `node-express` template 136 | 137 | # [0.2.0](https://github.com/awesome-starter/create-preset/compare/v0.1.0...v0.2.0) (2021-12-29) 138 | 139 | 140 | ### Features 141 | 142 | - refactored most of the code, powered by [@vue/cli](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli) 143 | - added new commands support 144 | 145 | ```bash 146 | Usage: preset [options] 147 | 148 | Options: 149 | -v, --version output the version number 150 | -h, --help output usage information 151 | 152 | Commands: 153 | init [app-name] generate a project from a preset template 154 | help [command] display help for command 155 | 156 | Run preset init to initialize your project. 157 | ``` 158 | 159 | # 0.1.0 (2021-12-27) 160 | 161 | The basic version, powered by [create-vite](https://github.com/vitejs/vite/tree/main/packages/create-vite) 162 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 chengpeiquan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | create-preset 7 |

8 | 9 |

create-preset

10 | 11 |

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | GitHub stars 23 | 24 |

25 | 26 | English | [简体中文](https://preset.js.org/zh/) 27 | 28 | ## Features 29 | 30 | Provides the ability to quickly create preset projects. 31 | 32 | > In addition to creating excellent templates provided by the official and open source communities as your projects, you are encouraged to use it as a tool to manage the configuration of your personal common project templates. Visit [Manage local configuration](https://preset.js.org/docs.html#manage-local-configuration) to learn more. 33 | 34 | If you find it useful, [Welcome to give it a Star](https://github.com/awesome-starter/create-preset) ! 35 | 36 | - ✈ Practicality - Out-of-the-box starter templates for projects. 37 | - ⚡️ Efficient - Reduces repetitive configuration processes every time a new project is created. 38 | - 🤹 Interactive - Simple command-line interactive operation. 39 | - 🛠 Multi-Tech Stacks - Provide commonly used multiple technology stack project support. 40 | - 🚀 keep pace with the times - Provide open source and long-term maintenance and update templates. 41 | - 🔑 Private Configuration - Support for local configuration files to manage private template lists. 42 | 43 | ## Simply Usage 44 | 45 | You can simply experience it through the command of the package manager, and directly create the template you need by create preset. 46 | 47 | ```bash 48 | npm create preset 49 | ``` 50 | 51 | Then follow the prompts! 52 | 53 | ## Global Usage 54 | 55 | It is recommended to install globally for easier usage, Please install it globally first: 56 | 57 | ```bash 58 | npm install -g create-preset 59 | ``` 60 | 61 | You can use the following command to check whether the installation was successful. If successful, you will get a version number. 62 | 63 | ```bash 64 | preset -v 65 | ``` 66 | 67 | You can refer to [Upgrade](https://preset.js.org/guide.html#upgrade) to learn how to upgrade in the future. 68 | 69 | ## Documentation 70 | 71 | Please visit the official website for full docs. 72 | 73 | See: [preset.js.org](https://preset.js.org/) 74 | 75 | ## Preview 76 | 77 | ![create-preset](https://cdn.jsdelivr.net/gh/chengpeiquan/assets-storage/img/2021/11/20220110155037.gif) 78 | 79 | ## License 80 | 81 | MIT License © 2022 [chengpeiquan](https://github.com/chengpeiquan) 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-preset", 3 | "description": "Provides the ability to quickly create preset projects.", 4 | "version": "0.13.1", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://preset.js.org", 8 | "files": [ 9 | "dist" 10 | ], 11 | "types": "./dist/create-preset.d.ts", 12 | "main": "./dist/create-preset.js", 13 | "bin": { 14 | "create-preset": "./dist/create-preset.js", 15 | "preset": "./dist/create-preset.js" 16 | }, 17 | "scripts": { 18 | "dev": "vite", 19 | "build": "tsc --noEmit && vite build && npm run build:types", 20 | "build:types": "tsx scripts/build/dts", 21 | "release": "semantic-release", 22 | "lint": "eslint src --ext .js,.ts", 23 | "test": "vitest", 24 | "coverage": "vitest --coverage", 25 | "format": "prettier --write .", 26 | "mirror:get": "npm config get registry", 27 | "mirror:set": "npm config set registry https://registry.npmmirror.com", 28 | "mirror:rm": "npm config rm registry", 29 | "backup": "git add . && git commit -m \"chore: backup\" && git push", 30 | "up": "npx npm-check-updates", 31 | "prepare": "simple-git-hooks" 32 | }, 33 | "engines": { 34 | "node": ">=12.0.0" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/awesome-starter/create-preset.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/awesome-starter/create-preset/issues" 42 | }, 43 | "dependencies": { 44 | "@bassist/node-utils": "^0.1.2", 45 | "@bassist/utils": "^0.9.0", 46 | "@withtypes/figlet": "^0.1.0", 47 | "@withtypes/fs-extra": "^0.1.1", 48 | "@withtypes/minimist": "^0.1.1", 49 | "@withtypes/prompts": "^0.1.1", 50 | "axios": "^1.2.4", 51 | "chalk": "4.1.2", 52 | "commander": "^10.0.0", 53 | "compare-versions": "3.6.0", 54 | "download-git-repo": "^3.0.2", 55 | "latest-version": "5.1.0", 56 | "leven": "3.1.0", 57 | "ora": "5.4.1" 58 | }, 59 | "devDependencies": { 60 | "@semantic-release/changelog": "^6.0.2", 61 | "@semantic-release/git": "^10.0.1", 62 | "@types/node": "^18.11.18", 63 | "@typescript-eslint/eslint-plugin": "^5.49.0", 64 | "@typescript-eslint/parser": "^5.49.0", 65 | "eslint": "^8.32.0", 66 | "eslint-config-prettier": "^8.6.0", 67 | "eslint-plugin-prettier": "^4.2.1", 68 | "lint-staged": "^13.1.0", 69 | "prettier": "^2.8.3", 70 | "semantic-release": "^20.1.0", 71 | "simple-git-hooks": "^2.8.1", 72 | "tsx": "^3.12.2", 73 | "typescript": "^4.9.4", 74 | "vite": "^4.0.4", 75 | "vite-plugin-banner": "^0.7.0", 76 | "vite-plugin-commonjs-externals": "^0.1.1", 77 | "vitest": "^0.28.1" 78 | }, 79 | "simple-git-hooks": { 80 | "pre-commit": "pnpm exec lint-staged --concurrent false", 81 | "commit-msg": "pnpm exec tsx scripts/verifyCommit $1" 82 | }, 83 | "lint-staged": { 84 | "*.{js,jsx,vue,ts,tsx}": [ 85 | "prettier --write", 86 | "eslint --fix" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /scripts/build/dts.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from '@withtypes/fs-extra' 2 | import { resolve } from 'path' 3 | import pkg from '../../package.json' 4 | 5 | /** 6 | * This package no need declare file. 7 | * But I like to show the `TS` icon after the package name on the npmjs website. 8 | */ 9 | async function run() { 10 | const content = `export {};` 11 | const rootPath = resolve(__dirname, '../..') 12 | const output = resolve(rootPath, `./dist/${pkg.name}.d.ts`) 13 | writeFileSync(output, content) 14 | } 15 | run().catch((e) => { 16 | console.log('run', e) 17 | }) 18 | -------------------------------------------------------------------------------- /scripts/build/options.ts: -------------------------------------------------------------------------------- 1 | import { escapeRegExp } from '@bassist/utils' 2 | import pkg from '../../package.json' 3 | 4 | export function cjsExternalsRegExp() { 5 | const deps: string = Object.keys(pkg.dependencies) 6 | .map((name) => escapeRegExp(name)) 7 | .join('|') 8 | return new RegExp(`^(${deps})(/.+)?$`) 9 | } 10 | -------------------------------------------------------------------------------- /scripts/verifyCommit.ts: -------------------------------------------------------------------------------- 1 | // Invoked on the commit-msg git hook by simple-git-hooks. 2 | 3 | import { readFileSync } from '@withtypes/fs-extra' 4 | 5 | // get $1 from commit-msg script 6 | const msgPath = process.argv[2] 7 | const msg = readFileSync(msgPath, 'utf-8').trim() 8 | 9 | const commitRE = 10 | /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/ 11 | 12 | if (!commitRE.test(msg)) { 13 | console.log() 14 | console.error( 15 | ` ERROR 16 | invalid commit message format. 17 | \n\n` + 18 | ` Proper commit message format is required for automated changelog generation. Examples:\n\n` + 19 | ` feat(compiler): add 'comments' option\n` + 20 | ` 21 | fix(v-model): handle events on blur (close #28) 22 | \n\n` + 23 | ` See .github/commit-convention.md for more details.\n` 24 | ) 25 | process.exit(1) 26 | } 27 | -------------------------------------------------------------------------------- /src/core/configure.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { 3 | getRuntimeConfig, 4 | setRuntimeConfig, 5 | removeRuntimeConfig, 6 | } from '@/libs/local' 7 | import argv from '@/libs/argv' 8 | import type { SubcommandItem } from '@/types' 9 | 10 | // If `true`, handle the tech config 11 | const isTech = Boolean(argv.tech) || Boolean(argv.t) 12 | const target = isTech ? 'tech stack' : 'configuration' 13 | const tips = ` Run ${chalk.cyan( 14 | `preset config${isTech ? ' --tech ' : ' '}set ` 15 | )} to bind your local ${target}.` 16 | 17 | /** 18 | * Subcommands 19 | */ 20 | export const CMDS: SubcommandItem[] = [ 21 | { 22 | cmd: 'get', 23 | desc: 'output the local config file path', 24 | }, 25 | { 26 | cmd: 'set ', 27 | desc: 'save the local config file path', 28 | }, 29 | { 30 | cmd: 'remove', 31 | desc: 'remove the local config file path', 32 | }, 33 | ] 34 | 35 | /** 36 | * The action for `configure` command 37 | * @param action - The action to operate local preset 38 | * cmd: Subcommand 39 | * filePath: The local config path when used for set 40 | */ 41 | export default async function configure({ 42 | cmd, 43 | filePath, 44 | }: { 45 | cmd: string 46 | filePath?: string 47 | }) { 48 | if (!CMDS.map((c) => c.cmd).includes(cmd)) return 49 | 50 | switch (cmd) { 51 | // Get the local config file path in .presetrc 52 | case 'get': { 53 | const localConfigfilePath = getRuntimeConfig() 54 | console.log() 55 | if (localConfigfilePath) { 56 | console.log(' The local configuration is stored in:') 57 | console.log(` Here → ${chalk.cyan(localConfigfilePath)}`) 58 | console.log() 59 | } else { 60 | console.log(' There is currently no local configuration.') 61 | } 62 | console.log(tips) 63 | console.log() 64 | break 65 | } 66 | 67 | // Set the local config file path into .presetrc 68 | case 'set ': { 69 | const isSuccess = setRuntimeConfig(String(filePath)) 70 | if (isSuccess) { 71 | console.log() 72 | console.log(` ${chalk.green(`Saved ${target} successfully.`)}`) 73 | console.log() 74 | } 75 | break 76 | } 77 | 78 | // Set the local config file path into .presetrc 79 | case 'remove': { 80 | const localConfigfilePath = getRuntimeConfig() 81 | if (!localConfigfilePath) { 82 | configure({ 83 | cmd: 'get', 84 | }) 85 | return 86 | } 87 | const isSuccess = removeRuntimeConfig() 88 | if (isSuccess) { 89 | console.log() 90 | console.log(` ${chalk.green(`Removed ${target} successfully.`)}`) 91 | console.log() 92 | console.log(tips) 93 | console.log() 94 | } 95 | break 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/core/init.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import prompts from '@withtypes/prompts' 4 | import chalk from 'chalk' 5 | import { 6 | write, 7 | remove, 8 | emptyDir, 9 | isEmpty, 10 | isValidPackageName, 11 | toValidPackageName, 12 | getPackageManagerByUserAgent, 13 | } from '@bassist/node-utils' 14 | import { getDownloadUrl, download } from '@/libs/download' 15 | import { getConfig } from '@/libs/config' 16 | import argv from '@/libs/argv' 17 | import type { UserInputInfoFromCommandLine } from '@/types' 18 | 19 | const cwd = process.cwd() 20 | 21 | /** 22 | * The action for `init` command 23 | * @param targetDirFromCMD - The dir name from CMD, if there is input 24 | */ 25 | export default async function init(targetDirFromCMD: string | undefined) { 26 | if (argv._.length > 2) { 27 | console.log( 28 | chalk.yellow( 29 | "\nInfo: You provided more than one argument. The first one will be used as the app's name, the rest are ignored." 30 | ) 31 | ) 32 | } 33 | 34 | const { techStacks, templates, allTemplates } = await getConfig() 35 | 36 | let targetDir = targetDirFromCMD || argv._[1] 37 | 38 | let template = argv.template || argv.t 39 | 40 | const defaultProjectName = !targetDir ? 'my-preset-app' : targetDir 41 | 42 | let result: UserInputInfoFromCommandLine = { 43 | projectName: '', 44 | packageName: '', 45 | overwrite: false, 46 | techStack: undefined, 47 | variant: '', 48 | } 49 | 50 | try { 51 | result = await prompts( 52 | [ 53 | { 54 | type: targetDir ? null : 'text', 55 | name: 'projectName', 56 | message: 'Project name:', 57 | initial: defaultProjectName, 58 | onState: (state) => 59 | (targetDir = state.value.trim() || defaultProjectName), 60 | }, 61 | { 62 | type: () => 63 | !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm', 64 | name: 'overwrite', 65 | message: () => 66 | (targetDir === '.' 67 | ? 'Current directory' 68 | : `Target directory "${targetDir}"`) + 69 | ` is not empty. Remove existing files and continue?`, 70 | }, 71 | { 72 | // @ts-ignore 73 | type: (_, { overwrite } = {}) => { 74 | if (overwrite === false) { 75 | throw new Error(chalk.red('✖') + ' Operation cancelled') 76 | } 77 | return null 78 | }, 79 | name: 'overwriteChecker', 80 | }, 81 | { 82 | type: () => (isValidPackageName(targetDir) ? null : 'text'), 83 | name: 'packageName', 84 | message: 'Package name:', 85 | initial: () => toValidPackageName(targetDir), 86 | validate: (dir) => 87 | isValidPackageName(dir) || 'Invalid package.json name', 88 | }, 89 | { 90 | type: template && templates.includes(template) ? null : 'select', 91 | name: 'techStack', 92 | message: 93 | typeof template === 'string' && !templates.includes(template) 94 | ? `"${template}" isn't a valid template. Please choose from below: ` 95 | : 'Select a tech stack:', 96 | initial: 0, 97 | choices: techStacks 98 | .filter((techStack) => { 99 | return techStack.variants.length 100 | }) 101 | .map((techStack) => { 102 | const techStackColor = techStack.color 103 | return { 104 | title: techStackColor(techStack.name), 105 | value: techStack, 106 | } 107 | }), 108 | }, 109 | { 110 | type: (techStack) => 111 | techStack && techStack.variants ? 'select' : null, 112 | name: 'variant', 113 | message: 'Select a variant:', 114 | // @ts-ignore 115 | choices: (techStack) => 116 | // @ts-ignore 117 | techStack.variants.map((variant) => { 118 | const variantColor = variant.color 119 | return { 120 | title: `${variantColor(variant.name)}${chalk.gray( 121 | ' - ' + variant.desc 122 | )}`, 123 | value: variant.name, 124 | } 125 | }), 126 | }, 127 | ], 128 | { 129 | onCancel: () => { 130 | throw new Error(chalk.red('✖') + ' Operation cancelled') 131 | }, 132 | } 133 | ) 134 | } catch (cancelled) { 135 | // @ts-ignore 136 | console.log(cancelled.message) 137 | return 138 | } 139 | 140 | // user choice associated with prompts 141 | const { techStack, overwrite, packageName, variant } = result 142 | 143 | const root = path.join(cwd, targetDir) 144 | 145 | if (overwrite) { 146 | emptyDir(root) 147 | } else if (!fs.existsSync(root)) { 148 | fs.mkdirSync(root) 149 | } 150 | 151 | // determine template 152 | template = variant || techStack || template 153 | 154 | console.log(`\nScaffolding project in ${root}...`) 155 | 156 | // Get download URL from CMD 157 | const downloadUrl = getDownloadUrl({ 158 | template, 159 | variants: techStack ? techStack.variants : allTemplates, 160 | }) 161 | 162 | // Download template 163 | await download({ 164 | repo: downloadUrl, 165 | folder: targetDir, 166 | }) 167 | 168 | // Remove some files out of templates 169 | const outOfTemplateFiles = [ 170 | '.git', 171 | '.github', 172 | '.gitlab', 173 | '.gitee', 174 | 'LICENSE', 175 | 'package-lock.json', 176 | 'yarn.lock', 177 | 'pnpm-lock.yaml', 178 | ] 179 | outOfTemplateFiles.forEach((name) => { 180 | remove(path.join(root, name)) 181 | }) 182 | 183 | // Get package info 184 | const pkg = path.join(root, `package.json`) 185 | const pkgContent = require(pkg) 186 | 187 | // Reset project info 188 | pkgContent['name'] = packageName || targetDir 189 | pkgContent['version'] = '0.0.0' 190 | pkgContent['description'] = '' 191 | pkgContent['author'] = '' 192 | write(pkg, JSON.stringify(pkgContent, null, 2)) 193 | 194 | // Notification result 195 | const pkgManagerInfo = getPackageManagerByUserAgent() 196 | const pkgManager = pkgManagerInfo.name || 'npm' 197 | 198 | console.log(`\nDone. Now run:\n`) 199 | if (root !== cwd) { 200 | console.log(` cd ${path.relative(cwd, root)}`) 201 | } 202 | switch (pkgManager) { 203 | case 'yarn': 204 | console.log(' yarn') 205 | console.log(' yarn dev') 206 | break 207 | default: 208 | console.log(` ${pkgManager} install`) 209 | console.log(` ${pkgManager} run dev`) 210 | break 211 | } 212 | console.log() 213 | } 214 | -------------------------------------------------------------------------------- /src/core/proxy.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { saveRuntimeConfigFile } from '@/libs/local' 3 | import type { SubcommandItem } from '@/types' 4 | 5 | /** 6 | * Subcommands 7 | */ 8 | export const CMDS: SubcommandItem[] = [ 9 | { 10 | cmd: 'on', 11 | desc: 'turn on proxy', 12 | }, 13 | { 14 | cmd: 'off', 15 | desc: 'turn off proxy', 16 | }, 17 | ] 18 | 19 | /** 20 | * The action for `proxy` command 21 | * @param action - The action to operate local preset 22 | * cmd: Subcommand 23 | */ 24 | export default async function proxy({ cmd }: { cmd: string }) { 25 | if (!CMDS.map((c) => c.cmd).includes(cmd)) return 26 | 27 | switch (cmd) { 28 | case 'on': 29 | case 'off': { 30 | const isOk = saveRuntimeConfigFile('proxy', cmd) 31 | if (isOk) { 32 | console.log() 33 | console.log(` ${chalk.green(`Turn ${cmd} proxy successfully.`)}`) 34 | console.log() 35 | } 36 | break 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/core/upgrade.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process' 2 | import prompts from '@withtypes/prompts' 3 | import ora from 'ora' 4 | import chalk from 'chalk' 5 | import { queryPackageUpgradeInfo } from '@/libs/pkg' 6 | 7 | /** 8 | * The action for `upgrade` command 9 | */ 10 | export default async function upgrade() { 11 | const { packageName, currentVersion, latestVersion, needToUpgrade } = 12 | await queryPackageUpgradeInfo() 13 | console.log() 14 | 15 | // No upgrade required, process terminated 16 | if (!needToUpgrade) { 17 | console.log( 18 | ` The current version is already the latest version, no need to upgrade.` 19 | ) 20 | console.log() 21 | return 22 | } 23 | 24 | // Display version information and confirm with users whether they want to upgrade 25 | console.log(` The current version: ${chalk.cyan(currentVersion)}`) 26 | console.log(` The latest version: ${chalk.cyan(latestVersion)}`) 27 | 28 | let confirmUpgrade = { 29 | hopeToUpgrade: true, 30 | } 31 | 32 | try { 33 | confirmUpgrade = await prompts( 34 | [ 35 | { 36 | type: 'confirm', 37 | name: 'hopeToUpgrade', 38 | message: 'Found a new version, do you need to upgrade?', 39 | initial: true, 40 | }, 41 | ], 42 | { 43 | onCancel: () => { 44 | throw new Error(chalk.red('✖') + ' Operation cancelled') 45 | }, 46 | } 47 | ) 48 | } catch (cancelled) { 49 | // @ts-ignore 50 | console.log(cancelled.message) 51 | return 52 | } 53 | console.log() 54 | 55 | // Users don't want to upgrade 56 | const { hopeToUpgrade } = confirmUpgrade 57 | if (!hopeToUpgrade) return 58 | 59 | // Choose a package manager 60 | let pkgManager = { 61 | targetManager: 'npm', 62 | } 63 | 64 | try { 65 | pkgManager = await prompts( 66 | [ 67 | { 68 | type: 'select', 69 | name: 'targetManager', 70 | message: 'Please select your package manager for global installation', 71 | choices: [ 72 | { 73 | title: 'npm', 74 | value: 'npm', 75 | }, 76 | { 77 | title: 'yarn', 78 | value: 'yarn', 79 | }, 80 | { 81 | title: 'pnpm', 82 | value: 'pnpm', 83 | }, 84 | ], 85 | }, 86 | ], 87 | { 88 | onCancel: () => { 89 | throw new Error(chalk.red('✖') + ' Operation cancelled') 90 | }, 91 | } 92 | ) 93 | } catch (cancelled) { 94 | // @ts-ignore 95 | console.log(cancelled.message) 96 | return 97 | } 98 | 99 | const { targetManager } = pkgManager 100 | 101 | let cmd = '' 102 | switch (targetManager) { 103 | case 'npm': 104 | cmd = `npm i -g ${packageName}@latest` 105 | break 106 | case 'yarn': 107 | cmd = `yarn global add ${packageName}@latest` 108 | break 109 | case 'pnpm': 110 | cmd = `pnpm i -g ${packageName}@latest` 111 | break 112 | } 113 | 114 | console.log() 115 | const spinner = ora('Upgrading…').start() 116 | exec(cmd, (err) => { 117 | if (err) { 118 | console.log() 119 | console.log() 120 | console.log(err) 121 | console.log() 122 | spinner.fail(chalk.red('Upgraded failed.')) 123 | process.exit() 124 | } 125 | console.log() 126 | spinner.succeed(chalk.green('Upgraded successfully.')) 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /src/data/index.ts: -------------------------------------------------------------------------------- 1 | import { isProxyOn } from '@/libs/local' 2 | 3 | /** 4 | * Whitelist for conversion shorthand repositories 5 | * @see https://gitlab.com/flippidippi/download-git-repo#repository 6 | */ 7 | export const whitelist = [ 8 | 'https://github.com/', 9 | 'https://gitlab.com/', 10 | 'https://bitbucket.com/', 11 | ] 12 | 13 | /** 14 | * If proxy is enabled, the configuration file also points to the mirroring domain name 15 | */ 16 | export function getBaseUrl() { 17 | return isProxyOn() 18 | ? 'https://gitee.com/awesome-starter/website/raw/main/docs/public/config' 19 | : 'https://preset.js.org/config' 20 | } 21 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'download-git-repo' 4 | declare module '*.json' 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // import minimist from 'minimist' 2 | import chalk from 'chalk' 3 | import { program } from 'commander' 4 | import { version } from '../package.json' 5 | import { suggestCommands } from './libs/cmd' 6 | import init from './core/init' 7 | import configure, { CMDS as CONFIG_SUB_CMDS } from './core/configure' 8 | import proxy, { CMDS as PROXY_SUB_CMDS } from './core/proxy' 9 | import upgrade from './core/upgrade' 10 | import type { SubcommandItem } from '@/types' 11 | 12 | /** 13 | * Main entry of the program 14 | */ 15 | function start() { 16 | program 17 | .name('preset') 18 | .version(version, '-v, --version', 'output the version number') 19 | .usage(' [options]') 20 | .option('-h, --help', 'output usage information') 21 | 22 | /** 23 | * The `init` command 24 | */ 25 | program 26 | .command('init [app-name]', { isDefault: true }) 27 | .alias('i') 28 | .description('generate a project from a preset template') 29 | .option('-t, --template', 'specify a template name') 30 | .action((source) => { 31 | init(source).catch((e) => { 32 | console.error(e) 33 | }) 34 | }) 35 | 36 | /** 37 | * The `config` command 38 | */ 39 | const configCMD = program.command('config') 40 | configCMD 41 | .alias('c') 42 | .description('use the local preset config') 43 | .option('-t, --tech', 'configure the local technology stack') 44 | CONFIG_SUB_CMDS.forEach((item: SubcommandItem) => { 45 | const { cmd, desc } = item 46 | configCMD 47 | .command(cmd) 48 | .description(desc) 49 | .action((filePath) => { 50 | configure({ 51 | cmd, 52 | filePath, 53 | }).catch((e: any) => { 54 | console.error(e) 55 | }) 56 | }) 57 | }) 58 | 59 | /** 60 | * The `config` command 61 | */ 62 | const proxyCMD = program.command('proxy') 63 | proxyCMD.alias('p').description('use proxy to download template') 64 | PROXY_SUB_CMDS.forEach((item: SubcommandItem) => { 65 | const { cmd, desc } = item 66 | proxyCMD 67 | .command(cmd) 68 | .description(desc) 69 | .action(() => { 70 | proxy({ 71 | cmd, 72 | }).catch((e: any) => { 73 | console.error(e) 74 | }) 75 | }) 76 | }) 77 | 78 | /** 79 | * The `upgrade` command 80 | */ 81 | program 82 | .command('upgrade') 83 | .alias('u') 84 | .description('updated version') 85 | .action(() => { 86 | upgrade().catch((e) => { 87 | console.error(e) 88 | }) 89 | }) 90 | 91 | // Output help information on unknown commands 92 | program.on('command:*', ([cmd]) => { 93 | program.outputHelp() 94 | console.log(` ` + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`)) 95 | suggestCommands(cmd) 96 | console.log() 97 | process.exitCode = 1 98 | }) 99 | 100 | // Add some useful info on help 101 | program.on('--help', () => { 102 | console.log() 103 | console.log( 104 | ` Run ${chalk.cyan( 105 | `preset init ` 106 | )} to initialize your project.` 107 | ) 108 | console.log() 109 | }) 110 | 111 | program.commands.forEach((c) => c.on('--help', () => console.log())) 112 | 113 | program.parse(process.argv) 114 | } 115 | start() 116 | -------------------------------------------------------------------------------- /src/libs/argv.ts: -------------------------------------------------------------------------------- 1 | import minimist from '@withtypes/minimist' 2 | 3 | // Avoids autoconversion to number of the project name by defining that the args 4 | // non associated with an option ( _ ) needs to be parsed as a string. See #4606 5 | const argv = minimist(process.argv.slice(2), { string: ['_'] }) 6 | export default argv 7 | -------------------------------------------------------------------------------- /src/libs/cmd.ts: -------------------------------------------------------------------------------- 1 | import leven from 'leven' 2 | import chalk from 'chalk' 3 | import { program } from 'commander' 4 | 5 | /** 6 | * Suggest command when user input unknow command 7 | * 8 | * @param unknownCMD - Unknown command 9 | */ 10 | export function suggestCommands(unknownCMD: string): void { 11 | // Find suggested commands 12 | let suggestion = '' 13 | const availableCommands: string[] = program.commands.map( 14 | (cmd: any) => cmd._name 15 | ) 16 | availableCommands.forEach((cmd) => { 17 | const isBestMatch = 18 | leven(cmd, unknownCMD) < leven(suggestion || '', unknownCMD) 19 | if (leven(cmd, unknownCMD) < 3 && isBestMatch) { 20 | suggestion = cmd 21 | } 22 | }) 23 | 24 | // If a suitable command is found, prompt the user 25 | if (suggestion) { 26 | console.log(` ` + chalk.red(`Did you mean ${chalk.yellow(suggestion)}?`)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/libs/config.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from '@withtypes/fs-extra' 2 | import chalk from 'chalk' 3 | import ora from 'ora' 4 | import axios from 'axios' 5 | import { ellipsis, shuffle, unique } from '@bassist/utils' 6 | import { getBaseUrl } from '@/data' 7 | import { showTipsToEnableProxy, welcome } from './tips' 8 | import { getRuntimeConfig as getLocalConfigFilePath, isProxyOn } from './local' 9 | import { isValidConfig } from './validator' 10 | import type { 11 | ColorConfig, 12 | TechConfig, 13 | TechStackItem, 14 | OriginConfigItem, 15 | ConfigItem, 16 | } from '@/types' 17 | 18 | /** 19 | * Template name's color 20 | */ 21 | const colorConfig: ColorConfig = { 22 | official: chalk.yellow, 23 | community: chalk.white, 24 | local: chalk.cyan, 25 | } 26 | 27 | /** 28 | * Read tech stack config file from local 29 | * @returns Tech list 30 | */ 31 | export function readTechConfig(): TechConfig[] { 32 | try { 33 | const filePath = getLocalConfigFilePath('localTech') 34 | const data = readFileSync(filePath, 'utf-8') 35 | const config: TechConfig[] = JSON.parse(data) 36 | if (!Array.isArray(config)) { 37 | return [] 38 | } 39 | return config 40 | } catch (e) { 41 | return [] 42 | } 43 | } 44 | 45 | /** 46 | * Get the list of supported tech stacks 47 | * @returns Tech list 48 | */ 49 | export async function queryTechConfig(): Promise { 50 | try { 51 | const baseUrl = getBaseUrl() 52 | const res = await axios(`${baseUrl}/tech.json`) 53 | const config: TechConfig[] = res.data 54 | if (!Array.isArray(config)) { 55 | return [] 56 | } 57 | return config 58 | } catch (e) { 59 | return [] 60 | } 61 | } 62 | 63 | /** 64 | * Unique tech stacks 65 | * @returns A unique list 66 | */ 67 | export async function uniqueTechConfig(): Promise { 68 | const uniqueList = unique({ 69 | primaryKey: 'name', 70 | list: [...(await queryTechConfig()), ...readTechConfig()], 71 | }) 72 | return uniqueList as TechConfig[] 73 | } 74 | 75 | /** 76 | * Get the basic tech stack config, without variants 77 | * @returns The tech stack config without variants 78 | */ 79 | export async function getTechStacks(): Promise { 80 | const techConfig: TechConfig[] = await uniqueTechConfig() 81 | const techStack: TechStackItem[] = techConfig.map((tech) => { 82 | return { 83 | name: tech.name, 84 | color: chalk.hex(tech.color), 85 | variants: [], 86 | } 87 | }) 88 | 89 | return techStack 90 | } 91 | 92 | /** 93 | * Handle origin config item to be config item 94 | * 95 | * @param fileName - The config file name 96 | * @param originConfig - The origin config 97 | * @returns The config array from root config file 98 | */ 99 | export async function handleOriginConfig( 100 | fileName: string, 101 | originConfig: OriginConfigItem[] 102 | ): Promise { 103 | if (!Array.isArray(originConfig)) { 104 | return [] 105 | } 106 | 107 | const techConfig: TechConfig[] = await uniqueTechConfig() 108 | const techNames: string[] = techConfig.map((t) => t.name) 109 | const config: ConfigItem[] = originConfig 110 | .filter((item) => techNames.includes(item.tech)) 111 | .map((item) => { 112 | return { 113 | ...item, 114 | color: colorConfig[fileName], 115 | } 116 | }) 117 | 118 | return config 119 | } 120 | 121 | /** 122 | * Read config file from local 123 | * @param fileName - The config file name 124 | * @returns The config array from root config file 125 | */ 126 | export async function readConfigFile(fileName: string): Promise { 127 | try { 128 | const filePath: string = getLocalConfigFilePath() 129 | const data: string = readFileSync(filePath, 'utf-8') 130 | const originConfig: OriginConfigItem[] = JSON.parse(data) 131 | const config: ConfigItem[] = await handleOriginConfig( 132 | fileName, 133 | originConfig 134 | ) 135 | return config 136 | } catch (e) { 137 | return [] 138 | } 139 | } 140 | 141 | /** 142 | * Fetch config file from remote 143 | * @param fileName - The config file name 144 | * @returns The config array from root config file 145 | */ 146 | export async function queryConfigFile(fileName: string): Promise { 147 | try { 148 | const baseUrl = getBaseUrl() 149 | const res = await axios(`${baseUrl}/${fileName}.json`) 150 | if (!Array.isArray(res.data)) { 151 | return [] 152 | } 153 | 154 | const originConfig: OriginConfigItem[] = res.data 155 | .map((item) => { 156 | return { 157 | tech: item.tech || '', 158 | name: item.name || '', 159 | desc: item.desc || '', 160 | repo: item.repo || '', 161 | mirror: item.mirror || '', 162 | } 163 | }) 164 | .filter((item) => isValidConfig(item)) 165 | 166 | const config: ConfigItem[] = await handleOriginConfig( 167 | fileName, 168 | originConfig 169 | ) 170 | 171 | return config 172 | } catch (e) { 173 | return [] 174 | } 175 | } 176 | 177 | /** 178 | * Unique tech stacks 179 | * @returns A unique list 180 | */ 181 | export async function uniqueConfig(): Promise { 182 | const uniqueListByName = unique({ 183 | primaryKey: 'name', 184 | list: [ 185 | ...(await readConfigFile('local')), 186 | ...(await queryConfigFile('official')), 187 | ...shuffle(await queryConfigFile('community')), 188 | ], 189 | }) 190 | const uniqueList = unique({ 191 | primaryKey: 'repo', 192 | list: uniqueListByName, 193 | }) 194 | return uniqueList 195 | } 196 | 197 | /** 198 | * Get config 199 | * @returns Config 200 | */ 201 | export async function getConfig(): Promise<{ 202 | techStacks: TechStackItem[] 203 | templates: string[] 204 | allTemplates: ConfigItem[] 205 | }> { 206 | console.log() 207 | await welcome() 208 | 209 | if (!isProxyOn()) { 210 | showTipsToEnableProxy() 211 | } 212 | 213 | const spinner = ora('Fetching the latest config…').start() 214 | 215 | // Get tech stack data 216 | const techStacks: TechStackItem[] = await getTechStacks() 217 | 218 | // Meger all template data 219 | const templateList: ConfigItem[] = await uniqueConfig() 220 | 221 | // Fill tech stack variants 222 | templateList.forEach((template) => { 223 | const { tech, name, desc, repo, mirror, color } = template 224 | const target = techStacks.find((t) => t.name === tech) 225 | if (!target) return 226 | target.variants.push({ 227 | name: ellipsis(name, 20), 228 | desc: ellipsis(desc, 80), 229 | repo, 230 | mirror, 231 | color, 232 | }) 233 | }) 234 | 235 | // Get template names from tech stack list 236 | const templates = techStacks 237 | .map((f) => (f.variants && f.variants.map((v) => v.name)) || [f.name]) 238 | .reduce((a, b) => a.concat(b), []) 239 | 240 | spinner.succeed(chalk.green('Get the latest config successfully.')) 241 | console.log() 242 | 243 | return { 244 | techStacks, 245 | templates, 246 | allTemplates: templateList, 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/libs/download.ts: -------------------------------------------------------------------------------- 1 | import ora from 'ora' 2 | import chalk from 'chalk' 3 | import downloadGitRepo from 'download-git-repo' 4 | import { whitelist } from '@/data' 5 | import { isProxyOn } from './local' 6 | import { isValidDownloadUrl } from './validator' 7 | import type { DownloadOptions, GetDownloadUrlOptions } from '@/types' 8 | 9 | function formatDownloadUrl(repo: string) { 10 | // By default, the repo url is used to download 11 | let url = repo 12 | 13 | // Use shorthand repository string 14 | whitelist.forEach((w) => { 15 | if (repo.startsWith(w)) { 16 | const short = w.replace(/https:\/\/(.*).com\//, '$1') 17 | url = repo.replace(w, `${short}:`) 18 | } 19 | }) 20 | 21 | // If the domain name is not in the whitelist 22 | // Need to add the `direct` option 23 | // It will bypass the shorthand normalizer and pass url directly 24 | if (repo.startsWith('http')) { 25 | url = `direct:${repo}` 26 | } 27 | 28 | // Use direct to clone private repo 29 | if (repo.startsWith('git@')) { 30 | url = `direct:${repo}` 31 | } 32 | 33 | return url 34 | } 35 | 36 | /** 37 | * Get Download URL 38 | * @returns The repo url about selected template starter 39 | */ 40 | export function getDownloadUrl({ template, variants }: GetDownloadUrlOptions) { 41 | if (!Array.isArray(variants)) return '' 42 | 43 | const target = variants.find((v) => v.name === template) 44 | if (!target) return '' 45 | 46 | // Check if required proxy to speed up GitHub download 47 | const isUseMirror = isProxyOn() && isValidDownloadUrl(target.mirror) 48 | const repo = isUseMirror ? target.mirror : target.repo 49 | 50 | return formatDownloadUrl(repo) 51 | } 52 | 53 | /** 54 | * Download Git Repo 55 | * @returns Whether the download status is successful 56 | */ 57 | export function download({ 58 | repo, 59 | folder, 60 | clone, 61 | }: DownloadOptions): Promise { 62 | return new Promise((resolve) => { 63 | console.log() 64 | const spinner = ora('Downloading…').start() 65 | 66 | // Since most hosting platforms require login to download the zip archive 67 | // So use the clone mode to pull the file correctly 68 | clone = clone || repo.startsWith('direct') ? true : false 69 | 70 | downloadGitRepo(repo, folder, { clone }, (err: any) => { 71 | if (err) { 72 | console.log() 73 | console.log() 74 | console.log(err) 75 | console.log() 76 | spinner.fail(chalk.red('Download failed.')) 77 | console.log() 78 | resolve(false) 79 | process.exit() 80 | } 81 | 82 | console.log() 83 | spinner.succeed(chalk.green('Download successfully.')) 84 | console.log() 85 | resolve(true) 86 | }) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /src/libs/local.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { readFileSync, writeFileSync } from '@withtypes/fs-extra' 3 | import { homedir } from 'os' 4 | import { resolve } from 'path' 5 | import { hasKey } from '@bassist/utils' 6 | import argv from './argv' 7 | import type { RuntimeConfigFileContent, LocalConfigType } from '@/types' 8 | 9 | // Get user's config path of program 10 | const rcFile = resolve(homedir(), '.presetrc') 11 | 12 | // If `true`, handle the tech config 13 | const isTech = Boolean(argv.tech) || Boolean(argv.t) 14 | const runtimeConfigType: LocalConfigType = isTech ? 'localTech' : 'localPreset' 15 | 16 | /** 17 | * Get `.presetrc` file content 18 | * @returns JSON of `.presetrc` 19 | */ 20 | export function readRuntimeConfigFile(): RuntimeConfigFileContent { 21 | let rcConfig: RuntimeConfigFileContent = {} 22 | 23 | try { 24 | const data = readFileSync(rcFile, 'utf-8') 25 | rcConfig = JSON.parse(data) 26 | } catch (e) { 27 | // console.log(e) 28 | } 29 | 30 | const keys = ['proxy', 'localTech', 'localPreset'] 31 | keys.forEach((k) => { 32 | if (!hasKey(rcConfig, k)) { 33 | rcConfig[k] = '' 34 | } 35 | }) 36 | 37 | return rcConfig 38 | } 39 | 40 | /** 41 | * Save `.presetrc` file content 42 | * @param key - Key of `.presetrc` JSON 43 | * @param value - Value of the key in `.presetrc` JSON 44 | * @returns Whether the save operation was successful 45 | */ 46 | export function saveRuntimeConfigFile(key: string, value: string) { 47 | try { 48 | const rcConfig: RuntimeConfigFileContent = readRuntimeConfigFile() 49 | rcConfig[key] = value 50 | writeFileSync(rcFile, JSON.stringify(rcConfig, null, 2)) 51 | return true 52 | } catch (e) { 53 | console.log(e) 54 | return false 55 | } 56 | } 57 | 58 | /** 59 | * Get local preset file path from user config 60 | * @param isGetTech - If `true`, return tech stack 61 | * @returns The local preset file path 62 | */ 63 | export function getRuntimeConfig(mode: LocalConfigType = 'localPreset') { 64 | try { 65 | const rcConfig = readRuntimeConfigFile() 66 | const targetKey = mode === 'localTech' ? 'localTech' : runtimeConfigType 67 | const filePath = rcConfig[targetKey] 68 | ? resolve(String(rcConfig[targetKey])) 69 | : '' 70 | return filePath 71 | } catch (e) { 72 | return '' 73 | } 74 | } 75 | 76 | /** 77 | * Set local preset file path into user config 78 | * @param filePath - The local config file path 79 | */ 80 | export function setRuntimeConfig(filePath: string) { 81 | if (!filePath.endsWith('.json')) { 82 | console.log() 83 | console.log(` ` + chalk.red('The file path must be a ".json" file.')) 84 | console.log() 85 | return false 86 | } 87 | return saveRuntimeConfigFile(runtimeConfigType, filePath) 88 | } 89 | 90 | /** 91 | * Remove local preset file path from user config 92 | */ 93 | export function removeRuntimeConfig() { 94 | return saveRuntimeConfigFile(runtimeConfigType, '') 95 | } 96 | 97 | /** 98 | * Check if the proxy feature is enabled 99 | */ 100 | export function isProxyOn() { 101 | const { proxy } = readRuntimeConfigFile() 102 | return proxy === 'on' 103 | } 104 | -------------------------------------------------------------------------------- /src/libs/pkg.ts: -------------------------------------------------------------------------------- 1 | import ora from 'ora' 2 | import chalk from 'chalk' 3 | import latestVersion from 'latest-version' 4 | import compareVersions from 'compare-versions' 5 | import { name as packageName, version } from '../../package.json' 6 | import { PackageUpgradeInfo } from '@/types' 7 | 8 | /** 9 | * Query the CLI upgrade info 10 | * @param curVersion - The current version number 11 | * @param assignVersion - Assign a version to test 12 | */ 13 | export async function queryPackageUpgradeInfo( 14 | curVersion = '', 15 | assignVersion?: string 16 | ): Promise { 17 | console.log() 18 | const spinner = ora('Detecting upgrade information…').start() 19 | // The current version 20 | let cv: string = curVersion || version || '0.0.0' 21 | 22 | // The latest version 23 | let lv: string = assignVersion || '0.0.0' 24 | 25 | // Check the current version is need to upgrade 26 | let needToUpgrade = false 27 | 28 | if (!assignVersion) { 29 | try { 30 | lv = await latestVersion(packageName) 31 | needToUpgrade = compareVersions(cv, lv) === -1 32 | } catch (e) { 33 | // console.log(e) 34 | } 35 | } 36 | 37 | console.log() 38 | spinner.succeed(chalk.green('Detected successfully.')) 39 | 40 | return { 41 | packageName, 42 | currentVersion: cv, 43 | latestVersion: lv, 44 | needToUpgrade, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/libs/tips.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import figlet from '@withtypes/figlet' 3 | 4 | export function welcome(): Promise { 5 | return new Promise((resolve) => { 6 | figlet('Create Preset', (err, data) => { 7 | if (err) { 8 | resolve() 9 | return 10 | } 11 | console.log() 12 | console.log( 13 | '---------------------------------------------------------------' 14 | ) 15 | console.log(data) 16 | console.log( 17 | ' https://preset.js.org ' 18 | ) 19 | console.log( 20 | '---------------------------------------------------------------' 21 | ) 22 | console.log() 23 | console.log() 24 | resolve() 25 | }) 26 | }) 27 | } 28 | 29 | export function showTipsToEnableProxy() { 30 | console.log( 31 | chalk.gray(`* If requests are slow, proxy download can be enabled:`) 32 | ) 33 | console.log( 34 | chalk.gray(` 1. Run "npm i -g create-preset" to install globally`) 35 | ) 36 | console.log( 37 | chalk.gray(` 2. Run "preset proxy on" to turn on proxy downloads`) 38 | ) 39 | console.log( 40 | chalk.gray( 41 | ` 3. Run "preset i" to create preset, it should be very fast now` 42 | ) 43 | ) 44 | console.log() 45 | console.log() 46 | } 47 | -------------------------------------------------------------------------------- /src/libs/validator.ts: -------------------------------------------------------------------------------- 1 | import type { OriginConfigItem } from '@/types' 2 | 3 | export function isValidConfig(config: OriginConfigItem) { 4 | return config.tech && config.name && config.repo 5 | } 6 | 7 | export function isValidDownloadUrl(url: string) { 8 | return url.startsWith('http') || url.startsWith('git') 9 | } 10 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { Chalk } from 'chalk' 2 | 3 | /** 4 | * The `.presetrc` file content type 5 | */ 6 | export interface RuntimeConfigFileContent { 7 | proxy?: string 8 | localTech?: string 9 | localPreset?: string 10 | } 11 | 12 | export type LocalConfigType = 'localTech' | 'localPreset' 13 | 14 | /** 15 | * Subcommand type for core 16 | */ 17 | export interface SubcommandItem { 18 | cmd: string 19 | desc: string 20 | } 21 | 22 | export interface PackageUpgradeInfo { 23 | packageName: string 24 | currentVersion: string 25 | latestVersion: string 26 | needToUpgrade: boolean 27 | } 28 | 29 | /** 30 | * The starter item type of root config file 31 | */ 32 | export interface OriginConfigItem { 33 | tech: string 34 | name: string 35 | desc: string 36 | repo: string 37 | mirror: string 38 | } 39 | 40 | /** 41 | * The item type of config 42 | */ 43 | export interface ConfigItem extends OriginConfigItem { 44 | color: Chalk 45 | } 46 | 47 | /** 48 | * The type for CMD from user input 49 | */ 50 | export interface UserInputInfoFromCommandLine { 51 | projectName: string 52 | packageName: string 53 | overwrite: boolean 54 | techStack: TechStackItem | undefined 55 | variant: string 56 | } 57 | 58 | /** 59 | * The item type of `variants` in tech stack 60 | */ 61 | export interface VariantItem extends Omit {} 62 | 63 | /** 64 | * The type of color about the source of starters 65 | */ 66 | export interface ColorConfig { 67 | official: Chalk 68 | community: Chalk 69 | local: Chalk 70 | } 71 | 72 | /** 73 | * The type of tech stack from fetch 74 | */ 75 | export interface TechConfig { 76 | name: string 77 | color: string 78 | } 79 | 80 | /** 81 | * The item type of tech stack in config 82 | */ 83 | export interface TechStackItem extends Omit { 84 | color: Chalk 85 | variants: VariantItem[] 86 | } 87 | 88 | export interface GetDownloadUrlOptions { 89 | /** 90 | * The selected template name from CMD 91 | */ 92 | template: string 93 | 94 | /** 95 | * The `variants` in `techStack` from config 96 | */ 97 | variants: VariantItem[] | ConfigItem[] 98 | } 99 | 100 | export interface DownloadOptions { 101 | /** 102 | * The repo url to download 103 | */ 104 | repo: string 105 | 106 | /** 107 | * The project folder name 108 | */ 109 | folder: string 110 | 111 | /** 112 | * If true, use `git clone` to download repo 113 | */ 114 | clone?: boolean 115 | } 116 | -------------------------------------------------------------------------------- /test/download.test.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { describe, expect, it } from 'vitest' 3 | import { emptyDir } from '@bassist/node-utils' 4 | import { download } from '../src/libs/download' 5 | 6 | const folder = './test/.test-download' 7 | const fullPath = resolve(__dirname, '.', folder) 8 | const timeout = 30000 9 | emptyDir(fullPath) 10 | 11 | describe('download', () => { 12 | // it( 13 | // 'Download Git Repo with master branch', 14 | // async () => { 15 | // expect( 16 | // await download({ 17 | // repo: 'github.com:awesome-starter/node-basic-starter', 18 | // folder, 19 | // }) 20 | // ).toBeTruthy() 21 | // emptyDir(fullPath) 22 | // }, 23 | // timeout 24 | // ) 25 | 26 | // it( 27 | // 'Download Git Repo with main branch', 28 | // async () => { 29 | // expect( 30 | // await download({ 31 | // repo: 'github.com:awesome-starter/website#main', 32 | // folder, 33 | // }) 34 | // ).toBeTruthy() 35 | // emptyDir(fullPath) 36 | // }, 37 | // timeout 38 | // ) 39 | 40 | it( 41 | 'Download git repo from Gitee, with `.git` extension', 42 | async () => { 43 | expect( 44 | await download({ 45 | repo: 'direct:https://gitee.com/awesome-starter/vue3-ts-vite-starter.git', 46 | folder, 47 | clone: true, 48 | }) 49 | ).toBeTruthy() 50 | emptyDir(fullPath) 51 | }, 52 | timeout 53 | ) 54 | 55 | it( 56 | 'Download git repo from Gitee, without `.git` extension', 57 | async () => { 58 | expect( 59 | await download({ 60 | repo: 'direct:https://gitee.com/awesome-starter/vue3-ts-vite-starter', 61 | folder, 62 | clone: true, 63 | }) 64 | ).toBeTruthy() 65 | emptyDir(fullPath) 66 | }, 67 | timeout 68 | ) 69 | 70 | // it( 71 | // 'Download Private Git Repo, direct clone', 72 | // async () => { 73 | // expect( 74 | // await download({ 75 | // repo: 'direct:git@gitee.com:somemore/git-branch-test.git', 76 | // folder, 77 | // clone: true, 78 | // }) 79 | // ).toBeTruthy() 80 | // emptyDir(fullPath) 81 | // }, 82 | // timeout 83 | // ) 84 | }) 85 | -------------------------------------------------------------------------------- /test/getDownloadUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest' 2 | import { getDownloadUrl } from '../src/libs/download' 3 | import { readRuntimeConfigFile } from '../src/libs/local' 4 | 5 | describe('getDownloadUrl', () => { 6 | test('GitHub', () => { 7 | const { proxy } = readRuntimeConfigFile() 8 | expect( 9 | getDownloadUrl({ 10 | template: 'test', 11 | variants: [ 12 | { 13 | name: 'test', 14 | repo: 'https://github.com/awesome-starter/vite-vue3-ts-starter', 15 | }, 16 | ], 17 | }) 18 | ).toBe( 19 | proxy 20 | ? 'github.com.cnpmjs.org:awesome-starter/vite-vue3-ts-starter' 21 | : 'github:awesome-starter/vite-vue3-ts-starter' 22 | ) 23 | }) 24 | }) 25 | 26 | describe('getDownloadUrl', () => { 27 | test('GitLab', () => { 28 | expect( 29 | getDownloadUrl({ 30 | template: 'test', 31 | variants: [ 32 | { 33 | name: 'test', 34 | repo: 'https://gitlab.com/flippidippi/download-git-repo', 35 | }, 36 | ], 37 | }) 38 | ).toBe('gitlab:flippidippi/download-git-repo') 39 | }) 40 | }) 41 | 42 | describe('getDownloadUrl', () => { 43 | test('Gitee', () => { 44 | expect( 45 | getDownloadUrl({ 46 | template: 'test', 47 | variants: [ 48 | { 49 | name: 'test', 50 | repo: 'https://gitee.com/mindspore/mindspore', 51 | }, 52 | ], 53 | }) 54 | ).toBe('https://gitee.com/mindspore/mindspore') 55 | }) 56 | }) 57 | 58 | describe('getDownloadUrl', () => { 59 | test('Unknown', () => { 60 | expect( 61 | getDownloadUrl({ 62 | template: 'test', 63 | variants: [ 64 | { 65 | name: 'test', 66 | }, 67 | ], 68 | }) 69 | ).toBe('') 70 | }) 71 | }) 72 | 73 | describe('getDownloadUrl', () => { 74 | test('Empty', () => { 75 | expect( 76 | getDownloadUrl({ 77 | template: 'test', 78 | variants: [ 79 | { 80 | name: 'test', 81 | repo: '', 82 | }, 83 | ], 84 | }) 85 | ).toBe('') 86 | }) 87 | }) 88 | 89 | describe('getDownloadUrl', () => { 90 | test('SSH', () => { 91 | expect( 92 | getDownloadUrl({ 93 | template: 'test', 94 | variants: [ 95 | { 96 | name: 'test1', 97 | repo: 'git@github.com:bcherny/json-schema-to-typescript.git', 98 | }, 99 | ], 100 | }) 101 | ).toBe('') 102 | }) 103 | }) 104 | 105 | describe('getDownloadUrl', () => { 106 | test('Private', () => { 107 | expect( 108 | getDownloadUrl({ 109 | template: 'test', 110 | variants: [ 111 | { 112 | name: 'test', 113 | repo: 'git@gitee.com:somemore/git-branch-test.git', 114 | }, 115 | ], 116 | }) 117 | ).toBe('direct:git@gitee.com:somemore/git-branch-test.git') 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /test/uniqueTechConfig.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest' 2 | import { uniqueTechConfig, uniqueConfig } from '../src/libs/config' 3 | 4 | describe('unique', () => { 5 | test('Unique Tech Config', async () => { 6 | const unique = await uniqueTechConfig() 7 | console.log('uniqueTechConfig', unique) 8 | }) 9 | }) 10 | 11 | describe('unique', () => { 12 | test('Unique Config', async () => { 13 | const unique = await uniqueConfig() 14 | console.log('uniqueConfig', unique) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/upgrade.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest' 2 | import { queryPackageUpgradeInfo } from '../src/libs/pkg' 3 | 4 | describe('upgrade', () => { 5 | test('compareVersion 0.1.0 vs 1.0.0', () => { 6 | expect(async () => { 7 | const { needToUpgrade } = await queryPackageUpgradeInfo('0.1.0', '1.0.0') 8 | return needToUpgrade 9 | }).toBeTruthy() 10 | }) 11 | }) 12 | 13 | describe('upgrade', () => { 14 | test('compareVersion 0.1.1 vs 1.0.0', () => { 15 | expect(async () => { 16 | const { needToUpgrade } = await queryPackageUpgradeInfo('0.1.1', '1.0.0') 17 | return needToUpgrade 18 | }).toBeTruthy() 19 | }) 20 | }) 21 | 22 | describe('upgrade', () => { 23 | test('compareVersion 0.2.0-alpha.0 vs 1.0.0', () => { 24 | expect(async () => { 25 | const { needToUpgrade } = await queryPackageUpgradeInfo( 26 | '0.2.0-alpha.0', 27 | '1.0.0' 28 | ) 29 | return needToUpgrade 30 | }).toBeTruthy() 31 | }) 32 | }) 33 | 34 | describe('upgrade', () => { 35 | test('compareVersion 1.0.0-alpha.0 vs 1.0.0-alpha.1', () => { 36 | expect(async () => { 37 | const { needToUpgrade } = await queryPackageUpgradeInfo( 38 | '1.0.0-alpha.0', 39 | '1.0.0-alpha.1' 40 | ) 41 | return needToUpgrade 42 | }).toBeTruthy() 43 | }) 44 | }) 45 | 46 | describe('upgrade', () => { 47 | test('compareVersion 1.0.0-alpha.0 vs 1.0.0', () => { 48 | expect(async () => { 49 | const { needToUpgrade } = await queryPackageUpgradeInfo( 50 | '1.0.0-alpha.0', 51 | '1.0.0' 52 | ) 53 | return needToUpgrade 54 | }).toBeTruthy() 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "commonjs", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "NodeNext", 9 | "resolveJsonModule": true, 10 | "suppressImplicitAnyIndexErrors": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "experimentalDecorators": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "incremental": false, 16 | "allowSyntheticDefaultImports": true, 17 | "sourceMap": true, 18 | "baseUrl": ".", 19 | "rootDir": ".", 20 | "types": ["vite/client", "node"], 21 | "paths": { 22 | "@/*": ["src/*"] 23 | }, 24 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 25 | "declaration": true 26 | }, 27 | "include": ["./src", "./src/global.d.ts"], 28 | "exclude": ["node_modules", "dist"] 29 | } 30 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import path from 'path' 3 | import banner from 'vite-plugin-banner' 4 | import commonjsExternals from 'vite-plugin-commonjs-externals' 5 | import { cjsExternalsRegExp } from './scripts/build/options' 6 | import pkg from './package.json' 7 | 8 | const resolve = (dir: string): string => path.resolve(__dirname, dir) 9 | 10 | // https://vitejs.dev/config/ 11 | export default defineConfig({ 12 | build: { 13 | outDir: 'dist', 14 | lib: { 15 | entry: resolve('src/index.ts'), 16 | name: 'preset', 17 | formats: ['cjs'], 18 | fileName: () => `${pkg.name}.js`, 19 | }, 20 | minify: true, 21 | }, 22 | resolve: { 23 | alias: { 24 | '@': resolve('src'), 25 | }, 26 | }, 27 | plugins: [ 28 | banner( 29 | [ 30 | `#!/usr/bin/env node`, 31 | ``, 32 | `/**`, 33 | ` * name: ${pkg.name}`, 34 | ` * version: v${pkg.version}`, 35 | ` * description: ${pkg.description}`, 36 | ` * author: ${pkg.author}`, 37 | ` * homepage: ${pkg.homepage}`, 38 | ` * license: ${pkg.license}`, 39 | ` */`, 40 | ].join('\n') 41 | ), 42 | commonjsExternals({ 43 | externals: ['fs', 'os', 'path', 'child_process', cjsExternalsRegExp()], 44 | }), 45 | ], 46 | }) 47 | --------------------------------------------------------------------------------