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 delete command #892

Open
wants to merge 4 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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ clasp
- [`clasp logout`](#logout)
- [`clasp create [--title <title>] [--type <type>] [--rootDir <dir>] [--parentId <id>]`](#create)
- [`clasp clone <scriptId | scriptURL> [versionNumber] [--rootDir <dir>]`](#clone)
- [`clasp delete [--force]`](#delete)
- [`clasp pull [--versionNumber]`](#pull)
- [`clasp push [--watch] [--force]`](#push)
- [`clasp status [--json]`](#status)
Expand Down Expand Up @@ -194,6 +195,19 @@ Clones the script project from script.google.com.
- `clasp clone "https://script.google.com/d/15ImUCpyi1Jsd8yF8Z6wey_7cw793CymWTLxOqwMka3P1CzE5hQun6qiC/edit"`
- `clasp clone "15ImUCpyi1Jsd8yF8Z6wey_7cw793CymWTLxOqwMka3P1CzE5hQun6qiC" --rootDir ./src`

### Delete

Interactively deletes a script or a project and the `.clasp.json` file. Prompt the user for confirmation if the --force option is not specified.

#### Options

- `-f` `--force`: Bypass any confirmation messages. It’s not a good idea to do this unless you want to run clasp from a script.

#### Examples

- `clasp delete`
- `clasp delete -f`

### Pull

Fetches a project from either a provided or saved script ID.
Expand Down
2 changes: 2 additions & 0 deletions src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ let localOAuth2Client: OAuth2Client; // Must be set up after authorize.

export const discovery = google.discovery({version: 'v1'});
export const drive = google.drive({version: 'v3', auth: globalOAuth2Client});
export const driveV2 = google.drive({version: 'v2', auth: globalOAuth2Client});
export const logger = google.logging({version: 'v2', auth: globalOAuth2Client});
export const script = google.script({version: 'v1', auth: globalOAuth2Client});
export const serviceUsage = google.serviceusage({version: 'v1', auth: globalOAuth2Client});
Expand All @@ -78,6 +79,7 @@ export const defaultScopes = [
'https://www.googleapis.com/auth/script.projects', // Apps Script management
scopeWebAppDeploy, // Apps Script Web Apps
'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata
'https://www.googleapis.com/auth/drive',
'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
Expand Down
63 changes: 63 additions & 0 deletions src/commands/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {driveV2, loadAPICredentials} from '../auth.js';
import {deleteClaspJsonPrompt, deleteDriveFilesPrompt} from '../inquirer.js';
import {LOG} from '../messages.js';
import {checkIfOnlineOrDie, deleteProject, getProjectSettings, spinner, stopSpinner} from '../utils.js';

interface CommandOption {
readonly force?: boolean;
}

/**
* Delete an Apps Script project.
* @param options.foce {boolean} force Bypass any confirmation messages.
*/
export default async (options: CommandOption): Promise<void> => {
// Handle common errors.
await checkIfOnlineOrDie();
await loadAPICredentials();

// Create defaults.
const {force} = options;

const projectSettings = await getProjectSettings();
const parentIds = projectSettings.parentId || [];
const hasParents = !!parentIds.length;

//ask confirmation
if (!force && !(await deleteDriveFilesPrompt(hasParents)).answer) {
return;
}

//delete the drive files
if (hasParents) {
await deleteDriveFiles(parentIds);
} else {
await deleteDriveFiles([projectSettings.scriptId]);
}

// TODO: delete .clasp.json //
if (force || (await deleteClaspJsonPrompt()).answer) {
await deleteProject();
}

console.log(LOG.DELETE_DRIVE_FILE_FINISH);
};

/**
* Delete Files on Drive.
*
* @param {string[]} fileIds the list of ids
*/
const deleteDriveFiles = async (fileIds: string[]): Promise<void> => {
for (let i = 0; i < fileIds.length; i++) {
const currId = fileIds[i];

// CLI Spinner
spinner.start(LOG.DELETE_DRIVE_FILE_START(currId));

// Delete Apps Script project
await driveV2.files.trash({fileId: currId});
}

stopSpinner();
};
16 changes: 16 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {ClaspError} from './clasp-error.js';
import apis from './commands/apis.js';
import clone from './commands/clone.js';
import create from './commands/create.js';
import deleteCmd from './commands/delete.js';
import defaultCmd from './commands/default.js';
import deploy from './commands/deploy.js';
import deployments from './commands/deployments.js';
Expand Down Expand Up @@ -158,6 +159,21 @@ program
.option('--rootDir <rootDir>', 'Local root directory in which clasp will store your project files.')
.action(create);

/**
* Delete a clasp project
* @name delete
* @param {boolean?} force Bypass any confirmation messages. It’s not a good idea to do this unless you want to run clasp from a script.
* @example delete
*/
program
.command('delete')
.description('Delete a project')
.option(
'-f, --force',
'Bypass any confirmation messages. It’s not a good idea to do this unless you want to run clasp from a script.'
)
.action(deleteCmd);

/**
* Fetches a project and saves the script id locally.
* @param {string?} [scriptId] The script ID to clone.
Expand Down
29 changes: 29 additions & 0 deletions src/inquirer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,32 @@ export const scriptTypePrompt = () =>
type: 'list',
},
]);

/**
* Inquirer prompt for deleting a drive file
* @param {boolean} withParent true if the script has a parent project, default is false
* @returns {Promise<{ answer: boolean }>} A promise for an object with the `answer` property.
*/
export const deleteDriveFilesPrompt = (withParent = false) =>
prompt<{answer: boolean}>([
{
default: false,
message: !withParent ? LOG.DELETE_DRIVE_FILE_CONFIRM : LOG.DELETE_DRIVE_FILE_WITH_PARENT_CONFIRM,
name: 'answer',
type: 'confirm',
},
]);

/**
* Inquirer prompt for deleting the .clasp.json file
* @returns {Promise<{ answer: boolean }>} A promise for an object with the `answer` property.
*/
export const deleteClaspJsonPrompt = () =>
prompt<{answer: boolean}>([
{
default: false,
message: LOG.DELETE_CLASPJSON_CONFIRM,
name: 'answer',
type: 'confirm',
},
]);
5 changes: 5 additions & 0 deletions src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ Cloned ${fileCount} ${fileCount === 1 ? 'file' : 'files'}.`,
CREATE_PROJECT_FINISH: (filetype: string, scriptId: string) =>
`Created new ${getScriptTypeName(filetype)} script: ${URL.SCRIPT(scriptId)}`,
CREATE_PROJECT_START: (title: string) => `Creating new script: ${title}…`,
DELETE_DRIVE_FILE_START: (fileId: string) => `Deleting ${fileId}…`,
DELETE_DRIVE_FILE_FINISH: 'Deleted project',
DELETE_DRIVE_FILE_CONFIRM: 'Are you sure you want to delete the script?',
DELETE_DRIVE_FILE_WITH_PARENT_CONFIRM: 'Are you sure you want to delete the script and his parent projects?',
DELETE_CLASPJSON_CONFIRM: 'Do you want to delete the .clasp.json file?',
CREDENTIALS_FOUND: 'Credentials found, using those to login…',
CREDS_FROM_PROJECT: (projectId: string) => `Using credentials located here:\n${URL.CREDS(projectId)}\n`,
DEFAULT_CREDENTIALS: 'No credentials given, continuing with default…',
Expand Down
5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ export const checkIfOnlineOrDie = async () => {
export const saveProject = async (projectSettings: ProjectSettings, append = true): Promise<ProjectSettings> =>
DOTFILE.PROJECT().write(append ? {...(await getProjectSettings()), ...projectSettings} : projectSettings);

/**
* Deletes the project dotfile.
*/
export const deleteProject = async (): Promise<void> => await DOTFILE.PROJECT().delete();

/**
* Gets the script's Cloud Platform Project Id from project settings file or prompt for one.
* @returns {Promise<string>} A promise to get the projectId string.
Expand Down
62 changes: 62 additions & 0 deletions test/commands/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import fs from 'fs-extra';
import {expect} from 'chai';
import {spawnSync} from 'child_process';
import {before, describe, it} from 'mocha';
import {Conf} from '../../src/conf.js';

// import {LOG} from '../../src/messages.js';
import {CLASP} from '../constants.js';
import {cleanup, setup} from '../functions.js';
import {LOG} from '../../src/messages.js';

const config = Conf.get();

describe('Test clasp delete function with standalone script', () => {
before(setup);
it('should create a new project correctly before delete', () => {
spawnSync('rm', ['.clasp.json']);
const result = spawnSync(CLASP, ['create', '--type', 'standalone', '--title', 'myTitleToDelete'], {
encoding: 'utf8',
});
expect(result.status).to.equal(0);
});

it('should delete the new project correctly', () => {
const result = spawnSync(CLASP, ['delete', '-f'], {
encoding: 'utf8',
});
expect(result.stdout).to.contains(LOG.DELETE_DRIVE_FILE_FINISH);
expect(fs.existsSync(config.projectConfig!)).to.be.false;
expect(result.status).to.equal(0);
});
after(cleanup);
});

describe('Test clasp delete function with a parent project', () => {
before(setup);
it('should create a new project correctly before delete', () => {
spawnSync('rm', ['.clasp.json']);
const result = spawnSync(CLASP, ['create', '--type', 'sheets', '--title', 'myTitleToDelete'], {
encoding: 'utf8',
});
expect(result.status).to.equal(0);
});

it('should ask if delete the parent project', () => {
const result = spawnSync(CLASP, ['delete'], {
encoding: 'utf8',
});
expect(result.stdout).to.contain(LOG.DELETE_DRIVE_FILE_WITH_PARENT_CONFIRM);
expect(result.status).to.equal(0);
});

it('should delete the new project correctly', () => {
const result = spawnSync(CLASP, ['delete', '-f'], {
encoding: 'utf8',
});
expect(result.stdout).to.contains(LOG.DELETE_DRIVE_FILE_FINISH);
expect(fs.existsSync(config.projectConfig!)).to.be.false;
expect(result.status).to.equal(0);
});
after(cleanup);
});
2 changes: 2 additions & 0 deletions test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe.skip('Test --help for each function', () => {
it('should logout --help', () => expectHelp('logout', 'Log out'));
it('should create --help', () => expectHelp('create', 'Create a script'));
it('should clone --help', () => expectHelp('clone', 'Clone a project'));
it('should delete --help', () => expectHelp('delete', 'Delete a project'));
it('should pull --help', () => expectHelp('pull', 'Fetch a remote project'));
it('should push --help', () => expectHelp('push', 'Update the remote project'));
it('should status --help', () => expectHelp('status', 'Lists files that will be pushed by clasp'));
Expand Down Expand Up @@ -266,6 +267,7 @@ describe('Test all functions while logged out', () => {
};
it('should fail to list (no credentials)', () => expectNoCredentials('list'));
it('should fail to clone (no credentials)', () => expectNoCredentials('clone'));
it('should fail to delete (no credentials)', () => expectNoCredentials('delete'));
it('should fail to push (no credentials)', () => expectNoCredentials('push'));
it('should fail to deployments (no credentials)', () => expectNoCredentials('deployments'));
it('should fail to deploy (no credentials)', () => expectNoCredentials('deploy'));
Expand Down