keeweb/plugins/kw-plugin-control.js

285 lines
8.2 KiB
JavaScript
Raw Normal View History

2017-02-18 23:46:59 +01:00
/**
* KeeWeb plugin creator
* (C) Antelle 2017, MIT license https://github.com/keeweb/keeweb
*/
/* eslint-disable no-console */
const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
const readline = require('readline');
const crypto = require('crypto');
const args = process.argv.splice(2);
const op = args.shift();
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
showBanner();
switch (op) {
case 'create':
createPlugin();
break;
case 'sign':
signPlugin();
break;
default:
showHelp();
}
function showBanner() {
console.log('KeeWeb plugin control');
}
function showHelp() {
rl.close();
console.log('Usage:');
console.log(' - node kw-plugin-control.js create');
console.log(' - node kw-plugin-control.js sign <plugin_name>');
}
function createPlugin() {
gatherPluginData(data => {
console.log('Creating plugin:', data.name);
createPluginFolder(data);
generateKeyPair(data);
createManifest(data);
createResources(data);
console.log('Plugin created, now it\'s time to make something awesome!');
});
}
function gatherPluginData(callback) {
const data = {
version: '0.0.1',
manifestVersion: '0.1.0'
};
const questions = [{
text: 'Plugin name',
callback: name => {
if (!name) {
return true;
}
if (fs.existsSync(name)) {
console.log(`Folder '${name}' already exists, please select another name`);
return true;
}
data.name = name;
next();
}
}, {
text: 'Description',
callback: description => {
if (!description) {
return true;
}
data.description = description;
next();
}
}, {
text: 'Author name',
callback: authorName => {
if (!authorName) {
return true;
}
data.author = { name: authorName };
next();
}
}, {
text: 'Author email',
callback: authorEmail => {
if (!authorEmail || authorEmail.indexOf('@') < 0 || authorEmail.indexOf('.') < 0) {
return true;
}
data.author.email = authorEmail;
next();
}
}, {
text: 'Author url',
callback: authorUrl => {
if (!authorUrl || authorUrl.indexOf('http://') < 0 && authorUrl.indexOf('https://') < 0) {
return true;
}
data.author.url = authorUrl;
next();
}
}, {
text: 'License (default: MIT)',
callback: licence => {
data.licence = licence || 'MIT';
next();
}
}, {
text: 'Plugin page URL',
callback: pluginUrl => {
if (!pluginUrl || pluginUrl.indexOf('http://') < 0 && pluginUrl.indexOf('https://') < 0) {
return true;
}
data.url = pluginUrl;
next();
}
}, {
text: 'There are different types of KeeWeb plugins:\n ' +
'1: js only\n ' +
'2: js + css\n ' +
'3: css only\n ' +
'4: locale\n' +
'Your plugin type is',
callback: res => {
const placeholder = `<resource signature will be here, please run 'kw-plugin-control sign ${data.name}'>`;
switch (res) {
case '1':
data.resources = { js: placeholder };
break;
case '2':
data.resources = { js: placeholder, css: placeholder };
break;
case '3':
data.resources = { css: placeholder };
break;
case '4':
data.resources = { loc: placeholder };
break;
default:
console.error('Please enter number from 1 to 4');
return true;
}
next();
}
}, {
text: 'Locale code (format: xx or xx-XX)',
if: () => data.resources.loc,
callback: loc => {
if (!/^[a-z]{2}(-[A-Z]{2})?$/.test(loc) || loc === 'en') {
console.error('Invalid locale');
return true;
}
data.locale = { name: loc };
next();
}
}, {
text: 'Locale name (human-readable)',
if: () => data.resources.loc,
callback: loc => {
if (!loc || loc.toLowerCase() === 'english' || loc.toLowerCase() === 'default') {
console.error('Invalid locale');
return true;
}
data.locale.title = loc;
next();
}
}];
next();
function next() {
const question = questions.shift();
if (!question) {
callback(data);
rl.close();
} else {
if (question.if && !question.if()) {
return next();
}
ask(question.text, question.callback);
}
}
}
function ask(question, callback) {
rl.question(`${question}: `, answer => {
const res = callback(answer.trim());
if (res) {
ask(res.question || question, callback);
}
});
}
function createPluginFolder(data) {
console.log('Creating plugin folder...');
fs.mkdirSync(data.name);
}
function generateKeyPair(data) {
console.log('Generating key pair...');
const privateKeyPath = path.join(data.name, 'private_key.pem');
const publicKeyPath = path.join(data.name, 'public_key.pem');
childProcess.execSync(`openssl genpkey -algorithm RSA -out "${privateKeyPath}" -pkeyopt rsa_keygen_bits:2048`);
childProcess.execSync(`openssl rsa -pubout -in "${privateKeyPath}" -out "${publicKeyPath}"`);
data.publicKey = fs.readFileSync(path.join(data.name, 'public_key.pem'), 'utf8')
.match(/-+BEGIN PUBLIC KEY-+([\s\S]+?)-+END PUBLIC KEY-+/)[1]
.replace(/\n/g, '');
fs.unlinkSync(publicKeyPath);
}
function createManifest(data) {
console.log('Creating manifest...');
fs.writeFileSync(path.join(data.name, 'manifest.json'), JSON.stringify(data, null, 2));
}
function createResources(data) {
console.log('Adding default files...');
if (data.resources.js) {
fs.writeFileSync(path.join(data.name, 'plugin.js'), `/**
* KeeWeb plugin: ${data.name}
* @author ${data.author.name}
* @license ${data.licence}
*/
2017-02-19 10:29:18 +01:00
module.exports.uninstall = function() {
};
2017-02-18 23:46:59 +01:00
`);
}
if (data.resources.css) {
fs.writeFileSync(path.join(data.name, 'plugin.css'), '');
}
if (data.resources.loc) {
fs.writeFileSync(path.join(data.name, data.locale.name + '.json'), '{\n}');
}
fs.writeFileSync(path.join(data.name, '.gitignore'), ['.DS_Store', '*.log', '*.pem'].join('\n'));
}
function signPlugin() {
rl.close();
const packageName = args.shift();
if (!packageName) {
showHelp();
return;
}
if (!fs.existsSync(packageName)) {
console.error('Package folder does not exist');
return process.exit(1);
}
const manifest = JSON.parse(fs.readFileSync(path.join(packageName, 'manifest.json')));
const privateKey = fs.readFileSync(path.join(packageName, 'private_key.pem'), 'binary');
for (const res of Object.keys(manifest.resources)) {
console.log(`Signing ${res}...`);
let fileName;
switch (res) {
case 'js':
fileName = 'plugin.js';
break;
case 'css':
fileName = 'plugin.css';
break;
case 'loc':
fileName = manifest.locale.name + '.json';
break;
}
fileName = path.join(packageName, fileName);
const sign = crypto.createSign('RSA-SHA256');
sign.write(fs.readFileSync(fileName));
sign.end();
manifest.resources[res] = sign.sign(privateKey).toString('base64');
}
fs.writeFileSync(path.join(packageName, 'manifest.json'), JSON.stringify(manifest, null, 2));
console.log('Done, package manifest updated');
}