Repository: infinitered/solidarity
Pull request #174 (2018-02-19 14:25:55)

Plugin generator
empower anyone to be able to type `solidarity create plugin` and 🎉  they are prompted and given a process to generate a simple plugin for Solidarity.

Improvements?
- [x] link to docs after creating a plugin
- [x]  thicken that readme
- [x] ask for a description to re-use
- [ ] ~detect the user's github url from git~
- [x] add tests

codecov[bot]: # [Codecov](https://codecov.io/gh/infinitered/solidarity/pull/174?src=pr&el=h1) Report
> Merging [#174](https://codecov.io/gh/infinitered/solidarity/pull/174?src=pr&el=desc) into [master](https://codecov.io/gh/infinitered/solidarity/commit/8a0532debb97d07aa324a55786ff9ed124e83dbd?src=pr&el=desc) will **decrease** coverage by `2.35%`.
> The diff coverage is `5.55%`.

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

```diff
@@            Coverage Diff            @@
##           master    #174      +/-   ##
=========================================
- Coverage   83.15%   80.8%   -2.36%     
=========================================
  Files          30      31       +1     
  Lines         576     594      +18     
  Branches       97      99       +2     
=========================================
+ Hits          479     480       +1     
- Misses         64      81      +17     
  Partials       33      33
```


| [Impacted Files](https://codecov.io/gh/infinitered/solidarity/pull/174?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/types.ts](https://codecov.io/gh/infinitered/solidarity/pull/174/diff?src=pr&el=tree#diff-c3JjL3R5cGVzLnRz) | `100% <ø> (ø)` | :arrow_up: |
| [src/extensions/functions/createPlugin.ts](https://codecov.io/gh/infinitered/solidarity/pull/174/diff?src=pr&el=tree#diff-c3JjL2V4dGVuc2lvbnMvZnVuY3Rpb25zL2NyZWF0ZVBsdWdpbi50cw==) | `5.55% <5.55%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/infinitered/solidarity/pull/174?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/174?src=pr&el=footer). Last update [8a0532d...032a293](https://codecov.io/gh/infinitered/solidarity/pull/174?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

TravisBuddy: ## Travis tests have failed
Hey **Gant Laborde**, 
Please read the following log in order to understand the failure reason. 
It'll be awesome if you fix what's wrong and commit the changes.


### Node.js: node

<details>
  <summary>
    <strong>
npm run test:ci
    </strong>
  </summary>

```
> solidarity@1.1.1 test:ci /home/travis/build/infinitered/solidarity
> jest --ci --runInBand && yarn test:extras

PASS __tests__/commands/snapshot.ts
PASS __tests__/command_helpers/checkRequirement.ts
PASS __tests__/command_helpers/updateRequirement.ts
PASS __tests__/command_helpers/reviewRule.ts
PASS __tests__/command_helpers/appendSolidaritySettings.ts
PASS __tests__/command_helpers/checkShell.ts
PASS __tests__/commands/solidarity.ts
PASS __tests__/command_helpers/checkRequirementPlugins.ts
PASS __tests__/command_helpers/getSolidaritySettings.ts
PASS __tests__/command_helpers/getVersion.ts
PASS __tests__/command_helpers/checkCLIForUpdates.ts
PASS __tests__/command_helpers/printResults.ts
PASS __tests__/command_helpers/removeNonVersionCharacters.ts
PASS __tests__/command_helpers/updateVersions.ts
PASS __tests__/command_helpers/findPluginInfo.ts
PASS __tests__/command_helpers/getLineWithVersion.ts
PASS __tests__/command_helpers/skipRule.ts
PASS __tests__/extensions/extensionCheck.ts
PASS __tests__/index.ts
PASS __tests__/commands/help.ts
PASS __tests__/command_helpers/checkCLI.ts
PASS __tests__/command_helpers/checkENV.ts
PASS __tests__/command_helpers/checkDir.ts
PASS __tests__/command_helpers/setSolidaritySettings.ts
PASS __tests__/command_helpers/checkFile.ts
PASS __tests__/command_helpers/solidarityReport.ts
PASS __tests__/integration/solidarity-check/check-invalid.ts
PASS __tests__/commands/report.ts
PASS __tests__/docs/testDocs.ts
PASS __tests__/command_helpers/binaryExists.ts
PASS __tests__/schema/validateExampleSchema.ts

Test Suites: 31 passed, 31 total
Tests:       144 passed, 144 total
Snapshots:   37 passed, 37 total
Time:        11.779s
Ran all test suites.
yarn run v1.3.2
$ tslint -p . && yarn madge --extensions ts --circular src/

ERROR: /home/travis/build/infinitered/solidarity/src/extensions/functions/createPlugin.ts[13, 9]: 'description' is declared but its value is never read.

error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! solidarity@1.1.1 test:ci: `jest --ci --runInBand && yarn test:extras`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the solidarity@1.1.1 test:ci script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/travis/.npm/_logs/2018-02-20T03_28_55_684Z-debug.log
```
</details>
<br />

<details>
  <summary>
    <strong>
yarn danger ci
    </strong>
  </summary>

```
yarn run v1.3.2
$ /home/travis/build/infinitered/solidarity/node_modules/.bin/danger ci
You may have updated from Danger 2.x -> 3.x without updating from `danger` to `danger ci`.
No issues or messages were sent. Removing any existing messages.
Done in 2.92s.
```
</details>
<br />


### Node.js: 8

<details>
  <summary>
    <strong>
npm run test:ci
    </strong>
  </summary>

```
> solidarity@1.1.1 test:ci /home/travis/build/infinitered/solidarity
> jest --ci --runInBand && yarn test:extras

PASS __tests__/commands/snapshot.ts
PASS __tests__/command_helpers/checkRequirement.ts
PASS __tests__/command_helpers/updateRequirement.ts
PASS __tests__/command_helpers/reviewRule.ts
PASS __tests__/command_helpers/appendSolidaritySettings.ts
PASS __tests__/command_helpers/checkShell.ts
PASS __tests__/commands/solidarity.ts
PASS __tests__/command_helpers/checkRequirementPlugins.ts
PASS __tests__/command_helpers/getSolidaritySettings.ts
PASS __tests__/command_helpers/getVersion.ts
PASS __tests__/command_helpers/checkCLIForUpdates.ts
PASS __tests__/command_helpers/printResults.ts
PASS __tests__/command_helpers/removeNonVersionCharacters.ts
PASS __tests__/command_helpers/updateVersions.ts
PASS __tests__/command_helpers/findPluginInfo.ts
PASS __tests__/command_helpers/getLineWithVersion.ts
PASS __tests__/command_helpers/skipRule.ts
PASS __tests__/extensions/extensionCheck.ts
PASS __tests__/index.ts
PASS __tests__/commands/help.ts
PASS __tests__/command_helpers/checkCLI.ts
PASS __tests__/command_helpers/checkENV.ts
PASS __tests__/command_helpers/checkDir.ts
PASS __tests__/command_helpers/setSolidaritySettings.ts
PASS __tests__/command_helpers/checkFile.ts
PASS __tests__/command_helpers/solidarityReport.ts
PASS __tests__/integration/solidarity-check/check-invalid.ts
PASS __tests__/commands/report.ts
PASS __tests__/docs/testDocs.ts
PASS __tests__/command_helpers/binaryExists.ts
PASS __tests__/schema/validateExampleSchema.ts

Test Suites: 31 passed, 31 total
Tests:       144 passed, 144 total
Snapshots:   37 passed, 37 total
Time:        16.642s
Ran all test suites.
yarn run v1.3.2
$ tslint -p . && yarn madge --extensions ts --circular src/

ERROR: /home/travis/build/infinitered/solidarity/src/extensions/functions/createPlugin.ts[13, 9]: 'description' is declared but its value is never read.

error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! solidarity@1.1.1 test:ci: `jest --ci --runInBand && yarn test:extras`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the solidarity@1.1.1 test:ci script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/travis/.npm/_logs/2018-02-20T03_28_51_679Z-debug.log
```
</details>
<br />

<details>
  <summary>
    <strong>
yarn danger ci
    </strong>
  </summary>

```
yarn run v1.3.2
$ /home/travis/build/infinitered/solidarity/node_modules/.bin/danger ci
You may have updated from Danger 2.x -> 3.x without updating from `danger` to `danger ci`.
Done in 2.86s.
```
</details>
<br />



Patch:
diff --git a/.travis.yml b/.travis.yml
index 792908c..3b0f7bf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,7 @@ script:
   - 'npm run test:ci'
   - yarn danger ci
 after_success:
-  - './node_modules/.bin/jest --coverage && cat ./coverage/lcov.info | ./node_modules/.bin/codecov'
+  - './node_modules/.bin/jest --coverage --runInBand && cat ./coverage/lcov.info | ./node_modules/.bin/codecov'
 notifications:
   webhooks:
     urls:
diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json
index eafdd51..dab156f 100644
--- a/.vscode/cSpell.json
+++ b/.vscode/cSpell.json
@@ -31,6 +31,7 @@
         "infinite.red",
         "info",
         "macos",
+        "namespacing",
         "newclear",
         "npmignore",
         "Plugin",
@@ -46,7 +47,8 @@
         "tada",
         "tempy",
         "updtr",
-        "v1"
+        "v1",
+        "walkthrough"
     ],
     // flagWords - list of words to be always considered incorrect
     // This is useful for offensive words and common spelling errors.
diff --git a/docs/plugins.md b/docs/plugins.md
index a291c0f..9e099da 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -1,6 +1,8 @@
 # Writing your own plugins
 If you're using a technology that doesn't have a plugin, or if you'd just like build your own custom rules to use in solidarity, we've made creating plugins extremely simple.
 
+The following docs will show you all the features of how to create a plugin.  If you'd like, you can generate a plugin-base by typing `solidarity create plugin`. This starts a walkthrough that will ask you questions to help you get started writing your plugin.
+
 ## Plugin Docs
 * [Write the Simplest Plugin](/docs/simplePlugin.md)
 * [Plugins that write Solidarity Files from Code](/docs/solidarityFromCode.md)
diff --git a/src/commands/create.ts b/src/commands/create.ts
new file mode 100644
index 0000000..0a540fe
--- /dev/null
+++ b/src/commands/create.ts
@@ -0,0 +1,25 @@
+import { GluegunCommand } from 'gluegun'
+
+const createables = ['plugin']
+
+module.exports = {
+  alias: 'c',
+  description: 'Displays this help',
+  run: async context => {
+    const { print, solidarity, parameters } = context
+    switch (parameters.first && parameters.first.toLowerCase()) {
+      case 'plugin':
+        // Handle errors like grown-ups
+        try {
+          await solidarity.createPlugin(context)
+        } catch (e) {
+          print.error(e)
+        }
+        break
+      default:
+        print.error('Missing what to create')
+        print.error('solidarity create <wut?>')
+        print.info(`Things you can create: ${createables}`)
+    }
+  },
+} as GluegunCommand
diff --git a/src/extensions/functions/createPlugin.ts b/src/extensions/functions/createPlugin.ts
new file mode 100644
index 0000000..7db2f7f
--- /dev/null
+++ b/src/extensions/functions/createPlugin.ts
@@ -0,0 +1,59 @@
+module.exports = async context => {
+  let files = [['.gitignore.ejs', '.gitignore'], ['README.md.ejs', 'README.md'], ['package.json.ejs', 'package.json']]
+  const { print, template, prompt } = context
+  const { colors } = print
+
+  const answerPluginName = await prompt.ask({
+    type: 'input',
+    name: 'plugin',
+    message: 'Plugin name? (we will add the namespacing for you)',
+  })
+  if (!answerPluginName.plugin) throw Error('A plugin requires a name')
+  const pluginName = `solidarity-${answerPluginName.plugin.replace('solidarity-', '')}`
+
+  const description = await prompt.ask({
+    type: 'input',
+    name: 'pluginDesc',
+    message: 'Short plugin description (used in various places)'
+  })
+
+  const ruleChoices = [
+    'I do not want a generated rule file',
+    'Just a simple rule template',
+    'Template + optional rules',
+  ]
+  const answer = await prompt.ask({
+    type: 'list',
+    name: 'ruleChoice',
+    message: 'Your initial rule file template?',
+    choices: ruleChoices,
+  })
+
+  if (answer.ruleChoice === ruleChoices[1]) {
+    files.push(['rules-template.json.ejs', `templates/${pluginName}-template.json`])
+    files.push(['simple-plugin.js.ejs', `extensions/${pluginName}.js`])
+  } else if (answer.ruleChoice === ruleChoices[2]) {
+    files.push(['rules-template.json.ejs', `templates/${pluginName}-template.json`])
+    files.push(['helpful-plugin.js.ejs', `extensions/${pluginName}.js`])
+    files.push(['addOptionalRules.js.ejs', `extensions/helpers/addOptionalRules.js`])
+  }
+
+  const customRules = await prompt.confirm('Custom rules? (e.g. Rules other than basic types)')
+
+  print.info(`Creating plugin ${pluginName}`)
+  // copy files over
+  files.map(fileSet => {
+    template.generate({
+      template: fileSet[0],
+      target: `${pluginName}/${fileSet[1]}`,
+      props: { pluginName, customRules, description: description.pluginDesc },
+    })
+  })
+
+  print.success(`
+    Done! ${colors.magenta('\n\nPlugin Docs: https://infinitered.github.io/solidarity/#/docs/plugins')}
+  `)
+
+  // for tests really
+  return files
+}
diff --git a/src/templates/.gitignore.ejs b/src/templates/.gitignore.ejs
new file mode 100644
index 0000000..7ccd6bd
--- /dev/null
+++ b/src/templates/.gitignore.ejs
@@ -0,0 +1,9 @@
+node_modules/
+yarn-error.log
+.node-version
+coverage
+.nyc_output
+.idea
+dist/
+.vscode/*
+!.vscode/cSpell.json
diff --git a/src/templates/README.md.ejs b/src/templates/README.md.ejs
new file mode 100644
index 0000000..9b4f826
--- /dev/null
+++ b/src/templates/README.md.ejs
@@ -0,0 +1,12 @@
+<a href='https://infinitered.github.io/solidarity/'><img src='https://github.com/infinitered/solidarity/raw/master/_art/plugin.jpg' align='left' height="60"/></a>
+
+# <%= props.pluginName %>
+## <%= props.description %>
+Much longer description
+
+## Install:
+`npm i <%= props.pluginName %>` or `yarn add <%= props.pluginName %>`
+This plugin will automatically be picked up by Solidarity (which should already be installed).
+
+## What is Solidarity?
+#### [:newspaper: Read More About Solidarity Here](https://github.com/infinitered/solidarity)
diff --git a/src/templates/addOptionalRules.js.ejs b/src/templates/addOptionalRules.js.ejs
new file mode 100644
index 0000000..5d2fbdd
--- /dev/null
+++ b/src/templates/addOptionalRules.js.ejs
@@ -0,0 +1,12 @@
+module.exports = (context, requirements) => {
+  const { solidarity, filesystem } = context
+  const { binaryExists } = solidarity
+
+  // Conditionally add requirements and rules
+
+  // Example:
+  // if (binaryExists('yarn', context) && filesystem.exists('./yarn.lock') === 'file') {
+  //   requirements['Yarn'] = [{rule: 'cli', binary: 'yarn', version: '--version', semver: '0.0.0'}]
+  // }
+
+}
diff --git a/src/templates/helpful-plugin.js.ejs b/src/templates/helpful-plugin.js.ejs
new file mode 100644
index 0000000..a4a1c0c
--- /dev/null
+++ b/src/templates/helpful-plugin.js.ejs
@@ -0,0 +1,42 @@
+const addOptionalRules = require('./helpers/addOptionalRules')
+
+module.exports = (context) => {
+  // Register this plugin
+  context.addPlugin({
+    name: '<%= props.pluginName %>',
+    description: '<%= props.description %>',
+    snapshot: async (context) => {
+      // start with template
+      let solidarity = require('../templates/<%= props.pluginName %>-template.json')
+      // add optional rules
+      addOptionalRules(context, solidarity.requirements)
+      // write out .solidarity file
+      context.solidarity.setSolidaritySettings(solidarity, context)
+      // update file with local versions
+      await context.system.run('solidarity snapshot')
+    }<% if(props.customRules){ %>,
+    rules: {
+      ruleName: {
+        check: async (rule, context) => {
+          return {
+            pass: true,
+            message: 'Check always passes'
+          }
+        },
+        snapshot: async (rule, context) => [
+          {
+            prop: 'addedProp',
+            value: '12.0.0'
+          },
+        ],
+        report: async (rule, context, report) => {
+          report.addCLI({
+            binary: 'imaginaryCLI',
+            version: '10',
+            desired: '12'
+          })
+        }
+      }
+    }<% } %>
+  })
+}
diff --git a/src/templates/package.json.ejs b/src/templates/package.json.ejs
new file mode 100644
index 0000000..172727e
--- /dev/null
+++ b/src/templates/package.json.ejs
@@ -0,0 +1,12 @@
+{
+  "name": "<%= props.pluginName %>",
+  "version": "1.0.0",
+  "repository": "https://github.com/<me>/<%= props.pluginName %>",
+  "description": "<%= props.description %>",
+  "author": "Super Awesome Coder",
+  "license": "MIT",
+  "private": false,
+  "scripts": {
+    "test": "echo not yet"
+  }
+}
diff --git a/src/templates/rules-template.json.ejs b/src/templates/rules-template.json.ejs
new file mode 100644
index 0000000..f51731f
--- /dev/null
+++ b/src/templates/rules-template.json.ejs
@@ -0,0 +1,6 @@
+{
+  "$schema": "http://json.schemastore.org/solidaritySchema",
+  "requirements": {
+    "Node Example": [{ "rule": "cli", "binary": "node", "semver": "0.0.0"}]
+  }
+}
diff --git a/src/templates/simple-plugin.js.ejs b/src/templates/simple-plugin.js.ejs
new file mode 100644
index 0000000..dff1bd6
--- /dev/null
+++ b/src/templates/simple-plugin.js.ejs
@@ -0,0 +1,31 @@
+module.exports = (context) => {
+  // Register this plugin
+  context.addPlugin({
+    name: '<%= props.pluginName %>',
+    description: '<%= props.description %>',
+    snapshot: '<%= props.pluginName %>-template.json'<% if(props.customRules){ %>,
+    rules: {
+      ruleName: {
+        check: async (rule, context) => {
+          return {
+            pass: true,
+            message: 'Check always passes'
+          }
+        },
+        snapshot: async (rule, context) => [
+          {
+            prop: 'addedProp',
+            value: '12.0.0'
+          },
+        ],
+        report: async (rule, context, report) => {
+          report.addCLI({
+            binary: 'imaginaryCLI',
+            version: '10',
+            desired: '12'
+          })
+        }
+      }
+    }<% } %>
+  })
+}
diff --git a/src/types.ts b/src/types.ts
index 24c4a75..6feb52a 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -19,6 +19,7 @@ export const solidarity = {
   buildSpecificRequirement: require('./extensions/functions/buildSpecificRequirement'),
   appendSolidaritySettings: require('./extensions/functions/appendSolidaritySettings'),
   ruleHandlers: require('./extensions/functions/ruleHandlers'),
+  createPlugin: require('./extensions/functions/createPlugin'),
 }
 
 export interface SolidarityPlugin {


Test patch:
diff --git a/__tests__/__mocks__/mockContext.ts b/__tests__/__mocks__/mockContext.ts
index 599375e..1ed2f51 100644
--- a/__tests__/__mocks__/mockContext.ts
+++ b/__tests__/__mocks__/mockContext.ts
@@ -17,6 +17,9 @@ const mockContext = {
   system: {
     startTimer: jest.fn(() => jest.fn()),
   },
+  template: {
+    generate: jest.fn(),
+  },
   print: {
     error: jest.fn(),
     success: jest.fn(),
@@ -33,11 +36,13 @@ const mockContext = {
       green: jest.fn(),
       red: jest.fn(),
       blue: jest.fn(),
+      magenta: jest.fn(),
     },
     colors: {
       green: jest.fn(),
       red: jest.fn(),
       blue: jest.fn(),
+      magenta: jest.fn(),
     },
   },
   printSeparator: jest.fn(),
@@ -45,7 +50,8 @@ const mockContext = {
     options: {},
   },
   prompt: {
-    ask: jest.fn(() => Promise.resolve({ createFile: true })),
+    ask: jest.fn(({ name }) => Promise.resolve({ [name]: 'taco', createFile: true })),
+    confirm: jest.fn(() => true)
   },
   solidarity: noConfigSolidarity,
 }
diff --git a/__tests__/command_helpers/__snapshots__/createPlugin.ts.snap b/__tests__/command_helpers/__snapshots__/createPlugin.ts.snap
new file mode 100644
index 0000000..34dd200
--- /dev/null
+++ b/__tests__/command_helpers/__snapshots__/createPlugin.ts.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`check result shape 1`] = `[Function]`;
+
+exports[`investigate createPlugin 1`] = `
+Array [
+  Array [
+    ".gitignore.ejs",
+    ".gitignore",
+  ],
+  Array [
+    "README.md.ejs",
+    "README.md",
+  ],
+  Array [
+    "package.json.ejs",
+    "package.json",
+  ],
+]
+`;
diff --git a/__tests__/command_helpers/createPlugin.ts b/__tests__/command_helpers/createPlugin.ts
new file mode 100644
index 0000000..f04b987
--- /dev/null
+++ b/__tests__/command_helpers/createPlugin.ts
@@ -0,0 +1,16 @@
+const createPlugin: Function = require('../../src/extensions/functions/createPlugin')
+const context = require('mockContext')
+
+test('check result shape', () => {
+  expect(createPlugin).toMatchSnapshot()
+})
+
+test('investigate createPlugin', async () => {
+
+  const result = await createPlugin(context)
+  expect(result).toMatchSnapshot()
+  expect(context.template.generate).toBeCalled()
+  expect(context.prompt.ask).toBeCalled()
+  expect(context.prompt.confirm).toBeCalled()
+  expect(context.print.success).toBeCalled()
+})
diff --git a/__tests__/commands/__snapshots__/create.ts.snap b/__tests__/commands/__snapshots__/create.ts.snap
new file mode 100644
index 0000000..940d4d3
--- /dev/null
+++ b/__tests__/commands/__snapshots__/create.ts.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Snapshot create command 1`] = `
+Object {
+  "alias": "c",
+  "description": "Displays this help",
+  "run": [Function],
+}
+`;
diff --git a/__tests__/commands/create.ts b/__tests__/commands/create.ts
new file mode 100644
index 0000000..fd6119e
--- /dev/null
+++ b/__tests__/commands/create.ts
@@ -0,0 +1,34 @@
+import createCommand from '../../src/commands/create'
+const mockContext = require('mockContext')
+
+test('Snapshot create command', () => {
+  expect(createCommand).toMatchSnapshot()
+})
+
+it('enforces required properties', () => {
+  expect(createCommand.description).toBeTruthy()
+  expect(createCommand.run).toBeTruthy()
+  expect(typeof createCommand.run).toBe('function')
+})
+
+test('check solidarity create with no parameter', async () => {
+  const result = await createCommand.run(mockContext)
+  expect(mockContext.print.error.mock.calls).toEqual([["Missing what to create"], ["solidarity create <wut?>"]])
+  expect(mockContext.print.info.mock.calls.length).toBe(1)
+})
+
+test('check solidarity create with plugin', async () => {
+  const goodContext = {
+    ...mockContext,
+    parameters: { first: 'plugin' },
+    solidarity: { createPlugin: jest.fn() }
+  }
+  await createCommand.run(goodContext)
+  expect(goodContext.solidarity.createPlugin).toBeCalled()
+
+  // now make it fail
+  const errorString = 'ER MA GERD ARRAWRS'
+  goodContext.solidarity.createPlugin = jest.fn(() => throw Error(errorString))
+  await createCommand.run(goodContext)
+  expect(goodContext.print.error.mock.calls.slice(-1).toString()).toEqual(`Error: ${errorString}`)
+})
diff --git a/__tests__/extensions/__snapshots__/extensionCheck.ts.snap b/__tests__/extensions/__snapshots__/extensionCheck.ts.snap
index f2c3df4..d33967f 100644
--- a/__tests__/extensions/__snapshots__/extensionCheck.ts.snap
+++ b/__tests__/extensions/__snapshots__/extensionCheck.ts.snap
@@ -10,6 +10,7 @@ Object {
   "checkENV": [Function],
   "checkFile": [Function],
   "checkRequirement": [Function],
+  "createPlugin": [Function],
   "getLineWithVersion": [Function],
   "getSolidaritySettings": [Function],
   "getVersion": [Function],