@n42/cli 0.2.32 → 0.2.71

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.
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: '20'
21
+ cache: 'npm'
22
+
23
+ - name: Install dependencies
24
+ run: npm ci
25
+
26
+ - name: Run ESLint
27
+ run: npx --no-install eslint src
28
+
29
+ - name: Run tests with coverage
30
+ run: npm run test:coverage
31
+
32
+ - name: Upload coverage to Codecov
33
+ uses: codecov/codecov-action@v4
34
+ with:
35
+ token: ${{ secrets.CODECOV_TOKEN }}
36
+ fail_ci_if_error: true
@@ -0,0 +1,66 @@
1
+ name: CLI Tests on npm Installations
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: 'Version of @n42/cli to test (e.g. 0.4.2 or 0.2.42)'
8
+ required: true
9
+ type: string
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ node-version: [20.x, 22.x, 'lts/*']
17
+
18
+ steps:
19
+ - name: Checkout repository
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Node.js ${{ matrix.node-version }}
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: ${{ matrix.node-version }}
26
+ cache: 'npm'
27
+
28
+ # ---- Global install (fresh) ----
29
+ - name: Global install fresh @n42/cli@${{ inputs.version }}
30
+ run: |
31
+ npm install -g @n42/cli@${{ inputs.version }}
32
+ echo "Installed version:"
33
+ n42 --version
34
+ # Run your validation script (assumes it exists in repo)
35
+ node test/asserts/validate_tests.js || echo "Validation failed - check logs"
36
+
37
+ # ---- Global upgrade to latest ----
38
+ - name: Global upgrade to latest @n42/cli
39
+ run: |
40
+ npm install -g @n42/cli
41
+ echo "Upgraded version:"
42
+ n42 --version
43
+ node test/asserts/validate_tests.js || echo "Validation failed after upgrade"
44
+
45
+ # ---- Local install (fresh) in isolated dir ----
46
+ - name: Local install fresh @n42/cli@${{ inputs.version }}
47
+ run: |
48
+ mkdir local-fresh && cd local-fresh
49
+ npm init -y
50
+ npm install @n42/cli@${{ inputs.version }}
51
+ echo "Local fresh version:"
52
+ npx n42 --version
53
+ node ../test/asserts/validate_tests.js || echo "Local validation failed"
54
+ cd ..
55
+
56
+ # ---- Local upgrade to latest ----
57
+ - name: Local upgrade to latest @n42/cli
58
+ run: |
59
+ mkdir local-upgrade && cd local-upgrade
60
+ npm init -y
61
+ npm install @n42/cli@${{ inputs.version }}
62
+ npm install @n42/cli
63
+ echo "Local upgraded version:"
64
+ npx n42 --version
65
+ node ../test/asserts/validate_tests.js || echo "Local upgrade validation failed"
66
+ cd ..
package/README.md CHANGED
@@ -1,13 +1,22 @@
1
- # Node42 CLI (n42)
1
+ [![codecov](https://codecov.io/gh/node42-dev/node42-cli/graph/badge.svg)](https://codecov.io/gh/node42-dev/node42-cli)
2
+ [![CI](https://github.com/node42-dev/node42-cli/actions/workflows/ci.yaml/badge.svg)](https://github.com/node42-dev/node42-cli/actions/workflows/ci.yaml)
3
+ [![npm](https://img.shields.io/npm/v/@n42/cli.svg)](https://www.npmjs.com/package/@n42/cli)
4
+ [![Swagger](https://img.shields.io/badge/Swagger-Discovery%20API-green)](https://node42.dev/docs/discovery)
2
5
 
3
- Command-line interface for **eDelivery discovery, diagnostics, and
4
- validation**, with support for the Peppol network.
6
+ # Node42 CLI
5
7
 
6
- The Node42 CLI is designed for **system integrators, service providers,
7
- and operators** who need fast, repeatable insight into eDelivery
8
- routing, SML/SMK, SMP resolution, and Access Point behavior.
8
+ Command-line frontend for Node42 **eDelivery path discovery**, diagnostics and validation with support for the Peppol network.
9
9
 
10
- ------------------------------------------------------------------------
10
+ The Node42 CLI is a **scriptable command-line client** to the **Node42 WebUI and API**,
11
+ built for **system integrators**, **service providers**, and **operators** who need fast,
12
+ repeatable insight into eDelivery routing, SML/SMK, SMP resolution, and Access Point behavior.
13
+
14
+ It exposes the **same capabilities as the Node42 WebUI** but optimized for automation
15
+ and local analysis.
16
+
17
+ While Node42's toolset **includes** modules capable of constructing and **sending
18
+ standards-compliant messages**, it is **intended for diagnostics**, validation,
19
+ and testing — not for production message exchange.
11
20
 
12
21
  ## Features
13
22
 
@@ -128,9 +137,15 @@ Artefacts are stored under:
128
137
 
129
138
  ## Error Handling
130
139
 
131
- Errors are printed with a clickable reference link:
140
+ Errors are printed with a clickable reference link.
132
141
 
133
- https://www.node42.dev/errors?code=XXXX
142
+ Example output:
143
+
144
+ ``` bash
145
+ Error: 9031 [View details]
146
+
147
+ Invalid token: the authorization token provided is invalid
148
+ ```
134
149
 
135
150
  ## Security
136
151
 
@@ -139,4 +154,9 @@ Errors are printed with a clickable reference link:
139
154
 
140
155
  ## License
141
156
 
142
- MIT License
157
+ MIT License
158
+
159
+ ## Author
160
+
161
+ Alex Olsson \
162
+ Node42
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "@n42/cli",
3
- "version": "0.2.32",
3
+ "version": "0.2.71",
4
4
  "description": "Node42 CLI – Command-line interface for Peppol eDelivery path discovery, diagnostics, and tooling",
5
5
  "keywords": [
6
- "node42", "peppol", "edelivery", "diagnostics",
7
- "cli", "api"
6
+ "node42",
7
+ "peppol",
8
+ "edelivery",
9
+ "diagnostics",
10
+ "cli",
11
+ "api"
8
12
  ],
9
13
  "homepage": "https://github.com/node42-dev/node42-cli",
10
14
  "repository": {
@@ -19,7 +23,22 @@
19
23
  },
20
24
  "scripts": {
21
25
  "lint": "eslint src",
22
- "test": "mocha"
26
+ "test": "mocha",
27
+ "test:coverage": "c8 --reporter=text-summary --reporter=lcov mocha",
28
+ "test:watch": "mocha --watch",
29
+ "build": "esbuild src/cli.js --bundle --platform=node --target=node20 --outfile=dist/n42 --format=cjs --minify --banner:js='#!/usr/bin/env node'"
30
+ },
31
+ "c8": {
32
+ "exclude": [
33
+ "src/assets/**",
34
+ "src/completion/**",
35
+ "src/config.js",
36
+ "src/colors.js"
37
+ ],
38
+ "check-coverage": true,
39
+ "lines": 70,
40
+ "functions": 70,
41
+ "branches": 70
23
42
  },
24
43
  "engines": {
25
44
  "node": ">=18"
@@ -27,10 +46,14 @@
27
46
  "dependencies": {
28
47
  "commander": "^11.1.0",
29
48
  "inquirer": "^8.2.7",
30
- "open": "^11.0.0"
49
+ "open": "^11.0.0",
50
+ "xmldom": "^0.6.0",
51
+ "xpath": "^0.0.34"
31
52
  },
32
53
  "devDependencies": {
54
+ "c8": "^10.1.3",
33
55
  "chai": "^4.5.0",
56
+ "esbuild": "^0.27.2",
34
57
  "eslint": "^9.39.2",
35
58
  "globals": "^17.2.0",
36
59
  "mocha": "^11.3.0",
@@ -28,6 +28,6 @@
28
28
  </div>
29
29
  </div>
30
30
  </div>
31
- <script src="../../assets/wrapper.js" defer></script>
31
+ <script src="../../assets/discover.js" defer></script>
32
32
  </body>
33
33
  </html>
@@ -0,0 +1,49 @@
1
+ #xmldata {
2
+ background: #fff;
3
+ padding: 6px 12px 6px 10px;
4
+ color: #9ca3af;
5
+ font-family: monospace;
6
+ white-space: pre-wrap;
7
+ overflow-x: auto;
8
+ cursor: default;
9
+ }
10
+
11
+ .xml-info0 {
12
+ background: #e5e7eb;
13
+ color: #374151;
14
+ border-left: 4px solid #9ca3af;
15
+ padding-left: 2px;
16
+ cursor: pointer;
17
+ }
18
+
19
+ .xml-info1 {
20
+ background: #e0f2fe;
21
+ color: #111827;
22
+ border-left: 4px solid #38bdf8;
23
+ padding-left: 2px;
24
+ cursor: pointer;
25
+ }
26
+
27
+ .xml-info2 {
28
+ background: #f3e8ff;
29
+ color: #111827;
30
+ border-left: 4px solid #a855f7;
31
+ padding-left: 2px;
32
+ cursor: pointer;
33
+ }
34
+
35
+ .xml-warning {
36
+ background: #fef3c7;
37
+ color: #111827;
38
+ border-left: 4px solid #f59e0b;
39
+ padding-left: 2px;
40
+ cursor: pointer;
41
+ }
42
+
43
+ .xml-error {
44
+ background: #fee2e2;
45
+ color: #111827;
46
+ border-left: 4px solid #dc2626;
47
+ padding-left: 2px;
48
+ cursor: pointer;
49
+ }
@@ -0,0 +1,27 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Node42</title>
6
+ <link rel="stylesheet" href="../../assets/wrapper-light.css">
7
+ <link rel="stylesheet" href="../../assets/validator-light.css">
8
+ </head>
9
+ <body>
10
+ <div id="app-header">
11
+ <div class="header-left">
12
+ <img id="appBtn" src="../../assets/node42-logo.svg" alt="Node42" onclick="window.open('https://www.node42.dev', '_blank');">
13
+ </div>
14
+ <div class="header-right"></div>
15
+ </div>
16
+ <div id="page">
17
+ <div id="app-shell">
18
+ <div id="timeline"></div>
19
+ <div id="bubble" class="bubble" data-uuid="/--UUID--/">
20
+ <div id="xmldata"><!-- XML --></div>
21
+ <div class="bubble-time"><!-- TIME --></div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </body>
27
+ </html>
@@ -95,6 +95,13 @@ body {
95
95
  display: block;
96
96
  }
97
97
 
98
+ .bubble-title {
99
+ font-size: 17px;
100
+ font-weight: 600;
101
+ margin-bottom: 2px;
102
+ text-align: center;
103
+ }
104
+
98
105
  .bubble-time {
99
106
  position: absolute;
100
107
  bottom: -24px;
package/src/auth.js CHANGED
@@ -7,11 +7,54 @@ const { ask, startSpinner } = require("./utils");
7
7
  const db = require("./db");
8
8
  const C = require("./colors");
9
9
 
10
+ function setApiKey(userId, key) {
11
+ if (!key) return;
12
+
13
+ const database = db.load();
14
+
15
+ const u = database.user.find(x => x.id === userId);
16
+ if (!u) return;
17
+
18
+ u.apiKey = {
19
+ "value": key,
20
+ "createdAt": Date.now()
21
+ }
22
+
23
+ db.save(database);
24
+ }
25
+
26
+ function getApiKey(userId) {
27
+ const database = db.load();
28
+
29
+ const u = database.user.find(x => x.id === userId);
30
+ if (!u || !u.apiKey) return null;
31
+
32
+ return u.apiKey.value;
33
+ }
34
+
35
+ function removeApiKey(userId) {
36
+ const database = db.load();
37
+
38
+ const u = database.user.find(x => x.id === userId);
39
+ if (!u || !u.apiKey) return false;
40
+
41
+ delete u.apiKey;
42
+ db.save(database);
43
+
44
+ return true;
45
+ }
10
46
 
11
47
  async function login() {
12
48
  console.log(`${C.BOLD}Sign in to your account${C.RESET}`);
13
49
  let user = getUserWithIndex(0);
14
50
 
51
+ const apiKey = getApiKey(user.id);
52
+ if (apiKey) {
53
+ console.log(`\n${C.RED}API key authentication is configured.${C.RESET}`);
54
+ console.log(`Login is not required.\n`);
55
+ return;
56
+ }
57
+
15
58
  const username = await ask("Username", user.userMail ?? "");
16
59
  const password = await ask("Password", null, true);
17
60
  console.log();
@@ -158,21 +201,33 @@ async function refreshSession() {
158
201
  }
159
202
 
160
203
  async function fetchWithAuth(url, options = {}) {
204
+ const user = getUserWithIndex(0);
205
+ const apiKey = user ? getApiKey(user.id) : null;
206
+
161
207
  let { accessToken } = loadTokens();
162
- if (!accessToken) {
208
+
209
+ if (!accessToken && !apiKey) {
163
210
  handleError({ code: "N42E-9032" });
164
- return;
211
+ return null;
212
+ }
213
+
214
+ if (apiKey) {
215
+ console.log(`${C.DIM}Authenticating with API key.${C.RESET}\n`);
165
216
  }
166
217
 
167
218
  const res = await fetch(url, {
168
219
  ...options,
169
220
  headers: {
170
221
  ...(options.headers || {}),
171
- ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {})
222
+ ...(accessToken
223
+ ? { Authorization: `Bearer ${accessToken}` }
224
+ : apiKey
225
+ ? { "X-Api-Key": apiKey }
226
+ : {})
172
227
  }
173
228
  });
174
229
 
175
- if (res.status !== 401) {
230
+ if (apiKey || res.status !== 401) {
176
231
  return res;
177
232
  }
178
233
 
@@ -191,4 +246,4 @@ async function fetchWithAuth(url, options = {}) {
191
246
  });
192
247
  }
193
248
 
194
- module.exports = { login, logout, loadTokens, checkAuth, fetchWithAuth };
249
+ module.exports = { setApiKey, getApiKey, removeApiKey, login, logout, loadTokens, checkAuth, fetchWithAuth };
package/src/cli.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { Command } = require("commander");
4
- const { login, logout, checkAuth } = require("./auth");
4
+ const { login, logout, checkAuth, setApiKey, getApiKey, removeApiKey } = require("./auth");
5
5
  const { getUserWithIndex, getUserUsage } = require("./user");
6
6
  const { runDiscovery } = require("./discover");
7
+ const { runValidation } = require("./validator");
7
8
  const { startSpinner, validateEnv, validateId, createAppDirs, capitalize, cleanAppDirs } = require("./utils");
8
9
  const { NODE42_DIR, ARTEFACTS_DIR, DEFAULT_OUTPUT, DEFAULT_FORMAT } = require("./config");
9
10
 
@@ -51,16 +52,39 @@ program
51
52
  .action(logout);
52
53
 
53
54
  program
54
- .command("clean")
55
- .description("Remove locally stored artefacts and cache")
56
- .option("--tokens", "Remove stored authentication tokens")
57
- .option("--artefacts", "Remove artefacts")
58
- .option("--transactions", "Remove transactions")
59
- .option("--validations", "Remove validations")
60
- .option("--db", "Remove local database")
61
- .option("--all", "Wipe all local data")
62
- .action((options)=> {
63
- cleanAppDirs(options);
55
+ .command("apikey")
56
+ .description("Manage API key authentication")
57
+ .option("--set <key>", "Authenticate using an API key")
58
+ .option("--remove", "Remove stored API key")
59
+ .action((options) => {
60
+ const user = getUserWithIndex(0);
61
+ if (!user) {
62
+ console.error(`${C.RED}No local user context found${C.RESET}`);
63
+ process.exit(1);
64
+ }
65
+
66
+ console.log(`${C.BOLD}Node42 Account${C.RESET} (${user.userMail})\n`);
67
+
68
+ if (options.set) {
69
+ setApiKey(user.id, options.set);
70
+
71
+ if (getApiKey(user.id) === options.set) {
72
+ console.log(`${C.GREEN}API key authentication configured${C.RESET}\n`);
73
+ } else {
74
+ console.log(`${C.RED}API key configuration failed${C.RESET}\n`);
75
+ }
76
+ return;
77
+ }
78
+
79
+ if (options.remove) {
80
+ const removed = removeApiKey(user.id);
81
+ console.log(removed ? `${C.RED}API key removed${C.RESET}\n` : `${C.RED}No API key configured${C.RESET}\n`);
82
+ return;
83
+ }
84
+
85
+ // default: show status
86
+ const apiKey = getApiKey(user.id);
87
+ console.log(apiKey ? `${C.RED}API key configured${C.RESET}\n` : `${C.RED}No API key configured${C.RESET}\n`);
64
88
  });
65
89
 
66
90
  program
@@ -78,9 +102,8 @@ program
78
102
 
79
103
  const user = getUserWithIndex(0);
80
104
  const currentMonth = new Date().toISOString().slice(0, 7);
81
- console.log(`Node42 Account (CLI v${pkg.version})
105
+ console.log(`Node42 Account: ${C.BOLD}${user.id}${C.RESET}
82
106
  ${C.BOLD}User${C.RESET}
83
- ID : ${C.CYAN}${user.id}${C.RESET}
84
107
  Name : ${user.userName}
85
108
  Email : ${user.userMail}
86
109
  Role : ${user.role}
@@ -113,6 +136,19 @@ program
113
136
  console.log(` • ${currentMonth}: ${C.RED}${usage}${C.RESET}\n`);
114
137
  });
115
138
 
139
+ program
140
+ .command("clean")
141
+ .description("Remove locally stored artefacts and cache")
142
+ .option("--tokens", "Remove stored authentication tokens")
143
+ .option("--artefacts", "Remove artefacts")
144
+ .option("--transactions", "Remove transactions")
145
+ .option("--validations", "Remove validations")
146
+ .option("--db", "Remove local database")
147
+ .option("--all", "Wipe all local data")
148
+ .action((options)=> {
149
+ cleanAppDirs(options);
150
+ });
151
+
116
152
  program
117
153
  .command("history [participantId]")
118
154
  .description("Show local history with filtering")
@@ -225,4 +261,26 @@ discover
225
261
  runDiscovery(participantId, options);
226
262
  });
227
263
 
264
+ const validate = program
265
+ .command("validate")
266
+ .description("Run document validation using configurable rulesets");
267
+
268
+ validate
269
+ .command("peppol <document>")
270
+ .description("Validate a document against Peppol validation rulesets")
271
+ .option("-r, --ruleset <ruleset>", "Validation ruleset to use (latest | current | legacy)", "current")
272
+ .option("--location", "Include XPath location for each validation assertion", true)
273
+ .option("--runtime", "Include execution time in the validation output", false)
274
+ .action((document, options) => {
275
+ if (!fs.existsSync(document)) {
276
+ console.error("Couldn't find a valid document at the selected path");
277
+ process.exit(1);
278
+ }
279
+
280
+ const xmlDoc = fs.readFileSync(document, "utf8");
281
+ const docName = path.basename(document);
282
+
283
+ runValidation(docName, xmlDoc, options);
284
+ });
285
+
228
286
  program.parse(process.argv);
@@ -3,7 +3,7 @@ _n42_completions()
3
3
  local cur prev words cword
4
4
  _init_completion || return
5
5
 
6
- local commands="login logout me usage history discover clean"
6
+ local commands="login logout apikey me usage clean history discover validate"
7
7
 
8
8
  if [[ $cword -eq 1 ]]; then
9
9
  COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
@@ -16,6 +16,12 @@ _n42_completions()
16
16
  return
17
17
  fi
18
18
 
19
+ # validate peppol
20
+ if [[ ${words[1]} == "validate" && $cword -eq 2 ]]; then
21
+ COMPREPLY=( $(compgen -W "peppol" -- "$cur") )
22
+ return
23
+ fi
24
+
19
25
  # usage discovery|validation|transactions
20
26
  if [[ ${words[1]} == "usage" && $cword -eq 2 ]]; then
21
27
  COMPREPLY=( $(compgen -W "discovery validation transactions" -- "$cur") )
package/src/config.js CHANGED
@@ -6,6 +6,8 @@ const config = {
6
6
  API_URL: "https://api.node42.dev",
7
7
  WWW_URL: "https://www.node42.dev",
8
8
 
9
+ VALIDATOR_URL: "https://validator.node42.dev",
10
+
9
11
  API_TIMEOUT_MS: 30000,
10
12
 
11
13
  NODE42_DIR: path.join(os.homedir(), ".node42"),
@@ -24,7 +26,8 @@ const config = {
24
26
  EP_SIGNIN: "auth/signin",
25
27
  EP_REFRESH: "auth/refresh",
26
28
  EP_ME: "users/me",
27
- EP_DISCOVER: "discover/peppol"
29
+ EP_DISCOVER: "discover/peppol",
30
+ EP_VALIDATE: "validate"
28
31
  };
29
32
 
30
33
  config.ARTEFACTS_DIR = path.join(config.NODE42_DIR, "artefacts", "discovery");
package/src/discover.js CHANGED
@@ -43,7 +43,7 @@ function wrapSvg(fileId, refId, svg) {
43
43
  minute: "2-digit"
44
44
  });
45
45
 
46
- const templateFile = path.join(NODE42_DIR, "assets/wrapper.html.template");
46
+ const templateFile = path.join(NODE42_DIR, "assets/discover.html.template");
47
47
  const template = fs.readFileSync(templateFile, "utf8");
48
48
 
49
49
  html = template.replace("<!-- SVG -->", svg);
package/src/errors.js CHANGED
@@ -15,10 +15,12 @@ function handleError(err) {
15
15
  : `${WWW_URL}/errors`;
16
16
  //console.log(url);
17
17
 
18
+ const link = `\u001B]8;;${url}\u0007View details\u001B]8;;\u0007`;
19
+
18
20
  if (message) {
19
- console.error(`\r${C.RED}${err.message}${C.RESET}\nSee details: ${C.BOLD}${url}${C.RESET}\n`);
21
+ console.error(`\r${C.BOLD}Error: ${code}${C.RESET} ${C.BLUE}[${link}]${C.RESET}\n\n${C.RED}${err.message}${C.RESET}\n`);
20
22
  } else {
21
- console.error(`\rSee details: ${C.BOLD}${url}${C.RESET}\n`);
23
+ console.error(`\r${C.BOLD}Error: ${code}${C.RESET} ${C.BLUE}[${url}]${C.RESET}\n\nFor details, see the documentation.\n`);
22
24
  }
23
25
  }
24
26
 
package/src/utils.js CHANGED
@@ -10,7 +10,7 @@ const pkg = require("../package.json");
10
10
  const db = require("./db");
11
11
  const C = require("./colors");
12
12
 
13
-
13
+ /* c8 ignore next */
14
14
  function writeHeader(text, clearScreen=false) {
15
15
  if (clearScreen) {
16
16
  process.stdout.write("\x1Bc");
@@ -22,6 +22,7 @@ function writeHeader(text, clearScreen=false) {
22
22
  }
23
23
  }
24
24
 
25
+ /* c8 ignore next */
25
26
  function ask(question, def, hidden=false) {
26
27
  return new Promise(resolve => {
27
28
  const rl = readline.createInterface({
@@ -58,6 +59,7 @@ function ask(question, def, hidden=false) {
58
59
  });
59
60
  }
60
61
 
62
+ /* c8 ignore next */
61
63
  function startSpinner(text = "Working") {
62
64
  const frames = ["-", "\\", "|", "/"];
63
65
  let i = 0;
@@ -73,6 +75,7 @@ function startSpinner(text = "Working") {
73
75
  };
74
76
  }
75
77
 
78
+ /* c8 ignore next */
76
79
  async function promptForDocument(docs) {
77
80
  const { document } = await inquirer.prompt([
78
81
  {
@@ -109,10 +112,12 @@ function validateId(type, id) {
109
112
  }
110
113
  }
111
114
 
115
+ /* c8 ignore next */
112
116
  function getShortId(id) {
113
117
  return id.slice(0, 8);
114
118
  }
115
119
 
120
+ /* c8 ignore next */
116
121
  function capitalize(s) {
117
122
  return s.charAt(0).toUpperCase() + s.slice(1);
118
123
  }