Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --account <account> flag to clasp open #715

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ clasp
- [`clasp pull [--versionNumber]`](#pull)
- [`clasp push [--watch] [--force]`](#push)
- [`clasp status [--json]`](#status)
- [`clasp open [scriptId] [--webapp] [--creds] [--account <account>]`](#open)
- [`clasp open [scriptId] [--webapp] [--creds] [--addon]`](#open)
- [`clasp deployments`](#deployments)
- [`clasp deploy [--versionNumber <version>] [--description <description>] [--deploymentId <id>]`](#deploy)
Expand Down Expand Up @@ -258,6 +259,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 <account>`: Open script using specific email or Google account number.
- `--addon`: List parent IDs and open the URL of the first one.

#### Examples
Expand All @@ -266,6 +268,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`
- `clasp open --addon`

### Deployments
Expand Down
21 changes: 17 additions & 4 deletions src/commands/openCmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import open from 'open';
import { loadAPICredentials, script } from '../auth';
import { deploymentIdPrompt } from '../inquirer';
import { URL } from '../urls';
import { ERROR, getProjectSettings, getWebApplicationURL, LOG, logError } from '../utils';
import { ERROR, getProjectSettings, getWebApplicationURL, LOG, logError, isValidEmail } from '../utils';

interface EllipizeOptions {
ellipse?: string;
Expand All @@ -17,12 +17,14 @@ import ellipsize from 'ellipsize';
* @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;
addon: boolean;
},
): Promise<void> => {
Expand Down Expand Up @@ -59,9 +61,20 @@ export default async (

// If we're not a web app, open the script URL.
if (!cmd.webapp) {
console.log(LOG.OPEN_PROJECT(scriptId));
await open(URL.SCRIPT(scriptId));
return;
// 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.
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ commander
.description('Open a script')
.option('--webapp', 'Open web application in the browser')
.option('--creds', 'Open the URL to create credentials')
.option('--account <email>', 'Authenticate with specific email when opening')
.option('--addon', 'List parent IDs and open the URL of the first one')
.action(handleError(openCmd));

Expand Down
2 changes: 1 addition & 1 deletion src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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}`,
};
23 changes: 18 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ export function getOAuthSettings(local: boolean): Promise<ClaspToken> {

// Error messages (some errors take required params)
export const ERROR = {
ACCESS_TOKEN: 'Error retrieving access token: ',
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.`,
Expand All @@ -69,9 +71,11 @@ Forgot ${PROJECT_NAME} commands? Get help:\n ${PROJECT_NAME} --help`,
CREATE_WITH_PARENT: 'Did you provide the correct parentId?',
CREATE: 'Error creating script.',
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',
EXECUTE_ENTITY_NOT_FOUND: 'Script API executable not published/deployed.',
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.',
FS_FILE_WRITE: 'Could not write file.',
Expand Down Expand Up @@ -165,7 +169,7 @@ Cloned ${fileNum} ${fileNum === 1 ? 'file' : 'files'}.`,
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}`,
OPEN_FIRST_PARENT: (parentId: string) => `Opening first parent: ${URL.DRIVE(parentId)}`,
FOUND_PARENT: (parentId: string) => `Found parent: ${URL.DRIVE(parentId)}`,
Expand Down Expand Up @@ -426,6 +430,15 @@ export function isValidProjectId(projectId: string) {
return /^[a-z][-\da-z]{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.
Expand Down
11 changes: 11 additions & 0 deletions tests/commands/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +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');
it('open parent page correctly', () => {
const result = spawnSync(
CLASP, ['open', '--addon'], { encoding: 'utf8' },
Expand Down
16 changes: 16 additions & 0 deletions tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import {
ERROR,
getValidJSON,
isValidEmail,
} from '../src/utils';

describe('Test getValidJSON function', () => {
Expand All @@ -20,4 +21,19 @@ 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';

// 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;
});

it('should return false for invalid combinations of input', () => {
expect(isValidEmail(invalidEmail)).to.be.false;
});
});