Skip to content

Commit 6155a0c

Browse files
Merge pull request #66 from MaartenDesnouck/next-version
Next version
2 parents 26de260 + 1528ca6 commit 6155a0c

File tree

13 files changed

+309
-189
lines changed

13 files changed

+309
-189
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ $ npm i -g google-apps-script
1616

1717
# Usage
1818

19-
Authenticate the Drive API (add '-f' to force reauthentication):
19+
Authenticate the Drive API:
20+
(Add '-f' to force reauthentication)
2021

2122
```
2223
$ gas auth [-f]
@@ -30,24 +31,25 @@ $ gas delete <projectName|projectId>
3031
$ gas rename <projectName|projectId> <newProjectName>
3132
```
3233

33-
List your remote projects and their iId's (optional filter on projectName):
34+
List your remote projects and their id's (optional filter on projectName):
3435

3536
```
3637
$ gas list [filter]
3738
```
3839

39-
Link a remote project to your current directory:
40+
Link a remote project to your current working directory:
4041

4142
```
4243
$ gas link <projectName|projectId>
4344
```
4445

45-
Pull and push code from/to your remote project:
46-
(We map files in local folders to their full path name in a project and the other way around)
46+
Pull and push code from/to your remote project:
47+
(Files in local folders are mapped to their full path name in a project and the other way around)
48+
(You can specify to pull or push only a single file by adding a filename to the command)
4749

4850
```
49-
$ gas pull
50-
$ gas push
51+
$ gas pull [fileName]
52+
$ gas push [fileName]
5153
```
5254

5355
Some shortcuts for creating, linking and pulling projects all in one:

bin/gas

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ program
2121

2222
program
2323
.command('init <projectName>')
24-
.alias('new' )
24+
.alias('new')
2525
.description('create a new Google Apps Script project in your Google Drive and then clone that project locally')
2626
.action(require(lib + '/init'));
2727

@@ -52,14 +52,14 @@ program
5252
.action(require(lib + '/clone'));
5353

5454
program
55-
.command('push')
55+
.command('push [fileName]')
5656
.alias('deploy')
57-
.description('push your local code to the linked project on your Google Drive')
57+
.description('push your local code to the linked folder on your Google Drive (add a fileName to only pull that file)')
5858
.action(require(lib + '/push'));
5959

6060
program
61-
.command('pull')
62-
.option('-i, --include', 'Pull included files from their sources')
61+
.command('pull [fileName]')
62+
.option('-i, --include', 'Pull included files from their sources (add a fileName to only pull that file)')
6363
.description('pull code from your Google Drive, (add \'-i\' to also pull included files)')
6464
.action(require(lib + '/pull'));
6565

@@ -86,12 +86,12 @@ program
8686

8787
program
8888
.command('*')
89-
.action(function(argv) {
89+
.action(function (argv) {
9090
console.log('\'gas %s\' is not a valid command use \'gas -h\' for help', argv);
9191
process.exit(2);
9292
});
9393

94-
program.on('--help', function() {
94+
program.on('--help', function () {
9595
console.log('');
9696
console.log(' Examples:');
9797
console.log('');

lib/clone.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ module.exports = (identifier) => {
6060
});
6161

6262
const unpacked = Promise.all([downloaded, gotMetadata, ]).then((values) => {
63-
return unpackRemote(path.join(process.cwd(), values[1].name));
63+
return unpackRemote(path.join(process.cwd(), values[1].name), null);
6464
});
6565

6666
unpacked.then(() => {

lib/functions/downloadRemote.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function downloadRemote(auth, projectId, dir, method) {
5353
} else {
5454
// Deleting id's from remote
5555
for (const file of content.files) {
56-
delete file.id;
56+
Reflect.deleteProperty(file,'id');
5757
}
5858
createFile({
5959
name: remote,

lib/functions/packLocal.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function getFileJSON(rootFolder, file, fileName, extension) {
2828
return;
2929
}
3030

31-
const type = extension === '.js' ? 'server_js' : 'html';
31+
const type = extension === '.html' ? 'html' : 'server_js';
3232
const fileJSON = {
3333
name: fileName,
3434
type,
@@ -64,7 +64,7 @@ function packLocal(rootFolder) {
6464
const folder = path.parse(file).dir;
6565

6666
// If extension is correct and fileName does not start with a dot
67-
if ((extension === '.js' || extension === '.html') && (nameWithoutExtension[0] !== '.')) {
67+
if ((extension === '.js' || extension === '.gs' || extension === '.html') && (nameWithoutExtension[0] !== '.')) {
6868
const filename = path.join(folder, nameWithoutExtension).replace(`\\`, `/`);
6969
if (filenames.includes(filename)) {
7070
reject(`Can't construct a Google Apps Script project with files with the same name: '${filename}.*'`);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const fs = require('fs-extra');
2+
const path = require('path');
3+
const createFile = require('./createFile.js');
4+
const getAllFiles = require('./getAllFiles.js');
5+
const constants = require('../constants.js');
6+
7+
/**
8+
* Pack all seperate .js files back into a raw google script file
9+
*
10+
* @param {string} rootFolder - relative path to the rootFolder of the project
11+
* @param {string} fileName - which file we should add to local.json
12+
* @returns {Promise} - A promise resolving no value
13+
*/
14+
function packLocalSingleFile(rootFolder, fileName) {
15+
// Read every local file and create a correct local.json file
16+
return new Promise((resolve, reject) => {
17+
const fileNameWithoutExtension = path.join(path.parse(fileName).dir, path.parse(fileName).name);
18+
const extension = path.parse(fileName).ext;
19+
20+
if (!(extension === '.js' || extension === '.gs' || extension === '.html')) {
21+
reject(`File has an invalid extension`);
22+
return;
23+
}
24+
25+
// Read the file we are going to include
26+
fs.readFile(path.join(rootFolder, fileName), 'utf8', (err, source) => {
27+
if (err) {
28+
// TODO handle this well
29+
reject(`Can't seem to find '${fileName}'`);
30+
return;
31+
}
32+
33+
// Read remote
34+
const remote = path.join(rootFolder, constants.META_DIR, constants.META_REMOTE);
35+
const remoteData = JSON.parse(fs.readFileSync(remote, 'utf8'));
36+
37+
// Check if file already in remote
38+
let alreadyInRemote = false;
39+
for (const file of remoteData.files) {
40+
if (file.name === fileNameWithoutExtension) {
41+
file.source = source;
42+
alreadyInRemote = true;
43+
break;
44+
}
45+
}
46+
47+
// Add the file if not already in remote
48+
if (!alreadyInRemote) {
49+
const extension = path.parse(fileName).ext;
50+
const type = extension === '.html' ? 'html' : 'server_js';
51+
remoteData.files.push({
52+
name: fileNameWithoutExtension,
53+
type,
54+
source,
55+
});
56+
}
57+
58+
// Write to local.json
59+
const local = path.join(rootFolder, constants.META_DIR, constants.META_LOCAL);
60+
const file = {
61+
name: local,
62+
source: JSON.stringify(remoteData),
63+
};
64+
createFile(file);
65+
resolve();
66+
});
67+
});
68+
}
69+
70+
module.exports = packLocalSingleFile;

lib/functions/unpackRemote.js

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ const gitignoreContent = '# .gitignore for Google Apps Script projects using htt
1414
* Unpack a remote google script file into seperate .js and .html files
1515
*
1616
* @param {string} rootFolder - relative path to the rootFolder of the project
17+
* @param {string} fileName - if specified, only this file will get unpacked
1718
* @returns {Promise} - A promise resolving no value
1819
*/
19-
function unpackRemote(rootFolder) {
20+
function unpackRemote(rootFolder, fileName) {
2021
return new Promise((resolve, reject) => {
22+
let foundSingleFile = false;
2123
const local = path.join(rootFolder, constants.META_DIR, constants.META_LOCAL);
2224
const remote = path.join(rootFolder, constants.META_DIR, constants.META_REMOTE);
2325

@@ -39,14 +41,22 @@ function unpackRemote(rootFolder) {
3941

4042
const included = file.name.substring(0, constants.INCLUDE_DIR.length + 1) === `${constants.INCLUDE_DIR}/`;
4143

44+
// What files do we need to create?
4245
if (!file.source.includes(constants.IGNORE) && !included) {
43-
remoteFiles.push(file);
46+
if (!fileName) {
47+
remoteFiles.push(file);
48+
} else if (fileName === remoteFileName) {
49+
remoteFiles.push(file);
50+
foundSingleFile = true;
51+
}
52+
4453
}
4554
}
4655

47-
// Synch create all necessary files
48-
for (const remoteFile of remoteFiles) {
49-
createFile(remoteFile);
56+
// Reject if we have not found our file
57+
if (fileName && !foundSingleFile) {
58+
reject(`Can't seem to find the file '${fileName}' in this project`);
59+
return;
5060
}
5161

5262
// Write local.json
@@ -55,28 +65,38 @@ function unpackRemote(rootFolder) {
5565
source: data,
5666
});
5767

58-
// Remove all .js and .html that were not in remote.json
59-
const toDelete = [];
60-
for (const localFileName of localFiles) {
61-
const extension = path.parse(localFileName).ext;
62-
if ((extension === '.html' || extension === '.js') && !remoteNames.includes(localFileName) && localFileName !== constants.INCLUDE_FILE) {
63-
toDelete.push(path.join(rootFolder, localFileName));
64-
}
68+
// Synch create all necessary files
69+
for (const remoteFile of remoteFiles) {
70+
createFile(remoteFile);
6571
}
6672

67-
for (const fileToDelete of toDelete) {
68-
fs.removeSync(fileToDelete);
69-
}
73+
// If there was no file specified to pull we will do a cleanup
74+
if (!fileName) {
75+
// Remove all .js and .html that were not in remote.json
76+
const toDelete = [];
77+
for (const localFileName of localFiles) {
78+
const extension = path.parse(localFileName).ext;
79+
if ((extension === '.html' || extension === '.js' || extension === '.gs') && !remoteNames.includes(localFileName) && localFileName !== constants.INCLUDE_FILE) {
80+
toDelete.push(path.join(rootFolder, localFileName));
81+
}
82+
}
83+
84+
for (const fileToDelete of toDelete) {
85+
if (!fileName) {
86+
fs.removeSync(fileToDelete);
87+
}
88+
}
7089

71-
// Remove all empty folders
72-
const allFolders = getAllFolders(rootFolder).sort().reverse();
90+
// Remove all empty folders
91+
const allFolders = getAllFolders(rootFolder).sort().reverse();
7392

74-
for (const emptyFolder of allFolders) {
75-
const files = fs.readdirSync(emptyFolder);
76-
if (files.length === 0) {
77-
fs.removeSync(emptyFolder);
78-
} else if (files.length === 1 && files[0] === '.DS_Store') {
79-
fs.removeSync(emptyFolder);
93+
for (const emptyFolder of allFolders) {
94+
const files = fs.readdirSync(emptyFolder);
95+
if (files.length === 0) {
96+
fs.removeSync(emptyFolder);
97+
} else if (files.length === 1 && files[0] === '.DS_Store') {
98+
fs.removeSync(emptyFolder);
99+
}
80100
}
81101
}
82102

lib/link.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ module.exports = (identifier) => {
5050

5151
const downloaded = Promise.all([gotMetadata, gotAuth, ]).then((values) => {
5252
const metadata = values[0];
53-
process.stdout.clearLine();
5453
readline.cursorTo(process.stdout, 0);
5554
process.stdout.write(`Linking '${metadata.name}' to this folder...`);
5655
return downloadRemote(values[1], metadata.id, '.', 'link');

lib/pull.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ const getProjectRoot = require('./functions/getProjectRoot.js');
1313
/**
1414
* Pull code from a linked remote Google Apps Script project
1515
*
16+
* @param {object} fileName - Optional fileName to pull.
1617
* @param {object} options - Extra options.
1718
* @returns {void}
1819
*/
19-
module.exports = (options) => {
20+
module.exports = (fileName, options) => {
2021
const gotProjectRoot = getProjectRoot('.');
2122

2223
const checkedVersion = checkNewVersion();
@@ -52,12 +53,17 @@ module.exports = (options) => {
5253
const downloaded = Promise.all([gotAuth, gotId, gotMetadata, gotProjectRoot, ]).then((values) => {
5354
process.stdout.clearLine();
5455
readline.cursorTo(process.stdout, 0);
55-
process.stdout.write(`Pulling \'${values[2].name}\' from Google Drive...`);
56+
if (fileName) {
57+
process.stdout.write(`Pulling \'${values[2].name} > ${fileName}\' from Google Drive...`);
58+
} else {
59+
process.stdout.write(`Pulling \'${values[2].name}\' from Google Drive...`);
60+
}
61+
5662
return downloadRemote(values[0], values[1], values[3].folder, 'pull');
5763
});
5864

5965
const unpacked = Promise.all([gotProjectRoot, downloaded, ]).then((values) => {
60-
return unpackRemote(values[0].folder);
66+
return unpackRemote(values[0].folder, fileName);
6167
});
6268

6369
unpacked.then(() => {

lib/push.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
const readline = require('readline');
12
const pull = require('./pull.js');
23
const authenticate = require('./functions/authenticate.js');
34
const getId = require('./functions/getId.js');
45
const packLocal = require('./functions/packLocal.js');
6+
const packLocalSingleFile = require('./functions/packLocalSingleFile.js');
57
const pushToRemote = require('./functions/pushToRemote.js');
68
const downloadRemote = require('./functions/downloadRemote.js');
79
const getMetadata = require('./functions/getMetadata.js');
@@ -14,8 +16,9 @@ const getProjectRoot = require('./functions/getProjectRoot.js');
1416
* Push all local code to the remote Google Apps Script project
1517
*
1618
* @returns {void}
19+
* @param {string} fileName - if defined, only this file will be pushed to remote
1720
*/
18-
module.exports = () => {
21+
module.exports = (fileName) => {
1922
const checkedVersion = checkNewVersion();
2023

2124
const gotProjectRoot = getProjectRoot('.');
@@ -48,12 +51,24 @@ module.exports = () => {
4851
return getMetadata(values[0], values[1]);
4952
});
5053

51-
const downloaded = Promise.all([gotAuth, gotId, gotProjectRoot, gotMetadata, ]).then((values) => {
52-
return downloadRemote(values[0], values[1], values[2].folder, 'pull');
54+
const downloaded = Promise.all([gotAuth, gotId, gotMetadata, gotProjectRoot, ]).then((values) => {
55+
process.stdout.clearLine();
56+
readline.cursorTo(process.stdout, 0);
57+
if (fileName) {
58+
process.stdout.write(`Pushing \'${values[2].name} > ${fileName}\' to Google Drive...`);
59+
} else {
60+
process.stdout.write(`Pushing \'${values[2].name}\' to Google Drive...`);
61+
}
62+
63+
return downloadRemote(values[0], values[1], values[3].folder, 'pull');
5364
});
5465

55-
const packed = Promise.all([gotProjectRoot, gotMetadata, ]).then((values) => {
56-
return packLocal(values[0].folder);
66+
const packed = Promise.all([gotProjectRoot, gotMetadata, downloaded, ]).then((values) => {
67+
if (fileName) {
68+
return packLocalSingleFile(values[0].folder, fileName);
69+
} else {
70+
return packLocal(values[0].folder);
71+
}
5772
});
5873

5974
const pushed = Promise.all([gotAuth, gotId, gotProjectRoot, downloaded, packed, ]).then((values) => {

0 commit comments

Comments
 (0)