From 257fdde25d6de96bbc097f91d739ec64b10418eb Mon Sep 17 00:00:00 2001 From: campionfellin Date: Fri, 25 Jan 2019 19:04:19 -0800 Subject: [PATCH 1/2] Refactor clone code Signed-off-by: campionfellin --- src/commands/clone.ts | 90 ++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/src/commands/clone.ts b/src/commands/clone.ts index 78864d5c..17c0f706 100644 --- a/src/commands/clone.ts +++ b/src/commands/clone.ts @@ -20,11 +20,12 @@ import { const padEnd = require('string.prototype.padend'); const prompt = require('inquirer').prompt; +const scriptIdStandardLength = 57; /** * Fetches an Apps Script project. * Prompts the user if no script ID is provided. - * @param scriptId {string} The Apps Script project ID to fetch. + * @param scriptId {string} The Apps Script project ID or project URL to fetch. * @param versionNumber {string} An optional version to pull the script from. * @param cmd.rootDir {string} Specifies the local directory in which clasp will store your project files. * If not specified, clasp will default to the current directory. @@ -33,45 +34,9 @@ export default async (scriptId: string, versionNumber: number, cmd: { rootDir: s await checkIfOnline(); if (hasProject()) return logError(null, ERROR.FOLDER_EXISTS); if (!scriptId) { - await loadAPICredentials(); - const list = await drive.files.list({ - // pageSize: 10, - // fields: 'files(id, name)', - orderBy: 'modifiedByMeTime desc', - q: 'mimeType="application/vnd.google-apps.script"', - }); - const data = list.data; - if (!data) return logError(list.statusText, 'Unable to use the Drive API.'); - const files = data.files; - if (!files || !files.length) return console.log(LOG.FINDING_SCRIPTS_DNE); - const fileIds = files.map((file: any) => { - return { - name: `${padEnd(file.name, 20)} – ${LOG.SCRIPT_LINK(file.id)}`, - value: file.id, - }; - }); - const answers = await prompt([ - { - type: 'list', - name: 'scriptId', - message: LOG.CLONE_SCRIPT_QUESTION, - choices: fileIds, - pageSize: 30, - }, - ]); - scriptId = answers.scriptId; - } - // We have a scriptId or URL - // If we passed a URL, extract the scriptId from that. For example: - // https://script.google.com/a/DOMAIN/d/1Ng7bNZ1K95wNi2H7IUwZzM68FL6ffxQhyc_ByV42zpS6qAFX8pFsWu2I/edit - if (scriptId.length !== 57) { - // 57 is the magic number - const ids = scriptId.split('/').filter(s => { - return s.length === 57; - }); - if (ids.length) { - scriptId = ids[0]; - } + scriptId = await getScriptId(); + } else { + scriptId = extractScriptId(scriptId); } spinner.setSpinnerTitle(LOG.CLONING); const rootDir = cmd.rootDir; @@ -81,4 +46,49 @@ export default async (scriptId: string, versionNumber: number, cmd: { rootDir: s }, false); const files = await fetchProject(scriptId, versionNumber); await writeProjectFiles(files, rootDir); +}; + +const getScriptId = async () => { + await loadAPICredentials(); + const list = await drive.files.list({ + // pageSize: 10, + // fields: 'files(id, name)', + orderBy: 'modifiedByMeTime desc', + q: 'mimeType="application/vnd.google-apps.script"', + }); + const data = list.data; + if (!data) return logError(list.statusText, 'Unable to use the Drive API.'); + const files = data.files; + if (!files || !files.length) return console.log(LOG.FINDING_SCRIPTS_DNE); + const fileIds = files.map((file: any) => { + return { + name: `${padEnd(file.name, 20)} – ${LOG.SCRIPT_LINK(file.id)}`, + value: file.id, + }; + }); + const answers = await prompt([ + { + type: 'list', + name: 'scriptId', + message: LOG.CLONE_SCRIPT_QUESTION, + choices: fileIds, + pageSize: 30, + }, + ]); + return answers.scriptId; +}; + +// We have a scriptId or URL +// If we passed a URL, extract the scriptId from that. For example: +// https://script.google.com/a/DOMAIN/d/1Ng7bNZ1K95wNi2H7IUwZzM68FL6ffxQhyc_ByV42zpS6qAFX8pFsWu2I/edit +const extractScriptId = (scriptId: string) => { + if (scriptId.length !== scriptIdStandardLength) { + const ids = scriptId.split('/').filter(s => { + return s.length === scriptIdStandardLength; + }); + if (ids.length) { + scriptId = ids[0]; + } + } + return scriptId; }; \ No newline at end of file From da038b4033c880a489cda0877853ad8c059ff1df Mon Sep 17 00:00:00 2001 From: campionfellin Date: Fri, 25 Jan 2019 20:03:59 -0800 Subject: [PATCH 2/2] Make urls.ts file and refactor Signed-off-by: campionfellin --- src/apis.ts | 2 ++ src/auth.ts | 3 ++- src/commands.ts | 2 +- src/commands/clone.ts | 29 ++++++++--------------------- src/urls.ts | 39 +++++++++++++++++++++++++++++++++++++++ src/utils.ts | 13 +------------ tests/test.ts | 15 +++++++++++++-- 7 files changed, 66 insertions(+), 37 deletions(-) create mode 100644 src/urls.ts diff --git a/src/apis.ts b/src/apis.ts index 6ce618e8..03157093 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -182,3 +182,5 @@ export const PUBLIC_ADVANCED_SERVICES: AdvancedService[] = [ version: 'v3', }, ]; + +export const SCRIPT_ID_LENGTH = 57; \ No newline at end of file diff --git a/src/auth.ts b/src/auth.ts index aa127f6b..91a453db 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -10,7 +10,8 @@ import { discovery_v1, drive_v3, google, logging_v2, script_v1, serviceusage_v1 import { prompt } from 'inquirer'; import { ClaspToken, DOTFILE } from './dotfile'; import { enableExecutionAPI, readManifest } from './manifest'; -import { ClaspCredentials, ERROR, LOG, URL, checkIfOnline, getOAuthSettings, logError } from './utils'; +import { URL } from './urls'; +import { ClaspCredentials, ERROR, LOG,checkIfOnline, getOAuthSettings, logError } from './utils'; import open = require('opn'); import readline = require('readline'); diff --git a/src/commands.ts b/src/commands.ts index 2bc26267..ff6e98db 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -36,12 +36,12 @@ import { manifestExists, readManifest, } from './manifest'; +import { URL } from './urls'; import { ERROR, LOG, PROJECT_MANIFEST_BASENAME, PROJECT_MANIFEST_FILENAME, - URL, checkIfOnline, getDefaultProjectName, getProjectId, diff --git a/src/commands/clone.ts b/src/commands/clone.ts index 17c0f706..0a20cf4f 100644 --- a/src/commands/clone.ts +++ b/src/commands/clone.ts @@ -3,6 +3,10 @@ import { loadAPICredentials, } from './../auth'; +import { + extractScriptId, +} from './../urls'; + import { checkIfOnline, ERROR, @@ -20,7 +24,6 @@ import { const padEnd = require('string.prototype.padend'); const prompt = require('inquirer').prompt; -const scriptIdStandardLength = 57; /** * Fetches an Apps Script project. @@ -33,11 +36,7 @@ const scriptIdStandardLength = 57; export default async (scriptId: string, versionNumber: number, cmd: { rootDir: string }) => { await checkIfOnline(); if (hasProject()) return logError(null, ERROR.FOLDER_EXISTS); - if (!scriptId) { - scriptId = await getScriptId(); - } else { - scriptId = extractScriptId(scriptId); - } + scriptId = scriptId ? extractScriptId(scriptId) : await getScriptId(); spinner.setSpinnerTitle(LOG.CLONING); const rootDir = cmd.rootDir; saveProject({ @@ -48,6 +47,9 @@ export default async (scriptId: string, versionNumber: number, cmd: { rootDir: s await writeProjectFiles(files, rootDir); }; +/** + * Lists a user's AppsScripts and prompts them to choose one to clone. + */ const getScriptId = async () => { await loadAPICredentials(); const list = await drive.files.list({ @@ -76,19 +78,4 @@ const getScriptId = async () => { }, ]); return answers.scriptId; -}; - -// We have a scriptId or URL -// If we passed a URL, extract the scriptId from that. For example: -// https://script.google.com/a/DOMAIN/d/1Ng7bNZ1K95wNi2H7IUwZzM68FL6ffxQhyc_ByV42zpS6qAFX8pFsWu2I/edit -const extractScriptId = (scriptId: string) => { - if (scriptId.length !== scriptIdStandardLength) { - const ids = scriptId.split('/').filter(s => { - return s.length === scriptIdStandardLength; - }); - if (ids.length) { - scriptId = ids[0]; - } - } - return scriptId; }; \ No newline at end of file diff --git a/src/urls.ts b/src/urls.ts new file mode 100644 index 00000000..fc97e4f0 --- /dev/null +++ b/src/urls.ts @@ -0,0 +1,39 @@ +import { + SCRIPT_ID_LENGTH, +} from './apis'; + +/** + * Extracts scriptId from URL if given in URL form. + * @param scriptId {string} either a scriptId or URL containing the scriptId + * @example + * extractScriptId( + * 'https://script.google.com/a/DOMAIN/d/1Ng7bNZ1K95wNi2H7IUwZzM68FL6ffxQhyc_ByV42zpS6qAFX8pFsWu2I/edit' + * ) + * returns '1Ng7bNZ1K95wNi2H7IUwZzM68FL6ffxQhyc_ByV42zpS6qAFX8pFsWu2I' + * @example + * extractScriptId('1Ng7bNZ1K95wNi2H7IUwZzM68FL6ffxQhyc_ByV42zpS6qAFX8pFsWu2I') + * returns '1Ng7bNZ1K95wNi2H7IUwZzM68FL6ffxQhyc_ByV42zpS6qAFX8pFsWu2I' + */ +export const extractScriptId = (scriptId: string) => { + if (scriptId.length !== SCRIPT_ID_LENGTH) { + const ids = scriptId.split('/').filter(s => { + return s.length === SCRIPT_ID_LENGTH; + }); + if (ids.length) { + scriptId = ids[0]; + } + } + return scriptId; +}; + +// Helpers to get Apps Script project URLs +export const URL = { + APIS: (projectId: string) => `https://console.developers.google.com/apis/dashboard?project=${projectId}`, + CREDS: (projectId: string) => `https://console.developers.google.com/apis/credentials?project=${projectId}`, + LOGS: (projectId: string) => + `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`, + DRIVE: (driveId: string) => `https://drive.google.com/open?id=${driveId}`, +}; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index ba33f40f..721256ed 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,6 +6,7 @@ import { prompt } from 'inquirer'; import * as pluralize from 'pluralize'; import { ClaspToken, DOT, DOTFILE, ProjectSettings } from './dotfile'; import {Manifest} from './manifest'; +import { URL } from './urls'; const ucfirst = require('ucfirst'); const isOnline = require('is-online'); @@ -57,18 +58,6 @@ export function getOAuthSettings(local: boolean): Promise { }); } -// Helpers to get Apps Script project URLs -export const URL = { - APIS: (projectId: string) => `https://console.developers.google.com/apis/dashboard?project=${projectId}`, - CREDS: (projectId: string) => `https://console.developers.google.com/apis/credentials?project=${projectId}`, - LOGS: (projectId: string) => - `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`, - DRIVE: (driveId: string) => `https://drive.google.com/open?id=${driveId}`, -}; - // Error messages (some errors take required params) export const ERROR = { ACCESS_TOKEN: `Error retrieving access token: `, diff --git a/tests/test.ts b/tests/test.ts index e3c0646e..f1deafaf 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -7,13 +7,12 @@ import { getAppsScriptFileName, getFileType } from './../src/files'; import { ERROR, LOG, - URL, getAPIFileType, getDefaultProjectName, getWebApplicationURL, hasOauthClientSettings, saveProject, -} from './../src/utils.js'; +} from './../src/utils'; import { backupSettings, @@ -36,6 +35,11 @@ import { TEST_CODE_JS, } from './constants'; +import { + extractScriptId, + URL, +} from './../src/urls'; + const { spawnSync } = require('child_process'); describe('Test --help for each function', () => { @@ -158,6 +162,13 @@ describe('Test clasp clone function', () => { after(cleanup); }); +describe('Test extractScriptId function', () => { + it('should return scriptId correctly', () => { + expect(extractScriptId(SCRIPT_ID)).to.equal(SCRIPT_ID); + expect(extractScriptId(URL.SCRIPT(SCRIPT_ID))).to.equal(SCRIPT_ID); + }); +}); + describe('Test clasp pull function', () => { before(function () { if (IS_PR) {