Skip to content

Commit

Permalink
feat: add push command (#260)
Browse files Browse the repository at this point in the history
  • Loading branch information
y-lakhdar authored Jun 21, 2021
1 parent f9ff3f2 commit 25b8c6c
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 130 deletions.
151 changes: 151 additions & 0 deletions packages/cli/src/commands/org/config/orgConfigBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import {Command, flags} from '@oclif/command';
import {cli} from 'cli-ux';
import {ReadStream} from 'fs';
import {dedent} from 'ts-dedent';
import {cwd} from 'process';
import {Config} from '../../../lib/config/config';
import {Project} from '../../../lib/project/project';
import {SnapshotFactory} from '../../../lib/snapshot/snapshotFactory';
import {
snapshotSynchronizationUrl,
snapshotUrl,
SnapshotUrlOptionsArgs,
} from '../../../lib/platform/url';
import {Snapshot} from '../../../lib/snapshot/snapshot';
import {red, green} from 'chalk';
import {normalize} from 'path';

export interface CustomFile extends ReadStream {
type?: string;
}

export default abstract class SnapshotBase extends Command {
public static description = 'Create and validate a snapshot';

public static flags = {
target: flags.string({
char: 't',
description:
'The unique identifier of the organization where to send the changes. If not specified, the organization you are connected to will be used.',
helpValue: 'destinationorganizationg7dg3gd',
required: false,
}),
projectPath: flags.string({
char: 'p',
description: 'The path to your Coveo project.',
helpValue: '/Users/Me/my-project',
default: cwd(),
required: false,
}),
deleteMissingResources: flags.boolean({
char: 'd',
description: 'Whether or not to show resources to delete',
default: false,
required: false,
}),
};

public static hidden = true;

protected async dryRun() {
const project = new Project(normalize(this.flags.projectPath));

cli.action.start('Creating snapshot');
const snapshot = await this.createSnapshotFromProject(project);

cli.action.start('Validating snapshot');
const isValid = await this.validateSnapshot(snapshot);

cli.action.stop(isValid ? green('✔') : red.bold('!'));
return {isValid, snapshot, project};
}

protected async createSnapshotFromProject(
project: Project
): Promise<Snapshot> {
const pathToZip = await project.compressResources();
const targetOrg = await this.getTargetOrg();

return await SnapshotFactory.createFromZip(pathToZip, targetOrg);
}

protected async validateSnapshot(snapshot: Snapshot): Promise<boolean> {
const {isValid} = await snapshot.validate(
this.flags.deleteMissingResources
);

if (!isValid) {
await this.handleReportWithErrors(snapshot);
}

return isValid;
}

protected async getTargetOrg() {
if (this.flags.target) {
return this.flags.target;
}
const cfg = await this.configuration.get();
return cfg.organization;
}

protected async handleReportWithErrors(snapshot: Snapshot) {
// TODO: CDX-362: handle invalid snapshot cases
const pathToReport = snapshot.saveDetailedReport(this.flags.projectPath);
const report = snapshot.latestReport;

if (snapshot.requiresSynchronization()) {
const synchronizationPlanUrl = await this.getSynchronizationPage(
snapshot
);
this.warn(
dedent`
Some conflicts were detected while comparing changes between the snapshot and the target organization.
Click on the URL below to synchronize your snapshot with your organization before running the command again.
${synchronizationPlanUrl}
`
);
return;
}

const snapshotUrl = await this.getSnapshotPage(snapshot);

this.error(
dedent`Invalid snapshot - ${report.resultCode}.
Detailed report saved at ${pathToReport}.
You can also use this link to view the snapshot in the Coveo Admin Console
${snapshotUrl}`
);
}

private get flags() {
const {flags} = this.parse(this.ctor as typeof SnapshotBase);
return flags;
}

private get configuration() {
return new Config(this.config.configDir, this.error);
}

private async getSnapshotPage(snapshot: Snapshot) {
const options = await this.getSnapshotUrlOptions(snapshot);
return snapshotUrl(options);
}

private async getSynchronizationPage(snapshot: Snapshot) {
const options = await this.getSnapshotUrlOptions(snapshot);
return snapshotSynchronizationUrl(options);
}

private async getSnapshotUrlOptions(
snapshot: Snapshot
): Promise<SnapshotUrlOptionsArgs> {
const {environment} = await this.configuration.get();
return {
environment,
targetOrgId: snapshot.targetId,
snapshotId: snapshot.id,
};
}
}
114 changes: 4 additions & 110 deletions packages/cli/src/commands/org/config/preview.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,21 @@
import {Command, flags} from '@oclif/command';
import {cli} from 'cli-ux';
import {ReadStream} from 'fs';
import {dedent} from 'ts-dedent';
import {cwd} from 'process';
import {Config} from '../../../lib/config/config';
import {
IsAuthenticated,
Preconditions,
} from '../../../lib/decorators/preconditions';
import {Project} from '../../../lib/project/project';
import {SnapshotFactory} from '../../../lib/snapshot/snapshotFactory';
import {platformUrl} from '../../../lib/platform/environment';
import {Snapshot} from '../../../lib/snapshot/snapshot';
import {red, green} from 'chalk';
import {normalize} from 'path';
import SnapshotBase from './orgConfigBase';

export interface CustomFile extends ReadStream {
type?: string;
}

export default class Preview extends Command {
export default class Preview extends SnapshotBase {
public static description = 'Preview resource updates';

public static flags = {
target: flags.string({
char: 't',
description:
'The unique identifier of the organization where to send the changes. If not specified, the organization you are connected to will be used.',
helpValue: 'destinationorganizationg7dg3gd',
required: false,
}),
projectPath: flags.string({
char: 'p',
description: 'The path to your Coveo project.',
helpValue: '/Users/Me/my-project',
default: cwd(),
required: false,
}),
showResourcesToDelete: flags.boolean({
char: 'd',
description: 'Whether or not to show resources to delete',
default: false,
required: false,
}),
...SnapshotBase.flags,
};

public static hidden = true;

@Preconditions(IsAuthenticated())
public async run() {
const {flags} = this.parse(Preview);
const project = new Project(normalize(flags.projectPath));
const pathToZip = await project.compressResources();
const targetOrg = await this.getTargetOrg();

cli.action.start('Creating snapshot');

const snapshot = await SnapshotFactory.createFromZip(pathToZip, targetOrg);

cli.action.start('Validating snapshot');

const {isValid} = await snapshot.validate(flags.showResourcesToDelete);

if (!isValid) {
await this.handleInvalidSnapshot(snapshot);
}

cli.action.stop(isValid ? green('✔') : red.bold('!'));
const {isValid, snapshot, project} = await this.dryRun();

await snapshot.preview();

Expand All @@ -76,59 +25,4 @@ export default class Preview extends Command {

project.deleteTemporaryZipFile();
}

public async getTargetOrg() {
const {flags} = this.parse(Preview);
if (flags.target) {
return flags.target;
}
const cfg = await this.configuration.get();
return cfg.organization;
}

private async handleInvalidSnapshot(snapshot: Snapshot) {
// TODO: CDX-362: handle invalid snapshot cases
const {flags} = this.parse(Preview);
const pathToReport = snapshot.saveDetailedReport(flags.projectPath);
const report = snapshot.latestReport;

if (snapshot.requiresSynchronization()) {
const synchronizationPlanUrl = await this.getSynchronizationPage(
snapshot
);
this.warn(
dedent`
Some conflicts were detected while comparing changes between the snapshot and the target organization.
Click on the URL below to synchronize your snapshot with your organization before running the command again.
${synchronizationPlanUrl}
`
);
return;
}

const snapshotUrl = await this.getSnapshotPage(snapshot);

this.error(
dedent`Invalid snapshot - ${report.resultCode}.
Detailed report saved at ${pathToReport}.
You can also use this link to view the snapshot in the Coveo Admin Console
${snapshotUrl}`
);
}

private get configuration() {
return new Config(this.config.configDir, this.error);
}

private async getSnapshotPage(snapshot: Snapshot) {
const {environment} = await this.configuration.get();
const url = platformUrl({environment});
const targetOrg = snapshot.targetId;
return `${url}/admin/#${targetOrg}/organization/resource-snapshots/${snapshot.id}`;
}

private async getSynchronizationPage(snapshot: Snapshot) {
return `${await this.getSnapshotPage(snapshot)}/synchronization`;
}
}
66 changes: 66 additions & 0 deletions packages/cli/src/commands/org/config/push.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {cli} from 'cli-ux';
import {ReadStream} from 'fs';
import {
IsAuthenticated,
Preconditions,
} from '../../../lib/decorators/preconditions';
import {Snapshot} from '../../../lib/snapshot/snapshot';
import {red, green, bold} from 'chalk';
import SnapshotBase from './orgConfigBase';

export interface CustomFile extends ReadStream {
type?: string;
}

export default class Push extends SnapshotBase {
public static description =
'Preview, validate and deploy your changes to the destination org';

public static flags = {
...SnapshotBase.flags,
};

public static hidden = true;

@Preconditions(IsAuthenticated())
public async run() {
const {isValid, snapshot, project} = await this.dryRun();

await snapshot.preview();

if (isValid) {
await this.handleValidReport(snapshot);
await snapshot.delete();
}

project.deleteTemporaryZipFile();
}

private async handleValidReport(snapshot: Snapshot) {
if (!snapshot.hasChangedResources()) {
return;
}
const targetOrg = await this.getTargetOrg();
const canBeApplied = await cli.confirm(
`\nWould you like to apply these changes to the org ${bold(
targetOrg
)}? (y/n)`
);

if (canBeApplied) {
await this.applySnapshot(snapshot);
}
}

private async applySnapshot(snapshot: Snapshot) {
cli.action.start('Applying snapshot');
const {flags} = this.parse(Push);
const {isValid} = await snapshot.apply(flags.deleteMissingResources);

if (!isValid) {
await this.handleReportWithErrors(snapshot);
}

cli.action.stop(isValid ? green('✔') : red.bold('!'));
}
}
29 changes: 29 additions & 0 deletions packages/cli/src/lib/platform/url.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
snapshotSynchronizationUrl,
snapshotUrl,
SnapshotUrlOptionsArgs,
} from './url';

describe('url', () => {
const getSnapshotUrlOptions = (): SnapshotUrlOptionsArgs => {
return {
environment: 'prod',
targetOrgId: 'foo',
snapshotId: 'bar',
};
};

it('#snapshotUrl should return the url to the snapshot page', () => {
const options = getSnapshotUrlOptions();
expect(snapshotUrl(options)).toEqual(
'https://platform.cloud.coveo.com/admin/#foo/organization/resource-snapshots/bar'
);
});

it('#snapshotSynchronizationUrl should return the url to the snapshot synchronization page', () => {
const options = getSnapshotUrlOptions();
expect(snapshotSynchronizationUrl(options)).toEqual(
'https://platform.cloud.coveo.com/admin/#foo/organization/resource-snapshots/bar/synchronization'
);
});
});
Loading

0 comments on commit 25b8c6c

Please sign in to comment.