ssh-keyman 2.0.0 → 2.0.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.
@@ -0,0 +1,335 @@
1
+ # Test Reporting Configuration
2
+
3
+ ## Overview
4
+
5
+ This document explains how test reporting works in the ssh-keyman project.
6
+
7
+ ## Test Report Formats
8
+
9
+ ### 1. **Console Output** (Default)
10
+ ```bash
11
+ npm test
12
+ ```
13
+ Shows test results in terminal with coverage table.
14
+
15
+ ### 2. **JUnit XML Report** (CI/CD)
16
+ ```bash
17
+ npm run test:ci
18
+ ```
19
+ Generates `coverage/junit.xml` for GitHub Actions integration.
20
+
21
+ ### 3. **HTML Coverage Report**
22
+ ```bash
23
+ npm test
24
+ open coverage/lcov-report/index.html
25
+ ```
26
+ Visual coverage report in browser.
27
+
28
+ ---
29
+
30
+ ## GitHub Actions Integration
31
+
32
+ ### Workflows Using Test Reports
33
+
34
+ #### 1. **CI Workflow** (`ci.yml`)
35
+ - Runs tests on every push/PR
36
+ - Uploads coverage to Codecov
37
+ - Validates across multiple platforms
38
+
39
+ #### 2. **Test Report Workflow** (`test-report.yml`)
40
+ - Generates detailed test reports
41
+ - Creates check runs with test results
42
+ - Comments on PRs with coverage details
43
+ - Uploads test artifacts
44
+
45
+ ---
46
+
47
+ ## JUnit Report Configuration
48
+
49
+ ### Package: `jest-junit`
50
+ Installed as dev dependency to generate JUnit XML format.
51
+
52
+ ### Jest Configuration
53
+ ```json
54
+ {
55
+ "reporters": [
56
+ "default",
57
+ [
58
+ "jest-junit",
59
+ {
60
+ "outputDirectory": "coverage",
61
+ "outputName": "junit.xml",
62
+ "classNameTemplate": "{classname}",
63
+ "titleTemplate": "{title}",
64
+ "ancestorSeparator": " › ",
65
+ "usePathForSuiteName": "true"
66
+ }
67
+ ]
68
+ ]
69
+ }
70
+ ```
71
+
72
+ ### Generated File
73
+ - **Location**: `coverage/junit.xml`
74
+ - **Size**: ~5KB
75
+ - **Format**: Standard JUnit XML
76
+ - **Contents**: Test results, timings, failures
77
+
78
+ ---
79
+
80
+ ## Test Report Workflow Breakdown
81
+
82
+ ### Triggers
83
+ - Push to `main`, `master`, or `develop`
84
+ - Pull requests to above branches
85
+
86
+ ### Steps
87
+
88
+ #### 1. **Run Tests**
89
+ ```yaml
90
+ - name: Run tests with coverage
91
+ run: npm run test:ci
92
+ continue-on-error: true
93
+ ```
94
+ Runs tests and generates JUnit report.
95
+
96
+ #### 2. **Upload Artifacts**
97
+ ```yaml
98
+ - name: Upload test results
99
+ uses: actions/upload-artifact@v4
100
+ with:
101
+ name: test-results
102
+ path: coverage/junit.xml
103
+ retention-days: 30
104
+ ```
105
+ Stores test results for 30 days.
106
+
107
+ #### 3. **Generate Report**
108
+ ```yaml
109
+ - name: Test Report
110
+ uses: dorny/test-reporter@v1
111
+ with:
112
+ name: Jest Tests
113
+ path: coverage/junit.xml
114
+ reporter: jest-junit
115
+ ```
116
+ Creates GitHub check runs with test details.
117
+
118
+ #### 4. **PR Comments**
119
+ ```yaml
120
+ - name: Comment PR with Coverage
121
+ if: github.event_name == 'pull_request'
122
+ uses: ArtiomTr/jest-coverage-report-action@v2
123
+ ```
124
+ Adds coverage comment to pull requests.
125
+
126
+ ---
127
+
128
+ ## Viewing Test Reports
129
+
130
+ ### In GitHub Actions
131
+
132
+ 1. **Go to Actions tab**
133
+ 2. **Select workflow run**
134
+ 3. **View "Test Report" job**
135
+ 4. **Check "Artifacts" section** for downloadable reports
136
+
137
+ ### Check Runs
138
+
139
+ GitHub automatically creates check runs showing:
140
+ - ✅ Tests passed/failed
141
+ - 📊 Test count by suite
142
+ - ⏱️ Execution time
143
+ - 📋 Individual test results
144
+
145
+ ### PR Comments
146
+
147
+ On pull requests, you'll see:
148
+ - Coverage percentage change
149
+ - Lines added/removed
150
+ - Coverage by file
151
+ - Detailed coverage report link
152
+
153
+ ---
154
+
155
+ ## Report Contents
156
+
157
+ ### JUnit XML Structure
158
+
159
+ ```xml
160
+ <testsuites name="jest tests" tests="35" failures="0">
161
+ <testsuite name="src/__tests__/cliOptions.test.js">
162
+ <testcase classname="cliOptions › prepareArgs"
163
+ name="should extract arguments"
164
+ time="0.007">
165
+ </testcase>
166
+ <!-- More test cases -->
167
+ </testsuite>
168
+ </testsuites>
169
+ ```
170
+
171
+ ### Report Includes
172
+
173
+ - ✅ Test suite names
174
+ - ✅ Individual test names
175
+ - ✅ Execution time per test
176
+ - ✅ Failure details (if any)
177
+ - ✅ Error messages
178
+ - ✅ Stack traces (on failure)
179
+
180
+ ---
181
+
182
+ ## Artifacts
183
+
184
+ ### Test Results Artifact
185
+ - **Name**: `test-results`
186
+ - **Contents**: `junit.xml`
187
+ - **Retention**: 30 days
188
+ - **Use**: Download to view detailed test data
189
+
190
+ ### Coverage Results Artifact
191
+ - **Name**: `coverage-results`
192
+ - **Contents**: Full coverage directory
193
+ - **Includes**:
194
+ - `coverage-final.json`
195
+ - `lcov.info`
196
+ - `junit.xml`
197
+ - HTML reports
198
+ - **Retention**: 30 days
199
+
200
+ ---
201
+
202
+ ## Troubleshooting
203
+
204
+ ### "No test report files were found"
205
+
206
+ **Cause**: JUnit XML not generated
207
+ **Solution**:
208
+ - Ensure `jest-junit` is installed
209
+ - Check Jest reporter configuration
210
+ - Verify tests ran successfully
211
+
212
+ ### "Reporter not found"
213
+
214
+ **Cause**: Incorrect reporter name
215
+ **Solution**: Use `jest-junit` (not `jest`)
216
+
217
+ ### "Permission denied"
218
+
219
+ **Cause**: Missing workflow permissions
220
+ **Solution**: Add to workflow:
221
+ ```yaml
222
+ permissions:
223
+ contents: read
224
+ checks: write
225
+ pull-requests: write
226
+ ```
227
+
228
+ ### Tests Pass but Report Fails
229
+
230
+ **Cause**: Report generation error
231
+ **Solution**:
232
+ - Check artifact upload logs
233
+ - Verify file path is correct
234
+ - Ensure XML is valid
235
+
236
+ ---
237
+
238
+ ## Best Practices
239
+
240
+ ### ✅ Do
241
+ - Run tests before pushing
242
+ - Check test reports on PRs
243
+ - Review coverage changes
244
+ - Keep artifacts for debugging
245
+ - Use `continue-on-error` for report steps
246
+
247
+ ### ❌ Don't
248
+ - Commit `junit.xml` to git
249
+ - Ignore test report failures
250
+ - Skip coverage checks
251
+ - Delete artifacts prematurely
252
+
253
+ ---
254
+
255
+ ## Local Testing
256
+
257
+ ### Generate Report Locally
258
+ ```bash
259
+ # Run tests with JUnit output
260
+ npm run test:ci
261
+
262
+ # View JUnit XML
263
+ cat coverage/junit.xml
264
+
265
+ # View HTML coverage
266
+ open coverage/lcov-report/index.html
267
+ ```
268
+
269
+ ### Validate XML
270
+ ```bash
271
+ # Check if XML is well-formed
272
+ xmllint --noout coverage/junit.xml
273
+
274
+ # Pretty print
275
+ xmllint --format coverage/junit.xml
276
+ ```
277
+
278
+ ---
279
+
280
+ ## CI/CD Flow Diagram
281
+
282
+ ```
283
+ Push/PR → CI Workflow
284
+ ├─ Install Dependencies
285
+ ├─ Run Tests
286
+ ├─ Generate Coverage
287
+ └─ Upload to Codecov
288
+
289
+ → Test Report Workflow
290
+ ├─ Run Tests
291
+ ├─ Generate JUnit XML
292
+ ├─ Upload Artifacts
293
+ ├─ Create Check Run
294
+ └─ Comment on PR (if PR)
295
+ ```
296
+
297
+ ---
298
+
299
+ ## Coverage Report Features
300
+
301
+ ### In PR Comments
302
+
303
+ **Coverage Summary:**
304
+ ```
305
+ Coverage: 75.59% (+1.2%)
306
+ Files: 5
307
+ Lines: 163/215 (+5)
308
+ ```
309
+
310
+ **By File:**
311
+ ```
312
+ commands.js 78.91% (+2.1%)
313
+ cliOptions.js 60.00% (no change)
314
+ ```
315
+
316
+ **Annotations:**
317
+ - Uncovered lines highlighted
318
+ - Coverage decrease warnings
319
+ - New uncovered code markers
320
+
321
+ ---
322
+
323
+ ## References
324
+
325
+ - [jest-junit Documentation](https://github.com/jest-community/jest-junit)
326
+ - [dorny/test-reporter](https://github.com/dorny/test-reporter)
327
+ - [ArtiomTr/jest-coverage-report-action](https://github.com/ArtiomTr/jest-coverage-report-action)
328
+ - [JUnit XML Format](https://llg.cubic.org/docs/junit/)
329
+
330
+ ---
331
+
332
+ **Status**: ✅ Fully Configured
333
+ **Last Updated**: 2026-01-15
334
+ **Maintained By**: ssh-keyman team
335
+
@@ -5,14 +5,15 @@ on:
5
5
  branches: [ main, master, develop ]
6
6
  pull_request:
7
7
  branches: [ main, master, develop ]
8
- schedule:
9
- # Run tests daily at 2 AM UTC
10
- - cron: '0 2 * * *'
11
8
 
12
9
  jobs:
13
10
  test-report:
14
11
  name: Generate Test Report
15
12
  runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ checks: write
16
+ pull-requests: write
16
17
 
17
18
  steps:
18
19
  - name: Checkout code
@@ -29,22 +30,37 @@ jobs:
29
30
 
30
31
  - name: Run tests with coverage
31
32
  run: npm run test:ci
33
+ continue-on-error: true
32
34
 
33
- - name: Generate test report
35
+ - name: Upload test results
34
36
  if: always()
37
+ uses: actions/upload-artifact@v4
38
+ with:
39
+ name: test-results
40
+ path: coverage/junit.xml
41
+ retention-days: 30
42
+
43
+ - name: Upload coverage results
44
+ if: always()
45
+ uses: actions/upload-artifact@v4
46
+ with:
47
+ name: coverage-results
48
+ path: coverage/
49
+ retention-days: 30
50
+
51
+ - name: Test Report
35
52
  uses: dorny/test-reporter@v1
53
+ if: always()
36
54
  with:
37
55
  name: Jest Tests
38
56
  path: coverage/junit.xml
39
57
  reporter: jest-junit
40
58
  fail-on-error: false
41
59
 
42
- - name: Comment PR with test results
60
+ - name: Comment PR with Coverage
43
61
  if: github.event_name == 'pull_request'
44
62
  uses: ArtiomTr/jest-coverage-report-action@v2
45
63
  with:
46
- coverage-file: ./coverage/coverage-summary.json
47
- base-coverage-file: ./coverage/coverage-summary.json
48
- annotations: failed-tests
64
+ annotations: coverage
49
65
  package-manager: npm
50
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh-keyman",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "A cli tool to manage multiple ssh keys and switch between different ssh keys with ease & grace.",
5
5
  "main": "index.js",
6
6
  "bin": "index.js",
@@ -35,6 +35,10 @@
35
35
  "url": "https://github.com/shahidullahkhankhattak/ssh-keyman/issues"
36
36
  },
37
37
  "homepage": "https://github.com/shahidullahkhankhattak/ssh-keyman#readme",
38
+ "engines": {
39
+ "node": ">=16.0.0",
40
+ "npm": ">=7.0.0"
41
+ },
38
42
  "dependencies": {
39
43
  "chalk": "^4.1.2",
40
44
  "fs-extra": "^10.0.0",
@@ -43,6 +47,7 @@
43
47
  },
44
48
  "devDependencies": {
45
49
  "jest": "^29.7.0",
50
+ "jest-junit": "^16.0.0",
46
51
  "@types/jest": "^29.5.11"
47
52
  },
48
53
  "jest": {
@@ -51,13 +56,27 @@
51
56
  "collectCoverageFrom": [
52
57
  "src/**/*.js",
53
58
  "!src/**/*.test.js",
59
+ "!src/**/*.spec.js",
54
60
  "!src/__tests__/helpers.js",
55
- "!**/node_modules/**"
61
+ "!src/__tests__/testUtils.js",
62
+ "!**/node_modules/**",
63
+ "!**/coverage/**"
56
64
  ],
57
65
  "testMatch": [
58
66
  "**/__tests__/**/*.test.js",
59
67
  "**/?(*.)+(spec|test).js"
60
68
  ],
69
+ "testPathIgnorePatterns": [
70
+ "/node_modules/",
71
+ "helpers.js",
72
+ "testUtils.js"
73
+ ],
74
+ "coveragePathIgnorePatterns": [
75
+ "/node_modules/",
76
+ "/coverage/",
77
+ "/__tests__/helpers.js",
78
+ "/__tests__/testUtils.js"
79
+ ],
61
80
  "coverageThreshold": {
62
81
  "global": {
63
82
  "branches": 50,
@@ -65,6 +84,20 @@
65
84
  "lines": 55,
66
85
  "statements": 55
67
86
  }
68
- }
87
+ },
88
+ "reporters": [
89
+ "default",
90
+ [
91
+ "jest-junit",
92
+ {
93
+ "outputDirectory": "coverage",
94
+ "outputName": "junit.xml",
95
+ "classNameTemplate": "{classname}",
96
+ "titleTemplate": "{title}",
97
+ "ancestorSeparator": " › ",
98
+ "usePathForSuiteName": "true"
99
+ }
100
+ ]
101
+ ]
69
102
  }
70
103
  }
package/readme.md CHANGED
@@ -4,21 +4,20 @@ ssh-keyman
4
4
  <!-- Build & Test Status -->
5
5
  [![Build Status](https://github.com/shahidullahkhankhattak/ssh-keyman/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/shahidullahkhankhattak/ssh-keyman/actions/workflows/ci.yml)
6
6
  [![Tests](https://img.shields.io/github/actions/workflow/status/shahidullahkhankhattak/ssh-keyman/ci.yml?branch=master&label=tests&logo=github)](https://github.com/shahidullahkhankhattak/ssh-keyman/actions/workflows/ci.yml)
7
- [![Test Coverage](https://codecov.io/gh/shahidullahkhankhattak/ssh-keyman/branch/master/graph/badge.svg)](https://codecov.io/gh/shahidullahkhankhattak/ssh-keyman)
8
- [![Coverage Status](https://img.shields.io/codecov/c/github/shahidullahkhankhattak/ssh-keyman/master.svg?logo=codecov)](https://codecov.io/gh/shahidullahkhankhattak/ssh-keyman)
7
+ ![Test Coverage](https://img.shields.io/badge/coverage-75.59%25-brightgreen)
8
+ ![Tests Passing](https://img.shields.io/badge/tests-35%20passing-brightgreen)
9
9
 
10
10
  <!-- Package Info -->
11
11
  [![npm version](https://img.shields.io/npm/v/ssh-keyman.svg?logo=npm&color=cb3837)](https://www.npmjs.com/package/ssh-keyman)
12
12
  [![npm downloads](https://img.shields.io/npm/dm/ssh-keyman.svg?logo=npm)](https://www.npmjs.com/package/ssh-keyman)
13
- [![npm bundle size](https://img.shields.io/bundlephobia/min/ssh-keyman?logo=npm)](https://bundlephobia.com/package/ssh-keyman)
13
+ ![Node Version](https://img.shields.io/badge/node-%3E%3D16.0.0-brightgreen?logo=node.js)
14
14
 
15
15
  <!-- Platform & Standards -->
16
- [![Node.js Version](https://img.shields.io/node/v/ssh-keyman.svg?logo=node.js)](https://nodejs.org/)
17
16
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
18
17
  [![Maintained](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/shahidullahkhankhattak/ssh-keyman/graphs/commit-activity)
19
18
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
20
19
 
21
- <!-- Repository Stats -->
20
+ <!-- Repository Stats (activate after first push) -->
22
21
  [![Last Commit](https://img.shields.io/github/last-commit/shahidullahkhankhattak/ssh-keyman?logo=github)](https://github.com/shahidullahkhankhattak/ssh-keyman/commits)
23
22
  [![Issues](https://img.shields.io/github/issues/shahidullahkhankhattak/ssh-keyman?logo=github)](https://github.com/shahidullahkhankhattak/ssh-keyman/issues)
24
23
  [![Pull Requests](https://img.shields.io/github/issues-pr/shahidullahkhankhattak/ssh-keyman?logo=github)](https://github.com/shahidullahkhankhattak/ssh-keyman/pulls)
@@ -31,8 +30,8 @@ A sophisticated key manager cli tool to manage multiple ssh keys and switch betw
31
30
  | Workflow | Status |
32
31
  |----------|--------|
33
32
  | **Build & Test** | [![CI](https://github.com/shahidullahkhankhattak/ssh-keyman/actions/workflows/ci.yml/badge.svg)](https://github.com/shahidullahkhankhattak/ssh-keyman/actions/workflows/ci.yml) |
34
- | **NPM Publish** | [![Publish](https://github.com/shahidullahkhankhattak/ssh-keyman/actions/workflows/publish.yml/badge.svg)](https://github.com/shahidullahkhankhattak/ssh-keyman/actions/workflows/publish.yml) |
35
- | **Test Coverage** | ![Coverage](https://img.shields.io/codecov/c/github/shahidullahkhankhattak/ssh-keyman/master.svg?label=coverage) |
33
+ | **Test Coverage** | ![Coverage](https://img.shields.io/badge/coverage-75.59%25-brightgreen) |
34
+ | **NPM Publish** | ![Publish](https://img.shields.io/badge/publish-ready-blue) |
36
35
 
37
36
  > 📊 **[View Detailed Status Report →](STATUS.md)**
38
37
 
package/src/commands.js CHANGED
@@ -282,7 +282,7 @@ const deleteEnv = async function (name) {
282
282
  }
283
283
  if (exist && available.find((env) => env === name)) {
284
284
  available.splice(available.indexOf(name), 1);
285
- fs.rmdirSync(path.join(KEYMAN_DIR_PATH, name), { recursive: true });
285
+ fs.rmSync(path.join(KEYMAN_DIR_PATH, name), { recursive: true, force: true });
286
286
  fs.writeFileSync(
287
287
  KEYMAN_PATH,
288
288
  JSON.stringify({ active, available: available })
package/src/extendFs.js CHANGED
@@ -2,16 +2,9 @@ const fs = require("fs-extra");
2
2
  const path = require("path");
3
3
 
4
4
  const delDirSync = (pathName) => {
5
- const content = fs.readdirSync(pathName);
6
- if (!content.length) {
7
- return fs.rmdirSync(pathName);
5
+ if (fs.existsSync(pathName)) {
6
+ fs.rmSync(pathName, { recursive: true, force: true });
8
7
  }
9
- content.forEach((contentPath) => {
10
- const isDir = fs.lstatSync(path.join(pathName, contentPath)).isDirectory();
11
- if (isDir) delDirSync(path.join(pathName, contentPath));
12
- else fs.rmSync(path.join(pathName, contentPath));
13
- });
14
- fs.rmdirSync(pathName);
15
8
  };
16
9
 
17
10
  const delAndCopySync = (fromPath, toPath) => {
@@ -1,113 +0,0 @@
1
- const fs = require("fs-extra");
2
- const path = require("path");
3
- const os = require("os");
4
-
5
- /**
6
- * Create a mock file system structure for testing
7
- */
8
- function createMockFileSystem(baseDir) {
9
- const sshPath = path.join(baseDir, ".ssh");
10
- const keymanPath = path.join(baseDir, ".sshkeyman");
11
- const keymanFile = path.join(keymanPath, ".sshkeyman");
12
- const defaultEnvPath = path.join(keymanPath, "default");
13
-
14
- return {
15
- sshPath,
16
- keymanPath,
17
- keymanFile,
18
- defaultEnvPath,
19
- setup: () => {
20
- if (!fs.existsSync(baseDir)) {
21
- fs.mkdirSync(baseDir, { recursive: true });
22
- }
23
- if (!fs.existsSync(sshPath)) {
24
- fs.mkdirSync(sshPath, { recursive: true });
25
- fs.writeFileSync(path.join(sshPath, "id_rsa"), "mock private key");
26
- fs.writeFileSync(path.join(sshPath, "id_rsa.pub"), "mock public key");
27
- }
28
- },
29
- cleanup: () => {
30
- if (fs.existsSync(baseDir)) {
31
- fs.rmSync(baseDir, { recursive: true, force: true });
32
- }
33
- },
34
- initializeKeyman: () => {
35
- if (!fs.existsSync(keymanPath)) {
36
- fs.mkdirSync(keymanPath, { recursive: true });
37
- }
38
- if (!fs.existsSync(defaultEnvPath)) {
39
- fs.mkdirSync(defaultEnvPath, { recursive: true });
40
- fs.copySync(sshPath, defaultEnvPath);
41
- }
42
- fs.writeFileSync(
43
- keymanFile,
44
- JSON.stringify({ active: "default", available: ["default"] })
45
- );
46
- },
47
- };
48
- }
49
-
50
- /**
51
- * Mock console methods
52
- */
53
- function mockConsole() {
54
- const originalLog = console.log;
55
- const originalError = console.error;
56
- const logs = [];
57
- const errors = [];
58
-
59
- console.log = (...args) => {
60
- logs.push(args.join(" "));
61
- };
62
- console.error = (...args) => {
63
- errors.push(args.join(" "));
64
- };
65
-
66
- return {
67
- logs,
68
- errors,
69
- restore: () => {
70
- console.log = originalLog;
71
- console.error = originalError;
72
- },
73
- getLogs: () => logs,
74
- getErrors: () => errors,
75
- clear: () => {
76
- logs.length = 0;
77
- errors.length = 0;
78
- },
79
- };
80
- }
81
-
82
- /**
83
- * Mock inquirer prompts
84
- */
85
- function mockInquirer(answers = {}) {
86
- const inquirer = require("inquirer");
87
- const originalPrompt = inquirer.prompt;
88
-
89
- inquirer.prompt = jest.fn((questions) => {
90
- const responses = {};
91
- questions.forEach((q) => {
92
- if (answers[q.name] !== undefined) {
93
- responses[q.name] = answers[q.name];
94
- } else if (q.default !== undefined) {
95
- responses[q.name] = q.default;
96
- }
97
- });
98
- return Promise.resolve(responses);
99
- });
100
-
101
- return {
102
- restore: () => {
103
- inquirer.prompt = originalPrompt;
104
- },
105
- };
106
- }
107
-
108
- module.exports = {
109
- createMockFileSystem,
110
- mockConsole,
111
- mockInquirer,
112
- };
113
-