ringcentral-embeddable-extension-factory
Version:
Cli tool to create a RingCentral Embeddable Voice chrome extension for sites.
244 lines (229 loc) • 6.39 kB
JavaScript
const prompts = require('prompts')
const {echo, rm, mv} = require('shelljs')
const {resolve} = require('path')
const os = require('os')
const {readFileSync, writeFileSync} = require('fs')
const tempDir = os.tmpdir()
const {log} = console
const download = require('download')
const copy = require('json-deep-copy')
const {generate} = require('shortid')
const templates = [
{
zip: 'https://github.com/zxdong262/ringcentral-chrome-extension-template-spa/archive/master.zip',
folderName: 'ringcentral-chrome-extension-template-spa-master',
title: 'Template for single page site',
manifestPath: 'src/manifest.json'
},
{
zip: 'https://github.com/zxdong262/ringcentral-chrome-extension-template-nospa/archive/master.zip',
folderName: 'ringcentral-chrome-extension-template-nospa-master',
title: 'Template for not single page site',
manifestPath: 'src/manifest.json'
}
// {
// zip: 'https://github.com/zxdong262/ringcentral-embeddable-chrome-extension-template-spa/archive/master.zip',
// folderName: 'ringcentral-embeddable-chrome-extension-template-spa-master',
// title: 'Template for single page site with loose files',
// manifestPath: 'dist/manifest.json'
// },
// {
// zip: 'https://github.com/zxdong262/ringcentral-embeddable-chrome-extension-template-nospa/archive/master.zip',
// folderName: 'ringcentral-embeddable-chrome-extension-template-nospa-master',
// title: 'Template for not single page site with loose files',
// manifestPath: 'dist/manifest.json'
// }
]
const choices = templates.map(k => {
return {
title: k.title,
value: k
}
})
const questions = [
{
name: 'name',
type: 'text',
message: 'project name, eg: my great app',
validate: input => {
if (!input) {
return 'project name is required'
} else if (input.length > 50) {
return 'project name max length: 50'
}
return true
}
},
{
name: 'description',
type: 'text',
message: 'project description',
validate: input => {
if (!input) {
return 'project description is required'
} else if (input.length > 300) {
return 'project description max length: 300'
}
return true
}
},
{
name: 'version',
type: 'text',
message: 'project version',
initial: '0.0.1'
},
{
name: 'siteMatch',
type: 'text',
message: 'Extension will work in what url, eg: https://example.com/*, must end with "/*"',
initial: 'https://',
validate: (input = '') => {
if (!input) {
return 'siteMatch is required'
} else if (!input.startsWith('http')) {
return 'site match should start with http'
} else if (!input.endsWith('/*')) {
return 'site url must ends "/*"'
}
return true
}
},
{
name: 'template',
type: 'select',
message: 'Pick a template',
initial: 0,
choices: choices
},
{
type: 'confirm',
name: 'confirm',
message: 'Can you confirm?',
initial: true
}
]
function fetchZip(url, folderPath) {
log('fetching', url, '-->', tempDir)
rm('-rf', folderPath, folderPath + '.zip')
return download(url, tempDir, {
extract: true
})
.then(() => true)
.catch(e => {
throw e
})
}
function verifyResult(res) {
return Object.keys(res).length === questions.length
}
function filterPermissions(permissions) {
return permissions.filter((p) => {
return p !== 'background' && p !== 'tabCapture' && p !== 'system.display'
})
}
async function editFiles(from, res, template) {
// dist/manifest.json
let pkg = resolve(from, template.manifestPath)
let pkgInfo = require(pkg)
let pkgObj = {
name: res.npmName,
version: res.version,
description: res.description,
permissions: [
...pkgInfo.permissions,
res.siteMatch
],
icons: pkgInfo.icons,
background: pkgInfo.background,
content_security_policy: pkgInfo.content_security_policy,
manifest_version: 2
}
const contentScript = {
matches: [
res.siteMatch
],
exclude_matches: [],
js: [
'./content.js'
],
all_frames: true
}
const pageAction = Object.assign(
{},
pkgInfo.page_action,
{
default_title: res.name
}
)
const pkgObjFirefox = copy(pkgObj)
const contentScriptFirefox = copy(contentScript)
delete pkgObjFirefox.background.persistent
delete contentScriptFirefox.exclude_matches
pkgObjFirefox.browser_action = pageAction
pkgObjFirefox.permissions = filterPermissions(pkgObjFirefox.permissions)
pkgObjFirefox.applications = {
gecko: {
id: generate() + '@dev-ringcentral.com',
strict_min_version: '60.0'
}
}
pkgObjFirefox.content_scripts = [contentScriptFirefox]
pkgObj.page_action = pageAction
pkgObj.content_scripts = [contentScript]
writeFileSync(
pkg,
JSON.stringify(pkgObj, null, 2)
)
writeFileSync(
pkg.replace('manifest', 'manifest-firefox'),
JSON.stringify(pkgObjFirefox, null, 2)
)
// package.json
pkg = resolve(from, 'package.json')
pkgInfo = require(pkg)
pkgObj = {
name: res.npmName,
version: res.version,
description: res.description,
keywords: pkgInfo.keywords,
scripts: pkgInfo.scripts,
devDependencies: pkgInfo.devDependencies,
dependencies: pkgInfo.dependencies
}
writeFileSync(
pkg,
JSON.stringify(pkgObj, null, 2)
)
// README
let readme = resolve(from, 'README.md')
let readmeStr = readFileSync(readme).toString()
let arr = readmeStr.split('## Features')
let final = `
# ${res.name}
${res.description}
## Features${arr[1]}
`
writeFileSync(readme, final)
}
module.exports = async function ask({path: targetPath, name}) {
questions[0].initial = name
let res = await prompts(questions)
if (!verifyResult(res)) {
return process.exit(0)
}
delete res.confirm
console.log(res)
res.npmName = res.name.replace(/\s+/g, '-')
echo('building')
let {template} = res
template = typeof template === 'object'
? template
: templates[template]
let {zip, folderName} = template
let from = resolve(tempDir, folderName)
await fetchZip(zip, from)
await editFiles(from, res, template)
mv(from, targetPath)
echo(`Done! Now you can run "cd ${name}" and follow ${name}/README.md's instruction to dev/test the chrome extension!`)
}