@google/clasp
Version:
Develop Apps Script Projects locally
132 lines (131 loc) • 6.24 kB
JavaScript
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file defines the 'login' command for the clasp CLI.
/**
* Clasp command method bodies.
*/
import { Command, InvalidOptionArgumentError } from 'commander';
import { authorize, getUnauthorizedOuth2Client, getUserInfo } from '../auth/auth.js';
import { intl } from '../intl.js';
import { validateOptionInt } from './validate.js';
const DEFAULT_SCOPES = [
// Default to clasp scopes
'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments
'https://www.googleapis.com/auth/script.projects', // Apps Script management
'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps
'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata
'https://www.googleapis.com/auth/drive.file', // Create Drive files
'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API
'https://www.googleapis.com/auth/logging.read', // StackDriver logs
'https://www.googleapis.com/auth/userinfo.email', // User email address
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/cloud-platform',
];
export const mergeScopes = (defaultScopes, projectScopes) => {
const scopes = [...defaultScopes];
if (projectScopes) {
scopes.push(...projectScopes);
}
return [...new Set(scopes)];
};
export const parseExtraScopes = (value) => {
const scopes = value.split(',').map(scope => scope.trim());
if (scopes.length === 0 || scopes.some(scope => !scope)) {
throw new InvalidOptionArgumentError('must be a comma-separated list of non-empty scopes.');
}
return scopes;
};
export const buildScopes = ({ defaultScopes, manifestScopes, useProjectScopes, includeClaspScopes, extraScopes, }) => {
let scopes = [...defaultScopes];
if (useProjectScopes) {
scopes = manifestScopes ? [...manifestScopes] : scopes;
if (includeClaspScopes) {
scopes = mergeScopes(defaultScopes, scopes);
}
}
if (extraScopes) {
scopes = mergeScopes(scopes, extraScopes);
}
return scopes;
};
export const command = new Command('login')
.description('Log in to script.google.com')
.option('--no-localhost', 'Do not run a local server, manually enter code instead')
.option('--creds <file>', 'Relative path to OAuth client secret file (from GCP).')
.option('--use-project-scopes', 'Use the scopes from the current project manifest. Used only when authorizing access for the run command.')
.option('--include-clasp-scopes', 'Include default clasp scopes in addition to project scopes. Can only be used with --use-project-scopes.')
.option('--extra-scopes <scopes>', 'Include additional OAuth scopes as a comma-separated list.', parseExtraScopes)
.option('--redirect-port <port>', 'Specify a custom port for the redirect URL.', val => validateOptionInt(val, 0, 65535))
.action(async function () {
const options = this.optsWithGlobals();
const auth = options.authInfo;
const clasp = options.clasp;
if (!auth.credentialStore) {
const msg = intl.formatMessage({ id: "u1wQGD", defaultMessage: [{ type: 0, value: "No credential store found, unable to login." }] });
this.error(msg);
}
if (auth.credentials) {
if (!options.json) {
const msg = intl.formatMessage({ id: "FTrSWo", defaultMessage: [{ type: 0, value: "Warning: You seem to already be logged in." }] });
console.error(msg);
}
}
const useLocalhost = Boolean(options.localhost);
const redirectPort = options.redirectPort;
const oauth2Client = getUnauthorizedOuth2Client(options.creds);
if (options.includeClaspScopes && !options.useProjectScopes) {
const msg = intl.formatMessage({ id: "VqKr/v", defaultMessage: [{ type: 0, value: "--include-clasp-scopes can only be used with --use-project-scopes." }] });
this.error(msg);
}
let manifestScopes;
if (options.useProjectScopes) {
const manifest = await clasp.project.readManifest();
manifestScopes = manifest.oauthScopes;
}
const scopes = buildScopes({
defaultScopes: DEFAULT_SCOPES,
manifestScopes,
useProjectScopes: options.useProjectScopes,
includeClaspScopes: options.includeClaspScopes,
extraScopes: options.extraScopes,
});
if ((options.useProjectScopes || options.extraScopes) && !options.json) {
const scopesLabel = intl.formatMessage({ id: "S0Kswv", defaultMessage: [{ type: 0, value: "Authorizing with the following scopes:" }] });
console.log('');
console.log(scopesLabel);
for (const scope of scopes) {
console.log(scope);
}
}
const credentials = await authorize({
store: auth.credentialStore,
userKey: auth.user,
oauth2Client,
scopes,
noLocalServer: !useLocalhost,
redirectPort,
});
const user = await getUserInfo(credentials);
if (options.json) {
const output = {
email: user === null || user === void 0 ? void 0 : user.email,
};
console.log(JSON.stringify(output, null, 2));
return;
}
const msg = intl.formatMessage({ id: "sZ9k34", defaultMessage: [{ type: 5, value: "email", options: { undefined: { value: [{ type: 0, value: "You are logged in as an unknown user." }] }, other: { value: [{ type: 0, value: "You are logged in as " }, { type: 1, value: "email" }, { type: 0, value: "." }] } } }] }, {
email: user === null || user === void 0 ? void 0 : user.email,
});
console.log(msg);
});