@google/clasp 2.5.0 → 3.0.1-alpha1
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 +251 -196
- package/build/src/auth/auth.js +176 -0
- package/build/src/auth/auth_code_flow.js +36 -0
- package/build/src/auth/credential_store.js +1 -0
- package/build/src/auth/file_credential_store.js +82 -0
- package/build/src/auth/localhost_auth_code_flow.js +63 -0
- package/build/src/auth/serverless_auth_code_flow.js +32 -0
- package/build/src/commands/clone-script.js +71 -0
- package/build/src/commands/create-deployment.js +33 -0
- package/build/src/commands/create-script.js +75 -0
- package/build/src/commands/create-version.js +31 -0
- package/build/src/commands/delete-deployment.js +71 -0
- package/build/src/commands/disable-api.js +19 -0
- package/build/src/commands/enable-api.js +31 -0
- package/build/src/commands/list-apis.js +23 -0
- package/build/src/commands/list-deployments.js +30 -0
- package/build/src/commands/list-scripts.js +28 -0
- package/build/src/commands/list-versions.js +29 -0
- package/build/src/commands/login.js +53 -54
- package/build/src/commands/logout.js +15 -15
- package/build/src/commands/open-apis.js +11 -0
- package/build/src/commands/open-container.js +15 -0
- package/build/src/commands/open-credentials.js +11 -0
- package/build/src/commands/open-logs.js +11 -0
- package/build/src/commands/open-script.js +18 -0
- package/build/src/commands/open-webapp.js +56 -0
- package/build/src/commands/program.js +108 -0
- package/build/src/commands/pull.js +19 -18
- package/build/src/commands/push.js +64 -74
- package/build/src/commands/run-function.js +61 -0
- package/build/src/commands/setup-logs.js +12 -0
- package/build/src/commands/show-authorized-user.js +18 -0
- package/build/src/commands/show-file-status.js +34 -0
- package/build/src/commands/tail-logs.js +92 -0
- package/build/src/commands/utils.js +82 -0
- package/build/src/constants.js +0 -3
- package/build/src/{apis.js → core/apis.js} +90 -92
- package/build/src/core/clasp.js +171 -0
- package/build/src/core/files.js +359 -0
- package/build/src/core/functions.js +51 -0
- package/build/src/core/logs.js +41 -0
- package/build/src/core/manifest.js +1 -0
- package/build/src/core/project.js +313 -0
- package/build/src/core/services.js +179 -0
- package/build/src/core/utils.js +121 -0
- package/build/src/index.js +16 -355
- package/build/src/intl.js +34 -0
- package/docs/README.md +0 -5
- package/docs/config-files.md +0 -1
- package/docs/run.md +2 -2
- package/package.json +86 -51
- package/CHANGELOG.md +0 -79
- package/build/src/apis.d.ts +0 -34
- package/build/src/apis.js.map +0 -1
- package/build/src/apiutils.d.ts +0 -16
- package/build/src/apiutils.js +0 -81
- package/build/src/apiutils.js.map +0 -1
- package/build/src/auth.d.ts +0 -37
- package/build/src/auth.js +0 -310
- package/build/src/auth.js.map +0 -1
- package/build/src/clasp-error.d.ts +0 -3
- package/build/src/clasp-error.js +0 -10
- package/build/src/clasp-error.js.map +0 -1
- package/build/src/commands/apis.d.ts +0 -10
- package/build/src/commands/apis.js +0 -90
- package/build/src/commands/apis.js.map +0 -1
- package/build/src/commands/clone.d.ts +0 -13
- package/build/src/commands/clone.js +0 -60
- package/build/src/commands/clone.js.map +0 -1
- package/build/src/commands/create.d.ts +0 -16
- package/build/src/commands/create.js +0 -81
- package/build/src/commands/create.js.map +0 -1
- package/build/src/commands/default.d.ts +0 -8
- package/build/src/commands/default.js +0 -10
- package/build/src/commands/default.js.map +0 -1
- package/build/src/commands/deploy.d.ts +0 -13
- package/build/src/commands/deploy.js +0 -51
- package/build/src/commands/deploy.js.map +0 -1
- package/build/src/commands/deployments.d.ts +0 -5
- package/build/src/commands/deployments.js +0 -29
- package/build/src/commands/deployments.js.map +0 -1
- package/build/src/commands/list.d.ts +0 -9
- package/build/src/commands/list.js +0 -34
- package/build/src/commands/list.js.map +0 -1
- package/build/src/commands/login.d.ts +0 -15
- package/build/src/commands/login.js.map +0 -1
- package/build/src/commands/logout.d.ts +0 -5
- package/build/src/commands/logout.js.map +0 -1
- package/build/src/commands/logs.d.ts +0 -17
- package/build/src/commands/logs.js +0 -181
- package/build/src/commands/logs.js.map +0 -1
- package/build/src/commands/open.d.ts +0 -15
- package/build/src/commands/open.js +0 -89
- package/build/src/commands/open.js.map +0 -1
- package/build/src/commands/pull.d.ts +0 -10
- package/build/src/commands/pull.js.map +0 -1
- package/build/src/commands/push.d.ts +0 -11
- package/build/src/commands/push.js.map +0 -1
- package/build/src/commands/run.d.ts +0 -14
- package/build/src/commands/run.js +0 -130
- package/build/src/commands/run.js.map +0 -1
- package/build/src/commands/setting.d.ts +0 -8
- package/build/src/commands/setting.js +0 -53
- package/build/src/commands/setting.js.map +0 -1
- package/build/src/commands/status.d.ts +0 -9
- package/build/src/commands/status.js +0 -25
- package/build/src/commands/status.js.map +0 -1
- package/build/src/commands/undeploy.d.ts +0 -9
- package/build/src/commands/undeploy.js +0 -55
- package/build/src/commands/undeploy.js.map +0 -1
- package/build/src/commands/version.d.ts +0 -5
- package/build/src/commands/version.js +0 -22
- package/build/src/commands/version.js.map +0 -1
- package/build/src/commands/versions.d.ts +0 -5
- package/build/src/commands/versions.js +0 -41
- package/build/src/commands/versions.js.map +0 -1
- package/build/src/conf.d.ts +0 -40
- package/build/src/conf.js +0 -100
- package/build/src/conf.js.map +0 -1
- package/build/src/constants.d.ts +0 -6
- package/build/src/constants.js.map +0 -1
- package/build/src/dotfile.d.ts +0 -50
- package/build/src/dotfile.js +0 -71
- package/build/src/dotfile.js.map +0 -1
- package/build/src/files.d.ts +0 -70
- package/build/src/files.js +0 -364
- package/build/src/files.js.map +0 -1
- package/build/src/index.d.ts +0 -18
- package/build/src/index.js.map +0 -1
- package/build/src/inquirer.d.ts +0 -82
- package/build/src/inquirer.js +0 -111
- package/build/src/inquirer.js.map +0 -1
- package/build/src/manifest.d.ts +0 -123
- package/build/src/manifest.js +0 -142
- package/build/src/manifest.js.map +0 -1
- package/build/src/messages.d.ts +0 -110
- package/build/src/messages.js +0 -161
- package/build/src/messages.js.map +0 -1
- package/build/src/urls.d.ts +0 -21
- package/build/src/urls.js +0 -33
- package/build/src/urls.js.map +0 -1
- package/build/src/utils.d.ts +0 -102
- package/build/src/utils.js +0 -232
- package/build/src/utils.js.map +0 -1
- package/docs/develop.md +0 -94
- package/docs/esmodules.md +0 -81
- package/docs/running-locally.md +0 -31
- package/docs/settings.md +0 -56
- package/docs/typescript.md +0 -354
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { GoogleAuth, OAuth2Client } from 'google-auth-library';
|
|
5
|
+
import { google } from 'googleapis';
|
|
6
|
+
import { FileCredentialStore } from './file_credential_store.js';
|
|
7
|
+
import { LocalServerAuthorizationCodeFlow } from './localhost_auth_code_flow.js';
|
|
8
|
+
import { ServerlessAuthorizationCodeFlow } from './serverless_auth_code_flow.js';
|
|
9
|
+
export async function initAuth(options) {
|
|
10
|
+
var _a, _b, _c;
|
|
11
|
+
const authFilePath = (_a = options.authFilePath) !== null && _a !== void 0 ? _a : path.join(os.homedir(), '.clasprc.json');
|
|
12
|
+
const credentialStore = new FileCredentialStore(authFilePath);
|
|
13
|
+
if (options.useApplicationDefaultCredentials) {
|
|
14
|
+
const credentials = await createApplicationDefaultCredentials();
|
|
15
|
+
return {
|
|
16
|
+
credentials,
|
|
17
|
+
credentialStore,
|
|
18
|
+
user: (_b = options.userKey) !== null && _b !== void 0 ? _b : 'default',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const credentials = await getAuthorizedOAuth2Client(credentialStore, options.userKey);
|
|
22
|
+
return {
|
|
23
|
+
credentials,
|
|
24
|
+
credentialStore,
|
|
25
|
+
user: (_c = options.userKey) !== null && _c !== void 0 ? _c : 'default',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function getUserInfo(credentials) {
|
|
29
|
+
const api = google.oauth2('v2');
|
|
30
|
+
const res = await api.userinfo.get({ auth: credentials });
|
|
31
|
+
if (res.status !== 200) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
email: res.data.email,
|
|
36
|
+
id: res.data.id,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Creates an an unauthorized oauth2 client given the client secret file. If no path is provided,
|
|
41
|
+
* teh default client is returned.
|
|
42
|
+
* @param clientSecretPath
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
export function getUnauthorizedOuth2Client(clientSecretPath) {
|
|
46
|
+
if (clientSecretPath) {
|
|
47
|
+
return createOauthClient(clientSecretPath);
|
|
48
|
+
}
|
|
49
|
+
return createDefaultOAuthClient();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create an authorized oauth2 client from saved credentials.
|
|
53
|
+
* @param userKey
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
56
|
+
export async function getAuthorizedOAuth2Client(store, userKey) {
|
|
57
|
+
if (!userKey) {
|
|
58
|
+
userKey = 'default';
|
|
59
|
+
}
|
|
60
|
+
const savedCredentials = await store.load(userKey);
|
|
61
|
+
if (!savedCredentials) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const client = new GoogleAuth().fromJSON(savedCredentials);
|
|
65
|
+
client.setCredentials(savedCredentials);
|
|
66
|
+
client.on('tokens', async (tokens) => {
|
|
67
|
+
const refreshedCredentials = {
|
|
68
|
+
...savedCredentials,
|
|
69
|
+
expiry_date: tokens.expiry_date,
|
|
70
|
+
access_token: tokens.access_token,
|
|
71
|
+
id_token: tokens.access_token,
|
|
72
|
+
};
|
|
73
|
+
await store.save(userKey, refreshedCredentials);
|
|
74
|
+
});
|
|
75
|
+
return client;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Requests authorization to manage Apps Script projects.
|
|
79
|
+
* @param {boolean} useLocalhost Uses a local HTTP server if true. Manual entry o.w.
|
|
80
|
+
* @param {ClaspCredentials?} creds An optional credentials object.
|
|
81
|
+
* @param {string[]} [scopes=[]] List of OAuth scopes to authorize.
|
|
82
|
+
* @param {number?} redirectPort Optional custom port for the local HTTP server during the authorization process.
|
|
83
|
+
* If not specified, a random available port will be used.
|
|
84
|
+
*/
|
|
85
|
+
export async function authorize(options) {
|
|
86
|
+
let flow;
|
|
87
|
+
if (options.noLocalServer) {
|
|
88
|
+
flow = new ServerlessAuthorizationCodeFlow(options.oauth2Client);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
flow = new LocalServerAuthorizationCodeFlow(options.oauth2Client);
|
|
92
|
+
}
|
|
93
|
+
const client = await flow.authorize(options.scopes);
|
|
94
|
+
await saveOauthClientCredentials(options.store, options.userKey, client);
|
|
95
|
+
return client;
|
|
96
|
+
}
|
|
97
|
+
async function saveOauthClientCredentials(store, userKey, oauth2Client) {
|
|
98
|
+
var _a, _b;
|
|
99
|
+
const savedCredentials = {
|
|
100
|
+
client_id: oauth2Client._clientId,
|
|
101
|
+
client_secret: oauth2Client._clientSecret,
|
|
102
|
+
type: 'authorized_user',
|
|
103
|
+
refresh_token: (_a = oauth2Client.credentials.refresh_token) !== null && _a !== void 0 ? _a : undefined,
|
|
104
|
+
access_token: (_b = oauth2Client.credentials.access_token) !== null && _b !== void 0 ? _b : undefined,
|
|
105
|
+
};
|
|
106
|
+
oauth2Client.on('tokens', async (tokens) => {
|
|
107
|
+
const refreshedCredentials = {
|
|
108
|
+
...savedCredentials,
|
|
109
|
+
expiry_date: tokens.expiry_date,
|
|
110
|
+
access_token: tokens.access_token,
|
|
111
|
+
id_token: tokens.access_token,
|
|
112
|
+
};
|
|
113
|
+
await store.save(userKey, refreshedCredentials);
|
|
114
|
+
});
|
|
115
|
+
await store.save(userKey, savedCredentials);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Creates an aunthorized oauth2 client with the given credentials
|
|
119
|
+
* @param clientSecretPath
|
|
120
|
+
* @returns
|
|
121
|
+
*/
|
|
122
|
+
function createOauthClient(clientSecretPath) {
|
|
123
|
+
if (!clientSecretPath) {
|
|
124
|
+
throw new Error('Invalid credentials');
|
|
125
|
+
}
|
|
126
|
+
const contents = readFileSync(clientSecretPath);
|
|
127
|
+
const keyFile = JSON.parse(contents.toString());
|
|
128
|
+
const keys = keyFile.installed || keyFile.web;
|
|
129
|
+
if (!keys.redirect_uris || keys.redirect_uris.length === 0) {
|
|
130
|
+
throw new Error('Invalid redirect URL');
|
|
131
|
+
}
|
|
132
|
+
const redirectUrl = keys.redirect_uris.find((uri) => new URL(uri).hostname === 'localhost');
|
|
133
|
+
if (!redirectUrl) {
|
|
134
|
+
throw new Error('No localhost redirect URL found');
|
|
135
|
+
}
|
|
136
|
+
// create an oAuth client to authorize the API call
|
|
137
|
+
return new OAuth2Client({
|
|
138
|
+
clientId: keys.client_id,
|
|
139
|
+
clientSecret: keys.client_secret,
|
|
140
|
+
redirectUri: redirectUrl,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Creates an aunthorized oauth2 client using the default id & secret.
|
|
145
|
+
* @param clientSecretPath
|
|
146
|
+
* @returns
|
|
147
|
+
*/
|
|
148
|
+
function createDefaultOAuthClient() {
|
|
149
|
+
// Default client
|
|
150
|
+
return new OAuth2Client({
|
|
151
|
+
clientId: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com',
|
|
152
|
+
clientSecret: 'v6V3fKV_zWU7iw1DrpO1rknX',
|
|
153
|
+
redirectUri: 'http://localhost',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export async function createApplicationDefaultCredentials() {
|
|
157
|
+
const defaultCreds = await new GoogleAuth({
|
|
158
|
+
scopes: [
|
|
159
|
+
'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments
|
|
160
|
+
'https://www.googleapis.com/auth/script.projects', // Apps Script management
|
|
161
|
+
'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps
|
|
162
|
+
'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata
|
|
163
|
+
'https://www.googleapis.com/auth/drive.file', // Create Drive files
|
|
164
|
+
'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API
|
|
165
|
+
'https://www.googleapis.com/auth/logging.read', // StackDriver logs
|
|
166
|
+
'https://www.googleapis.com/auth/userinfo.email', // User email address
|
|
167
|
+
'https://www.googleapis.com/auth/userinfo.profile',
|
|
168
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
|
169
|
+
],
|
|
170
|
+
}).getClient();
|
|
171
|
+
// Remove this check after https://github.com/googleapis/google-auth-library-nodejs/issues/1677 fixed
|
|
172
|
+
if (defaultCreds instanceof OAuth2Client) {
|
|
173
|
+
return defaultCreds;
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class AuthorizationCodeFlow {
|
|
2
|
+
constructor(oauth2client) {
|
|
3
|
+
this.oauth2Client = oauth2client;
|
|
4
|
+
}
|
|
5
|
+
async authorize(scopes) {
|
|
6
|
+
const scope = Array.isArray(scopes) ? scopes.join(' ') : scopes;
|
|
7
|
+
const redirectUri = await this.getRedirectUri();
|
|
8
|
+
const authUrl = this.oauth2Client.generateAuthUrl({
|
|
9
|
+
redirect_uri: redirectUri,
|
|
10
|
+
access_type: 'offline',
|
|
11
|
+
scope: scope,
|
|
12
|
+
});
|
|
13
|
+
const code = await this.promptAndReturnCode(authUrl);
|
|
14
|
+
const tokens = await this.oauth2Client.getToken({
|
|
15
|
+
code,
|
|
16
|
+
redirect_uri: redirectUri,
|
|
17
|
+
});
|
|
18
|
+
this.oauth2Client.setCredentials(tokens.tokens);
|
|
19
|
+
return this.oauth2Client;
|
|
20
|
+
}
|
|
21
|
+
async getRedirectUri() {
|
|
22
|
+
throw new Error('Not implemented');
|
|
23
|
+
}
|
|
24
|
+
async promptAndReturnCode(_authorizationUrl) {
|
|
25
|
+
throw new Error('Not implemented');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function parseAuthResponseUrl(url) {
|
|
29
|
+
const urlParts = new URL(url, 'http://localhost/').searchParams;
|
|
30
|
+
const code = urlParts.get('code');
|
|
31
|
+
const error = urlParts.get('error');
|
|
32
|
+
return {
|
|
33
|
+
code,
|
|
34
|
+
error,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
function hasLegacyLocalCredentials(store) {
|
|
3
|
+
return store.token && store.oauth2ClientSettings;
|
|
4
|
+
}
|
|
5
|
+
function hasLegacyGlobalCredentials(store) {
|
|
6
|
+
return !!store.access_token;
|
|
7
|
+
}
|
|
8
|
+
export class FileCredentialStore {
|
|
9
|
+
constructor(filePath) {
|
|
10
|
+
this.filePath = filePath;
|
|
11
|
+
}
|
|
12
|
+
async save(user, credentials) {
|
|
13
|
+
const store = this.readFile();
|
|
14
|
+
if (!store.tokens) {
|
|
15
|
+
store.tokens = {};
|
|
16
|
+
}
|
|
17
|
+
store.tokens[user] = credentials;
|
|
18
|
+
this.writeFile(store);
|
|
19
|
+
}
|
|
20
|
+
async delete(user) {
|
|
21
|
+
let store = this.readFile();
|
|
22
|
+
if (!store.tokens) {
|
|
23
|
+
store.tokens = {};
|
|
24
|
+
}
|
|
25
|
+
store.tokens[user] = undefined;
|
|
26
|
+
if (user === 'default') {
|
|
27
|
+
// Remove legacy keys if default user
|
|
28
|
+
store = {
|
|
29
|
+
tokens: store.tokens,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
this.writeFile(store);
|
|
33
|
+
}
|
|
34
|
+
async deleteAll() {
|
|
35
|
+
await this.writeFile({
|
|
36
|
+
tokens: {},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async load(user) {
|
|
40
|
+
var _a, _b, _c;
|
|
41
|
+
const store = this.readFile();
|
|
42
|
+
const credentials = (_a = store.tokens) === null || _a === void 0 ? void 0 : _a[user];
|
|
43
|
+
if (credentials) {
|
|
44
|
+
return credentials;
|
|
45
|
+
}
|
|
46
|
+
if (user !== 'default') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (hasLegacyLocalCredentials(store)) {
|
|
50
|
+
// Support previous un
|
|
51
|
+
return {
|
|
52
|
+
type: 'authorized_user',
|
|
53
|
+
...store.token,
|
|
54
|
+
client_id: (_b = store.oauth2ClientSettings) === null || _b === void 0 ? void 0 : _b.clientId,
|
|
55
|
+
client_secret: (_c = store.oauth2ClientSettings) === null || _c === void 0 ? void 0 : _c.clientSecret,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (hasLegacyGlobalCredentials(store)) {
|
|
59
|
+
return {
|
|
60
|
+
type: 'authorized_user',
|
|
61
|
+
access_token: store.access_token,
|
|
62
|
+
refresh_token: store.refresh_token,
|
|
63
|
+
expiry_date: store.exprity_date,
|
|
64
|
+
token_type: store.token_type,
|
|
65
|
+
client_id: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com',
|
|
66
|
+
client_secret: 'v6V3fKV_zWU7iw1DrpO1rknX',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
readFile() {
|
|
72
|
+
if (fs.existsSync(this.filePath)) {
|
|
73
|
+
// TODO - use promises
|
|
74
|
+
const content = fs.readFileSync(this.filePath, { encoding: 'utf8' });
|
|
75
|
+
return JSON.parse(content);
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
writeFile(store) {
|
|
80
|
+
fs.writeFileSync(this.filePath, JSON.stringify(store, null, 2));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import enableDestroy from 'server-destroy';
|
|
3
|
+
import { intl } from '../intl.js';
|
|
4
|
+
import { AuthorizationCodeFlow, parseAuthResponseUrl } from './auth_code_flow.js';
|
|
5
|
+
import open from 'open';
|
|
6
|
+
export class LocalServerAuthorizationCodeFlow extends AuthorizationCodeFlow {
|
|
7
|
+
constructor(oauth2client) {
|
|
8
|
+
super(oauth2client);
|
|
9
|
+
this.port = 0;
|
|
10
|
+
}
|
|
11
|
+
async getRedirectUri() {
|
|
12
|
+
this.server = await new Promise((resolve, reject) => {
|
|
13
|
+
const s = createServer();
|
|
14
|
+
enableDestroy(s);
|
|
15
|
+
s.listen(this.port, () => resolve(s)).on('error', (err) => {
|
|
16
|
+
if (err.code === 'EADDRINUSE') {
|
|
17
|
+
const msg = intl.formatMessage({ id: "smVcjx", defaultMessage: [{ type: 0, value: "Error: Port " }, { type: 1, value: "port" }, { type: 0, value: " is already in use. Please specify a different port with --redirect-port" }] }, {
|
|
18
|
+
port: this.port,
|
|
19
|
+
});
|
|
20
|
+
console.error(msg);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const msg = intl.formatMessage({ id: "3X2J9l", defaultMessage: [{ type: 0, value: "Error: Unable to start the server on port " }, { type: 1, value: "port" }] }, {
|
|
24
|
+
port: this.port,
|
|
25
|
+
errorMessage: err.message,
|
|
26
|
+
});
|
|
27
|
+
console.error(msg, err.message);
|
|
28
|
+
}
|
|
29
|
+
reject(err);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
const { port } = this.server.address();
|
|
33
|
+
return `http://localhost:${port}`;
|
|
34
|
+
}
|
|
35
|
+
async promptAndReturnCode(authorizationUrl) {
|
|
36
|
+
return await new Promise((resolve, reject) => {
|
|
37
|
+
if (!this.server) {
|
|
38
|
+
reject(new Error('Server not started'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.server.on('request', (request, response) => {
|
|
42
|
+
if (!request.url) {
|
|
43
|
+
reject(new Error('Missing URL in request'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const { code, error } = parseAuthResponseUrl(request.url);
|
|
47
|
+
if (code) {
|
|
48
|
+
resolve(code);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
reject(error);
|
|
52
|
+
}
|
|
53
|
+
const msg = intl.formatMessage({ id: "ZT8LeG", defaultMessage: [{ type: 0, value: "Logged in! You may close this page." }] });
|
|
54
|
+
response.end(msg);
|
|
55
|
+
});
|
|
56
|
+
void open(authorizationUrl);
|
|
57
|
+
const msg = intl.formatMessage({ id: "NbCrKh", defaultMessage: [{ type: 0, value: "`\uD83D\uDD11 Authorize clasp by visiting this url: " }, { type: 1, value: "url" }] }, {
|
|
58
|
+
url: authorizationUrl,
|
|
59
|
+
});
|
|
60
|
+
console.log(msg);
|
|
61
|
+
}).finally(() => { var _a; return (_a = this.server) === null || _a === void 0 ? void 0 : _a.destroy(); });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { intl } from '../intl.js';
|
|
3
|
+
import { AuthorizationCodeFlow, parseAuthResponseUrl } from './auth_code_flow.js';
|
|
4
|
+
export class ServerlessAuthorizationCodeFlow extends AuthorizationCodeFlow {
|
|
5
|
+
constructor(oauth2client) {
|
|
6
|
+
super(oauth2client);
|
|
7
|
+
}
|
|
8
|
+
async getRedirectUri() {
|
|
9
|
+
return 'http://localhost:8888';
|
|
10
|
+
}
|
|
11
|
+
async promptAndReturnCode(authorizationUrl) {
|
|
12
|
+
const prompt = intl.formatMessage({ id: "Tx67iE", defaultMessage: [{ type: 0, value: "Authorize clasp by visiting the following URL on another device: " }, { type: 1, value: "url" }, { type: 0, value: " After authorization, copy the URL in the browser. Enter the URL from your browser after completing authorization" }] }, {
|
|
13
|
+
url: authorizationUrl,
|
|
14
|
+
});
|
|
15
|
+
const answer = await inquirer.prompt([
|
|
16
|
+
{
|
|
17
|
+
message: prompt,
|
|
18
|
+
name: 'url',
|
|
19
|
+
type: 'input',
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
const { code, error } = parseAuthResponseUrl(answer.url);
|
|
23
|
+
if (error) {
|
|
24
|
+
throw new Error(error);
|
|
25
|
+
}
|
|
26
|
+
if (!code) {
|
|
27
|
+
const msg = intl.formatMessage({ id: "XvVmSR", defaultMessage: [{ type: 0, value: "Missing code in responde URL" }] });
|
|
28
|
+
throw new Error(msg);
|
|
29
|
+
}
|
|
30
|
+
return code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { intl } from '../intl.js';
|
|
4
|
+
import { isInteractive, withSpinner } from './utils.js';
|
|
5
|
+
export const command = new Command('clone-script')
|
|
6
|
+
.alias('clone')
|
|
7
|
+
.description('Clone an existing script')
|
|
8
|
+
.arguments('[scriptId] [versionNumber]')
|
|
9
|
+
.option('--rootDir <rootDir>', 'Local root directory in which clasp will store your project files.')
|
|
10
|
+
.action(async function (scriptId, versionNumber) {
|
|
11
|
+
var _a;
|
|
12
|
+
let clasp = this.opts().clasp;
|
|
13
|
+
if (clasp.project.exists()) {
|
|
14
|
+
const msg = intl.formatMessage({ id: "kk5+4G", defaultMessage: [{ type: 0, value: "Project file already exists." }] });
|
|
15
|
+
this.error(msg);
|
|
16
|
+
}
|
|
17
|
+
const rootDir = this.opts().rootDir;
|
|
18
|
+
clasp.withContentDir(rootDir !== null && rootDir !== void 0 ? rootDir : '.');
|
|
19
|
+
if (scriptId) {
|
|
20
|
+
const match = scriptId.match(/https:\/\/script\.google\.com\/d\/([^/]+)\/.*/);
|
|
21
|
+
if (match) {
|
|
22
|
+
scriptId = match[1];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
scriptId = scriptId.trim();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (isInteractive()) {
|
|
29
|
+
const projects = await clasp.project.listScripts();
|
|
30
|
+
const choices = projects.results.map(file => ({
|
|
31
|
+
name: `${file.name.padEnd(20)} - https://script.google.com/d/${file.id}/edit`,
|
|
32
|
+
value: file.id,
|
|
33
|
+
}));
|
|
34
|
+
const prompt = intl.formatMessage({ id: "3aQTl3", defaultMessage: [{ type: 0, value: "Clone which script?" }] });
|
|
35
|
+
const answer = await inquirer.prompt([
|
|
36
|
+
{
|
|
37
|
+
choices: choices,
|
|
38
|
+
message: prompt,
|
|
39
|
+
name: 'scriptId',
|
|
40
|
+
pageSize: 30,
|
|
41
|
+
type: 'list',
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
scriptId = answer.scriptId;
|
|
45
|
+
}
|
|
46
|
+
if (!scriptId) {
|
|
47
|
+
const msg = intl.formatMessage({ id: "DJWCmP", defaultMessage: [{ type: 0, value: "No script ID." }] });
|
|
48
|
+
this.error(msg);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const cloningScriptMsg = intl.formatMessage({ id: "UTMHnH", defaultMessage: [{ type: 0, value: "Cloning script..." }] });
|
|
52
|
+
const files = await withSpinner(cloningScriptMsg, async () => {
|
|
53
|
+
clasp = clasp.withScriptId(scriptId);
|
|
54
|
+
const files = await clasp.files.pull(versionNumber);
|
|
55
|
+
clasp.project.updateSettings();
|
|
56
|
+
return files;
|
|
57
|
+
});
|
|
58
|
+
files.forEach(f => console.log(`└─ ${f.localPath}`));
|
|
59
|
+
const successMessage = intl.formatMessage({ id: "Hw9Gqn", defaultMessage: [{ type: 0, value: "Warning: files in subfolder are not accounted for unless you set a .claspignore file. 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
|
+
count: files.length,
|
|
61
|
+
});
|
|
62
|
+
console.log(successMessage);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (((_a = error.cause) === null || _a === void 0 ? void 0 : _a.code) === 'INVALID_ARGUMENT') {
|
|
66
|
+
const msg = intl.formatMessage({ id: "jRe7cT", defaultMessage: [{ type: 0, value: "Invalid script ID." }] });
|
|
67
|
+
this.error(msg);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { intl } from '../intl.js';
|
|
3
|
+
import { withSpinner } from './utils.js';
|
|
4
|
+
export const command = new Command('create-deployment')
|
|
5
|
+
.alias('deploy')
|
|
6
|
+
.description('Deploy a project')
|
|
7
|
+
.option('-V, --versionNumber <version>', 'The project version')
|
|
8
|
+
.option('-d, --description <description>', 'The deployment description')
|
|
9
|
+
.option('-i, --deploymentId <id>', 'The deployment ID to redeploy')
|
|
10
|
+
.action(async function (options) {
|
|
11
|
+
var _a, _b, _c;
|
|
12
|
+
const clasp = this.opts().clasp;
|
|
13
|
+
const deploymentId = options.deploymentId;
|
|
14
|
+
const description = (_a = options.description) !== null && _a !== void 0 ? _a : '';
|
|
15
|
+
const versionNumber = options.versionNumber ? Number(options.versionNumber) : undefined;
|
|
16
|
+
try {
|
|
17
|
+
const spinnerMsg = intl.formatMessage({ id: "oL8t7p", defaultMessage: [{ type: 0, value: "Deploying project..." }] });
|
|
18
|
+
const deployment = await withSpinner(spinnerMsg, async () => {
|
|
19
|
+
return await clasp.project.deploy(description, deploymentId, versionNumber);
|
|
20
|
+
});
|
|
21
|
+
const successMessage = intl.formatMessage({ id: "182gSV", defaultMessage: [{ type: 0, value: "Deployed " }, { 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" }] } } }] }, {
|
|
22
|
+
deploymentId: deployment.deploymentId,
|
|
23
|
+
version: (_b = deployment.deploymentConfig) === null || _b === void 0 ? void 0 : _b.versionNumber,
|
|
24
|
+
});
|
|
25
|
+
console.log(successMessage);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (((_c = error.cause) === null || _c === void 0 ? void 0 : _c.code) === 'INVALID_ARGUMENT') {
|
|
29
|
+
this.error(error.cause.message);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import inflection from 'inflection';
|
|
4
|
+
import { intl } from '../intl.js';
|
|
5
|
+
import { withSpinner } from './utils.js';
|
|
6
|
+
// https://developers.google.com/drive/api/v3/mime-types
|
|
7
|
+
const DRIVE_FILE_MIMETYPES = {
|
|
8
|
+
docs: 'application/vnd.google-apps.document',
|
|
9
|
+
forms: 'application/vnd.google-apps.form',
|
|
10
|
+
sheets: 'application/vnd.google-apps.spreadsheet',
|
|
11
|
+
slides: 'application/vnd.google-apps.presentation',
|
|
12
|
+
};
|
|
13
|
+
export const command = new Command('create-script')
|
|
14
|
+
.command('create')
|
|
15
|
+
.description('Create a script')
|
|
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
|
+
.option('--title <title>', 'The project title.')
|
|
18
|
+
.option('--parentId <id>', 'A project parent Id.')
|
|
19
|
+
.option('--rootDir <rootDir>', 'Local root directory in which clasp will store your project files.')
|
|
20
|
+
.action(async function (options) {
|
|
21
|
+
var _a;
|
|
22
|
+
const clasp = this.opts().clasp;
|
|
23
|
+
if (clasp.project.exists()) {
|
|
24
|
+
const msg = intl.formatMessage({ id: "kk5+4G", defaultMessage: [{ type: 0, value: "Project file already exists." }] });
|
|
25
|
+
this.error(msg);
|
|
26
|
+
}
|
|
27
|
+
// Create defaults.
|
|
28
|
+
const parentId = options.parentId;
|
|
29
|
+
const name = options.title ? options.title : getDefaultProjectName(process.cwd());
|
|
30
|
+
const type = options.type ? options.type.toLowerCase() : 'standalone';
|
|
31
|
+
const rootDir = (_a = options.rootDir) !== null && _a !== void 0 ? _a : '.';
|
|
32
|
+
clasp.withContentDir(rootDir);
|
|
33
|
+
if (type && type !== 'standalone') {
|
|
34
|
+
const mimeType = DRIVE_FILE_MIMETYPES[type];
|
|
35
|
+
if (!mimeType) {
|
|
36
|
+
const msg = intl.formatMessage({ id: "d2MBtN", defaultMessage: [{ type: 0, value: "Invalid container file type" }] });
|
|
37
|
+
this.error(msg);
|
|
38
|
+
}
|
|
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 url = `https://drive.google.com/open?id=${parentId}`;
|
|
42
|
+
const successMessage = intl.formatMessage({ id: "11VZzp", defaultMessage: [{ type: 0, value: "Created new container: [url}" }] }, {
|
|
43
|
+
url,
|
|
44
|
+
});
|
|
45
|
+
console.log(successMessage);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const spinnerMsg = intl.formatMessage({ id: "TMfpGK", defaultMessage: [{ type: 0, value: "Creating script..." }] });
|
|
49
|
+
const scriptId = await withSpinner(spinnerMsg, async () => await clasp.project.createScript(name, parentId));
|
|
50
|
+
const url = `https://script.google.com/d/${scriptId}/edit`;
|
|
51
|
+
const successMessage = intl.formatMessage({ id: "NUzw0t", defaultMessage: [{ type: 0, value: "Created new script: " }, { type: 1, value: "url" }] }, {
|
|
52
|
+
url,
|
|
53
|
+
});
|
|
54
|
+
console.log(successMessage);
|
|
55
|
+
}
|
|
56
|
+
const spinnerMsg = intl.formatMessage({ id: "UTMHnH", defaultMessage: [{ type: 0, value: "Cloning script..." }] });
|
|
57
|
+
const files = await withSpinner(spinnerMsg, async () => {
|
|
58
|
+
const files = await clasp.files.pull();
|
|
59
|
+
clasp.project.updateSettings();
|
|
60
|
+
return files;
|
|
61
|
+
});
|
|
62
|
+
files.forEach(f => console.log(`└─ ${f.localPath}`));
|
|
63
|
+
const successMessage = intl.formatMessage({ id: "Hw9Gqn", defaultMessage: [{ type: 0, value: "Warning: files in subfolder are not accounted for unless you set a .claspignore file. 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
|
+
count: files.length,
|
|
65
|
+
});
|
|
66
|
+
console.log(successMessage);
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Gets default project name.
|
|
70
|
+
* @return {string} default project name.
|
|
71
|
+
*/
|
|
72
|
+
export function getDefaultProjectName(dir) {
|
|
73
|
+
const dirName = path.basename(dir);
|
|
74
|
+
return inflection.humanize(dirName);
|
|
75
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { intl } from '../intl.js';
|
|
4
|
+
import { isInteractive, withSpinner } from './utils.js';
|
|
5
|
+
export const command = new Command('create-version')
|
|
6
|
+
.alias('version')
|
|
7
|
+
.arguments('[description]')
|
|
8
|
+
.description('Creates an immutable version of the script')
|
|
9
|
+
.action(async function (description) {
|
|
10
|
+
const clasp = this.opts().clasp;
|
|
11
|
+
if (!description && isInteractive()) {
|
|
12
|
+
const prompt = intl.formatMessage({ id: "6U9ksF", defaultMessage: [{ type: 0, value: "Give a description:" }] });
|
|
13
|
+
const answer = await inquirer.prompt([
|
|
14
|
+
{
|
|
15
|
+
default: '',
|
|
16
|
+
message: prompt,
|
|
17
|
+
name: 'description',
|
|
18
|
+
type: 'input',
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
description = answer.description;
|
|
22
|
+
}
|
|
23
|
+
const spinnerMsg = intl.formatMessage({ id: "HhBwsB", defaultMessage: [{ type: 0, value: "Creating a new version..." }] });
|
|
24
|
+
const versionNumber = await withSpinner(spinnerMsg, async () => {
|
|
25
|
+
return clasp.project.version(description);
|
|
26
|
+
});
|
|
27
|
+
const successMessage = intl.formatMessage({ id: "TVOEZz", defaultMessage: [{ type: 0, value: "Created version " }, { type: 2, value: "version", style: null }] }, {
|
|
28
|
+
version: versionNumber,
|
|
29
|
+
});
|
|
30
|
+
console.log(successMessage);
|
|
31
|
+
});
|