diff --git a/.deps.json b/.deps.json deleted file mode 100644 index f08af1d..0000000 --- a/.deps.json +++ /dev/null @@ -1 +0,0 @@ -npm i -S postcss-import postcss-inline-svg postcss-color-function autoprefixer postcss-extend precss babel-plugin-transform-object-rest-spread babel-plugin-transform-object-assign \ No newline at end of file diff --git a/index.js b/index.js index 4ba35f6..acc42bf 100755 --- a/index.js +++ b/index.js @@ -1,8 +1,6 @@ #! /usr/bin/env node 'use strict' -const path = require('path') -const fs = require('fs') const program = require('commander') const colors = require('colors') @@ -27,7 +25,9 @@ if (program.debug) { process.env.ENV = program.env || 'development' -if (process.env.ENV !== 'development') { +if (process.env.ENV === 'development') { + process.env.NODE_ENV = 'development' +} else { process.env.NODE_ENV = 'production' } diff --git a/install.js b/install.js new file mode 100644 index 0000000..ff9def7 --- /dev/null +++ b/install.js @@ -0,0 +1,42 @@ +#! /usr/bin/env node +'use strict' + +const deps = require('./lib/deps.json') +const {exec} = require('child_process') + +class Installer { + run () { + const dependencies = Object.keys(deps) + .map(key => `${key}@${deps[key]}`) + .join(' ') + + this.install(dependencies) + .then(msg => { + console.log(msg) + }) + .catch(msg => { + console.log(msg) + }) + } + + exec ( command = "ls" ) { + return new Promise( (resolve, reject) => { + exec(command, (err, stdout) => { + if (err) { + console.error(err) + reject(err.message) + return + } + resolve(stdout) + }) + }) + } + + install (dependencies) { + // Last npm i is to recreate full .bin folder + return this.exec(`cd ${process.cwd()} && npm i -D ${dependencies} && npm i`) + } +} + +const installer = new Installer() +installer.run() diff --git a/lib/configure.js b/lib/configure.js index 1eb717c..5433415 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -16,7 +16,7 @@ class Configure { setDefaults () { this.defaults = { publicPath: '/dev', - proxy: 'localhost', + local: 'localhost', dist: `${this.cwd}/dist`, theme_id: '', api_key: '', diff --git a/lib/deployer.js b/lib/deployer.js index 926c6cf..110e89d 100644 --- a/lib/deployer.js +++ b/lib/deployer.js @@ -1,13 +1,13 @@ const colors = require('colors') -const fs = require('fs'); const path = require('path') -const {exec, spawn} = require('child_process') +const {exec} = require('child_process') const shopifyAPI = require('shopify-node-api') const Moment = require('moment-timezone') const readline = require('readline') const config = require('./configure') const Err = require('./error') const Builder = require('./builder') +const locales = require('./locales') class Deployer { constructor () { @@ -17,107 +17,160 @@ class Deployer { access_token: config.get('password'), verbose: false }); + this.files = [] + this.questioner = false + this.deployAll = false + + this.askQuestion = this.askQuestion.bind(this) + this.interpretLastDeploymentFlag = this.interpretLastDeploymentFlag.bind(this) + this.guessLastDeploymentFlag = this.guessLastDeploymentFlag.bind(this) + this.getLastVersionTag = this.getLastVersionTag.bind(this) + this.getBranchBaseCommit = this.getBranchBaseCommit.bind(this) + this.getChangedFiles = this.getChangedFiles.bind(this) + this.filterFiles = this.filterFiles.bind(this) + this.confirmFiles = this.confirmFiles.bind(this) + this.buildAndDeploy = this.buildAndDeploy.bind(this) + this.finishDeployment = this.finishDeployment.bind(this) } run () { - const questioner = readline.createInterface({ + this.questioner = readline.createInterface({ input: process.stdin, output: process.stdout }) - let question1 = '\n\nWhat\'s your deployment breakpoint (commit, tag or branch)? \n\n'.bgBlack.yellow - question1 += 'eg. commithash, v1.0.1, develop or.. "all" \n'.bgBlack.yellow - question1 += 'Default: Last version tag \n\n'.bgBlack.yellow - question1 += '[write your answer now..] \n'.bgBlack.yellow - - this.askQuestion(questioner, question1) - .then(answer => { - let bp; - if (!answer) { - bp = this.getLastVersionTag() - .then(t => Promise.resolve(t.replace(/[\r\n]+/, ''))) - .then(t => { - if (!t) { - let comment = 'No version tag found\n'.underline.bgWhite.black - comment += 'Getting branch merge-base..\n'.underline.bgWhite.black - console.log(comment) - - return this.getBranchName() - .then(b => this.getBranchBaseCommit(b)) - } else { - return Promise.resolve(t) - } - }) - } else if (/^all/.test(answer)){ - bp = Promise.resolve(null) - } else { - bp = Promise.resolve(answer.replace(/ /, '')) + this.askQuestion(locales.deployer.question1) + .then(this.interpretLastDeploymentFlag) + .then(this.getChangedFiles) + .then(this.confirmFiles) + .then(this.buildAndDeploy) + .then(this.finishDeployment) + .catch(err => { + console.log(`${err}`.red) + this.questioner.close() + }) + } + + askQuestion (question) { + question = this.attachColor(question) + return new Promise((resolve, reject) => { + this.questioner.question(question, answer => { + resolve(answer) + }) + }) + } + + interpretLastDeploymentFlag (answer) { + answer = this.trimString(answer) + if (!answer) { + return this.guessLastDeploymentFlag() + } + if (/^all/.test(answer)) { + this.deployAll = true + } + return Promise.resolve(answer) + } + + guessLastDeploymentFlag () { + return this.getLastVersionTag() + .then(t => { + t = this.trimString(t) + if (t) { + return Promise.resolve(t) } - return bp.then(str => { - if (str) { - return this.getChangedFiles(str) - } else { - // Deploy all files - return Promise.resolve(null) - } - }).then(str => { - if (str === null) { - // Deploy all files - return Promise.resolve(str) - } + this.talk(locales.deployer.noVersionTagFound) + return this.getBranchName().then(this.getBranchBaseCommit) + }) + } - if (!str) { - return Promise.reject('No files to upload :('.red) - } - - const files = str.split(/\n/) - .filter(f => f) - .filter(f => /^src/.test(f)) - return Promise.resolve(files) - }) - }).then(files => { - if (!files) { - // Deploy all files - return Promise.resolve({answer: 'Y'}) + getChangedFiles (breakpoint) { + if (this.deployAll) { + return Promise.resolve(this.files) + } + return this.getChangedFilesFromDiff(breakpoint) + .then(this.filterFiles) + .then(files => { + if (!files || !files.length) { + throw new Error('No files to deploy') } + this.files = files + return Promise.resolve(this.files) + }) + } - let question2 = '\n\nDo these files look correct? \n'.underline.bgWhite.black - question2 += '* JS and CSS will always be uploaded *\n\n'.underline.bgWhite.black - question2 += files.join('\n').bgBlack.white - question2 += '\n\n[Y|n]\n'.bgBlack.white - - return this.askQuestion(questioner, question2) - .then(answer => { - return Promise.resolve({answer, files}) - }) - - }).then(({answer, files = null}) => { - if (!answer || /^[Yy]+/.test(answer)) { - console.log('Starting deployment..'.green) - return Promise.resolve(files) + getChangedFilesFromDiff (breakpoint) { + return this.exec(`git diff --name-status ${this.trimString(breakpoint)}..HEAD`) + } + + filterFiles (files) { + const cleansed = files.split(/\n/) + .filter(f => f) + .map(line => { + return line.split(/\t/) + }) + // Forget about files that have been deleted + // Unless they are a compiled file (config, css, js) + .filter(line => { + if (!/D/.test(line[0])) { + return true } - return Promise.reject('Files do not look good :('.red) - }).then(files => { - if (files === null) { - return Builder(true) + if (/(?:config|[.]js|[.]s?css)/i.test(line[1])) { + return true + } + return false + }) + // If a file has been replaced, get the new location + .map(line => { + if (/R/.test(line[0])) { + return line[2] } else { - return Builder(true, files) + return line[1] } - }).then(() => { - this.renameThemeFile() - questioner.close() - console.log('🏆 Deployment completed!'.green) - }).catch(err => { - console.log(`${err}`.red) - questioner.close() }) + .filter(f => /^src/.test(f)) + + return Promise.resolve(cleansed) } - askQuestion (process, question) { - return new Promise((resolve, reject) => { - process.question(question, answer => { - resolve(answer) + confirmFiles () { + if (this.deployAll) { + return Promise.resolve(true) + } + let question2 = locales.deployer.question2 + .replace('{{files}}', this.files.join('\n')) + + return this.askQuestion(question2) + .then(answer => { + if (!answer || /^[Yy]+/.test(answer)) { + this.talk('Starting deployment..') + return Promise.resolve(true) + } + throw new Error('Files do not look good :(') }) - }) + } + + buildAndDeploy () { + if (this.deployAll) { + return Builder(true) + } + return Builder(true, this.files) + } + + finishDeployment () { + this.renameThemeFile() + this.questioner.close() + this.talk('🏆 Deployment completed!') + } + + talk (msg) { + console.log(this.attachColor(msg)) + } + + attachColor (str) { + return `${str}`.bgBlack.yellow + } + + trimString (str) { + return str.replace(/[\r\n]+/, '') } exec ( command = "ls" ) { @@ -133,10 +186,6 @@ class Deployer { }) } - getChangedFiles (breakpoint) { - return this.exec(`git diff --name-only HEAD..${breakpoint}`) - } - getBranchBaseCommit (branchName) { let cleansed = branchName.replace(/[\r\n]+/, '') if (cleansed === 'develop') { @@ -144,6 +193,7 @@ class Deployer { } return this.exec(`git merge-base ${cleansed} develop`) } + getBranchName () { return this.exec('git rev-parse --abbrev-ref HEAD') } diff --git a/lib/deps.json b/lib/deps.json new file mode 100644 index 0000000..a990e79 --- /dev/null +++ b/lib/deps.json @@ -0,0 +1,28 @@ +{ + "autoprefixer": "^7.1.4", + "babel-core": "^6.25.0", + "babel-eslint": "^7.2.3", + "babel-loader": "^7.1.5", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-object-assign": "^6.22.0", + "babel-plugin-transform-object-rest-spread": "^6.23.0", + "babel-preset-es2015": "^6.24.1", + "css-loader": "^0.28.7", + "eslint-loader": "^2.0.0", + "file-loader": "^1.1.11", + "globby": "^5.0.0", + "gulp": "^3.9.1", + "postcss-automath": "^1.0.1", + "postcss-color-function": "^4.0.0", + "postcss-extend": "^1.0.5", + "postcss-fontpath": "^1.0.0", + "postcss-hexrgba": "^1.0.0", + "postcss-import": "^10.0.0", + "postcss-inline-svg": "^3.0.0", + "postcss-loader": "^2.0.6", + "precss": "^2.0.0", + "standard": "^10.0.2", + "style-loader": "^0.18.2", + "url-loader": "^1.0.1", + "webpack": "^4.16.2" +} \ No newline at end of file diff --git a/lib/gulp.js b/lib/gulp.js index 77e167e..9d86298 100644 --- a/lib/gulp.js +++ b/lib/gulp.js @@ -1,12 +1,10 @@ const colors = require('colors') const nodePath = require('path') const gulp = require('gulp') -const del = require('delete') const glob = require('glob-all') const concat = require('gulp-concat') const wrap = require('gulp-wrapper') const rename = require('gulp-rename') -const path = require('path') const config = require('./configure') const bs = require('./browsersync') @@ -129,14 +127,6 @@ class Gulper { resolve() } }) - } else if (type === 'deleted') { - del(dest, {force: true}, err => { - if (err) { - reject(`Error deleting ${dest}`) - } - resolve() - console.log(`Deleted ${dest}`.blue) - }) } }) } @@ -197,8 +187,6 @@ class Gulper { } } ) - - return this.build() } } diff --git a/lib/locales.js b/lib/locales.js new file mode 100644 index 0000000..64102a7 --- /dev/null +++ b/lib/locales.js @@ -0,0 +1,9 @@ +const deployer = { + "question1": "\n\nWhen was the last deployment (commit, tag or branch)?\n\neg. commithash, v1.0.1, develop or.. 'all'\n\nDefault: Last version tag \n\n", + "question2": "\n\nDo these files look correct? \n* JS and CSS will always be uploaded *\n\n{{files}}\n\n[Y|n]\n", + "noVersionTagFound": "No version tag found\nGetting branch merge-base..\n" +} + +module.exports = { + deployer +} \ No newline at end of file diff --git a/lib/watcher.js b/lib/watcher.js index 37b92c1..c78cbff 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -16,9 +16,8 @@ class Watcher { constructor() { this.compiler = Webpack(true).compiler - this.copy().then(() => { - this.serve() - }) + this.copy() + this.serve() } getServerConfig () { @@ -49,8 +48,8 @@ class Watcher { ], target }, - open: (config.get('proxy') ? 'external' : 'internal'), - host: config.get('proxy'), + open: (this.isLocalhost() ? 'internal' : 'external'), + host: config.get('local'), https: this.getSSL(), notify: false } @@ -73,6 +72,10 @@ class Watcher { return /^https:/.test(target) } + isLocalhost () { + return /localhost/.test(config.get('local')) + } + getSSL() { if (this.isSSL()) { return { diff --git a/lib/webpack.js b/lib/webpack.js index 39d53e1..25f6f5c 100644 --- a/lib/webpack.js +++ b/lib/webpack.js @@ -1,9 +1,8 @@ +const colors = require('colors') const path = require('path') const webpack = require('webpack') -const merge = require('webpack-merge') const MinifyPlugin = require("babel-minify-webpack-plugin") const ExtractTextPlugin = require("extract-text-webpack-plugin") -const colors = require('colors') const config = require('./configure') const Err = require('./error') @@ -100,8 +99,10 @@ class Webpacker { ]: []), new webpack.NoEmitOnErrorsPlugin(), new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + DEBUG: !(process.env.NODE_ENV === 'production'), BRRL_PUBLIC_PATH: JSON.stringify(config.get('publicPath')), - BRRL_PROXY: JSON.stringify(config.get('proxy')), + BRRL_PROXY: JSON.stringify(config.get('local')), BRRL_PATH: function(path, proxy) { if (!proxy) { proxy = 'localhost'; @@ -112,10 +113,6 @@ class Webpacker { return SHOPIFY_CDN; }; } - }), - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - DEBUG: !(process.env.NODE_ENV === 'production') }) ] } @@ -164,6 +161,7 @@ class Webpacker { } = this const obj = { + mode: process.env.NODE_ENV, devtool, entry, output, diff --git a/package.json b/package.json index 7c68fc9..d1c65b3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "A build and deployment tool for the devs at Barrel", "main": "index.js", "bin": { - "brrl": "./index.js" + "brrl": "./index.js", + "brrl-install": "./install.js" }, "author": "maxrolon", "license": "MIT", @@ -13,65 +14,23 @@ }, "dependencies": { "@shopify/themekit": "^0.6.12", - "autoprefixer": "^7.1.4", - "babel-core": "^6.25.0", - "babel-eslint": "^7.2.3", - "babel-loader": "^7.0.0", - "babel-minify-webpack-plugin": "^0.2.0", - "babel-plugin-lodash": "^3.2.11", - "babel-plugin-module-resolver": "^2.7.1", - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-object-assign": "^6.22.0", - "babel-plugin-transform-object-rest-spread": "^6.23.0", - "babel-preset-es2015": "^6.24.1", - "browser-sync": "^2.18.5", - "colors": "^1.1.2", - "commander": "^2.11.0", - "css-hot-loader": "^1.3.4", - "css-loader": "^0.28.7", - "deepmerge": "^1.5.1", - "delete": "^1.1.0", - "eslint-loader": "^1.9.0", - "event-emitter": "^0.3.5", - "express": "^4.15.4", - "extract-text-webpack-plugin": "^3.0.0", - "file-loader": "^1.1.11", - "function-rate-limit": "^1.1.0", - "gitignore-parser": "0.0.2", + "babel-minify-webpack-plugin": "^0.3.1", + "browser-sync": "^2.24.6", + "colors": "^1.3.1", + "commander": "^2.16.0", + "extract-text-webpack-plugin": "v4.0.0-beta.0", "glob-all": "^3.1.0", - "globby": "^5.0.0", - "gulp": "^3.9.1", "gulp-concat": "^2.6.1", - "gulp-rename": "^1.2.2", + "gulp-rename": "^1.4.0", "gulp-wrapper": "^1.0.0", - "js-yaml": "^3.9.1", - "lodash-webpack-plugin": "^0.11.4", - "moment-timezone": "^0.5.13", - "node-sass": "^4.5.3", - "node-watch": "^0.5.5", - "postcss-automath": "^1.0.1", - "postcss-color-function": "^4.0.0", - "postcss-extend": "^1.0.5", - "postcss-fontpath": "^1.0.0", - "postcss-hexrgba": "^1.0.0", - "postcss-import": "^10.0.0", - "postcss-inline-svg": "^3.0.0", - "postcss-loader": "^2.0.6", - "postcss-sassy-mixins": "^2.1.0", - "postcss-scss": "^1.0.3", - "precss": "^2.0.0", - "rimraf": "^2.6.2", - "sass-loader": "^6.0.6", + "js-yaml": "^3.12.0", + "moment-timezone": "^0.5.21", "shopify-node-api": "^1.8.0", - "socket.io": "^2.0.3", - "standard": "^10.0.2", - "standard-loader": "^6.0.1", - "style-loader": "^0.18.2", - "url-loader": "^1.0.1", - "webpack": "^3.5.6", - "webpack-dev-middleware": "^1.12.0", - "webpack-hot-middleware": "^2.19.1", - "webpack-merge": "^4.1.0" + "webpack-dev-middleware": "^3.1.3", + "webpack-hot-middleware": "^2.22.3" + }, + "peerDependencies": { + "webpack": "^4.16.2", + "gulp": "^3.9.1" } }