diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..25fa621 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/package.json b/package.json index e0ba930..071b323 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "eslint-config-prettier": "^8.3.0", "husky": "^6.0.0", "jest": "^27.0.6", + "node-notifier": "^10.0.0", "prettier": "^2.3.2", "pretty-quick": "^3.1.1", "rimraf": "^3.0.2", @@ -31,7 +32,7 @@ "semantic-release": "^17.4.4", "ts-jest": "^27.0.3", "ts-node": "^10.0.0", - "typescript": "^4.3.4" + "typescript": "^4.4.2" }, "scripts": { "eslint": "eslint . --ext .js,.jsx,.ts,.tsx", diff --git a/src/__tests__/command.spec.ts b/src/__tests__/command.spec.ts index 0b08b5c..a9feb29 100644 --- a/src/__tests__/command.spec.ts +++ b/src/__tests__/command.spec.ts @@ -375,6 +375,18 @@ describe('buildkite-graph', () => { ).toThrow(); }); }); + + describe('withParameterOverride', () => { + it('produces an override for a given key', async () => { + await expect( + new CommandStep('noop') + .withParameterOverride('priority', 'MY_PRIORITY') + .toJson(), + ).resolves.toEqual( + expect.objectContaining({ priority: '${MY_PRIORITY}' }), + ); + }); + }); }); }); }); diff --git a/src/steps/command.ts b/src/steps/command.ts index 8ebe930..c80c851 100644 --- a/src/steps/command.ts +++ b/src/steps/command.ts @@ -83,9 +83,25 @@ const transformSkipValue = ( type ConcurrencyMethod = 'eager' | 'ordered'; +type CommandProperty = + | 'command' + | 'priority' + | 'parallelism' + | 'concurrency' + | 'concurrency_group' + | 'concurrency_method' + | 'artifact_paths' + | 'agents' + | 'timeout_in_minutes' + | 'soft_fail' + | 'plugins' + | 'skip' + | 'retry' + | 'env'; export class CommandStep extends LabeledStep { public readonly command: Command[] = []; public readonly env: KeyValue; + private readonly overrides: Map = new Map(); private _parallelism?: number; private get parallelism(): number | undefined { @@ -234,6 +250,20 @@ export class CommandStep extends LabeledStep { return this; } + /** + * Allows to override the value of a property of the command. + * The override is the name of a environment variable. + * + * E.g. `withParameterOverride('priority', 'CUSTOM_PRIORITY')` would + * then yield `priority: ${CUSTOM_PRIORITY}` in the resulting serialization. + */ + withParameterOverride(key: CommandProperty, value: string): this { + ow(key, ow.string.nonEmpty); + ow(value, ow.string.nonEmpty); + this.overrides.set(key, value); + return this; + } + skip(skip: SkipValue | SkipFunction): this { if (typeof skip !== 'function') { assertSkipValue(skip); @@ -253,9 +283,16 @@ export class CommandStep extends LabeledStep { ); } + private valueWithOverride(value: T, key: CommandProperty): string | T { + if (this.overrides.has(key)) { + return `$\{${this.overrides.get(key) as string}}`; + } + return value; + } + async toJson( opts: ToJsonSerializationOptions = { explicitDependencies: false }, - ): Promise> { + ): Promise> { // Need to pull out one of env/retry to get around a weird Typescript v4.0 bug. // When both env and retry were specified inside the return object, // the contents of retry were being copied to env. @@ -263,22 +300,47 @@ export class CommandStep extends LabeledStep { const retry = await (this.retry as RetryImpl).toJson(); return { ...(await super.toJson(opts)), - command: Command[transformCommandKey](this.command), - priority: this.priority, - env, - parallelism: this.parallelism, - concurrency: this.concurrency, - concurrency_group: this.concurrencyGroup, - concurrency_method: this.concurrencyMethod, - artifact_paths: this._artifactPaths.size - ? Array.from(this._artifactPaths) - : undefined, - agents: this.agents.size ? mapToObject(this.agents) : undefined, - timeout_in_minutes: this.timeout, - plugins: transformPlugins(this.plugins as PluginsImpl), - soft_fail: transformSoftFail(this._softFail), - skip: this._skip ? transformSkipValue(this._skip) : undefined, - retry, + command: this.valueWithOverride( + Command[transformCommandKey](this.command), + 'command', + ), + priority: this.valueWithOverride(this.priority, 'priority'), + env: this.valueWithOverride(env, 'env'), + parallelism: this.valueWithOverride(this.parallelism, 'parallelism'), + concurrency: this.valueWithOverride(this.concurrency, 'concurrency'), + concurrency_group: this.valueWithOverride( + this.concurrencyGroup, + 'concurrency_group', + ), + concurrency_method: this.valueWithOverride( + this.concurrencyMethod, + 'concurrency_method', + ), + artifact_paths: this.valueWithOverride( + this._artifactPaths.size ? Array.from(this._artifactPaths) : undefined, + 'artifact_paths', + ), + agents: this.valueWithOverride( + this.agents.size ? mapToObject(this.agents) : undefined, + 'agents', + ), + timeout_in_minutes: this.valueWithOverride( + this.timeout, + 'timeout_in_minutes', + ), + plugins: this.valueWithOverride( + transformPlugins(this.plugins as PluginsImpl), + 'plugins', + ), + soft_fail: this.valueWithOverride( + transformSoftFail(this._softFail), + 'soft_fail', + ), + skip: this.valueWithOverride( + this._skip ? transformSkipValue(this._skip) : undefined, + 'skip', + ), + retry: this.valueWithOverride(retry, 'retry'), }; } } diff --git a/yarn.lock b/yarn.lock index d8d9a2f..bbecd02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2913,6 +2913,11 @@ graphviz@^0.0.9: dependencies: temp "~0.4.0" +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + handlebars@^4.7.6: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -3223,6 +3228,11 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -3324,6 +3334,13 @@ is-windows@^1.0.1: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -4565,6 +4582,18 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= +node-notifier@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-10.0.0.tgz#93c154055b07b550a33a1966a1b11291c2113e69" + integrity sha512-ZTqP90y1eyb2xAZTa7j4AlAayTwh6cL8mn0nlJhLDq8itXGnJUmQGYOnpaMUvqZVfGo0vhU7KZ3HtDW6CT2SiQ== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.5" + shellwords "^0.1.1" + uuid "^8.3.2" + which "^2.0.2" + node-releases@^1.1.71: version "1.1.73" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" @@ -5578,6 +5607,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -6194,10 +6228,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc" - integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew== +typescript@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86" + integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ== uglify-js@^3.1.4: version "3.13.10" @@ -6267,6 +6301,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"