diff --git a/README.md b/README.md index 4713175..f828159 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,13 @@ So let's implement a very primitive counter as a basic example of how to use thi { "name" : "counter", "version" : "0.1.0", - "main" : "main.js" + "main" : "main.js", + "scripts": { + "run": "quark-prebuilt ./package.json", + }, + "dev-dependencies": { + "quark-prebuilt": "0.0.1" + } } ``` @@ -87,5 +93,11 @@ ApplicationWindow { } ``` +### Running +```bash +npm install +npm run +``` + ## Downloads Prebuilt binaries can be found on the [releases page](https://github.com/freemountain/quark/releases). diff --git a/src/libquark/cpp/environment.cpp b/src/libquark/cpp/environment.cpp index 6a2d3d1..43d2510 100644 --- a/src/libquark/cpp/environment.cpp +++ b/src/libquark/cpp/environment.cpp @@ -1,254 +1,270 @@ #include "environment.h" #include -#include #include -#include -#include -#include -#include +#include +#include #include +#include #include -#include - -Environment::Environment(QStringList args, QObject *parent) : QObject(parent) -{ - this->out = new QTextStream(stdout); - this->parser = new QCommandLineParser(); - this->env = QProcessEnvironment::systemEnvironment(); +#include +#include +#include - parser->setApplicationDescription("Test helper"); - parser->addHelpOption(); - parser->addPositionalArgument("script", "script"); +Environment::Environment(QStringList args, QObject* parent) : QObject(parent) { + this->out = new QTextStream(stdout); + this->parser = new QCommandLineParser(); + this->env = QProcessEnvironment::systemEnvironment(); + this->appArguments = args; + parser->setApplicationDescription("Test helper"); + parser->addHelpOption(); + parser->addPositionalArgument("script", "script"); - parser->process(args); + parser->process(args); } QString Environment::getBundledCommand(QString name) { - QString binPath = QFileInfo( QCoreApplication::applicationFilePath() ).absolutePath(); - QString path = binPath + "/" + name; + QString binPath = + QFileInfo(QCoreApplication::applicationFilePath()).absolutePath(); + QString path = binPath + "/" + name; - #ifdef _WIN32 - path = path + ".exe"; - #endif +#ifdef _WIN32 + path = path + ".exe"; +#endif - QFileInfo info = QFileInfo(path); + QFileInfo info = QFileInfo(path); - if(info.exists() && info.isFile()) return QDir::toNativeSeparators(path); + if (info.exists() && info.isFile()) return QDir::toNativeSeparators(path); - return NULL; + return NULL; } QString Environment::getSystemCommand(QString name) { - #if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) - QString files[] = {"/bin/", "/usr/bin/", "/usr/local/bin/"}; - for( unsigned int i = 0; i < 3; i = i + 1 ) - { - this->printLine("get " + name); - QString current = files[i] + name; - QFileInfo info = QFileInfo(current); - bool isFile = info.exists() && info.isFile(); - if(isFile) return current; - } - #endif - return NULL; +#if defined(__unix__) || defined(__unix) || \ + (defined(__APPLE__) && defined(__MACH__)) + QString files[] = {"/bin/", "/usr/bin/", "/usr/local/bin/"}; + for (unsigned int i = 0; i < 3; i = i + 1) { + this->printLine("get " + name); + QString current = files[i] + name; + QFileInfo info = QFileInfo(current); + bool isFile = info.exists() && info.isFile(); + if (isFile) return current; + } +#endif + return NULL; } QString Environment::getCommand(QString name) { - QString cmd = NULL; + QString cmd = NULL; - QString envCmd = this->env.value("QUARK_CMD_" + name.toUpper(), NULL); - if(envCmd != NULL) cmd = QDir::fromNativeSeparators(envCmd); + QString envCmd = this->env.value("QUARK_CMD_" + name.toUpper(), NULL); + if (envCmd != NULL) cmd = QDir::fromNativeSeparators(envCmd); - QString bundledCmd = this->getBundledCommand(name); - if(cmd == NULL && bundledCmd != NULL) cmd = bundledCmd; + QString bundledCmd = this->getBundledCommand(name); + if (cmd == NULL && bundledCmd != NULL) cmd = bundledCmd; - QString shellCmd = this->getShellCommand(name); - if(cmd == NULL && shellCmd != NULL) cmd = shellCmd; + QString shellCmd = this->getShellCommand(name); + if (cmd == NULL && shellCmd != NULL) cmd = shellCmd; - QString sysCmd = this->getSystemCommand(name); - if(cmd == NULL && sysCmd != NULL) cmd = sysCmd; + QString sysCmd = this->getSystemCommand(name); + if (cmd == NULL && sysCmd != NULL) cmd = sysCmd; - if(cmd == NULL) return NULL; + if (cmd == NULL) return NULL; - QFileInfo info(cmd); - return info.isFile() && info.isExecutable() ? cmd : NULL; + QFileInfo info(cmd); + return info.isFile() && info.isExecutable() ? cmd : NULL; } QString Environment::getShellCommand(QString name) { - #if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) - QProcess proc; - QString cmd = "which " + name; - QString shell = env.value("SHELL", "/bin/bash"); - proc.start(shell, QStringList() << "-c" << cmd); - - if (!proc.waitForStarted()) { - this->printLine("not started"); - return nullptr; - } - - if (!proc.waitForFinished()) { - this->printLine("not finished"); - return nullptr; - } - - QString result = proc.readAll(); // contains \n - int n = result.size() - 1; - - return result.left(n); - #else - return NULL; - #endif +#if defined(__unix__) || defined(__unix) || \ + (defined(__APPLE__) && defined(__MACH__)) + QProcess proc; + QString cmd = "which " + name; + QString shell = env.value("SHELL", "/bin/bash"); + proc.start(shell, QStringList() << "-c" << cmd); + + if (!proc.waitForStarted()) { + this->printLine("not started"); + return nullptr; + } + + if (!proc.waitForFinished()) { + this->printLine("not finished"); + return nullptr; + } + + QString result = proc.readAll(); // contains \n + int n = result.size() - 1; + + return result.left(n); +#else + return NULL; +#endif } QString Environment::getConfigPath() { - return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); } QDir Environment::getDataPath() { - QString base = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - QString dir = QDir(base).filePath("data"); + QString base = + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QString dir = QDir(base).filePath("data"); - return QDir(dir); + return QDir(dir); } QString Environment::getBundledAppPath() { - QString result = NULL; - #ifdef _WIN32 - QString binPath = QFileInfo( QCoreApplication::applicationFilePath() ).absolutePath(); + QString result = NULL; +#ifdef _WIN32 + QString binPath = + QFileInfo(QCoreApplication::applicationFilePath()).absolutePath(); - result = binPath + "/resources/default/package.json"; - #elif __linux__ - QString binPath = QFileInfo( QCoreApplication::applicationFilePath() ).absolutePath(); + result = binPath + "/resources/default/package.json"; +#elif __linux__ + QString binPath = + QFileInfo(QCoreApplication::applicationFilePath()).absolutePath(); - result = binPath + "/resources/default/package.json"; - #elif __APPLE__ - QString binPath = QFileInfo( QCoreApplication::applicationFilePath() ).absolutePath(); + result = binPath + "/resources/default/package.json"; +#elif __APPLE__ + QString binPath = + QFileInfo(QCoreApplication::applicationFilePath()).absolutePath(); - result = binPath + "/../Resources/default/package.json"; - #endif + result = binPath + "/../Resources/default/package.json"; +#endif - return result; + return result; } QString Environment::getScriptPath() { - QStringList args = this->parser->positionalArguments(); - QString envPath = this->env.value("QUARK_SCRIPT", NULL); + QStringList args = this->parser->positionalArguments(); + QString envPath = this->env.value("QUARK_SCRIPT", NULL); - if(args.size() > 0) return args.at(0); + bool splitArgs = + this->appArguments.size() > 3 && this->appArguments.at(2) == "--"; - return envPath; + if (appArguments.size() > 1) return appArguments.at(1); + + return envPath; } QProcessEnvironment Environment::getProcEnv() { - QString nodePath = NULL; - - #ifdef _WIN32 - nodePath = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath() + "/resources/node_path"; - #elif __linux__ - nodePath = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath() + "/resources/node_path"; - //nodePath = QDir( QCoreApplication::applicationFilePath() + "/../../resources/" ).absoluteFilePath("node_path"); - - #elif __APPLE__ - nodePath = QDir( QCoreApplication::applicationFilePath() + "/../../Resources/" ).absoluteFilePath("node_path"); - #endif - - QProcessEnvironment procEnv = QProcessEnvironment(this->env); - - if(nodePath != NULL) - procEnv.insert("NODE_PATH", QDir::toNativeSeparators(nodePath)); - else - this->printLine("could not set NODE_PATH\n"); - - return procEnv; + QString nodePath = NULL; + +#ifdef _WIN32 + nodePath = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath() + + "/resources/node_path"; +#elif __linux__ + nodePath = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath() + + "/resources/node_path"; +// nodePath = QDir( QCoreApplication::applicationFilePath() + +// "/../../resources/" ).absoluteFilePath("node_path"); + +#elif __APPLE__ + nodePath = QDir(QCoreApplication::applicationFilePath() + "/../../Resources/") + .absoluteFilePath("node_path"); +#endif + + QProcessEnvironment procEnv = QProcessEnvironment(this->env); + + if (nodePath != NULL) + procEnv.insert("NODE_PATH", QDir::toNativeSeparators(nodePath)); + else + this->printLine("could not set NODE_PATH\n"); + + return procEnv; } QuarkProcess* Environment::startProcess() { - return this->startProcess(this->getScriptPath()); + return this->startProcess(this->getScriptPath()); } QuarkProcess* Environment::startProcess(QString path) { - Either, QJsonParseError> mayJson = this->loadJson(path); + Either, QJsonParseError> mayJson = + this->loadJson(path); - if(mayJson.is2nd()) { - this->printLine("Could not parse: " + path + - "error: " + mayJson.as2nd().errorString()); - return nullptr; - } + if (mayJson.is2nd()) { + this->printLine("Could not parse: " + path + "error: " + + mayJson.as2nd().errorString()); + return nullptr; + } - QMap json = mayJson.as1st(); + QMap json = mayJson.as1st(); - QString main = json.value("main"); - QString name = json.value("name"); + QString main = json.value("main"); + QString name = json.value("name"); - QStringList arguments = QStringList() << main - << "--dataPath" << this->getDataPath().filePath(name) - << "--configPath" << this->getConfigPath() - << "--shellPath" << QCoreApplication::applicationFilePath(); + QStringList arguments = + QStringList() << main << "--dataPath" + << this->getDataPath().filePath(name) << "--configPath" + << this->getConfigPath() << "--shellPath" + << QCoreApplication::applicationFilePath(); - QuarkProcess* proc = new QuarkProcess(this->getProcEnv(), this, this); + QuarkProcess* proc = new QuarkProcess(this->getProcEnv(), this, this); - if(json.contains("initialQml")) proc->handleLoadQml(json.value("initialQml")); + if (json.contains("initialQml")) + proc->handleLoadQml(json.value("initialQml")); - connect(proc, &QuarkProcess::startProcess, [this](const QString &path) { - this->startProcess(path); - }); + connect(proc, &QuarkProcess::startProcess, + [this](const QString& path) { this->startProcess(path); }); - proc->start(this->getCommand("node"), arguments); + proc->start(this->getCommand("node"), arguments); - return proc; + return proc; } -Either, QJsonParseError> Environment::loadJson(QString path) { - QString data; - QString main; - QString initialQml; - QJsonObject json; - QFile file(path); - QJsonParseError err; - QMap result; - QDir baseDir = QDir(QFileInfo(path).path()); - file.open(QIODevice::ReadOnly | QIODevice::Text); - data = file.readAll(); - json = QJsonDocument::fromJson(data.toUtf8(), &err).object(); - - if(err.error != QJsonParseError::NoError) { - return some(err); - } - - main = json.value("main").toString("main.js"); - initialQml = json.value("initialQml").toString(""); - - if(initialQml != "") { - result.insert("initialQml", QDir::toNativeSeparators(baseDir.absolutePath() + "/" + initialQml)); - } - - result.insert("main", QDir::toNativeSeparators(baseDir.absolutePath() + "/" + main)); - result.insert("name", json.value("name").toString(hashPath(path))); - - return some(result); +Either, QJsonParseError> Environment::loadJson( + QString path) { + QString data; + QString main; + QString initialQml; + QJsonObject json; + QFile file(path); + QJsonParseError err; + QMap result; + QDir baseDir = QDir(QFileInfo(path).path()); + file.open(QIODevice::ReadOnly | QIODevice::Text); + data = file.readAll(); + json = QJsonDocument::fromJson(data.toUtf8(), &err).object(); + + if (err.error != QJsonParseError::NoError) { + return some(err); + } + + main = json.value("main").toString("main.js"); + initialQml = json.value("initialQml").toString(""); + + if (initialQml != "") { + result.insert("initialQml", QDir::toNativeSeparators( + baseDir.absolutePath() + "/" + initialQml)); + } + + result.insert("main", + QDir::toNativeSeparators(baseDir.absolutePath() + "/" + main)); + result.insert("name", json.value("name").toString(hashPath(path))); + + return some(result); } QString Environment::hashPath(QString path) { - const char * s = path.toStdString().c_str(); - uint32_t hash = 0; + const char* s = path.toStdString().c_str(); + uint32_t hash = 0; - for(; *s; ++s) - { - hash += *s; - hash += (hash << 10); - hash ^= (hash >> 6); - } + for (; *s; ++s) { + hash += *s; + hash += (hash << 10); + hash ^= (hash >> 6); + } - hash += (hash << 3); - hash ^= (hash >> 11); - hash += (hash << 15); + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); - return QString::number(hash); + return QString::number(hash); } void Environment::printLine(QString msg) { - this->out->operator <<(msg); - this->out->operator <<("\n"); - this->out->flush(); + this->out->operator<<(msg); + this->out->operator<<("\n"); + this->out->flush(); } diff --git a/src/libquark/cpp/environment.h b/src/libquark/cpp/environment.h index 1ee907a..ff8a1cd 100644 --- a/src/libquark/cpp/environment.h +++ b/src/libquark/cpp/environment.h @@ -1,54 +1,51 @@ #ifndef ENVIRONMENT_H #define ENVIRONMENT_H +#include +#include +#include +#include #include #include +#include #include #include -#include -#include -#include -#include -#include #include -#include "quarkprocess.h" #include "either.h" #include "logger.h" +#include "quarkprocess.h" Q_DECLARE_METATYPE(QJsonParseError) - -class Environment : public QObject, Logger -{ - Q_OBJECT -public: - explicit Environment(QStringList = QStringList(), QObject *parent = 0); - - QString getCommand(QString name); - QString getShellCommand(QString name); - QString getBundledCommand(QString name); - QString getSystemCommand(QString name); - - QString getScriptPath(); - QDir getDataPath(); - QString getConfigPath(); - QProcessEnvironment getProcEnv(); - QString getBundledAppPath(); - - QuarkProcess* startProcess(QString path); - QuarkProcess* startProcess(); - - void printLine(QString msg); - - -private: - QTextStream* out; - QCommandLineParser* parser; - QProcessEnvironment env; - static QString hashPath(QString path); - static Either, QJsonParseError> loadJson(QString path); - +class Environment : public QObject, Logger { + Q_OBJECT + public: + explicit Environment(QStringList = QStringList(), QObject* parent = 0); + + QString getCommand(QString name); + QString getShellCommand(QString name); + QString getBundledCommand(QString name); + QString getSystemCommand(QString name); + + QString getScriptPath(); + QDir getDataPath(); + QString getConfigPath(); + QProcessEnvironment getProcEnv(); + QString getBundledAppPath(); + + QuarkProcess* startProcess(QString path); + QuarkProcess* startProcess(); + + void printLine(QString msg); + + private: + QTextStream* out; + QCommandLineParser* parser; + QProcessEnvironment env; + QStringList appArguments; + static QString hashPath(QString path); + static Either, QJsonParseError> loadJson(QString path); }; -#endif // ENVIRONMENT_H +#endif // ENVIRONMENT_H diff --git a/src/quark-downloader/.eslintrc b/src/quark-downloader/.eslintrc new file mode 100644 index 0000000..b0c0c8b --- /dev/null +++ b/src/quark-downloader/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "airbnb" +} diff --git a/src/quark-downloader/README.md b/src/quark-downloader/README.md new file mode 100644 index 0000000..f49ad9a --- /dev/null +++ b/src/quark-downloader/README.md @@ -0,0 +1,5 @@ +# quark-downloader + +Download [Quark](https://github.com/freemountain/quark) prebuilt binaries without having to compile anything. + +Used by [quark-downloader](https://www.npmjs.com/package/quark-prebuilt). diff --git a/src/quark-downloader/cli.js b/src/quark-downloader/cli.js new file mode 100755 index 0000000..8b1f26c --- /dev/null +++ b/src/quark-downloader/cli.js @@ -0,0 +1,40 @@ +#! /usr/bin/env node + +const os = require('os'); + +const commandLineArgs = require('command-line-args'); +const downloader = require('./index.js'); + +const optionDefinitions = [ + { name: 'latest', type: Boolean }, + { name: 'list', type: Boolean }, + { name: 'version', alias: 'v', type: String }, + { name: 'target', alias: 't', type: String, multiple: true, defaultValue: [] }, + { name: 'output', alias: 'o', type: String, defaultValue: './' }, +]; + +const options = commandLineArgs(optionDefinitions); +const print = msg => console.log(msg); + +const catchError = (e) => { + console.error(e.message); + process.exit(1); +}; + + +if (options.list) { + downloader.list().then(print).catch(catchError); +} + +if (options.latest) { + downloader.latest().then(print).catch(catchError); +} + +if (!options.list && !options.latest) { + if (options.target.length === 0) options.target.push(downloader.currentTag()); + if (!options.version) throw new Error('version is required'); + + downloader.download(options.version, options.target, options.output) + .then(print) + .catch(catchError); +} diff --git a/src/quark-downloader/index.js b/src/quark-downloader/index.js new file mode 100644 index 0000000..5218da3 --- /dev/null +++ b/src/quark-downloader/index.js @@ -0,0 +1,211 @@ +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const parseUrl = require('url').parse; +const fetch = require('node-fetch'); +const moment = require('moment'); +const unzip = require('unzipper'); +const tmp = require('tmp'); +const chmod = require('chmod'); + +const log = (msg, silent) => (x) => { + if (!silent) console.log(msg); + return x; +}; + +const ls = dir => new Promise((resolve, reject) => { + fs.readdir(dir, (err, files) => { + if (err) return reject(err); + + return resolve(files); + }); +}); + +const chmodBundle = (bundle) => { + const names = ['QuarkShell', 'QuarkShell.exe', 'node', 'node.exe', 'AppRun']; + const filterFiles = files => files.filter(name => names.includes(name)); + const permission = { read: true, execute: true }; + const permissions = { owner: permission, group: permission, others: permission }; + + const searchPath = path.basename(bundle).endsWith('.app') + ? path.join(bundle, 'Contents', 'MacOS') + : bundle; + + const mapFiles = files => files.map(file => path.join(searchPath, file)); + + return ls(searchPath) + .then(filterFiles) + .then(mapFiles) + .then(files => files.map(file => chmod(file, permissions))) + .then(() => bundle); +}; + +const chmodRelease = dir => ls(dir).then((p) => { + const name = p.includes('QuarkShell.app') ? 'QuarkShell.app' : 'QuarkShell'; + + return chmodBundle(path.join(dir, name)); +}); + + +const tmpFileCreator = mayFile => new Promise((resolve, reject) => { + if (mayFile) return resolve(mayFile); + + return tmp.file({ unsafeCleanup: true }, (err, p) => { + if (err) return reject(err); + return resolve(p); + }); +}); + +const extractZip = (file, destination) => new Promise((resolve, reject) => { + const archive = fs.createReadStream(file); + const extract = archive.pipe(unzip.Extract({ path: destination })); + + archive.on('error', err => reject(err)); + extract.on('error', err => reject(err)); + extract.on('close', () => resolve(destination)); +}); + +const httpError = (response) => { + const error = new Error(`ResponseError ${response.status} (url: ${response.url})`); + + return error; +}; + +const saveResponse = dest => response => new Promise((resolve, reject) => { + if (!response.ok) { return reject(httpError(response)); } + + const file = fs.createWriteStream(dest); + file.on('finish', () => resolve(dest)); + file.on('error', err => reject(err)); + + return response.body.pipe(file); +}); + +const sortRelease = (a, b) => a.published.unix() < b.published.unix(); +const filterRelease = ({ prerelease, draft }) => !draft && !prerelease; + +const mapResponse = (response) => { + if (!response.ok) throw httpError(response); + + return response.json(); +}; + +const mapAssets = assets => assets + .filter(({ name }) => name.startsWith('quark-shell-') && name.endsWith('.zip')) + .map(asset => ({ + id: asset.id, + name: asset.name, + label: asset.label, + contentType: asset.content_type, + created: moment(asset.created_at), + published: moment(asset.published_at), + downloadUrl: asset.browser_download_url, + })); + +const mapRelease = json => ({ + id: json.id, + tag: json.tag_name, + name: json.name, + desc: json.body, + created: moment(json.created_at), + published: moment(json.published_at), + assets: mapAssets(json.assets), +}); + +const getReleases = (user, repo) => fetch(`https://api.github.com/repos/${user}/${repo}/releases`) + .then(mapResponse) + .then(json => json + .filter(filterRelease) + .map(mapRelease) + .sort(sortRelease)); + + +const getRelease = (user, repo, id) => fetch(`https://api.github.com/repos/${user}/${repo}/releases/${id}`) + .then(mapResponse) + .then(mapRelease); + +const getLatestRelease = (user, repo) => getRelease(user, repo, 'latest'); + +const getReleaseByTag = (user, repo, tag) => fetch(`https://api.github.com/repos/${user}/${repo}/releases/tags/${tag}`) + .then(mapResponse) + .then(mapRelease); + +const urlToDebugName = u => { + const urlPathStr = parseUrl(u).path; + const urlPath = path.parse(urlPathStr); + const fileName = urlPath.base; + const pathTokens = urlPath.dir.split('/'); + const version = pathTokens[pathTokens.length -1]; + + return `${version}/${fileName}`; +} + +const downloadAndExtract = (url, destination, mayTmp, silent = false) => tmpFileCreator(mayTmp) + .then(downloadDest => fetch(url) + .then(log(`downloading ${urlToDebugName(url)}`, silent)) + .then(saveResponse(downloadDest)) + .then(() => downloadDest) + .then(log(`extracting ${urlToDebugName(url)}`, silent))) + .then(archive => extractZip(archive, destination)); + + +const filterAssets = target => ({ assets }) => { + const single = t => ({ name }) => name.startsWith(`quark-shell-${t}`) && name.endsWith('.zip'); + const multi = name => target.map(single).some(c => c(name)); + const filter = typeof target === 'string' ? single(target) : multi; + + return assets.filter(filter); +}; +const assetNameToTarget = name => name.slice('quark-shell'.length + 1, -'.zip'.length); + +const download = (version, targets, destination) => getReleaseByTag('freemountain', 'quark', version) + .then(filterAssets(targets)) + .then((assets) => { + const excpected = [].concat(targets).length; + if (assets.length !== excpected) throw new Error(`Excpected ${excpected} assets (${targets}). Found ${assets.length} assets (${assets})`); + + return assets; + }) + .then(assets => Promise.all(assets.map(({ name, downloadUrl }) => { + const dest = typeof targets === 'string' ? destination : path.join(destination, assetNameToTarget(name)); + + return downloadAndExtract(downloadUrl, dest).then(chmodRelease); + }))); + +const createVersionInfo = release => ({ + version: release.tag, + targets: release.assets.map(asset => assetNameToTarget(asset.name)), +}); + +const list = () => getReleases('freemountain', 'quark') + .then(releases => releases.map(createVersionInfo).filter(({ targets }) => targets.length > 0)); + +const latest = () => getLatestRelease('freemountain', 'quark').then(createVersionInfo); + +const mapOs = (osName) => { + switch (osName) { + case 'Linux': return 'linux'; + case 'Darwin': return 'mac'; + case 'Windows_NT': return 'windows'; + default: throw new Error(`${os} is not supported`); + } +}; + +const mapArch = (arch) => { + switch (arch) { + case 'x64': return 'x86_64'; + case 'x86': return 'x86'; + case 'x32': return 'x86'; + default: throw new Error(`${arch} is not supported`); + } +}; + +module.exports = { + currentTag: () => `${mapOs(os.type())}-${mapArch(os.arch())}`, + download, + list, + latest, +}; + +// download('v20170402', ['linux-x86_64', 'mac-x86_64'], './multi').then(w => console.log('multi', w)); +// download('v20170402', 'win-x86', './single').then(w => console.log('single', w)); diff --git a/src/quark-downloader/package.json b/src/quark-downloader/package.json new file mode 100644 index 0000000..3175402 --- /dev/null +++ b/src/quark-downloader/package.json @@ -0,0 +1,33 @@ +{ + "name": "quark-downloader", + "version": "0.0.1", + "description": "", + "main": "index.js", + "bin": { + "quark-downloader": "./cli.js" + }, + "scripts": { + "test": "eslint *.js --fix" + }, + "repository" : { + "type" : "git", + "url" : "https://github.com/freemountain/quark.git" + }, + "author": "", + "license": "ISC", + "dependencies": { + "chmod": "^0.2.1", + "command-line-args": "^4.0.1", + "moment": "^2.17.1", + "node-fetch": "^1.6.3", + "tmp": "0.0.31", + "unzipper": "^0.8.3" + }, + "devDependencies": { + "eslint": "^3.15.0", + "eslint-config-airbnb": "^14.1.0", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-jsx-a11y": "^4.0.0", + "eslint-plugin-react": "^6.9.0" + } +} diff --git a/src/quark-gui/main.cpp b/src/quark-gui/main.cpp index cd1f6c3..09ceebd 100644 --- a/src/quark-gui/main.cpp +++ b/src/quark-gui/main.cpp @@ -1,30 +1,30 @@ -#include #include -#include #include +#include #include +#include -#include "quarkprocess.h" #include "environment.h" +#include "quarkprocess.h" -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); +int main(int argc, char* argv[]) { + QGuiApplication app(argc, argv); - Environment* env = new Environment(app.arguments()); - QuarkProcess* proc; + Environment* env = new Environment(app.arguments()); + QuarkProcess* proc; - env->printLine("node:" + env->getCommand("node")); - env->printLine("NODE_PATH" + env->getProcEnv().value("NODE_PATH")); - env->printLine("script:" + env->getScriptPath()); - env->printLine("data:" + env->getDataPath().path()); - env->printLine("bundled app:" + env->getBundledAppPath()); + env->printLine("node:" + env->getCommand("node")); + env->printLine("NODE_PATH" + env->getProcEnv().value("NODE_PATH")); + env->printLine("script:" + env->getScriptPath()); + env->printLine("data:" + env->getDataPath().path()); + env->printLine("bundled app:" + env->getBundledAppPath()); - if(env->getScriptPath() == "") { - proc = env->startProcess(env->getBundledAppPath()); - } else { - proc = env->startProcess(env->getScriptPath()); - } + qDebug() << app.arguments(); + if (env->getScriptPath() == "") { + proc = env->startProcess(env->getBundledAppPath()); + } else { + proc = env->startProcess(env->getScriptPath()); + } - return app.exec(); + return app.exec(); } diff --git a/src/quark-prebuilt/.eslintrc b/src/quark-prebuilt/.eslintrc new file mode 100644 index 0000000..b0c0c8b --- /dev/null +++ b/src/quark-prebuilt/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "airbnb" +} diff --git a/src/quark-prebuilt/.npmignore b/src/quark-prebuilt/.npmignore new file mode 100644 index 0000000..a65b417 --- /dev/null +++ b/src/quark-prebuilt/.npmignore @@ -0,0 +1 @@ +lib diff --git a/src/quark-prebuilt/README.md b/src/quark-prebuilt/README.md new file mode 100644 index 0000000..b8efc18 --- /dev/null +++ b/src/quark-prebuilt/README.md @@ -0,0 +1,21 @@ +# quark-prebuilt + +Install [Quark](https://github.com/freemountain/quark) prebuilt binaries for +command-line use using npm without having to compile anything. + +## Installation + +Download and install the latest build of Quark for your OS and add it to your +project's `package.json` as a `devDependency`: + +```shell +npm install quark-prebuilt --save-dev +``` + +Then add th following to your package.json: + +``` +"scripts": { + "run": "quark-prebuilt ./package.json", +} +``` diff --git a/src/quark-prebuilt/cli.js b/src/quark-prebuilt/cli.js new file mode 100755 index 0000000..0b9fe5b --- /dev/null +++ b/src/quark-prebuilt/cli.js @@ -0,0 +1,19 @@ +#! /usr/bin/env node + +const start = require('./index.js'); + +if (process.argv.length < 3) throw new Error('need at least one argument, package.json'); + +const pkgJson = process.argv[2]; +let appArgs = []; + +if (process.argv.length >= 4) appArgs = process.argv.slice(4).join(' '); + +const child = start(pkgJson, appArgs); + +child.stdout.pipe(process.stdout); +child.stderr.pipe(process.stderr); + +child.on('close', (code) => { + process.exit(code); +}); diff --git a/src/quark-prebuilt/index.js b/src/quark-prebuilt/index.js new file mode 100644 index 0000000..936c0d1 --- /dev/null +++ b/src/quark-prebuilt/index.js @@ -0,0 +1,26 @@ +const os = require('os'); +const path = require('path'); + +const spawn = require('child_process').spawn; + +const downloader = require('quark-downloader'); + +const getBin = (osName = os.type()) => { + switch (osName) { + case 'Linux': return 'QuarkShell/QuarkShell'; + case 'Darwin': return 'QuarkShell.app/Contents/MacOS/QuarkShell'; + case 'Windows_NT': return 'QuarkShell\\QuarkShell.exe'; + default: throw new Error(`${os} is not supported`); + } +}; + +const start = (pkgJson, appArguments = []) => { + const currentTag = downloader.currentTag(); + const quarkBin = path.join(__dirname, 'lib', currentTag, getBin()); + + const args = [pkgJson, '--'].concat(appArguments); + + return spawn(quarkBin, args); +}; + +module.exports = start; diff --git a/src/quark-prebuilt/package.json b/src/quark-prebuilt/package.json new file mode 100644 index 0000000..89c9013 --- /dev/null +++ b/src/quark-prebuilt/package.json @@ -0,0 +1,29 @@ +{ + "name": "quark-prebuilt", + "version": "0.0.1", + "description": "", + "main": "index.js", + "bin": { + "quark-downloader": "./cli.js" + }, + "scripts": { + "test": "eslint *.js --fix", + "postinstall": "quark-downloader -o ./lib -v v0.0.1" + }, + "repository" : { + "type" : "git", + "url" : "https://github.com/freemountain/quark.git" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "eslint": "^3.15.0", + "eslint-config-airbnb": "^14.1.0", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-jsx-a11y": "^4.0.0", + "eslint-plugin-react": "^6.9.0" + }, + "dependencies": { + "quark-downloader": "0.0.1" + } +}