Support config file for inso (#2420)

This commit is contained in:
Opender Singh 2020-07-29 11:47:36 +12:00 committed by GitHub
parent c5283ab14a
commit b50a4bf623
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 828 additions and 1622 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,56 @@
# `inso`
A CLI to accompany <a href="https://insomnia.rest">Insomnia Designer</a>
<div align="center">
<pre>npm i -g <a href="https://www.npmjs.com/package/insomnia-inso">insomnia-inso</a></pre>
</div>
---
Table of Contents
=================
## Data source
* [Data source](#data-source)
* [The `[identifier]` argument](#the-identifier-argument)
* [Global options](#global-options)
* [Commands](#commands)
+ [ `inso generate config` ](#-inso-generate-config-options-identifier)
+ [ `inso lint spec` ](#-inso-lint-spec-identifier)
+ [ `inso run test` ](#-inso-run-test-options-identifier)
+ [ `inso export spec` ](#-inso-export-spec-identifier)
+ [ `inso script` ](#-inso-script-name)
* [Configuration](#configuration)
* [Git Bash](#git-bash)
* [Continuous Integration](#continuous-integration)
* [Development](#development)
# Data source
`inso` will first try to find a `.insomnia` directory in it's working directory. This directory is generated in a git repository when using git sync in Designer. When `inso` is used in a CI environment, it will always run against the `.insomnia` directory.
If `inso` cannot find the `.insomnia` directory, it will try to run against the Designer app data directory (if found). You can override both the working directory, and the app data directory, using the `--working-dir` and `--app-data-dir` global options.
## The `[identifier]` argument
# The `[identifier]` argument
Typically, Insomnia database id's are quite long, for example: `wrk_012d4860c7da418a85ffea7406e1292a`. When specifying an identifier for `inso`, similar to Git hashes, you may choose to concatenate and use the first x characters (for example, `wrk_012d486` ), which is very likely to be unique. If in the rare chance the short id is _not_ unique against the data, `inso` will inform as such.
Typically, Insomnia database id's are quite long, for example: `wrk_012d4860c7da418a85ffea7406e1292a` . When specifying an identifier for `inso` , similar to Git hashes, you may choose to concatenate and use the first x characters (for example, `wrk_012d486` ), which is very likely to be unique. If in the rare chance the short id is _not_ unique against the data, `inso` will inform as such.
Additionally, if the `[identifier]` argument is ommitted from the command, `inso` will search in the database for the information it needs, and prompt the user. Prompts can be disabled with the `--ci` global option.
![](https://raw.githubusercontent.com/Kong/insomnia/develop/packages/insomnia-inso/assets/ci-demo.gif)
## Commands
### `$ inso [global options] [command]`
# Global options
`$ inso [global options] [command]`
|Global option|Alias|Description|
|- |- |- |
| `--working-dir <dir>` | `-w` |set working directory|
| `--app-data-dir <dir>` | `-a` |set the app data directory|
| `--workingDir <dir>` | `-w` |set working directory|
| `--appDataDir <dir>` | `-a` |set the app data directory|
| `--config <path>` | |path to the configuration file|
| `--ci` | | run in CI, disables all prompts |
| `--version` | `-v` |output the version number|
| `--help` | `-h` |display help for a command|
### `$ inso generate config [options] [identifier]`
# Commands
## `$ inso generate config [identifier]`
Similar to the Kong [Kubernetes](https://insomnia.rest/plugins/insomnia-plugin-kong-kubernetes-config) and [Declarative](https://insomnia.rest/plugins/insomnia-plugin-kong-declarative-config) config plugins for Designer, this command can generate configuration from an API specification, using [openapi-2-kong](https://www.npmjs.com/package/openapi-2-kong).
@ -45,47 +61,64 @@ Similar to the Kong [Kubernetes](https://insomnia.rest/plugins/insomnia-plugin-k
| `--type <type>` | `-t` |type of configuration to generate, options are `kubernetes` and `declarative` (default: `declarative` ) |
| `--output <path>` | `-o` |save the generated config to a file in the working directory|
#### Examples
### Examples
<details>
<summary>When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory</summary>
When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory
```
Not specifying any arguments will prompt
``` sh
inso generate config
inso generate config spc_46c5a4 --type declarative
inso generate config spc_46c5a4 --type declarative > output.yaml
inso generate config "Sample Specification" --output output.yaml
inso generate config "Sample Specification" --type kubernetes
inso generate config spec.yaml --working-dir another/dir
```
Scope by the document name or id
``` sh
inso generate config spc_46c5a4 --type declarative
inso generate config "Sample Specification" --type kubernetes
```
Scope by a file on the filesystem
``` sh
inso generate config spec.yaml
inso generate config spec.yaml --workingDir another/dir
```
Output to file
``` sh
inso generate config spc_46c5a4 --output output.yaml
inso generate config spc_46c5a4 > output.yaml
```
</details>
### `$ inso lint spec [identifier]`
## `$ inso lint spec [identifier]`
Designer has the ability to lint and validate your OpenAPI specification as you write it. This command adds the same functionality to `inso` , in order to run linting during CI workflows. Lint results will be printed to the console, and `inso` will exit with an appropriate exit code.
**`[identifier]`**: this can be a **document name, or id**.
#### Examples
### Examples
<details>
<summary>When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory</summary>
When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory
```
Not specifying any arguments will prompt
``` sh
inso lint spec
```
Scope by the document name or id
``` sh
inso lint spec spc_46c5a4
inso lint spec "Sample Specification"
```
</details>
### `$ inso run test [options] [identifier]`
## `$ inso run test [identifier]`
API Unit Testing was introduced with Designer 2020.3.0, and this command adds the functionality to execute those unit tests via the command line, very useful for a CI environment. `inso` will report on test results, and exit with an appropriate exit code.
@ -97,54 +130,177 @@ The test runner is built on top of Mocha, thus many of the options behave as the
|- |- |- |
| `--env <identifier>` | `-e` |the environment to use - an environment name or id |
| `--reporter <value>` | `-r` |reporter to use, options are `dot, list, spec, min and progress` (default: `spec` )|
| `--test-name-pattern <regex>` | `-t` | run tests that match the regex|
| `--testNamePattern <regex>` | `-t` | run tests that match the regex|
| `--bail` | `-b` | abort ("bail") after the first test failure|
| `--keep-file` | | do not delete the generated test file (useful for debugging)|
| `--keepFile` | | do not delete the generated test file (useful for debugging)|
#### Examples
### Examples
<details>
<summary>When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory</summary>
When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory
Not specifying any arguments will prompt
```
``` sh
inso run test
```
Scope by the document name or id
```
``` sh
inso run test "Sample Specification" --env "OpenAPI env"
inso run test spc_46c5a4 --env env_env_ca046a
```
Scope by the a test suite name or id
```
``` sh
inso run test "Math Suite" --env "OpenAPI env"
inso run test uts-7f0f85 --env env_env_ca046a
```
Scope by test name regex, and control test running and reporting
```
inso run test "Sample Specification" --test-name-pattern Math --env env_env_ca046a
inso run test spc_46c5a4 --reporter progress --bail --keep-file
``` sh
inso run test "Sample Specification" --testNamePattern Math --env env_env_ca046a
inso run test spc_46c5a4 --reporter progress --bail --keepFile
```
More examples: [#2338](https://github.com/Kong/insomnia/pull/2338).
</details>
## Git Bash
## `$ inso export spec [identifier]`
Git Bash on Windows is not interactive and therefore prompts from `inso` will not work as expected. You may choose to specify the identifiers for each command explicitly, or run `inso` using `winpty`:
This command will extract and export the raw OpenAPI specification from the data store. If the `--output` option is not specified, the spec will print to console.
**`[identifier]`**: this can be a **document name, or id**.
|Option|Alias|Description|
|- |- |- |
| `--output <path>` | `-o` |save the generated config to a file in the working directory|
### Examples
When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory
Not specifying any arguments will prompt
``` sh
inso export spec
```
Scope by the document name or id
``` sh
inso export spec spc_46c5a4
inso export spec "Sample Specification"
```
Output to file
``` sh
inso export spec spc_46c5a4 --output output.yaml
inso export spec spc_46c5a4 > output.yaml
```
## `$ inso script <name>`
The `inso` [config file](#configuration) supports scripts, akin to NPM scripts defined in a `package.json` file. These scripts can be executed by `inso` by running `inso script <name>` , or simply `inso <name>` as this is the default command. Any options passed to this command, will be forwarded to the script being executed.
### Examples
When running in the <a href="https://github.com/Kong/insomnia/tree/develop/packages/insomnia-inso/src/db/__fixtures__/git-repo">git-repo</a> directory, with the following inso config file.
``` yaml
# .insorc.yaml
scripts:
lint: lint spec "Sample Specification"
gen-conf: generate config "Sample Specification"
gen-conf:k8s: gen-conf --type kubernetes
```
Run commands with or without the `script` prefix
``` bash
inso script gen-conf
inso gen-conf
```
If a conflict exists with another command (eg. `lint` ), you must prefix with `script`
``` bash
inso script lint
inso lint # will not work
```
Any options passed during script invocation will be forwarded to the script
``` bash
inso gen-conf # generates declarative config (default)
inso gen-conf:k8s # generates kubernetes config
inso gen-conf:k8s -t declarative # generates declarative config
inso gen-conf:k8s -o output.yaml # generates kubernetes config to output.yaml
```
# Configuration
Inso can be configured with a configuration file, allowing you to specify options and scripts. For example, when running in a CI environment, you may choose to specify the steps as scripts in a config file, so that the same commands can be run both locally and in CI.
Inso uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) for config file management, meaning any of the following items found in the working tree are automatically used:
+ `inso` property in `package.json`
+ `.insorc` file in JSON or YAML format
+ `.insorc.json` file
+ `.insorc.yaml` , `.insorc.yml` , or `.insorc.js` file
+ `inso.config.js` file exporting a JS object
Alternatively, you can use the `--config <file>` global option to specify an exact file to use, if it exists outside the directory tree.
**Options**
Options from the config file are combined with option defaults and any explicit overrides specified in script or command invocations. This combination is in priority order: command options > config file options > default options.
Any options specified in this file will apply to all scripts and manual commands. You can override these options by specifying them explicitly, when invoking a script or command.
Only [global options](#global-options) can be set in the config file.
**Scripts**
Scripts can have any name, and can be nested. Scripts must be prefixed with `inso` (see example below). Each command behaves the same way, as described in the sections above.
### Example
``` yaml
# .insorc.yaml
options:
ci: false
scripts:
test-spec: inso run test Demo --env DemoEnv --reporter progress
test-spec:200s: inso testSpec --testNamePattern 200
test-spec:404s: inso testSpec --testNamePattern 404
test-math-suites: inso run test uts_8783c30a24b24e9a851d96cce48bd1f2 --env DemoEnv
test-request-suite: inso run test uts_bce4af --env DemoEnv --bail
lint: inso lint spec Demo # must be invoked as `inso script lint`
gen-conf: inso generate config "Designer Demo" --type declarative
gen-conf:k8s: inso gen-conf --type kubernetes
```
# Git Bash
Git Bash on Windows is not interactive and therefore prompts from `inso` will not work as expected. You may choose to specify the identifiers for each command explicitly, or run `inso` using `winpty` :
```
winpty inso.cmd generate config
```
## Continuous Integration
# Continuous Integration
`inso` has been designed to run in a CI environment, disabling prompts and providing exit codes to pass or fail the CI workflow accordingly. An example workflow run in Github Actions is as follows. This example will checkout > install NodeJS > install inso > run linting > run unit tests > generate configuration. If any of these steps fail, the GH workflow will as well.
```yaml
``` yaml
# .github/workflows/test.yml
name: Test
@ -168,7 +324,7 @@ jobs:
run: inso generate config "Designer Demo" --type declarative --ci
```
## Development
# Development
* Bootstrap: `npm run bootstrap`
* Start the compiler in watch mode: `npm run watch`

View File

@ -0,0 +1,5 @@
const mock = { load: jest.fn(), search: jest.fn() };
module.exports = {
cosmiconfigSync: () => mock,
};

View File

@ -0,0 +1,5 @@
// @flow
declare module 'cosmiconfig' {
declare module.exports: *;
}

View File

@ -0,0 +1,5 @@
// @flow
declare module 'string-argv' {
declare module.exports: *;
}

View File

@ -1541,6 +1541,11 @@
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
"dev": true
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"@types/prettier": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.1.tgz",
@ -2643,8 +2648,7 @@
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
},
"camelcase": {
"version": "5.3.1",
@ -3090,6 +3094,18 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
}
},
"create-ecdh": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
@ -3525,7 +3541,6 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
@ -4676,6 +4691,22 @@
"resolved": "https://registry.npmjs.org/immer/-/immer-4.0.2.tgz",
"integrity": "sha512-Q/tm+yKqnKy4RIBmmtISBlhXuSDrB69e9EKTYiIenIKQkXBQir43w+kN/eGiax3wt1J0O1b2fYcNqLSbEcXA7w=="
},
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
"integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"dependencies": {
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
}
}
},
"import-local": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz",
@ -4768,8 +4799,7 @@
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"is-binary-path": {
"version": "1.0.1",
@ -5601,8 +5631,7 @@
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
},
"json-schema": {
"version": "0.2.3",
@ -5712,8 +5741,7 @@
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
"dev": true
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
},
"loader-runner": {
"version": "2.4.0",
@ -6418,6 +6446,14 @@
"readable-stream": "^2.1.5"
}
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"requires": {
"callsites": "^3.0.0"
}
},
"parse-asn1": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
@ -6436,7 +6472,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
"integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
@ -6498,6 +6533,11 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
},
"pbkdf2": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
@ -7968,6 +8008,11 @@
"resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz",
"integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA=="
},
"string-argv": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
"integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg=="
},
"string-length": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",

View File

@ -64,6 +64,7 @@
"dependencies": {
"@stoplight/spectral": "^5.4.0",
"commander": "^5.1.0",
"cosmiconfig": "^6.0.0",
"enquirer": "^2.3.5",
"env-paths": "^2.2.0",
"insomnia-plugin-base64": "^2.2.10",
@ -84,6 +85,7 @@
"mkdirp": "^1.0.4",
"nedb": "^1.8.0",
"openapi-2-kong": "^2.2.12",
"string-argv": "^0.3.1",
"yaml": "^1.10.0"
}
}

View File

@ -0,0 +1 @@
should-be-ignored: 'test'

View File

@ -0,0 +1,15 @@
options:
ci: false
scripts:
lint: inso lint spec "Designer Demo"
test: inso run test "Designer Demo" --env UnitTest --bail --reporter progress
test:200s: inso test --testNamePattern 200
test:404s: inso test --testNamePattern 404
test:suite:math: inso run test uts_8783c30a24b24e9a851d96cce48bd1f2 --env UnitTest --bail --reporter progress
test:suite:requests: inso run test uts_bce4af --env UnitTest --bail --reporter progress
gen-conf: inso generate config "Designer Demo" --type declarative
gen-conf:k8s: inso gen-conf --type kubernetes
invalid-script: generate config "Designer Demo"

View File

@ -0,0 +1,7 @@
options:
appDataDir: configFile
workingDir: workingDir
ci: true
shouldBeIgnored: this should be ignored because it is not a global option
scripts:
lint: inso lint spec

View File

@ -6,18 +6,20 @@ exports[`Snapshot for "inso --help" 1`] = `
A CLI for Insomnia!
Options:
-v, --version output the version number
-w, --working-dir <dir> set working directory
-a, --app-data-dir <dir> set the app data directory
--ci run in CI, disables all prompts
-h, --help display help for command
-v, --version output the version number
-w, --workingDir <dir> set working directory
-a, --appDataDir <dir> set the app data directory
--config <path> path to configuration file
--ci run in CI, disables all prompts
-h, --help display help for command
Commands:
generate Code generation utilities
run Execution utilities
lint Linting utilities
export Export data from insomnia models
help [command] display help for command"
generate Code generation utilities
run Execution utilities
lint Linting utilities
export Export data from insomnia models
script <name> Run scripts defined in .insorc
help [command] display help for command"
`;
exports[`Snapshot for "inso -h" 1`] = `
@ -26,18 +28,20 @@ exports[`Snapshot for "inso -h" 1`] = `
A CLI for Insomnia!
Options:
-v, --version output the version number
-w, --working-dir <dir> set working directory
-a, --app-data-dir <dir> set the app data directory
--ci run in CI, disables all prompts
-h, --help display help for command
-v, --version output the version number
-w, --workingDir <dir> set working directory
-a, --appDataDir <dir> set the app data directory
--config <path> path to configuration file
--ci run in CI, disables all prompts
-h, --help display help for command
Commands:
generate Code generation utilities
run Execution utilities
lint Linting utilities
export Export data from insomnia models
help [command] display help for command"
generate Code generation utilities
run Execution utilities
lint Linting utilities
export Export data from insomnia models
script <name> Run scripts defined in .insorc
help [command] display help for command"
`;
exports[`Snapshot for "inso export -h" 1`] = `
@ -83,7 +87,7 @@ Generate configuration from an api spec.
Options:
-t, --type <value> type of configuration to generate, options are
[kubernetes, declarative] (default: \\"declarative\\")
[kubernetes, declarative] (default: declarative)
-o, --output <path> save the generated config to a file
-h, --help display help for command"
`;
@ -94,18 +98,20 @@ exports[`Snapshot for "inso help" 1`] = `
A CLI for Insomnia!
Options:
-v, --version output the version number
-w, --working-dir <dir> set working directory
-a, --app-data-dir <dir> set the app data directory
--ci run in CI, disables all prompts
-h, --help display help for command
-v, --version output the version number
-w, --workingDir <dir> set working directory
-a, --appDataDir <dir> set the app data directory
--config <path> path to configuration file
--ci run in CI, disables all prompts
-h, --help display help for command
Commands:
generate Code generation utilities
run Execution utilities
lint Linting utilities
export Export data from insomnia models
help [command] display help for command"
generate Code generation utilities
run Execution utilities
lint Linting utilities
export Export data from insomnia models
script <name> Run scripts defined in .insorc
help [command] display help for command"
`;
exports[`Snapshot for "inso lint -h" 1`] = `
@ -149,11 +155,11 @@ exports[`Snapshot for "inso run test -h" 1`] = `
Run Insomnia unit test suites
Options:
-e, --env <identifier> environment to use
-t, --test-name-pattern <regex> run tests that match the regex
-r, --reporter <reporter> reporter to use, options are [dot, list,
spec, min, progress] (default: \\"spec\\")
-b, --bail abort (\\"bail\\") after first test failure
--keep-file do not delete the generated test file
-h, --help display help for command"
-e, --env <identifier> environment to use
-t, --testNamePattern <regex> run tests that match the regex
-r, --reporter <reporter> reporter to use, options are [dot, list, spec,
min, progress] (default: spec)
-b, --bail abort (\\"bail\\") after first test failure
--keepFile do not delete the generated test file
-h, --help display help for command"
`;

View File

@ -4,20 +4,18 @@ import { generateConfig } from '../commands/generate-config';
import { lintSpecification } from '../commands/lint-specification';
import { runInsomniaTests } from '../commands/run-tests';
import { exportSpecification } from '../commands/export-specification';
import { parseArgsStringToArgv } from 'string-argv';
jest.mock('../commands/generate-config');
jest.mock('../commands/lint-specification');
jest.mock('../commands/run-tests');
jest.mock('../commands/export-specification');
jest.unmock('cosmiconfig');
const initInso = () => {
return (args: string): void => {
const cliArgs = `node test ${args}`
.split(' ')
.map(t => t.trim())
.filter(t => t);
return (...args: Array<string>): void => {
const cliArgs = parseArgsStringToArgv(`node test ${args.join(' ')}`);
// console.log('calling cli.go with: %o', cliArgs);
return cli.go(cliArgs, true);
};
};
@ -133,7 +131,7 @@ describe('cli', () => {
});
it('should call runInsomniaTests with expected options', () => {
inso('run test uts_123 -e env_123 -t name -r min -b --keep-file');
inso('run test uts_123 -e env_123 -t name -r min -b --keepFile');
expect(runInsomniaTests).toHaveBeenCalledWith('uts_123', {
reporter: 'min',
keepFile: true,
@ -177,4 +175,82 @@ describe('cli', () => {
);
});
});
describe('script', () => {
let consoleLogSpy;
beforeEach(() => {
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
});
const insorcFilePath = '--config src/__fixtures__/.insorc-with-scripts.yaml';
it('should call script command by default', () => {
inso('gen-conf', insorcFilePath);
expect(generateConfig).toHaveBeenCalledWith(
'Designer Demo',
expect.objectContaining({ type: 'declarative' }),
);
});
it('should call script command', () => {
inso('script gen-conf', insorcFilePath);
expect(generateConfig).toHaveBeenCalledWith(
'Designer Demo',
expect.objectContaining({ type: 'declarative' }),
);
});
it('should warn if script task does not start with inso', () => {
inso('invalid-script', insorcFilePath);
expect(consoleLogSpy).toHaveBeenCalledWith("Tasks in the script should start with 'inso'.");
expect(generateConfig).not.toHaveBeenCalledWith();
});
it('should call nested command', () => {
inso('gen-conf:k8s', insorcFilePath);
expect(generateConfig).toHaveBeenCalledWith(
'Designer Demo',
expect.objectContaining({ type: 'kubernetes' }),
);
expect(consoleLogSpy).toHaveBeenNthCalledWith(1, '>> inso gen-conf --type kubernetes');
expect(consoleLogSpy).toHaveBeenNthCalledWith(
2,
'>> inso generate config Designer Demo --type declarative --type kubernetes',
);
});
it('should call nested command and pass through props', () => {
inso('gen-conf:k8s --type declarative', insorcFilePath);
expect(generateConfig).toHaveBeenCalledWith(
'Designer Demo',
expect.objectContaining({ type: 'declarative' }),
);
});
it('should override env setting from command', () => {
inso('test:200s --env NewEnv', insorcFilePath);
expect(runInsomniaTests).toHaveBeenCalledWith(
'Designer Demo',
expect.objectContaining({
env: 'NewEnv',
}),
);
});
it('should fail if script not found', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
inso('not-found-script', insorcFilePath);
expect(consoleSpy).toHaveBeenCalledWith(
'Could not find inso script "not-found-script" in the config file.',
);
});
});
});

View File

@ -0,0 +1,144 @@
// @flow
import commander from 'commander';
import getOptions, { extractCommandOptions, loadCosmiConfig } from '../get-options';
import path from 'path';
jest.unmock('cosmiconfig');
const fixturesDir = path.join('src', '__fixtures__');
describe('extractCommandOptions()', () => {
it('should combine options from all commands into one object', () => {
const command = new commander.Command('command').exitOverride();
command
.command('subCommand')
.option('-s, --subCmd')
.action(cmd => {
expect(extractCommandOptions(cmd)).toEqual({
global: true,
subCmd: true,
});
});
const parent = new commander.Command()
.exitOverride()
.option('-g, --global')
.addCommand(command);
parent.parse('self inso command subCommand --global --subCmd'.split(' '));
});
});
describe('loadCosmiConfig()', () => {
it('should load .insorc.yaml config file in fixtures dir', () => {
const result = loadCosmiConfig(path.join(fixturesDir, '.insorc.yaml'));
expect(result).toEqual({
__configFile: {
options: { appDataDir: 'configFile', workingDir: 'workingDir', ci: true },
scripts: { lint: 'inso lint spec' },
filePath: path.resolve(fixturesDir, '.insorc.yaml'),
},
});
expect(result.__configFile?.options?.shouldBeIgnored).toBe(undefined);
});
it('should return empty object and report error if specified config file not found', () => {
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const result = loadCosmiConfig('not-found.yaml');
expect(result).toEqual({});
expect(consoleLogSpy).toHaveBeenCalledWith('Could not find config file at not-found.yaml.');
expect(consoleErrorSpy).toHaveBeenCalled();
});
it('should return empty object if config file is blank', () => {
const result = loadCosmiConfig(path.join(fixturesDir, '.insorc-blank.yaml'));
expect(result).toEqual({});
});
it('should return blank properties and ignore extra items if settings and scripts not found in file', () => {
const result = loadCosmiConfig(path.join(fixturesDir, '.insorc-missing-properties.yaml'));
expect(result).toEqual({
__configFile: {
options: {},
scripts: {},
filePath: path.resolve(fixturesDir, '.insorc-missing-properties.yaml'),
},
});
});
});
describe('getOptions', () => {
it('should load default options', () => {
const commandOptions = { opts: () => ({}) };
const defaultOptions = { appDataDir: 'default' };
const result = getOptions(commandOptions, defaultOptions);
expect(result).toEqual({ appDataDir: 'default' });
});
it('should combine default options with command options, favouring command', () => {
const commandOptions = { opts: () => ({ appDataDir: 'command' }) };
const defaultOptions = { appDataDir: 'default', anotherDefault: '0' };
const result = getOptions(commandOptions, defaultOptions);
expect(result).toEqual({ appDataDir: 'command', anotherDefault: '0' });
});
it('should combine config file options with default options, favouring config file', () => {
// Will also load src/__fixtures__/.insorc.yaml
const commandOptions = {
opts: () => ({
config: path.join(fixturesDir, '.insorc.yaml'),
}),
};
const defaultOptions = { appDataDir: 'default', anotherDefault: '0' };
const result = getOptions(commandOptions, defaultOptions);
expect(result).toEqual({
appDataDir: 'configFile',
workingDir: 'workingDir',
ci: true,
anotherDefault: '0',
config: path.join(fixturesDir, '.insorc.yaml'),
__configFile: {
options: { appDataDir: 'configFile', workingDir: 'workingDir', ci: true },
scripts: { lint: 'inso lint spec' },
filePath: path.resolve(fixturesDir, '.insorc.yaml'),
},
});
});
it('should print error to console if config file not found', () => {
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const configFilePath = path.join(fixturesDir, '.insorc-not-found.yaml');
// Will also load src/__fixtures__/.insorc.yaml
const commandOptions = {
opts: () => ({
config: configFilePath,
}),
};
const defaultOptions = { appDataDir: 'default', anotherDefault: '0' };
const result = getOptions(commandOptions, defaultOptions);
expect(result).toEqual({
appDataDir: 'default',
anotherDefault: '0',
config: configFilePath,
});
expect(logSpy).toHaveBeenCalledWith(`Could not find config file at ${configFilePath}.`);
expect(errSpy).toHaveBeenCalled();
});
});

View File

@ -1,26 +1,5 @@
// @flow
import commander from 'commander';
import { getAllOptions, exit, logErrorExit1, getDefaultAppDataDir } from '../util';
describe('getAllOptions()', () => {
it('should combine options from all commands into one object', () => {
const command = new commander.Command('command');
command
.command('subCommand')
.option('-s, --subCmd')
.action(cmd => {
expect(getAllOptions(cmd)).toEqual({
global: true,
subCmd: true,
});
});
const parent = new commander.Command().option('-g, --global').addCommand(command);
parent.parse('node test command subCommand --global --subCmd'.split(' '));
});
});
import { exit, logErrorExit1, getDefaultAppDataDir } from '../util';
describe('exit()', () => {
it('should exit 0 if successful result', async () => {

View File

@ -1,100 +1,167 @@
// @flow
import { ConversionTypeMap, generateConfig } from './commands/generate-config';
import { getVersion, createCommand, getAllOptions, logErrorExit1, exit } from './util';
import { getVersion, logErrorExit1, exit } from './util';
import { runInsomniaTests, TestReporterEnum } from './commands/run-tests';
import { lintSpecification } from './commands/lint-specification';
import { exportSpecification } from './commands/export-specification';
import { parseArgsStringToArgv } from 'string-argv';
import commander from 'commander';
import getOptions from './get-options';
function makeGenerateCommand(exitOverride: boolean) {
type CreateCommandType = (command?: string, options?: Object) => Object;
function makeGenerateCommand(createCommand: CreateCommandType) {
// inso generate
const generate = createCommand(exitOverride, 'generate').description('Code generation utilities');
const generate = createCommand('generate').description('Code generation utilities');
const conversionTypes = Object.keys(ConversionTypeMap).join(', ');
const defaultType = 'declarative';
// inso generate config -t kubernetes config.yaml
generate
.command('config [identifier]')
.description('Generate configuration from an api spec.')
.requiredOption(
.option(
'-t, --type <value>',
`type of configuration to generate, options are [${conversionTypes}]`,
'declarative',
`type of configuration to generate, options are [${conversionTypes}] (default: ${defaultType})`,
)
.option('-o, --output <path>', 'save the generated config to a file')
.action((identifier, cmd) => exit(generateConfig(identifier, getAllOptions(cmd))));
.action((identifier, cmd) =>
exit(generateConfig(identifier, getOptions(cmd, { type: defaultType }))),
);
return generate;
}
function makeTestCommand(exitOverride: boolean) {
function makeTestCommand(createCommand: CreateCommandType) {
// inso run
const run = createCommand(exitOverride, 'run').description('Execution utilities');
const run = createCommand('run').description('Execution utilities');
const reporterTypes = Object.keys(TestReporterEnum).join(', ');
const defaultReporter = TestReporterEnum.spec;
// inso run tests
run
.command('test [identifier]')
.description('Run Insomnia unit test suites')
.option('-e, --env <identifier>', 'environment to use')
.option('-t, --test-name-pattern <regex>', 'run tests that match the regex')
.option('-t, --testNamePattern <regex>', 'run tests that match the regex')
.option(
'-r, --reporter <reporter>',
`reporter to use, options are [${reporterTypes}]`,
TestReporterEnum.spec,
`reporter to use, options are [${reporterTypes}] (default: ${defaultReporter})`,
)
.option('-b, --bail', 'abort ("bail") after first test failure')
.option('--keep-file', 'do not delete the generated test file')
.action((identifier, cmd) => exit(runInsomniaTests(identifier, getAllOptions(cmd))));
.option('--keepFile', 'do not delete the generated test file')
.action((identifier, cmd) =>
exit(runInsomniaTests(identifier, getOptions(cmd, { reporter: defaultReporter }))),
);
return run;
}
function makeLintCommand(exitOverride: boolean) {
function makeLintCommand(createCommand: CreateCommandType) {
// inso lint
const lint = createCommand(exitOverride, 'lint').description('Linting utilities');
const lint = createCommand('lint').description('Linting utilities');
// inso lint spec
lint
.command('spec [identifier]')
.description('Lint an API Specification')
.action((identifier, cmd) => exit(lintSpecification(identifier, getAllOptions(cmd))));
.action((identifier, cmd) => exit(lintSpecification(identifier, getOptions(cmd))));
return lint;
}
function makeExportCommand(exitOverride: boolean) {
function makeExportCommand(createCommand: CreateCommandType) {
// inso export
const exportCmd = createCommand(exitOverride, 'export').description(
'Export data from insomnia models',
);
const exportCmd = createCommand('export').description('Export data from insomnia models');
// inso export spec
exportCmd
.command('spec [identifier]')
.description('Export an API Specification to a file')
.option('-o, --output <path>', 'save the generated config to a file')
.action((identifier, cmd) => exit(exportSpecification(identifier, getAllOptions(cmd))));
.action((identifier, cmd) => exit(exportSpecification(identifier, getOptions(cmd))));
return exportCmd;
}
export function go(args?: Array<string>, exitOverride?: boolean): void {
if (!args) {
args = process.argv;
}
function addScriptCommand(originalCommand: Object) {
// inso script
originalCommand
.command('script <name>', { isDefault: true })
.description('Run scripts defined in .insorc')
.allowUnknownOption()
.action((scriptName, cmd) => {
// Load scripts
const options = getOptions(cmd);
// inso -v
createCommand(!!exitOverride)
.version(getVersion(), '-v, --version')
.description('A CLI for Insomnia!')
.option('-w, --working-dir <dir>', 'set working directory')
.option('-a, --app-data-dir <dir>', 'set the app data directory')
.option('--ci', 'run in CI, disables all prompts')
.addCommand(makeGenerateCommand(!!exitOverride))
.addCommand(makeTestCommand(!!exitOverride))
.addCommand(makeLintCommand(!!exitOverride))
.addCommand(makeExportCommand(!!exitOverride))
.parseAsync(args)
.catch(logErrorExit1);
// Ignore the first arg because that will be scriptName, get the rest
const passThroughArgs = cmd.args.slice(1);
// Find script
const scriptTask = options.__configFile?.scripts?.[scriptName];
if (!scriptTask) {
console.log(`Could not find inso script "${scriptName}" in the config file.`);
return exit(new Promise(resolve => resolve(false)));
}
if (!scriptTask.startsWith('inso')) {
console.log(`Tasks in the script should start with 'inso'.`);
return exit(new Promise(resolve => resolve(false)));
}
// Collect args
const scriptArgs: Array<string> = parseArgsStringToArgv(
`self ${scriptTask} ${passThroughArgs.join(' ')}`,
);
// Print command
console.log(`>> ${scriptArgs.slice(1).join(' ')}`);
// Run
runWithArgs(originalCommand, scriptArgs);
});
}
export function go(args?: Array<string>, exitOverride?: boolean): void {
const createCommand: CreateCommandType = (cmd?: string) => {
const command = new commander.Command(cmd).storeOptionsAsProperties(false);
if (exitOverride) {
return command.exitOverride();
}
return command;
};
// inso
const cmd = createCommand();
// Version and description
cmd.version(getVersion(), '-v, --version').description('A CLI for Insomnia!');
// Global options
cmd
.option('-w, --workingDir <dir>', 'set working directory')
.option('-a, --appDataDir <dir>', 'set the app data directory')
.option('--config <path>', 'path to configuration file')
.option('--ci', 'run in CI, disables all prompts');
// Add commands and sub commands
cmd
.addCommand(makeGenerateCommand(createCommand))
.addCommand(makeTestCommand(createCommand))
.addCommand(makeLintCommand(createCommand))
.addCommand(makeExportCommand(createCommand));
// Add script base command
addScriptCommand(cmd);
runWithArgs(cmd, args || process.argv);
}
function runWithArgs(cmd: Object, args: Array<string>) {
cmd.parseAsync(args).catch(logErrorExit1);
}

View File

@ -1,5 +1,5 @@
// @flow
import type { GlobalOptions } from '../util';
import type { GlobalOptions } from '../get-options';
import { loadDb } from '../db';
import { loadApiSpec, promptApiSpec } from '../db/models/api-spec';
import { writeFileWithCliOptions } from '../write-file';

View File

@ -2,7 +2,7 @@
import * as o2k from 'openapi-2-kong';
import YAML from 'yaml';
import path from 'path';
import type { GlobalOptions } from '../util';
import type { GlobalOptions } from '../get-options';
import { loadDb } from '../db';
import { loadApiSpec, promptApiSpec } from '../db/models/api-spec';
import { writeFileWithCliOptions } from '../write-file';

View File

@ -1,6 +1,6 @@
// @flow
import { Spectral } from '@stoplight/spectral';
import type { GlobalOptions } from '../util';
import type { GlobalOptions } from '../get-options';
import { loadDb } from '../db';
import { loadApiSpec, promptApiSpec } from '../db/models/api-spec';

View File

@ -1,7 +1,7 @@
// @flow
import { generate, runTestsCli } from 'insomnia-testing';
import type { GlobalOptions } from '../util';
import type { GlobalOptions } from '../get-options';
import { loadDb } from '../db';
import type { UnitTest, UnitTestSuite } from '../db/models/types';
import { noConsoleLog } from '../logger';

View File

@ -0,0 +1,87 @@
// @flow
import { cosmiconfigSync } from 'cosmiconfig';
type ConfigFileOptions = {
__configFile?: {
options?: Object,
scripts?: Object,
filePath: string,
},
};
export type GlobalOptions = {
appDataDir?: string,
workingDir?: string,
ci?: boolean,
config?: string,
} & ConfigFileOptions;
const OptionsSupportedInConfigFile: Array<$Keys<GlobalOptions>> = [
'appDataDir',
'workingDir',
'ci',
];
export const loadCosmiConfig = (configFile?: string): ConfigFileOptions => {
try {
const explorer = cosmiconfigSync('inso');
const results = configFile ? explorer.load(configFile) : explorer.search();
if (results && !results?.isEmpty) {
const options = {};
OptionsSupportedInConfigFile.forEach(key => {
options[key] = results.config?.options?.[key];
});
return {
__configFile: {
options,
scripts: results.config?.scripts || {},
filePath: results.filepath,
},
};
}
} catch (e) {
// Report fatal error when loading from explicitly defined config file
if (configFile) {
console.log(`Could not find config file at ${configFile}.`);
console.error(e);
}
}
return {};
};
export const extractCommandOptions = <T>(cmd: Object): $Shape<T> => {
let opts = {};
let command = cmd;
do {
// overwrite options with more specific ones
opts = { ...command.opts(), ...opts };
command = command.parent;
} while (command);
return opts;
};
const getOptions = <T>(cmd: Object, defaultOptions: $Shape<T> = {}): T => {
const commandOptions = extractCommandOptions(cmd);
const { __configFile } = loadCosmiConfig(commandOptions.config);
if (__configFile) {
return {
...defaultOptions,
...(__configFile.options || {}),
...commandOptions,
__configFile,
};
}
return { ...defaultOptions, ...commandOptions };
};
export default getOptions;

View File

@ -1,41 +1,10 @@
// @flow
import commander from 'commander';
import * as packageJson from '../package.json';
export type GlobalOptions = {
appDataDir?: string,
workingDir?: string,
ci?: boolean,
};
export function createCommand(exitOverride: boolean, cmd?: string) {
const command = new commander.Command(cmd).storeOptionsAsProperties(false);
// TODO: can probably remove this
if (exitOverride) {
return command.exitOverride();
}
return command;
}
export function getVersion() {
return packageJson.version;
}
export function getAllOptions<T>(cmd: Object): T {
let opts = {};
let command = cmd;
do {
// overwrite options with more specific ones
opts = { ...command.opts(), ...opts };
command = command.parent;
} while (command);
return opts;
}
export function logErrorExit1(err: Error) {
console.error(err);