diff --git a/.jshintrc b/.jshintrc index 8ab3485..eceeaf9 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,3 +1,3 @@ { - "esversion": 6 + "esversion": 8 } \ No newline at end of file diff --git a/analytics.js b/analytics.js index f159fed..39e3f04 100644 --- a/analytics.js +++ b/analytics.js @@ -8,10 +8,10 @@ const https = require('https'); const sendEvent = async (event) => { if (event && event.length && event.length > 0) { try { - const req = https.get(`https://curl.press/api/librarian/add?event=${event}`).on('error', function(err) {}) + const req = https.get(`https://curl.press/api/librarian/add?event=${event}`).on('error', function(err) {}); } catch (error) {} } -} +}; const LibrarianEvents = { SetupStarted: "setup.start", @@ -20,6 +20,6 @@ const LibrarianEvents = { ServerStarted: "server.start", ServerError: "server.error", BuildSubmitted: "build.submit" -} +}; module.exports = { sendEvent, LibrarianEvents }; \ No newline at end of file diff --git a/librarian.js b/librarian.js index ace8fe2..1b4f0ca 100755 --- a/librarian.js +++ b/librarian.js @@ -1,13 +1,15 @@ #!/usr/bin/env node -/*jshint esversion: 6 */ +/*jshint esversion: 8 */ const program = require('commander'); const ngrok = require('ngrok'); const chalk = require('chalk'); +const path = require('path'); const preferences = require('node-persist'); const os = require('os'); const fs = require('fs-extra'); const plist = require('plist'); const qrcode = require('qrcode-terminal'); +const AWS = require('aws-sdk'); const log = console.log; const home = os.homedir(); const updateNotifier = require('update-notifier'); @@ -16,409 +18,560 @@ const gitP = require('simple-git/promise'); const git = gitP(); const { Extract } = require('app-metadata'); const { spawn } = require('child_process'); +const { isEmpty } = require('lodash'); const { sendEvent, LibrarianEvents } = require('./analytics.js'); const { beginSetup, isSetup, shouldOverwriteConfiguration, purgeExistingInstallation, configurationKey } = require('./setup.js'); const { setWebConfiguration, addBuild } = require('./webBridge.js'); const storageOptions = { - dir: `${home}/librarian/configuration`, - stringify: JSON.stringify, - parse: JSON.parse, - encoding: 'utf8', - forgiveParseErrors: true + dir: `${home}/librarian/configuration`, + stringify: JSON.stringify, + parse: JSON.parse, + encoding: 'utf8', + forgiveParseErrors: true }; const JEYLL_FRONT_MATTER_CHARACTER = "---\n---\n\n"; const noUpdateConfiguration = { - "update": { - "available": false, - "notes": "" - } + "update": { + "available": false, + "notes": "" + } }; +// AWS.config.update({region: 'REGION'}); -program - .version(pkg.version) - .description('Librarian is a local server for your iOS & Android builds, cause local is best!') program - .command('setup') - .alias('s') - .description('Setup Librarian to Run on your machine') - .action(async () => { - printHeader('Welcome to Librarian!'); - await preferences.init(storageOptions); + .version(pkg.version) + .description('Librarian is a local server for your iOS & Android builds, cause local is best!'); - if (await isSetup(preferences)) { - if (await shouldOverwriteConfiguration()) { - await purgeExistingInstallation(preferences); +program + .command('setup') + .alias('s') + .description('Setup Librarian to Run on your machine') + .action(async () => { + printHeader('Welcome to Librarian!'); await preferences.init(storageOptions); - await beginSetup(preferences); - } - } else { - await beginSetup(preferences); - } - await checkForUpdate(preferences); - }); + if (await isSetup(preferences)) { + if (await shouldOverwriteConfiguration()) { + await purgeExistingInstallation(preferences); + await preferences.init(storageOptions); + await beginSetup(preferences); + } + } else { + await beginSetup(preferences); + } + + await checkForUpdate(preferences); + }); program - .command('start') - .alias('st') - .description('Start the Librarian Server') - .action(async () => { - await preferences.init(storageOptions); + .command('start') + .alias('st') + .description('Start the Librarian Server') + .action(async () => { + await preferences.init(storageOptions); - if (!await isSetup(preferences)) { - fatalError('Librarian has not been setup yet! Run ' + chalk.yellow('librarian setup') + ' to begin') - } + if (!await isSetup(preferences)) { + fatalError('Librarian has not been setup yet! Run ' + chalk.yellow('librarian setup') + ' to begin'); + } - sendEvent(LibrarianEvents.ServerStarted); + sendEvent(LibrarianEvents.ServerStarted); + + printHeader('Starting Librarian...'); + + const prefs = await preferences.getItem(configurationKey); + const webPath = prefs.working_directory + 'web'; + const webPort = prefs.jekyll_port; + const webCommand = `JEKYLL_ENV=production bundle exec jekyll serve --port ${webPort}`; + + // Start the Jekyll Web Server + const web = spawn(webCommand, { + shell: true, + cwd: webPath + }); + + web.stdout.on('data', (data) => { + if (String(data).indexOf('Server address:') > -1) { + log('Jekyll Server Started'); + } + if (String(data).toLowerCase().indexOf('error') > -1) { + log(String(data)); + } + }); + + web.stderr.on('data', (data) => { + log('Error:'); + log(String(data)); + }); + + web.on('exit', function (code, signal) { + if(code !== 0) { sendEvent(LibrarianEvents.ServerError); } + if(code === 1) { fatalError("Do you have another instance of Librarian running?"); } + fatalError('The Jekyll Server has quit unexpectedly. Librarian is now exiting.'); + }); + + if (prefs.assets_web) { + const assetsPath = prefs.working_directory + 'asset_server'; + const assetsPort = prefs.assets_port; + const webCommand = `JEKYLL_ENV=production bundle exec jekyll serve --port ${assetsPort}`; + + const asset_server = spawn(webCommand, { + shell: true, + cwd: assetsPath + }); + + asset_server.stdout.on('data', (data) => { + if (String(data).indexOf('Server address:') > -1) { + log('Assets Server Started'); + } + if (String(data).toLowerCase().indexOf('error') > -1) { + log(String(data)); + } + }); + + asset_server.stderr.on('data', (data) => { + log('Error:'); + log(String(data)); + }); + + asset_server.on('exit', function (code, signal) { + if(code === 1) { fatalError("Do you have another instance of Librarian running?"); } + if(code !== 0) { sendEvent(LibrarianEvents.ServerError); } + fatalError('The Assets Server has quit unexpectedly. Librarian is now exiting.'); + }); + } - printHeader('Starting Librarian...'); + // Start the ngrok tunnel to the webserver + let tunnelURL; - const prefs = await preferences.getItem(configurationKey); - const webPath = prefs.working_directory + 'web'; - const webPort = prefs.jekyll_port; - const webCommand = `JEKYLL_ENV=production bundle exec jekyll serve --port ${webPort}`; - - // Start the Jekyll Web Server - const web = spawn(webCommand, { - shell: true, - cwd: webPath - }); + try { + const port = prefs.assets_web ? prefs.assets_port : prefs.jekyll_port; + let options = { addr: port, region: 'ap' }; - web.stdout.on('data', (data) => { - if (String(data).indexOf('Server address:') > -1) { - log('Jekyll Server Started'); - } - if (String(data).toLowerCase().indexOf('error') > -1) { - log(String(data)); - } - }); + if (prefs.ngrok_token && prefs.ngrok_token !== "") { + options.authtoken = prefs.ngrok_token; + } - web.stderr.on('data', (data) => { - log('Error:'); - log(String(data)); - }); - - web.on('exit', function (code, signal) { - if(code != 0) { sendEvent(LibrarianEvents.ServerError); } - if(code == 1) { fatalError("Do you have another instance of Librarian running?") } - fatalError('The Jekyll Server has quit unexpectedly. Librarian is now exiting.'); - }); + if (prefs.private_web) { + options.auth = `${prefs.web_username}:${prefs.web_password}`; + } - if (prefs.assets_web) { - const assetsPath = prefs.working_directory + 'asset_server'; - const assetsPort = prefs.assets_port; - const webCommand = `JEKYLL_ENV=production bundle exec jekyll serve --port ${assetsPort}`; + tunnelURL = await ngrok.connect(options); - const asset_server = spawn(webCommand, { - shell: true, - cwd: assetsPath - }); + } catch (error) { + sendEvent(LibrarianEvents.ServerError); + log(JSON.stringify(error)); + fatalError("\nFailed to start the ngrok tunnel.\nPlease make sure your ngRok token is valid."); + } - asset_server.stdout.on('data', (data) => { - if (String(data).indexOf('Server address:') > -1) { - log('Assets Server Started'); + if (tunnelURL === undefined || tunnelURL === '') { + fatalError('Failed to start the ngrok tunnel.'); } - if (String(data).toLowerCase().indexOf('error') > -1) { - log(String(data)); + + prefs.currentURL = tunnelURL; + + const currentIP = os.networkInterfaces().en0.find(elm => elm.family === 'IPv4').address; + // const currentIP = "127.0.0.1";// os.networkInterfaces().en0.find(elm => elm.family === 'IPv4').address; + if (currentIP !== prefs.local_ip) { + prefs.local_ip = 'http://' + currentIP + ':' + prefs.jekyll_port; } - }); - - asset_server.stderr.on('data', (data) => { - log('Error:'); - log(String(data)); - }); - - asset_server.on('exit', function (code, signal) { - if(code == 1) { fatalError("Do you have another instance of Librarian running?") } - if(code != 0) { sendEvent(LibrarianEvents.ServerError); } - fatalError('The Assets Server has quit unexpectedly. Librarian is now exiting.'); - }); - } - // Start the ngrok tunnel to the webserver - let tunnelURL; + await preferences.setItem(configurationKey, prefs); - try { - const port = prefs.assets_web ? prefs.assets_port : prefs.jekyll_port; - let options = { addr: port, region: 'ap' }; + let webConfiguration = { + "webBaseURL": prefs.currentURL, + "localBaseURL": prefs.local_ip + }; + await setWebConfiguration(preferences, webConfiguration); - if (prefs.ngrok_token && prefs.ngrok_token !== "") { - options.authtoken = prefs.ngrok_token; - } + const webURL = prefs.assets_web ? prefs.local_ip : tunnelURL; - if (prefs.private_web) { - options.auth = `${prefs.web_username}:${prefs.web_password}` - } + log('\nLibrarian is up at: '); + log(chalk.yellow.bold(webURL)); - tunnelURL = await ngrok.connect(options); + log('\nScan the QR code to jump to Librarian\'s web interface:'); + qrcode.generate(webURL); - } catch (error) { - sendEvent(LibrarianEvents.ServerError); - log(JSON.stringify(error)); - fatalError("\nFailed to start the ngrok tunnel.\nPlease make sure your ngRok token is valid."); - } + await checkForUpdate(preferences); + }); - if (tunnelURL == undefined || tunnelURL === '') { - fatalError('Failed to start the ngrok tunnel.') - } - prefs.currentURL = tunnelURL; +program + .command('submit ') + .alias('a') + .option('-b, --branch ', 'The branch the build is from') + .option('-n, --notes ', 'Release Notes for the build') + .option('-p, --public', 'Allow the build to be downloaded via the Internet using Librarian\'s HTTPS Tunnel') + .description('Submit a build to librarian') + .action(async (pathToFile, options) => { - const currentIP = os.networkInterfaces().en0.find(elm => elm.family == 'IPv4').address; - if (currentIP !== prefs.local_ip) { - prefs.local_ip = 'http://' + currentIP + ':' + prefs.jekyll_port; - } + sendEvent(LibrarianEvents.BuildSubmitted); - await preferences.setItem(configurationKey, prefs); + await preferences.init(storageOptions); - let webConfiguration = { - "webBaseURL": prefs.currentURL, - "localBaseURL": prefs.local_ip - }; - await setWebConfiguration(preferences, webConfiguration); + if (!await isSetup(preferences)) { + fatalError('Librarian has not been setup yet! Run ' + chalk.yellow('librarian setup') + ' to begin'); + } - const webURL = prefs.assets_web ? prefs.local_ip : tunnelURL; + const prefs = await preferences.getItem(configurationKey); - log('\nLibrarian is up at: '); - log(chalk.yellow.bold(webURL)); + if (prefs.currentURL === undefined) { + fatalError("Please start the librarian server with " + chalk.yellow('librarian start') + " before trying to submit a build"); + } - log('\nScan the QR code to jump to Librarian\'s web interface:'); - qrcode.generate(webURL); + if (!fs.existsSync(pathToFile)) { + fatalError('Couldn\'t find or access the file in the given path: ' + pathToFile); + } - await checkForUpdate(preferences); - }); + const metadata = await Extract.run(pathToFile); + const bundleIdentifier = metadata.uniqueIdentifier; + const version = metadata.version; + const build = metadata.buildVersion; + const platform = metadata.deviceFamily.indexOf("Android") > -1 ? "android" : "ios"; + let buildInfo; + + if (platform === "ios") { + const appName = metadata.displayName; + + if (bundleIdentifier === undefined || appName === undefined || version === undefined || build === undefined) { + fatalError("The IPA is missing critical information."); + } + + const buildTime = new Date(); + const folderName = buildTime.getTime(); + const templatePath = prefs.working_directory + 'web/templates/manifest.plist'; + const localManifestPath = prefs.working_directory + (prefs.assets_web ? 'asset_server' : 'web') + '/assets/b/' + folderName + '/local/manifest.plist'; + const webManifestPath = prefs.working_directory + 'web/assets/b/' + folderName + '/web/manifest.plist'; + const ipaPath = prefs.working_directory + 'web/assets/b/' + folderName + '/' + appName + '.ipa'; + + try { + fs.copySync(templatePath, localManifestPath); + fs.copySync(pathToFile, ipaPath); + const manifest = fs.readFileSync(localManifestPath, 'utf8'); + let editablePlist = plist.parse(manifest); + editablePlist.items[0].metadata["bundle-version"] = version; + editablePlist.items[0].metadata["bundle-identifier"] = bundleIdentifier; + editablePlist.items[0].metadata.title = appName; + editablePlist.items[0].assets[0].url = '{{site.data.config.localBaseURL}}/assets/b/' + folderName + '/' + appName + '.ipa'; + fs.writeFileSync(localManifestPath, JEYLL_FRONT_MATTER_CHARACTER + plist.build(editablePlist)); + if (options.public && !prefs.assets_web) { + fs.copySync(templatePath, webManifestPath); + editablePlist.items[0].assets[0].url = '{{site.data.config.webBaseURL}}/assets/b/' + folderName + '/' + appName + '.ipa'; + fs.writeFileSync(webManifestPath, JEYLL_FRONT_MATTER_CHARACTER + plist.build(editablePlist)); + } + } catch (error) { + fatalError(error); + } + + buildInfo = { + "version": version, + "buildNumber": build, + "bundle": bundleIdentifier, + "folderPath": folderName, + "date": buildTime.toISOString() + }; + } else { + const appName = metadata.originalFileName; + const buildTime = new Date(); + const folderName = buildTime.getTime(); + const apkPath = prefs.working_directory + 'web/assets/b/' + folderName + '/' + appName; + + if (bundleIdentifier === undefined || appName === undefined || version === undefined || build === undefined) { + fatalError("The APK is missing critical information."); + } + + try { + fs.copySync(pathToFile, apkPath); + } catch (error) { + fatalError(error); + } + + buildInfo = { + "version": version, + "buildNumber": build, + "bundle": bundleIdentifier, + "folderPath": folderName, + "fileName": appName, + "date": buildTime.toISOString() + }; + } + + buildInfo.notes = options.notes ? options.notes : ""; + buildInfo.branch = options.branch ? options.branch : ""; + buildInfo.public = !!options.public; + buildInfo.platform = platform; + + await addBuild(preferences, buildInfo); + printHeader("Build Added Successfully!"); + await checkForUpdate(preferences); + process.exit(0); + }); +/** + * prerequisites : + * 1. Aws secret key and secret id with s3 (iam read and write access.) + * 2. Either self provided s3 bucket name.( or the iam access should have permission to create bucket. --> not doing this for now.) + * 3. We can then provide a flag for delete file if required. + */ + +const uploadFile = async (s3Client, filePath, bucketName) => { + try{ + // Read content from the file + const fileContent = fs.readFileSync(filePath); + let fileName = path.basename(filePath); + + // Setting up S3 upload parameters + const params = { + Bucket: bucketName, + Key: fileName, // File name you want to save as in S3 + Body: fileContent, + Acl : "public-read" + }; + + // Uploading files to the bucket + let data = await s3Client.upload(params).promise(); + log(`File uploaded successfully. ${data.Location}`); + return data.Location; + } catch (e) { + throw e; + } +}; program - .command('submit ') - .alias('a') - .option('-b, --branch ', 'The branch the build is from') - .option('-n, --notes ', 'Release Notes for the build') - .option('-p, --public', 'Allow the build to be downloaded via the Internet using Librarian\'s HTTPS Tunnel') - .description('Submit a build to librarian') - .action(async (pathToFile, options) => { - - sendEvent(LibrarianEvents.BuildSubmitted); + .command('submitS3 ') + .alias('a') + .option('-b, --branch ', 'The branch the build is from') + .option('-n, --notes ', 'Release Notes for the build') + .option('-d, --delete_apk ', 'Release Notes for the build') + .option('-p, --public', 'Allow the build to be downloaded via the Internet using Librarian\'s HTTPS Tunnel') + .description('Submit a build to librarian') + .action(async (pathToFile, options) => { - await preferences.init(storageOptions); + sendEvent(LibrarianEvents.BuildSubmitted); - if (!await isSetup(preferences)) { - fatalError('Librarian has not been setup yet! Run ' + chalk.yellow('librarian setup') + ' to begin') - } + await preferences.init(storageOptions); - const prefs = await preferences.getItem(configurationKey); + if (!await isSetup(preferences)) { + fatalError('Librarian has not been setup yet! Run ' + chalk.yellow('librarian setup') + ' to begin'); + } - if (prefs.currentURL === undefined) { - fatalError("Please start the librarian server with " + chalk.yellow('librarian start') + " before trying to submit a build"); - } + const prefs = await preferences.getItem(configurationKey); - if (!fs.existsSync(pathToFile)) { - fatalError('Couldn\'t find or access the file in the given path: ' + pathToFile); - } + if (prefs.currentURL === undefined) { + fatalError("Please start the librarian server with " + chalk.yellow('librarian start') + " before trying to submit a build"); + } - const metadata = await Extract.run(pathToFile); - const bundleIdentifier = metadata.uniqueIdentifier; - const version = metadata.version; - const build = metadata.buildVersion; - const platform = metadata.deviceFamily.indexOf("Android") > -1 ? "android" : "ios"; - let buildInfo; - - if (platform == "ios") { - const appName = metadata.displayName; - - if (bundleIdentifier === undefined || appName === undefined || version === undefined || build === undefined) { - fatalError("The IPA is missing critical information."); - } - - const buildTime = new Date(); - const folderName = buildTime.getTime(); - const templatePath = prefs.working_directory + 'web/templates/manifest.plist'; - const localManifestPath = prefs.working_directory + (prefs.assets_web ? 'asset_server' : 'web') + '/assets/b/' + folderName + '/local/manifest.plist'; - const webManifestPath = prefs.working_directory + 'web/assets/b/' + folderName + '/web/manifest.plist'; - const ipaPath = prefs.working_directory + 'web/assets/b/' + folderName + '/' + appName + '.ipa'; - - try { - fs.copySync(templatePath, localManifestPath); - fs.copySync(pathToFile, ipaPath); - const manifest = fs.readFileSync(localManifestPath, 'utf8'); - let editablePlist = plist.parse(manifest); - editablePlist.items[0].metadata["bundle-version"] = version; - editablePlist.items[0].metadata["bundle-identifier"] = bundleIdentifier; - editablePlist.items[0].metadata["title"] = appName; - editablePlist.items[0].assets[0].url = '{{site.data.config.localBaseURL}}/assets/b/' + folderName + '/' + appName + '.ipa'; - fs.writeFileSync(localManifestPath, JEYLL_FRONT_MATTER_CHARACTER + plist.build(editablePlist)); - if (options.public && !prefs.assets_web) { - fs.copySync(templatePath, webManifestPath); - editablePlist.items[0].assets[0].url = '{{site.data.config.webBaseURL}}/assets/b/' + folderName + '/' + appName + '.ipa'; - fs.writeFileSync(webManifestPath, JEYLL_FRONT_MATTER_CHARACTER + plist.build(editablePlist)); + if (!fs.existsSync(pathToFile)) { + fatalError('Couldn\'t find or access the file in the given path: ' + pathToFile); } - } catch (error) { - fatalError(error); - } - - buildInfo = { - "version": version, - "buildNumber": build, - "bundle": bundleIdentifier, - "folderPath": folderName, - "date": buildTime.toISOString() - }; - } else { - const appName = metadata.originalFileName; - const buildTime = new Date(); - const folderName = buildTime.getTime(); - const apkPath = prefs.working_directory + 'web/assets/b/' + folderName + '/' + appName; - - if (bundleIdentifier === undefined || appName === undefined || version === undefined || build === undefined) { - fatalError("The APK is missing critical information."); - } - - try { - fs.copySync(pathToFile, apkPath); - } catch (error) { - fatalError(error); - } - - buildInfo = { - "version": version, - "buildNumber": build, - "bundle": bundleIdentifier, - "folderPath": folderName, - "fileName": appName, - "date": buildTime.toISOString() - }; - } - buildInfo.notes = options.notes ? options.notes : ""; - buildInfo.branch = options.branch ? options.branch : ""; - buildInfo.public = options.public ? true : false; - buildInfo.platform = platform; + if (isEmpty(prefs.s3_key) || isEmpty(prefs.s3_bucket) || isEmpty(prefs.s3_secret)) { + fatalError("Please setup librarian for s3 properly before using this command"); + } - await addBuild(preferences, buildInfo); - printHeader("Build Added Successfully!") - await checkForUpdate(preferences); - process.exit(0); - }); + // s3 upload. + const s3 = new AWS.S3({region: prefs.s3_region, accessKeyId: prefs.s3_key, secretAccessKey: prefs.s3_secret, apiVersion: '2006-03-01'}); + let fileUploadResult; + try{fileUploadResult = await uploadFile(s3, pathToFile, prefs.s3_bucket);} + catch (e) {log("fatal error in uploading the file to s3");fatalError(e);} + + const metadata = await Extract.run(pathToFile); + const bundleIdentifier = metadata.uniqueIdentifier; + const version = metadata.version; + const build = metadata.buildVersion; + const platform = metadata.deviceFamily.indexOf("Android") > -1 ? "android" : "ios"; + let buildInfo; + + if (platform === "ios") { + const appName = metadata.displayName; + + if (bundleIdentifier === undefined || appName === undefined || version === undefined || build === undefined) { + fatalError("The IPA is missing critical information."); + } + + const buildTime = new Date(); + const folderName = buildTime.getTime(); + const templatePath = prefs.working_directory + 'web/templates/manifest.plist'; + const localManifestPath = prefs.working_directory + (prefs.assets_web ? 'asset_server' : 'web') + '/assets/b/' + folderName + '/local/manifest.plist'; + const webManifestPath = prefs.working_directory + 'web/assets/b/' + folderName + '/web/manifest.plist'; + + try { + fs.copySync(templatePath, localManifestPath); + const manifest = fs.readFileSync(localManifestPath, 'utf8'); + let editablePlist = plist.parse(manifest); + editablePlist.items[0].metadata["bundle-version"] = version; + editablePlist.items[0].metadata["bundle-identifier"] = bundleIdentifier; + editablePlist.items[0].metadata.title = appName; + editablePlist.items[0].assets[0].url = fileUploadResult; + fs.writeFileSync(localManifestPath, JEYLL_FRONT_MATTER_CHARACTER + plist.build(editablePlist)); + if (options.public && !prefs.assets_web) { + fs.copySync(templatePath, webManifestPath); + editablePlist.items[0].assets[0].url = fileUploadResult; + fs.writeFileSync(webManifestPath, JEYLL_FRONT_MATTER_CHARACTER + plist.build(editablePlist)); + } + } catch (error) { + fatalError(error); + } + // s3_url --> new variable for pointing the resource path available in s3. + buildInfo = { + "version": version, + "buildNumber": build, + "bundle": bundleIdentifier, + "folderPath": folderName, + "s3_url":fileUploadResult, + "date": buildTime.toISOString() + }; + } else { + const appName = metadata.originalFileName; + const buildTime = new Date(); + const folderName = buildTime.getTime(); + const apkPath = fileUploadResult; + + if (bundleIdentifier === undefined || appName === undefined || version === undefined || build === undefined) { + fatalError("The APK is missing critical information."); + } + + // s3_url --> new variable for pointing the resource path available in s3. + buildInfo = { + "version": version, + "buildNumber": build, + "bundle": bundleIdentifier, + "folderPath": folderName, + "fileName": appName, + "s3_url":fileUploadResult, + "date": buildTime.toISOString() + }; + } + + buildInfo.notes = options.notes ? options.notes : ""; + buildInfo.branch = options.branch ? options.branch : ""; + buildInfo.public = !!options.public; + buildInfo.platform = platform; + await addBuild(preferences, buildInfo); + printHeader("Build Added Successfully!"); + await checkForUpdate(preferences); + if(options.delete_apk.toString() === "1") {fs.unlink(pathToFile, ()=>{})} + process.exit(0); + }); program - .command('update') - .description('Update Librarian to be the latest and greatest!') - .action(async () => { - printHeader('Updating Librarian...'); - await preferences.init(storageOptions); + .command('update') + .description('Update Librarian to be the latest and greatest!') + .action(async () => { + printHeader('Updating Librarian...'); + await preferences.init(storageOptions); - if (!await isSetup(preferences)) { - fatalError('Librarian has not been setup yet! Run ' + chalk.yellow('librarian setup') + ' to begin') - } - - const configuration = await preferences.getItem(configurationKey); + if (!await isSetup(preferences)) { + fatalError('Librarian has not been setup yet! Run ' + chalk.yellow('librarian setup') + ' to begin'); + } - const localPath = `${configuration.working_directory}web`; - const assetServerPath = `${configuration.working_directory}/asset_server`; + const configuration = await preferences.getItem(configurationKey); - try { - await updateServer(localPath); - if (configuration.assets_web) { - await updateServer(assetServerPath); - } + const localPath = `${configuration.working_directory}web`; + const assetServerPath = `${configuration.working_directory}/asset_server`; - await setWebConfiguration(preferences, noUpdateConfiguration); + try { + await updateServer(localPath); + if (configuration.assets_web) { + await updateServer(assetServerPath); + } - log(chalk.bold("Update Complete!")); - log(chalk.bold('\nAll set! Run Librarian using: ') + chalk.yellow.bold('librarian start')); - } catch (error) { - log(error); - log("Failed to update"); - } - }); + await setWebConfiguration(preferences, noUpdateConfiguration); -const updateServer = async (path) => { - return new Promise(async (resolve, reject) => { - git.cwd(path).then(() => git.add('./*')).then(() => git.commit(`Snapshot before Librarian Update at ${new Date()}`)).then(() => { - console.log(`Updating Librarian Web Server at ${path}...`) - git.pull((err, update) => { - if (update && update.summary.changes) { - console.log(update.summary.changes); - } - }).then(async () => { - try { - console.log(`Updating bundle for ${path}`); - await installBundle(path); - resolve(true); + log(chalk.bold("Update Complete!")); + log(chalk.bold('\nAll set! Run Librarian using: ') + chalk.yellow.bold('librarian start')); } catch (error) { - reject(error); + log(error); + log("Failed to update"); } - }) }); - }) -} -const installBundle = async (path) => { - return new Promise(async (resolve, reject) => { - const bundler = spawn('bundle install --path ./localgems', { - shell: true, - cwd: path - }); - - bundler.stdout.on('data', (data) => { - if (String(data).toLowerCase().indexOf('error') > -1) { - log(String(data)); - } +const updateServer = async (path) => { + return new Promise(async (resolve, reject) => { + git.cwd(path).then(() => git.add('./*')).then(() => git.commit(`Snapshot before Librarian Update at ${new Date()}`)).then(() => { + console.log(`Updating Librarian Web Server at ${path}...`); + git.pull((err, update) => { + if (update && update.summary.changes) { + console.log(update.summary.changes); + } + }).then(async () => { + try { + console.log(`Updating bundle for ${path}`); + await installBundle(path); + resolve(true); + } catch (error) { + reject(error); + } + }); + }); }); +}; - bundler.on('exit', function (code, signal) { - if (code === 0) { - log(chalk.green('Bundle Installation Complete!')); - resolve(true); - return; - } - - if (code == 127) { - console.log('Librarian requires bundler to work. Please install bundler by running ' + chalk.bold.yellow('gem install bundler') + ' and run librarian setup again.'); - reject(false); - } else { - reject(false) - } +const installBundle = async (path) => { + return new Promise(async (resolve, reject) => { + const bundler = spawn('bundle install --path ./localgems', { + shell: true, + cwd: path + }); + + bundler.stdout.on('data', (data) => { + if (String(data).toLowerCase().indexOf('error') > -1) { + log(String(data)); + } + }); + + bundler.on('exit', function (code, signal) { + if (code === 0) { + log(chalk.green('Bundle Installation Complete!')); + resolve(true); + return; + } + + if (code == 127) { + console.log('Librarian requires bundler to work. Please install bundler by running ' + chalk.bold.yellow('gem install bundler') + ' and run librarian setup again.'); + reject(false); + } else { + reject(false); + } + }); }); - }); -} +}; const checkForUpdate = async (preferences) => { - const notifier = updateNotifier({ pkg }); - notifier.notify(); - if (notifier.update) { - const configuration = { - "update": { - "available": true, - "notes": `An Update to Librarian is available! The new version is ${notifier.update.latest} (You have ${notifier.update.current})` - } + const notifier = updateNotifier({ pkg }); + notifier.notify(); + if (notifier.update) { + const configuration = { + "update": { + "available": true, + "notes": `An Update to Librarian is available! The new version is ${notifier.update.latest} (You have ${notifier.update.current})` + } + }; + await setWebConfiguration(preferences, configuration); + } else { + await setWebConfiguration(preferences, noUpdateConfiguration); } - await setWebConfiguration(preferences, configuration); - } else { - await setWebConfiguration(preferences, noUpdateConfiguration); - } -} +}; const printHeader = (message) => { - log('---------------------'); - log(chalk.black.bgCyan.bold(message)); - log('---------------------'); + log('---------------------'); + log(chalk.black.bgCyan.bold(message)); + log('---------------------'); }; const fatalError = (message) => { - log(chalk.red.bold('🚨 Error: ' + message + ' 🚨')); - process.exit(1); + log(chalk.red.bold('🚨 Error: ' + message + ' 🚨')); + process.exit(1); }; program.parse(process.argv); process.on('SIGINT', async function () { - log("\nExiting...") - await preferences.init(storageOptions); - const prefs = await preferences.getItem(configurationKey); - prefs.currentURL = undefined; - await preferences.setItem(configurationKey, prefs); - printHeader("Thanks for using Librarian!"); - process.exit(0); + log("\nExiting..."); + await preferences.init(storageOptions); + const prefs = await preferences.getItem(configurationKey); + prefs.currentURL = undefined; + await preferences.setItem(configurationKey, prefs); + printHeader("Thanks for using Librarian!"); + process.exit(0); }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8a1f43b..cc464e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "librarian-server", - "version": "1.3.0", + "version": "1.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -261,6 +261,34 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "aws-sdk": { + "version": "2.630.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.630.0.tgz", + "integrity": "sha512-7BDPUIqmMZfZf+KN2Z3RpGDYGkEucQORLM2EqXuE91ETW5ySvoNd771+EaE3OS+FUx3JejfcVk8Rr2ZFU38RjA==", + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -344,6 +372,23 @@ "concat-map": "0.0.1" } }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } + } + }, "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", @@ -695,6 +740,11 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -927,6 +977,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -1073,6 +1128,11 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", @@ -1443,6 +1503,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1866,6 +1931,22 @@ "punycode": "^2.1.0" } }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", diff --git a/package.json b/package.json index cd70e61..c054d83 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "license": "MIT", "dependencies": { "app-metadata": "^0.1.26", + "aws-sdk": "^2.630.0", "chalk": "^2.4.2", "commander": "^3.0.2", "fs-extra": "8.1.0", diff --git a/setup.js b/setup.js index 1b62475..3f27a7b 100644 --- a/setup.js +++ b/setup.js @@ -1,4 +1,4 @@ -/*jshint esversion: 6 */ +/*jshint esversion: 8 */ const { prompt } = require('inquirer'); const chalk = require('chalk'); const os = require('os'); @@ -32,7 +32,7 @@ const setupQuestions = [ type: 'input', name: 'local_ip', message: 'What is the local IP Librarian Website should be running at? (Enter for autodetected default)', - default: os.networkInterfaces().en0.find(elm => elm.family == 'IPv4').address + default: os.networkInterfaces().en0.find(elm => elm.family === 'IPv4').address }, { type: 'confirm', @@ -82,14 +82,49 @@ const setupQuestions = [ name: 'web_password', message: 'Please enter the password for the web interface:', when: (answers) => { return answers.existing_token === true && answers.private_web === true; } - } + }, + { + type: 'confirm', + name: 'enable_s3', + message: 'Do you want to enable s3 storage?', + default: false, + }, + { + type: 'input', + name: 's3_region', + message: 'S3 REGION:', + default: '', + when: (answers) => { return answers.enable_s3 === true; } + }, + { + type: 'input', + name: 's3_key', + message: 'S3 SECRET ID:', + default: '', + when: (answers) => { return answers.enable_s3 === true; } + }, + { + type: 'input', + name: 's3_secret', + message: 'S3 SECRET KEY:', + default: '', + when: (answers) => { return answers.enable_s3 === true; } + }, + { + type: 'input', + name: 's3_bucket', + message: 'S3 BUCKET:', + default: '', + when: (answers) => { return answers.enable_s3 === true; } + }, + ]; const beginSetup = async (preferences) => { sendEvent(LibrarianEvents.SetupStarted); const configuration = await prompt(setupQuestions); - if (configuration.local_ip.indexOf('http') == -1) { + if (configuration.local_ip.indexOf('http') === -1) { configuration.local_ip = 'http://' + configuration.local_ip + ':' + configuration.jekyll_port; configuration.assets_web = !configuration.assets_web; } @@ -146,20 +181,20 @@ const beginSetup = async (preferences) => { bundler.on('exit', function (code, signal) { if(code != 0) { sendEvent(LibrarianEvents.SetupError); } if (code == 127) { - fatalError('Librarian requires bundler to work. Please install bundler by running ' + chalk.bold.yellow('gem install bundler') + ' and run librarian setup again.') + fatalError('Librarian requires bundler to work. Please install bundler by running ' + chalk.bold.yellow('gem install bundler') + ' and run librarian setup again.'); } }); await preferences.setItem(configurationKey, configuration); -} +}; const purgeExistingInstallation = async (preferences) => { const prefs = await preferences.getItem(configurationKey); console.log("Purging the Existing Installation at: " + prefs.working_directory); await fs.emptyDir(prefs.working_directory); - await fs.removeSync(prefs.working_directory) + await fs.removeSync(prefs.working_directory); console.log("Purge Complete!\n"); -} +}; const isSetup = async (preferences) => { const isSetup = await preferences.getItem(configurationKey); diff --git a/webBridge.js b/webBridge.js index b06b34a..1a25fb2 100644 --- a/webBridge.js +++ b/webBridge.js @@ -1,4 +1,4 @@ -/*jshint esversion: 6 */ +/*jshint esversion: 8 */ const { configurationKey } = require('./setup.js'); const fs = require('fs-extra'); const yaml = require('js-yaml'); @@ -20,7 +20,7 @@ const setWebConfiguration = async (preferences, configuration) => { } catch (error) { console.log(error); } -} +}; const addBuild = async (preferences, build) => { try { @@ -37,6 +37,6 @@ const addBuild = async (preferences, build) => { } catch (error) { console.log(error); } -} +}; module.exports = { setWebConfiguration, addBuild }; \ No newline at end of file