UNPKG

7.49 kBJavaScriptView Raw
1const resolveFrom = require('resolve-from')
2const loadConfig = require('./loadConfig')
3const BaseGeneratorContext = require('./GeneratorContext')
4const parseGenerator = require('./parseGenerator')
5const updateCheck = require('./updateCheck')
6const PluginAPI = require('./PluginAPI')
7const paths = require('./paths')
8const defaultOptions = require('./defaultOptions')
9const { setGenerator } = require('./cache')
10const {
11 fs,
12 path,
13 chalk,
14 logger,
15 spinner,
16 isLocalPath,
17 downloadGitRepo,
18 installPackages,
19 SherryError,
20 npmClient
21} = require('sherry-utils')
22
23module.exports = class Sherry {
24 /**
25 * Create an instance of Sherry
26 * @param {Object} opts
27 */
28 constructor(opts) {
29 this.logger = logger
30 logger.setOptions(
31 process.argv.includes('--debug')
32 ? 4
33 : 1
34 )
35 if (opts) {
36 this.setOptions(opts)
37 }
38 }
39
40 setCLI(cli) {
41 this.cli = cli
42 this.pluginAPI = new PluginAPI(this)
43 }
44
45 setOptions(opts) {
46 this.opts = Object.assign({}, defaultOptions, opts)
47 logger.setOptions({
48 logLevel:
49 this.opts.debug
50 ? 4
51 : this.opts.quiet
52 ? 1
53 : 3
54 })
55
56 this.parsedGenerator = parseGenerator(this.opts.generator, this.opts)
57 logger.debug('parsedGenerator', this.parsedGenerator)
58 // Sub generator can only be used in an existing
59 if (this.parsedGenerator.subGenerator) {
60 logger.debug(
61 `Setting out directory to process.cwd() since it's a sub generator`
62 )
63 this.opts.outDir = process.cwd()
64 }
65
66 // Respect npmClient from options.
67 npmClient.set(this.opts.npmClient)
68
69 return this
70 }
71
72 /**
73 * Run the generator.
74 */
75 async run(generator, parent) {
76 generator = generator || this.parsedGenerator
77
78 if (generator.type === 'repo') {
79 await ensureRepo(generator, this.opts)
80 } else if (generator.type === 'npm') {
81 await ensurePackage(generator, this.opts)
82 } else if (generator.type === 'local') {
83 await ensureLocal(generator)
84 }
85
86 const loaded = await loadConfig(generator.path)
87 const config = loaded.path ?
88 loaded.data :
89 require(path.join(__dirname, 'sherry-config.fallback.js'))
90
91 // Only run following code for root generator
92 if (!parent) {
93 if (this.opts.updateCheck) {
94 updateCheck({
95 generator,
96 checkGenerator:
97 config.updateCheck !== false && generator.type === 'npm',
98 // Don't show the notifier after updated the generator
99 // Since the notifier is for the older version
100 showNotifier: !this.opts.update
101 })
102 }
103 // Keep the generator info
104 setGenerator(generator)
105 }
106
107 if (generator.subGenerator) {
108 // TODO: remove `config.generators` support, you should use `subGenerators` instead
109 const subGenerators = config.subGenerators || config.generators
110 const subGenerator =
111 subGenerators &&
112 subGenerators.find(g => g.name === generator.subGenerator)
113 if (subGenerator) {
114 // TODO: remove `subGenerator.from` support, you should use `subGenerator.generator` instead
115 let generatorPath = subGenerator.generator || subGenerator.from
116 generatorPath = isLocalPath(generatorPath) ?
117 path.resolve(generator.path, generatorPath) :
118 resolveFrom(generator.path, generatorPath)
119 return this.run(parseGenerator(generatorPath, this.opts), generator)
120 }
121 throw new SherryError(
122 `No such sub generator in generator ${generator.path}`
123 )
124 }
125
126 await this.runGenerator(generator, config)
127 }
128
129 async runGenerator(generator, config) {
130 if (config.description) {
131 logger.status('green', 'Generator', config.description)
132 }
133
134 const GeneratorContext = this.opts.getContext ?
135 this.opts.getContext(BaseGeneratorContext) :
136 BaseGeneratorContext
137 const generatorContext = new GeneratorContext(this, generator)
138 this.generatorContext = generatorContext
139
140 if (typeof config.prepare === 'function') {
141 await config.prepare.call(generatorContext, generatorContext)
142 }
143
144 if (config.prompts) {
145 await require('./runPrompts')(config, generatorContext)
146 }
147
148 generatorContext.data = typeof config.data === 'function' ?
149 config.data.call(generatorContext, generatorContext) :
150 (config.data || {})
151
152 if (config.actions) {
153 await require('./runActions')(config, generatorContext)
154 }
155
156 if (!this.opts.mock && config.completed) {
157 await config.completed.call(generatorContext, generatorContext)
158 }
159 }
160
161 applyPlugins(plugins) {
162 for (const { name, path, options = {} } of plugins) {
163 logger.debug(`Apply plugin: ${name}`)
164 const pluginFn = require(path)
165 pluginFn(this.pluginAPI, options)
166 }
167 }
168}
169
170/**
171 * Ensure packages are installed in a generator
172 * In most cases this is used for `repo` generators
173 * @param {Object} generator
174 * @param {Object} options
175 */
176async function ensureRepo(generator, { update, gitProtocol, registry, gitOrigin }) {
177 if (!update && (await fs.pathExists(generator.path))) {
178 return
179 }
180
181 // Download repo
182 spinner.start('Downloading repo')
183 try {
184 await downloadGitRepo(generator.slug, generator.path, { protocol: gitProtocol, gitOrigin })
185
186 spinner.stop()
187 logger.success('Downloaded repo')
188 } catch (err) {
189 if (err) {
190 const { statusCode } = err
191 if (typeof statusCode === 'number' && statusCode > 500) {
192 console.log()
193 console.log(err)
194 throw new SherryError(`Please check if you have the access to ${chalk.cyan(generator.slug)}`)
195 }
196 }
197
198 let message = err.message
199 if (err.host && err.path) {
200 message += '\n' + err.host + err.path
201 }
202 throw new SherryError(message)
203 }
204 // Only try to install dependencies for real generator
205 const [hasConfig, hasPackageJson] = await Promise.all([
206 loadConfig.hasConfig(generator.path),
207 fs.pathExists(path.join(generator.path, 'package.json'))
208 ])
209
210 if (hasConfig && hasPackageJson) {
211 await installPackages({
212 cwd: generator.path,
213 registry,
214 installArgs: ['--production']
215 })
216 }
217}
218
219async function ensureLocal(generator) {
220 const exists = await fs.pathExists(generator.path)
221
222 if (!exists) {
223 throw new SherryError(
224 `Directory ${chalk.underline(generator.path)} does not exist`
225 )
226 }
227}
228
229async function ensurePackage(generator, { update, registry }) {
230 const installPath = path.join(paths.packagePath, generator.hash)
231
232 if (update || !(await fs.pathExists(generator.path))) {
233 await fs.ensureDir(installPath)
234 await fs.writeFile(
235 path.join(installPath, 'package.json'),
236 JSON.stringify({
237 private: true
238 }),
239 'utf8'
240 )
241 logger.debug('Installing generator at', installPath)
242 await installPackages({
243 cwd: installPath,
244 registry,
245 packages: [`${generator.name}@${generator.version || 'latest'}`]
246 })
247 }
248}
249
250// async function ensureOutDir(outDir, force) {
251// if (force) return
252// if (!(await fs.pathExists(outDir))) return
253
254// const files = await fs.readdir(outDir)
255// if (files.length === 0) return
256
257// const answers = await require('inquirer').prompt([{
258// name: 'force',
259// message: `Directory ${chalk.underline(outDir)} already exists, do you want to continue`,
260// type: 'confirm',
261// default: false
262// }])
263// if (!answers.force) {
264// throw new SherryError(`Aborted`)
265// }
266// }