Repository: infinitered/solidarity
Pull request #195 (2018-03-30 16:34:35)

feat: inject versions into friendly errors
In your error message, you can include `{{wantedVersion}}` and `{{installedVersion}}` to give the user version information, or even to customize scripts to help them install or update.

```json
  "Yarn": [
    {
      "rule": "cli",
      "binary": "yarn",
      "error": "You have yarn@{{installedVersion}}, and need yarn@{{wantedVersion}}. Fix with `npm install -g yarn@{{wantedVersion}}`"
    }
  ]
```

codecov[bot]: # [Codecov](https://codecov.io/gh/infinitered/solidarity/pull/195?src=pr&el=h1) Report
> Merging [#195](https://codecov.io/gh/infinitered/solidarity/pull/195?src=pr&el=desc) into [master](https://codecov.io/gh/infinitered/solidarity/commit/dfe8fd8ed01f36b095751a4081e4d11f8df94df9?src=pr&el=desc) will **increase** coverage by `0.12%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/infinitered/solidarity/pull/195/graphs/tree.svg?height=150&width=650&token=eTjcyCooFB&src=pr)](https://codecov.io/gh/infinitered/solidarity/pull/195?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #195      +/-   ##
==========================================
+ Coverage   80.76%   80.88%   +0.12%     
==========================================
  Files          33       33              
  Lines         629      633       +4     
  Branches      108      111       +3     
==========================================
+ Hits          508      512       +4     
  Misses         83       83              
  Partials       38       38
```


| [Impacted Files](https://codecov.io/gh/infinitered/solidarity/pull/195?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/extensions/functions/checkCLI.ts](https://codecov.io/gh/infinitered/solidarity/pull/195/diff?src=pr&el=tree#diff-c3JjL2V4dGVuc2lvbnMvZnVuY3Rpb25zL2NoZWNrQ0xJLnRz) | `89.47% <100%> (+2.8%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/infinitered/solidarity/pull/195?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/infinitered/solidarity/pull/195?src=pr&el=footer). Last update [dfe8fd8...39fe7a6](https://codecov.io/gh/infinitered/solidarity/pull/195?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/options.md b/docs/options.md
index 82b4c21..1bb0fc7 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -99,9 +99,11 @@ Lastly, if output has multiple versions, you can identify the index of the versi
 ```
 
 ### Friendly Errors
-So what do we do if a rule fails?  The return code will be non-zero, but that's not the most friendly option.  You can set the `error` for any rule to give the user legible instruction on why the failure happened, and how they should solve it.
 
-*e.g.* Prompt them to install the missing CLI
+So what do we do if a rule fails? The return code will be non-zero, but that's not the most friendly option. You can set the `error` for any rule to give the user legible instruction on why the failure happened, and how they should solve it.
+
+_e.g._ Prompt them to install the missing CLI
+
 ```json
   "Watchman": [
     {
@@ -112,12 +114,26 @@ So what do we do if a rule fails?  The return code will be non-zero, but that's
   ]
 ```
 
+In your error message, you can include `{{wantedVersion}}` and `{{installedVersion}}` to give the user version information, or even to customize scripts to help them install or update.
+
+```json
+  "Yarn": [
+    {
+      "rule": "cli",
+      "binary": "yarn",
+      "error": "You have yarn@{{installedVersion}}, and need yarn@{{wantedVersion}}. Fix with `npm install -g yarn@{{wantedVersion}}`"
+    }
+  ]
+```
+
 ### Platform Specific Rules
-Some rules are only essential for a given node platform.  You can identify these rules with passing the `"platform"` property on any rule.
 
-A platform property takes a string or and array of strings that identify the platforms that rule pertains to.  Platforms can be any of the following: `["darwin", "macos", "freebsd", "linux", "sunos", "win32", "windows"]`
+Some rules are only essential for a given node platform. You can identify these rules with passing the `"platform"` property on any rule.
+
+A platform property takes a string or and array of strings that identify the platforms that rule pertains to. Platforms can be any of the following: `["darwin", "macos", "freebsd", "linux", "sunos", "win32", "windows"]`
+
+_e.g._ Rule only performs a check on Mac and Linux
 
-*e.g.* Rule only performs a check on Mac and Linux
 ```json
   "Watchman": [
     {
diff --git a/src/extensions/functions/checkCLI.ts b/src/extensions/functions/checkCLI.ts
index 672a067..87def3d 100644
--- a/src/extensions/functions/checkCLI.ts
+++ b/src/extensions/functions/checkCLI.ts
@@ -26,9 +26,15 @@ module.exports = async (rule: CLIRule, context: SolidarityRunContext): Promise<s
       binarySemantic += '.0'
     }
 
+    const customMessage = (rule.error || '')
+      .replace(/{{wantedVersion}}/gi, /\^|\~/.test(rule.semver) ? rule.semver.substr(1) : rule.semver)
+      .replace(/{{installedVersion}}/gi, binaryVersion)
+    const standardMessage = `${rule.binary}: you have '${binaryVersion}', but the project requires '${rule.semver}'`
+    const message = customMessage || standardMessage
+
     // I can't get no satisfaction
     if (!semver.satisfies(binarySemantic, rule.semver)) {
-      return `${rule.binary}: you have '${binaryVersion}', but the project requires '${rule.semver}'`
+      return message
     }
   }
 }


Test patch:
diff --git a/__tests__/command_helpers/checkCLI.ts b/__tests__/command_helpers/checkCLI.ts
index d8d7c40..0189105 100644
--- a/__tests__/command_helpers/checkCLI.ts
+++ b/__tests__/command_helpers/checkCLI.ts
@@ -19,6 +19,12 @@ const outOfDateCLI = {
   semver: '10.99',
 }
 
+const injectVersion = {
+  binary: 'node',
+  semver: '~7.6',
+  error: "Wanted: '{{wantedVersion}}', Installed: '{{installedVersion}}'. Update with `nvm install {{wantedVersion}}`",
+}
+
 const context = require('gluegun/toolbox')
 
 test('error on missing binary', async () => {
@@ -40,3 +46,10 @@ test('returns message on improper version', async () => {
 
   expect(await checkCLI(outOfDateCLI, context)).toBe(message)
 })
+
+test('returns message with injected versions', async () => {
+  const message = `Wanted: '7.6', Installed: '7.5'. Update with \`nvm install 7.6\``
+  solidarityExtension(context)
+  context.solidarity.getVersion = () => '7.5'
+  expect(await checkCLI(injectVersion, context)).toBe(message)
+})
diff --git a/__tests__/command_helpers/checkRequirement.ts b/__tests__/command_helpers/checkRequirement.ts
index 1e885fc..8d4b55e 100644
--- a/__tests__/command_helpers/checkRequirement.ts
+++ b/__tests__/command_helpers/checkRequirement.ts
@@ -90,6 +90,16 @@ describe('checkRequirement', () => {
       const result = await checkRequirement(rule, context)
       expect(result).toEqual([[]])
     })
+
+    test('inject versions', async () => {
+      checkCLI.mockImplementation(async () => "Wanted: '~1.5.1', Installed '1.3.2'")
+
+      const rule = toPairs({
+        YARN: [{ rule: 'cli', binary: 'yarn' }],
+      })[0]
+      const result = await checkRequirement(rule, context)
+      expect(result).toEqual(["Wanted: '~1.5.1', Installed '1.3.2'"])
+    })
   })
 
   describe('when rule: dir', () => {