Skip to content

Commit 2e0d86d

Browse files
author
Guillaume Chau
committed
feat: project create wizard
1 parent eef64a5 commit 2e0d86d

File tree

2 files changed

+297
-3
lines changed

2 files changed

+297
-3
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
2+
const { defaults } = require('@vue/cli-global-utils/lib/options')
3+
const { resolvePreset } = require('@vue/cli-global-utils/lib/util/resolvePreset')
4+
const { getPresetFromAnswers } = require('@vue/cli-global-utils/lib/util/getPresetFromAnswers')
5+
const { execa } = require('@vue/cli-shared-utils')
6+
7+
exports.createProject = async function ({
8+
cwd,
9+
answers,
10+
setProgress,
11+
addLog
12+
}, promptCompleteCbs) {
13+
// Config files
14+
let index
15+
if ((index = answers.features.indexOf('use-config-files')) !== -1) {
16+
answers.features.splice(index, 1)
17+
answers.useConfigFiles = 'files'
18+
}
19+
20+
// Preset
21+
if (answers.presetName) {
22+
answers.save = true
23+
answers.saveName = answers.presetName
24+
}
25+
26+
setProgress({
27+
info: 'Resolving preset...'
28+
})
29+
let preset
30+
if (answers.preset === '__remote__' && answers.remotePreset) {
31+
// vue create foo --preset bar
32+
preset = await resolvePreset(answers.remotePreset.url, answers.remotePreset.clone)
33+
} else if (answers.preset === 'default') {
34+
// vue create foo --default
35+
preset = defaults.presets.default
36+
} else {
37+
preset = await getPresetFromAnswers(answers, promptCompleteCbs)
38+
}
39+
setProgress({
40+
info: null
41+
})
42+
43+
// Create
44+
const args = [
45+
'--skipGetStarted'
46+
]
47+
if (answers.packageManager) args.push('--packageManager', answers.packageManager)
48+
if (answers.bare) args.push('--bare')
49+
if (answers.force) args.push('--force')
50+
// Git
51+
if (!answers.useGit) {
52+
args.push('--no-git')
53+
} else if (answers.commitMessage) {
54+
args.push('--git', answers.commitMessage)
55+
}
56+
// Preset
57+
args.push('--inlinePreset', JSON.stringify(preset))
58+
59+
const cmd = execa('vue', [
60+
'create',
61+
answers.projectName,
62+
...args
63+
], {
64+
cwd,
65+
stdio: ['inherit', 'pipe', 'inherit']
66+
})
67+
68+
const onData = buffer => {
69+
const text = buffer.toString().trim()
70+
if (text) {
71+
setProgress({
72+
info: text
73+
})
74+
addLog({
75+
type: 'info',
76+
message: text
77+
})
78+
}
79+
}
80+
81+
cmd.stdout.on('data', onData)
82+
83+
await cmd
84+
}
Lines changed: 213 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,218 @@
1+
const { getPromptModules } = require('@vue/cli-global-utils/lib/util/createTools')
2+
const PromptModuleAPI = require('@vue/cli-global-utils/lib/PromptModuleAPI')
3+
const { getPresets } = require('@vue/cli-global-utils/lib/util/getPresets')
4+
const { getFeatures } = require('@vue/cli-global-utils/lib/util/features')
5+
const { toShortPluginId } = require('@vue/cli-shared-utils')
6+
const { createProject } = require('./create-project')
7+
18
module.exports = api => {
2-
api.addProjectType('vue', config => {
3-
config.logo = '/_plugin/@vue%2Fcli-guijs-plugin/vue-project.png'
9+
api.addProjectType('vue', 'Vue CLI', projectType => {
10+
projectType.logo = '/_plugin/@vue%2Fcli-guijs-plugin/vue-project.png'
411

512
// Detect Vue CLI project
6-
config.filterProject(({ pkg }) => ({ ...pkg.dependencies, ...pkg.devDependencies })['@vue/cli-service'])
13+
projectType.filterProject(({ pkg }) => ({ ...pkg.dependencies, ...pkg.devDependencies })['@vue/cli-service'])
14+
15+
// Project creation
16+
projectType.onCreate(onCreate)
17+
})
18+
}
19+
20+
function onCreate ({ wizard }) {
21+
const promptModules = getPromptModules()
22+
const promptAPI = new PromptModuleAPI()
23+
promptModules.forEach(m => m(promptAPI))
24+
const promptCompleteCbs = promptAPI.promptCompleteCbs
25+
26+
wizard.extendGeneralStep({
27+
prompts: [
28+
{
29+
name: 'force',
30+
type: 'confirm',
31+
message: 'Overwrite target directory if it exists'
32+
},
33+
{
34+
name: 'bare',
35+
type: 'confirm',
36+
message: 'Scaffold project without beginner instructions'
37+
},
38+
{
39+
name: 'packageManager',
40+
type: 'list',
41+
message: 'Package manager',
42+
group: 'Dependencies',
43+
description: 'Use specified npm client when installing dependencies',
44+
default: null,
45+
choices: [
46+
{
47+
name: 'Use previous',
48+
value: null
49+
},
50+
{
51+
name: 'Npm',
52+
value: 'npm'
53+
},
54+
{
55+
name: 'Yarn',
56+
value: 'yarn'
57+
},
58+
{
59+
name: 'Pnpm',
60+
value: 'pnpm'
61+
}
62+
],
63+
skin: 'buttongroup'
64+
},
65+
{
66+
name: 'registryUrl',
67+
type: 'input',
68+
message: 'Registry URL',
69+
group: 'Dependencies',
70+
description: 'Use specified npm registry when installing dependencies'
71+
},
72+
{
73+
name: 'useGit',
74+
type: 'confirm',
75+
message: 'Initialize git repository (recommended)',
76+
group: 'Git',
77+
default: true
78+
},
79+
{
80+
name: 'commitMessage',
81+
type: 'input',
82+
message: 'First commit message',
83+
group: 'Git',
84+
when: answers => answers.useGit
85+
}
86+
]
87+
})
88+
89+
// Presets
90+
const presetsData = getPresets()
91+
wizard.addSelectStep('preset', 'org.vue.views.project-create.tabs.presets.title', {
92+
icon: 'check_circle',
93+
description: 'org.vue.views.project-create.tabs.presets.description',
94+
message: 'org.vue.views.project-create.tabs.presets.message',
95+
choices: [
96+
...Object.keys(presetsData).map(key => {
97+
const preset = presetsData[key]
98+
const features = getFeatures(preset).map(
99+
f => toShortPluginId(f)
100+
)
101+
const info = {
102+
name: key === 'default' ? 'org.vue.views.project-create.tabs.presets.default-preset' : key,
103+
value: key,
104+
features,
105+
raw: preset
106+
}
107+
info.description = generatePresetDescription(info)
108+
return info
109+
}),
110+
{
111+
name: 'org.vue.views.project-create.tabs.presets.manual.name',
112+
value: '__manual__',
113+
description: 'org.vue.views.project-create.tabs.presets.manual.description',
114+
features: promptAPI.features.filter(
115+
f => f.checked
116+
).map(
117+
f => f.value
118+
)
119+
},
120+
{
121+
name: 'org.vue.views.project-create.tabs.presets.remote.name',
122+
value: '__remote__',
123+
description: 'org.vue.views.project-create.tabs.presets.remote.description',
124+
features: []
125+
}
126+
]
7127
})
128+
129+
const isManualPreset = answers => answers.preset && answers.preset === '__manual__'
130+
131+
// Features
132+
wizard.addStep('features', 'org.vue.views.project-create.tabs.features.title', {
133+
icon: 'device_hub',
134+
description: 'org.vue.views.project-create.tabs.features.description',
135+
prompts: promptAPI.features.map(
136+
data => ({
137+
name: `featureMap.${data.value}`,
138+
type: 'confirm',
139+
message: data.name,
140+
description: data.description,
141+
link: data.link,
142+
default: !!data.checked
143+
})
144+
)
145+
}, isManualPreset)
146+
147+
// Additional configuration
148+
wizard.addStep('config', 'org.vue.views.project-create.tabs.configuration.title', {
149+
icon: 'settings_applications',
150+
prompts: [
151+
{
152+
name: 'useConfigFiles',
153+
type: 'confirm',
154+
message: 'org.vue.views.project-create.tabs.features.userConfigFiles.name',
155+
description: 'org.vue.views.project-create.tabs.features.userConfigFiles.description'
156+
},
157+
...promptAPI.injectedPrompts.map(prompt => ({
158+
...prompt,
159+
when: withAnswers(prompt.when, () => ({
160+
features: getFeatureList(wizard.answers.featureMap)
161+
})),
162+
choices: withAnswers(prompt.choices, () => ({
163+
features: getFeatureList(wizard.answers.featureMap)
164+
})),
165+
default: withAnswers(prompt.default, () => ({
166+
features: getFeatureList(wizard.answers.featureMap)
167+
}))
168+
}))
169+
]
170+
}, isManualPreset)
171+
172+
// Save preset modal
173+
wizard.addModalStep('savePreset', 'org.vue.views.project-create.tabs.configuration.modal.title', {
174+
prompts: [
175+
{
176+
name: 'presetName',
177+
type: 'input',
178+
message: 'org.vue.views.project-create.tabs.configuration.modal.body.title',
179+
description: 'org.vue.views.project-create.tabs.configuration.modal.body.subtitle',
180+
validate: value => !!value
181+
}
182+
],
183+
canSkip: true
184+
}, isManualPreset)
185+
186+
// Submit
187+
wizard.onSubmit(options => createProject({
188+
...options,
189+
answers: {
190+
...options.answers,
191+
features: getFeatureList(options.answers.featureMap)
192+
}
193+
}, promptCompleteCbs))
194+
}
195+
196+
function generatePresetDescription (presetChoice) {
197+
let description = presetChoice.features.join(', ')
198+
if (presetChoice.raw.useConfigFiles) {
199+
description += ` (Use config files)`
200+
}
201+
return description
202+
}
203+
204+
function getFeatureList (answers) {
205+
if (answers == null) return []
206+
return Object.keys(answers).filter(key => answers[key])
207+
}
208+
209+
function withAnswers (option, overrides) {
210+
if (typeof option === 'function') {
211+
return (answers) => option({
212+
...answers,
213+
...overrides()
214+
})
215+
} else {
216+
return option
217+
}
8218
}

0 commit comments

Comments
 (0)