From 47b0ba8b109b902cad9f2cbb49227f7c5e9626d9 Mon Sep 17 00:00:00 2001 From: Maxmillion McLaughlin Date: Fri, 15 Nov 2019 14:03:58 -0700 Subject: [PATCH 1/5] Add `--account ` flag to `clasp open` Allows the `clasp open` command to open a script's url using a specified email or user number --- README.md | 5 ++++- src/commands/openCmd.ts | 21 ++++++++++++++++++--- src/index.ts | 1 + src/urls.ts | 2 +- src/utils.ts | 15 ++++++++++++++- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e56bcd69..e9e0a206 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ clasp - [`clasp pull [--versionNumber]`](#pull) - [`clasp push [--watch] [--force]`](#push) - [`clasp status [--json]`](#status) -- [`clasp open [scriptId] [--webapp] [--creds]`](#open) +- [`clasp open [scriptId] [--webapp] [--creds] [--account ]`](#open) - [`clasp deployments`](#deployments) - [`clasp deploy [--versionNumber ] [--description ] [--deploymentId ]`](#deploy) - [`clasp undeploy [deploymentId] [--all]`](#undeploy) @@ -247,6 +247,7 @@ Opens the current directory's `clasp` project on script.google.com. Provide a `s - `[scriptId]`: The optional script project to open. - `--webapp`: open web application in a browser. - `--creds`: Open the URL to create credentials. +- `--account `: Open script using specific email or Google account number. #### Examples @@ -254,6 +255,8 @@ Opens the current directory's `clasp` project on script.google.com. Provide a `s - `clasp open "15ImUCpyi1Jsd8yF8Z6wey_7cw793CymWTLxOqwMka3P1CzE5hQun6qiC"` - `clasp open --webapp` - `clasp open --creds` +- `clasp open --account user@example.com` +- `clasp open --account 1` ### Deployments diff --git a/src/commands/openCmd.ts b/src/commands/openCmd.ts index 5f8319ed..9fc069c6 100644 --- a/src/commands/openCmd.ts +++ b/src/commands/openCmd.ts @@ -2,7 +2,7 @@ import open from 'open'; import { loadAPICredentials, script } from '../auth'; import { deploymentIdPrompt } from '../inquirer'; import { URL } from '../urls'; -import { ERROR, LOG, getProjectSettings, getWebApplicationURL, logError } from '../utils'; +import { ERROR, LOG, getProjectSettings, getWebApplicationURL, logError, isValidEmail } from '../utils'; interface EllipizeOptions { ellipse?: string; @@ -16,12 +16,14 @@ const ellipsize: (str?: string, max?: number, opts?: EllipizeOptions) => string * @param scriptId {string} The Apps Script project to open. * @param cmd.webapp {boolean} If true, the command will open the webapps URL. * @param cmd.creds {boolean} If true, the command will open the credentials URL. + * @param cmd.account {string} Email or user number authenticate with when opening */ export default async ( scriptId: string, cmd: { webapp: boolean; creds: boolean; + account: string; }, ) => { const projectSettings = await getProjectSettings(); @@ -39,8 +41,21 @@ export default async ( // If we're not a web app, open the script URL. if (!cmd.webapp) { - console.log(LOG.OPEN_PROJECT(scriptId)); - return open(URL.SCRIPT(scriptId), { wait: false }); + // If we should open script with a specific account + if (cmd.account) { + // Confirm account looks like an email address + if (cmd.account.length > 2 && !isValidEmail(cmd.account)) { + logError(null, ERROR.EMAIL_INCORRECT(cmd.account)); + } + + // Check if account is number + if (cmd.account.length < 3 && isNaN(Number(cmd.account))) { + logError(null, ERROR.ACCOUNT_INCORRECT(cmd.account)); + } + } + + console.log(LOG.OPEN_PROJECT(scriptId, cmd.account)); + return open(URL.SCRIPT(scriptId, cmd.account), { wait: false }); } // Web app: Otherwise, open the latest deployment. diff --git a/src/index.ts b/src/index.ts index 897380e9..652c36ac 100755 --- a/src/index.ts +++ b/src/index.ts @@ -179,6 +179,7 @@ commander .description('Open a script') .option('--webapp', 'Open web application in the browser') .option('--creds', 'Open the URL to create credentials') + .option('--account ', 'Authenticate with specific email when opening') .action(handleError(openCmd)); /** diff --git a/src/urls.ts b/src/urls.ts index 68cb76ae..a945f9eb 100644 --- a/src/urls.ts +++ b/src/urls.ts @@ -32,6 +32,6 @@ export const URL = { `https://console.cloud.google.com/logs/viewer?project=${projectId}&resource=app_script_function`, SCRIPT_API_USER: 'https://script.google.com/home/usersettings', // It is too expensive to get the script URL from the Drive API. (Async/not offline) - SCRIPT: (scriptId: string) => `https://script.google.com/d/${scriptId}/edit`, + SCRIPT: (scriptId: string, account?: string) => `https://script.google.com/d/${scriptId}/edit${typeof account === 'undefined' ? '' : `?authuser=${account}`}`, DRIVE: (driveId: string) => `https://drive.google.com/open?id=${driveId}`, }; diff --git a/src/utils.ts b/src/utils.ts index 19555aae..5f969cc4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -59,6 +59,8 @@ export function getOAuthSettings(local: boolean): Promise { // Error messages (some errors take required params) export const ERROR = { ACCESS_TOKEN: `Error retrieving access token: `, + ACCOUNT_INCORRECT: (account: string) => `The account "${account}" looks incorrect. +Is this an email or user number?`, BAD_CREDENTIALS_FILE: 'Incorrect credentials file format.', BAD_REQUEST: (message: string) => `Error: ${message} Your credentials may be invalid. Try logging in again.`, @@ -71,6 +73,8 @@ Forgot ${PROJECT_NAME} commands? Get help:\n ${PROJECT_NAME} --help`, CREDENTIALS_DNE: (filename: string) => `Credentials file "${filename}" not found.`, DEPLOYMENT_COUNT: `Unable to deploy; Scripts may only have up to 20 versioned deployments at a time.`, DRIVE: `Something went wrong with the Google Drive API`, + EMAIL_INCORRECT: (email: string) => `The email address "${email}" syntax looks incorrect. +There may be typos, did you provide a valid email?`, EXECUTE_ENTITY_NOT_FOUND: `Script API executable not published/deployed.`, FOLDER_EXISTS: `Project file (${DOT.PROJECT.PATH}) already exists.`, FS_DIR_WRITE: 'Could not create directory.', @@ -164,7 +168,7 @@ Cloned ${fileNum} ${pluralize('files', fileNum)}.`, NO_GCLOUD_PROJECT: `No projectId found. Running ${PROJECT_NAME} logs --setup.`, OPEN_CREDS: (projectId: string) => `Opening credentials page: ${URL.CREDS(projectId)}`, OPEN_LINK: (link: string) => `Open this link: ${link}`, - OPEN_PROJECT: (scriptId: string) => `Opening script: ${URL.SCRIPT(scriptId)}`, + OPEN_PROJECT: (scriptId: string, account?: string) => `Opening script: ${URL.SCRIPT(scriptId, account)}`, OPEN_WEBAPP: (deploymentId?: string) => `Opening web application: ${deploymentId}`, PULLING: 'Pulling files...', PUSH_FAILURE: 'Push failed. Errors:', @@ -425,6 +429,15 @@ export function isValidProjectId(projectId: string) { return new RegExp(/^[a-z][a-z0-9\-]{5,29}$/).test(projectId); } +/** + * Validate email address. + * @param {string} email The email address. + * @returns {boolean} Is the email address valid + */ +export function isValidEmail(email: string) { + return new RegExp(/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(email); +} + /** * Gets valid JSON obj or throws error. * @param str JSON string. From df667be329f0bcd4b725c5d5231989b9a02147b9 Mon Sep 17 00:00:00 2001 From: Maxmillion McLaughlin Date: Fri, 15 Nov 2019 14:08:58 -0700 Subject: [PATCH 2/5] Fix trailing whitespace linter error --- src/commands/openCmd.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/openCmd.ts b/src/commands/openCmd.ts index 9fc069c6..9bd780b1 100644 --- a/src/commands/openCmd.ts +++ b/src/commands/openCmd.ts @@ -47,7 +47,6 @@ export default async ( if (cmd.account.length > 2 && !isValidEmail(cmd.account)) { logError(null, ERROR.EMAIL_INCORRECT(cmd.account)); } - // Check if account is number if (cmd.account.length < 3 && isNaN(Number(cmd.account))) { logError(null, ERROR.ACCOUNT_INCORRECT(cmd.account)); From 1e8b49ae3d8db848fd32fa4432152a022162034d Mon Sep 17 00:00:00 2001 From: Maxmillion McLaughlin Date: Fri, 15 Nov 2019 15:05:24 -0700 Subject: [PATCH 3/5] Add test for utils isValidEmail function --- tests/utils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/utils.ts b/tests/utils.ts index 1f070ec2..38766903 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -9,6 +9,7 @@ import { import { ERROR, getValidJSON, + isValidEmail } from '../src/utils'; describe('Test getValidJSON function', () => { @@ -20,4 +21,17 @@ describe('Test getValidJSON function', () => { expect(() => getValidJSON(invalidExampleJSONString)).to.throw(ERROR.INVALID_JSON); }); after(cleanup); +}); + +describe('Test utils isValidEmail function', () => { + const validEmail = 'user@example.com'; + const invalidEmail = 'user@example'; + + it('should return true for valid combinations of input', () => { + expect(isValidEmail(validEmail)).to.be.true; + }); + + it('should return false for invalid combinations of input', () => { + expect(isValidEmail(invalidEmail)).to.be.false; + }); }); \ No newline at end of file From ce441aa04bb818c452696108787aed2e04cec04d Mon Sep 17 00:00:00 2001 From: Maxmillion McLaughlin Date: Fri, 15 Nov 2019 15:05:50 -0700 Subject: [PATCH 4/5] Add tests for `clasp open` `--account` option --- tests/commands/open.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/commands/open.ts b/tests/commands/open.ts index 1168542f..22c9a944 100644 --- a/tests/commands/open.ts +++ b/tests/commands/open.ts @@ -33,5 +33,17 @@ describe('Test clasp open function', () => { ); expect(result.stdout).to.contain('Open which deployment?'); }); + it('should open script with account email correctly', () => { + const result = spawnSync( + CLASP, ['open', '--account', 'max@example.com'], { encoding: 'utf8' }, + ); + expect(result.stdout).to.contain('?authuser=max@example.com'); + }); + it('should open script with account number correctly', () => { + const result = spawnSync( + CLASP, ['open', '--account', '1'], { encoding: 'utf8' }, + ); + expect(result.stdout).to.contain('?authuser=1'); + }); after(cleanup); }); \ No newline at end of file From 492a62cd4e9b51c56a25f8ce77d362f372db7a7f Mon Sep 17 00:00:00 2001 From: Maxmillion McLaughlin Date: Fri, 15 Nov 2019 15:10:39 -0700 Subject: [PATCH 5/5] Fix trailing comma and unused expression linter errors --- tests/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/utils.ts b/tests/utils.ts index 38766903..c1c961b6 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -9,7 +9,7 @@ import { import { ERROR, getValidJSON, - isValidEmail + isValidEmail, } from '../src/utils'; describe('Test getValidJSON function', () => { @@ -27,6 +27,8 @@ describe('Test utils isValidEmail function', () => { const validEmail = 'user@example.com'; const invalidEmail = 'user@example'; + // Disable a couple of linting rules just for these tests + // tslint:disable:no-unused-expression it('should return true for valid combinations of input', () => { expect(isValidEmail(validEmail)).to.be.true; });