UNPKG

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