Skip to content

Commit

Permalink
feat: add hook generator
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Jan 8, 2022
1 parent 71ce447 commit 861f516
Show file tree
Hide file tree
Showing 15 changed files with 400 additions and 29 deletions.
14 changes: 11 additions & 3 deletions messages/dev.generate.command.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ Generate a new sf command.

# description

This will clone the template repo 'salesforcecli/plugin-template-sf' and update package properties
This will generate a basic hello world command, a .md file for messages, and test files.

# flags.force.description

overwrite existing files
Overwrite existing files.

# flags.nuts.description

Generate a NUT test file for the command.

# flags.unit.description

Generate a unit test file for the command.

# args.name.description

Directory name of new plugin. Must begin with "plugin-"
Name of the new command. Must be separated by colons.

# examples

Expand Down
19 changes: 19 additions & 0 deletions messages/dev.generate.hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# summary

Generate a new sf hook.

# description

This will generate a basic hook, add the hook to the package.json, and a generate a test file.

# flags.force.description

Overwrite existing files.

# flags.event.description

Event to run hook on.

# examples

- <%= config.bin %> <%= command.id %> my:command
16 changes: 14 additions & 2 deletions src/commands/dev/generate/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,27 @@ export default class GenerateCommand extends GeneratorCommand {
force: Flags.boolean({
description: messages.getMessage('flags.force.description'),
}),
nuts: Flags.boolean({
description: messages.getMessage('flags.nuts.description'),
allowNo: true,
default: true,
}),
unit: Flags.boolean({
description: messages.getMessage('flags.unit.description'),
allowNo: true,
default: false,
}),
};

public static args = [{ name: 'name', required: true, description: messages.getMessage('args.name.description') }];

public async run(): Promise<void> {
const { args } = await this.parse(GenerateCommand);
const { args, flags } = await this.parse(GenerateCommand);
await super.generate('command', {
name: args.name,
force: true,
force: flags.force,
nuts: flags.nuts,
unit: flags.unit,
});
}
}
37 changes: 37 additions & 0 deletions src/commands/dev/generate/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { Flags } from '@oclif/core';
import { Messages } from '@salesforce/core';
import { GeneratorCommand } from '../../../generatorCommand';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-dev', 'dev.generate.hook');

export default class GenerateHook extends GeneratorCommand {
public static summary = messages.getMessage('summary');
public static description = messages.getMessage('description');
public static examples = messages.getMessages('examples');

public static flags = {
force: Flags.boolean({
description: messages.getMessage('flags.force.description'),
}),
event: Flags.string({
description: messages.getMessage('flags.event.description'),
options: ['sf:env:display', 'sf:env:list', 'sf:deploy', 'sf:logout'],
required: true,
}),
};

public async run(): Promise<void> {
const { flags } = await this.parse(GenerateHook);
await super.generate('hook', {
force: flags.force,
event: flags.event,
});
}
}
20 changes: 18 additions & 2 deletions src/generators/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,36 @@ import * as path from 'path';
import * as Generator from 'yeoman-generator';
import yosay = require('yosay');
import { pascalCase } from 'change-case';
import { PackageJson } from '../types';

// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
const { version } = require('../../package.json');

export interface CommandGeneratorOptions extends Generator.GeneratorOptions {
name: string;
nuts: boolean;
unit: boolean;
}

export default class Command extends Generator {
public options: CommandGeneratorOptions;
public pjson!: { name: string };
public pjson!: PackageJson;

public constructor(args: string | string[], opts: CommandGeneratorOptions) {
super(args, opts);
}

// eslint-disable-next-line @typescript-eslint/require-await
public async prompting(): Promise<void> {
this.pjson = this.fs.readJSON('package.json') as unknown as { name: string };
this.pjson = this.fs.readJSON('package.json') as unknown as PackageJson;
this.log(yosay(`Adding a command to ${this.pjson.name} Version: ${version as string}`));
}

public writing(): void {
this.writeCmdFile();
this.writeMessageFile();
this.writeNutFile();
this.writeUnitTestFile();
}

private getMessageFileName(): string {
Expand Down Expand Up @@ -66,6 +70,7 @@ export default class Command extends Generator {
}

private writeNutFile(): void {
if (!this.options.nuts) return;
this.sourceRoot(path.join(__dirname, '../../templates'));
const cmdPath = this.options.name.split(':').join('/');
const nutPah = this.destinationPath(`test/commands/${cmdPath}.nut.ts`);
Expand All @@ -77,4 +82,15 @@ export default class Command extends Generator {
};
this.fs.copyTpl(this.templatePath('test/command.nut.ts.ejs'), nutPah, opts);
}

private writeUnitTestFile(): void {
if (!this.options.unit) return;
this.sourceRoot(path.join(__dirname, '../../templates'));
const cmdPath = this.options.name.split(':').join('/');
const unitPath = this.destinationPath(`test/commands/${cmdPath}.test.ts`);
this.fs.copyTpl(this.templatePath('test/command.test.ts.ejs'), unitPath, {
...this.options,
year: new Date().getFullYear(),
});
}
}
67 changes: 67 additions & 0 deletions src/generators/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as path from 'path';
import { camelCase } from 'change-case';
import * as Generator from 'yeoman-generator';
import yosay = require('yosay');
import { PackageJson } from '../types';

// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
const { version } = require('../../package.json');

export interface CommandGeneratorOptions extends Generator.GeneratorOptions {
name: string;
event: string;
}

function toArray(item: string | string[]): string[] {
return Array.isArray(item) ? item : [item];
}

export default class Command extends Generator {
public options: CommandGeneratorOptions;
public pjson!: PackageJson;

public constructor(args: string | string[], opts: CommandGeneratorOptions) {
super(args, opts);
}

// eslint-disable-next-line @typescript-eslint/require-await
public async prompting(): Promise<void> {
this.pjson = this.fs.readJSON('package.json') as unknown as PackageJson;
this.log(yosay(`Adding a ${this.options.event} hook to ${this.pjson.name} Version: ${version as string}`));
}

public writing(): void {
this.sourceRoot(path.join(__dirname, '../../templates'));
const filename = camelCase(this.options.event.replace('sf:', ''));
this.fs.copyTpl(
this.templatePath(`src/hooks/${this.options.event.replace(/:/g, '.')}.ts.ejs`),
this.destinationPath(`src/hooks/${filename}.ts`),
{ year: new Date().getFullYear() }
);

// TODO
// this.fs.copyTpl(
// this.templatePath('test/hook.test.ts.ejs'),
// this.destinationPath(`test/hooks/${this.options.event}/${this.options.name}.test.ts`),
// this
// );

this.pjson.oclif.hooks = this.pjson.oclif.hooks || {};
const hooks = this.pjson.oclif.hooks;
const p = `./lib/hooks/${filename}`;
if (hooks[this.options.event]) {
hooks[this.options.event] = [...toArray(hooks[this.options.event]), p];
} else {
this.pjson.oclif.hooks[this.options.event] = p;
}

this.fs.writeJSON(this.destinationPath('./package.json'), this.pjson);
}
}
14 changes: 1 addition & 13 deletions src/generators/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as Generator from 'yeoman-generator';
import yosay = require('yosay');
import { exec } from 'shelljs';
import replace = require('replace-in-file');
import { PackageJson } from '../types';

// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
const { version } = require('../../package.json');
Expand All @@ -19,19 +20,6 @@ export interface PluginGeneratorOptions extends Generator.GeneratorOptions {
name: string;
}

export interface PackageJson {
name: string;
devDependencies: Record<string, string>;
dependencies: Record<string, string>;
oclif: {
bin: string;
dirname: string;
hooks: Record<string, string | string[]>;
};
repository: string;
homepage: string;
}

export default class Plugin extends Generator {
private name: string;
private answers!: {
Expand Down
19 changes: 19 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

export interface PackageJson {
name: string;
devDependencies: Record<string, string>;
dependencies: Record<string, string>;
oclif: {
bin: string;
dirname: string;
hooks: Record<string, string | string[]>;
};
repository: string;
homepage: string;
}
4 changes: 2 additions & 2 deletions templates/src/command.ts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export default class <%- className %> extends SfCommand<<%- returnType %>> {

public static flags = {
name: Flags.string({
description: messages.getMessage('flag.name.description'),
description: messages.getMessage('flags.name.description'),
char: 'n',
required: true,
required: false,
}),
};

Expand Down
7 changes: 0 additions & 7 deletions templates/src/hook.ts.ejs

This file was deleted.

80 changes: 80 additions & 0 deletions templates/src/hooks/sf.deploy.ts.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) <%- year %>, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { basename } from 'path';
import { Deployable, Deployer, generateTableChoices, SfHook } from '@salesforce/sf-plugins-core';

class MyEnvDeployable extends Deployable {
public constructor(public myEnvDir: string, private parent: Deployer) {
super();
}

public getName(): string {
return basename(this.myEnvDir);
}

public getType(): string {
return 'myEnv';
}

public getPath(): string {
return basename(this.myEnvDir);
}

public getParent(): Deployer {
return this.parent;
}
}

export class MyEnvDeployer extends Deployer {
public static NAME = 'Salesforce MyEnvs';

private username!: string;

public constructor(myEnvDir: string) {
super();
this.deployables = [new MyEnvDeployable(myEnvDir, this)];
}

public getName(): string {
return MyEnvDeployer.NAME;
}

public async setup(flags: Deployer.Flags, options: Deployer.Options): Promise<Deployer.Options> {
if (flags.interactive) {
this.username = await this.promptForUsername();
} else {
this.username = (options.username as string) || (await this.promptForUsername());
}
return { username: this.username };
}

public async deploy(): Promise<void> {
this.log(`Deploying to ${this.username}.`);
await Promise.resolve();
}

public async promptForUsername(): Promise<string> {
const columns = { name: 'Env' };
const options = [{ name: 'Env1' }, { name: 'Env2' }];
const { username } = await this.prompt<{ username: string }>([
{
name: 'username',
message: 'Select the environment you want to deploy to:',
type: 'list',
choices: generateTableChoices(columns, options, false),
},
]);
return username;
}
}

// eslint-disable-next-line @typescript-eslint/require-await
const hook: SfHook.Deploy<MyEnvDeployer> = async function () {
return [new MyEnvDeployer('myEnvs/')];
};

export default hook;
Loading

0 comments on commit 861f516

Please sign in to comment.