UNPKG

6.75 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.update)
57 } else if (generator.type === 'npm') {
58 await ensurePackage(generator, this.opts.update)
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 })
76 }
77 }
78
79 // When updated, the cached answers should be cleared too
80 if (this.opts.update && generator.hash) {
81 store.delete(`answers.${generator.hash}`)
82 }
83
84 if (generator.subGenerator) {
85 const subGenerator =
86 config.generators &&
87 config.generators.find(g => g.name === generator.subGenerator)
88 if (subGenerator) {
89 const generatorPath = isLocalPath(subGenerator.from)
90 ? path.resolve(generator.path, subGenerator.from)
91 : resolveFrom(generator.path, subGenerator.from)
92 return this.run(parseGenerator(generatorPath), generator)
93 }
94 throw new SAOError(`No such sub generator in generator ${generator.path}`)
95 }
96
97 await this.runGenerator(generator, config)
98 }
99
100 async runGenerator(generator, config) {
101 if (config.description) {
102 logger.status('green', 'Generator', config.description)
103 }
104
105 const GeneratorContext = this.opts.getContext
106 ? this.opts.getContext(BaseGeneratorContext)
107 : BaseGeneratorContext
108 const generatorContext = new GeneratorContext(this, generator)
109 this.generatorContext = generatorContext
110
111 if (typeof config.prepare === 'function') {
112 await config.prepare.call(generatorContext, generatorContext)
113 }
114
115 if (config.prompts) {
116 await require('./runPrompts')(config, generatorContext)
117 }
118
119 if (config.actions) {
120 await require('./runActions')(config, generatorContext)
121 }
122
123 if (!this.opts.mock && config.completed) {
124 await config.completed.call(generatorContext, generatorContext)
125 }
126 }
127}
128
129/**
130 * Create an instance of SAO
131 * @param {Object} opts
132 */
133module.exports = opts => new SAO(opts)
134
135module.exports.mock = require('./mock')
136
137module.exports.handleError = require('./handleError')
138
139/**
140 * Download git repo
141 * @param {string} repo
142 * @param {string} target
143 * @param {Object=} opts
144 */
145function downloadRepo(repo, target, opts) {
146 return fs.remove(target).then(
147 () =>
148 new Promise((resolve, reject) => {
149 downloadGitRepo(repo, target, opts, err => {
150 if (err) return reject(err)
151 resolve()
152 })
153 })
154 )
155}
156
157/**
158 * Ensure packages are installed in a generator
159 * In most cases this is used for `repo` generators
160 * @param {Object} generator
161 * @param {boolean=} update
162 */
163async function ensureRepo(generator, update) {
164 if (!update && (await fs.pathExists(generator.path))) {
165 return
166 }
167
168 // Download repo
169 spinner.start('Downloading repo')
170 try {
171 await downloadRepo(generator.slug, generator.path)
172 spinner.stop()
173 logger.success('Downloaded repo')
174 } catch (err) {
175 let message = err.message
176 if (err.host && err.path) {
177 message += '\n' + err.host + err.path
178 }
179 throw new SAOError(message)
180 }
181 // Only try to install dependencies for real generator
182 const [hasConfig, hasPackageJson] = await Promise.all([
183 loadConfig.hasConfig(generator.path),
184 fs.pathExists(path.join(generator.path, 'package.json'))
185 ])
186
187 if (hasConfig && hasPackageJson) {
188 await installPackages({
189 cwd: generator.path,
190 installArgs: ['--production']
191 })
192 }
193}
194
195async function ensureLocal(generator) {
196 const exists = await fs.pathExists(generator.path)
197
198 if (!exists) {
199 throw new SAOError(
200 `Directory ${chalk.underline(generator.path)} does not exist`
201 )
202 }
203}
204
205async function ensurePackage(generator, update) {
206 const installPath = path.join(paths.packagePath, generator.hash)
207
208 if (update || !(await fs.pathExists(generator.path))) {
209 await fs.ensureDir(installPath)
210 await fs.writeFile(
211 path.join(installPath, 'package.json'),
212 JSON.stringify({
213 private: true
214 }),
215 'utf8'
216 )
217 logger.debug('Installing generator at', installPath)
218 await installPackages({
219 cwd: installPath,
220 packages: [`${generator.name}@${generator.version || 'latest'}`]
221 })
222 }
223}
224
225// async function ensureOutDir(outDir, force) {
226// if (force) return
227// if (!(await fs.pathExists(outDir))) return
228
229// const files = await fs.readdir(outDir)
230// if (files.length === 0) return
231
232// const answers = await require('inquirer').prompt([{
233// name: 'force',
234// message: `Directory ${chalk.underline(outDir)} already exists, do you want to continue`,
235// type: 'confirm',
236// default: false
237// }])
238// if (!answers.force) {
239// throw new SAOError(`Aborted`)
240// }
241// }