@testream/vitest-reporter 0.1.0
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/LICENSE +30 -0
- package/README.md +79 -0
- package/dist/cjs/ctrf-builder.d.ts +6 -0
- package/dist/cjs/ctrf-builder.d.ts.map +1 -0
- package/dist/cjs/index.d.ts +9 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/reporter.d.ts +15 -0
- package/dist/cjs/reporter.d.ts.map +1 -0
- package/dist/cjs/types.d.ts +79 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/uploader.d.ts +13 -0
- package/dist/cjs/uploader.d.ts.map +1 -0
- package/dist/ctrf-builder.d.ts +7 -0
- package/dist/ctrf-builder.d.ts.map +1 -0
- package/dist/esm/index.mjs +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/reporter.d.ts +16 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/uploader.d.ts +14 -0
- package/dist/uploader.d.ts.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Testream Team. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are licensed,
|
|
6
|
+
not sold, to you for use only under the terms of this license.
|
|
7
|
+
|
|
8
|
+
GRANT OF LICENSE:
|
|
9
|
+
You are granted a non-exclusive, non-transferable license to use the Software
|
|
10
|
+
solely for the purpose of integrating test result reporting with the Testream service.
|
|
11
|
+
|
|
12
|
+
RESTRICTIONS:
|
|
13
|
+
- You may not modify, adapt, or create derivative works of the Software
|
|
14
|
+
- You may not distribute, sublicense, lease, or rent the Software
|
|
15
|
+
- You may not reverse engineer, decompile, or disassemble the Software
|
|
16
|
+
- The Software may only be used in conjunction with the Testream service
|
|
17
|
+
|
|
18
|
+
OWNERSHIP:
|
|
19
|
+
The Software is licensed, not sold. This license does not grant you any rights
|
|
20
|
+
to trademarks or service marks.
|
|
21
|
+
|
|
22
|
+
TERMINATION:
|
|
23
|
+
This license is effective until terminated. Your rights under this license will
|
|
24
|
+
terminate automatically without notice if you fail to comply with any term of
|
|
25
|
+
this license.
|
|
26
|
+
|
|
27
|
+
DISCLAIMER:
|
|
28
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
29
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
30
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @testream/vitest-reporter
|
|
2
|
+
|
|
3
|
+
Vitest reporter that generates [CTRF](https://ctrf.io) test reports and automatically uploads them to [Testream](https://testream.app).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev @testream/vitest-reporter
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Add the reporter to your `vitest.config.ts`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { defineConfig } from 'vitest/config'
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
test: {
|
|
20
|
+
reporters: [
|
|
21
|
+
'default',
|
|
22
|
+
['@testream/vitest-reporter', {
|
|
23
|
+
apiKey: process.env.TESTREAM_API_KEY,
|
|
24
|
+
}],
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
| Option | Type | Default | Description |
|
|
33
|
+
|--------|------|---------|-------------|
|
|
34
|
+
| `apiKey` | `string` | **required** | Testream API key |
|
|
35
|
+
| `uploadEnabled` | `boolean` | `true` | Enable/disable upload to Testream |
|
|
36
|
+
| `failOnUploadError` | `boolean` | `false` | Fail the test run if upload fails |
|
|
37
|
+
| `outputDir` | `string` | `'ctrf'` | Directory for the CTRF report |
|
|
38
|
+
| `outputFile` | `string` | `'ctrf-report.json'` | Filename for the CTRF report |
|
|
39
|
+
| `testType` | `string` | `'unit'` | Test type identifier |
|
|
40
|
+
| `branch` | `string` | auto-detected | Git branch name |
|
|
41
|
+
| `commitSha` | `string` | auto-detected | Git commit SHA |
|
|
42
|
+
| `repositoryUrl` | `string` | auto-detected | Git repository URL |
|
|
43
|
+
| `buildName` | `string` | — | Build name/identifier |
|
|
44
|
+
| `buildNumber` | `string` | auto-detected | Build number |
|
|
45
|
+
| `buildUrl` | `string` | auto-detected | CI pipeline URL |
|
|
46
|
+
| `testEnvironment` | `string` | — | Environment name (e.g., 'staging') |
|
|
47
|
+
| `appName` | `string` | — | Application name under test |
|
|
48
|
+
| `appVersion` | `string` | — | Application version under test |
|
|
49
|
+
|
|
50
|
+
## CI/CD Setup
|
|
51
|
+
|
|
52
|
+
Git context (branch, commit SHA, repository URL) and build metadata are auto-detected for:
|
|
53
|
+
|
|
54
|
+
- GitHub Actions
|
|
55
|
+
- GitLab CI
|
|
56
|
+
- Azure Pipelines
|
|
57
|
+
- CircleCI
|
|
58
|
+
- Jenkins
|
|
59
|
+
- Bitbucket Pipelines
|
|
60
|
+
|
|
61
|
+
## Local Development
|
|
62
|
+
|
|
63
|
+
Generate a CTRF report locally without uploading:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
['@testream/vitest-reporter', {
|
|
67
|
+
apiKey: 'not-needed',
|
|
68
|
+
uploadEnabled: false,
|
|
69
|
+
}]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Requirements
|
|
73
|
+
|
|
74
|
+
- Vitest >= 3.0.0
|
|
75
|
+
- Node.js >= 18.0.0
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
See [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TestModule } from 'vitest/node';
|
|
2
|
+
import type { CTRFReport, VitestReporterConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Build a CTRF report from Vitest TestModules
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildCTRFReport(testModules: ReadonlyArray<TestModule>, config: VitestReporterConfig, startTimestamp: number): CTRFReport;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"","sourceRoot":"","sources":["file:///home/runner/work/jira-test-manager/jira-test-manager/packages/vitest-reporter/src/ctrf-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAY,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAuC1E;;GAEG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,EACtC,MAAM,EAAE,oBAAoB,EAC5B,cAAc,EAAE,MAAM,GACrB,UAAU,CAoGZ"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @testream/vitest-reporter
|
|
3
|
+
*
|
|
4
|
+
* Vitest reporter that generates CTRF-format test reports and
|
|
5
|
+
* automatically uploads them to the Testream backend.
|
|
6
|
+
*/
|
|
7
|
+
export { TestreamVitestReporter as default } from './reporter';
|
|
8
|
+
export { TestreamVitestReporter } from './reporter';
|
|
9
|
+
export type { VitestReporterConfig } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"","sourceRoot":"","sources":["file:///home/runner/work/jira-test-manager/jira-test-manager/packages/vitest-reporter/src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,sBAAsB,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{"use strict";var e={406:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.detectCIContext=detectCIContext;function detectCIContext(){const e=process.env;if(e.GITHUB_ACTIONS==="true"){return{branch:e.GITHUB_HEAD_REF||e.GITHUB_REF_NAME||e.GITHUB_REF?.replace("refs/heads/",""),commitSha:e.GITHUB_SHA,repositoryUrl:e.GITHUB_SERVER_URL&&e.GITHUB_REPOSITORY?`${e.GITHUB_SERVER_URL}/${e.GITHUB_REPOSITORY}`:undefined,buildNumber:e.GITHUB_RUN_NUMBER,buildUrl:e.GITHUB_SERVER_URL&&e.GITHUB_REPOSITORY&&e.GITHUB_RUN_ID?`${e.GITHUB_SERVER_URL}/${e.GITHUB_REPOSITORY}/actions/runs/${e.GITHUB_RUN_ID}`:undefined}}if(e.GITLAB_CI==="true"){return{branch:e.CI_COMMIT_BRANCH||e.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,commitSha:e.CI_COMMIT_SHA,repositoryUrl:e.CI_PROJECT_URL,buildNumber:e.CI_PIPELINE_IID,buildUrl:e.CI_PIPELINE_URL}}if(e.TF_BUILD==="True"){return{branch:e.BUILD_SOURCEBRANCH?.replace("refs/heads/",""),commitSha:e.BUILD_SOURCEVERSION,repositoryUrl:e.BUILD_REPOSITORY_URI,buildNumber:e.BUILD_BUILDNUMBER,buildUrl:e.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI&&e.SYSTEM_TEAMPROJECT&&e.BUILD_BUILDID?`${e.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${e.SYSTEM_TEAMPROJECT}/_build/results?buildId=${e.BUILD_BUILDID}`:undefined}}if(e.CIRCLECI==="true"){return{branch:e.CIRCLE_BRANCH,commitSha:e.CIRCLE_SHA1,repositoryUrl:e.CIRCLE_REPOSITORY_URL,buildNumber:e.CIRCLE_BUILD_NUM,buildUrl:e.CIRCLE_BUILD_URL}}if(e.JENKINS_URL){return{branch:e.GIT_BRANCH?.replace("origin/",""),commitSha:e.GIT_COMMIT,repositoryUrl:e.GIT_URL,buildNumber:e.BUILD_NUMBER,buildUrl:e.BUILD_URL}}if(e.BITBUCKET_BUILD_NUMBER){return{branch:e.BITBUCKET_BRANCH,commitSha:e.BITBUCKET_COMMIT,repositoryUrl:e.BITBUCKET_GIT_HTTP_ORIGIN,buildNumber:e.BITBUCKET_BUILD_NUMBER,buildUrl:e.BITBUCKET_REPO_FULL_NAME&&e.BITBUCKET_BUILD_NUMBER?`https://bitbucket.org/${e.BITBUCKET_REPO_FULL_NAME}/pipelines/results/${e.BITBUCKET_BUILD_NUMBER}`:undefined}}return{}}},827:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true})},322:function(e,t,r){var o=this&&this.__createBinding||(Object.create?function(e,t,r,o){if(o===undefined)o=r;var s=Object.getOwnPropertyDescriptor(t,r);if(!s||("get"in s?!t.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return t[r]}}}Object.defineProperty(e,o,s)}:function(e,t,r,o){if(o===undefined)o=r;e[o]=t[r]});var s=this&&this.__exportStar||function(e,t){for(var r in e)if(r!=="default"&&!Object.prototype.hasOwnProperty.call(t,r))o(t,e,r)};Object.defineProperty(t,"__esModule",{value:true});t.ensureReportId=t.mapAttachmentsToTestResults=t.uploadArtifacts=t.uploadTestRun=t.detectCIContext=void 0;s(r(827),t);var n=r(406);Object.defineProperty(t,"detectCIContext",{enumerable:true,get:function(){return n.detectCIContext}});var a=r(969);Object.defineProperty(t,"uploadTestRun",{enumerable:true,get:function(){return a.uploadTestRun}});Object.defineProperty(t,"uploadArtifacts",{enumerable:true,get:function(){return a.uploadArtifacts}});Object.defineProperty(t,"mapAttachmentsToTestResults",{enumerable:true,get:function(){return a.mapAttachmentsToTestResults}});Object.defineProperty(t,"ensureReportId",{enumerable:true,get:function(){return a.ensureReportId}})},969:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:true});t.ensureReportId=ensureReportId;t.uploadTestRun=uploadTestRun;t.mapAttachmentsToTestResults=mapAttachmentsToTestResults;t.uploadArtifacts=uploadArtifacts;const o="https://test-manager-backend.fly.dev";function ensureReportId(e){if(typeof e.reportId==="string"&&e.reportId.trim().length>0){return e.reportId}const t=crypto.randomUUID();e.reportId=t;return t}function resolveApiUrl(e){const t=normalizeApiUrl(e);if(t){return t}const r=getEnvApiUrl();if(r){return r}return o}function getEnvApiUrl(){if(typeof process==="undefined"||!process?.env){return undefined}return normalizeApiUrl(process.env.TESTREAM_API_URL)}function normalizeApiUrl(e){if(!e){return undefined}const t=e.trim();if(!t){return undefined}return t.replace(/\/+$/,"")}async function uploadTestRun(e){const{report:t,apiKey:r}=e;const o=resolveApiUrl(e.apiUrl);const s=ensureReportId(t);const n={report:t,reportId:s,commitSha:e.commitSha,branch:e.branch,repositoryUrl:e.repositoryUrl,buildName:e.buildName,buildNumber:e.buildNumber,buildUrl:e.buildUrl,testEnvironment:e.testEnvironment,appName:e.appName,appVersion:e.appVersion,testType:e.testType};console.log("Uploading test results...");let a;try{a=await fetch(`${o}/api/v1/ingest`,{method:"POST",headers:{"X-API-KEY":r,"Content-Type":"application/json"},body:JSON.stringify(n)})}catch(e){const t=e instanceof Error?e.message:String(e);return{success:false,reportId:s,error:`Connection failed: ${t}`}}if(a.status===409){console.warn("Report already exists (workflow may have been re-run)");return{success:true,reportId:s,summary:{passed:t.results.summary.passed,failed:t.results.summary.failed,skipped:t.results.summary.skipped,total:t.results.summary.tests},alreadyExists:true}}if(!a.ok){const e=await a.text();return{success:false,reportId:s,error:`Upload failed (HTTP ${a.status}): ${e}`}}const i=await a.json();console.log(`✓ Test results uploaded successfully`);console.log(` Report ID: ${i.reportId}`);console.log(` Test Run ID: ${i.testRunId}`);console.log(` Tests: ${i.summary.passed}/${i.summary.total} passed`);return{success:true,reportId:i.reportId,testRunId:i.testRunId,summary:{passed:i.summary.passed,failed:i.summary.failed,skipped:i.summary.skipped,total:i.summary.total},testResults:i.testResults}}function mapAttachmentsToTestResults(e,t){const r=new Map;for(const e of t){const t=r.get(e.name)??[];t.push(e.id);r.set(e.name,t)}const o=[];for(const t of e){const e=r.get(t.name);const s=e?.shift();if(!t.attachments||t.attachments.length===0){continue}if(!s){console.warn(`Skipping artifact upload: could not match test result for "${t.name}"`);continue}o.push({testResultId:s,attachments:t.attachments})}return o}async function uploadArtifacts(e){const{reportId:t,apiKey:r,testResults:o}=e;const s=resolveApiUrl(e.apiUrl);let n=0;for(const e of o){for(const o of e.attachments){try{const a=await uploadSingleArtifact({testResultId:e.testResultId,attachment:o,reportId:t,apiKey:r,apiUrl:s});if(a)n++}catch(e){const t=e instanceof Error?e.message:String(e);console.error(`Failed to upload artifact ${o.name}: ${t}`)}}}if(n>0){console.log(`✓ Uploaded ${n} artifact(s)`)}return n}async function uploadSingleArtifact(e){const{testResultId:t,attachment:o,reportId:s,apiKey:n,apiUrl:a}=e;const i=await Promise.resolve().then(r.t.bind(r,943,23));const l=await Promise.resolve().then(r.t.bind(r,928,23));const c=l.resolve(o.path);try{await i.access(c)}catch{console.warn(`Artifact not found: ${l.basename(c)}`);return false}let u;let p;const d=l.basename(c);const f=o.contentType||"application/octet-stream";try{u=await i.readFile(c);p=u.byteLength}catch(e){const t=e instanceof Error?e.message:String(e);throw new Error(`Failed to read file: ${t}`)}try{return await uploadWithPresignedUrl({testResultId:t,reportId:s,apiKey:n,apiUrl:a,attachmentName:o.name,fileName:d,contentType:f,sizeBytes:p,fileBuffer:u})}catch(e){if(!(e instanceof LegacyUploadRequiredError)){throw e}return await uploadWithLegacyEndpoint({testResultId:t,attachmentName:o.name,reportId:s,apiKey:n,apiUrl:a,fileName:d,contentType:f,fileBuffer:u})}}class LegacyUploadRequiredError extends Error{constructor(){super("Legacy upload endpoint required")}}async function uploadWithPresignedUrl(e){const{testResultId:t,reportId:r,apiKey:o,apiUrl:s,attachmentName:n,fileName:a,contentType:i,sizeBytes:l,fileBuffer:c}=e;const u={testResultId:t,ctrfAttachmentName:n,fileName:a,contentType:i,sizeBytes:l};const p=await fetch(`${s}/api/v1/artifacts/${r}/upload-url`,{method:"POST",headers:{"X-API-KEY":o,"Content-Type":"application/json"},body:JSON.stringify(u)});if(shouldFallbackToLegacyUpload(p.status)){throw new LegacyUploadRequiredError}if(!p.ok){const e=await p.text();throw new Error(`Failed to create direct upload URL (HTTP ${p.status}): ${e}`)}const d=await p.json();const f={...d.requiredHeaders??{}};if(!hasHeaderIgnoreCase(f,"Content-Type")){f["Content-Type"]=i}const m=await fetch(d.uploadUrl,{method:"PUT",headers:f,body:c});if(!m.ok){const e=await m.text();throw new Error(`Direct artifact upload failed (HTTP ${m.status}): ${e}`)}const h={testResultId:t,ctrfAttachmentName:n,fileName:a,contentType:i,sizeBytes:l,storageKey:d.storageKey};const _=await fetch(`${s}/api/v1/artifacts/${r}/complete`,{method:"POST",headers:{"X-API-KEY":o,"Content-Type":"application/json"},body:JSON.stringify(h)});if(!_.ok){const e=await _.text();throw new Error(`Failed to finalize artifact upload (HTTP ${_.status}): ${e}`)}return true}async function uploadWithLegacyEndpoint(e){const{testResultId:t,attachmentName:r,reportId:o,apiKey:s,apiUrl:n,fileName:a,contentType:i,fileBuffer:l}=e;const c=new Blob([new Uint8Array(l)],{type:i});const u=new FormData;u.append("testResultId",t);u.append("ctrfAttachmentName",r);u.append("file",c,a);const p=await fetch(`${n}/api/v1/artifacts/${o}`,{method:"POST",headers:{"X-API-KEY":s},body:u});if(!p.ok){const e=await p.text();throw new Error(`Legacy artifact upload failed (HTTP ${p.status}): ${e}`)}return true}function shouldFallbackToLegacyUpload(e){return e===404||e===405||e===501}function hasHeaderIgnoreCase(e,t){const r=t.toLowerCase();return Object.keys(e).some((e=>e.toLowerCase()===r))}},943:e=>{e.exports=require("fs/promises")},928:e=>{e.exports=require("path")}};var t={};function __nccwpck_require__(r){var o=t[r];if(o!==undefined){return o.exports}var s=t[r]={exports:{}};var n=true;try{e[r].call(s.exports,s,s.exports,__nccwpck_require__);n=false}finally{if(n)delete t[r]}return s.exports}(()=>{var e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;var t;__nccwpck_require__.t=function(r,o){if(o&1)r=this(r);if(o&8)return r;if(typeof r==="object"&&r){if(o&4&&r.__esModule)return r;if(o&16&&typeof r.then==="function")return r}var s=Object.create(null);__nccwpck_require__.r(s);var n={};t=t||[null,e({}),e([]),e(e)];for(var a=o&2&&r;typeof a=="object"&&!~t.indexOf(a);a=e(a)){Object.getOwnPropertyNames(a).forEach((e=>n[e]=()=>r[e]))}n["default"]=()=>r;__nccwpck_require__.d(s,n);return s}})();(()=>{__nccwpck_require__.d=(e,t)=>{for(var r in t){if(__nccwpck_require__.o(t,r)&&!__nccwpck_require__.o(e,r)){Object.defineProperty(e,r,{enumerable:true,get:t[r]})}}}})();(()=>{__nccwpck_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})();(()=>{__nccwpck_require__.r=e=>{if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(e,"__esModule",{value:true})}})();if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r={};__nccwpck_require__.r(r);__nccwpck_require__.d(r,{TestreamVitestReporter:()=>TestreamVitestReporter,default:()=>TestreamVitestReporter});var o=__nccwpck_require__(943);var s=__nccwpck_require__(928);var n=__nccwpck_require__(322);function mapStatus(e){switch(e){case"passed":return"passed";case"failed":return"failed";case"skipped":return"skipped";default:return"pending"}}function buildSuiteName(e){const t=[];let r=e.parent;while(r&&r.type==="suite"){t.unshift(r.name);r=r.parent}return t.join(" > ")}function collectTests(e){const t=[];for(const r of e){if(r.type==="test"){t.push(r)}else if(r.type==="suite"){t.push(...collectTests(r.children))}}return t}function buildCTRFReport(e,t,r){const o=Date.now();const s=[];const n=new Set;for(const o of e){const e=collectTests(o.children);for(const o of e){const e=o.result();const a=o.diagnostic();const i=e.state;const l=mapStatus(i);const c=Math.round(a?.duration??0);const u=a?.startTime??r;const p=u+c;const d=a?.retryCount??0;const f=a?.flaky??false;const m=buildSuiteName(o);if(m){n.add(m)}let h;let _;const b=e.errors;if(b&&b.length>0){const e=b[0];h=e.message;const t=e.stackTrace??e.stack;_=typeof t==="string"?t:undefined}const y={name:o.name,status:l,duration:c,start:u,stop:p,rawStatus:i,tags:[],type:t.testType||"unit",filePath:o.module.moduleId,retries:d,flaky:f,steps:[],suite:m||undefined,stdout:[],stderr:[],message:h,trace:_};s.push(y)}}const a={tests:s.length,passed:s.filter((e=>e.status==="passed")).length,failed:s.filter((e=>e.status==="failed")).length,skipped:s.filter((e=>e.status==="skipped")).length,pending:s.filter((e=>e.status==="pending")).length,other:s.filter((e=>e.status==="other")).length,suites:n.size,start:r,stop:o};const i={reportFormat:"CTRF",specVersion:"0.0.1",timestamp:new Date(r).toISOString(),generatedBy:"@testream/vitest-reporter",extra:{branch:t.branch,commitSha:t.commitSha},results:{tool:{name:"vitest"},summary:a,tests:s,environment:{...t.testEnvironment?{testEnvironment:t.testEnvironment}:{},...t.appName?{appName:t.appName}:{},...t.appVersion?{appVersion:t.appVersion}:{},...t.buildName?{buildName:t.buildName}:{},...t.buildNumber?{buildNumber:t.buildNumber}:{},...t.buildUrl?{buildUrl:t.buildUrl}:{}}}};return i}class ReportUploader{constructor(e){this.config={apiKey:e.apiKey,branch:e.branch,commitSha:e.commitSha,repositoryUrl:e.repositoryUrl,failOnUploadError:e.failOnUploadError??false,buildName:e.buildName,buildNumber:e.buildNumber,buildUrl:e.buildUrl,testEnvironment:e.testEnvironment,appName:e.appName,appVersion:e.appVersion,testType:e.testType}}async uploadReport(e){try{const t=await(0,n.uploadTestRun)({report:e,apiKey:this.config.apiKey,commitSha:this.config.commitSha,branch:this.config.branch,repositoryUrl:this.config.repositoryUrl,buildName:this.config.buildName,buildNumber:this.config.buildNumber,buildUrl:this.config.buildUrl,testEnvironment:this.config.testEnvironment,appName:this.config.appName,appVersion:this.config.appVersion,testType:this.config.testType});if(!t.success){if(this.config.failOnUploadError){throw new Error(t.error||"Upload failed")}return t}let r=0;if(!t.alreadyExists&&t.testRunId&&t.testResults){const o=(0,n.mapAttachmentsToTestResults)(e.results.tests,t.testResults);if(o.length>0){r=await(0,n.uploadArtifacts)({reportId:t.reportId,apiKey:this.config.apiKey,testResults:o})}}return{...t,artifactCount:r}}catch(t){const r=t instanceof Error?t.message:String(t);if(this.config.failOnUploadError){throw new Error(`Failed to upload report: ${r}`)}return{success:false,reportId:(0,n.ensureReportId)(e),error:r}}}}class TestreamVitestReporter{constructor(e){this.uploader=null;this.startTimestamp=0;if(!e.apiKey){throw new Error("TestreamVitestReporter: apiKey is required")}this.config={uploadEnabled:true,outputFile:"ctrf-report.json",outputDir:"ctrf",failOnUploadError:false,testType:"unit",...e};const t=(0,n.detectCIContext)();if(!this.config.branch&&t.branch){this.config.branch=t.branch}if(!this.config.commitSha&&t.commitSha){this.config.commitSha=t.commitSha}if(!this.config.repositoryUrl&&t.repositoryUrl){this.config.repositoryUrl=t.repositoryUrl}if(!this.config.buildNumber&&t.buildNumber){this.config.buildNumber=t.buildNumber}if(!this.config.buildUrl&&t.buildUrl){this.config.buildUrl=t.buildUrl}this.reportPath=s.join(this.config.outputDir,this.config.outputFile);if(this.config.uploadEnabled){this.uploader=new ReportUploader(this.config)}}onTestRunStart(e){this.startTimestamp=Date.now();console.log("");console.log("=".repeat(60));console.log("Vitest Testream Reporter");console.log("=".repeat(60));if(this.config.uploadEnabled){if(this.config.branch)console.log(`Branch: ${this.config.branch}`);if(this.config.commitSha)console.log(`Commit: ${this.config.commitSha.substring(0,7)}`);if(this.config.repositoryUrl)console.log(`Repository: ${this.config.repositoryUrl}`)}else{console.log("Upload: Disabled")}console.log(`Report: ${this.reportPath}`);console.log("=".repeat(60));console.log("")}async onTestRunEnd(e,t,r){const s=buildCTRFReport(e,this.config,this.startTimestamp);try{await o.mkdir(this.config.outputDir,{recursive:true});await o.writeFile(this.reportPath,JSON.stringify(s,null,2))}catch(e){const t=e instanceof Error?e.message:String(e);console.error(`Failed to write CTRF report: ${t}`)}if(!this.config.uploadEnabled||!this.uploader){console.log("");console.log("CTRF report generated");console.log(` ${this.reportPath}`);return}console.log("");console.log("=".repeat(60));console.log("Uploading Test Results");console.log("=".repeat(60));try{const e=await this.uploader.uploadReport(s);if(e.success){console.log("=".repeat(60));console.log("Upload Complete");console.log("=".repeat(60));console.log("")}else{console.error("Upload completed with errors");console.error("")}}catch(e){const t=e instanceof Error?e.message:String(e);console.error("");console.error("=".repeat(60));console.error("Upload Failed");console.error("=".repeat(60));console.error(`Error: ${t}`);console.error("");if(this.config.failOnUploadError){throw e}}}}const a=null&&TestreamVitestReporter;module.exports=r})();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Reporter, TestModule, TestSpecification, SerializedError, TestRunEndReason } from 'vitest/node';
|
|
2
|
+
import { VitestReporterConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Vitest Reporter that generates CTRF reports and uploads them to Testream
|
|
5
|
+
*/
|
|
6
|
+
export declare class TestreamVitestReporter implements Reporter {
|
|
7
|
+
private config;
|
|
8
|
+
private uploader;
|
|
9
|
+
private reportPath;
|
|
10
|
+
private startTimestamp;
|
|
11
|
+
constructor(config: VitestReporterConfig);
|
|
12
|
+
onTestRunStart(_specifications: ReadonlyArray<TestSpecification>): void;
|
|
13
|
+
onTestRunEnd(testModules: ReadonlyArray<TestModule>, _unhandledErrors: ReadonlyArray<SerializedError>, _reason: TestRunEndReason): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export default TestreamVitestReporter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"","sourceRoot":"","sources":["file:///home/runner/work/jira-test-manager/jira-test-manager/packages/vitest-reporter/src/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAI/C;;GAEG;AACH,qBAAa,sBAAuB,YAAW,QAAQ;IACrD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAa;gBAEvB,MAAM,EAAE,oBAAoB;IAuCxC,cAAc,CAAC,eAAe,EAAE,aAAa,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAmBjE,YAAY,CAChB,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,EACtC,gBAAgB,EAAE,aAAa,CAAC,eAAe,CAAC,EAChD,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC;CAoDjB;AAED,eAAe,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export CTRF types from shared-types package
|
|
3
|
+
*/
|
|
4
|
+
export type { CTRFReport, CTRFTest, CTRFSummary, CTRFResults, CTRFAttachment, CTRFStep, } from '@jira-test-manager/shared-types';
|
|
5
|
+
/**
|
|
6
|
+
* Re-export API types from shared-types package
|
|
7
|
+
*/
|
|
8
|
+
export type { UploadResult, } from '@jira-test-manager/shared-types';
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for the Vitest Testream Reporter
|
|
11
|
+
*/
|
|
12
|
+
export interface VitestReporterConfig {
|
|
13
|
+
/**
|
|
14
|
+
* API key for authentication with Testream
|
|
15
|
+
* @required
|
|
16
|
+
*/
|
|
17
|
+
apiKey: string;
|
|
18
|
+
/**
|
|
19
|
+
* Git branch name (auto-detected from CI environment if not provided)
|
|
20
|
+
*/
|
|
21
|
+
branch?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Git commit SHA (auto-detected from CI environment if not provided)
|
|
24
|
+
*/
|
|
25
|
+
commitSha?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Git repository URL (auto-detected from CI environment if not provided)
|
|
28
|
+
*/
|
|
29
|
+
repositoryUrl?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Enable/disable automatic upload to Testream backend
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
uploadEnabled?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Fail the test run if upload fails
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
failOnUploadError?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Directory where CTRF report will be written
|
|
42
|
+
* @default 'ctrf'
|
|
43
|
+
*/
|
|
44
|
+
outputDir?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Filename for the CTRF report
|
|
47
|
+
* @default 'ctrf-report.json'
|
|
48
|
+
*/
|
|
49
|
+
outputFile?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Test type identifier (e.g., 'unit', 'integration', 'e2e')
|
|
52
|
+
* @default 'unit'
|
|
53
|
+
*/
|
|
54
|
+
testType?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Build name/identifier
|
|
57
|
+
*/
|
|
58
|
+
buildName?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Build number
|
|
61
|
+
*/
|
|
62
|
+
buildNumber?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Build URL (CI pipeline URL)
|
|
65
|
+
*/
|
|
66
|
+
buildUrl?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Test environment (e.g., 'staging', 'production', 'ci')
|
|
69
|
+
*/
|
|
70
|
+
testEnvironment?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Application name under test
|
|
73
|
+
*/
|
|
74
|
+
appName?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Application version under test
|
|
77
|
+
*/
|
|
78
|
+
appVersion?: string;
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"","sourceRoot":"","sources":["file:///home/runner/work/jira-test-manager/jira-test-manager/packages/vitest-reporter/src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,YAAY,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,EACd,QAAQ,GACT,MAAM,iCAAiC,CAAC;AAEzC;;GAEG;AACH,YAAY,EACV,YAAY,GACb,MAAM,iCAAiC,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CTRFReport, UploadResult } from '@jira-test-manager/shared-types';
|
|
2
|
+
import { VitestReporterConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Upload CTRF report and artifacts to the Testream backend API
|
|
5
|
+
*/
|
|
6
|
+
export declare class ReportUploader {
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: VitestReporterConfig);
|
|
9
|
+
/**
|
|
10
|
+
* Upload CTRF report to the backend
|
|
11
|
+
*/
|
|
12
|
+
uploadReport(report: CTRFReport): Promise<UploadResult>;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"","sourceRoot":"","sources":["file:///home/runner/work/jira-test-manager/jira-test-manager/packages/vitest-reporter/src/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,YAAY,EAKb,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAMV;gBAEQ,MAAM,EAAE,oBAAoB;IAiBxC;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CA2D9D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TestModule } from 'vitest/node';
|
|
2
|
+
import type { CTRFReport, VitestReporterConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Build a CTRF report from Vitest TestModules
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildCTRFReport(testModules: ReadonlyArray<TestModule>, config: VitestReporterConfig, startTimestamp: number): CTRFReport;
|
|
7
|
+
//# sourceMappingURL=ctrf-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ctrf-builder.d.ts","sourceRoot":"","sources":["../src/ctrf-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAY,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAuC1E;;GAEG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,EACtC,MAAM,EAAE,oBAAoB,EAC5B,cAAc,EAAE,MAAM,GACrB,UAAU,CAoGZ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var W=Object.create;var v=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty;var b=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var re=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Z(t))!te.call(e,s)&&s!==r&&v(e,s,{get:()=>t[s],enumerable:!(o=Q(t,s))||o.enumerable});return e};var $=(e,t,r)=>(r=e!=null?W(ee(e)):{},re(t||!e||!e.__esModule?v(r,"default",{value:e,enumerable:!0}):r,e));var F=b(D=>{"use strict";Object.defineProperty(D,"__esModule",{value:!0})});var H=b(O=>{"use strict";Object.defineProperty(O,"__esModule",{value:!0});O.detectCIContext=se;function se(){let e=process.env;return e.GITHUB_ACTIONS==="true"?{branch:e.GITHUB_HEAD_REF||e.GITHUB_REF_NAME||e.GITHUB_REF?.replace("refs/heads/",""),commitSha:e.GITHUB_SHA,repositoryUrl:e.GITHUB_SERVER_URL&&e.GITHUB_REPOSITORY?`${e.GITHUB_SERVER_URL}/${e.GITHUB_REPOSITORY}`:void 0,buildNumber:e.GITHUB_RUN_NUMBER,buildUrl:e.GITHUB_SERVER_URL&&e.GITHUB_REPOSITORY&&e.GITHUB_RUN_ID?`${e.GITHUB_SERVER_URL}/${e.GITHUB_REPOSITORY}/actions/runs/${e.GITHUB_RUN_ID}`:void 0}:e.GITLAB_CI==="true"?{branch:e.CI_COMMIT_BRANCH||e.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,commitSha:e.CI_COMMIT_SHA,repositoryUrl:e.CI_PROJECT_URL,buildNumber:e.CI_PIPELINE_IID,buildUrl:e.CI_PIPELINE_URL}:e.TF_BUILD==="True"?{branch:e.BUILD_SOURCEBRANCH?.replace("refs/heads/",""),commitSha:e.BUILD_SOURCEVERSION,repositoryUrl:e.BUILD_REPOSITORY_URI,buildNumber:e.BUILD_BUILDNUMBER,buildUrl:e.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI&&e.SYSTEM_TEAMPROJECT&&e.BUILD_BUILDID?`${e.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${e.SYSTEM_TEAMPROJECT}/_build/results?buildId=${e.BUILD_BUILDID}`:void 0}:e.CIRCLECI==="true"?{branch:e.CIRCLE_BRANCH,commitSha:e.CIRCLE_SHA1,repositoryUrl:e.CIRCLE_REPOSITORY_URL,buildNumber:e.CIRCLE_BUILD_NUM,buildUrl:e.CIRCLE_BUILD_URL}:e.JENKINS_URL?{branch:e.GIT_BRANCH?.replace("origin/",""),commitSha:e.GIT_COMMIT,repositoryUrl:e.GIT_URL,buildNumber:e.BUILD_NUMBER,buildUrl:e.BUILD_URL}:e.BITBUCKET_BUILD_NUMBER?{branch:e.BITBUCKET_BRANCH,commitSha:e.BITBUCKET_COMMIT,repositoryUrl:e.BITBUCKET_GIT_HTTP_ORIGIN,buildNumber:e.BITBUCKET_BUILD_NUMBER,buildUrl:e.BITBUCKET_REPO_FULL_NAME&&e.BITBUCKET_BUILD_NUMBER?`https://bitbucket.org/${e.BITBUCKET_REPO_FULL_NAME}/pipelines/results/${e.BITBUCKET_BUILD_NUMBER}`:void 0}:{}}});var G=b(y=>{"use strict";Object.defineProperty(y,"__esModule",{value:!0});y.ensureReportId=K;y.uploadTestRun=ae;y.mapAttachmentsToTestResults=ie;y.uploadArtifacts=le;var oe="https://test-manager-backend.fly.dev";function K(e){if(typeof e.reportId=="string"&&e.reportId.trim().length>0)return e.reportId;let t=crypto.randomUUID();return e.reportId=t,t}function V(e){let t=x(e);if(t)return t;let r=ne();return r||oe}function ne(){if(!(typeof process>"u"||!process?.env))return x(process.env.TESTREAM_API_URL)}function x(e){if(!e)return;let t=e.trim();if(t)return t.replace(/\/+$/,"")}async function ae(e){let{report:t,apiKey:r}=e,o=V(e.apiUrl),s=K(t),a={report:t,reportId:s,commitSha:e.commitSha,branch:e.branch,repositoryUrl:e.repositoryUrl,buildName:e.buildName,buildNumber:e.buildNumber,buildUrl:e.buildUrl,testEnvironment:e.testEnvironment,appName:e.appName,appVersion:e.appVersion,testType:e.testType};console.log("Uploading test results...");let i;try{i=await fetch(`${o}/api/v1/ingest`,{method:"POST",headers:{"X-API-KEY":r,"Content-Type":"application/json"},body:JSON.stringify(a)})}catch(n){let d=n instanceof Error?n.message:String(n);return{success:!1,reportId:s,error:`Connection failed: ${d}`}}if(i.status===409)return console.warn("Report already exists (workflow may have been re-run)"),{success:!0,reportId:s,summary:{passed:t.results.summary.passed,failed:t.results.summary.failed,skipped:t.results.summary.skipped,total:t.results.summary.tests},alreadyExists:!0};if(!i.ok){let n=await i.text();return{success:!1,reportId:s,error:`Upload failed (HTTP ${i.status}): ${n}`}}let l=await i.json();return console.log("\u2713 Test results uploaded successfully"),console.log(` Report ID: ${l.reportId}`),console.log(` Test Run ID: ${l.testRunId}`),console.log(` Tests: ${l.summary.passed}/${l.summary.total} passed`),{success:!0,reportId:l.reportId,testRunId:l.testRunId,summary:{passed:l.summary.passed,failed:l.summary.failed,skipped:l.summary.skipped,total:l.summary.total},testResults:l.testResults}}function ie(e,t){let r=new Map;for(let s of t){let a=r.get(s.name)??[];a.push(s.id),r.set(s.name,a)}let o=[];for(let s of e){let i=r.get(s.name)?.shift();if(!(!s.attachments||s.attachments.length===0)){if(!i){console.warn(`Skipping artifact upload: could not match test result for "${s.name}"`);continue}o.push({testResultId:i,attachments:s.attachments})}}return o}async function le(e){let{reportId:t,apiKey:r,testResults:o}=e,s=V(e.apiUrl),a=0;for(let i of o)for(let l of i.attachments)try{await ue({testResultId:i.testResultId,attachment:l,reportId:t,apiKey:r,apiUrl:s})&&a++}catch(n){let d=n instanceof Error?n.message:String(n);console.error(`Failed to upload artifact ${l.name}: ${d}`)}return a>0&&console.log(`\u2713 Uploaded ${a} artifact(s)`),a}async function ue(e){let{testResultId:t,attachment:r,reportId:o,apiKey:s,apiUrl:a}=e,i=await import("fs/promises"),l=await import("path"),n=l.resolve(r.path);try{await i.access(n)}catch{return console.warn(`Artifact not found: ${l.basename(n)}`),!1}let d,c,p=l.basename(n),f=r.contentType||"application/octet-stream";try{d=await i.readFile(n),c=d.byteLength}catch(m){let T=m instanceof Error?m.message:String(m);throw new Error(`Failed to read file: ${T}`)}try{return await ce({testResultId:t,reportId:o,apiKey:s,apiUrl:a,attachmentName:r.name,fileName:p,contentType:f,sizeBytes:c,fileBuffer:d})}catch(m){if(!(m instanceof g))throw m;return await pe({testResultId:t,attachmentName:r.name,reportId:o,apiKey:s,apiUrl:a,fileName:p,contentType:f,fileBuffer:d})}}var g=class extends Error{constructor(){super("Legacy upload endpoint required")}};async function ce(e){let{testResultId:t,reportId:r,apiKey:o,apiUrl:s,attachmentName:a,fileName:i,contentType:l,sizeBytes:n,fileBuffer:d}=e,c={testResultId:t,ctrfAttachmentName:a,fileName:i,contentType:l,sizeBytes:n},p=await fetch(`${s}/api/v1/artifacts/${r}/upload-url`,{method:"POST",headers:{"X-API-KEY":o,"Content-Type":"application/json"},body:JSON.stringify(c)});if(de(p.status))throw new g;if(!p.ok){let R=await p.text();throw new Error(`Failed to create direct upload URL (HTTP ${p.status}): ${R}`)}let f=await p.json(),m={...f.requiredHeaders??{}};fe(m,"Content-Type")||(m["Content-Type"]=l);let T=await fetch(f.uploadUrl,{method:"PUT",headers:m,body:d});if(!T.ok){let R=await T.text();throw new Error(`Direct artifact upload failed (HTTP ${T.status}): ${R}`)}let I={testResultId:t,ctrfAttachmentName:a,fileName:i,contentType:l,sizeBytes:n,storageKey:f.storageKey},U=await fetch(`${s}/api/v1/artifacts/${r}/complete`,{method:"POST",headers:{"X-API-KEY":o,"Content-Type":"application/json"},body:JSON.stringify(I)});if(!U.ok){let R=await U.text();throw new Error(`Failed to finalize artifact upload (HTTP ${U.status}): ${R}`)}return!0}async function pe(e){let{testResultId:t,attachmentName:r,reportId:o,apiKey:s,apiUrl:a,fileName:i,contentType:l,fileBuffer:n}=e,d=new Blob([new Uint8Array(n)],{type:l}),c=new FormData;c.append("testResultId",t),c.append("ctrfAttachmentName",r),c.append("file",d,i);let p=await fetch(`${a}/api/v1/artifacts/${o}`,{method:"POST",headers:{"X-API-KEY":s},body:c});if(!p.ok){let f=await p.text();throw new Error(`Legacy artifact upload failed (HTTP ${p.status}): ${f}`)}return!0}function de(e){return e===404||e===405||e===501}function fe(e,t){let r=t.toLowerCase();return Object.keys(e).some(o=>o.toLowerCase()===r)}});var P=b(u=>{"use strict";var me=u&&u.__createBinding||(Object.create?function(e,t,r,o){o===void 0&&(o=r);var s=Object.getOwnPropertyDescriptor(t,r);(!s||("get"in s?!t.__esModule:s.writable||s.configurable))&&(s={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,o,s)}:function(e,t,r,o){o===void 0&&(o=r),e[o]=t[r]}),he=u&&u.__exportStar||function(e,t){for(var r in e)r!=="default"&&!Object.prototype.hasOwnProperty.call(t,r)&&me(t,e,r)};Object.defineProperty(u,"__esModule",{value:!0});u.ensureReportId=u.mapAttachmentsToTestResults=u.uploadArtifacts=u.uploadTestRun=u.detectCIContext=void 0;he(F(),u);var Te=H();Object.defineProperty(u,"detectCIContext",{enumerable:!0,get:function(){return Te.detectCIContext}});var E=G();Object.defineProperty(u,"uploadTestRun",{enumerable:!0,get:function(){return E.uploadTestRun}});Object.defineProperty(u,"uploadArtifacts",{enumerable:!0,get:function(){return E.uploadArtifacts}});Object.defineProperty(u,"mapAttachmentsToTestResults",{enumerable:!0,get:function(){return E.mapAttachmentsToTestResults}});Object.defineProperty(u,"ensureReportId",{enumerable:!0,get:function(){return E.ensureReportId}})});var q=$(P());import*as N from"fs/promises";import*as Y from"path";function Re(e){switch(e){case"passed":return"passed";case"failed":return"failed";case"skipped":return"skipped";default:return"pending"}}function Ue(e){let t=[],r=e.parent;for(;r&&r.type==="suite";)t.unshift(r.name),r=r.parent;return t.join(" > ")}function j(e){let t=[];for(let r of e)r.type==="test"?t.push(r):r.type==="suite"&&t.push(...j(r.children));return t}function k(e,t,r){let o=Date.now(),s=[],a=new Set;for(let n of e){let d=j(n.children);for(let c of d){let p=c.result(),f=c.diagnostic(),m=p.state,T=Re(m),I=Math.round(f?.duration??0),U=f?.startTime??r,R=U+I,J=f?.retryCount??0,z=f?.flaky??!1,S=Ue(c);S&&a.add(S);let A,L,B=p.errors;if(B&&B.length>0){let w=B[0];A=w.message;let M=w.stackTrace??w.stack;L=typeof M=="string"?M:void 0}let X={name:c.name,status:T,duration:I,start:U,stop:R,rawStatus:m,tags:[],type:t.testType||"unit",filePath:c.module.moduleId,retries:J,flaky:z,steps:[],suite:S||void 0,stdout:[],stderr:[],message:A,trace:L};s.push(X)}}let i={tests:s.length,passed:s.filter(n=>n.status==="passed").length,failed:s.filter(n=>n.status==="failed").length,skipped:s.filter(n=>n.status==="skipped").length,pending:s.filter(n=>n.status==="pending").length,other:s.filter(n=>n.status==="other").length,suites:a.size,start:r,stop:o};return{reportFormat:"CTRF",specVersion:"0.0.1",timestamp:new Date(r).toISOString(),generatedBy:"@testream/vitest-reporter",extra:{branch:t.branch,commitSha:t.commitSha},results:{tool:{name:"vitest"},summary:i,tests:s,environment:{...t.testEnvironment?{testEnvironment:t.testEnvironment}:{},...t.appName?{appName:t.appName}:{},...t.appVersion?{appVersion:t.appVersion}:{},...t.buildName?{buildName:t.buildName}:{},...t.buildNumber?{buildNumber:t.buildNumber}:{},...t.buildUrl?{buildUrl:t.buildUrl}:{}}}}}var h=$(P()),_=class{constructor(t){this.config={apiKey:t.apiKey,branch:t.branch,commitSha:t.commitSha,repositoryUrl:t.repositoryUrl,failOnUploadError:t.failOnUploadError??!1,buildName:t.buildName,buildNumber:t.buildNumber,buildUrl:t.buildUrl,testEnvironment:t.testEnvironment,appName:t.appName,appVersion:t.appVersion,testType:t.testType}}async uploadReport(t){try{let r=await(0,h.uploadTestRun)({report:t,apiKey:this.config.apiKey,commitSha:this.config.commitSha,branch:this.config.branch,repositoryUrl:this.config.repositoryUrl,buildName:this.config.buildName,buildNumber:this.config.buildNumber,buildUrl:this.config.buildUrl,testEnvironment:this.config.testEnvironment,appName:this.config.appName,appVersion:this.config.appVersion,testType:this.config.testType});if(!r.success){if(this.config.failOnUploadError)throw new Error(r.error||"Upload failed");return r}let o=0;if(!r.alreadyExists&&r.testRunId&&r.testResults){let s=(0,h.mapAttachmentsToTestResults)(t.results.tests,r.testResults);s.length>0&&(o=await(0,h.uploadArtifacts)({reportId:r.reportId,apiKey:this.config.apiKey,testResults:s}))}return{...r,artifactCount:o}}catch(r){let o=r instanceof Error?r.message:String(r);if(this.config.failOnUploadError)throw new Error(`Failed to upload report: ${o}`);return{success:!1,reportId:(0,h.ensureReportId)(t),error:o}}}};var C=class{constructor(t){this.uploader=null;this.startTimestamp=0;if(!t.apiKey)throw new Error("TestreamVitestReporter: apiKey is required");this.config={uploadEnabled:!0,outputFile:"ctrf-report.json",outputDir:"ctrf",failOnUploadError:!1,testType:"unit",...t};let r=(0,q.detectCIContext)();!this.config.branch&&r.branch&&(this.config.branch=r.branch),!this.config.commitSha&&r.commitSha&&(this.config.commitSha=r.commitSha),!this.config.repositoryUrl&&r.repositoryUrl&&(this.config.repositoryUrl=r.repositoryUrl),!this.config.buildNumber&&r.buildNumber&&(this.config.buildNumber=r.buildNumber),!this.config.buildUrl&&r.buildUrl&&(this.config.buildUrl=r.buildUrl),this.reportPath=Y.join(this.config.outputDir,this.config.outputFile),this.config.uploadEnabled&&(this.uploader=new _(this.config))}onTestRunStart(t){this.startTimestamp=Date.now(),console.log(""),console.log("=".repeat(60)),console.log("Vitest Testream Reporter"),console.log("=".repeat(60)),this.config.uploadEnabled?(this.config.branch&&console.log(`Branch: ${this.config.branch}`),this.config.commitSha&&console.log(`Commit: ${this.config.commitSha.substring(0,7)}`),this.config.repositoryUrl&&console.log(`Repository: ${this.config.repositoryUrl}`)):console.log("Upload: Disabled"),console.log(`Report: ${this.reportPath}`),console.log("=".repeat(60)),console.log("")}async onTestRunEnd(t,r,o){let s=k(t,this.config,this.startTimestamp);try{await N.mkdir(this.config.outputDir,{recursive:!0}),await N.writeFile(this.reportPath,JSON.stringify(s,null,2))}catch(a){let i=a instanceof Error?a.message:String(a);console.error(`Failed to write CTRF report: ${i}`)}if(!this.config.uploadEnabled||!this.uploader){console.log(""),console.log("CTRF report generated"),console.log(` ${this.reportPath}`);return}console.log(""),console.log("=".repeat(60)),console.log("Uploading Test Results"),console.log("=".repeat(60));try{(await this.uploader.uploadReport(s)).success?(console.log("=".repeat(60)),console.log("Upload Complete"),console.log("=".repeat(60)),console.log("")):(console.error("Upload completed with errors"),console.error(""))}catch(a){let i=a instanceof Error?a.message:String(a);if(console.error(""),console.error("=".repeat(60)),console.error("Upload Failed"),console.error("=".repeat(60)),console.error(`Error: ${i}`),console.error(""),this.config.failOnUploadError)throw a}}};export{C as TestreamVitestReporter,C as default};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @testream/vitest-reporter
|
|
3
|
+
*
|
|
4
|
+
* Vitest reporter that generates CTRF-format test reports and
|
|
5
|
+
* automatically uploads them to the Testream backend.
|
|
6
|
+
*/
|
|
7
|
+
export { TestreamVitestReporter as default } from './reporter';
|
|
8
|
+
export { TestreamVitestReporter } from './reporter';
|
|
9
|
+
export type { VitestReporterConfig } from './types';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,sBAAsB,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Reporter, TestModule, TestSpecification, SerializedError, TestRunEndReason } from 'vitest/node';
|
|
2
|
+
import { VitestReporterConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Vitest Reporter that generates CTRF reports and uploads them to Testream
|
|
5
|
+
*/
|
|
6
|
+
export declare class TestreamVitestReporter implements Reporter {
|
|
7
|
+
private config;
|
|
8
|
+
private uploader;
|
|
9
|
+
private reportPath;
|
|
10
|
+
private startTimestamp;
|
|
11
|
+
constructor(config: VitestReporterConfig);
|
|
12
|
+
onTestRunStart(_specifications: ReadonlyArray<TestSpecification>): void;
|
|
13
|
+
onTestRunEnd(testModules: ReadonlyArray<TestModule>, _unhandledErrors: ReadonlyArray<SerializedError>, _reason: TestRunEndReason): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export default TestreamVitestReporter;
|
|
16
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAI/C;;GAEG;AACH,qBAAa,sBAAuB,YAAW,QAAQ;IACrD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAa;gBAEvB,MAAM,EAAE,oBAAoB;IAuCxC,cAAc,CAAC,eAAe,EAAE,aAAa,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAmBjE,YAAY,CAChB,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,EACtC,gBAAgB,EAAE,aAAa,CAAC,eAAe,CAAC,EAChD,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC;CAoDjB;AAED,eAAe,sBAAsB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export CTRF types from shared-types package
|
|
3
|
+
*/
|
|
4
|
+
export type { CTRFReport, CTRFTest, CTRFSummary, CTRFResults, CTRFAttachment, CTRFStep, } from '@jira-test-manager/shared-types';
|
|
5
|
+
/**
|
|
6
|
+
* Re-export API types from shared-types package
|
|
7
|
+
*/
|
|
8
|
+
export type { UploadResult, } from '@jira-test-manager/shared-types';
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for the Vitest Testream Reporter
|
|
11
|
+
*/
|
|
12
|
+
export interface VitestReporterConfig {
|
|
13
|
+
/**
|
|
14
|
+
* API key for authentication with Testream
|
|
15
|
+
* @required
|
|
16
|
+
*/
|
|
17
|
+
apiKey: string;
|
|
18
|
+
/**
|
|
19
|
+
* Git branch name (auto-detected from CI environment if not provided)
|
|
20
|
+
*/
|
|
21
|
+
branch?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Git commit SHA (auto-detected from CI environment if not provided)
|
|
24
|
+
*/
|
|
25
|
+
commitSha?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Git repository URL (auto-detected from CI environment if not provided)
|
|
28
|
+
*/
|
|
29
|
+
repositoryUrl?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Enable/disable automatic upload to Testream backend
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
uploadEnabled?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Fail the test run if upload fails
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
failOnUploadError?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Directory where CTRF report will be written
|
|
42
|
+
* @default 'ctrf'
|
|
43
|
+
*/
|
|
44
|
+
outputDir?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Filename for the CTRF report
|
|
47
|
+
* @default 'ctrf-report.json'
|
|
48
|
+
*/
|
|
49
|
+
outputFile?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Test type identifier (e.g., 'unit', 'integration', 'e2e')
|
|
52
|
+
* @default 'unit'
|
|
53
|
+
*/
|
|
54
|
+
testType?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Build name/identifier
|
|
57
|
+
*/
|
|
58
|
+
buildName?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Build number
|
|
61
|
+
*/
|
|
62
|
+
buildNumber?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Build URL (CI pipeline URL)
|
|
65
|
+
*/
|
|
66
|
+
buildUrl?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Test environment (e.g., 'staging', 'production', 'ci')
|
|
69
|
+
*/
|
|
70
|
+
testEnvironment?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Application name under test
|
|
73
|
+
*/
|
|
74
|
+
appName?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Application version under test
|
|
77
|
+
*/
|
|
78
|
+
appVersion?: string;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,YAAY,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,EACd,QAAQ,GACT,MAAM,iCAAiC,CAAC;AAEzC;;GAEG;AACH,YAAY,EACV,YAAY,GACb,MAAM,iCAAiC,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CTRFReport, UploadResult } from '@jira-test-manager/shared-types';
|
|
2
|
+
import { VitestReporterConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Upload CTRF report and artifacts to the Testream backend API
|
|
5
|
+
*/
|
|
6
|
+
export declare class ReportUploader {
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: VitestReporterConfig);
|
|
9
|
+
/**
|
|
10
|
+
* Upload CTRF report to the backend
|
|
11
|
+
*/
|
|
12
|
+
uploadReport(report: CTRFReport): Promise<UploadResult>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=uploader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../src/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,YAAY,EAKb,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAMV;gBAEQ,MAAM,EAAE,oBAAoB;IAiBxC;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CA2D9D"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@testream/vitest-reporter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vitest reporter to upload test results to Testream",
|
|
5
|
+
"main": "dist/cjs/index.js",
|
|
6
|
+
"module": "dist/esm/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/esm/index.mjs",
|
|
12
|
+
"require": "./dist/cjs/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:types",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"build:cjs": "ncc build src/index.ts -o dist/cjs --minify -e vitest",
|
|
19
|
+
"build:esm": "esbuild src/index.ts --bundle --minify --format=esm --platform=node --target=es2020 --outfile=dist/esm/index.mjs --external:vitest",
|
|
20
|
+
"build:types": "tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
21
|
+
"format": "prettier --write '**/*.ts'",
|
|
22
|
+
"lint": "eslint src/**/*.ts",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"all": "npm run format && npm run lint && npm run build"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/testream/docs.git"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"vitest",
|
|
33
|
+
"reporter",
|
|
34
|
+
"testing",
|
|
35
|
+
"test-management",
|
|
36
|
+
"test-results",
|
|
37
|
+
"jira",
|
|
38
|
+
"ctrf",
|
|
39
|
+
"testream"
|
|
40
|
+
],
|
|
41
|
+
"author": "Testream",
|
|
42
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"vitest": ">=3.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@jira-test-manager/shared-types": "*",
|
|
51
|
+
"@vercel/ncc": "^0.38.1",
|
|
52
|
+
"esbuild": "^0.24.0",
|
|
53
|
+
"vitest": "^3.0.0",
|
|
54
|
+
"@types/node": "^20.10.6",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
|
56
|
+
"@typescript-eslint/parser": "^6.17.0",
|
|
57
|
+
"eslint": "^8.56.0",
|
|
58
|
+
"prettier": "^3.1.1",
|
|
59
|
+
"typescript": "^5.3.3"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.0.0"
|
|
63
|
+
},
|
|
64
|
+
"files": [
|
|
65
|
+
"dist",
|
|
66
|
+
"README.md",
|
|
67
|
+
"LICENSE"
|
|
68
|
+
]
|
|
69
|
+
}
|