UNPKG

9.61 kBJavaScriptView Raw
1const
2 logger = require('../helpers/logger'),
3 log = logger('app:extension'),
4 warn = logger('app:extension', 'red'),
5 appPaths = require('../app-paths'),
6 { spawnSync } = require('../helpers/spawn'),
7 extensionJson = require('./extension-json')
8
9async function renderFolders ({ source, rawCopy, scope }) {
10 const
11 fs = require('fs-extra'),
12 path = require('path'),
13 fglob = require('fast-glob'),
14 isBinary = require('isbinaryfile').isBinaryFileSync,
15 inquirer = require('inquirer'),
16 compileTemplate = require('lodash.template')
17
18 let overwrite
19 const files = fglob.sync(['**/*'], { cwd: source })
20
21 for (const rawPath of files) {
22 const targetRelativePath = rawPath.split('/').map(name => {
23 // dotfiles are ignored when published to npm, therefore in templates
24 // we need to use underscore instead (e.g. "_gitignore")
25 if (name.charAt(0) === '_' && name.charAt(1) !== '_') {
26 return `.${name.slice(1)}`
27 }
28 if (name.charAt(0) === '_' && name.charAt(1) === '_') {
29 return `${name.slice(1)}`
30 }
31 return name
32 }).join('/')
33
34 const targetPath = appPaths.resolve.app(targetRelativePath)
35 const sourcePath = path.resolve(source, rawPath)
36
37 if (overwrite !== 'overwriteAll' && fs.existsSync(targetPath)) {
38 if (overwrite === 'skipAll') {
39 continue
40 }
41 else {
42 const answer = await inquirer.prompt([{
43 name: 'action',
44 type: 'list',
45 message: `Overwrite "${path.relative(appPaths.appDir, targetPath)}"?`,
46 choices: [
47 { name: 'Overwrite', value: 'overwrite' },
48 { name: 'Overwrite all', value: 'overwriteAll' },
49 { name: 'Skip (might break extension)', value: 'skip' },
50 { name: 'Skip all (might break extension)', value: 'skipAll' }
51 ],
52 default: 'overwrite'
53 }])
54
55 if (answer.action === 'overwriteAll') {
56 overwrite = 'overwriteAll'
57 }
58 else if (answer.action === 'skipAll') {
59 overwrite = 'skipAll'
60 continue
61 }
62 else if (answer.action === 'skip') {
63 continue
64 }
65 }
66 }
67
68 fs.ensureFileSync(targetPath)
69
70 if (rawCopy || isBinary(sourcePath)) {
71 fs.copyFileSync(sourcePath, targetPath)
72 }
73 else {
74 const rawContent = fs.readFileSync(sourcePath, 'utf-8')
75 const template = compileTemplate(rawContent, { 'interpolate': /<%=([\s\S]+?)%>/g })
76 fs.writeFileSync(targetPath, template(scope), 'utf-8')
77 }
78 }
79}
80
81module.exports = class Extension {
82 constructor (name) {
83 if (name.charAt(0) === '@') {
84 const slashIndex = name.indexOf('/')
85 if (slashIndex === -1) {
86 warn(`⚠️ Invalid Quasar App Extension name: "${name}"`)
87 process.exit(1)
88 }
89
90 this.packageFullName = name.substring(0, slashIndex + 1) +
91 'quasar-app-extension-' +
92 name.substring(slashIndex + 1)
93
94 this.packageName = '@' + this.__stripVersion(this.packageFullName.substring(1))
95 this.extId = '@' + this.__stripVersion(name.substring(1))
96 }
97 else {
98 this.packageFullName = 'quasar-app-extension-' + name
99 this.packageName = this.__stripVersion('quasar-app-extension-' + name)
100 this.extId = this.__stripVersion(name)
101 }
102 }
103
104 isInstalled () {
105 try {
106 require.resolve(this.packageName, {
107 paths: [ appPaths.appDir ]
108 })
109 }
110 catch (e) {
111 return false
112 }
113
114 return true
115 }
116
117 async install (skipPkgInstall) {
118 if (/quasar-app-extension-/.test(this.extId)) {
119 this.extId = this.extId.replace('quasar-app-extension-', '')
120 log(
121 `When using an extension, "quasar-app-extension-" is added automatically. Just run "quasar ext --add ${
122 this.extId
123 }"`
124 )
125 }
126
127 log(`${skipPkgInstall ? 'Invoking' : 'Installing'} "${this.extId}" Quasar App Extension`)
128 log()
129
130 const isInstalled = this.isInstalled()
131
132 // verify if already installed
133 if (skipPkgInstall === true) {
134 if (!isInstalled) {
135 warn(`⚠️ Tried to invoke App Extension "${this.extId}" but it's npm package is not installed`)
136 process.exit(1)
137 }
138 }
139 else {
140 if (isInstalled) {
141 const inquirer = require('inquirer')
142 const answer = await inquirer.prompt([{
143 name: 'reinstall',
144 type: 'confirm',
145 message: `Already installed. Reinstall?`,
146 default: false
147 }])
148
149 if (!answer.reinstall) {
150 return
151 }
152 }
153 }
154
155 // yarn/npm install
156 skipPkgInstall !== true && this.__installPackage()
157
158 const prompts = await this.__getPrompts()
159
160 extensionJson.set(this.extId, prompts)
161
162 // run extension install
163 const hooks = await this.__runInstallScript(prompts)
164
165 log(`Quasar App Extension "${this.extId}" successfully installed.`)
166 log()
167
168 if (hooks && hooks.exitLog.length > 0) {
169 hooks.exitLog.forEach(msg => {
170 console.log(msg)
171 })
172 console.log()
173 }
174 }
175
176 async uninstall (skipPkgUninstall) {
177 log(`${skipPkgUninstall ? 'Uninvoking' : 'Uninstalling'} "${this.extId}" Quasar App Extension`)
178 log()
179
180 const isInstalled = this.isInstalled()
181
182 // verify if already installed
183 if (skipPkgUninstall === true) {
184 if (!isInstalled) {
185 warn(`⚠️ Tried to uninvoke App Extension "${this.extId}" but there's no npm package installed for it.`)
186 process.exit(1)
187 }
188 }
189 else {
190 if (!isInstalled) {
191 warn(`⚠️ Quasar App Extension "${this.packageName}" is not installed...`)
192 return
193 }
194 }
195
196 const prompts = extensionJson.getPrompts(this.extId)
197 const hooks = await this.__runUninstallScript(prompts)
198
199 extensionJson.remove(this.extId)
200
201 // yarn/npm uninstall
202 skipPkgUninstall !== true && this.__uninstallPackage()
203
204 log(`Quasar App Extension "${this.extId}" successfully removed.`)
205 log()
206
207 if (hooks && hooks.exitLog.length > 0) {
208 hooks.exitLog.forEach(msg => {
209 console.log(msg)
210 })
211 console.log()
212 }
213 }
214
215 async run (ctx) {
216 if (!this.isInstalled()) {
217 warn(`⚠️ Quasar App Extension "${this.extId}" is missing...`)
218 process.exit(1, 'ext-missing')
219 }
220
221 const script = this.__getScript('index', true)
222 const IndexAPI = require('./IndexAPI')
223
224 const api = new IndexAPI({
225 extId: this.extId,
226 prompts: extensionJson.getPrompts(this.extId),
227 ctx
228 })
229
230 log(`Running "${this.extId}" Quasar App Extension...`)
231 await script(api)
232
233 return api.__getHooks()
234 }
235
236 __stripVersion (packageFullName) {
237 const index = packageFullName.indexOf('@')
238
239 return index > -1
240 ? packageFullName.substring(0, index)
241 : packageFullName
242 }
243
244 async __getPrompts () {
245 const questions = this.__getScript('prompts')
246
247 if (!questions) {
248 return {}
249 }
250
251 const inquirer = require('inquirer')
252 const prompts = await inquirer.prompt(questions())
253
254 console.log()
255 return prompts
256 }
257
258 __installPackage () {
259 const
260 nodePackager = require('../helpers/node-packager'),
261 cmdParam = nodePackager === 'npm'
262 ? ['install', '--save-dev']
263 : ['add', '--dev']
264
265 log(`Retrieving "${this.packageFullName}"...`)
266 spawnSync(
267 nodePackager,
268 cmdParam.concat(this.packageFullName),
269 appPaths.appDir,
270 () => warn(`⚠️ Failed to install ${this.packageFullName}`)
271 )
272 }
273
274 __uninstallPackage () {
275 const
276 nodePackager = require('../helpers/node-packager'),
277 cmdParam = nodePackager === 'npm'
278 ? ['uninstall', '--save-dev']
279 : ['remove']
280
281 log(`Uninstalling "${this.packageName}"...`)
282 spawnSync(
283 nodePackager,
284 cmdParam.concat(this.packageName),
285 appPaths.appDir,
286 () => warn(`⚠️ Failed to uninstall "${this.packageName}"`)
287 )
288 }
289
290 __getScript (scriptName, fatal) {
291 let script
292
293 try {
294 script = require.resolve(this.packageName + '/src/' + scriptName, {
295 paths: [ appPaths.appDir ]
296 })
297 }
298 catch (e) {
299 if (fatal) {
300 warn(`⚠️ App Extension "${this.extId}" has missing ${scriptName} script...`)
301 process.exit(1)
302 }
303
304 return
305 }
306
307 return require(script)
308 }
309
310 async __runInstallScript (prompts) {
311 const script = this.__getScript('install')
312
313 if (!script) {
314 return
315 }
316
317 log('Running App Extension install script...')
318
319 const InstallAPI = require('./InstallAPI')
320
321 const api = new InstallAPI({
322 extId: this.extId,
323 prompts
324 })
325
326 await script(api)
327
328 const hooks = api.__getHooks()
329
330 if (hooks.renderFolders.length > 0) {
331 for (let entry of hooks.renderFolders) {
332 await renderFolders(entry)
333 }
334 }
335
336 if (api.__needsNodeModulesUpdate) {
337 const
338 nodePackager = require('../helpers/node-packager'),
339 cmdParam = nodePackager === 'npm'
340 ? ['install']
341 : []
342
343 log(`Updating dependencies...`)
344 spawnSync(
345 nodePackager,
346 cmdParam,
347 appPaths.appDir,
348 () => warn(`⚠️ Failed to update dependencies`)
349 )
350 }
351
352 return hooks
353 }
354
355 async __runUninstallScript (prompts) {
356 const script = this.__getScript('uninstall')
357
358 if (!script) {
359 return
360 }
361
362 log('Running App Extension uninstall script...')
363
364 const UninstallAPI = require('./UninstallAPI')
365 const api = new UninstallAPI({
366 extId: this.extId,
367 prompts
368 })
369
370 await script(api)
371
372 return api.__getHooks()
373 }
374}