@google/clasp 3.0.2-alpha → 3.0.4-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -12
- package/build/src/auth/auth.js +33 -8
- package/build/src/commands/clone-script.js +1 -1
- package/build/src/commands/create-script.js +14 -7
- package/build/src/commands/program.js +1 -1
- package/build/src/commands/push.js +1 -1
- package/build/src/core/clasp.js +7 -1
- package/build/src/core/files.js +34 -8
- package/build/src/core/project.js +1 -0
- package/build/src/experiments.js +1 -5
- package/build/src/index.js +5 -1
- package/docs/run.md +7 -6
- package/package.json +24 -46
package/README.md
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
<a href="https://coveralls.io/github/google/clasp?branch=master"><img src="https://coveralls.io/repos/github/google/clasp/badge.svg?branch=master" alt="Coverage Status"></a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/@google/clasp"><img src="https://img.shields.io/npm/v/@google/clasp.svg" alt="npm Version"></a>
|
|
6
6
|
<a href="https://npmcharts.com/compare/@google/clasp?minimal=true"><img src="https://img.shields.io/npm/dw/@google/clasp.svg" alt="npm Downloads"></a>
|
|
7
|
-
<a href="https://david-dm.org/google/clasp" title="dependencies status"><img src="https://david-dm.org/google/clasp/status.svg"/></a>
|
|
8
7
|
<a href="https://github.com/google/gts" title="Code Style: Google"><img src="https://img.shields.io/badge/code%20style-google-blueviolet.svg"/></a>
|
|
9
8
|
|
|
10
9
|
> Develop [Apps Script](https://developers.google.com/apps-script/) projects locally using clasp (**C**ommand **L**ine **A**pps **S**cript **P**rojects).
|
|
@@ -81,8 +80,8 @@ clasp
|
|
|
81
80
|
- [`clasp clone-script <scriptId | scriptURL> [versionNumber] [--rootDir <dir>]`](#clone)
|
|
82
81
|
- [`clasp pull [--versionNumber]`](#pull)
|
|
83
82
|
- [`clasp push [--watch] [--force]`](#push)
|
|
84
|
-
- [`clasp status [--json]`](#status)
|
|
85
|
-
- [`clasp open-script](#open)
|
|
83
|
+
- [`clasp show-file-status [--json]`](#status)
|
|
84
|
+
- [`clasp open-script`](#open)
|
|
86
85
|
- [`clasp list-deployments`](#deployments)
|
|
87
86
|
- [`clasp create-deployment [--versionNumber <version>] [--description <description>] [--deploymentId <id>]`](#deploy)
|
|
88
87
|
- [`clasp delete-deployment [deploymentId] [--all]`](#undeploy)
|
|
@@ -98,7 +97,7 @@ clasp
|
|
|
98
97
|
- [`clasp list-apis`](#apis)
|
|
99
98
|
- [`clasp enable-api<api>`](#apis)
|
|
100
99
|
- [`clasp disable-api <api>`](#apis)
|
|
101
|
-
- [`clasp run-function [function]`](#
|
|
100
|
+
- [`clasp run-function [function]`](#clasp-run)
|
|
102
101
|
|
|
103
102
|
## Guides
|
|
104
103
|
|
|
@@ -111,9 +110,10 @@ robust support for Typescript features along with ESM module and NPM package sup
|
|
|
111
110
|
|
|
112
111
|
There are several template projects on GitHub that show how to transform Typescript code into Apps Script that are all excellent choices.
|
|
113
112
|
|
|
114
|
-
* https://github.com/sqrrrl/apps-script-typescript-rollup-starter
|
|
115
113
|
* https://github.com/WildH0g/apps-script-engine-template
|
|
116
114
|
* https://github.com/tomoyanakano/clasp-typescript-template
|
|
115
|
+
* https://github.com/google/aside
|
|
116
|
+
* https://github.com/sqrrrl/apps-script-typescript-rollup-starter
|
|
117
117
|
|
|
118
118
|
|
|
119
119
|
#### Command renames
|
|
@@ -143,7 +143,7 @@ Most command require user authorization. Run `clasp login` to authorize access t
|
|
|
143
143
|
|
|
144
144
|
#### Multiple user support
|
|
145
145
|
|
|
146
|
-
Use the global `--user` option to switch between accounts.
|
|
146
|
+
Use the global `--user` option to switch between accounts. This supports both running clasp as different users as well as when invoking the `clasp run-function` command.
|
|
147
147
|
|
|
148
148
|
Examples:
|
|
149
149
|
|
|
@@ -151,7 +151,7 @@ Examples:
|
|
|
151
151
|
clasp login # Saves as default credentials
|
|
152
152
|
clasp clone # User not specified, runs using default credentials
|
|
153
153
|
clasp login --user testaccount # Authorized new named credentials
|
|
154
|
-
|
|
154
|
+
clasp run-function --user testaccount myFunction # Runs function as test account
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
### Bring your own project/credentials
|
|
@@ -160,9 +160,9 @@ While clasp includes a default OAuth client, using your own project is recommend
|
|
|
160
160
|
|
|
161
161
|
1. [Create a new project](https://cloud.google.com/resource-manager/docs/creating-managing-projects) in the Google Cloud Developer Console.
|
|
162
162
|
1. [Create an OAuth client](https://support.google.com/cloud/answer/15549257?hl=en#:~:text=To%20create%20an%20OAuth%202.0,are%20yet%20to%20do%20so.). The client type must be `Desktop Application`. Download and save the generated client secrets file. This is required when authorizing using the`clasp login --creds <filename>` command.
|
|
163
|
-
1. [Enable services](https://cloud.google.com/endpoints/docs/openapi/enable-api). For full
|
|
164
|
-
* Apps
|
|
165
|
-
* Service Usage API - `serviceusage.googleapis.com` (
|
|
163
|
+
1. [Enable services](https://cloud.google.com/endpoints/docs/openapi/enable-api). For full functionality, clasp requires the following:
|
|
164
|
+
* Apps Script API - `script.googleapis.com` (required)
|
|
165
|
+
* Service Usage API - `serviceusage.googleapis.com` (required to list/enable/disable APIs)
|
|
166
166
|
* Google Drive API - `drive.googleapis.com` (required to list scripts, create container-bound scripts)
|
|
167
167
|
- Cloud Logging API - `logging.googleapis.com` (required to read logs)
|
|
168
168
|
|
|
@@ -182,9 +182,16 @@ https://www.googleapis.com/auth/userinfo.profile
|
|
|
182
182
|
https://www.googleapis.com/auth/cloud-platform
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
+
### Allow-list clasp
|
|
186
|
+
|
|
187
|
+
If your organization restricts authorization for third-party apps, you may either:
|
|
188
|
+
|
|
189
|
+
* Request your admin allow-list clasp's client id `1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com`
|
|
190
|
+
* Set up an internal-only GCP project for clasp as described in the previous section.
|
|
191
|
+
|
|
185
192
|
### Service accounts
|
|
186
193
|
|
|
187
|
-
Use the `--adc` option on any command to read credentials from the
|
|
194
|
+
Use the `--adc` option on any command to read credentials from the environment using Google Cloud's [application default credentials](https://cloud.google.com/docs/authentication/application-default-credentials) mechanism.
|
|
188
195
|
|
|
189
196
|
Note that if using a service account, service accounts can not own scripts. To use a service account to push or pull files from Apps Script, the scripts must be shared with the service account with the appropriate role (e.g. `Editor` in able to push.)
|
|
190
197
|
|
|
@@ -315,7 +322,6 @@ Logs the user in. Saves the client credentials to a `.clasprc.json` file in the
|
|
|
315
322
|
|
|
316
323
|
- `--no-localhost`: Do not run a local server, manually enter code instead.
|
|
317
324
|
- `--creds <file>`: Use custom credentials used for `clasp run`. Saves a `.clasprc.json` file to current working directory. This file should be private!
|
|
318
|
-
- `--status`: Print who you are currently logged in as, if anyone.
|
|
319
325
|
- `--redirect-port <port>`: Specify a custom port for the local redirect server during the login process. Useful for environments where a specific port is required.
|
|
320
326
|
|
|
321
327
|
#### Examples
|
package/build/src/auth/auth.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import Debug from 'debug';
|
|
4
5
|
import { GoogleAuth, OAuth2Client } from 'google-auth-library';
|
|
5
6
|
import { google } from 'googleapis';
|
|
6
7
|
import { FileCredentialStore } from './file_credential_store.js';
|
|
7
8
|
import { LocalServerAuthorizationCodeFlow } from './localhost_auth_code_flow.js';
|
|
8
9
|
import { ServerlessAuthorizationCodeFlow } from './serverless_auth_code_flow.js';
|
|
10
|
+
const debug = Debug('clasp:auth');
|
|
9
11
|
export async function initAuth(options) {
|
|
10
12
|
var _a, _b, _c;
|
|
11
13
|
const authFilePath = (_a = options.authFilePath) !== null && _a !== void 0 ? _a : path.join(os.homedir(), '.clasprc.json');
|
|
12
14
|
const credentialStore = new FileCredentialStore(authFilePath);
|
|
15
|
+
debug('Initializng auth from %s', options.authFilePath);
|
|
13
16
|
if (options.useApplicationDefaultCredentials) {
|
|
14
17
|
const credentials = await createApplicationDefaultCredentials();
|
|
15
18
|
return {
|
|
@@ -26,15 +29,23 @@ export async function initAuth(options) {
|
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
export async function getUserInfo(credentials) {
|
|
32
|
+
debug('Fetching user info');
|
|
29
33
|
const api = google.oauth2('v2');
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
try {
|
|
35
|
+
const res = await api.userinfo.get({ auth: credentials });
|
|
36
|
+
if (!res.data) {
|
|
37
|
+
debug('No user info returned');
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
email: res.data.email,
|
|
42
|
+
id: res.data.id,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
debug('Error while fetching userinfo: %O', err);
|
|
32
47
|
return undefined;
|
|
33
48
|
}
|
|
34
|
-
return {
|
|
35
|
-
email: res.data.email,
|
|
36
|
-
id: res.data.id,
|
|
37
|
-
};
|
|
38
49
|
}
|
|
39
50
|
/**
|
|
40
51
|
* Creates an an unauthorized oauth2 client given the client secret file. If no path is provided,
|
|
@@ -57,13 +68,16 @@ export async function getAuthorizedOAuth2Client(store, userKey) {
|
|
|
57
68
|
if (!userKey) {
|
|
58
69
|
userKey = 'default';
|
|
59
70
|
}
|
|
71
|
+
debug('Loading credentials for user %s', userKey);
|
|
60
72
|
const savedCredentials = await store.load(userKey);
|
|
61
73
|
if (!savedCredentials) {
|
|
74
|
+
debug('No saved credentials found.');
|
|
62
75
|
return undefined;
|
|
63
76
|
}
|
|
64
77
|
const client = new GoogleAuth().fromJSON(savedCredentials);
|
|
65
78
|
client.setCredentials(savedCredentials);
|
|
66
79
|
client.on('tokens', async (tokens) => {
|
|
80
|
+
debug('Saving refreshed token for user %s', userKey);
|
|
67
81
|
const refreshedCredentials = {
|
|
68
82
|
...savedCredentials,
|
|
69
83
|
expiry_date: tokens.expiry_date,
|
|
@@ -85,13 +99,16 @@ export async function getAuthorizedOAuth2Client(store, userKey) {
|
|
|
85
99
|
export async function authorize(options) {
|
|
86
100
|
let flow;
|
|
87
101
|
if (options.noLocalServer) {
|
|
102
|
+
debug('Starting auth with serverless flow');
|
|
88
103
|
flow = new ServerlessAuthorizationCodeFlow(options.oauth2Client);
|
|
89
104
|
}
|
|
90
105
|
else {
|
|
106
|
+
debug('Starting auth with local server flow');
|
|
91
107
|
flow = new LocalServerAuthorizationCodeFlow(options.oauth2Client);
|
|
92
108
|
}
|
|
93
109
|
const client = await flow.authorize(options.scopes);
|
|
94
110
|
await saveOauthClientCredentials(options.store, options.userKey, client);
|
|
111
|
+
debug('Auth complete');
|
|
95
112
|
return client;
|
|
96
113
|
}
|
|
97
114
|
async function saveOauthClientCredentials(store, userKey, oauth2Client) {
|
|
@@ -110,8 +127,10 @@ async function saveOauthClientCredentials(store, userKey, oauth2Client) {
|
|
|
110
127
|
access_token: tokens.access_token,
|
|
111
128
|
id_token: tokens.access_token,
|
|
112
129
|
};
|
|
130
|
+
debug('Saving refreshed credentials for user %s', userKey);
|
|
113
131
|
await store.save(userKey, refreshedCredentials);
|
|
114
132
|
});
|
|
133
|
+
debug('Saving credentials for user %s', userKey);
|
|
115
134
|
await store.save(userKey, savedCredentials);
|
|
116
135
|
}
|
|
117
136
|
/**
|
|
@@ -120,6 +139,7 @@ async function saveOauthClientCredentials(store, userKey, oauth2Client) {
|
|
|
120
139
|
* @returns
|
|
121
140
|
*/
|
|
122
141
|
function createOauthClient(clientSecretPath) {
|
|
142
|
+
debug('Creating new oauth client from %s', clientSecretPath);
|
|
123
143
|
if (!clientSecretPath) {
|
|
124
144
|
throw new Error('Invalid credentials');
|
|
125
145
|
}
|
|
@@ -134,11 +154,13 @@ function createOauthClient(clientSecretPath) {
|
|
|
134
154
|
throw new Error('No localhost redirect URL found');
|
|
135
155
|
}
|
|
136
156
|
// create an oAuth client to authorize the API call
|
|
137
|
-
|
|
157
|
+
const client = new OAuth2Client({
|
|
138
158
|
clientId: keys.client_id,
|
|
139
159
|
clientSecret: keys.client_secret,
|
|
140
160
|
redirectUri: redirectUrl,
|
|
141
161
|
});
|
|
162
|
+
debug('Created built-in oauth client, id: %s', client._clientId);
|
|
163
|
+
return client;
|
|
142
164
|
}
|
|
143
165
|
/**
|
|
144
166
|
* Creates an aunthorized oauth2 client using the default id & secret.
|
|
@@ -147,11 +169,13 @@ function createOauthClient(clientSecretPath) {
|
|
|
147
169
|
*/
|
|
148
170
|
function createDefaultOAuthClient() {
|
|
149
171
|
// Default client
|
|
150
|
-
|
|
172
|
+
const client = new OAuth2Client({
|
|
151
173
|
clientId: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com',
|
|
152
174
|
clientSecret: 'v6V3fKV_zWU7iw1DrpO1rknX',
|
|
153
175
|
redirectUri: 'http://localhost',
|
|
154
176
|
});
|
|
177
|
+
debug('Created built-in oauth client, id: %s', client._clientId);
|
|
178
|
+
return client;
|
|
155
179
|
}
|
|
156
180
|
export async function createApplicationDefaultCredentials() {
|
|
157
181
|
const defaultCreds = await new GoogleAuth({
|
|
@@ -170,6 +194,7 @@ export async function createApplicationDefaultCredentials() {
|
|
|
170
194
|
}).getClient();
|
|
171
195
|
// Remove this check after https://github.com/googleapis/google-auth-library-nodejs/issues/1677 fixed
|
|
172
196
|
if (defaultCreds instanceof OAuth2Client) {
|
|
197
|
+
debug('Created service account credentials, id: %s', defaultCreds._clientId);
|
|
173
198
|
return defaultCreds;
|
|
174
199
|
}
|
|
175
200
|
return undefined;
|
|
@@ -56,7 +56,7 @@ export const command = new Command('clone-script')
|
|
|
56
56
|
return files;
|
|
57
57
|
});
|
|
58
58
|
files.forEach(f => console.log(`└─ ${f.localPath}`));
|
|
59
|
-
const successMessage = intl.formatMessage({ id: "
|
|
59
|
+
const successMessage = intl.formatMessage({ id: "XABSyD", defaultMessage: [{ type: 0, value: "Cloned " }, { type: 6, value: "count", options: { "=0": { value: [{ type: 0, value: "no files." }] }, one: { value: [{ type: 0, value: "one file." }] }, other: { value: [{ type: 7 }, { type: 0, value: " files" }] } }, offset: 0, pluralType: "cardinal" }, { type: 0, value: "." }] }, {
|
|
60
60
|
count: files.length,
|
|
61
61
|
});
|
|
62
62
|
console.log(successMessage);
|
|
@@ -37,19 +37,26 @@ export const command = new Command('create-script')
|
|
|
37
37
|
this.error(msg);
|
|
38
38
|
}
|
|
39
39
|
const spinnerMsg = intl.formatMessage({ id: "TMfpGK", defaultMessage: [{ type: 0, value: "Creating script..." }] });
|
|
40
|
-
const { parentId } = await withSpinner(spinnerMsg, async () => await clasp.project.createWithContainer(name, mimeType));
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
40
|
+
const { parentId, scriptId } = await withSpinner(spinnerMsg, async () => await clasp.project.createWithContainer(name, mimeType));
|
|
41
|
+
const parentUrl = `https://drive.google.com/open?id=${parentId}`;
|
|
42
|
+
const scriptUrl = `https://script.google.com/d/${scriptId}/edit`;
|
|
43
|
+
const successMessage = intl.formatMessage({ id: "yf9wXJ", defaultMessage: [{ type: 0, value: "Created new document: " }, { type: 1, value: "parentUrl" }, { type: 1, value: "br" }, { type: 0, value: "Created new script: " }, { type: 1, value: "scriptUrl" }] }, {
|
|
44
|
+
parentUrl,
|
|
45
|
+
scriptUrl,
|
|
46
|
+
br: '\n',
|
|
44
47
|
});
|
|
45
48
|
console.log(successMessage);
|
|
46
49
|
}
|
|
47
50
|
else {
|
|
48
51
|
const spinnerMsg = intl.formatMessage({ id: "TMfpGK", defaultMessage: [{ type: 0, value: "Creating script..." }] });
|
|
49
52
|
const scriptId = await withSpinner(spinnerMsg, async () => await clasp.project.createScript(name, parentId));
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
+
const parentUrl = `https://drive.google.com/open?id=${parentId}`;
|
|
54
|
+
const scriptUrl = `https://script.google.com/d/${scriptId}/edit`;
|
|
55
|
+
const successMessage = intl.formatMessage({ id: "0a429S", defaultMessage: [{ type: 0, value: "Created new script: " }, { type: 1, value: "scriptUrl" }, { type: 5, value: "parentId", options: { undefined: { value: [] }, other: { value: [{ type: 1, value: "br" }, { type: 0, value: "Bound to document: " }, { type: 1, value: "parentUrl" }] } } }] }, {
|
|
56
|
+
parentId,
|
|
57
|
+
parentUrl,
|
|
58
|
+
scriptUrl,
|
|
59
|
+
br: '\n',
|
|
53
60
|
});
|
|
54
61
|
console.log(successMessage);
|
|
55
62
|
}
|
|
@@ -27,7 +27,7 @@ import { command as filesStatusCommand } from './show-file-status.js';
|
|
|
27
27
|
import { command as tailLogsCommand } from './tail-logs.js';
|
|
28
28
|
import { dirname } from 'path';
|
|
29
29
|
import { fileURLToPath } from 'url';
|
|
30
|
-
import { readPackageUpSync } from 'read-
|
|
30
|
+
import { readPackageUpSync } from 'read-package-up';
|
|
31
31
|
import { initAuth } from '../auth/auth.js';
|
|
32
32
|
import { initClaspInstance } from '../core/clasp.js';
|
|
33
33
|
import { intl } from '../intl.js';
|
|
@@ -48,7 +48,7 @@ export const command = new Command('push')
|
|
|
48
48
|
const msg = intl.formatMessage({ id: "m/C0lF", defaultMessage: [{ type: 0, value: "Waiting for changes..." }] });
|
|
49
49
|
console.log(msg);
|
|
50
50
|
};
|
|
51
|
-
const stopWatching = clasp.files.watchLocalFiles(onReady, async (paths) => {
|
|
51
|
+
const stopWatching = await clasp.files.watchLocalFiles(onReady, async (paths) => {
|
|
52
52
|
if (!(await onChange(paths))) {
|
|
53
53
|
stopWatching();
|
|
54
54
|
}
|
package/build/src/core/clasp.js
CHANGED
|
@@ -108,7 +108,7 @@ export async function initClaspInstance(options) {
|
|
|
108
108
|
project: {
|
|
109
109
|
scriptId: config.scriptId,
|
|
110
110
|
projectId: config.projectId,
|
|
111
|
-
parentId: config.parentId,
|
|
111
|
+
parentId: firstValue(config.parentId),
|
|
112
112
|
},
|
|
113
113
|
});
|
|
114
114
|
}
|
|
@@ -216,3 +216,9 @@ async function hasReadAccess(path) {
|
|
|
216
216
|
}
|
|
217
217
|
return true;
|
|
218
218
|
}
|
|
219
|
+
function firstValue(values) {
|
|
220
|
+
if (Array.isArray(values) && values.length > 0) {
|
|
221
|
+
return values[0];
|
|
222
|
+
}
|
|
223
|
+
return values;
|
|
224
|
+
}
|
package/build/src/core/files.js
CHANGED
|
@@ -28,8 +28,15 @@ async function getLocalFiles(rootDir, ignorePatterns, recursive) {
|
|
|
28
28
|
fdirBuilder = fdirBuilder.withMaxDepth(0);
|
|
29
29
|
}
|
|
30
30
|
const files = await fdirBuilder.crawl(rootDir).withPromise();
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
let filteredFiles;
|
|
32
|
+
if (ignorePatterns && ignorePatterns.length) {
|
|
33
|
+
filteredFiles = micromatch.not(files, ignorePatterns, { dot: true });
|
|
34
|
+
debug('Filtered %d files from ignore rules', files.length - filteredFiles.length);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
debug('Ignore rules are empty, using all files.');
|
|
38
|
+
filteredFiles = files;
|
|
39
|
+
}
|
|
33
40
|
filteredFiles.sort((a, b) => a.localeCompare(b));
|
|
34
41
|
return filteredFiles[Symbol.iterator]();
|
|
35
42
|
}
|
|
@@ -104,11 +111,14 @@ function debounceFileChanges(callback, delayMs) {
|
|
|
104
111
|
return function (path) {
|
|
105
112
|
// Already tracked as changed, ignore
|
|
106
113
|
if (collectedPaths.includes(path)) {
|
|
114
|
+
debug('Ignoring pending file change for path %s', path);
|
|
107
115
|
return;
|
|
108
116
|
}
|
|
117
|
+
debug('Debouncing change for path %s', path);
|
|
109
118
|
collectedPaths.push(path);
|
|
110
119
|
clearTimeout(timeoutId);
|
|
111
120
|
timeoutId = setTimeout(() => {
|
|
121
|
+
debug('Firing debounced file');
|
|
112
122
|
callback(collectedPaths);
|
|
113
123
|
collectedPaths = [];
|
|
114
124
|
}, delayMs);
|
|
@@ -167,7 +177,7 @@ export class Files {
|
|
|
167
177
|
const localPath = path.relative(process.cwd(), path.join(contentDir, filename));
|
|
168
178
|
const resolvedPath = path.relative(contentDir, localPath);
|
|
169
179
|
const parsedPath = path.parse(resolvedPath);
|
|
170
|
-
let remotePath = path.format({ dir:
|
|
180
|
+
let remotePath = normalizePath(path.format({ dir: parsedPath.dir, name: parsedPath.name }));
|
|
171
181
|
const type = getFileType(localPath, fileExtensionMap);
|
|
172
182
|
if (!type) {
|
|
173
183
|
debug('Ignoring unsupported file %s', localPath);
|
|
@@ -188,21 +198,37 @@ export class Files {
|
|
|
188
198
|
const ignorePatterns = (_a = this.options.files.ignorePatterns) !== null && _a !== void 0 ? _a : [];
|
|
189
199
|
const collector = debounceFileChanges(onFilesChanged, 500);
|
|
190
200
|
const onChange = async (path) => {
|
|
201
|
+
debug('Have file changes: %s', path);
|
|
191
202
|
collector(path);
|
|
192
203
|
};
|
|
204
|
+
let matcher;
|
|
205
|
+
if (ignorePatterns && ignorePatterns.length) {
|
|
206
|
+
matcher = (file, stats) => {
|
|
207
|
+
if (!(stats === null || stats === void 0 ? void 0 : stats.isFile())) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
file = path.relative(this.options.files.projectRootDir, file);
|
|
211
|
+
const ignore = micromatch.not([file], ignorePatterns, { dot: true }).length === 0;
|
|
212
|
+
return ignore;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
193
215
|
const watcher = chokidar.watch(this.options.files.contentDir, {
|
|
194
216
|
persistent: true,
|
|
195
217
|
ignoreInitial: true,
|
|
196
218
|
cwd: this.options.files.contentDir,
|
|
197
|
-
ignored:
|
|
198
|
-
return micromatch.not([file], ignorePatterns, { dot: true }).length === 0;
|
|
199
|
-
},
|
|
219
|
+
ignored: matcher,
|
|
200
220
|
});
|
|
201
221
|
watcher.on('ready', onReady); // Push on start
|
|
202
222
|
watcher.on('add', onChange);
|
|
203
223
|
watcher.on('change', onChange);
|
|
204
224
|
watcher.on('unlink', onChange);
|
|
205
|
-
|
|
225
|
+
watcher.on('error', err => {
|
|
226
|
+
debug('Unexpected error during watch: %O', err);
|
|
227
|
+
});
|
|
228
|
+
return async () => {
|
|
229
|
+
debug('Stopping watch');
|
|
230
|
+
await watcher.close();
|
|
231
|
+
};
|
|
206
232
|
}
|
|
207
233
|
async getChangedFiles() {
|
|
208
234
|
const [localFiles, remoteFiles] = await Promise.all([this.collectLocalFiles(), this.fetchRemote()]);
|
|
@@ -244,7 +270,7 @@ export class Files {
|
|
|
244
270
|
if (dirsWithIncludedFiles.has(dir)) {
|
|
245
271
|
break;
|
|
246
272
|
}
|
|
247
|
-
excludedPath = `${dir}
|
|
273
|
+
excludedPath = path.normalize(`${dir}/`);
|
|
248
274
|
}
|
|
249
275
|
debug('Found untracked file %s', excludedPath);
|
|
250
276
|
untrackedFiles.add(excludedPath);
|
|
@@ -285,6 +285,7 @@ export class Project {
|
|
|
285
285
|
const settings = {
|
|
286
286
|
scriptId: this.options.project.scriptId,
|
|
287
287
|
rootDir: srcDir,
|
|
288
|
+
parentId: this.options.project.parentId,
|
|
288
289
|
projectId: this.options.project.projectId,
|
|
289
290
|
scriptExtensions: this.options.files.fileExtensions['SERVER_JS'],
|
|
290
291
|
htmlExtensions: this.options.files.fileExtensions['HTML'],
|
package/build/src/experiments.js
CHANGED
|
@@ -7,10 +7,6 @@ export function isEnabled(experimentName, defaultValue = false) {
|
|
|
7
7
|
if (envVarValue.toLowerCase() === 'true' || envVarValue === '1') {
|
|
8
8
|
return true;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
// If it's not a boolean, return the raw string value (for string experiments)
|
|
14
|
-
return envVarValue;
|
|
10
|
+
return false;
|
|
15
11
|
}
|
|
16
12
|
export const INCLUDE_USER_HINT_IN_URL = isEnabled('enable_user_hints');
|
package/build/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import Debug from 'debug';
|
|
22
22
|
import loudRejection from 'loud-rejection';
|
|
23
|
+
import { CommanderError } from 'commander';
|
|
23
24
|
import { makeProgram } from './commands/program.js';
|
|
24
25
|
const debug = Debug('clasp:cli');
|
|
25
26
|
// Suppress warnings about punycode and other issues caused by dependencies
|
|
@@ -34,7 +35,10 @@ try {
|
|
|
34
35
|
}
|
|
35
36
|
catch (error) {
|
|
36
37
|
debug('Error: %O', error);
|
|
37
|
-
if (error instanceof
|
|
38
|
+
if (error instanceof CommanderError) {
|
|
39
|
+
debug('Ignoring commander error, output already logged');
|
|
40
|
+
}
|
|
41
|
+
else if (error instanceof Error) {
|
|
38
42
|
process.exitCode = 1;
|
|
39
43
|
console.error(error.message);
|
|
40
44
|
}
|
package/docs/run.md
CHANGED
|
@@ -8,7 +8,7 @@ To use `clasp run`, you need to complete 5 steps:
|
|
|
8
8
|
|
|
9
9
|
- Set up the **Project ID** in your `.clasp.json` if missing.
|
|
10
10
|
- Create an **OAuth Client ID** of type `Desktop Application`. Download as `client_secret.json`.
|
|
11
|
-
- `clasp login --creds client_secret.json
|
|
11
|
+
- `clasp login --creds client_secret.json --user <key>` with this downloaded file.
|
|
12
12
|
- Add the following to `appsscript.json`:
|
|
13
13
|
```json
|
|
14
14
|
"executionApi": {
|
|
@@ -33,12 +33,13 @@ To use `clasp run`, you need to complete 5 steps:
|
|
|
33
33
|
- If the `Project Number` is missing,
|
|
34
34
|
- Click `Change Project`, paste the PROJECT_NUMBER, and click `Set project`
|
|
35
35
|
1. Use your own OAuth 2 client. Create one by following these instructions:
|
|
36
|
-
- `clasp open
|
|
36
|
+
- `clasp open-credentials-setup`
|
|
37
37
|
- Press **Create credentials** > **OAuth client ID**
|
|
38
38
|
- Application type: **Desktop App**
|
|
39
39
|
- **Create** > **OK**
|
|
40
|
-
- Download the file (⬇), move it to your directory, and name it `
|
|
41
|
-
1.
|
|
40
|
+
- Download the file (⬇), move it to your directory, and name it `client_secret.json`. Please keep this file secret!
|
|
41
|
+
1. Ensure that the [scopes required to run the script are listed in `appsscript.json`](https://developers.google.com/apps-script/concepts/scopes#set-explicit).
|
|
42
|
+
1. Call `clasp login --user <name> --use-project-scopes --creds client_secret.json`
|
|
42
43
|
1. Add the following to `appsscript.json`:
|
|
43
44
|
```json
|
|
44
45
|
"executionApi": {
|
|
@@ -74,6 +75,6 @@ To run functions that use these scopes, you must add the scopes to your Apps Scr
|
|
|
74
75
|
|
|
75
76
|
- `clasp open`
|
|
76
77
|
- `File > Project Properties > Scopes`
|
|
77
|
-
- Add these scopes to your `appsscript.json
|
|
78
|
-
- Log in again: `clasp login --creds creds.json`. This will add these scopes to your credentials.
|
|
78
|
+
- Add these [scopes to your `appsscript.json`](https://developers.google.com/apps-script/concepts/scopes#set-explicit).
|
|
79
|
+
- Log in again: `clasp login --user <name> --use-project-scopes --creds creds.json`. This will add these scopes to your credentials.
|
|
79
80
|
- `clasp run --user <name> sendMail`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@google/clasp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4-alpha",
|
|
4
4
|
"description": "Develop Apps Script Projects locally",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./build/src/index.js",
|
|
@@ -55,81 +55,59 @@
|
|
|
55
55
|
"author": "Grant Timmerman",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@formatjs/intl": "^3.1.
|
|
59
|
-
"@messageformat/core": "^3.4.0",
|
|
60
|
-
"@sindresorhus/is": "^7.0.1",
|
|
61
|
-
"@types/debug": "^4.1.12",
|
|
58
|
+
"@formatjs/intl": "^3.1.6",
|
|
62
59
|
"chalk": "^5.4.1",
|
|
63
60
|
"chokidar": "^4.0.3",
|
|
64
61
|
"cli-truncate": "^4.0.0",
|
|
65
|
-
"commander": "^13.
|
|
66
|
-
"debounce": "^2.2.0",
|
|
62
|
+
"commander": "^13.1.0",
|
|
67
63
|
"debug": "^4.4.0",
|
|
68
|
-
"
|
|
69
|
-
"fdir": "^6.4.3",
|
|
64
|
+
"fdir": "^6.4.4",
|
|
70
65
|
"find-up": "^7.0.0",
|
|
71
66
|
"fuzzy": "^0.1.3",
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"googleapis": "^144.0.0",
|
|
67
|
+
"google-auth-library": "^9.15.1",
|
|
68
|
+
"googleapis": "^148.0.0",
|
|
75
69
|
"googleapis-common": "7.2.0",
|
|
76
70
|
"inflection": "^3.0.2",
|
|
77
|
-
"inquirer": "^12.
|
|
71
|
+
"inquirer": "^12.6.0",
|
|
78
72
|
"inquirer-autocomplete-standalone": "^0.8.1",
|
|
79
|
-
"log-symbols": "^7.0.0",
|
|
80
73
|
"loud-rejection": "^2.2.0",
|
|
81
|
-
"make-dir": "^5.0.0",
|
|
82
74
|
"micromatch": "^4.0.8",
|
|
83
|
-
"multimatch": "^7.0.0",
|
|
84
|
-
"normalize-newline": "^4.1.0",
|
|
85
75
|
"normalize-path": "^3.0.0",
|
|
86
|
-
"open": "^10.1.
|
|
76
|
+
"open": "^10.1.2",
|
|
87
77
|
"ora": "^8.1.1",
|
|
88
78
|
"p-map": "^7.0.3",
|
|
89
79
|
"picomatch": "^4.0.2",
|
|
90
|
-
"read-
|
|
80
|
+
"read-package-up": "^11.0.0",
|
|
91
81
|
"server-destroy": "^1.0.1",
|
|
92
82
|
"split-lines": "^3.0.0",
|
|
93
|
-
"strip-bom": "^5.0.0"
|
|
94
|
-
"typescript": "^5.7.3"
|
|
83
|
+
"strip-bom": "^5.0.0"
|
|
95
84
|
},
|
|
96
85
|
"devDependencies": {
|
|
97
86
|
"@biomejs/biome": "^1.9.4",
|
|
98
|
-
"@commander-js/extra-typings": "^13.
|
|
99
|
-
"@formatjs/ts-transformer": "^3.13.
|
|
100
|
-
"@
|
|
101
|
-
"@
|
|
102
|
-
"@types/
|
|
103
|
-
"@types/chai-as-promised": "^8.0.1",
|
|
104
|
-
"@types/chai-fs": "^2.0.5",
|
|
105
|
-
"@types/chai-subset": "^1.3.5",
|
|
106
|
-
"@types/debounce": "^1.2.4",
|
|
107
|
-
"@types/fs-extra": "^11.0.4",
|
|
87
|
+
"@commander-js/extra-typings": "^13.1.0",
|
|
88
|
+
"@formatjs/ts-transformer": "^3.13.34",
|
|
89
|
+
"@types/chai": "^5.2.2",
|
|
90
|
+
"@types/chai-as-promised": "^8.0.2",
|
|
91
|
+
"@types/debug": "^4.1.12",
|
|
108
92
|
"@types/micromatch": "^4.0.9",
|
|
109
93
|
"@types/mocha": "^10.0.10",
|
|
110
94
|
"@types/mock-fs": "^4.13.4",
|
|
111
|
-
"@types/node": "^22.
|
|
95
|
+
"@types/node": "^22.15.17",
|
|
112
96
|
"@types/normalize-path": "^3.0.2",
|
|
113
|
-
"@types/picomatch": "^
|
|
97
|
+
"@types/picomatch": "^4.0.0",
|
|
114
98
|
"@types/server-destroy": "^1.0.4",
|
|
115
99
|
"@types/sinon": "^17.0.4",
|
|
116
|
-
"@types/tmp": "^0.2.6",
|
|
117
|
-
"@types/wtfnode": "^0.7.3",
|
|
118
100
|
"c8": "^10.1.3",
|
|
119
|
-
"chai": "^5.
|
|
101
|
+
"chai": "^5.2.0",
|
|
120
102
|
"chai-as-promised": "^8.0.1",
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"nyc": "^17.1.0",
|
|
126
|
-
"sinon": "^19.0.2",
|
|
103
|
+
"mocha": "^11.2.2",
|
|
104
|
+
"mock-fs": "^5.5.0",
|
|
105
|
+
"nock": "^14.0.4",
|
|
106
|
+
"sinon": "^20.0.0",
|
|
127
107
|
"source-map-support": "^0.5.21",
|
|
128
|
-
"tmp": "^0.2.3",
|
|
129
108
|
"ts-node": "^10.9.2",
|
|
130
109
|
"ts-patch": "^3.3.0",
|
|
131
|
-
"type-fest": "^4.
|
|
132
|
-
"
|
|
133
|
-
"wtfnode": "^0.10.0"
|
|
110
|
+
"type-fest": "^4.41.0",
|
|
111
|
+
"typescript": "^5.8.3"
|
|
134
112
|
}
|
|
135
113
|
}
|