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/ 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/README.md b/README.md index 80f7396..7205804 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,25 +128,24 @@ 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. Options: - -k, --key [key] -s, --subdir [subdir] -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,31 +153,60 @@ 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 +Development +----------- -``` - Usage: gapps deployment oauth-callback-url +Ensure that both before and after you make any changes that all the +tests run by `npm test` pass. - Get the OAuth Callback URL for a project -``` - -Returns the OAuth Callback URL required by most 3rd-party OAuth services. +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*` -## Development 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 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 -## Limitations +#### Unit Tests -`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. +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 `gas auth` subcommand. + + +Limitations +----------- + +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: @@ -187,10 +222,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 66% rename from bin/gapps rename to bin/gas index d340154..f08c9df 100755 --- a/bin/gapps +++ 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'); @@ -14,7 +13,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() { @@ -30,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.') @@ -38,15 +36,23 @@ program .action(require(commands + '/init')); program - .command('oauth-callback-url') - .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); + .command('download') + .description('Download copy of current code for Apps Script project.') + .action(require(commands + '/download').command) + +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) { + var p = require('process') + console.error(err) + program.outputHelp() + p.exit(2) } diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..0622d7a --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,31 @@ +'use strict' + +const + gulp = require('gulp'), + exclude_gitignore = require('gulp-exclude-gitignore'), + tape = require('gulp-tape'), + faucet = require('faucet') + +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 + .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() })) +}) 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/lib/commands/download.js b/lib/commands/download.js new file mode 100644 index 0000000..24e6541 --- /dev/null +++ b/lib/commands/download.js @@ -0,0 +1,37 @@ +'use strict' + +const Promise = require('bluebird'); +const mkdirpAsync = Promise.promisify(require('mkdirp')); +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) + .then(function() { + return manifestor.getExternalFiles(config.fileId) + }) + .map(function(file) { + return manifestor.writeExternalFile(file, config.path) + }) +} + +function command() { + console.log('Downloading from Google Drive...') + + return Config.read() + .then(function(config) { return config.getProject() }) + .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 a7bcc5d..8497045 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -7,44 +7,19 @@ var fs = Promise.promisifyAll(require('fs')); 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, - key: options.key - }; - - var overwritePromise = options.overwrite ? - Promise.resolve() : - manifestor.throwIfConfig(); - - return overwritePromise - .then(function() { - return manifestor.set(config); - }) - .then(function() { - return mkdirp(subdir); - }) - .then(function(config) { - return manifestor.getExternalFiles(fileId) - }) - .map(function(file) { - return writeExternalFile(file, subdir) - }) + 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); 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/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); - } - }); -}; 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'); diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..998b8f6 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,42 @@ +'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 } + return this +} + +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.writeFileAsync(defaults.CONFIG_NAME, JSON.stringify(this._data)) + .then(() => this) +} + + +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() +}) diff --git a/lib/defaults.js b/lib/defaults.js index bc46a29..8c51d38 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -1,9 +1,8 @@ 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=' }; function getUserHome() { diff --git a/lib/manifestor.js b/lib/manifestor.js index 2577cf8..0567794 100644 --- a/lib/manifestor.js +++ b/lib/manifestor.js @@ -65,21 +65,27 @@ 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 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; @@ -93,17 +99,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) @@ -117,15 +112,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.throwIfConfig = throwIfConfig; +module.exports.writeExternalFile = writeExternalFile; diff --git a/package.json b/package.json index a4bc013..5c83993 100644 --- a/package.json +++ b/package.json @@ -1,13 +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/cynic-net/gas-tools.git" }, "bin": { - "gapps": "./bin/gapps" + "gas": "./bin/gas" }, "main": "index", "preferGlobal": true, @@ -27,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/cynic-net/gas-tools/issues" }, - "homepage": "https://github.com/danthareja/node-google-apps-script", + "homepage": "https://github.com/cynic-net/gas-tools", "dependencies": { "bluebird": "^2.9.30", "colors": "^1.0.3", @@ -43,7 +55,15 @@ "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-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.1" } } diff --git a/t/t001-tlib.js b/t/t001-tlib.js new file mode 100644 index 0000000..dc3c66f --- /dev/null +++ b/t/t001-tlib.js @@ -0,0 +1,22 @@ +'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'))) + 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/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-basic-cli.js b/t/t010-basic-cli.js new file mode 100644 index 0000000..2d4bf4a --- /dev/null +++ b/t/t010-basic-cli.js @@ -0,0 +1,28 @@ +'use strict' +const tlib = require('./tlib')(module) + +// Smoke test for the command line program. +tlib.test('help', (t) => { + const + expected = require('../package.json').version + '\n', + 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/gas') + cmd.stderr.match('No command specified\n') + cmd.stdout.match(/Usage:/) + cmd.fails() + cmd.end() +}) + +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:/) + cmd.fails() + cmd.end() +}) diff --git a/t/t020-init.js b/t/t020-init.js new file mode 100644 index 0000000..b49e114 --- /dev/null +++ b/t/t020-init.js @@ -0,0 +1,31 @@ +'use strict' +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) { + const cmd = tlib.spawnInScratchDir(t, '../../../bin/gas init ' + docId) + cmd.stdout.match('') + cmd.stderr.match('') + cmd.succeeds() + 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') + t.match(actual, / GAScode.gs /, 'Document contents') +} diff --git a/t/tlib.js b/t/tlib.js new file mode 100644 index 0000000..d2621ca --- /dev/null +++ b/t/tlib.js @@ -0,0 +1,64 @@ +'use strict' + +const + fs = require('fs'), + 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] +} + +function testData(test_module, p) { + return path.join( + path.dirname(test_module.filename), testName(test_module), p) +} + +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) +} + +function cleanScratchDir(test_module) { + const scratchDir = scratchFile(test_module) + rimraf.sync(scratchDir) + mkdirp.sync(scratchDir) + return scratchDir +} + +function spawnInScratchDir(test_module, t, command) { + return spawn(t, command, { cwd: scratchFile(test_module) }) +} + + +module.exports = (test_module) => { return { + test: tape, + 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), +}}