Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "download" command. #28

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3392302
Add "download" command.
0cjs Jan 6, 2016
f6ffa8d
.gitignore: ignore `.build/`, `tmp/` and syntax etc. fixes
0cjs Jan 23, 2016
844a017
Add a unit test framework and a simple test that the module loads pro…
0cjs Jan 23, 2016
f2744f6
README: Add some documentation about the unit tests.
0cjs Jan 24, 2016
4860c05
Don't look for unit tests in dot-files and gitignore files/dirs.
0cjs Jan 24, 2016
f563309
Start a basic functional test framework.
0cjs Jan 24, 2016
43a4f5b
gulp: Add task for functional tests.
nishantjr Jan 24, 2016
c368678
gapps: Fail when a bad command is passed on the CLI.
nishantjr Jan 24, 2016
2bf9c00
t: Work around a bug in tape-spawn 1.4.0.
0cjs Jan 24, 2016
896a036
bin/gapps: Print usage on argument error.
nishantjr Jan 24, 2016
df4632a
Log error messages to stderr instead of stdout.
nishantjr Jan 24, 2016
b8620f0
t/tlib: extract testName function.
0cjs Jan 24, 2016
59e0038
t/tlib: add scratchFile() function to give us scratch workspace for t…
nishantjr Jan 24, 2016
a09edfd
'gapps init' command line test and README updates re testing.
nishantjr Jan 30, 2016
d4ea6a1
Rename project to gas-tools.
0cjs Jan 30, 2016
7f694f4
t/t020-init.js: Verify contents of downloaded script.
nishantjr Jan 30, 2016
76565c8
t020-init: Test that calling 'gas init --override' works after init i…
nishantjr Jan 31, 2016
ebd3940
gulpfile: run jshint on `t/*` stuff and fix a couple of errors there.
0cjs Jan 31, 2016
6575ac0
t020-init: No need to use promises, since tape runs tests synchronously.
nishantjr Jan 31, 2016
736acd8
package.json: move repo from github.com/cjs-cynic-net to cynic-net.
0cjs Feb 2, 2016
e87f472
t020-init: extract spawnInScratchDir and move to tlib
nishantjr Feb 3, 2016
5a0c92c
t020-init: Move comment regarding test project to where projectId is …
nishantjr Jan 31, 2016
83b32e5
t020-init: fix expected/actual reversal and slightly better names.
0cjs Feb 3, 2016
2b56401
tlib: create a match(/.../) function and use it to simplify a test.
0cjs Feb 3, 2016
165b0d7
t/: Remove tape_spawn_fixed since our pull request has been accepted.
nishantjr Feb 1, 2016
0936f3b
Move writeExternalFile and copy to manifestor.js so download, init ca…
nishantjr Feb 3, 2016
f9ad489
Remove unused `require('lodash')`s.
nishantjr Feb 6, 2016
f326a59
Remove the oauth-callback-url and 'key' parameter.
nishantjr Feb 9, 2016
50606cf
t020-init: check that init exits with correct exit code, no stderr ou…
nishantjr Feb 6, 2016
08ff86b
checkProjectContent: Extract from annonymous lambda.
nishantjr Feb 6, 2016
3ca9814
Extract code common to download and init.
nishantjr Feb 9, 2016
be31777
'Config' class for manipulating, retrieving, saving data from/to the …
nishantjr Feb 8, 2016
4c0db45
config: Async operations return `this` to simplify chaining `then`s.
nishantjr Feb 9, 2016
a952baa
Add basic functional test for download.
nishantjr Feb 9, 2016
2b0d39c
Move download and init to the new config API
nishantjr Feb 9, 2016
4715688
getProjectFiles: Use Google's API instead of sending REST request our…
nishantjr Feb 10, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_STORE
node_modules
*.log
/.build/
.DS_Store/
/node_modules/
/tmp/
8 changes: 8 additions & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"asi": true,
"esversion": 6,
"node": true,
"strict": "global",
"undef": false,
"varstmt": true
}
130 changes: 84 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <danthareja@gmail.com>
and Matt Condon <mattcondon@google.com>.

[node-google-apps-script]: https://github.com/danthareja/node-google-apps-script

Quickstart
----------

### 1. Get Google Drive Credentials

Expand Down Expand Up @@ -53,29 +63,29 @@ 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.

You can pass the option `--no-launch-browser` to generate a url that will give you a code to paste back into the console. This is useful if you're using ssh to develop.

### 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

1. Navigate to your Apps Script project from Google Drive (must be a [standalone script](https://developers.google.com/apps-script/guides/standalone))
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 <fileId>` within your project directory.
* For example, `gapps init abc123-xyz098`
1. Run `gas init <fileId>` within your project directory.
* For example, `gas init abc123-xyz098`

#### 3.2 A new Apps Script project

Expand All @@ -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 <fileId>` within your project directory.
* For example, `gapps init abc123-xyz098`
1. Run `gas init <fileId>` within your project directory.
* For example, `gas init abc123-xyz098`


### 4. Develop locally
Expand All @@ -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] <path/to/client/secret.json>
Usage: gas auth [options] <path/to/client/secret.json>

Authorize gapps to use the Google Drive API
Authorize gas to use the Google Drive API

Options:

Expand All @@ -121,57 +128,85 @@ 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 <fileId> [options]
Usage: gas init|clone <fileId> [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
```

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:

Expand All @@ -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/)
34 changes: 20 additions & 14 deletions bin/gapps → bin/gas
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env node
var _ = require('lodash');
var path = require('path');
var program = require('commander');
var pkg = require('../package.json');
Expand All @@ -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() {
Expand All @@ -30,23 +29,30 @@ program

program
.command('init <fileId>')
.option('-k, --key [key]')
.option('-s, --subdir [subdir]')
.option('-o, --overwrite')
.description('Initialize project. The external Apps Script project must exist.')
.alias('clone')
.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)
}
31 changes: 31 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -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() }))
})
11 changes: 11 additions & 0 deletions index.jt
Original file line number Diff line number Diff line change
@@ -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()
})

37 changes: 37 additions & 0 deletions lib/commands/download.js
Original file line number Diff line number Diff line change
@@ -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,
}

Loading