UNPKG

@quasar/app

Version:

Quasar Framework local CLI

393 lines (315 loc) 10.2 kB
const fs = require('fs-extra') const path = require('path') const { log, warn, fatal } = require('../helpers/logger') const appPaths = require('../app-paths') const { spawnSync } = require('../helpers/spawn') const extensionJson = require('./extension-json') async function promptOverwrite ({ targetPath, options }) { const inquirer = require('inquirer') const choices = [ { name: 'Overwrite', value: 'overwrite' }, { name: 'Overwrite all', value: 'overwriteAll' }, { name: 'Skip (might break extension)', value: 'skip' }, { name: 'Skip all (might break extension)', value: 'skipAll' } ] const answer = await inquirer.prompt([{ name: 'action', type: 'list', message: `Overwrite "${path.relative(appPaths.appDir, targetPath)}"?`, choices: options !== void 0 ? choices.filter(choice => options.includes(choice.value)) : choices, default: 'overwrite' }]) return answer } async function renderFile ({ sourcePath, targetPath, rawCopy, scope, overwritePrompt }) { const isBinary = require('isbinaryfile').isBinaryFileSync const compileTemplate = require('lodash.template') if (overwritePrompt === true && fs.existsSync(targetPath)) { const answer = await promptOverwrite({ targetPath, options: [ 'overwrite', 'skip' ] }) if (answer.action === 'skip') { return } } fs.ensureFileSync(targetPath) if (rawCopy || isBinary(sourcePath)) { fs.copyFileSync(sourcePath, targetPath) } else { const rawContent = fs.readFileSync(sourcePath, 'utf-8') const template = compileTemplate(rawContent, { 'interpolate': /<%=([\s\S]+?)%>/g }) fs.writeFileSync(targetPath, template(scope), 'utf-8') } } async function renderFolders ({ source, rawCopy, scope }) { const fglob = require('fast-glob') let overwrite const files = fglob.sync(['**/*'], { cwd: source }) for (const rawPath of files) { const targetRelativePath = rawPath.split('/').map(name => { // dotfiles are ignored when published to npm, therefore in templates // we need to use underscore instead (e.g. "_gitignore") if (name.charAt(0) === '_' && name.charAt(1) !== '_') { return `.${name.slice(1)}` } if (name.charAt(0) === '_' && name.charAt(1) === '_') { return `${name.slice(1)}` } return name }).join('/') const targetPath = appPaths.resolve.app(targetRelativePath) const sourcePath = path.resolve(source, rawPath) if (overwrite !== 'overwriteAll' && fs.existsSync(targetPath)) { if (overwrite === 'skipAll') { continue } else { const answer = await promptOverwrite({ targetPath }) if (answer.action === 'overwriteAll') { overwrite = 'overwriteAll' } else if (answer.action === 'skipAll') { overwrite = 'skipAll' continue } else if (answer.action === 'skip') { continue } } } renderFile({ sourcePath, targetPath, rawCopy, scope }) } } module.exports = class Extension { constructor (name) { if (name.charAt(0) === '@') { const slashIndex = name.indexOf('/') if (slashIndex === -1) { fatal(`Invalid Quasar App Extension name: "${name}"`) } this.packageFullName = name.substring(0, slashIndex + 1) + 'quasar-app-extension-' + name.substring(slashIndex + 1) this.packageName = '@' + this.__stripVersion(this.packageFullName.substring(1)) this.extId = '@' + this.__stripVersion(name.substring(1)) } else { this.packageFullName = 'quasar-app-extension-' + name this.packageName = this.__stripVersion('quasar-app-extension-' + name) this.extId = this.__stripVersion(name) } } isInstalled () { try { require.resolve(this.packageName + '/src/index', { paths: [ appPaths.appDir ] }) } catch (e) { return false } return true } async install (skipPkgInstall) { if (/quasar-app-extension-/.test(this.extId)) { this.extId = this.extId.replace('quasar-app-extension-', '') log( `When using an extension, "quasar-app-extension-" is added automatically. Just run "quasar ext add ${ this.extId }"` ) } log(`${skipPkgInstall ? 'Invoking' : 'Installing'} "${this.extId}" Quasar App Extension`) log() const isInstalled = this.isInstalled() // verify if already installed if (skipPkgInstall === true) { if (!isInstalled) { fatal(`Tried to invoke App Extension "${this.extId}" but its npm package is not installed`) } } else if (isInstalled) { const inquirer = require('inquirer') const answer = await inquirer.prompt([{ name: 'reinstall', type: 'confirm', message: `Already installed. Reinstall?`, default: false }]) if (!answer.reinstall) { return } } // yarn/npm install skipPkgInstall !== true && this.__installPackage() const prompts = await this.__getPrompts() extensionJson.set(this.extId, prompts) // run extension install const hooks = await this.__runInstallScript(prompts) log(`Quasar App Extension "${this.extId}" successfully installed.`) log() if (hooks && hooks.exitLog.length > 0) { hooks.exitLog.forEach(msg => { console.log(msg) }) console.log() } } async uninstall (skipPkgUninstall) { log(`${skipPkgUninstall ? 'Uninvoking' : 'Uninstalling'} "${this.extId}" Quasar App Extension`) log() const isInstalled = this.isInstalled() // verify if already installed if (skipPkgUninstall === true) { if (!isInstalled) { fatal(`Tried to uninvoke App Extension "${this.extId}" but there's no npm package installed for it.`) } } else if (!isInstalled) { warn(`Quasar App Extension "${this.packageName}" is not installed...`) return } const prompts = extensionJson.getPrompts(this.extId) const hooks = await this.__runUninstallScript(prompts) extensionJson.remove(this.extId) // yarn/npm uninstall skipPkgUninstall !== true && this.__uninstallPackage() log(`Quasar App Extension "${this.extId}" successfully removed.`) log() if (hooks && hooks.exitLog.length > 0) { hooks.exitLog.forEach(msg => { console.log(msg) }) console.log() } } async run (ctx) { if (!this.isInstalled()) { warn(`Quasar App Extension "${this.extId}" is missing...`) process.exit(1, 'ext-missing') } const script = this.__getScript('index', true) const IndexAPI = require('./IndexAPI') const api = new IndexAPI({ extId: this.extId, prompts: extensionJson.getPrompts(this.extId), ctx }) log(`Running "${this.extId}" Quasar App Extension...`) await script(api) return api.__getHooks() } __stripVersion (packageFullName) { const index = packageFullName.indexOf('@') return index > -1 ? packageFullName.substring(0, index) : packageFullName } async __getPrompts () { const questions = this.__getScript('prompts') if (!questions) { return {} } const inquirer = require('inquirer') const prompts = await inquirer.prompt(questions()) console.log() return prompts } __installPackage () { const nodePackager = require('../helpers/node-packager') const cmdParam = nodePackager === 'npm' ? ['install', '--save-dev'] : ['add', '--dev'] log(`Retrieving "${this.packageFullName}"...`) spawnSync( nodePackager, cmdParam.concat(this.packageFullName), { cwd: appPaths.appDir, env: { ...process.env, NODE_ENV: 'development' } }, () => fatal(`Failed to install ${this.packageFullName}`, 'FAIL') ) } __uninstallPackage () { const nodePackager = require('../helpers/node-packager') const cmdParam = nodePackager === 'npm' ? ['uninstall', '--save-dev'] : ['remove'] log(`Uninstalling "${this.packageName}"...`) spawnSync( nodePackager, cmdParam.concat(this.packageName), { cwd: appPaths.appDir, env: { ...process.env, NODE_ENV: 'development' } }, () => warn(`Failed to uninstall "${this.packageName}"`) ) } __getScript (scriptName, fatalError) { let script try { script = require.resolve(this.packageName + '/src/' + scriptName, { paths: [ appPaths.appDir ] }) } catch (e) { if (fatalError) { fatal(`App Extension "${this.extId}" has missing ${scriptName} script...`) } return } return require(script) } async __runInstallScript (prompts) { const script = this.__getScript('install') if (!script) { return } log('Running App Extension install script...') const InstallAPI = require('./InstallAPI') const api = new InstallAPI({ extId: this.extId, prompts }) await script(api) const hooks = api.__getHooks() if (hooks.renderFolders.length > 0) { for (let entry of hooks.renderFolders) { await renderFolders(entry) } } if (hooks.renderFiles.length > 0) { for (let entry of hooks.renderFiles) { await renderFile(entry) } } if (api.__needsNodeModulesUpdate) { const nodePackager = require('../helpers/node-packager') const cmdParam = nodePackager === 'npm' ? ['install'] : [] log(`Updating dependencies...`) spawnSync( nodePackager, cmdParam, { cwd: appPaths.appDir, env: { ...process.env, NODE_ENV: 'development' } }, () => warn(`Failed to update dependencies`) ) } return hooks } async __runUninstallScript (prompts) { const script = this.__getScript('uninstall') if (!script) { return } log('Running App Extension uninstall script...') const UninstallAPI = require('./UninstallAPI') const api = new UninstallAPI({ extId: this.extId, prompts }) await script(api) return api.__getHooks() } }