-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: ShenQingchuan <[email protected]>
- Loading branch information
1 parent
76de79f
commit c404c79
Showing
39 changed files
with
1,767 additions
and
283 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# create-vue-vine <a href="https://npmjs.com/package/create-vite"><img src="https://img.shields.io/npm/v/create-vue-vine" alt="npm package"></a> <img src="https://img.shields.io/badge/experimental-aa58ff" /> | ||
|
||
The official CLI for creating your Vue Vine projects. | ||
|
||
> **Compatibility Note**: Vue Vine dev-server which is `vite` is requires Node.js version 18+, 20+. However, some templates require a higher Node.js version to work, please upgrade if your package manager warns about it. | ||
With NPM: | ||
|
||
```bash | ||
$ npm create vue-vine@latest | ||
``` | ||
|
||
With Yarn: | ||
|
||
```bash | ||
$ yarn create vue-vine | ||
``` | ||
|
||
With PNPM: | ||
|
||
```bash | ||
$ pnpm create vue-vine | ||
``` | ||
|
||
With Bun: | ||
|
||
```bash | ||
$ bun create vue-vine | ||
``` | ||
|
||
Then follow the prompts! | ||
|
||
You can also directly specify the project name and the template you want to use via additional command line options. For example, to scaffold a Vue Vine `vue-router` app, run: | ||
|
||
```bash | ||
# npm 7+, extra double-dash is needed: | ||
npm create vue-vine@latest my-vue-vine-app -- --router | ||
|
||
# yarn | ||
yarn create vue-vine my-vue-vine-app --router | ||
|
||
# pnpm | ||
pnpm create vue-vine my-vue-vine-app --router | ||
|
||
# Bun | ||
bun create vue-vine my-vue-vine-app --router | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/usr/bin/env node | ||
'use strict' | ||
import '../dist/index.mjs' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{ | ||
"name": "create-vue-vine", | ||
"version": "0.0.1", | ||
"description": "Official CLI for creating Vue Vine project.", | ||
"author": "ShenQingchuan", | ||
"license": "MIT", | ||
"funding": "https://github.com/vue-vine/vue-vine?sponsor=1", | ||
"homepage": "https://github.com/vue-vine/vue-vine/tree/main/packages/create-vue-vine#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/vue-vine/vue-vine.git", | ||
"directory": "packages/create-vue-vine" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/vue-vine/vue-vine/issues" | ||
}, | ||
"keywords": [ | ||
"Vue", | ||
"Vine" | ||
], | ||
"exports": { | ||
".": { | ||
"import": "./dist/index.mjs" | ||
} | ||
}, | ||
"module": "./dist/index.mjs", | ||
"bin": "./bin/create-vue-vine.mjs", | ||
"files": [ | ||
"bin", | ||
"dist", | ||
"template" | ||
], | ||
"scripts": { | ||
"dev": "tsup --watch", | ||
"build": "tsup" | ||
}, | ||
"dependencies": { | ||
"@clack/prompts": "^0.7.0", | ||
"clerc": "^0.42.2", | ||
"execa": "^8.0.1", | ||
"pkg-dir": "^7.0.0", | ||
"yoctocolors": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.4.9", | ||
"eslint": "^8.48.0", | ||
"unocss": "^0.55.3", | ||
"unplugin-auto-import": "^0.16.6", | ||
"vite": "^4.4.9", | ||
"vite-plugin-inspect": "^0.7.38", | ||
"vue": "^3.3.4", | ||
"vue-vine": "workspace:*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { join, relative } from 'node:path' | ||
import { rm } from 'node:fs/promises' | ||
import process from 'node:process' | ||
import { intro, log, outro, spinner } from '@clack/prompts' | ||
import { Root, defineCommand } from 'clerc' | ||
import { bold, green } from 'yoctocolors' | ||
import { cancel, confirm, exists, formatPmCommand, getPmCommand, getTemplateDirectory, gradientBanner, runPmCommand, text, validateProjectName } from '@/utils' | ||
import { creaateProjectOptions, createProject } from '@/create' | ||
import { useFlags } from '@/flags' | ||
|
||
const defaultProjectName = 'vue-vine-project' | ||
|
||
const { flags, executeFlags } = useFlags() | ||
|
||
export const createCommand = defineCommand({ | ||
name: Root, | ||
description: 'Create a Vue Vine project', | ||
parameters: [ | ||
'[projectName]', | ||
], | ||
flags: { | ||
force: { | ||
type: Boolean, | ||
description: 'Delete existing folder', | ||
alias: 'f', | ||
default: false, | ||
}, | ||
install: { | ||
type: Boolean, | ||
description: 'Install dependencies', | ||
alias: 'i', | ||
}, | ||
...flags, | ||
}, | ||
alias: 'create', | ||
}, async (ctx) => { | ||
intro(gradientBanner) | ||
const cwd = process.cwd() | ||
if (!ctx.parameters.projectName) { | ||
ctx.parameters.projectName = await text({ | ||
message: 'Project name:', | ||
placeholder: defaultProjectName, | ||
defaultValue: defaultProjectName, | ||
validate: (value) => { | ||
if (!validateProjectName(value)) { | ||
return 'Invalid project name' | ||
} | ||
}, | ||
}) | ||
} | ||
const projectPath = join(cwd, ctx.parameters.projectName) | ||
if (await exists(projectPath)) { | ||
if (!ctx.flags.force) { | ||
ctx.flags.force = await confirm({ | ||
message: `Folder ${ctx.parameters.projectName} already exists. Delete?`, | ||
initialValue: false, | ||
}) | ||
} | ||
if (!ctx.flags.force) { | ||
cancel(`Folder ${ctx.parameters.projectName} already exists. Goodbye!`) | ||
} | ||
else { | ||
log.info(`Folder ${ctx.parameters.projectName} will be deleted.`) | ||
await rm(projectPath, { recursive: true }) | ||
} | ||
} | ||
const templateDir = await getTemplateDirectory() | ||
if (!templateDir) { | ||
cancel('Unable to find template directory') | ||
} | ||
|
||
const projectOptions = creaateProjectOptions({ | ||
path: projectPath, | ||
name: ctx.parameters.projectName, | ||
templateDir, | ||
}) | ||
|
||
await executeFlags(ctx.flags, projectOptions) | ||
|
||
const s = spinner() | ||
s.start(`Creating project ${ctx.parameters.projectName}`) | ||
await createProject(projectOptions) | ||
s.stop(`Project created at: ${projectPath}`) | ||
if (ctx.flags.install === undefined) { | ||
ctx.flags.install = await confirm({ | ||
message: 'Install dependencies?', | ||
initialValue: true, | ||
}) | ||
} | ||
|
||
if (ctx.flags.install) { | ||
s.start('Installing dependencies') | ||
await runPmCommand('install', projectPath) | ||
s.stop('Dependencies installed!') | ||
} | ||
const cdProjectPath = relative(cwd, projectPath) | ||
const helpText = [ | ||
'You\'re all set! Now run:', | ||
'', | ||
` cd ${bold(green(cdProjectPath.includes(' ') ? `"${cdProjectPath}"` : cdProjectPath))}`, | ||
ctx.flags.install ? undefined : ` ${bold(green(formatPmCommand(getPmCommand('install'))))}`, | ||
` ${bold(green(formatPmCommand(getPmCommand('dev'))))}`, | ||
'', | ||
' Happy hacking!', | ||
].filter(s => s !== undefined).join('\n') | ||
outro(helpText) | ||
process.exit() // Ugh, why | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { join } from 'node:path' | ||
import { mkdir, writeFile } from 'node:fs/promises' | ||
import { getTemplateDirectory, renderTemplate } from './utils' | ||
|
||
export interface ProjectOptions { | ||
path: string | ||
name: string | ||
templateDir: string | ||
|
||
templates: string[] | ||
} | ||
|
||
export function creaateProjectOptions(params: Pick<ProjectOptions, 'path' | 'name' | 'templateDir'>): ProjectOptions { | ||
return { | ||
...params, | ||
templates: [], | ||
} | ||
} | ||
|
||
export async function createProject(options: ProjectOptions) { | ||
const templateDirectory = (await getTemplateDirectory())! | ||
const withBase = (path: string) => join(templateDirectory, path) | ||
|
||
await mkdir(options.path) | ||
await writeFile(join(options.path, 'package.json'), JSON.stringify({ | ||
name: options.name, | ||
}, null, 2)) | ||
|
||
for (const template of ['common', 'code/base', 'config/ts', ...options.templates]) { | ||
await renderTemplate(withBase(template), options.path) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import type { ProjectOptions } from '@/create' | ||
import { confirm, defineFlagMeta } from '@/utils' | ||
import type { FeatureFlagActionCtx } from '@/utils' | ||
|
||
const metas = { | ||
router: defineFlagMeta({ | ||
name: 'router', | ||
message: 'Use Vue Router?', | ||
flag: { | ||
type: Boolean, | ||
description: 'Add Vue Router', | ||
default: false, | ||
} as const, | ||
initialValue: false, | ||
}), | ||
} | ||
|
||
const flags = Object.entries(metas).reduce((acc, [key, value]) => { | ||
Reflect.set(acc, key, value.flag) | ||
return acc | ||
}, {} as { | ||
[K in keyof typeof metas]: typeof metas[K]['flag'] | ||
}) | ||
|
||
export type ParsedFlags = { | ||
[K in keyof typeof metas]: boolean | ||
} | ||
|
||
export function useFlags() { | ||
return { | ||
flags, | ||
executeFlags: async (flags: ParsedFlags, options: ProjectOptions) => { | ||
const context: FeatureFlagActionCtx = { | ||
template: (...path) => { | ||
options.templates.push(...path) | ||
}, | ||
} | ||
|
||
// Confirm flags, order is sensitive | ||
for (const item of [metas.router.name]) { | ||
if (!flags[item]) { | ||
const { initialValue, message } = metas[item] | ||
flags[item] = await confirm({ | ||
message, | ||
initialValue, | ||
}) | ||
} | ||
} | ||
if (flags.router) { | ||
context.template('code/router', 'config/router') | ||
} | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Clerc, friendlyErrorPlugin, helpPlugin, notFoundPlugin, strictFlagsPlugin } from 'clerc' | ||
|
||
import { description, name, version } from '../package.json' | ||
import { createCommand } from './commands/create' | ||
|
||
Clerc.create(name, version, description) | ||
.use(helpPlugin()) | ||
.use(notFoundPlugin()) | ||
.use(strictFlagsPlugin()) | ||
.use(notFoundPlugin()) | ||
.use(friendlyErrorPlugin()) | ||
.command(createCommand) | ||
.parse() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const defaultBanner = 'Vue Vine - Another style of writing Vue components' | ||
|
||
/** | ||
* generated by the following code: | ||
* | ||
* import gradient from 'gradient-string' | ||
* | ||
* gradient(['#364fae', '#43954b']) | ||
* ('Vue Vine - Another style of writing Vue components') | ||
* | ||
* Use the output directly here to keep the bundle small. | ||
* | ||
* How to generate: | ||
* | ||
* - get graient text | ||
* - text.replace(/\x1B/g, '\\x1B') | ||
* - write the output to a txt file (which will not handle the ansi escapes) and copy the output here | ||
*/ | ||
const gradientBanner | ||
= '\x1B[38;2;54;79;174mV\x1B[39m\x1B[38;2;54;81;172mu\x1B[39m\x1B[38;2;55;82;169me\x1B[39m \x1B[38;2;55;84;167mV\x1B[39m\x1B[38;2;55;86;164mi\x1B[39m\x1B[38;2;56;88;162mn\x1B[39m\x1B[38;2;56;89;160me\x1B[39m \x1B[38;2;56;91;157m-\x1B[39m \x1B[38;2;57;93;155mA\x1B[39m\x1B[38;2;57;94;152mn\x1B[39m\x1B[38;2;57;96;150mo\x1B[39m\x1B[38;2;57;98;147mt\x1B[39m\x1B[38;2;58;99;145mh\x1B[39m\x1B[38;2;58;101;143me\x1B[39m\x1B[38;2;58;103;140mr\x1B[39m \x1B[38;2;59;105;138ms\x1B[39m\x1B[38;2;59;106;135mt\x1B[39m\x1B[38;2;59;108;133my\x1B[39m\x1B[38;2;60;110;131ml\x1B[39m\x1B[38;2;60;111;128me\x1B[39m \x1B[38;2;60;113;126mo\x1B[39m\x1B[38;2;61;115;123mf\x1B[39m \x1B[38;2;61;117;121mw\x1B[39m\x1B[38;2;61;118;118mr\x1B[39m\x1B[38;2;62;120;116mi\x1B[39m\x1B[38;2;62;122;114mt\x1B[39m\x1B[38;2;62;123;111mi\x1B[39m\x1B[38;2;63;125;109mn\x1B[39m\x1B[38;2;63;127;106mg\x1B[39m \x1B[38;2;63;129;104mV\x1B[39m\x1B[38;2;64;130;102mu\x1B[39m\x1B[38;2;64;132;99me\x1B[39m \x1B[38;2;64;134;97mc\x1B[39m\x1B[38;2;64;135;94mo\x1B[39m\x1B[38;2;65;137;92mm\x1B[39m\x1B[38;2;65;139;89mp\x1B[39m\x1B[38;2;65;140;87mo\x1B[39m\x1B[38;2;66;142;85mn\x1B[39m\x1B[38;2;66;144;82me\x1B[39m\x1B[38;2;66;146;80mn\x1B[39m\x1B[38;2;67;147;77mt\x1B[39m\x1B[38;2;67;149;75ms\x1B[39m' | ||
|
||
export { defaultBanner, gradientBanner } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import process from 'node:process' | ||
import { cancel as _cancel, confirm as _confirm, text as _text, isCancel } from '@clack/prompts' | ||
|
||
export function cancel(...args: Parameters<typeof _cancel>): never { | ||
_cancel(...args) | ||
process.exit(0) | ||
} | ||
|
||
function wrapClack<T extends (...args: any[]) => any>(fn: T) { | ||
return async (...args: Parameters<T>) => { | ||
const result = await fn(...args) | ||
if (isCancel(result)) { | ||
cancel('Operation cancelled. Goodbye!') | ||
process.exit(0) | ||
} | ||
return result as Exclude<Awaited<ReturnType<T>>, symbol> | ||
} | ||
} | ||
|
||
export const text = wrapClack(_text) | ||
export const confirm = wrapClack(_confirm) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const isObject = (val: unknown) => val && typeof val === 'object' | ||
const mergeArrayWithDedupe = (a: unknown[], b: unknown[]) => Array.from(new Set([...a, ...b])) | ||
|
||
/** | ||
* Recursively merge the content of the new object to the existing one | ||
* @param target the existing object | ||
* @param obj the new object | ||
*/ | ||
export function deepMerge(target: Record<string, any>, obj: Record<string, any>) { | ||
for (const key of Object.keys(obj)) { | ||
const oldVal = target[key] | ||
const newVal = obj[key] | ||
|
||
if (Array.isArray(oldVal) && Array.isArray(newVal)) { | ||
target[key] = mergeArrayWithDedupe(oldVal, newVal) | ||
} | ||
else if (isObject(oldVal) && isObject(newVal)) { | ||
target[key] = deepMerge(oldVal, newVal) | ||
} | ||
else { | ||
target[key] = newVal | ||
} | ||
} | ||
|
||
return target | ||
} | ||
|
||
export default deepMerge |
Oops, something went wrong.