UNPKG

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