@google/clasp 3.0.0-alpha1 → 3.0.2-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 +53 -15
- package/build/src/auth/localhost_auth_code_flow.js +1 -0
- package/build/src/commands/create-script.js +2 -2
- package/build/src/commands/open-apis.js +8 -2
- package/build/src/commands/open-container.js +8 -2
- package/build/src/commands/open-credentials.js +8 -2
- package/build/src/commands/open-logs.js +9 -2
- package/build/src/commands/open-script.js +7 -2
- package/build/src/commands/open-webapp.js +8 -3
- package/build/src/commands/push.js +1 -0
- package/build/src/commands/run-function.js +1 -1
- package/build/src/commands/tail-logs.js +15 -1
- package/build/src/commands/update-deployment.js +36 -0
- package/build/src/commands/utils.js +5 -4
- package/build/src/core/clasp.js +50 -3
- package/build/src/core/files.js +37 -17
- package/build/src/core/project.js +4 -1
- package/build/src/core/utils.js +24 -1
- package/build/src/experiments.js +16 -0
- package/build/src/index.js +2 -0
- package/build/src/intl.js +2 -0
- package/docs/run.md +2 -2
- package/package.json +4 -19
package/README.md
CHANGED
|
@@ -118,22 +118,22 @@ There are several template projects on GitHub that show how to transform Typescr
|
|
|
118
118
|
|
|
119
119
|
#### Command renames
|
|
120
120
|
|
|
121
|
-
Clasp 3.x
|
|
121
|
+
Clasp 3.x introduces some breaking changes from 2.x. For common use cases these changes should not impact usage, but some lesser used commands have been restructured and renamed to improve consistency.
|
|
122
122
|
|
|
123
123
|
| 2.x | 3.x |
|
|
124
124
|
|----------------------------|----------------------------------------|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
|
136
|
-
|
|
125
|
+
|`open` | `open-script` |
|
|
126
|
+
|`open --web` | `open-web-app` |
|
|
127
|
+
|`open --addon` | `open-container` |
|
|
128
|
+
|`open --creds` | `open-credentials-setup` |
|
|
129
|
+
|`login --creds <file>` | `login -u <name> --creds <file>` |
|
|
130
|
+
|`logs --open` | `open-logs` |
|
|
131
|
+
|`logs --setup` | N/A |
|
|
132
|
+
|`apis --open` | `open-api-console` |
|
|
133
|
+
|`apis enable <api>` | `enable-api <api>` |
|
|
134
|
+
|`apis disable <api>` | `disable-api <api>` |
|
|
135
|
+
|`deploy -i <id>` | `update-deployment <id>` |
|
|
136
|
+
|`settings` | N/A |
|
|
137
137
|
|
|
138
138
|
Other commands have also been renamed but retain aliases for compatibility.
|
|
139
139
|
|
|
@@ -263,14 +263,39 @@ You must [associate Google Script project with Google Cloud Platform](https://gi
|
|
|
263
263
|
|
|
264
264
|
Even if you do not set this manually, clasp will ask this via a prompt to you at the required time.
|
|
265
265
|
|
|
266
|
-
### `fileExtension` (optional)
|
|
266
|
+
### `fileExtension` (deprecated, optional)
|
|
267
267
|
|
|
268
268
|
Specifies the file extension for **local** script files in your Apps Script project.
|
|
269
269
|
|
|
270
|
+
### `scriptExtensions` (optional)
|
|
271
|
+
|
|
272
|
+
Specifies the file extensions for **local** script files in your Apps Script project. May be a string or array of strings. Files matching the extension will be considered scripts files.
|
|
273
|
+
|
|
274
|
+
When pulling files, the first extension listed is used to write files.
|
|
275
|
+
|
|
276
|
+
Defaults to `[".js", ".gs"]`
|
|
277
|
+
|
|
278
|
+
### `htmlExtensions` (optional)
|
|
279
|
+
|
|
280
|
+
Specifies the file extensions for **local** HTML files in your Apps Script project. May be a string or array of strings. Files matching the extension will be considered HTML files.
|
|
281
|
+
|
|
282
|
+
When pulling files, the first extension listed is used to write files.
|
|
283
|
+
|
|
284
|
+
Defaults to `[".html"]`
|
|
285
|
+
|
|
270
286
|
### `filePushOrder` (optional)
|
|
271
287
|
|
|
272
|
-
Specifies the files that should be pushed first, useful for scripts that rely on order of execution. All other files are pushed after this list of files.
|
|
288
|
+
Specifies the files that should be pushed first, useful for scripts that rely on order of execution. All other files are pushed after this list of files, sorted by name.
|
|
289
|
+
|
|
290
|
+
Note that file paths are relative to directory containing .clasp.json. If `rootDir` is also set, any files listed should include that path as well.
|
|
291
|
+
|
|
292
|
+
### `skipSubdirectories` (optional)
|
|
273
293
|
|
|
294
|
+
For backwards compatibility with previous behavior where subdirectories
|
|
295
|
+
are ignored if a `.claspignore` file is not present. Clasp provides default
|
|
296
|
+
ignore rules, making the previous warning and behavior confusing. If you
|
|
297
|
+
need to force clasp to ignore subdirectories and do not want to construct
|
|
298
|
+
a `.claspignore` file, set this option to true.
|
|
274
299
|
|
|
275
300
|
## Reference
|
|
276
301
|
|
|
@@ -453,6 +478,19 @@ To update/redeploy an existing deployment, provide the deployment ID.
|
|
|
453
478
|
- `clasp create-deployment --deploymentId abcd1234` (redeploy and create new version)
|
|
454
479
|
- `clasp create-deployment -V 7 -d "Updates sidebar logo." -i abdc1234`
|
|
455
480
|
|
|
481
|
+
### Redeploy
|
|
482
|
+
|
|
483
|
+
Updates an existing deployment. Same as `create-deployment -i id`.
|
|
484
|
+
|
|
485
|
+
#### Options
|
|
486
|
+
|
|
487
|
+
- `-V <version>` `--versionNumber <version>`: The project version to deploy at.
|
|
488
|
+
- `-d <description>` `--description <description>`: The deployment description.
|
|
489
|
+
|
|
490
|
+
#### Examples
|
|
491
|
+
|
|
492
|
+
- `clasp update-deployment abcd1234` (redeploy and create new version)
|
|
493
|
+
|
|
456
494
|
### Undeploy
|
|
457
495
|
|
|
458
496
|
Undeploys a deployment of a script.
|
|
@@ -11,7 +11,7 @@ const DRIVE_FILE_MIMETYPES = {
|
|
|
11
11
|
slides: 'application/vnd.google-apps.presentation',
|
|
12
12
|
};
|
|
13
13
|
export const command = new Command('create-script')
|
|
14
|
-
.
|
|
14
|
+
.alias('create')
|
|
15
15
|
.description('Create a script')
|
|
16
16
|
.option('--type <type>', 'Creates a new Apps Script project attached to a new Document, Spreadsheet, Presentation, Form, or as a standalone script, web app, or API.', 'standalone')
|
|
17
17
|
.option('--title <title>', 'The project title.')
|
|
@@ -60,7 +60,7 @@ export const command = new Command('create-script')
|
|
|
60
60
|
return files;
|
|
61
61
|
});
|
|
62
62
|
files.forEach(f => console.log(`└─ ${f.localPath}`));
|
|
63
|
-
const successMessage = intl.formatMessage({ id: "
|
|
63
|
+
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: "." }] }, {
|
|
64
64
|
count: files.length,
|
|
65
65
|
});
|
|
66
66
|
console.log(successMessage);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { INCLUDE_USER_HINT_IN_URL } from '../experiments.js';
|
|
2
3
|
import { assertGcpProjectConfigured, maybePromptForProjectId, openUrl } from './utils.js';
|
|
3
4
|
export const command = new Command('open-api-console')
|
|
4
5
|
.description('Open the API console for the current project.')
|
|
@@ -6,6 +7,11 @@ export const command = new Command('open-api-console')
|
|
|
6
7
|
const clasp = this.opts().clasp;
|
|
7
8
|
const projectId = await maybePromptForProjectId(clasp);
|
|
8
9
|
assertGcpProjectConfigured(clasp);
|
|
9
|
-
const url =
|
|
10
|
-
|
|
10
|
+
const url = new URL('https://console.developers.google.com/apis/dashboard');
|
|
11
|
+
url.searchParams.set('project', projectId !== null && projectId !== void 0 ? projectId : '');
|
|
12
|
+
if (INCLUDE_USER_HINT_IN_URL) {
|
|
13
|
+
const userHint = await clasp.authorizedUser();
|
|
14
|
+
url.searchParams.set('authUser', userHint !== null && userHint !== void 0 ? userHint : '');
|
|
15
|
+
}
|
|
16
|
+
await openUrl(url.toString());
|
|
11
17
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { INCLUDE_USER_HINT_IN_URL } from '../experiments.js';
|
|
2
3
|
import { intl } from '../intl.js';
|
|
3
4
|
import { openUrl } from './utils.js';
|
|
4
5
|
export const command = new Command('open-container')
|
|
@@ -10,6 +11,11 @@ export const command = new Command('open-container')
|
|
|
10
11
|
const msg = intl.formatMessage({ id: "eXBzoP", defaultMessage: [{ type: 0, value: "Parent ID not set, unable to open document." }] });
|
|
11
12
|
this.error(msg);
|
|
12
13
|
}
|
|
13
|
-
const url =
|
|
14
|
-
|
|
14
|
+
const url = new URL('https://drive.google.com/open');
|
|
15
|
+
url.searchParams.set('id', parentId);
|
|
16
|
+
if (INCLUDE_USER_HINT_IN_URL) {
|
|
17
|
+
const userHint = await clasp.authorizedUser();
|
|
18
|
+
url.searchParams.set('authUser', userHint !== null && userHint !== void 0 ? userHint : '');
|
|
19
|
+
}
|
|
20
|
+
await openUrl(url.toString());
|
|
15
21
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { INCLUDE_USER_HINT_IN_URL } from '../experiments.js';
|
|
2
3
|
import { assertGcpProjectConfigured, maybePromptForProjectId, openUrl } from './utils.js';
|
|
3
4
|
export const command = new Command('open-credentials-setup')
|
|
4
5
|
.description("Open credentials page for the script's GCP project")
|
|
@@ -6,6 +7,11 @@ export const command = new Command('open-credentials-setup')
|
|
|
6
7
|
const clasp = this.opts().clasp;
|
|
7
8
|
const projectId = await maybePromptForProjectId(clasp);
|
|
8
9
|
assertGcpProjectConfigured(clasp);
|
|
9
|
-
const url =
|
|
10
|
-
|
|
10
|
+
const url = new URL('https://console.developers.google.com/apis/credentials');
|
|
11
|
+
url.searchParams.set('project', projectId !== null && projectId !== void 0 ? projectId : '');
|
|
12
|
+
if (INCLUDE_USER_HINT_IN_URL) {
|
|
13
|
+
const userHint = await clasp.authorizedUser();
|
|
14
|
+
url.searchParams.set('authUser', userHint !== null && userHint !== void 0 ? userHint : '');
|
|
15
|
+
}
|
|
16
|
+
await openUrl(url.toString());
|
|
11
17
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { INCLUDE_USER_HINT_IN_URL } from '../experiments.js';
|
|
2
3
|
import { assertGcpProjectConfigured, maybePromptForProjectId, openUrl } from './utils.js';
|
|
3
4
|
export const command = new Command('open-logs')
|
|
4
5
|
.description('Open logs in the developer console')
|
|
@@ -6,6 +7,12 @@ export const command = new Command('open-logs')
|
|
|
6
7
|
const clasp = this.opts().clasp;
|
|
7
8
|
const projectId = await maybePromptForProjectId(clasp);
|
|
8
9
|
assertGcpProjectConfigured(clasp);
|
|
9
|
-
const url =
|
|
10
|
-
|
|
10
|
+
const url = new URL('https://console.cloud.google.com/logs/viewer');
|
|
11
|
+
url.searchParams.set('project', projectId !== null && projectId !== void 0 ? projectId : '');
|
|
12
|
+
url.searchParams.set('resource', 'app_script_function');
|
|
13
|
+
if (INCLUDE_USER_HINT_IN_URL) {
|
|
14
|
+
const userHint = await clasp.authorizedUser();
|
|
15
|
+
url.searchParams.set('authUser', userHint !== null && userHint !== void 0 ? userHint : '');
|
|
16
|
+
}
|
|
17
|
+
await openUrl(url.toString());
|
|
11
18
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { INCLUDE_USER_HINT_IN_URL } from '../experiments.js';
|
|
2
3
|
import { intl } from '../intl.js';
|
|
3
4
|
import { openUrl } from './utils.js';
|
|
4
5
|
export const command = new Command('open-script')
|
|
@@ -13,6 +14,10 @@ export const command = new Command('open-script')
|
|
|
13
14
|
const msg = intl.formatMessage({ id: "RXEA+0", defaultMessage: [{ type: 0, value: "Script ID not set, unable to open IDE." }] });
|
|
14
15
|
this.error(msg);
|
|
15
16
|
}
|
|
16
|
-
const url = `https://script.google.com/d/${scriptId}/edit
|
|
17
|
-
|
|
17
|
+
const url = new URL(`https://script.google.com/d/${scriptId}/edit`);
|
|
18
|
+
if (INCLUDE_USER_HINT_IN_URL) {
|
|
19
|
+
const userHint = await clasp.authorizedUser();
|
|
20
|
+
url.searchParams.set('authUser', userHint !== null && userHint !== void 0 ? userHint : '');
|
|
21
|
+
}
|
|
22
|
+
await openUrl(url.toString());
|
|
18
23
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
|
+
import { INCLUDE_USER_HINT_IN_URL } from '../experiments.js';
|
|
3
4
|
import { intl } from '../intl.js';
|
|
4
5
|
import { ellipsize, isInteractive, openUrl } from './utils.js';
|
|
5
6
|
export const command = new Command('open-web-app')
|
|
@@ -39,7 +40,7 @@ export const command = new Command('open-web-app')
|
|
|
39
40
|
deploymentId = answer.deployment;
|
|
40
41
|
}
|
|
41
42
|
if (!deploymentId) {
|
|
42
|
-
const msg = intl.formatMessage({ id: "
|
|
43
|
+
const msg = intl.formatMessage({ id: "VJZ9X5", defaultMessage: [{ type: 0, value: "Deployment ID is required." }] });
|
|
43
44
|
this.error(msg);
|
|
44
45
|
}
|
|
45
46
|
const entryPoints = (_a = (await clasp.project.entryPoints(deploymentId))) !== null && _a !== void 0 ? _a : [];
|
|
@@ -51,6 +52,10 @@ export const command = new Command('open-web-app')
|
|
|
51
52
|
const msg = intl.formatMessage({ id: "Kfeimc", defaultMessage: [{ type: 0, value: "No web app entry point found." }] });
|
|
52
53
|
this.error(msg);
|
|
53
54
|
}
|
|
54
|
-
const url = webAppEntry.webApp.url;
|
|
55
|
-
|
|
55
|
+
const url = new URL(webAppEntry.webApp.url);
|
|
56
|
+
if (INCLUDE_USER_HINT_IN_URL) {
|
|
57
|
+
const userHint = await clasp.authorizedUser();
|
|
58
|
+
url.searchParams.set('authUser', userHint !== null && userHint !== void 0 ? userHint : '');
|
|
59
|
+
}
|
|
60
|
+
await openUrl(url.toString());
|
|
56
61
|
});
|
|
@@ -18,6 +18,7 @@ export const command = new Command('push')
|
|
|
18
18
|
if (!force) {
|
|
19
19
|
const msg = intl.formatMessage({ id: "TItFfu", defaultMessage: [{ type: 0, value: "Skipping push." }] });
|
|
20
20
|
console.log(msg);
|
|
21
|
+
return;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
const spinnerMsg = intl.formatMessage({ id: "qUq++d", defaultMessage: [{ type: 0, value: "Pushing files..." }] });
|
|
@@ -39,7 +39,7 @@ export const command = new Command('run-function')
|
|
|
39
39
|
console.error(`${chalk.red(msg)}`, errorMessage, scriptStackTraceElements || []);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
if (response && response.result) {
|
|
42
|
+
if (response && response.result !== undefined) {
|
|
43
43
|
console.log(response.result);
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
@@ -41,6 +41,7 @@ export const command = new Command('tail-logs')
|
|
|
41
41
|
await maybePromptForProjectId(clasp);
|
|
42
42
|
}
|
|
43
43
|
assertGcpProjectConfigured(clasp);
|
|
44
|
+
console.log('PAST', clasp.project.projectId);
|
|
44
45
|
await fetchAndPrintLogs();
|
|
45
46
|
if (watch) {
|
|
46
47
|
const POLL_INTERVAL = 6000; // 6s
|
|
@@ -62,6 +63,9 @@ function formatEntry(entry, options) {
|
|
|
62
63
|
if (!resource) {
|
|
63
64
|
return undefined;
|
|
64
65
|
}
|
|
66
|
+
if (!timestamp) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
65
69
|
let functionName = (_b = (_a = resource.labels) === null || _a === void 0 ? void 0 : _a['function_name']) !== null && _b !== void 0 ? _b : 'N/A';
|
|
66
70
|
let payloadData = '';
|
|
67
71
|
if (options.json) {
|
|
@@ -85,8 +89,18 @@ function formatEntry(entry, options) {
|
|
|
85
89
|
const coloredSeverity = `${severityColor[severity](severity) || severity}`.padEnd(20);
|
|
86
90
|
functionName = functionName.padEnd(15);
|
|
87
91
|
payloadData = payloadData.padEnd(20);
|
|
92
|
+
const localizedTime = getLocalISODateTime(new Date(timestamp));
|
|
88
93
|
if (options.simplified) {
|
|
89
94
|
return `${coloredSeverity} ${functionName} ${payloadData}`;
|
|
90
95
|
}
|
|
91
|
-
return `${coloredSeverity} ${
|
|
96
|
+
return `${coloredSeverity} ${localizedTime} ${functionName} ${payloadData}`;
|
|
97
|
+
}
|
|
98
|
+
function getLocalISODateTime(date) {
|
|
99
|
+
const year = date.getFullYear();
|
|
100
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
101
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
102
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
103
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
104
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
105
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
92
106
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { intl } from '../intl.js';
|
|
3
|
+
import { withSpinner } from './utils.js';
|
|
4
|
+
export const command = new Command('update-deployment')
|
|
5
|
+
.alias('redeploy')
|
|
6
|
+
.argument('<deploymentId>')
|
|
7
|
+
.description('Updates a deployment for a project to a new version')
|
|
8
|
+
.option('-V, --versionNumber <version>', 'The project version')
|
|
9
|
+
.option('-d, --description <description>', 'The deployment description')
|
|
10
|
+
.action(async function (deploymentId, options) {
|
|
11
|
+
var _a, _b, _c;
|
|
12
|
+
const clasp = this.opts().clasp;
|
|
13
|
+
const description = (_a = options.description) !== null && _a !== void 0 ? _a : '';
|
|
14
|
+
const versionNumber = options.versionNumber ? Number(options.versionNumber) : undefined;
|
|
15
|
+
if (!deploymentId) {
|
|
16
|
+
const msg = intl.formatMessage({ id: "OXJvuR", defaultMessage: [{ type: 0, value: "Deployment ID is required to redeploy." }] });
|
|
17
|
+
this.error(msg);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const spinnerMsg = intl.formatMessage({ id: "oL8t7p", defaultMessage: [{ type: 0, value: "Deploying project..." }] });
|
|
21
|
+
const deployment = await withSpinner(spinnerMsg, async () => {
|
|
22
|
+
return await clasp.project.deploy(description, deploymentId, versionNumber);
|
|
23
|
+
});
|
|
24
|
+
const successMessage = intl.formatMessage({ id: "wWBE7L", defaultMessage: [{ type: 0, value: "Redeployed " }, { type: 1, value: "deploymentId" }, { type: 0, value: " " }, { type: 5, value: "version", options: { undefined: { value: [{ type: 0, value: "@HEAD" }] }, other: { value: [{ type: 0, value: "@" }, { type: 1, value: "version" }] } } }] }, {
|
|
25
|
+
deploymentId: deployment.deploymentId,
|
|
26
|
+
version: (_b = deployment.deploymentConfig) === null || _b === void 0 ? void 0 : _b.versionNumber,
|
|
27
|
+
});
|
|
28
|
+
console.log(successMessage);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (((_c = error.cause) === null || _c === void 0 ? void 0 : _c.code) === 'INVALID_ARGUMENT') {
|
|
32
|
+
this.error(error.cause.message);
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
@@ -3,14 +3,14 @@ import inquirer from 'inquirer';
|
|
|
3
3
|
import open from 'open';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import { intl } from '../intl.js';
|
|
6
|
-
export
|
|
6
|
+
export function assertScriptConfigured(clasp) {
|
|
7
7
|
if (clasp.project.scriptId) {
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
const msg = intl.formatMessage({ id: "2IuvqO", defaultMessage: [{ type: 0, value: "Script ID is not set, unable to continue." }] });
|
|
11
11
|
throw new Error(msg);
|
|
12
12
|
}
|
|
13
|
-
export
|
|
13
|
+
export function assertGcpProjectConfigured(clasp) {
|
|
14
14
|
if (clasp.project.projectId) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
@@ -61,13 +61,14 @@ export function ellipsize(value, length) {
|
|
|
61
61
|
}
|
|
62
62
|
// Exporting and wrapping to allow it to be toggled in tests
|
|
63
63
|
export const claspEnv = {
|
|
64
|
-
isInteractive: process.stdout.isTTY
|
|
64
|
+
isInteractive: process.stdout.isTTY,
|
|
65
|
+
isBrowserPresent: process.stdout.isTTY,
|
|
65
66
|
};
|
|
66
67
|
export function isInteractive() {
|
|
67
68
|
return claspEnv.isInteractive;
|
|
68
69
|
}
|
|
69
70
|
export async function openUrl(url) {
|
|
70
|
-
if (!
|
|
71
|
+
if (!claspEnv.isBrowserPresent) {
|
|
71
72
|
const msg = intl.formatMessage({ id: "kvR0OI", defaultMessage: [{ type: 0, value: "Open " }, { type: 1, value: "url" }, { type: 0, value: " in your browser to continue." }] }, {
|
|
72
73
|
url,
|
|
73
74
|
});
|
package/build/src/core/clasp.js
CHANGED
|
@@ -4,11 +4,13 @@ import { findUpSync } from 'find-up';
|
|
|
4
4
|
import fs from 'fs/promises';
|
|
5
5
|
import splitLines from 'split-lines';
|
|
6
6
|
import stripBom from 'strip-bom';
|
|
7
|
+
import { getUserInfo } from '../auth/auth.js';
|
|
7
8
|
import { Files } from './files.js';
|
|
8
9
|
import { Functions } from './functions.js';
|
|
9
10
|
import { Logs } from './logs.js';
|
|
10
11
|
import { Project } from './project.js';
|
|
11
12
|
import { Services } from './services.js';
|
|
13
|
+
import { ensureStringArray } from './utils.js';
|
|
12
14
|
const debug = Debug('clasp:core');
|
|
13
15
|
const DEFAULT_CLASP_IGNORE = [
|
|
14
16
|
'**/**',
|
|
@@ -30,6 +32,19 @@ export class Clasp {
|
|
|
30
32
|
this.logs = new Logs(options);
|
|
31
33
|
this.functions = new Functions(options);
|
|
32
34
|
}
|
|
35
|
+
async authorizedUser() {
|
|
36
|
+
if (!this.options.credentials) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const user = await getUserInfo(this.options.credentials);
|
|
41
|
+
return user === null || user === void 0 ? void 0 : user.id;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
debug('Unable to fetch user info, %O', err);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
33
48
|
withScriptId(scriptId) {
|
|
34
49
|
if (this.options.project) {
|
|
35
50
|
throw new Error('Science project already set, create new instance instead');
|
|
@@ -65,7 +80,8 @@ export async function initClaspInstance(options) {
|
|
|
65
80
|
ignoreFilePath: ignoreFile,
|
|
66
81
|
ignorePatterns: ignoreRules,
|
|
67
82
|
filePushOrder: [],
|
|
68
|
-
|
|
83
|
+
skipSubdirectories: false,
|
|
84
|
+
fileExtensions: readFileExtensions({}),
|
|
69
85
|
},
|
|
70
86
|
});
|
|
71
87
|
}
|
|
@@ -74,7 +90,7 @@ export async function initClaspInstance(options) {
|
|
|
74
90
|
const ignoreRules = await loadIgnoreFileOrDefaults(ignoreFile);
|
|
75
91
|
const content = await fs.readFile(projectRoot.configPath, { encoding: 'utf8' });
|
|
76
92
|
const config = JSON.parse(content);
|
|
77
|
-
const
|
|
93
|
+
const fileExtensions = readFileExtensions(config);
|
|
78
94
|
const filePushOrder = config.filePushOrder || [];
|
|
79
95
|
const contentDir = path.resolve(projectRoot.rootDir, config.srcDir || config.rootDir || '.');
|
|
80
96
|
return new Clasp({
|
|
@@ -86,7 +102,8 @@ export async function initClaspInstance(options) {
|
|
|
86
102
|
ignoreFilePath: ignoreFile,
|
|
87
103
|
ignorePatterns: ignoreRules,
|
|
88
104
|
filePushOrder: filePushOrder,
|
|
89
|
-
|
|
105
|
+
fileExtensions: fileExtensions,
|
|
106
|
+
skipSubdirectories: config.ignoreSubdirectories,
|
|
90
107
|
},
|
|
91
108
|
project: {
|
|
92
109
|
scriptId: config.scriptId,
|
|
@@ -95,6 +112,36 @@ export async function initClaspInstance(options) {
|
|
|
95
112
|
},
|
|
96
113
|
});
|
|
97
114
|
}
|
|
115
|
+
function readFileExtensions(config) {
|
|
116
|
+
let scriptExtensions = ['js', 'gs'];
|
|
117
|
+
let htmlExtensions = ['html'];
|
|
118
|
+
let jsonExtensions = ['json'];
|
|
119
|
+
if (config === null || config === void 0 ? void 0 : config.fileExtension) {
|
|
120
|
+
// legacy fileExtension setting
|
|
121
|
+
scriptExtensions = [config.fileExtension];
|
|
122
|
+
}
|
|
123
|
+
if (config === null || config === void 0 ? void 0 : config.scriptExtensions) {
|
|
124
|
+
scriptExtensions = ensureStringArray(config.scriptExtensions);
|
|
125
|
+
}
|
|
126
|
+
if (config === null || config === void 0 ? void 0 : config.htmlExtensions) {
|
|
127
|
+
htmlExtensions = ensureStringArray(config.htmlExtensions);
|
|
128
|
+
}
|
|
129
|
+
if (config === null || config === void 0 ? void 0 : config.jsonExtensions) {
|
|
130
|
+
jsonExtensions = ensureStringArray(config.jsonExtensions);
|
|
131
|
+
}
|
|
132
|
+
const fixupExtension = (ext) => {
|
|
133
|
+
ext = ext.toLowerCase().trim();
|
|
134
|
+
if (!ext.startsWith('.')) {
|
|
135
|
+
ext = `.${ext}`;
|
|
136
|
+
}
|
|
137
|
+
return ext;
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
SERVER_JS: scriptExtensions.map(fixupExtension),
|
|
141
|
+
HTML: htmlExtensions.map(fixupExtension),
|
|
142
|
+
JSON: jsonExtensions.map(fixupExtension),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
98
145
|
async function findProjectRootdDir(configFilePath) {
|
|
99
146
|
debug('Searching for project root');
|
|
100
147
|
if (configFilePath) {
|
package/build/src/core/files.js
CHANGED
|
@@ -59,28 +59,36 @@ function createFilenameConflictChecker() {
|
|
|
59
59
|
return file;
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
function getFileType(fileName) {
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
function getFileType(fileName, fileExtensions) {
|
|
63
|
+
var _a, _b, _c;
|
|
64
|
+
const originalExtension = path.extname(fileName);
|
|
65
|
+
const extension = originalExtension.toLowerCase();
|
|
66
|
+
if ((_a = fileExtensions['SERVER_JS']) === null || _a === void 0 ? void 0 : _a.includes(extension)) {
|
|
65
67
|
return 'SERVER_JS';
|
|
66
68
|
}
|
|
67
|
-
if (
|
|
68
|
-
return 'JSON';
|
|
69
|
-
}
|
|
70
|
-
if (extension === '.HTML') {
|
|
69
|
+
if ((_b = fileExtensions['HTML']) === null || _b === void 0 ? void 0 : _b.includes(extension)) {
|
|
71
70
|
return 'HTML';
|
|
72
71
|
}
|
|
72
|
+
if (((_c = fileExtensions['JSON']) === null || _c === void 0 ? void 0 : _c.includes(extension)) && path.basename(fileName, originalExtension) === 'appsscript') {
|
|
73
|
+
return 'JSON';
|
|
74
|
+
}
|
|
73
75
|
return undefined;
|
|
74
76
|
}
|
|
75
|
-
function getFileExtension(type) {
|
|
77
|
+
function getFileExtension(type, fileExtensions) {
|
|
76
78
|
// TODO - Include project setting override
|
|
79
|
+
const extensionFor = (type, defaultValue) => {
|
|
80
|
+
if (fileExtensions[type] && fileExtensions[type][0]) {
|
|
81
|
+
return fileExtensions[type][0];
|
|
82
|
+
}
|
|
83
|
+
return defaultValue;
|
|
84
|
+
};
|
|
77
85
|
switch (type) {
|
|
78
86
|
case 'SERVER_JS':
|
|
79
|
-
return 'js';
|
|
87
|
+
return extensionFor('SERVER_JS', '.js');
|
|
80
88
|
case 'JSON':
|
|
81
|
-
return 'json';
|
|
89
|
+
return extensionFor('JSON', '.json');
|
|
82
90
|
case 'HTML':
|
|
83
|
-
return 'html';
|
|
91
|
+
return extensionFor('HTML', '.html');
|
|
84
92
|
default:
|
|
85
93
|
throw new Error('Invalid file type', {
|
|
86
94
|
cause: {
|
|
@@ -119,15 +127,16 @@ export class Files {
|
|
|
119
127
|
const contentDir = this.options.files.contentDir;
|
|
120
128
|
const scriptId = this.options.project.scriptId;
|
|
121
129
|
const script = google.script({ version: 'v1', auth: credentials });
|
|
130
|
+
const fileExtensionMap = this.options.files.fileExtensions;
|
|
122
131
|
try {
|
|
123
132
|
const requestOptions = { scriptId, versionNumber };
|
|
124
|
-
debug('
|
|
133
|
+
debug('Fetching script content, request %o', requestOptions);
|
|
125
134
|
const response = await script.projects.getContent(requestOptions);
|
|
126
135
|
const files = (_a = response.data.files) !== null && _a !== void 0 ? _a : [];
|
|
127
136
|
return files.map(f => {
|
|
128
137
|
var _a, _b, _c;
|
|
129
|
-
const ext = getFileExtension(f.type);
|
|
130
|
-
const localPath = path.relative(process.cwd(), path.resolve(contentDir, `${f.name}
|
|
138
|
+
const ext = getFileExtension(f.type, fileExtensionMap);
|
|
139
|
+
const localPath = path.relative(process.cwd(), path.resolve(contentDir, `${f.name}${ext}`));
|
|
131
140
|
const file = {
|
|
132
141
|
localPath: localPath,
|
|
133
142
|
remotePath: (_a = f.name) !== null && _a !== void 0 ? _a : undefined,
|
|
@@ -148,17 +157,18 @@ export class Files {
|
|
|
148
157
|
assertScriptConfigured(this.options);
|
|
149
158
|
const contentDir = this.options.files.contentDir;
|
|
150
159
|
const ignorePatterns = (_a = this.options.files.ignorePatterns) !== null && _a !== void 0 ? _a : [];
|
|
151
|
-
const recursive = this.options.files.
|
|
160
|
+
const recursive = !this.options.files.skipSubdirectories;
|
|
152
161
|
// Read all filenames as a flattened tree
|
|
153
162
|
// Note: filePaths contain relative paths such as "test/bar.ts", "../../src/foo.js"
|
|
154
|
-
const filelist = await getLocalFiles(contentDir, ignorePatterns, recursive);
|
|
163
|
+
const filelist = Array.from(await getLocalFiles(contentDir, ignorePatterns, recursive));
|
|
155
164
|
const checkDuplicate = createFilenameConflictChecker();
|
|
165
|
+
const fileExtensionMap = this.options.files.fileExtensions;
|
|
156
166
|
const files = await Promise.all(filelist.map(async (filename) => {
|
|
157
167
|
const localPath = path.relative(process.cwd(), path.join(contentDir, filename));
|
|
158
168
|
const resolvedPath = path.relative(contentDir, localPath);
|
|
159
169
|
const parsedPath = path.parse(resolvedPath);
|
|
160
170
|
let remotePath = path.format({ dir: normalizePath(parsedPath.dir), name: parsedPath.name });
|
|
161
|
-
const type = getFileType(localPath);
|
|
171
|
+
const type = getFileType(localPath, fileExtensionMap);
|
|
162
172
|
if (!type) {
|
|
163
173
|
debug('Ignoring unsupported file %s', localPath);
|
|
164
174
|
return undefined;
|
|
@@ -309,6 +319,16 @@ export class Files {
|
|
|
309
319
|
handleApiError(error);
|
|
310
320
|
}
|
|
311
321
|
}
|
|
322
|
+
checkMissingFilesFromPushOrder(pushedFiles) {
|
|
323
|
+
var _a;
|
|
324
|
+
const missingFiles = [];
|
|
325
|
+
for (const path of (_a = this.options.files.filePushOrder) !== null && _a !== void 0 ? _a : []) {
|
|
326
|
+
const wasPushed = pushedFiles.find(f => f.localPath === path);
|
|
327
|
+
if (!wasPushed) {
|
|
328
|
+
missingFiles.push(path);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
312
332
|
async pull(version) {
|
|
313
333
|
debug('Pulling files');
|
|
314
334
|
assertAuthenticated(this.options);
|
|
@@ -286,8 +286,11 @@ export class Project {
|
|
|
286
286
|
scriptId: this.options.project.scriptId,
|
|
287
287
|
rootDir: srcDir,
|
|
288
288
|
projectId: this.options.project.projectId,
|
|
289
|
-
|
|
289
|
+
scriptExtensions: this.options.files.fileExtensions['SERVER_JS'],
|
|
290
|
+
htmlExtensions: this.options.files.fileExtensions['HTML'],
|
|
291
|
+
jsonExtensions: this.options.files.fileExtensions['JSON'],
|
|
290
292
|
filePushOrder: [],
|
|
293
|
+
skipSubdirectories: this.options.files.skipSubdirectories,
|
|
291
294
|
};
|
|
292
295
|
await fs.writeFile(this.options.configFilePath, JSON.stringify(settings, null, 2));
|
|
293
296
|
}
|
package/build/src/core/utils.js
CHANGED
|
@@ -100,7 +100,7 @@ export function handleApiError(error) {
|
|
|
100
100
|
throw new Error('Unexpected error', {
|
|
101
101
|
cause: {
|
|
102
102
|
code: 'UNEPECTED_ERROR',
|
|
103
|
-
message: String(error),
|
|
103
|
+
message: new String(error),
|
|
104
104
|
error: error,
|
|
105
105
|
},
|
|
106
106
|
});
|
|
@@ -119,3 +119,26 @@ export function handleApiError(error) {
|
|
|
119
119
|
},
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
|
+
export function ensureStringArray(value) {
|
|
123
|
+
if (typeof value === 'string') {
|
|
124
|
+
return [value];
|
|
125
|
+
}
|
|
126
|
+
else if (Array.isArray(value)) {
|
|
127
|
+
// Ensure all elements in the array are strings.
|
|
128
|
+
if (value.every(item => typeof item === 'string')) {
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Handle cases where the array contains non-string elements.
|
|
133
|
+
// You could throw an error, filter out non-strings, or convert them to strings.
|
|
134
|
+
// Example: filter out non-strings
|
|
135
|
+
return value.filter(item => typeof item === 'string');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Handle cases where the value is neither a string nor an array of strings.
|
|
140
|
+
// You could throw an error or return an empty array.
|
|
141
|
+
// Example: return an empty array
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function isEnabled(experimentName, defaultValue = false) {
|
|
2
|
+
const envVarName = `CLASP_${experimentName.toUpperCase()}`;
|
|
3
|
+
const envVarValue = process.env[envVarName];
|
|
4
|
+
if (envVarValue === undefined || envVarValue === null) {
|
|
5
|
+
return defaultValue;
|
|
6
|
+
}
|
|
7
|
+
if (envVarValue.toLowerCase() === 'true' || envVarValue === '1') {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (envVarValue.toLowerCase() === 'false' || envVarValue === '0') {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// If it's not a boolean, return the raw string value (for string experiments)
|
|
14
|
+
return envVarValue;
|
|
15
|
+
}
|
|
16
|
+
export const INCLUDE_USER_HINT_IN_URL = isEnabled('enable_user_hints');
|
package/build/src/index.js
CHANGED
|
@@ -22,6 +22,8 @@ import Debug from 'debug';
|
|
|
22
22
|
import loudRejection from 'loud-rejection';
|
|
23
23
|
import { makeProgram } from './commands/program.js';
|
|
24
24
|
const debug = Debug('clasp:cli');
|
|
25
|
+
// Suppress warnings about punycode and other issues caused by dependencies
|
|
26
|
+
process.removeAllListeners('warning');
|
|
25
27
|
// Ensure any unhandled exception won't go unnoticed
|
|
26
28
|
loudRejection();
|
|
27
29
|
const program = makeProgram();
|
package/build/src/intl.js
CHANGED
|
@@ -25,10 +25,12 @@ function loadMessages(_locale) {
|
|
|
25
25
|
}
|
|
26
26
|
const cache = createIntlCache();
|
|
27
27
|
const locale = getLocale();
|
|
28
|
+
const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
28
29
|
debug('Using locale: %s', locale);
|
|
29
30
|
export const intl = createIntl({
|
|
30
31
|
// Locale of the application
|
|
31
32
|
locale,
|
|
33
|
+
timeZone: localTimeZone,
|
|
32
34
|
defaultLocale: 'en',
|
|
33
35
|
messages: loadMessages(locale),
|
|
34
36
|
}, cache);
|
package/docs/run.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
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
|
-
- Create an **OAuth Client ID**
|
|
11
|
-
- `clasp login --creds
|
|
10
|
+
- Create an **OAuth Client ID** of type `Desktop Application`. Download as `client_secret.json`.
|
|
11
|
+
- `clasp login --creds client_secret.json` with this downloaded file.
|
|
12
12
|
- Add the following to `appsscript.json`:
|
|
13
13
|
```json
|
|
14
14
|
"executionApi": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@google/clasp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2-alpha",
|
|
4
4
|
"description": "Develop Apps Script Projects locally",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./build/src/index.js",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"watch": "tspc --project tsconfig.json --watch",
|
|
22
22
|
"prepare": "npm run compile",
|
|
23
23
|
"lint": "npm run check",
|
|
24
|
-
"test": "
|
|
25
|
-
"coverage": "
|
|
24
|
+
"test": "mocha",
|
|
25
|
+
"test:coverage": "c8 mocha",
|
|
26
26
|
"prettier": "biome format src test --write",
|
|
27
27
|
"check": "biome check src test && npm run compile",
|
|
28
28
|
"clean": "rm -rf build",
|
|
@@ -39,21 +39,6 @@
|
|
|
39
39
|
],
|
|
40
40
|
"outfile": "src/messages/messages.js"
|
|
41
41
|
},
|
|
42
|
-
"nyc": {
|
|
43
|
-
"extends": "@istanbuljs/nyc-config-typescript",
|
|
44
|
-
"include": [
|
|
45
|
-
"src/**/*.ts"
|
|
46
|
-
],
|
|
47
|
-
"extension": [
|
|
48
|
-
".ts"
|
|
49
|
-
],
|
|
50
|
-
"reporter": [
|
|
51
|
-
"text-summary",
|
|
52
|
-
"html"
|
|
53
|
-
],
|
|
54
|
-
"sourceMap": true,
|
|
55
|
-
"instrument": true
|
|
56
|
-
},
|
|
57
42
|
"repository": {
|
|
58
43
|
"type": "git",
|
|
59
44
|
"url": "https://github.com/google/clasp"
|
|
@@ -130,10 +115,10 @@
|
|
|
130
115
|
"@types/sinon": "^17.0.4",
|
|
131
116
|
"@types/tmp": "^0.2.6",
|
|
132
117
|
"@types/wtfnode": "^0.7.3",
|
|
118
|
+
"c8": "^10.1.3",
|
|
133
119
|
"chai": "^5.1.2",
|
|
134
120
|
"chai-as-promised": "^8.0.1",
|
|
135
121
|
"chai-subset": "^1.6.0",
|
|
136
|
-
"coveralls": "^3.1.1",
|
|
137
122
|
"mocha": "^11.1.0",
|
|
138
123
|
"mock-fs": "^5.4.1",
|
|
139
124
|
"nock": "^14.0.0",
|