UNPKG

7.52 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 (this.opts.answers) {
120 try {
121 generatorContext._answers =
122 typeof this.opts.answers === 'string'
123 ? JSON.parse(this.opts.answers)
124 : this.opts.answers
125 } catch (err) {
126 throw new Error(`Answers JSON could not be parsed (${err.message})`)
127 }
128 } else if (config.prompts) {
129 await require('./runPrompts')(config, generatorContext)
130 }
131
132 if (config.actions) {
133 await require('./runActions')(config, generatorContext)
134 }
135
136 if (!this.opts.mock && config.completed) {
137 await config.completed.call(generatorContext, generatorContext)
138 }
139 }
140}
141
142/**
143 * Create an instance of SAO
144 * @param {Object} opts
145 */
146module.exports = opts => new SAO(opts)
147
148module.exports.mock = require('./mock')
149
150module.exports.handleError = require('./handleError')
151
152/**
153 * Download git repo
154 * @param {string} repo
155 * @param {string} target
156 * @param {Object=} opts
157 */
158function downloadRepo(repo, target, opts) {
159 return fs.remove(target).then(
160 () =>
161 new Promise((resolve, reject) => {
162 downloadGitRepo(repo, target, opts, err => {
163 if (err) return reject(err)
164 resolve()
165 })
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, clone, registry }) {
177 if (!update && (await fs.pathExists(generator.path))) {
178 return
179 }
180
181 // Download repo
182 spinner.start('Downloading repo')
183 try {
184 await downloadRepo(generator.slug, generator.path, { clone })
185 spinner.stop()
186 logger.success('Downloaded repo')
187 } catch (err) {
188 let message = err.message
189 if (err.host && err.path) {
190 message += '\n' + err.host + err.path
191 }
192 throw new SAOError(message)
193 }
194 // Only try to install dependencies for real generator
195 const [hasConfig, hasPackageJson] = await Promise.all([
196 loadConfig.hasConfig(generator.path),
197 fs.pathExists(path.join(generator.path, 'package.json'))
198 ])
199
200 if (hasConfig && hasPackageJson) {
201 await installPackages({
202 cwd: generator.path,
203 registry,
204 installArgs: ['--production']
205 })
206 }
207}
208
209async function ensureLocal(generator) {
210 const exists = await fs.pathExists(generator.path)
211
212 if (!exists) {
213 throw new SAOError(
214 `Directory ${chalk.underline(generator.path)} does not exist`
215 )
216 }
217}
218
219async function ensurePackage(generator, { update, registry }) {
220 const installPath = path.join(paths.packagePath, generator.hash)
221
222 if (update || !(await fs.pathExists(generator.path))) {
223 await fs.ensureDir(installPath)
224 await fs.writeFile(
225 path.join(installPath, 'package.json'),
226 JSON.stringify({
227 private: true
228 }),
229 'utf8'
230 )
231 logger.debug('Installing generator at', installPath)
232 await installPackages({
233 cwd: installPath,
234 registry,
235 packages: [`${generator.name}@${generator.version || 'latest'}`]
236 })
237 }
238}
239
240// async function ensureOutDir(outDir, force) {
241// if (force) return
242// if (!(await fs.pathExists(outDir))) return
243
244// const files = await fs.readdir(outDir)
245// if (files.length === 0) return
246
247// const answers = await require('inquirer').prompt([{
248// name: 'force',
249// message: `Directory ${chalk.underline(outDir)} already exists, do you want to continue`,
250// type: 'confirm',
251// default: false
252// }])
253// if (!answers.force) {
254// throw new SAOError(`Aborted`)
255// }
256// }