From 33923027575269cd87947948af769e7311ceef87 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Wed, 6 Jan 2016 18:47:40 +0900 Subject: [PATCH 01/36] Add "download" command. This can only be run after an "init"; it downloads the current code from Google. Typically you'd want to use this to check that the code you're working with is up to date with what's on Google. --- bin/gapps | 5 +++++ lib/commands/download.js | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 lib/commands/download.js diff --git a/bin/gapps b/bin/gapps index d340154..327d9da 100755 --- a/bin/gapps +++ b/bin/gapps @@ -37,6 +37,11 @@ program .alias('clone') .action(require(commands + '/init')); +program + .command('download') + .description('Download copy of current code for Apps Script project.') + .action(require(commands + '/download')) + program .command('oauth-callback-url') .description('Get the OAuth Callback URL for a project') diff --git a/lib/commands/download.js b/lib/commands/download.js new file mode 100644 index 0000000..eb947a5 --- /dev/null +++ b/lib/commands/download.js @@ -0,0 +1,44 @@ +'use strict' + +const mkdirp = require('mkdirp'); +const Promise = require('bluebird'); +const fs = Promise.promisifyAll(require('fs')); + +const util = require('../util') +const manifestor = require('../manifestor'); + +module.exports = function download() { + console.log('Downloading from Google Drive...') + + var subdir, fileId; + + return manifestor.get() + .then(function(config) { + subdir = config.path + fileId = config.fileId + }) + .then(function() { + return mkdirp(subdir); + }) + .then(function() { + return manifestor.getExternalFiles(fileId) + }) + .map(function(file) { + console.log('Writing ' + file.name + util.getFileExtension(file)) + return writeExternalFile(file, subdir) + }) + .catch(function(err) { + console.log('Error running download command'.red); + throw err; + }); +} + +// XXX duplicated function from `command/init.js` +function writeExternalFile(file, dir) { + var filename = file.name + util.getFileExtension(file) + return fs.writeFileAsync(dir + '/' + filename, file.source) + .catch(function(err) { + console.log('Could not write file ' + filename); + throw err; + }) +} From f6ffa8da55b1452db3e8cb92a8480314ba2e05f9 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sat, 23 Jan 2016 15:39:36 +0900 Subject: [PATCH 02/36] .gitignore: ignore `.build/`, `tmp/` and syntax etc. fixes * Prefix a slash to all patterns expected to be only in root of project. * Suffix a slash to all patterns expected to be directories. * `tmp/` is used as a scratch directory by developers. * `.build/` is for files generated by the build/test system. --- .gitignore | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 39bf3eb..ba7129b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -.DS_STORE -node_modules -*.log \ No newline at end of file +/.build/ +.DS_Store/ +/node_modules/ +/tmp/ From 844a0175ee24f464d82f7d54f1b97075cf315221 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sat, 23 Jan 2016 19:45:33 +0900 Subject: [PATCH 03/36] Add a unit test framework and a simple test that the module loads properly. * Bring in gulp to do our build/test stuff, give it a "test" and "unit-test" targets (the former depending on the latter). * Use tape to run the unit tests, and faucet to pretty-print the results. (Still a bit verbose when there are no errors.) --- gulpfile.js | 14 ++++++++++++++ index.jt | 11 +++++++++++ package.json | 10 ++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 gulpfile.js create mode 100644 index.jt diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..a1fb68f --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,14 @@ +'use strict' + +const gulp = require('gulp') + +gulp.task('test', ['unit-test']) + +gulp.task('unit-test', () => { + const + tape = require('gulp-tape'), + faucet = require('faucet') + return gulp + .src('**/*.jt') + .pipe(tape({ reporter: faucet() })) +}) diff --git a/index.jt b/index.jt new file mode 100644 index 0000000..6956611 --- /dev/null +++ b/index.jt @@ -0,0 +1,11 @@ +'use strict' + +const test = require('tape') + +test('require_package', (t) => { + const gas = require('./') + t.assert(gas) + t.equal(gas.defaults.DEFAULT_SUBDIR, 'src') + t.end() +}) + diff --git a/package.json b/package.json index a4bc013..d4b6173 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "node-google-apps-script", "version": "1.1.4", "description": "The easiest way to develop Google Apps Script projects", + "scripts": { + "test": "./node_modules/.bin/gulp test --silent" + }, "repository": { "type": "git", "url": "https://github.com/danthareja/node-google-apps-script.git" @@ -43,7 +46,10 @@ "node-dir": "^0.1.6", "request": "^2.54.0" }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "devDependencies": { + "faucet": "0.0.1", + "gulp": "^3.9.0", + "gulp-tape": "0.0.7", + "tape": "^4.4.0" } } From f2744f6df12d4c5fa68b4756db62701ef47d718c Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sun, 24 Jan 2016 16:40:40 +0900 Subject: [PATCH 04/36] README: Add some documentation about the unit tests. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 80f7396..8545aa9 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,18 @@ Please submit any bugs to the Issues page. Pull Requests also welcome. If you want to develop, clone down the repo and have at it! You can run `npm link` from the root directory of the repo to symlink to your local copy. You'll have to uninstall the production version first `npm uninstall -g node-google-apps-script`. +### Testing + +The test suite is still under construction, but does/will consist of +unit tests and functional tests. There are gulp targets to find and +run the various kinds of tests, and the tests themselves use the [tape] +framework. + +* Unit tests are in `*.jt` files anywhere in the repo; the build system +will find these and execute them individually by running `node` on them. + +[tape]: https://www.npmjs.com/package/tape + ## Limitations `gapps` allows you to nest files in folders, but the Apps Script platform expects a flat file structure. Because of this, **no files can have the same name, even if they are in separate directories**. One file will overwrite the other, making debugging difficult. From 4860c0515d81c9393df1070b25a8bb7324691705 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sun, 24 Jan 2016 16:53:21 +0900 Subject: [PATCH 05/36] Don't look for unit tests in dot-files and gitignore files/dirs. --- README.md | 6 ++++-- gulpfile.js | 2 ++ package.json | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8545aa9..04aa5e4 100644 --- a/README.md +++ b/README.md @@ -169,8 +169,10 @@ unit tests and functional tests. There are gulp targets to find and run the various kinds of tests, and the tests themselves use the [tape] framework. -* Unit tests are in `*.jt` files anywhere in the repo; the build system -will find these and execute them individually by running `node` on them. +* Unit tests are in `*.jt` files anywhere in the repo (excepting + directories and files start with a dot and anything ignored by + `.gitignore`). The build system will find these and execute them + individually by running `node` on them. [tape]: https://www.npmjs.com/package/tape diff --git a/gulpfile.js b/gulpfile.js index a1fb68f..39b6631 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,6 +1,7 @@ 'use strict' const gulp = require('gulp') +const exclude_gitignore = require('gulp-exclude-gitignore') gulp.task('test', ['unit-test']) @@ -10,5 +11,6 @@ gulp.task('unit-test', () => { faucet = require('faucet') return gulp .src('**/*.jt') + .pipe(exclude_gitignore()) .pipe(tape({ reporter: faucet() })) }) diff --git a/package.json b/package.json index d4b6173..4db7343 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "devDependencies": { "faucet": "0.0.1", "gulp": "^3.9.0", + "gulp-exclude-gitignore": "^1.0.0", "gulp-tape": "0.0.7", "tape": "^4.4.0" } From f5633091d89efbb4da05c51e5594d1aff0e29f42 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sun, 24 Jan 2016 17:59:20 +0900 Subject: [PATCH 06/36] Start a basic functional test framework. We use a standard TAP functional test format with `tNNN*.js` test scripts under a `t/` subdirectory. These can be run directly from the project root with `node t/t001.js` or similar, and later the build system will be extended to find and run tests automatically. The `t/tlib.js` file is a library of handy support functions for this, including bringing it various test tools (tape, tape-spawn, etc.) and functions to read in data files that store expected output and whatnot. tlib itself is tested with `t/t001-tlib.js`. The first test of the non-test-framework code is `t010-version.js`, which proves we can run the `gapps` binary. --- package.json | 3 ++- t/t001-tlib.js | 8 ++++++++ t/t001/expected | 1 + t/t010-version.js | 12 ++++++++++++ t/tlib.js | 22 ++++++++++++++++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 t/t001-tlib.js create mode 100644 t/t001/expected create mode 100644 t/t010-version.js create mode 100644 t/tlib.js diff --git a/package.json b/package.json index 4db7343..5afe5a9 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "gulp": "^3.9.0", "gulp-exclude-gitignore": "^1.0.0", "gulp-tape": "0.0.7", - "tape": "^4.4.0" + "tape": "^4.4.0", + "tape-spawn": "^1.4.0" } } diff --git a/t/t001-tlib.js b/t/t001-tlib.js new file mode 100644 index 0000000..7efe57f --- /dev/null +++ b/t/t001-tlib.js @@ -0,0 +1,8 @@ +'use strict' +const tlib = require('./tlib')(module) + +tlib.test('readTestData', (t) => { + t.equal('A file containing expected output.\n', + tlib.readTestData(tlib.testData('expected'))) + t.end() +}) diff --git a/t/t001/expected b/t/t001/expected new file mode 100644 index 0000000..7da468e --- /dev/null +++ b/t/t001/expected @@ -0,0 +1 @@ +A file containing expected output. diff --git a/t/t010-version.js b/t/t010-version.js new file mode 100644 index 0000000..037f9ca --- /dev/null +++ b/t/t010-version.js @@ -0,0 +1,12 @@ +'use strict' +const tlib = require('./tlib')(module) + +// Smoke test for the command line program. +tlib.test('version', (t) => { + const + expected = require('../package.json').version + '\n', + cmd = tlib.spawn(t, './bin/gapps -V') + cmd.succeeds() + cmd.stdout.match(expected) + cmd.end() +}) diff --git a/t/tlib.js b/t/tlib.js new file mode 100644 index 0000000..6bb10d3 --- /dev/null +++ b/t/tlib.js @@ -0,0 +1,22 @@ +const + fs = require('fs'), + path = require('path'), + _ = require('lodash') + +function testData(test_module, p) { + const + dir = path.dirname(test_module.filename), + subdir = path.basename(test_module.filename, '.js').split('-')[0] + return path.join(dir, subdir, p) +} + +function readTestData(p) { + return fs.readFileSync(p, { encoding: 'UTF-8' }) +} + +module.exports = (test_module) => { return { + test: require('tape'), + spawn: require('tape-spawn'), + testData: _.partial(testData, test_module), + readTestData: readTestData, +}} From 43a4f5b54391df9f11b4505c0c2631efd225a23b Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sun, 24 Jan 2016 18:45:01 +0900 Subject: [PATCH 07/36] gulp: Add task for functional tests. The functional tests depend on the unit tests to prevent interleaving the output of unit-test and functional-test tasks. --- gulpfile.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 39b6631..b037fa8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,16 +1,22 @@ 'use strict' -const gulp = require('gulp') -const exclude_gitignore = require('gulp-exclude-gitignore') +const + gulp = require('gulp'), + exclude_gitignore = require('gulp-exclude-gitignore'), + tape = require('gulp-tape'), + faucet = require('faucet') -gulp.task('test', ['unit-test']) +gulp.task('test', ['unit-test', 'functional-test']) gulp.task('unit-test', () => { - const - tape = require('gulp-tape'), - faucet = require('faucet') return gulp .src('**/*.jt') .pipe(exclude_gitignore()) .pipe(tape({ reporter: faucet() })) }) + +gulp.task('functional-test', ['unit-test'], () => { + return gulp + .src('t/t[0-9]*.js') + .pipe(tape({ reporter: faucet() })) +}) From c368678ab451da86470610cdc3ef385f77ab765f Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sun, 24 Jan 2016 19:19:21 +0900 Subject: [PATCH 08/36] gapps: Fail when a bad command is passed on the CLI. --- bin/gapps | 20 +++++++++++++------- t/{t010-version.js => t010-basic-cli.js} | 14 +++++++++++++- 2 files changed, 26 insertions(+), 8 deletions(-) rename t/{t010-version.js => t010-basic-cli.js} (50%) diff --git a/bin/gapps b/bin/gapps index 327d9da..266843d 100755 --- a/bin/gapps +++ b/bin/gapps @@ -47,11 +47,17 @@ program .description('Get the OAuth Callback URL for a project') .action(require(commands + '/oauthCallbackUrl')); -program - .parse(process.argv); - -if (program.args.length < 1 ) { - console.log('No command specified.'); - program.outputHelp(); - process.exit(2); +program.parse(process.argv) + +var err +if (program.args.length == 0) { + err = 'No command specified' +} else if (program.args[program.args.length - 1] instanceof program.Command) { + err = undefined +} else { + err = 'Bad command specified' +} +if (err) { + console.log(err) + require('process').exit(2) } diff --git a/t/t010-version.js b/t/t010-basic-cli.js similarity index 50% rename from t/t010-version.js rename to t/t010-basic-cli.js index 037f9ca..077b1ee 100644 --- a/t/t010-version.js +++ b/t/t010-basic-cli.js @@ -2,7 +2,7 @@ const tlib = require('./tlib')(module) // Smoke test for the command line program. -tlib.test('version', (t) => { +tlib.test('help', (t) => { const expected = require('../package.json').version + '\n', cmd = tlib.spawn(t, './bin/gapps -V') @@ -10,3 +10,15 @@ tlib.test('version', (t) => { cmd.stdout.match(expected) cmd.end() }) + +tlib.test('no command fails', (t) => { + const cmd = tlib.spawn(t, './bin/gapps') + cmd.fails() + cmd.end() +}) + +tlib.test('bad commands fail', (t) => { + const cmd = tlib.spawn(t, './bin/gapps rubbish') + cmd.fails() + cmd.end() +}) From 2bf9c00e59ff66fb02f0bb2152ce1617932b810f Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sun, 24 Jan 2016 23:18:42 +0900 Subject: [PATCH 09/36] t: Work around a bug in tape-spawn 1.4.0. --- bin/gapps | 3 ++- t/t010-basic-cli.js | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/bin/gapps b/bin/gapps index 266843d..3f563ed 100755 --- a/bin/gapps +++ b/bin/gapps @@ -58,6 +58,7 @@ if (program.args.length == 0) { err = 'Bad command specified' } if (err) { + var p = require('process') console.log(err) - require('process').exit(2) + p.exit(2) } diff --git a/t/t010-basic-cli.js b/t/t010-basic-cli.js index 077b1ee..30aefd5 100644 --- a/t/t010-basic-cli.js +++ b/t/t010-basic-cli.js @@ -1,6 +1,19 @@ 'use strict' const tlib = require('./tlib')(module) +/* + * There's a bug in tape-spawn 1.4.0 that we trigger with a race condition: + * https://github.com/maxogden/tape-spawn/pull/12 + * The check for this bug that prevents cmd.fails() being called below when + * we are using a tape-spawn known to have this bug can be removed when a + * new version with the bugfix is released. Remember to update the required + * version of tape-spawn in package.json when you remove this code. + */ +const + semver = require('semver'), + tape_spawn_ver = require('tape-spawn/package.json').version, + tape_spawn_fixed = semver.gt(tape_spawn_ver, '1.4.0') + // Smoke test for the command line program. tlib.test('help', (t) => { const @@ -13,12 +26,16 @@ tlib.test('help', (t) => { tlib.test('no command fails', (t) => { const cmd = tlib.spawn(t, './bin/gapps') - cmd.fails() + cmd.stdout.match('No command specified\n') + if (tape_spawn_fixed) + cmd.fails() cmd.end() }) tlib.test('bad commands fail', (t) => { const cmd = tlib.spawn(t, './bin/gapps rubbish') - cmd.fails() + cmd.stdout.match('Bad command specified\n') + if (tape_spawn_fixed) + cmd.fails() cmd.end() }) From 896a036f7f55d5325e51efbc857c2854550d4c1a Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sun, 24 Jan 2016 23:23:36 +0900 Subject: [PATCH 10/36] bin/gapps: Print usage on argument error. --- bin/gapps | 1 + t/t010-basic-cli.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/bin/gapps b/bin/gapps index 3f563ed..885d7e7 100755 --- a/bin/gapps +++ b/bin/gapps @@ -60,5 +60,6 @@ if (program.args.length == 0) { if (err) { var p = require('process') console.log(err) + program.outputHelp() p.exit(2) } diff --git a/t/t010-basic-cli.js b/t/t010-basic-cli.js index 30aefd5..7f1af1a 100644 --- a/t/t010-basic-cli.js +++ b/t/t010-basic-cli.js @@ -27,6 +27,7 @@ tlib.test('help', (t) => { tlib.test('no command fails', (t) => { const cmd = tlib.spawn(t, './bin/gapps') cmd.stdout.match('No command specified\n') + cmd.stdout.match(/Usage:/) if (tape_spawn_fixed) cmd.fails() cmd.end() @@ -35,6 +36,7 @@ tlib.test('no command fails', (t) => { tlib.test('bad commands fail', (t) => { const cmd = tlib.spawn(t, './bin/gapps rubbish') cmd.stdout.match('Bad command specified\n') + cmd.stdout.match(/Usage:/) if (tape_spawn_fixed) cmd.fails() cmd.end() From df4632a01575a4d9b6c2c23f9eaf0f88bc974a60 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sun, 24 Jan 2016 23:28:11 +0900 Subject: [PATCH 11/36] Log error messages to stderr instead of stdout. --- bin/gapps | 2 +- t/t010-basic-cli.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/gapps b/bin/gapps index 885d7e7..6c576e0 100755 --- a/bin/gapps +++ b/bin/gapps @@ -59,7 +59,7 @@ if (program.args.length == 0) { } if (err) { var p = require('process') - console.log(err) + console.error(err) program.outputHelp() p.exit(2) } diff --git a/t/t010-basic-cli.js b/t/t010-basic-cli.js index 7f1af1a..981c5a2 100644 --- a/t/t010-basic-cli.js +++ b/t/t010-basic-cli.js @@ -26,7 +26,7 @@ tlib.test('help', (t) => { tlib.test('no command fails', (t) => { const cmd = tlib.spawn(t, './bin/gapps') - cmd.stdout.match('No command specified\n') + cmd.stderr.match('No command specified\n') cmd.stdout.match(/Usage:/) if (tape_spawn_fixed) cmd.fails() @@ -35,7 +35,7 @@ tlib.test('no command fails', (t) => { tlib.test('bad commands fail', (t) => { const cmd = tlib.spawn(t, './bin/gapps rubbish') - cmd.stdout.match('Bad command specified\n') + cmd.stderr.match('Bad command specified\n') cmd.stdout.match(/Usage:/) if (tape_spawn_fixed) cmd.fails() From b8620f0291579cab2b57e553609fadd744c83572 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Mon, 25 Jan 2016 00:09:28 +0900 Subject: [PATCH 12/36] t/tlib: extract testName function. --- t/t001-tlib.js | 5 +++++ t/tlib.js | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/t/t001-tlib.js b/t/t001-tlib.js index 7efe57f..78c8581 100644 --- a/t/t001-tlib.js +++ b/t/t001-tlib.js @@ -1,6 +1,11 @@ 'use strict' const tlib = require('./tlib')(module) +tlib.test('testName', (t) => { + t.equal(tlib.testName(), 't001') + t.end() +}) + tlib.test('readTestData', (t) => { t.equal('A file containing expected output.\n', tlib.readTestData(tlib.testData('expected'))) diff --git a/t/tlib.js b/t/tlib.js index 6bb10d3..bbc7e64 100644 --- a/t/tlib.js +++ b/t/tlib.js @@ -3,11 +3,13 @@ const path = require('path'), _ = require('lodash') +function testName(test_module) { + return path.basename(test_module.filename, '.js').split('-')[0] +} + function testData(test_module, p) { - const - dir = path.dirname(test_module.filename), - subdir = path.basename(test_module.filename, '.js').split('-')[0] - return path.join(dir, subdir, p) + return path.join( + path.dirname(test_module.filename), testName(test_module), p) } function readTestData(p) { @@ -17,6 +19,7 @@ function readTestData(p) { module.exports = (test_module) => { return { test: require('tape'), spawn: require('tape-spawn'), + testName: _.partial(testName, test_module), testData: _.partial(testData, test_module), readTestData: readTestData, }} From 59e0038d6265554892f915b9748b1dd71f122c8e Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Mon, 25 Jan 2016 00:55:58 +0900 Subject: [PATCH 13/36] t/tlib: add scratchFile() function to give us scratch workspace for tests. --- t/t001-tlib.js | 9 +++++++++ t/tlib.js | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/t/t001-tlib.js b/t/t001-tlib.js index 78c8581..dc3c66f 100644 --- a/t/t001-tlib.js +++ b/t/t001-tlib.js @@ -11,3 +11,12 @@ tlib.test('readTestData', (t) => { tlib.readTestData(tlib.testData('expected'))) t.end() }) + +tlib.test('scratchFile', (t) => { + const path = require('path') + const expected = path.join('.build', 't', 't001', 'abc', 'def') + t.equal( + tlib.scratchFile('abc', 'def').substr(0 - expected.length), + expected) + t.end() +}) diff --git a/t/tlib.js b/t/tlib.js index bbc7e64..c437dd8 100644 --- a/t/tlib.js +++ b/t/tlib.js @@ -16,10 +16,17 @@ function readTestData(p) { return fs.readFileSync(p, { encoding: 'UTF-8' }) } +function scratchFile(test_module, p) { + const args = [process.cwd(), '.build', 't', testName(test_module)] + .concat(Array.prototype.slice.call(arguments, 1)) + return path.join.apply(undefined, args) +} + module.exports = (test_module) => { return { test: require('tape'), spawn: require('tape-spawn'), testName: _.partial(testName, test_module), testData: _.partial(testData, test_module), readTestData: readTestData, + scratchFile: _.partial(scratchFile, test_module) }} From a09edfdb118f53972f6d03f6ccc6ee04ff535f97 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sat, 30 Jan 2016 15:36:19 +0900 Subject: [PATCH 14/36] 'gapps init' command line test and README updates re testing. This initializes from a publicly readable apps script project made available on Google Drive. The user will have to have done his own gapps authorization first. --- README.md | 43 +++++++++++++++++++++++++++++++++---------- package.json | 1 + t/t020-init.js | 14 ++++++++++++++ t/tlib.js | 12 +++++++++++- 4 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 t/t020-init.js diff --git a/README.md b/README.md index 04aa5e4..8a98bed 100644 --- a/README.md +++ b/README.md @@ -158,24 +158,47 @@ configured subdirectory. Returns the OAuth Callback URL required by most 3rd-party OAuth services. ## Development + +Ensure that both before and after you make any changes that all the +tests run by `npm test` pass. + +If you want to use the command line tool in other projects, you can run +`npm link` from the root directory of the repo to symlink to your local +copy. If you already have another version installed, you can uninstall it +first with `npm uninstall -g *package_name*` + Please submit any bugs to the Issues page. Pull Requests also welcome. -If you want to develop, clone down the repo and have at it! You can run `npm link` from the root directory of the repo to symlink to your local copy. You'll have to uninstall the production version first `npm uninstall -g node-google-apps-script`. ### Testing -The test suite is still under construction, but does/will consist of -unit tests and functional tests. There are gulp targets to find and -run the various kinds of tests, and the tests themselves use the [tape] -framework. - -* Unit tests are in `*.jt` files anywhere in the repo (excepting - directories and files start with a dot and anything ignored by - `.gitignore`). The build system will find these and execute them - individually by running `node` on them. +The test suite is still under construction, but consists of unit tests and +functional tests. There are gulp targets to find and run the various kinds +of tests, and the tests themselves use the [tape] framework. [tape]: https://www.npmjs.com/package/tape +#### Unit Tests + +Unit tests are in `*.jt` files anywhere in the repo (excepting directories +and files that either start with a dot or are ignored by `.gitignore`). The +build system will find these and execute them individually by running +`node` on them. + +These tests should run without any special setup. + + +#### Functional Tests + +Functional tests are under the `t/` subdirectory. While these can load and +run JavaScript code just like the unit tests do, they typically go beyond +this to run command-line programs, create and check files, access Google +Drive via the REST API, and the like. + +To run them you will need to authenticate yourself to use the Google Drive +API using the `gapps auth` subcommand. + + ## Limitations `gapps` allows you to nest files in folders, but the Apps Script platform expects a flat file structure. Because of this, **no files can have the same name, even if they are in separate directories**. One file will overwrite the other, making debugging difficult. diff --git a/package.json b/package.json index 5afe5a9..0c039e8 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "gulp": "^3.9.0", "gulp-exclude-gitignore": "^1.0.0", "gulp-tape": "0.0.7", + "rimraf": "^2.5.1", "tape": "^4.4.0", "tape-spawn": "^1.4.0" } diff --git a/t/t020-init.js b/t/t020-init.js new file mode 100644 index 0000000..d2389b3 --- /dev/null +++ b/t/t020-init.js @@ -0,0 +1,14 @@ +'use strict' +const tlib = require('./tlib')(module) + +tlib.test('pwd', (t) => { + // GDrive publicly readable test file made available by . + // (In the long run we should probably create our own test files.) + const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg' + + const scratchDir = tlib.cleanScratchDir() + const cmd = tlib.spawn(t, '../../../bin/gapps init ' + docId, + { cwd: scratchDir } ) + cmd.stdout.match('') + cmd.end() +}) diff --git a/t/tlib.js b/t/tlib.js index c437dd8..bf6dd98 100644 --- a/t/tlib.js +++ b/t/tlib.js @@ -1,6 +1,8 @@ const fs = require('fs'), path = require('path'), + mkdirp = require('mkdirp'), + rimraf = require('rimraf'), _ = require('lodash') function testName(test_module) { @@ -22,11 +24,19 @@ function scratchFile(test_module, p) { return path.join.apply(undefined, args) } +function cleanScratchDir(test_module) { + scratchDir = scratchFile(test_module) + rimraf.sync(scratchDir) + mkdirp.sync(scratchDir) + return scratchDir +} + module.exports = (test_module) => { return { test: require('tape'), spawn: require('tape-spawn'), testName: _.partial(testName, test_module), testData: _.partial(testData, test_module), readTestData: readTestData, - scratchFile: _.partial(scratchFile, test_module) + scratchFile: _.partial(scratchFile, test_module), + cleanScratchDir: _.partial(cleanScratchDir, test_module), }} From d4ea6a159eaba17fc04d02f1820b75315f41f53a Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sat, 30 Jan 2016 16:53:28 +0900 Subject: [PATCH 15/36] Rename project to gas-tools. We're effectively forking the project here as our development path (and even some current features, e.g., the "download" command) are moving in a different direction from the one in which danthareja wants to go. The command-line tool is renamed to `gas`, to avoid conflict with those who have projects that use the `gapps` tool. The local configuration file for the GAS projects is renamed to avoid confusion and because we'll soon be changing the format, but we will continue to use the `$HOME/.gapps` file for Google Drive authentication information until we expand the format of that to handle multiple accounts. We did a quick pass over the README to fix up the most obvious things related to the rename, but there's much more to be done there. --- README.md | 105 +++++++++++++++++++++++++------------------- bin/{gapps => gas} | 2 +- lib/defaults.js | 2 +- package.json | 25 +++++++---- t/t010-basic-cli.js | 6 +-- t/t020-init.js | 2 +- 6 files changed, 82 insertions(+), 60 deletions(-) rename bin/{gapps => gas} (96%) diff --git a/README.md b/README.md index 8a98bed..ca6d7a9 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,23 @@ -# gapps (Google Apps Script) ->The easiest way to develop Google Apps Script projects +gas-tools +========= -Using **gapps**, you can develop your Apps Script locally and push files to the Apps Script servers. This allows you to use any editor of your choice, version control, and other modern webdev patterns in to Apps Script development. +This is a set of tools to help develop Google Apps Script projects. +It supports development of GAS code on a local system (using your +favourite editor and version control system), transfer of the code to +and from GAS project files on Google Drive, and (soon) local testing of +GAS code. -## Requirements - - [node v0.12.x](https://nodejs.org/download/) - - `npm install -g node-google-apps-script` +The tools run under [node-js](https://nodejs.org/) and use +[npm](https://www.npmjs.com/) package management. -## Quickstart +Much of the original work was done in the project from which this was +forked, [node-google-apps-script] by Dan Thareja +and Matt Condon . + +[node-google-apps-script]: https://github.com/danthareja/node-google-apps-script + +Quickstart +---------- ### 1. Get Google Drive Credentials @@ -53,12 +63,12 @@ You can do this one of two ways: 1. You may close the Developer Console window. * To return to this project later, select `Resources` > `Developer Console Project` while editing your script. Then click the link at the top of the dialog to open the Developer Console with this project selected. -### 2. Authenticate `gapps` +### 2. Authenticate the Tools This process will set up Google Drive authentication to allow uploading and importing of the Apps Script project. -1. Run `gapps auth path/to/client_secret_abcd.json` - * i.e. `gapps auth ~/Downloads/client_secret_1234567890-abcd.apps.googleusercontent.com.json` +1. Run `gas auth path/to/client_secret_abcd.json` + * i.e. `gas auth ~/Downloads/client_secret_1234567890-abcd.apps.googleusercontent.com.json` 1. Follow the directions by clicking on the link generated by the script. 1. After you're successfully authenticated, feel free to delete the `client_secret.json` credentials file. @@ -66,7 +76,7 @@ You can pass the option `--no-launch-browser` to generate a url that will give y ### 3. Initialize your project -This proces will create `gapps.config.json` and a sub-directory where all Apps Script project files will be downloaded to. You can either use an existing project or create a new one. +This proces will create `gas-tools.json` and a sub-directory where all Apps Script project files will be downloaded to. You can either use an existing project or create a new one. #### 3.1 An existing Apps Script project @@ -74,8 +84,8 @@ This proces will create `gapps.config.json` and a sub-directory where all Apps S 1. Get your project ID from the address bar, located after `/d/` and before `/edit`. * For example, '//script.google.com/a/google.com/d/__abc123-xyz098__/edit?usp=drive_web' 1. Navigate to a directory where your Apps Script project will live -1. Run `gapps init ` within your project directory. - * For example, `gapps init abc123-xyz098` +1. Run `gas init ` within your project directory. + * For example, `gas init abc123-xyz098` #### 3.2 A new Apps Script project @@ -85,8 +95,8 @@ This proces will create `gapps.config.json` and a sub-directory where all Apps S 1. Get your project ID from the address bar, located after `/d/` and before `/edit`. * For example, '//script.google.com/a/google.com/d/__abc123-xyz098__/edit?usp=drive_web' 1. Navigate to a directory where your Apps Script project will live -1. Run `gapps init ` within your project directory. - * For example, `gapps init abc123-xyz098` +1. Run `gas init ` within your project directory. + * For example, `gas init abc123-xyz098` ### 4. Develop locally @@ -95,21 +105,18 @@ Start scripting and enjoy total freedom of your local dev environment and source ### 5. Upload New Files -Run `gapps upload` from within your project directory. You should then be able to reload your Apps Script project in the browser and see the changes. - +Run `gas upload` from within your project directory. You should then be able to reload your Apps Script project in the browser and see the changes. -## Best Practices -Check out Matt Hessinger's blog post: [Advanced development process with apps](http://googledevelopers.blogspot.com/2015/12/advanced-development-process-with-apps.html) +Usage +----- -## Docs - -### gapps auth +### gas auth ``` - Usage: gapps auth [options] + Usage: gas auth [options] - Authorize gapps to use the Google Drive API + Authorize gas to use the Google Drive API Options: @@ -121,10 +128,10 @@ Check out Matt Hessinger's blog post: [Advanced development process with apps](h Performs the authentication flow described in the quickstart above. -### gapps init +### gas init ``` - Usage: gapps init|clone [options] + Usage: gas init|clone [options] Initialize project locally. The external Apps Script project must exist. @@ -134,12 +141,12 @@ Performs the authentication flow described in the quickstart above. -o, --overwrite ``` -Creates `gapps.config.json`, which contains information about your Apps Script project and downloads all project files into a subdirectory (default: `src/`) +Creates `gas-tools.json`, which contains information about your Apps Script project and downloads all project files into a subdirectory (default: `src/`) -### gapps upload +### gas upload ``` - Usage: gapps upload|push + Usage: gas upload|push Upload back to Google Drive. Run from root of project directory ``` @@ -147,29 +154,30 @@ Creates `gapps.config.json`, which contains information about your Apps Script p Upload the project to Google Drive. Sources files from `./src` or the configured subdirectory. -### gapps oauth-callback-url +### gas oauth-callback-url ``` - Usage: gapps deployment oauth-callback-url + Usage: gas oauth-callback-url Get the OAuth Callback URL for a project ``` Returns the OAuth Callback URL required by most 3rd-party OAuth services. -## Development + +Development +----------- Ensure that both before and after you make any changes that all the tests run by `npm test` pass. -If you want to use the command line tool in other projects, you can run -`npm link` from the root directory of the repo to symlink to your local -copy. If you already have another version installed, you can uninstall it -first with `npm uninstall -g *package_name*` +If you want to use a development version of the command line tool in other +projects, you can run `npm link` from the root directory of the repo to +symlink to your local copy. If you already have another version installed, +you can uninstall it first with `npm uninstall -g *package_name*` Please submit any bugs to the Issues page. Pull Requests also welcome. - ### Testing The test suite is still under construction, but consists of unit tests and @@ -187,7 +195,6 @@ build system will find these and execute them individually by running These tests should run without any special setup. - #### Functional Tests Functional tests are under the `t/` subdirectory. While these can load and @@ -196,19 +203,22 @@ this to run command-line programs, create and check files, access Google Drive via the REST API, and the like. To run them you will need to authenticate yourself to use the Google Drive -API using the `gapps auth` subcommand. +API using the `gas auth` subcommand. -## Limitations +Limitations +----------- -`gapps` allows you to nest files in folders, but the Apps Script platform expects a flat file structure. Because of this, **no files can have the same name, even if they are in separate directories**. One file will overwrite the other, making debugging difficult. +The upload/download tools allow you to nest files in folders, but the Apps Script platform expects a flat file structure. Because of this, **no files can have the same name, even if they are in separate directories**. One file will overwrite the other, making debugging difficult. Your add-on must be developed as a [standalone script](https://developers.google.com/apps-script/guides/standalone) and tested within Doc or Sheet. This means that it cannot use certain functions of [bound scripts](https://developers.google.com/apps-script/guides/bound), namely installable triggers. While testing within a Doc, you have access to the "Special methods" mentioned in [the docs](https://developers.google.com/apps-script/guides/bound), though. If you followed the quickstart above, you should be set up correctly. -## Common Issues + +Common Issues +------------- The Google Drive API frequently returns a *400* error without a helpful error message. Common causes for this are: @@ -224,10 +234,13 @@ The Google Drive API frequently returns a *400* error without a helpful error me - Verify that `~/.gapps` exists and has 4 values: `client_id`, `client_secret, `redirect_uri`, and `refresh_token` - To reset authentication, 'rm ~/.gapps' and then perform the authentication steps in part #2 of the quickstart again. -## Acknowledgements -Huge thanks to [Shrugs](https://github.com/Shrugs) for a massive PR that bumped this project to v1.0 +References +---------- + +The following documentation and articles may be useful in giving +background on this tool, similar tools, and how they can be used. -## Extra Resources +* [Google Apps Script Documentation](https://developers.google.com/apps-script/) +* Matt Hessinger, [Advanced development process with apps](http://googledevelopers.blogspot.com/2015/12/advanced-development-process-with-apps.html) -- [Google Apps Script Documentation](https://developers.google.com/apps-script/) diff --git a/bin/gapps b/bin/gas similarity index 96% rename from bin/gapps rename to bin/gas index 6c576e0..b8f72cd 100755 --- a/bin/gapps +++ b/bin/gas @@ -14,7 +14,7 @@ program .option('-b, --no-launch-browser', 'Do not use a local webserver to capture oauth code and instead require copy/paste') .option('-p, --port [port]', 'Port to use for webserver') - .description('Authorize gapps to use the Google Drive API') + .description('Authorize gas to use the Google Drive API') .action(function(clientSecretPath, options) { require(commands + '/auth')(clientSecretPath, options.launchBrowser) .then(function() { diff --git a/lib/defaults.js b/lib/defaults.js index bc46a29..332c486 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -1,7 +1,7 @@ module.exports = { DEFAULT_SUBDIR: 'src', STORAGE_FILE: getUserHome() + '/.gapps', - CONFIG_NAME: 'gapps.config.json', + CONFIG_NAME: 'gas-tools.json', WEBSERVER_PORT: 2386, DOWNLOAD_URL: 'https://script.google.com/feeds/download/export?format=json&id=' }; diff --git a/package.json b/package.json index 0c039e8..67860f1 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { - "name": "node-google-apps-script", - "version": "1.1.4", - "description": "The easiest way to develop Google Apps Script projects", + "name": "gas-tools", + "version": "2.0.0-dev.0", + "description": "Tools to help test and deploy Google Action Script projects", "scripts": { "test": "./node_modules/.bin/gulp test --silent" }, "repository": { "type": "git", - "url": "https://github.com/danthareja/node-google-apps-script.git" + "url": "https://github.com/cjs-cynic-net/gas-tools.git" }, "bin": { - "gapps": "./bin/gapps" + "gas": "./bin/gas" }, "main": "index", "preferGlobal": true, @@ -30,12 +30,21 @@ "push", "clone" ], - "author": "Dan Thareja (danthareja@gmail.com)", + "contributors": [ + { + "name": "Curt J. Sampson", + "email": "cjs@cynic.net" + }, + { + "name": "Nishant Rodrigues", + "email": "nishantjr@gmail.com" + } + ], "license": "MIT", "bugs": { - "url": "https://github.com/danthareja/node-google-apps-script/issues" + "url": "https://github.com/cjs-cynic-net/gas-tools/issues" }, - "homepage": "https://github.com/danthareja/node-google-apps-script", + "homepage": "https://github.com/cjs-cynic-net/gas-tools", "dependencies": { "bluebird": "^2.9.30", "colors": "^1.0.3", diff --git a/t/t010-basic-cli.js b/t/t010-basic-cli.js index 981c5a2..3d20faf 100644 --- a/t/t010-basic-cli.js +++ b/t/t010-basic-cli.js @@ -18,14 +18,14 @@ const tlib.test('help', (t) => { const expected = require('../package.json').version + '\n', - cmd = tlib.spawn(t, './bin/gapps -V') + cmd = tlib.spawn(t, './bin/gas -V') cmd.succeeds() cmd.stdout.match(expected) cmd.end() }) tlib.test('no command fails', (t) => { - const cmd = tlib.spawn(t, './bin/gapps') + const cmd = tlib.spawn(t, './bin/gas') cmd.stderr.match('No command specified\n') cmd.stdout.match(/Usage:/) if (tape_spawn_fixed) @@ -34,7 +34,7 @@ tlib.test('no command fails', (t) => { }) tlib.test('bad commands fail', (t) => { - const cmd = tlib.spawn(t, './bin/gapps rubbish') + const cmd = tlib.spawn(t, './bin/gas rubbish') cmd.stderr.match('Bad command specified\n') cmd.stdout.match(/Usage:/) if (tape_spawn_fixed) diff --git a/t/t020-init.js b/t/t020-init.js index d2389b3..d7c7f92 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -7,7 +7,7 @@ tlib.test('pwd', (t) => { const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg' const scratchDir = tlib.cleanScratchDir() - const cmd = tlib.spawn(t, '../../../bin/gapps init ' + docId, + const cmd = tlib.spawn(t, '../../../bin/gas init ' + docId, { cwd: scratchDir } ) cmd.stdout.match('') cmd.end() From 7f694f472d39ba8da4a934f1128d220e17bfa700 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sat, 30 Jan 2016 23:07:07 +0900 Subject: [PATCH 16/36] t/t020-init.js: Verify contents of downloaded script. --- t/t020-init.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index d7c7f92..dead33d 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -1,14 +1,24 @@ 'use strict' -const tlib = require('./tlib')(module) +const tlib = require('./tlib')(module), + bluebird = require('bluebird'), + fs = bluebird.promisifyAll(require('fs')) -tlib.test('pwd', (t) => { +tlib.test('gas init', (t) => { // GDrive publicly readable test file made available by . // (In the long run we should probably create our own test files.) const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg' const scratchDir = tlib.cleanScratchDir() - const cmd = tlib.spawn(t, '../../../bin/gas init ' + docId, - { cwd: scratchDir } ) + const cmd = bluebird.promisifyAll( + tlib.spawn(t, '../../../bin/gas init ' + docId, + { cwd: scratchDir })) + cmd.stdout.match('') - cmd.end() + + const firstLine = + '// GAScode.gs from node-google-apps-script Public Test GAS Project' + cmd.endAsync() + .then(() => fs.readFileAsync(tlib.scratchFile('src/GAScode.js'))) + .then((buffer) => + t.equal(buffer.toString('utf8').split('\n')[0], firstLine)) }) From 76565c88fe77b97b9f1d7c55ea5bd69b5649b46c Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sun, 31 Jan 2016 13:14:16 +0900 Subject: [PATCH 17/36] t020-init: Test that calling 'gas init --override' works after init is called. Use bluebird's promisify to allow use to call spawn a second time after the first process finished. --- t/t020-init.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index dead33d..545b2c7 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -11,14 +11,23 @@ tlib.test('gas init', (t) => { const scratchDir = tlib.cleanScratchDir() const cmd = bluebird.promisifyAll( tlib.spawn(t, '../../../bin/gas init ' + docId, - { cwd: scratchDir })) - + { cwd: scratchDir, end: false })) cmd.stdout.match('') const firstLine = '// GAScode.gs from node-google-apps-script Public Test GAS Project' cmd.endAsync() .then(() => fs.readFileAsync(tlib.scratchFile('src/GAScode.js'))) - .then((buffer) => - t.equal(buffer.toString('utf8').split('\n')[0], firstLine)) + .then((buffer) => buffer.toString('utf8').split('\n')[0] ) + .then((actualFirstLine) => t.equal(firstLine, actualFirstLine)) + .then(() => { + const cmd = bluebird.promisifyAll( + tlib.spawn(t, '../../../bin/gas init --overwrite' + docId, + { cwd: scratchDir, end: false })) + cmd.stdout.match('') + return cmd.endAsync() + }) + .then(t.end) + + }) From ebd394078f96ea4633a9775b7a00f7324c6f977a Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Sun, 31 Jan 2016 21:26:38 +0900 Subject: [PATCH 18/36] gulpfile: run jshint on `t/*` stuff and fix a couple of errors there. We don't use it yet on the other code because it doesn't conform to our style guidelines yet and thus produces a lot of errors. --- .jshintrc | 8 ++++++++ gulpfile.js | 11 ++++++++++- package.json | 2 ++ t/tlib.js | 4 +++- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .jshintrc diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..18fe76b --- /dev/null +++ b/.jshintrc @@ -0,0 +1,8 @@ +{ + "asi": true, + "esversion": 6, + "node": true, + "strict": "global", + "undef": false, + "varstmt": true +} diff --git a/gulpfile.js b/gulpfile.js index b037fa8..0622d7a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,16 @@ const tape = require('gulp-tape'), faucet = require('faucet') -gulp.task('test', ['unit-test', 'functional-test']) +gulp.task('test', ['jshint', 'unit-test', 'functional-test']) + +gulp.task('jshint', () => { + const jshint = require('gulp-jshint') + return gulp + .src('t/**/*.j[st]') + .pipe(exclude_gitignore()) + .pipe(jshint()) + .pipe(jshint.reporter('default')) +}) gulp.task('unit-test', () => { return gulp diff --git a/package.json b/package.json index 67860f1..4566985 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,9 @@ "faucet": "0.0.1", "gulp": "^3.9.0", "gulp-exclude-gitignore": "^1.0.0", + "gulp-jshint": "^2.0.0", "gulp-tape": "0.0.7", + "jshint": "^2.9.1", "rimraf": "^2.5.1", "tape": "^4.4.0", "tape-spawn": "^1.4.0" diff --git a/t/tlib.js b/t/tlib.js index bf6dd98..7a4b213 100644 --- a/t/tlib.js +++ b/t/tlib.js @@ -1,3 +1,5 @@ +'use strict' + const fs = require('fs'), path = require('path'), @@ -25,7 +27,7 @@ function scratchFile(test_module, p) { } function cleanScratchDir(test_module) { - scratchDir = scratchFile(test_module) + const scratchDir = scratchFile(test_module) rimraf.sync(scratchDir) mkdirp.sync(scratchDir) return scratchDir From 6575ac08372a4caaa3f5b87409978470edaaeb30 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sun, 31 Jan 2016 21:47:25 +0900 Subject: [PATCH 19/36] t020-init: No need to use promises, since tape runs tests synchronously. Tape (and at least some things that use it, such as tape-spawn) ensures that tests run one-at-a-time and in order. Thus, we don't need to use promises to enforce this; we can just put the tests in a particular file in dependency order. This actually isn't terribly pretty or comfortable, but it's the best we've got for now. --- t/t020-init.js | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index 545b2c7..65b86f8 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -1,33 +1,25 @@ 'use strict' const tlib = require('./tlib')(module), - bluebird = require('bluebird'), - fs = bluebird.promisifyAll(require('fs')) + fs = require('fs') -tlib.test('gas init', (t) => { +const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg', + scratchDir = tlib.cleanScratchDir() + +tlib.test('gas init', function(t) { // GDrive publicly readable test file made available by . // (In the long run we should probably create our own test files.) - const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg' - const scratchDir = tlib.cleanScratchDir() - const cmd = bluebird.promisifyAll( - tlib.spawn(t, '../../../bin/gas init ' + docId, - { cwd: scratchDir, end: false })) + const cmd = tlib.spawn(t, '../../../bin/gas init ' + docId, + { cwd: scratchDir }) cmd.stdout.match('') + cmd.end() +}) +tlib.test('check init content', t => { const firstLine = '// GAScode.gs from node-google-apps-script Public Test GAS Project' - cmd.endAsync() - .then(() => fs.readFileAsync(tlib.scratchFile('src/GAScode.js'))) - .then((buffer) => buffer.toString('utf8').split('\n')[0] ) - .then((actualFirstLine) => t.equal(firstLine, actualFirstLine)) - .then(() => { - const cmd = bluebird.promisifyAll( - tlib.spawn(t, '../../../bin/gas init --overwrite' + docId, - { cwd: scratchDir, end: false })) - cmd.stdout.match('') - return cmd.endAsync() - }) - .then(t.end) - - + const buffer = fs.readFileSync(tlib.scratchFile('src/GAScode.js')) + const actualFirstLine = buffer.toString('utf8').split('\n')[0] + t.equal(firstLine, actualFirstLine) + t.end() }) From 736acd83c9bdb2c69972a4e5ef434d0dd565992b Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Tue, 2 Feb 2016 11:18:13 +0900 Subject: [PATCH 20/36] package.json: move repo from github.com/cjs-cynic-net to cynic-net. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 4566985..4e35d5e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/cjs-cynic-net/gas-tools.git" + "url": "https://github.com/cynic-net/gas-tools.git" }, "bin": { "gas": "./bin/gas" @@ -42,9 +42,9 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/cjs-cynic-net/gas-tools/issues" + "url": "https://github.com/cynic-net/gas-tools/issues" }, - "homepage": "https://github.com/cjs-cynic-net/gas-tools", + "homepage": "https://github.com/cynic-net/gas-tools", "dependencies": { "bluebird": "^2.9.30", "colors": "^1.0.3", From e87f472a36ced1b314f1208ff273dec01dd76d99 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Thu, 4 Feb 2016 01:13:09 +0900 Subject: [PATCH 21/36] t020-init: extract spawnInScratchDir and move to tlib --- t/t020-init.js | 8 ++++---- t/tlib.js | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index 65b86f8..5f2718e 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -2,15 +2,15 @@ const tlib = require('./tlib')(module), fs = require('fs') -const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg', - scratchDir = tlib.cleanScratchDir() +const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg' + +tlib.cleanScratchDir() tlib.test('gas init', function(t) { // GDrive publicly readable test file made available by . // (In the long run we should probably create our own test files.) - const cmd = tlib.spawn(t, '../../../bin/gas init ' + docId, - { cwd: scratchDir }) + const cmd = tlib.spawnInScratchDir(t, '../../../bin/gas init ' + docId) cmd.stdout.match('') cmd.end() }) diff --git a/t/tlib.js b/t/tlib.js index 7a4b213..09fc96d 100644 --- a/t/tlib.js +++ b/t/tlib.js @@ -5,6 +5,7 @@ const path = require('path'), mkdirp = require('mkdirp'), rimraf = require('rimraf'), + spawn = require('tape-spawn'), _ = require('lodash') function testName(test_module) { @@ -33,12 +34,18 @@ function cleanScratchDir(test_module) { return scratchDir } +function spawnInScratchDir(test_module, t, command) { + return spawn(t, command, { cwd: scratchFile(test_module) }) +} + + module.exports = (test_module) => { return { test: require('tape'), - spawn: require('tape-spawn'), + spawn: spawn, testName: _.partial(testName, test_module), testData: _.partial(testData, test_module), readTestData: readTestData, scratchFile: _.partial(scratchFile, test_module), cleanScratchDir: _.partial(cleanScratchDir, test_module), + spawnInScratchDir: _.partial(spawnInScratchDir, test_module), }} From 5a0c92cefef40b009ce6887a0e391c4a4ed86644 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sun, 31 Jan 2016 22:48:41 +0900 Subject: [PATCH 22/36] t020-init: Move comment regarding test project to where projectId is declared. --- t/t020-init.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index 5f2718e..d705310 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -2,14 +2,13 @@ const tlib = require('./tlib')(module), fs = require('fs') +// GDrive publicly readable test file made available by . +// (In the long run we should probably create our own test files.) const docId = '1CodFWMEXI-5MfzNniEe8Uw8pSi82Iz0uU_jdbUvs2YpAIVmNqb-aH-Xg' tlib.cleanScratchDir() tlib.test('gas init', function(t) { - // GDrive publicly readable test file made available by . - // (In the long run we should probably create our own test files.) - const cmd = tlib.spawnInScratchDir(t, '../../../bin/gas init ' + docId) cmd.stdout.match('') cmd.end() From 83b32e5ae02e87e93c0ec5c0d9e132cf2f3fabf7 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Thu, 4 Feb 2016 01:43:18 +0900 Subject: [PATCH 23/36] t020-init: fix expected/actual reversal and slightly better names. --- t/t020-init.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index d705310..0994fcd 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -15,10 +15,10 @@ tlib.test('gas init', function(t) { }) tlib.test('check init content', t => { - const firstLine = + const expected = '// GAScode.gs from node-google-apps-script Public Test GAS Project' const buffer = fs.readFileSync(tlib.scratchFile('src/GAScode.js')) - const actualFirstLine = buffer.toString('utf8').split('\n')[0] - t.equal(firstLine, actualFirstLine) + const lineOne = buffer.toString('utf8').split('\n')[0] + t.equal(lineOne, expected) t.end() }) From 2b56401cef3af22663b7a0a14cf03979014de8e7 Mon Sep 17 00:00:00 2001 From: "Curt J. Sampson" Date: Thu, 4 Feb 2016 02:03:28 +0900 Subject: [PATCH 24/36] tlib: create a match(/.../) function and use it to simplify a test. --- t/t020-init.js | 7 ++----- t/tlib.js | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index 0994fcd..7f212fd 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -15,10 +15,7 @@ tlib.test('gas init', function(t) { }) tlib.test('check init content', t => { - const expected = - '// GAScode.gs from node-google-apps-script Public Test GAS Project' - const buffer = fs.readFileSync(tlib.scratchFile('src/GAScode.js')) - const lineOne = buffer.toString('utf8').split('\n')[0] - t.equal(lineOne, expected) + const actual = fs.readFileSync(tlib.scratchFile('src/GAScode.js'), 'utf8') + t.match(actual, / GAScode.gs /, 'Document contents') t.end() }) diff --git a/t/tlib.js b/t/tlib.js index 09fc96d..d2621ca 100644 --- a/t/tlib.js +++ b/t/tlib.js @@ -5,9 +5,22 @@ const path = require('path'), mkdirp = require('mkdirp'), rimraf = require('rimraf'), + tape = require('tape'), spawn = require('tape-spawn'), _ = require('lodash') + +tape.Test.prototype.match = function(actual, pattern, msg, extra) { + this._assert(actual.match(pattern), { + message: msg, + operator: 'match', + actual: actual, + expected: pattern, + extra: extra + }) +} + + function testName(test_module) { return path.basename(test_module.filename, '.js').split('-')[0] } @@ -40,7 +53,7 @@ function spawnInScratchDir(test_module, t, command) { module.exports = (test_module) => { return { - test: require('tape'), + test: tape, spawn: spawn, testName: _.partial(testName, test_module), testData: _.partial(testData, test_module), From 165b0d7ee37a2e303096c3d1baef417c4cff84e8 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Tue, 2 Feb 2016 01:52:07 +0900 Subject: [PATCH 25/36] t/: Remove tape_spawn_fixed since our pull request has been accepted. --- package.json | 2 +- t/t010-basic-cli.js | 19 ++----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 4e35d5e..5c83993 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,6 @@ "jshint": "^2.9.1", "rimraf": "^2.5.1", "tape": "^4.4.0", - "tape-spawn": "^1.4.0" + "tape-spawn": "^1.4.1" } } diff --git a/t/t010-basic-cli.js b/t/t010-basic-cli.js index 3d20faf..2d4bf4a 100644 --- a/t/t010-basic-cli.js +++ b/t/t010-basic-cli.js @@ -1,19 +1,6 @@ 'use strict' const tlib = require('./tlib')(module) -/* - * There's a bug in tape-spawn 1.4.0 that we trigger with a race condition: - * https://github.com/maxogden/tape-spawn/pull/12 - * The check for this bug that prevents cmd.fails() being called below when - * we are using a tape-spawn known to have this bug can be removed when a - * new version with the bugfix is released. Remember to update the required - * version of tape-spawn in package.json when you remove this code. - */ -const - semver = require('semver'), - tape_spawn_ver = require('tape-spawn/package.json').version, - tape_spawn_fixed = semver.gt(tape_spawn_ver, '1.4.0') - // Smoke test for the command line program. tlib.test('help', (t) => { const @@ -28,8 +15,7 @@ tlib.test('no command fails', (t) => { const cmd = tlib.spawn(t, './bin/gas') cmd.stderr.match('No command specified\n') cmd.stdout.match(/Usage:/) - if (tape_spawn_fixed) - cmd.fails() + cmd.fails() cmd.end() }) @@ -37,7 +23,6 @@ tlib.test('bad commands fail', (t) => { const cmd = tlib.spawn(t, './bin/gas rubbish') cmd.stderr.match('Bad command specified\n') cmd.stdout.match(/Usage:/) - if (tape_spawn_fixed) - cmd.fails() + cmd.fails() cmd.end() }) From 0936f3b3d63a1a2e1c49664b237caa5f5d98f14b Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Wed, 3 Feb 2016 23:33:53 +0900 Subject: [PATCH 26/36] Move writeExternalFile and copy to manifestor.js so download, init can share it --- lib/commands/download.js | 11 +---------- lib/commands/init.js | 11 +---------- lib/manifestor.js | 9 +++++++++ 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/lib/commands/download.js b/lib/commands/download.js index eb947a5..65443b4 100644 --- a/lib/commands/download.js +++ b/lib/commands/download.js @@ -25,7 +25,7 @@ module.exports = function download() { }) .map(function(file) { console.log('Writing ' + file.name + util.getFileExtension(file)) - return writeExternalFile(file, subdir) + return manifestor.writeExternalFile(file, subdir) }) .catch(function(err) { console.log('Error running download command'.red); @@ -33,12 +33,3 @@ module.exports = function download() { }); } -// XXX duplicated function from `command/init.js` -function writeExternalFile(file, dir) { - var filename = file.name + util.getFileExtension(file) - return fs.writeFileAsync(dir + '/' + filename, file.source) - .catch(function(err) { - console.log('Could not write file ' + filename); - throw err; - }) -} diff --git a/lib/commands/init.js b/lib/commands/init.js index a7bcc5d..659c98d 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -32,19 +32,10 @@ module.exports = function init(fileId, options) { return manifestor.getExternalFiles(fileId) }) .map(function(file) { - return writeExternalFile(file, subdir) + return manifestor.writeExternalFile(file, subdir) }) .catch(function(err) { console.log('Error running init command'.red); throw err; }); }; - -function writeExternalFile(file, dir) { - var filename = file.name + util.getFileExtension(file) - return fs.writeFileAsync(dir + '/' + filename, file.source) - .catch(function(err) { - console.log('Could not write file ' + filename); - throw err; - }) -} diff --git a/lib/manifestor.js b/lib/manifestor.js index 2577cf8..4c894de 100644 --- a/lib/manifestor.js +++ b/lib/manifestor.js @@ -65,6 +65,14 @@ function getExternalFiles(fileId) { }); } +function writeExternalFile(file, dir) { + var filename = file.name + util.getFileExtension(file) + return fs.writeFileAsync(dir + '/' + filename, file.source) + .catch(function(err) { + console.log('Could not write file ' + filename); + throw err; + }) +} function getProjectFiles(fileId, auth) { var options = { @@ -128,4 +136,5 @@ module.exports.build = build; module.exports.get = getConfig; module.exports.set = setConfig; module.exports.getExternalFiles = getExternalFiles; +module.exports.writeExternalFile = writeExternalFile; module.exports.throwIfConfig = throwIfConfig; From f9ad489a50980469d66f1ef9731591c825bfa6bb Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sat, 6 Feb 2016 23:30:14 +0800 Subject: [PATCH 27/36] Remove unused `require('lodash')`s. --- bin/gas | 1 - lib/commands/upload.js | 1 - 2 files changed, 2 deletions(-) diff --git a/bin/gas b/bin/gas index b8f72cd..a382574 100755 --- a/bin/gas +++ b/bin/gas @@ -1,5 +1,4 @@ #!/usr/bin/env node -var _ = require('lodash'); var path = require('path'); var program = require('commander'); var pkg = require('../package.json'); diff --git a/lib/commands/upload.js b/lib/commands/upload.js index 21c53b5..51f5056 100755 --- a/lib/commands/upload.js +++ b/lib/commands/upload.js @@ -1,4 +1,3 @@ -var _ = require('lodash'); var colors = require('colors'); var google = require('googleapis'); var Promise = require('bluebird'); From f326a59166e4c2bd0fae9b3dc53aa2311aaa117b Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Tue, 9 Feb 2016 18:34:32 +0800 Subject: [PATCH 28/36] Remove the oauth-callback-url and 'key' parameter. I'm not sure what this is used for exactly. It's clearly not that important considering that the official documention for it is out of date. It seems simple enough to put back in if need be. --- README.md | 12 ------------ bin/gas | 6 ------ lib/commands/init.js | 1 - lib/commands/oauthCallbackUrl.js | 13 ------------- 4 files changed, 32 deletions(-) delete mode 100644 lib/commands/oauthCallbackUrl.js diff --git a/README.md b/README.md index ca6d7a9..7205804 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,6 @@ Performs the authentication flow described in the quickstart above. Initialize project locally. The external Apps Script project must exist. Options: - -k, --key [key] -s, --subdir [subdir] -o, --overwrite ``` @@ -154,17 +153,6 @@ Creates `gas-tools.json`, which contains information about your Apps Script proj Upload the project to Google Drive. Sources files from `./src` or the configured subdirectory. -### gas oauth-callback-url - -``` - Usage: gas oauth-callback-url - - Get the OAuth Callback URL for a project -``` - -Returns the OAuth Callback URL required by most 3rd-party OAuth services. - - Development ----------- diff --git a/bin/gas b/bin/gas index a382574..ed1455f 100755 --- a/bin/gas +++ b/bin/gas @@ -29,7 +29,6 @@ program program .command('init ') - .option('-k, --key [key]') .option('-s, --subdir [subdir]') .option('-o, --overwrite') .description('Initialize project. The external Apps Script project must exist.') @@ -41,11 +40,6 @@ program .description('Download copy of current code for Apps Script project.') .action(require(commands + '/download')) -program - .command('oauth-callback-url') - .description('Get the OAuth Callback URL for a project') - .action(require(commands + '/oauthCallbackUrl')); - program.parse(process.argv) var err diff --git a/lib/commands/init.js b/lib/commands/init.js index 659c98d..271bd29 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -14,7 +14,6 @@ module.exports = function init(fileId, options) { var config = { path: subdir, fileId: fileId, - key: options.key }; var overwritePromise = options.overwrite ? diff --git a/lib/commands/oauthCallbackUrl.js b/lib/commands/oauthCallbackUrl.js deleted file mode 100644 index 2d48a8d..0000000 --- a/lib/commands/oauthCallbackUrl.js +++ /dev/null @@ -1,13 +0,0 @@ -var defaults = require('../defaults'); -var manifestor = require('../manifestor'); - -module.exports = function() { - return manifestor.get() - .then(function(config) { - if (config.key) { - console.log('https://script.google.com/macros/d/' + config.key + '/usercallback'); - } else { - console.log('No Project Key provided in ' + defaults.CONFIG_NAME); - } - }); -}; From 50606cff479b81881fd5a56ef37196fe9fbbf929 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sat, 6 Feb 2016 23:38:09 +0800 Subject: [PATCH 29/36] t020-init: check that init exits with correct exit code, no stderr output. --- t/t020-init.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/t020-init.js b/t/t020-init.js index 7f212fd..2f7b675 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -11,6 +11,8 @@ tlib.cleanScratchDir() tlib.test('gas init', function(t) { const cmd = tlib.spawnInScratchDir(t, '../../../bin/gas init ' + docId) cmd.stdout.match('') + cmd.stderr.match('') + cmd.succeeds() cmd.end() }) From 08ff86bf3283b5f807d4672927cdec63d97b01e3 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Sat, 6 Feb 2016 23:50:57 +0800 Subject: [PATCH 30/36] checkProjectContent: Extract from annonymous lambda. --- t/t020-init.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/t020-init.js b/t/t020-init.js index 2f7b675..249f8d2 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -13,11 +13,11 @@ tlib.test('gas init', function(t) { cmd.stdout.match('') cmd.stderr.match('') cmd.succeeds() - cmd.end() + cmd.end(() => checkProjectContent(t)) }) -tlib.test('check init content', t => { + +function checkProjectContent(t) { const actual = fs.readFileSync(tlib.scratchFile('src/GAScode.js'), 'utf8') t.match(actual, / GAScode.gs /, 'Document contents') - t.end() -}) +} From 3ca9814c5f24117e5655bd974b750965d2a3e8cc Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Tue, 9 Feb 2016 19:54:57 +0800 Subject: [PATCH 31/36] Extract code common to download and init. --- bin/gas | 2 +- lib/commands/download.js | 36 ++++++++++++++++++------------------ lib/commands/init.js | 12 +++--------- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/bin/gas b/bin/gas index ed1455f..f08c9df 100755 --- a/bin/gas +++ b/bin/gas @@ -38,7 +38,7 @@ program program .command('download') .description('Download copy of current code for Apps Script project.') - .action(require(commands + '/download')) + .action(require(commands + '/download').command) program.parse(process.argv) diff --git a/lib/commands/download.js b/lib/commands/download.js index 65443b4..3d5260a 100644 --- a/lib/commands/download.js +++ b/lib/commands/download.js @@ -1,35 +1,35 @@ 'use strict' -const mkdirp = require('mkdirp'); const Promise = require('bluebird'); +const mkdirpAsync = Promise.promisify(require('mkdirp')); const fs = Promise.promisifyAll(require('fs')); const util = require('../util') const manifestor = require('../manifestor'); -module.exports = function download() { - console.log('Downloading from Google Drive...') +function download(config) { + return mkdirpAsync(config.path) + .then(function() { + return manifestor.getExternalFiles(config.fileId) + }) + .map(function(file) { + return manifestor.writeExternalFile(file, config.path) + }) +} - var subdir, fileId; +function command() { + console.log('Downloading from Google Drive...') return manifestor.get() - .then(function(config) { - subdir = config.path - fileId = config.fileId - }) - .then(function() { - return mkdirp(subdir); - }) - .then(function() { - return manifestor.getExternalFiles(fileId) - }) - .map(function(file) { - console.log('Writing ' + file.name + util.getFileExtension(file)) - return manifestor.writeExternalFile(file, subdir) - }) + .then(download) .catch(function(err) { console.log('Error running download command'.red); throw err; }); } +module.exports = { + command: command, + factory: download, +} + diff --git a/lib/commands/init.js b/lib/commands/init.js index 271bd29..1b47165 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -7,6 +7,7 @@ var fs = Promise.promisifyAll(require('fs')); var util = require('../util') var defaults = require('../defaults'); var manifestor = require('../manifestor'); +var download = require('./download'); module.exports = function init(fileId, options) { var subdir = options.subdir || defaults.DEFAULT_SUBDIR; @@ -24,15 +25,8 @@ module.exports = function init(fileId, options) { .then(function() { return manifestor.set(config); }) - .then(function() { - return mkdirp(subdir); - }) - .then(function(config) { - return manifestor.getExternalFiles(fileId) - }) - .map(function(file) { - return manifestor.writeExternalFile(file, subdir) - }) + .return(config) + .then(download.factory) .catch(function(err) { console.log('Error running init command'.red); throw err; From be317779907fa46a28f432c52035283c8a6c39d3 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Mon, 8 Feb 2016 18:32:26 +0800 Subject: [PATCH 32/36] 'Config' class for manipulating, retrieving, saving data from/to the config file --- lib/config.js | 41 +++++++++++++++++++++++++++++++++++++++++ lib/config.jt | 20 ++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 lib/config.js create mode 100644 lib/config.jt diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..3c5dc22 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,41 @@ +'use strict' + +var Promise = require('bluebird'); +var fs = Promise.promisifyAll(require('fs')); +var defaults = require('./defaults'); + +function Config(object) { + if (object) this._data = object +} + +Config.prototype.addProject = function(fileId, path) { + if (this._data) + throw new Error("Configuration already exists") + this._data = { fileId: fileId, path: path } +} + +Config.prototype.getProject = function() { + return this._data +} + +Config.read = function() { + return fs.readFileAsync(defaults.CONFIG_NAME) + .then(JSON.parse) + .catch(SyntaxError, function(err) { + console.log('Error parsing config'.red); + throw err; + }) + .catch(Error, function(err) { + if (err.code !== 'ENOENT') throw err; + return undefined; + }) + .then((obj) => new Config(obj)) +} + +Config.prototype.write = function() { + return fs.writeFileSync( + defaults.CONFIG_NAME, JSON.stringify(this._data)) +} + + +module.exports = Config diff --git a/lib/config.jt b/lib/config.jt new file mode 100644 index 0000000..f9483dd --- /dev/null +++ b/lib/config.jt @@ -0,0 +1,20 @@ +'use strict' +const test = require('tape'), + Config = require('./config.js') + +test('config', t => { + const config = new Config() + config.addProject('fileIdXXX1234', 'path/to/project') + t.equal(config.getProject().fileId, 'fileIdXXX1234') + t.equal(config.getProject().path, 'path/to/project') + t.end() +}) + +test('config doesnt allow multiple projects', t => { + const config = new Config() + config.addProject('fileIdXXX1234', 'path/to/project') + t.throws( + () => config.addProject('1234', 'other/path'), + /.*already exists/) + t.end() +}) From 4c0db45860ba557866ec5cb4f7ce78ddc2bb26c6 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Tue, 9 Feb 2016 20:26:39 +0800 Subject: [PATCH 33/36] config: Async operations return `this` to simplify chaining `then`s. --- lib/config.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/config.js b/lib/config.js index 3c5dc22..998b8f6 100644 --- a/lib/config.js +++ b/lib/config.js @@ -12,6 +12,7 @@ Config.prototype.addProject = function(fileId, path) { if (this._data) throw new Error("Configuration already exists") this._data = { fileId: fileId, path: path } + return this } Config.prototype.getProject = function() { @@ -33,8 +34,8 @@ Config.read = function() { } Config.prototype.write = function() { - return fs.writeFileSync( - defaults.CONFIG_NAME, JSON.stringify(this._data)) + return fs.writeFileAsync(defaults.CONFIG_NAME, JSON.stringify(this._data)) + .then(() => this) } From a952baa6b59ea0785eed4d787ba2e759a123eba6 Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Tue, 9 Feb 2016 20:36:24 +0800 Subject: [PATCH 34/36] Add basic functional test for download. The only additional things this is checking is the parsing of arguments and how it passes the config to the promises. I don't know enough about promises and mocking in JS to be able to write a unit test for this. --- t/t020-init.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/t020-init.js b/t/t020-init.js index 249f8d2..b49e114 100644 --- a/t/t020-init.js +++ b/t/t020-init.js @@ -16,6 +16,14 @@ tlib.test('gas init', function(t) { cmd.end(() => checkProjectContent(t)) }) +tlib.test('gas download', function(t) { + const cmd = tlib.spawnInScratchDir(t, '../../../bin/gas download') + cmd.stdout.match(/Downloading/) + cmd.stderr.match('') + cmd.succeeds() + cmd.end(() => checkProjectContent(t)) +}) + function checkProjectContent(t) { const actual = fs.readFileSync(tlib.scratchFile('src/GAScode.js'), 'utf8') From 2b0d39cb9dbad144769e90b035fbd19680914f6f Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Tue, 9 Feb 2016 20:38:59 +0800 Subject: [PATCH 35/36] Move download and init to the new config API I'm afraid to touch init, because it's untested and I don't understand the manifest code. --- lib/commands/download.js | 4 +++- lib/commands/init.js | 19 +++++-------------- lib/manifestor.js | 20 -------------------- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/lib/commands/download.js b/lib/commands/download.js index 3d5260a..24e6541 100644 --- a/lib/commands/download.js +++ b/lib/commands/download.js @@ -6,6 +6,7 @@ const fs = Promise.promisifyAll(require('fs')); const util = require('../util') const manifestor = require('../manifestor'); +const Config = require('../config'); function download(config) { return mkdirpAsync(config.path) @@ -20,7 +21,8 @@ function download(config) { function command() { console.log('Downloading from Google Drive...') - return manifestor.get() + return Config.read() + .then(function(config) { return config.getProject() }) .then(download) .catch(function(err) { console.log('Error running download command'.red); diff --git a/lib/commands/init.js b/lib/commands/init.js index 1b47165..8497045 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -8,24 +8,15 @@ var util = require('../util') var defaults = require('../defaults'); var manifestor = require('../manifestor'); var download = require('./download'); +var Config = require('../config'); module.exports = function init(fileId, options) { var subdir = options.subdir || defaults.DEFAULT_SUBDIR; - var config = { - path: subdir, - fileId: fileId, - }; - - var overwritePromise = options.overwrite ? - Promise.resolve() : - manifestor.throwIfConfig(); - - return overwritePromise - .then(function() { - return manifestor.set(config); - }) - .return(config) + return Config.read() + .then(function(config) { return config.addProject(fileId, subdir) }) + .then(function(config) { return config.write() }) + .then(function(config) { return config.getProject() }) .then(download.factory) .catch(function(err) { console.log('Error running init command'.red); diff --git a/lib/manifestor.js b/lib/manifestor.js index 4c894de..2336207 100644 --- a/lib/manifestor.js +++ b/lib/manifestor.js @@ -101,17 +101,6 @@ function getProjectFiles(fileId, auth) { }); } -function throwIfConfig() { - return fs.readFileAsync(defaults.CONFIG_NAME) - .then(JSON.parse) - .then(function() { - throw 'Config already exists. Cowardly refusing to overwrite.'; - }) - .error(function() { - // swallow error - }); -} - function getConfig() { return fs.readFileAsync(defaults.CONFIG_NAME) .then(JSON.parse) @@ -125,16 +114,7 @@ function getConfig() { }); } -function setConfig(config) { - return fs.writeFileAsync(defaults.CONFIG_NAME, JSON.stringify(config, "", 2)) - .then(function() { - return config; - }); -} - module.exports.build = build; module.exports.get = getConfig; -module.exports.set = setConfig; module.exports.getExternalFiles = getExternalFiles; module.exports.writeExternalFile = writeExternalFile; -module.exports.throwIfConfig = throwIfConfig; From 47156887a844a084460ec1baa2b786b14f1fb7fa Mon Sep 17 00:00:00 2001 From: Nishant Rodrigues Date: Wed, 10 Feb 2016 21:10:17 +0800 Subject: [PATCH 36/36] getProjectFiles: Use Google's API instead of sending REST request ourselves * Removes the need to hard code the REST API path * Gives better error messages since errors are thrown as JSON objects, instead of HTML that needs to be parsed. --- lib/defaults.js | 1 - lib/manifestor.js | 22 ++++++++++------------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/defaults.js b/lib/defaults.js index 332c486..8c51d38 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -3,7 +3,6 @@ module.exports = { STORAGE_FILE: getUserHome() + '/.gapps', CONFIG_NAME: 'gas-tools.json', WEBSERVER_PORT: 2386, - DOWNLOAD_URL: 'https://script.google.com/feeds/download/export?format=json&id=' }; function getUserHome() { diff --git a/lib/manifestor.js b/lib/manifestor.js index 2336207..0567794 100644 --- a/lib/manifestor.js +++ b/lib/manifestor.js @@ -75,19 +75,17 @@ function writeExternalFile(file, dir) { } function getProjectFiles(fileId, auth) { + var google = require('googleapis'); + var drive = google.drive({ version: 'v3', auth: auth }); var options = { - url: defaults.DOWNLOAD_URL + fileId, - qs : { - 'access_token': auth.credentials.access_token - } - }; - - return request.getAsync(options) - .spread(function(res, body) { - return JSON.parse(body); - }) - .then(function(project) { - if (!project.files) { + 'fileId': fileId, + 'mimeType': 'application/vnd.google-apps.script+json' + } + return Promise.promisify(drive.files.export)(options) + .then(function(args) { + const body = args[0] + const response = args[1] + if (!body.files) { throw 'Looks like there are no files associated with this project. Check the id and try again.'; } return project.files;