specshield 2.0.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  ---
9
9
 
10
- > **OpenAPI Diff · API Breaking Change Detection · Swagger Diff · Consumer-Driven Contract Testing · Pact Alternative · can-i-deploy · Bi-Directional Contract Testing · GitHub PR Checks · CI/CD**
10
+ > **OpenAPI Diff · API Breaking Change Detection · Swagger Diff · Bi-Directional Contract Testing · Pact Alternative · can-i-deploy · GitHub PR Checks · CI/CD**
11
11
 
12
12
  ---
13
13
 
@@ -57,7 +57,7 @@ No runtime surprise. No production incident. No 3am page.
57
57
 
58
58
  [![SpecShield Demo — API Breaking Change Detection & Contract Testing](https://img.youtube.com/vi/mugDyQQGqZw/maxresdefault.jpg)](https://www.youtube.com/watch?v=mugDyQQGqZw)
59
59
 
60
- > Watch: Catching a breaking API change before it hits production — compare specs, publish contracts, verify providers, and gate deployments with `can-i-deploy`.
60
+ > Watch: Catching a breaking API change before it hits production — compare specs, publish provider/consumer specs via BDCT, and gate deployments with `can-i-deploy`.
61
61
 
62
62
  **CLI preview:**
63
63
 
@@ -117,6 +117,26 @@ specshield compare base.yaml target.yaml --fail-on-breaking
117
117
 
118
118
  That's it. Works with any OpenAPI 3.x YAML or JSON spec.
119
119
 
120
+ **For BDCT**, run the wizard once and never re-type the same flags again:
121
+
122
+ ```bash
123
+ specshield init
124
+ ```
125
+
126
+ It autodetects your OpenAPI spec, your service name (from `package.json` /
127
+ `pyproject.toml` / `pom.xml` / `go.mod`), your git branch, and your
128
+ default environment, asks you a few questions, and writes a
129
+ **`.specshield.yml`** at the project root. Every subsequent
130
+ `specshield bdct ...` invocation reads this file, so your CI commands
131
+ collapse to:
132
+
133
+ ```bash
134
+ specshield bdct publish-provider --version $GITHUB_SHA
135
+ specshield bdct can-i-deploy --version $GITHUB_SHA
136
+ ```
137
+
138
+ See [§ specshield init](#specshield-init--first-run-setup-wizard) below.
139
+
120
140
  ---
121
141
 
122
142
  ## 🚀 Create Your Free Account
@@ -141,8 +161,6 @@ That's it. Works with any OpenAPI 3.x YAML or JSON spec.
141
161
  | JSON / human output | ✅ | ✅ | ✅ |
142
162
  | Fail CI on breaking change | ✅ | ✅ | ✅ |
143
163
  | **Compare history & dashboard** | ❌ | ✅ | ✅ |
144
- | **CDCT contract testing registry** | ❌ | ✅ | ✅ |
145
- | **CDCT can-i-deploy gating** | ❌ | ✅ | ✅ |
146
164
  | **GitHub App PR checks** | ❌ | ✅ | ✅ |
147
165
  | **BDCT bi-directional contracts** | ❌ | ❌ | ✅ |
148
166
  | **BDCT can-i-deploy gating** | ❌ | ❌ | ✅ |
@@ -160,8 +178,9 @@ That's it. Works with any OpenAPI 3.x YAML or JSON spec.
160
178
  > Without SpecShield, this would have reached staging, broken checkout for every user,
161
179
  > and triggered an incident at 2am.
162
180
  >
163
- > With SpecShield, the provider's CI ran `specshield contracts verify` against the
164
- > consumer's published contract. The mismatch was caught immediately:
181
+ > With SpecShield, the provider's CI published the new spec via
182
+ > `specshield bdct publish-provider`. The compatibility engine ran against
183
+ > `checkout-ui`'s published contract and the mismatch was caught immediately:
165
184
  >
166
185
  > ```
167
186
  > ● MISSING_FIELD at $.status
@@ -195,7 +214,6 @@ Track API drift over time across your entire platform. Know what changed, when,
195
214
  |---|---|---|---|
196
215
  | OpenAPI / Swagger native | ✅ | ❌ (code-level) | ✅ |
197
216
  | No broker required | ✅ | ❌ (needs Pact Broker) | ✅ |
198
- | Consumer-driven contract testing | ✅ | ✅ | ❌ |
199
217
  | **Bi-directional contract testing** | ✅ | ✅ (Pactflow paid) | ❌ |
200
218
  | Breaking change detection | ✅ | ❌ | ✅ |
201
219
  | can-i-deploy gating | ✅ | ✅ (via broker) | ❌ |
@@ -212,7 +230,7 @@ Track API drift over time across your entire platform. Know what changed, when,
212
230
 
213
231
  | Plan | Price | What's included |
214
232
  |---|---|---|
215
- | **Free** | $0 forever | Local compare (unlimited) · Compare history & dashboard · CDCT contracts & can-i-deploy · GitHub App PR checks |
233
+ | **Free** | $0 forever | Local compare (unlimited) · Compare history & dashboard · GitHub App PR checks |
216
234
  | **Pro** | Coming soon | Everything in Free + BDCT bi-directional contracts · BDCT can-i-deploy & matrix · Team collaboration · Advanced reporting · Priority support |
217
235
 
218
236
  No credit card ever required for the free plan.
@@ -351,116 +369,80 @@ github:
351
369
 
352
370
  ---
353
371
 
354
- ## Contract Testing (CDCT)
355
-
356
- Consumer-driven contract testing for microservices — without a broker.
372
+ ## `specshield init` — first-run setup wizard
357
373
 
358
- **How it works:**
374
+ Run once at the root of your project. The wizard:
359
375
 
360
- 1. Consumer team publishes a contract (what they expect from the provider)
361
- 2. Provider team verifies their service satisfies it by actually calling it
362
- 3. `can-i-deploy` gates the deployment based on verification results
363
-
364
- ### Contract File Format
365
-
366
- ```json
367
- {
368
- "consumer": { "name": "checkout-ui", "version": "2.0.0" },
369
- "provider": { "name": "payment-service" },
370
- "orgKey": "acme-store",
371
- "contractName": "create-payment",
372
- "contractType": "HTTP",
373
- "interactions": [
374
- {
375
- "description": "checkout-ui creates a payment",
376
- "request": {
377
- "method": "POST",
378
- "path": "/payments",
379
- "headers": { "Content-Type": "application/json" },
380
- "body": { "orderId": "ORD-123", "amount": 1299, "currency": "INR" }
381
- },
382
- "expectedResponse": {
383
- "status": 201,
384
- "headers": { "Content-Type": "application/json" },
385
- "body": { "paymentId": "PAY-123", "status": "CREATED" }
386
- }
387
- }
388
- ]
389
- }
390
- ```
391
-
392
- ### Publish a Contract
376
+ 1. Detects your OpenAPI spec (looks under `api/`, `spec/`, `docs/`, repo root).
377
+ 2. Detects your service name (`package.json`, `pyproject.toml`, `pom.xml`, `go.mod`, `Cargo.toml`, or directory name).
378
+ 3. Detects your git branch and suggests `production` for `main`/`master`, `staging` otherwise.
379
+ 4. Asks whether this project is a provider, a consumer, or both.
380
+ 5. Asks for your org key (autocompletes from your account if you're already signed in).
381
+ 6. Validates / stores your API key.
382
+ 7. Writes **`.specshield.yml`** and, optionally, a starter **`.github/workflows/specshield-bdct.yml`** that uses [`specshield26/bdct-action@v1`](https://github.com/marketplace/actions/specshield-bdct).
393
383
 
394
384
  ```bash
395
- specshield contracts publish \
396
- --file ./contracts/create-payment.json \
397
- --org acme-store \
398
- --consumer-version 2.0.0 \
399
- --tag main
385
+ specshield init
400
386
  ```
401
387
 
402
- ### Verify a Contract
388
+ ### Example `.specshield.yml`
403
389
 
404
- ```bash
405
- specshield contracts verify \
406
- --contract-id 42 \
407
- --base-url http://localhost:8080 \
408
- --provider-version v2.1.0 \
409
- --env staging
410
- ```
411
-
412
- Pass output:
413
- ```
414
- ✔ Verification PASSED (1/1 interactions)
415
- ```
390
+ ```yaml
391
+ schemaVersion: 1
416
392
 
417
- Fail output:
418
- ```
419
- ✖ Verification FAILED (0/1 interactions passed, 1 failed)
393
+ failOnBreaking: true
394
+ severity: error
420
395
 
421
- Mismatches
422
- [create payment] MISSING_FIELD at $.status
423
- expected: "CREATED" → actual: null
424
- ```
396
+ bdct:
397
+ org: acme-pay
398
+ environment: staging
425
399
 
426
- ### Can I Deploy?
400
+ provider:
401
+ name: payment-service
402
+ spec: api/openapi.yaml
427
403
 
428
- ```bash
429
- specshield contracts can-i-deploy \
430
- --provider payment-service \
431
- --version v2.1.0 \
432
- --env staging
433
- ```
404
+ # consumer (optional — present when --kind=consumer or --kind=both):
405
+ # consumer:
406
+ # name: checkout-ui
407
+ # provider: payment-service
408
+ # contract: contracts/payment-service.yaml
409
+ # format: OPENAPI
434
410
 
435
- ```
436
- ✔ PASS: payment-service v2.1.0 is deployable in staging
437
- ```
438
- ```
439
- ✖ FAIL: payment-service v2.1.0 is NOT deployable in staging
411
+ github:
412
+ specPath: api/openapi.yaml
413
+ failOnBreaking: true
414
+ commentOnPr: true
440
415
  ```
441
416
 
442
- Exit codes: `0` = deployable · `1` = blocked · `2` = error
417
+ CLI flags **always** override this file. Paths in the file are resolved
418
+ relative to the file's own directory, so you can run `specshield bdct ...`
419
+ from any subdirectory of your project.
443
420
 
444
- ### List and Inspect Contracts
421
+ ### Non-interactive (scriptable) mode
445
422
 
446
423
  ```bash
447
- specshield contracts list --provider payment-service
448
- specshield contracts latest --consumer checkout-ui --provider payment-service --json
449
- specshield contracts history --contract-id 42
424
+ specshield init --no-interactive \
425
+ --kind provider \
426
+ --org acme-pay \
427
+ --provider payment-service \
428
+ --spec api/openapi.yaml \
429
+ --env staging \
430
+ --write-workflow
450
431
  ```
451
432
 
452
- ### Full CDCT Workflow
433
+ ### Other useful flags
453
434
 
454
- ```bash
455
- # 1. Consumer publishes contract
456
- specshield contracts publish --file ./contracts/create-payment.json --org acme-store
457
-
458
- # 2. Provider verifies it
459
- specshield contracts verify --contract-id 42 --base-url http://localhost:8080 --provider-version v2.1.0
435
+ | Flag | Purpose |
436
+ | --- | --- |
437
+ | `--print` | Detect everything, print the proposed YAML, write nothing. Good for `--dry-run` review in CI. |
438
+ | `--force` | Skip the overwrite-confirmation if `.specshield.yml` already exists. |
439
+ | `--server <url>` | Use a non-default SpecShield endpoint (self-hosted / staging). |
440
+ | `--write-workflow` | Also write a starter GitHub Actions workflow under `.github/workflows/`. |
460
441
 
461
- # 3. Gate the deployment
462
- specshield contracts can-i-deploy --provider payment-service --version v2.1.0
463
- ```
442
+ > **What is _not_ written into `.specshield.yml`:** your API key. It is
443
+ > stored in `~/.specshield/config.json` (set by `specshield login`) or
444
+ > read from `SPECSHIELD_API_KEY` in CI. The project file is meant to be
445
+ > committed; never commit a secret into it.
464
446
 
465
447
  ---
466
448
 
@@ -468,20 +450,12 @@ specshield contracts can-i-deploy --provider payment-service --version v2.1.0
468
450
 
469
451
  **Spec-to-spec contract testing — no running services required.**
470
452
 
471
- BDCT is the static alternative to CDCT. Instead of running the provider server, both sides publish their OpenAPI specs. SpecShield compares them and flags mismatches immediately — ideal for teams that don't run services locally or in CI.
453
+ Both sides publish their OpenAPI specs. SpecShield compares them and flags
454
+ mismatches immediately — ideal for teams that don't run services locally or in
455
+ CI. Pact JSON consumer contracts are also accepted and auto-converted.
472
456
 
473
457
  > BDCT requires a **Pro plan**. [Upgrade at specshield.io/upgrade](https://specshield.io/upgrade)
474
458
 
475
- **CDCT vs BDCT:**
476
-
477
- | | CDCT | BDCT |
478
- |---|---|---|
479
- | How verification works | Replay requests against a live server | Compare OpenAPI specs statically |
480
- | Provider needs to run | Yes | No |
481
- | Feedback speed | After deploy to test env | Immediately on spec publish |
482
- | Pact JSON contracts | Supported | Supported (auto-converted) |
483
- | Best for | Runtime correctness | Early spec-level safety |
484
-
485
459
  ### How BDCT Works
486
460
 
487
461
  1. Consumer team publishes an OpenAPI spec subset (the endpoints they use)
@@ -687,7 +661,7 @@ specshield bdct list-consumers --org acme-store --provider payment-service
687
661
  ### List Verifications
688
662
 
689
663
  ```bash
690
- specshield bdct list-verifications \
664
+ specshield bdct list \
691
665
  --org acme-store \
692
666
  --provider payment-service \
693
667
  --env production \
@@ -780,73 +754,6 @@ jobs:
780
754
  run: specshield compare /tmp/base.yaml api/openapi.yaml --fail-on-breaking
781
755
  ```
782
756
 
783
- ### On Push — Publish consumer contract (CDCT)
784
-
785
- ```yaml
786
- name: Publish Contract
787
-
788
- on:
789
- push:
790
- branches: [main]
791
- paths:
792
- - 'contracts/**'
793
-
794
- jobs:
795
- publish:
796
- runs-on: ubuntu-latest
797
- steps:
798
- - uses: actions/checkout@v4
799
- - uses: actions/setup-node@v4
800
- with:
801
- node-version: '20'
802
- - run: npm install -g specshield
803
- - name: Publish contract
804
- env:
805
- SPECSHIELD_API_KEY: ${{ secrets.SPECSHIELD_API_KEY }}
806
- run: |
807
- specshield contracts publish \
808
- --file ./contracts/create-payment.json \
809
- --org acme-store \
810
- --tag ${{ github.ref_name }}
811
- ```
812
-
813
- ### On Push — Verify provider + gate deployment (CDCT)
814
-
815
- ```yaml
816
- name: Contract Verification
817
-
818
- on:
819
- push:
820
- branches: [main]
821
-
822
- jobs:
823
- verify:
824
- runs-on: ubuntu-latest
825
- steps:
826
- - uses: actions/checkout@v4
827
- - uses: actions/setup-node@v4
828
- with:
829
- node-version: '20'
830
- - run: npm install -g specshield
831
- - name: Verify contract
832
- env:
833
- SPECSHIELD_API_KEY: ${{ secrets.SPECSHIELD_API_KEY }}
834
- run: |
835
- specshield contracts verify \
836
- --contract-id ${{ vars.CONTRACT_ID }} \
837
- --base-url http://localhost:8080 \
838
- --provider-version ${{ github.sha }} \
839
- --env staging
840
- - name: Can I deploy?
841
- env:
842
- SPECSHIELD_API_KEY: ${{ secrets.SPECSHIELD_API_KEY }}
843
- run: |
844
- specshield contracts can-i-deploy \
845
- --provider payment-service \
846
- --version ${{ github.sha }} \
847
- --env staging
848
- ```
849
-
850
757
  ### On Push — Publish provider spec (BDCT)
851
758
 
852
759
  ```yaml
@@ -981,7 +888,7 @@ specshield bdct <subcommand> [options]
981
888
  | `matrix` | View compatibility matrix across all pairs |
982
889
  | `list-providers` | List published provider specs |
983
890
  | `list-consumers` | List published consumer contracts |
984
- | `list-verifications` | List verification history |
891
+ | `list` | List verification history |
985
892
 
986
893
  All `bdct` subcommands support `--json` for machine-readable output.
987
894
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specshield",
3
- "version": "2.0.1",
4
- "description": "CLI for OpenAPI breaking change detection, consumer-driven contract testing, and bi-directional contract verification — with can-i-deploy gating and GitHub PR checks.",
3
+ "version": "3.1.1",
4
+ "description": "CLI for OpenAPI breaking change detection and bi-directional contract verification — with can-i-deploy gating, GitHub PR checks, and a first-run setup wizard.",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
7
7
  "specshield": "./bin/specshield.js"
@@ -13,36 +13,36 @@
13
13
  "lint": "eslint src tests --ext .js"
14
14
  },
15
15
  "keywords": [
16
- "openapi",
17
- "swagger",
18
- "api-diff",
19
- "openapi-diff",
20
- "swagger-diff",
21
- "breaking-change-detection",
22
- "api-breaking-changes",
23
- "api-compatibility",
24
- "contract-testing",
25
- "consumer-driven-contract",
26
- "api-contract",
27
- "contract-verification",
28
- "can-i-deploy",
29
- "ci-cd",
30
- "github-actions",
31
- "cli",
32
- "developer-tools",
33
- "openapi-compare",
34
- "swagger-compare",
35
- "pact-alternative",
36
- "api-contract-testing",
37
- "microservices",
38
- "api-quality",
39
- "api-governance",
40
- "bi-directional-contract-testing",
41
- "bdct",
42
- "pact-json",
43
- "github-app",
44
- "api-drift"
45
- ],
16
+ "openapi",
17
+ "swagger",
18
+ "api-diff",
19
+ "openapi-diff",
20
+ "swagger-diff",
21
+ "breaking-change-detection",
22
+ "api-breaking-changes",
23
+ "api-compatibility",
24
+ "contract-testing",
25
+ "consumer-driven-contract",
26
+ "api-contract",
27
+ "contract-verification",
28
+ "can-i-deploy",
29
+ "ci-cd",
30
+ "github-actions",
31
+ "cli",
32
+ "developer-tools",
33
+ "openapi-compare",
34
+ "swagger-compare",
35
+ "pact-alternative",
36
+ "api-contract-testing",
37
+ "microservices",
38
+ "api-quality",
39
+ "api-governance",
40
+ "bi-directional-contract-testing",
41
+ "bdct",
42
+ "pact-json",
43
+ "github-app",
44
+ "api-drift"
45
+ ],
46
46
  "license": "MIT",
47
47
  "files": [
48
48
  "bin",
@@ -56,7 +56,8 @@
56
56
  "commander": "^12.0.0",
57
57
  "fs-extra": "^11.2.0",
58
58
  "js-yaml": "^4.1.0",
59
- "ora": "^5.4.1"
59
+ "ora": "^5.4.1",
60
+ "prompts": "^2.4.2"
60
61
  },
61
62
  "devDependencies": {
62
63
  "jest": "^29.7.0"
@@ -22,7 +22,10 @@ function apiError(err) {
22
22
  const data = err.response.data;
23
23
  const msg = (data && (data.message || data.error || data.title))
24
24
  || `HTTP ${err.response.status}`;
25
- return new Error(`API error (${err.response.status}): ${msg}`);
25
+ const e = new Error(`API error (${err.response.status}): ${msg}`);
26
+ e.status = err.response.status;
27
+ e.responseData = data;
28
+ return e;
26
29
  }
27
30
  if (err.request) return new Error(`No response from server: ${err.message}`);
28
31
  return new Error(`Request failed: ${err.message}`);
@@ -51,11 +54,10 @@ async function verify(server, apiToken, payload) {
51
54
 
52
55
  async function listVerifications(server, apiToken, { org, consumer, provider, env, page = 0, size = 20 } = {}) {
53
56
  try {
54
- const params = { page, size };
55
- if (org) params.orgKey = org;
56
- if (consumer) params.consumer = consumer;
57
- if (provider) params.provider = provider;
58
- if (env) params.env = env;
57
+ const params = { orgKey: org, page, size };
58
+ if (consumer) params.consumerName = consumer;
59
+ if (provider) params.providerName = provider;
60
+ if (env) params.environment = env;
59
61
  const res = await buildClient(server, apiToken).get('/api/bdct/verifications', { params });
60
62
  return res.data;
61
63
  } catch (err) { throw apiError(err); }
@@ -63,41 +65,44 @@ async function listVerifications(server, apiToken, { org, consumer, provider, en
63
65
 
64
66
  async function canIDeploy(server, apiToken, { org, service, version: ver, env } = {}) {
65
67
  try {
66
- const params = { service, version: ver };
67
- if (org) params.orgKey = org;
68
- if (env) params.env = env;
68
+ const params = { orgKey: org, service, version: ver };
69
+ if (env) params.environment = env;
69
70
  const res = await buildClient(server, apiToken).get('/api/bdct/can-i-deploy', { params });
70
71
  return res.data;
71
- } catch (err) { throw apiError(err); }
72
+ } catch (err) {
73
+ // 409 = NOT DEPLOYABLE — valid result, not an error (backend signals via status)
74
+ if (err.response && err.response.status === 409 && err.response.data
75
+ && typeof err.response.data === 'object'
76
+ && Object.prototype.hasOwnProperty.call(err.response.data, 'deployable')) {
77
+ return err.response.data;
78
+ }
79
+ throw apiError(err);
80
+ }
72
81
  }
73
82
 
74
83
  async function getMatrix(server, apiToken, { org, env } = {}) {
75
84
  try {
76
- const params = {};
77
- if (org) params.orgKey = org;
78
- if (env) params.env = env;
85
+ const params = { orgKey: org };
86
+ if (env) params.environment = env;
79
87
  const res = await buildClient(server, apiToken).get('/api/bdct/matrix', { params });
80
88
  return res.data;
81
89
  } catch (err) { throw apiError(err); }
82
90
  }
83
91
 
84
- async function listProviderSpecs(server, apiToken, { org, provider, env, page = 0, size = 20 } = {}) {
92
+ async function listProviderSpecs(server, apiToken, { org, provider } = {}) {
85
93
  try {
86
- const params = { page, size };
87
- if (org) params.orgKey = org;
88
- if (provider) params.provider = provider;
89
- if (env) params.env = env;
94
+ const params = { orgKey: org };
95
+ if (provider) params.providerName = provider;
90
96
  const res = await buildClient(server, apiToken).get('/api/bdct/provider-specs', { params });
91
97
  return res.data;
92
98
  } catch (err) { throw apiError(err); }
93
99
  }
94
100
 
95
- async function listConsumerContracts(server, apiToken, { org, consumer, provider, page = 0, size = 20 } = {}) {
101
+ async function listConsumerContracts(server, apiToken, { org, consumer, provider } = {}) {
96
102
  try {
97
- const params = { page, size };
98
- if (org) params.orgKey = org;
99
- if (consumer) params.consumer = consumer;
100
- if (provider) params.provider = provider;
103
+ const params = { orgKey: org };
104
+ if (consumer) params.consumerName = consumer;
105
+ if (provider) params.providerName = provider;
101
106
  const res = await buildClient(server, apiToken).get('/api/bdct/consumer-contracts', { params });
102
107
  return res.data;
103
108
  } catch (err) { throw apiError(err); }
package/src/cli.js CHANGED
@@ -3,9 +3,9 @@
3
3
  const { Command } = require('commander');
4
4
  const { version } = require('../package.json');
5
5
  const compareCommand = require('./commands/compare');
6
+ const initCommand = require('./commands/init');
6
7
  const loginCommand = require('./commands/login');
7
8
  const logoutCommand = require('./commands/logout');
8
- const contractsCommand = require('./commands/contracts');
9
9
  const bdctCommand = require('./commands/bdct');
10
10
 
11
11
  const program = new Command();
@@ -13,12 +13,16 @@ const program = new Command();
13
13
  program
14
14
  .name('specshield')
15
15
  .description('Compare OpenAPI specs and detect breaking changes')
16
- .version(version);
16
+ .version(version)
17
+ // Without this, a subcommand option named `--version` (e.g. on `bdct can-i-deploy`)
18
+ // is greedily consumed by the root `--version` flag and the program prints the
19
+ // CLI version and exits before the subcommand runs.
20
+ .enablePositionalOptions();
17
21
 
18
22
  program.addCommand(compareCommand);
23
+ program.addCommand(initCommand);
19
24
  program.addCommand(loginCommand);
20
25
  program.addCommand(logoutCommand);
21
- program.addCommand(contractsCommand);
22
26
  program.addCommand(bdctCommand);
23
27
 
24
28
  program.parseAsync(process.argv).catch((err) => {