Skip to content

Commit 7f54416

Browse files
author
Jules Amosah
committed
Use pseudo-build process
I'm not particularly proud of this solution as it requires manual intervention to run tests. Still searching for a way to very simply bundle the entrypoint functions such that they are the only ones detected by GAS. The bundlers I've tried thus far (esbuild, rollup) add too much boilerplate and expose additional functions that GAS shouldn't care about. This works for now, but I'll continue looking for a tool suited for the above (perhaps creating my own if necessary).
1 parent c2f9495 commit 7f54416

7 files changed

+3907
-2805
lines changed

.clasp.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"$schema": "http://json.schemastore.org/clasp",
3-
"scriptId": "1R8lHinD4Gc2PL2MLqncB7lV_SpHy33b-Ay3OejWnTTXmsf4C9aYX1ZAh"
3+
"scriptId": "1R8lHinD4Gc2PL2MLqncB7lV_SpHy33b-Ay3OejWnTTXmsf4C9aYX1ZAh",
4+
"rootDir": "dist"
45
}

.claspignore

-4
This file was deleted.

README.md

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
# GMail Scripts
1+
# Gmail Scripts
22

33
[![clasp](https://img.shields.io/badge/built%20with-clasp-4285f4.svg)](https://github.com/google/clasp)
44

5-
These are just some basic automated tasks created using [Google Apps Script](https://www.google.com/script/start/). They are described below.
5+
These are just some basic automated tasks created using [Google Apps Script (GAS)](https://www.google.com/script/start/). They are described below.
66

7-
## Delete Old Labels
7+
## Functions
8+
9+
### Delete Old Labels
810

911
I use labels to hang on to emails that I could need again in the immediate future. After months, however, I usually don't still need them. This function deletes emails by label older than a certain threshold (for my use case: 90 days).
1012

11-
## Mark Deleted As Read
13+
### Mark Deleted As Read
1214

1315
Sometimes I delete emails without opening them (you know the type). I run this function periodically to mark all unread deleted emails as read.
16+
17+
## Building
18+
19+
Run `npm run build`. 'Course, all this does right now is copy `main.js` and `appsscript.json` into the `dist/` folder. Haven't yet figured out a satisfactory way to bundle the source code for GAS, but am open to suggestions.
20+
21+
## Testing
22+
23+
Run `npm test`. Note that you will need to uncomment the export at the end of `main.js` for the tests to run properly.

main.js

+37-5
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,54 @@
1+
// Using IIFE to prevent GAS from picking up these utility functions
12
const bulkThreadAction = (() => {
3+
/**
4+
* Splits the given array into subarrays of the given size
5+
* @param {unknown[]} array
6+
* @param {number} chunkSize
7+
* @returns {unknown[][]}
8+
*/
29
const chunk = (array, chunkSize) =>
310
array.reduce((array2, thread, idx) =>
411
idx % chunkSize === 0
512
? [...array2, [thread]]
613
: [...array2.slice(0, -1), [...array2.slice(-1)[0], thread]], []);
714

15+
/** @typedef {(threads: unknown[]) => void} threadAction */
16+
17+
/**
18+
* GmailApp will error if its thread functions are called with more than 100 threads at a time.
19+
* This wrapper ensures threads are appropriately split to prevent said error.
20+
* @param {string} query
21+
* @param {threadAction} action
22+
* @returns {void}
23+
*/
824
function bulkThreadAction(query, action) {
25+
console.info(`Searching for threads matching: [${query}]`);
26+
/** @type {unknown[]} */
927
const targetThreads = GmailApp.search(query);
28+
1029
if (targetThreads.length === 0) {
1130
console.info("No threads to operate on");
1231
return;
1332
}
33+
34+
console.info(`Calling ${action.name} with ${targetThreads.length} threads`);
35+
1436
if (targetThreads.length <= 100) {
15-
console.info("<= 100 threads; not chunking");
37+
console.info("Not chunking; <= 100 threads");
1638
action(targetThreads);
1739
return;
1840
}
19-
console.info(`Chunking ${targetThreads.length} threads`);
41+
2042
const chunkedThreads = chunk(targetThreads, 100);
43+
console.info(`Split threads into ${chunkedThreads.length} chunks`);
2144
chunkedThreads.forEach((threadChunk) => action(threadChunk));
2245
}
23-
2446
return bulkThreadAction;
2547
})();
2648

49+
/**
50+
* Deletes labels of interest older than 90 days
51+
*/
2752
function deleteOldLabels() {
2853
const ninetyDaysAgo = new Date();
2954
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
@@ -38,7 +63,14 @@ function deleteOldLabels() {
3863
);
3964
}
4065

41-
const markDeletedAsRead = () =>
66+
/**
67+
* Marks deleted threads as read
68+
*/
69+
function markDeletedAsRead() {
4270
bulkThreadAction("is:unread in:trash", GmailApp.markThreadsRead);
71+
}
4372

44-
export { bulkThreadAction };
73+
// (Until I figure out a way to bundle this file
74+
// that isn't over-engineered and unreadable)
75+
// Uncomment to run tests
76+
// export { bulkThreadAction };

test.js main.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const query = "test";
88
const search = stub();
99
const threadAction = fake();
1010

11-
const act = bulkThreadAction.bind(null, query, threadAction);
11+
const act = () => bulkThreadAction(query, threadAction);
1212

1313
global.GmailApp = {
1414
search,

0 commit comments

Comments
 (0)