Skip to content

Commit

Permalink
feat: duration flag
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Feb 28, 2022
1 parent 00ab4fc commit b11eaab
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 5 deletions.
8 changes: 8 additions & 0 deletions messages/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,11 @@ No file found: %s.
# flags.existingFile.errors.NotAFile

No file found: %s.

# flags.duration.errors.InvalidInput

The value must be an integer.

# flags.duration.errors.DurationBounds

The value must be between %s and %s (inclusive).
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"/messages"
],
"dependencies": {
"@oclif/core": "^1.2.0",
"@oclif/core": "^1.3.6",
"@salesforce/core": "3.7.3",
"@salesforce/kit": "^1.5.17",
"@salesforce/ts-types": "^1.5.20",
Expand Down
1 change: 1 addition & 0 deletions src/exported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { requiredOrgFlag, requiredHubFlag } from './flags/orgFlags';
export { buildIdFlag } from './flags/salesforceId';
export { apiVersionFlag } from './flags/apiVersion';
export { existingDirectory, existingFile } from './flags/fsFlags';
export { buildDurationFlag, DurationFlagConfig } from './flags/duration';
71 changes: 71 additions & 0 deletions src/flags/duration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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
*/
import { Flags } from '@oclif/core';
import { Definition } from '@oclif/core/lib/interfaces';
import { Messages } from '@salesforce/core';
import { Duration } from '@salesforce/kit';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/sf-plugins-core', 'messages');

type DurationUnit = Lowercase<keyof typeof Duration.Unit>;

export interface DurationFlagConfig {
unit: Required<DurationUnit>;
defaultValue?: number;
min?: number;
max?: number;
}

/**
* Duration flag with built-in default and min/max validation
* You must specify a unit
* Defaults to undefined if you don't specify a default
*
* @example
* import { SfCommand, buildDurationFlag } from '@salesforce/sf-plugins-core';
* public static flags = {
* 'wait': buildDurationFlag({ min: 1, unit: , defaultValue: 33 })({
* char: 'w',
* description: 'Wait time in minutes'
* }),
* }
*/
export const buildDurationFlag = (durationConfig: DurationFlagConfig): Definition<Duration> => {
return Flags.build<Duration>({
parse: async (input: string) => validate(input, durationConfig),
default: durationConfig.defaultValue
? async () => toDuration(durationConfig.defaultValue as number, durationConfig.unit)
: undefined,
});
};

const validate = (input: string, config: DurationFlagConfig): Duration => {
const { min, max, unit } = config || {};
let parsedInput: number;

try {
parsedInput = parseInt(input, 10);
if (typeof parsedInput !== 'number' || isNaN(parsedInput)) {
throw messages.createError('flags.duration.errors.InvalidInput');
}
} catch (e) {
throw messages.createError('flags.duration.errors.InvalidInput');
}

if (min && parsedInput < min) {
throw messages.createError('flags.duration.errors.DurationBounds', [min, max]);
}
if (max && parsedInput > max) {
throw messages.createError('flags.duration.errors.DurationBounds', [min, max]);
}
return toDuration(parsedInput, unit);
};

const toDuration = (parsedInput: number, unit: DurationUnit): Duration => {
return Duration[unit](parsedInput);
};
119 changes: 119 additions & 0 deletions test/unit/flags/duration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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
*/

import { Parser } from '@oclif/core';
import { Messages } from '@salesforce/core';
import { expect } from 'chai';
import { Duration } from '@salesforce/kit';
import { buildDurationFlag } from '../../../src/flags/duration';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/sf-plugins-core', 'messages');

describe('duration flag', () => {
describe('no default, hours', () => {
const buildProps = {
flags: {
wait: buildDurationFlag({
unit: 'hours',
})({ description: 'test', char: 'w' }),
},
};
it('passes', async () => {
const out = await Parser.parse(['--wait=10'], buildProps);
expect(out.flags.wait.quantity).to.equal(10);
expect(out.flags.wait.unit).to.equal(Duration.Unit.HOURS);
});
it('passes with default', async () => {
const out = await Parser.parse([], buildProps);
expect(out.flags.wait).to.equal(undefined);
});
});

describe('validation with no options and weeks unit', () => {
const defaultValue = 33;
const buildProps = {
flags: {
wait: buildDurationFlag({
unit: 'weeks',
defaultValue,
})({ description: 'test', char: 'w' }),
},
};
it('passes', async () => {
const out = await Parser.parse(['--wait=10'], buildProps);
expect(out.flags.wait.quantity).to.equal(10);
expect(out.flags.wait.unit).to.equal(Duration.Unit.WEEKS);
});
it('passes with default', async () => {
const out = await Parser.parse([], buildProps);
expect(out.flags.wait.quantity).to.equal(33);
});
});

describe('validation with all options', () => {
const min = 1;
const max = 60;
const defaultValue = 33;
const buildProps = {
flags: {
wait: buildDurationFlag({
defaultValue,
min,
max,
unit: 'minutes',
})({ description: 'test', char: 'w' }),
},
};
it('passes', async () => {
const out = await Parser.parse(['--wait=10'], buildProps);
expect(out.flags.wait.quantity).to.equal(10);
});
it('min passes', async () => {
const out = await Parser.parse([`--wait=${min}`], buildProps);
expect(out.flags.wait.quantity).to.equal(min);
});
it('max passes', async () => {
const out = await Parser.parse([`--wait=${max}`], buildProps);
expect(out.flags.wait.quantity).to.equal(max);
});
it('default works', async () => {
const out = await Parser.parse([], buildProps);
expect(out.flags.wait.quantity).to.equal(defaultValue);
});
describe('failures', () => {
it('below min fails', async () => {
try {
const out = await Parser.parse([`--wait=${min - 1}`], buildProps);

throw new Error(`Should have thrown an error ${JSON.stringify(out)}`);
} catch (err) {
const error = err as Error;
expect(error.message).to.equal(messages.getMessage('flags.duration.errors.DurationBounds', [1, 60]));
}
});
it('above max fails', async () => {
try {
const out = await Parser.parse([`--wait=${max + 1}`], buildProps);
throw new Error(`Should have thrown an error ${JSON.stringify(out)}`);
} catch (err) {
const error = err as Error;
expect(error.message).to.equal(messages.getMessage('flags.duration.errors.DurationBounds', [1, 60]));
}
});
it('invalid input', async () => {
try {
const out = await Parser.parse(['--wait=abc}'], buildProps);
throw new Error(`Should have thrown an error ${JSON.stringify(out)}`);
} catch (err) {
const error = err as Error;
expect(error.message).to.equal(messages.getMessage('flags.duration.errors.InvalidInput'));
}
});
});
});
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,10 @@
is-wsl "^2.1.1"
tslib "^2.0.0"

"@oclif/core@^1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@oclif/core/-/core-1.2.0.tgz#f1110b1fe868e439f94f8b4ffad5dd8acf862294"
integrity sha512-h1n8NEAUzaL3+wky7W1FMeySmJWQpYX1LhWMltFY/ScvmapZzee7D9kzy/XI/ZIWWfz2ZYCTMD1wOKXO6ueynw==
"@oclif/core@^1.3.6":
version "1.3.6"
resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.3.6.tgz#b3c3b3c865c33d88aa6e7f8f596a7939720c4d68"
integrity sha512-WSb5uyHlfTcN2HQT1miKDe90AhaiZ5Di0jmyqWlPZA0XE+xvJgMPOAyQdxxVed+XpkP6AhCPJEoIlZvQb1y1Xw==
dependencies:
"@oclif/linewrap" "^1.0.0"
"@oclif/screen" "^3.0.2"
Expand Down

0 comments on commit b11eaab

Please sign in to comment.