@testream/pytest-reporter 1.0.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/source-snippets.d.ts +7 -0
- package/dist/source-snippets.d.ts.map +1 -0
- package/dist/types.d.ts +1 -1
- package/package.json +6 -4
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA6ZA,YAAY,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,YAAY,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,oBAAoB,EACpB,SAAS,GACV,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
(()=>{"use strict";var e={18:function(e,t,r){var n=this&&this.__createBinding||(Object.create?function(e,t,r,n){if(n===undefined)n=r;var o=Object.getOwnPropertyDescriptor(t,r);if(!o||("get"in o?!t.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return t[r]}}}Object.defineProperty(e,n,o)}:function(e,t,r,n){if(n===undefined)n=r;e[n]=t[r]});var o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}:function(e,t){e["default"]=t});var i=this&&this.__importStar||function(){var ownKeys=function(e){ownKeys=Object.getOwnPropertyNames||function(e){var t=[];for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r))t[t.length]=r;return t};return ownKeys(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r=ownKeys(e),i=0;i<r.length;i++)if(r[i]!=="default")n(t,e,r[i]);o(t,e);return t}}();Object.defineProperty(t,"__esModule",{value:true});const s=r(317);const a=i(r(928));const p=i(r(943));const l=r(617);const u="junit/pytest-junit.xml";function printUsage(){console.log(`\n@testream/pytest-reporter - Run pytest and upload results to Testream\n\nUsage:\n npx @testream/pytest-reporter --api-key <key> [options]\n\nRequired arguments (unless --no-upload):\n --api-key, -k API key for authentication\n\nOptional arguments:\n --project <path> Path to pytest project (defaults to current directory)\n --python <path> Python executable (default: python, fallback: python3)\n --junit-path <path> Skip running pytest, ingest existing JUnit XML path/glob\n\nGit context (auto-detected in CI):\n --branch Git branch name\n --commit-sha Git commit SHA\n --repository-url Git repository URL\n\nBuild metadata (auto-detected in CI):\n --build-name Build name/identifier\n --build-number Build number\n --build-url Build URL (CI pipeline URL)\n\nEnvironment metadata:\n --test-environment Test environment (e.g., staging, production, ci)\n --app-name Application name under test\n --app-version Application version under test\n --test-type Test type (e.g., unit, integration, e2e)\n\nUpload options:\n --no-upload Skip uploading CTRF results\n --fail-on-error Fail process if upload fails\n\nOther:\n --help, -h Show help message\n -- <args> Additional arguments to pass to pytest\n\nExamples:\n # Run pytest and upload\n npx @testream/pytest-reporter -k your-api-key\n\n # Ingest existing JUnit XML without running pytest\n npx @testream/pytest-reporter -k your-api-key --junit-path ./reports/junit.xml\n`)}function parseArgs(e){const t={uploadEnabled:true,failOnUploadError:false,continueOnTestFailure:true};const r=[];let n=false;for(let o=0;o<e.length;o++){const i=e[o];const s=e[o+1];if(i==="--"){n=true;continue}if(n){r.push(i);continue}switch(i){case"--help":case"-h":printUsage();process.exit(0);break;case"--api-key":case"-k":t.apiKey=s;o++;break;case"--project":t.projectPath=s;o++;break;case"--python":t.pythonExecutable=s;o++;break;case"--junit-path":t.junitPath=s;o++;break;case"--branch":t.branch=s;o++;break;case"--commit-sha":t.commitSha=s;o++;break;case"--repository-url":t.repositoryUrl=s;o++;break;case"--build-name":t.buildName=s;o++;break;case"--build-number":t.buildNumber=s;o++;break;case"--build-url":t.buildUrl=s;o++;break;case"--test-environment":t.testEnvironment=s;o++;break;case"--app-name":t.appName=s;o++;break;case"--app-version":t.appVersion=s;o++;break;case"--test-type":t.testType=s;o++;break;case"--no-upload":t.uploadEnabled=false;break;case"--fail-on-error":t.failOnUploadError=true;break;default:if(i.startsWith("-")){console.error(`Unknown argument: ${i}`);return null}break}}if(r.length>0){t.pytestArgs=r}if(!t.apiKey&&t.uploadEnabled){console.error("Error: Missing required argument: --api-key (unless --no-upload is set)");return null}return t}function isPytestTestFailureCode(e){return e===1}async function runPytest(e){const t=e.projectPath?a.resolve(e.projectPath):process.cwd();const r=a.resolve(t,u);await p.mkdir(a.dirname(r),{recursive:true});const n=["-m","pytest","--junitxml",r,...e.pytestArgs??[]];const o=e.pythonExecutable?[e.pythonExecutable]:process.platform==="win32"?["python","py"]:["python","python3"];let i=null;for(const a of o){console.info(`Running pytest with ${a}...`);console.info(` Command: ${a} -m pytest --junitxml ${r}`);if((e.pytestArgs??[]).length>0){console.info(` Additional pytest args: ${(e.pytestArgs??[]).length} provided`)}console.info(` Working directory: ${t}`);const o=await new Promise(((e,r)=>{const o=(0,s.spawn)(a,n,{cwd:t,stdio:["inherit","inherit","inherit"],shell:false});o.on("error",(e=>{r(e)}));o.on("close",(t=>{e(t)}))})).catch((e=>{i=e;return null}));if(i&&o===null){continue}try{await p.access(r)}catch{throw new Error(`Pytest run completed but no JUnit XML was found at: ${r}`)}return{junitPath:r,exitCode:o}}const l=i instanceof Error?i.message:"Failed to start python executable for pytest";throw new Error(l)}async function main(){const e=process.argv.slice(2);if(e.length===0){printUsage();process.exit(1)}const t=parseArgs(e);if(!t){process.exit(1)}let r=false;let n=0;try{let e=t.junitPath;if(e){console.info("Using existing JUnit XML file(s), tests will not be re-run...")}else{const o=await runPytest(t);e=o.junitPath;n=o.exitCode;if(o.exitCode&&o.exitCode!==0){r=isPytestTestFailureCode(o.exitCode);if(!r){throw new Error(`pytest exited with code ${o.exitCode}`)}}}const o=await(0,l.convertJUnitToCtrf)({junitPath:e});o.report.results.tool.name="pytest";o.report.generatedBy="@testream/pytest-reporter";await p.writeFile(o.reportPath,JSON.stringify(o.report,null,2));let i;const s=t.uploadEnabled!==false;if(s&&t.apiKey){i=await(0,l.uploadCtrfReport)({report:o.report,apiKey:t.apiKey,apiUrl:t.apiUrl,branch:t.branch,commitSha:t.commitSha,repositoryUrl:t.repositoryUrl,buildName:t.buildName,buildNumber:t.buildNumber,buildUrl:t.buildUrl,testEnvironment:t.testEnvironment,appName:t.appName,appVersion:t.appVersion,testType:t.testType});if(!i.success&&t.failOnUploadError){throw new Error(i.error||"Upload failed")}}console.info("Test run ingestion completed");console.info(` JUnit source files: ${o.sourceFiles.length}`);console.info(` CTRF output: ${o.reportPath}`);console.info(` Tests: ${o.report.results.summary.tests}`);console.info(` Passed: ${o.report.results.summary.passed}`);console.info(` Failed: ${o.report.results.summary.failed}`);console.info(` Skipped: ${o.report.results.summary.skipped}`);if(!i){console.info("Upload disabled, skipping upload")}if(r){process.exit(n||1)}}catch(e){const t=e instanceof Error?e.message:String(e);console.error(`Error: ${t}`);process.exit(1)}}main()},617:e=>{e.exports=require("@testream/junit-reporter")},317:e=>{e.exports=require("child_process")},943:e=>{e.exports=require("fs/promises")},928:e=>{e.exports=require("path")}};var t={};function __nccwpck_require__(r){var n=t[r];if(n!==undefined){return n.exports}var o=t[r]={exports:{}};var i=true;try{e[r].call(o.exports,o,o.exports,__nccwpck_require__);i=false}finally{if(i)delete t[r]}return o.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r=__nccwpck_require__(18);module.exports=r})();
|
|
2
|
+
(()=>{"use strict";var e={406:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.detectCIContext=detectCIContext;function removePrefix(e,t){if(!e){return undefined}return e.startsWith(t)?e.slice(t.length):e}function readEnvironment(){const e=globalThis;return e.process?.env??{}}function buildGithubRepositoryUrl(e){if(!e.GITHUB_SERVER_URL||!e.GITHUB_REPOSITORY){return undefined}return`${e.GITHUB_SERVER_URL}/${e.GITHUB_REPOSITORY}`}function buildGithubBuildUrl(e){if(!e.GITHUB_SERVER_URL||!e.GITHUB_REPOSITORY||!e.GITHUB_RUN_ID){return undefined}return`${e.GITHUB_SERVER_URL}/${e.GITHUB_REPOSITORY}/actions/runs/${e.GITHUB_RUN_ID}`}function detectGitHubBranch(e){if(e.GITHUB_HEAD_REF){return e.GITHUB_HEAD_REF}if(e.GITHUB_REF?.startsWith("refs/heads/")){return e.GITHUB_REF_NAME??removePrefix(e.GITHUB_REF,"refs/heads/")}return undefined}function detectGitHubActionsContext(e){return{branch:detectGitHubBranch(e),commitSha:e.GITHUB_SHA,repositoryUrl:buildGithubRepositoryUrl(e),buildNumber:e.GITHUB_RUN_NUMBER,buildUrl:buildGithubBuildUrl(e)}}function detectGitLabContext(e){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}}function detectAzurePipelinesContext(e){return{branch:removePrefix(e.BUILD_SOURCEBRANCH,"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}}function detectCircleCIContext(e){return{branch:e.CIRCLE_BRANCH,commitSha:e.CIRCLE_SHA1,repositoryUrl:e.CIRCLE_REPOSITORY_URL,buildNumber:e.CIRCLE_BUILD_NUM,buildUrl:e.CIRCLE_BUILD_URL}}function detectJenkinsContext(e){return{branch:removePrefix(e.GIT_BRANCH,"origin/"),commitSha:e.GIT_COMMIT,repositoryUrl:e.GIT_URL,buildNumber:e.BUILD_NUMBER,buildUrl:e.BUILD_URL}}function detectBitbucketContext(e){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}}const n=[{isMatch:e=>e.GITHUB_ACTIONS==="true",detect:detectGitHubActionsContext},{isMatch:e=>e.GITLAB_CI==="true",detect:detectGitLabContext},{isMatch:e=>e.TF_BUILD==="True",detect:detectAzurePipelinesContext},{isMatch:e=>e.CIRCLECI==="true",detect:detectCircleCIContext},{isMatch:e=>Boolean(e.JENKINS_URL),detect:detectJenkinsContext},{isMatch:e=>Boolean(e.BITBUCKET_BUILD_NUMBER),detect:detectBitbucketContext}];function detectCIContext(){const e=readEnvironment();for(const t of n){if(t.isMatch(e)){return t.detect(e)}}return{}}},827:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true})},322:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){if(r===undefined)r=n;var o=Object.getOwnPropertyDescriptor(t,n);if(!o||("get"in o?!t.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return t[n]}}}Object.defineProperty(e,r,o)}:function(e,t,n,r){if(r===undefined)r=n;e[r]=t[n]});var o=this&&this.__exportStar||function(e,t){for(var n in e)if(n!=="default"&&!Object.prototype.hasOwnProperty.call(t,n))r(t,e,n)};Object.defineProperty(t,"__esModule",{value:true});t.createTestLocationCollector=t.enrichReportWithJsTestNameSourceSnippets=t.resolveSourceSnippetOptions=t.enrichReportWithSourceSnippets=t.ensureReportId=t.mapAttachmentsToTestResults=t.uploadArtifacts=t.uploadTestRun=t.detectCIContext=void 0;o(n(827),t);var i=n(406);Object.defineProperty(t,"detectCIContext",{enumerable:true,get:function(){return i.detectCIContext}});var s=n(969);Object.defineProperty(t,"uploadTestRun",{enumerable:true,get:function(){return s.uploadTestRun}});Object.defineProperty(t,"uploadArtifacts",{enumerable:true,get:function(){return s.uploadArtifacts}});Object.defineProperty(t,"mapAttachmentsToTestResults",{enumerable:true,get:function(){return s.mapAttachmentsToTestResults}});Object.defineProperty(t,"ensureReportId",{enumerable:true,get:function(){return s.ensureReportId}});var a=n(304);Object.defineProperty(t,"enrichReportWithSourceSnippets",{enumerable:true,get:function(){return a.enrichReportWithSourceSnippets}});Object.defineProperty(t,"resolveSourceSnippetOptions",{enumerable:true,get:function(){return a.resolveSourceSnippetOptions}});var c=n(913);Object.defineProperty(t,"enrichReportWithJsTestNameSourceSnippets",{enumerable:true,get:function(){return c.enrichReportWithJsTestNameSourceSnippets}});var u=n(293);Object.defineProperty(t,"createTestLocationCollector",{enumerable:true,get:function(){return u.createTestLocationCollector}})},913:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.enrichReportWithJsTestNameSourceSnippets=enrichReportWithJsTestNameSourceSnippets;const o=n(455);const i=r(n(760));const s=n(304);const a=3;const c=/\b(describe|it|test)(?:\.(only|skip))?\s*\(\s*(['"`])/g;async function enrichReportWithJsTestNameSourceSnippets(e,t){const n=(0,s.resolveSourceSnippetOptions)(t);const r=new Map;await Promise.all(e.results.tests.map((async e=>{if(e.snippet||!e.filePath){return}const t=i.default.resolve(n.sourceRoot,e.filePath);if(!isInsideSourceRoot(t,n.sourceRoot)){return}const o=await getCachedJsTestCandidates(t,r);if(!o){return}const s=rankCandidatesForTest(e,o).slice(0,a);if(s.length===0){return}e.line??(e.line=s[0].line);e.snippet=formatCandidateSnippet(s,n)})))}function getCachedJsTestCandidates(e,t){let n=t.get(e);if(!n){n=(0,o.readFile)(e,"utf-8").then((e=>scanJsTestCandidates(e))).catch((()=>undefined));t.set(e,n)}return n}function scanJsTestCandidates(e){const t=buildLineOffsets(e);const n=scanJsTestBlocks(e,t);const r=n.filter((e=>e.kind==="describe"));return n.filter((e=>e.kind==="test")).map((e=>{const t=r.filter((t=>t.startIndex<e.startIndex&&t.endIndex>=e.endIndex)).sort(((e,t)=>e.startIndex-t.startIndex)).map((e=>e.title));return{title:e.title,describeTitles:t,fullName:[...t,e.title].join(" ").trim(),line:e.startLine,snippet:e.snippet}}))}function scanJsTestBlocks(e,t){const n=[];c.lastIndex=0;let r;while((r=c.exec(e))!==null){if(isCommentedLine(e,r.index)){continue}const o=readQuotedTitle(e,r.index+r[0].length,r[3]);if(!o){continue}const i=skipWhitespaceAndFindComma(e,o.endIndex+1);if(i==null){continue}const s=r[1]==="describe"?"describe":"test";const a=r.index;const c=findCallbackOpenBrace(e,i+1);if(c==null){continue}const u=findMatchingBrace(e,c);if(u==null){continue}const l=findSnippetEndIndex(e,u);const p=e.slice(a,l).trim();if(!p){continue}n.push({kind:s,title:o.value.trim(),startIndex:a,endIndex:l,startLine:getLineNumber(t,a),snippet:p})}return n}function rankCandidatesForTest(e,t){const n=normalizeWhitespace(e.name);const r=normalizeDescribeChain(e.suite);const o=t.filter((e=>normalizeWhitespace(e.fullName)===n));if(o.length>0){return o}if(r){const e=t.filter((e=>normalizeWhitespace(e.title)===n&&normalizeWhitespace(e.describeTitles.join(" "))===r));if(e.length>0){return e}}const i=t.filter((e=>normalizeWhitespace(e.title)===n));if(i.length>0){return i}return t.filter((e=>{const t=normalizeWhitespace(e.title);if(!n.endsWith(t)){return false}const r=n.slice(0,n.length-t.length).trim();return r.length>0&&normalizeWhitespace(e.describeTitles.join(" "))===r}))}function formatCandidateSnippet(e,t){const n=e.length===1?e[0].snippet:e.map(((e,t)=>`Candidate ${t+1} (line ${e.line})\n${e.snippet}`)).join("\n\n");const r=n.split(/\r?\n/).slice(0,t.maxLines).join("\n").trim();if(r.length<=t.maxChars){return r}return r.slice(0,t.maxChars)}function findCallbackOpenBrace(e,t){for(let n=t;n<e.length;n++){const t=e[n];if(t==="'"||t==='"'||t==="`"){n=skipQuotedString(e,n);continue}if(t==="/"&&e[n+1]==="/"){n=skipLineComment(e,n+2);continue}if(t==="/"&&e[n+1]==="*"){n=skipBlockComment(e,n+2);continue}if(e.startsWith("=>",n)){return findNextBlockBrace(e,n+2)}if(startsWithWord(e,n,"function")){return findNextBlockBrace(e,n+"function".length)}if(t===";"){return undefined}}return undefined}function findNextBlockBrace(e,t){for(let n=t;n<e.length;n++){const t=e[n];if(t==="'"||t==='"'||t==="`"){n=skipQuotedString(e,n);continue}if(t==="{"){return n}if(t==="\n"&&looksLikeLineTerminator(e,n+1)){return undefined}}return undefined}function findMatchingBrace(e,t){let n=0;for(let r=t;r<e.length;r++){const t=e[r];if(t==="'"||t==='"'||t==="`"){r=skipQuotedString(e,r);continue}if(t==="/"&&e[r+1]==="/"){r=skipLineComment(e,r+2);continue}if(t==="/"&&e[r+1]==="*"){r=skipBlockComment(e,r+2);continue}if(t==="{"){n++;continue}if(t==="}"){n--;if(n===0){return r}}}return undefined}function readQuotedTitle(e,t,n){let r="";for(let o=t;o<e.length;o++){const t=e[o];if(t==="\\"){r+=t;if(o+1<e.length){o++;r+=e[o]}continue}if(t===n){return{value:r,endIndex:o}}if(t==="\n"&&n!=="`"){return undefined}r+=t}return undefined}function skipWhitespaceAndFindComma(e,t){for(let n=t;n<e.length;n++){const t=e[n];if(/\s/.test(t)){continue}return t===","?n:undefined}return undefined}function findSnippetEndIndex(e,t){let n=t+1;while(n<e.length){const t=e[n];if(t==="\r"||t==="\n"){return n}n++}return e.length}function skipQuotedString(e,t){const n=e[t];for(let r=t+1;r<e.length;r++){const t=e[r];if(t==="\\"){r++;continue}if(t===n){return r}}return e.length-1}function skipLineComment(e,t){for(let n=t;n<e.length;n++){if(e[n]==="\n"){return n}}return e.length-1}function skipBlockComment(e,t){for(let n=t;n<e.length-1;n++){if(e[n]==="*"&&e[n+1]==="/"){return n+1}}return e.length-1}function looksLikeLineTerminator(e,t){for(let n=t;n<e.length;n++){const t=e[n];if(!/\s/.test(t)){return t!=="."&&t!=="("}}return false}function buildLineOffsets(e){const t=[0];for(let n=0;n<e.length;n++){if(e[n]==="\n"){t.push(n+1)}}return t}function getLineNumber(e,t){let n=0;for(let r=0;r<e.length;r++){if(e[r]>t){break}n=r}return n+1}function isCommentedLine(e,t){const n=e.lastIndexOf("\n",t-1)+1;const r=e.slice(n,t).trimStart();return r.startsWith("//")||r.startsWith("*")}function startsWithWord(e,t,n){if(!e.startsWith(n,t)){return false}const r=t===0?"":e[t-1];const o=e[t+n.length]??"";return!isIdentifierChar(r)&&!isIdentifierChar(o)}function isIdentifierChar(e){return/[A-Za-z0-9_$]/.test(e)}function normalizeWhitespace(e){return(e??"").trim().replace(/\s+/g," ")}function normalizeDescribeChain(e){if(!e){return""}const t=e.split(">").map((e=>e.trim())).filter(Boolean);if(t.length===0){return""}const n=t[0].includes(".");const r=n?t.slice(1):t;return normalizeWhitespace(r.join(" "))}function isInsideSourceRoot(e,t){const n=i.default.relative(t,e);return n.length>0&&!n.startsWith("..")&&!i.default.isAbsolute(n)}},304:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.resolveSourceSnippetOptions=resolveSourceSnippetOptions;t.enrichReportWithSourceSnippets=enrichReportWithSourceSnippets;const o=n(455);const i=r(n(760));const s=2e3;const a=40;function resolveSourceSnippetOptions(e){return{sourceRoot:i.default.resolve(e?.sourceRoot??process.cwd()),maxChars:normalizePositiveInt(e?.maxChars,s),maxLines:normalizePositiveInt(e?.maxLines,a)}}async function enrichReportWithSourceSnippets(e,t){const n=resolveSourceSnippetOptions(t);const r=new Map;await Promise.all(e.results.tests.map((async e=>{if(e.snippet||!e.filePath||!e.line){return}const t=await tryExtractSnippet(e,n,r);if(t){e.snippet=t}})))}async function tryExtractSnippet(e,t,n){const r=i.default.resolve(t.sourceRoot,e.filePath);if(!isInsideSourceRoot(r,t.sourceRoot)){return undefined}const o=await getCachedFileContent(r,n);if(!o){return undefined}return extractEnclosingTestBlock(o,e.line,t)}function getCachedFileContent(e,t){let n=t.get(e);if(!n){n=(0,o.readFile)(e,"utf-8").catch((()=>undefined));t.set(e,n)}return n}function extractEnclosingTestBlock(e,t,n){const r=e.split(/\r?\n/);const o=Math.max(0,Math.min(r.length-1,t-1));const i=findTestStart(r,o);if(i==null){return undefined}const s=[];let a=0;let c=false;for(let e=i;e<r.length&&s.length<n.maxLines;e++){const t=r[e];s.push(t);for(const e of t){if(e==="{"){a++;c=true}else if(e==="}"){a--}}if(c&&a<=0&&isJsTestBlockTerminator(t)){break}}const u=s.join("\n").trim();if(!u){return undefined}return u.length<=n.maxChars?u:u.slice(0,n.maxChars)}function findTestStart(e,t){for(let n=t;n>=0;n--){if(/\b(?:test|it)(?:\.(?:only|skip|fixme|slow))?\s*\(/.test(e[n])){return n}}return undefined}function isJsTestBlockTerminator(e){let t=e.length-1;while(t>=0&&/\s/.test(e[t])){t--}if(t>=0&&e[t]===";"){t--}while(t>=0&&/\s/.test(e[t])){t--}if(t<1||e[t]!==")"){return false}t--;while(t>=0&&/\s/.test(e[t])){t--}return t>=0&&e[t]==="}"}function isInsideSourceRoot(e,t){const n=i.default.relative(t,e);return n.length>0&&!n.startsWith("..")&&!i.default.isAbsolute(n)}function normalizePositiveInt(e,t){return typeof e==="number"&&Number.isInteger(e)&&e>0?e:t}},293:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.createTestLocationCollector=createTestLocationCollector;function createTestLocationCollector(){const e=new Map;return{add(t,n){if(!t||!isValidLocation(n)){return}const r=e.get(t)??[];r.push(n);e.set(t,r)},applyToReport(t){const n=new Map([...e.entries()].map((([e,t])=>[e,[...t]])));for(const e of t.results.tests){const t=n.get(e.name);const r=t?.shift();if(!r){continue}e.filePath??(e.filePath=r.filePath);e.line??(e.line=r.line)}}}}function isValidLocation(e){return Boolean(e.filePath)&&Number.isInteger(e.line)&&e.line>0}},969:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:true});t.ensureReportId=ensureReportId;t.uploadTestRun=uploadTestRun;t.mapAttachmentsToTestResults=mapAttachmentsToTestResults;t.uploadArtifacts=uploadArtifacts;const r=n(406);const o=n(304);const i="https://api.testream.app";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 n=getEnvApiUrl();if(n){return n}return i}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}let n=t;while(n.endsWith("/")){n=n.slice(0,-1)}return n}function isReportAlreadyExistsConflict(e){return e.errorCode==="report_already_exists"}function formatUploadError(e){const t=e.detail||e.title||e.rawBody||"Unknown error";const n=e.errorCode?`, code=${e.errorCode}`:"";return`Upload failed (HTTP ${e.statusCode}${n}): ${t}`}async function parseHttpError(e){const t=await e.text();const n=e.headers.get("content-type")||"";if(n.toLowerCase().includes("json")&&t.trim().length>0){try{const n=JSON.parse(t);return{statusCode:e.status,title:n.title,detail:n.detail||n.details||n.error,errorCode:n.errorCode,rawBody:t}}catch{}}return{statusCode:e.status,rawBody:t}}function resolveUploadContext(e){const t=(0,r.detectCIContext)();return{commitSha:e.commitSha??t.commitSha,branch:e.branch??t.branch,repositoryUrl:e.repositoryUrl??t.repositoryUrl,buildName:e.buildName,buildNumber:e.buildNumber??t.buildNumber,buildUrl:e.buildUrl??t.buildUrl,testEnvironment:e.testEnvironment,appName:e.appName,appVersion:e.appVersion,testType:e.testType}}async function uploadTestRun(e){const{report:t,apiKey:n}=e;const r=resolveApiUrl(e.apiUrl);const i=ensureReportId(t);const s=resolveUploadContext(e);await(0,o.enrichReportWithSourceSnippets)(t);const a={report:t,reportId:i,...s};console.log("Uploading test results...");let c;try{c=await fetch(`${r}/api/v1/ingest`,{method:"POST",headers:{"X-API-KEY":n,"Content-Type":"application/json"},body:JSON.stringify(a)})}catch(e){const t=e instanceof Error?e.message:String(e);const n=`Connection failed: ${t}`;console.error(n);return{success:false,reportId:i,error:n}}if(!c.ok){const e=await parseHttpError(c);if(c.status===409&&isReportAlreadyExistsConflict(e)){console.warn("Report already exists (workflow may have been re-run)");return{success:true,reportId:i,summary:{passed:t.results.summary.passed,failed:t.results.summary.failed,skipped:t.results.summary.skipped,total:t.results.summary.tests},alreadyExists:true,statusCode:e.statusCode,errorCode:e.errorCode}}const n=formatUploadError(e);console.error(n);return{success:false,reportId:i,error:n,statusCode:e.statusCode,errorCode:e.errorCode,errorDetail:e.detail||e.rawBody}}const u=await c.json();console.log(`✓ Test results uploaded successfully`);console.log(` Report ID: ${u.reportId}`);console.log(` Test Run ID: ${u.testRunId}`);console.log(` Tests: ${u.summary.passed}/${u.summary.total} passed`);return{success:true,reportId:u.reportId,testRunId:u.testRunId,summary:{passed:u.summary.passed,failed:u.summary.failed,skipped:u.summary.skipped,total:u.summary.total},testResults:u.testResults}}function mapAttachmentsToTestResults(e,t){const n=new Map;for(const e of t){const t=n.get(e.name)??[];t.push(e.id);n.set(e.name,t)}const r=[];for(const t of e){const e=n.get(t.name);const o=e?.shift();if(!t.attachments||t.attachments.length===0){continue}if(!o){console.warn(`Skipping artifact upload: could not match test result for "${t.name}"`);continue}r.push({testResultId:o,attachments:t.attachments})}return r}async function uploadArtifacts(e){const{reportId:t,apiKey:n,testResults:r}=e;const o=resolveApiUrl(e.apiUrl);let i=0;for(const e of r){for(const r of e.attachments){try{const s=await uploadSingleArtifact({testResultId:e.testResultId,attachment:r,reportId:t,apiKey:n,apiUrl:o});if(s)i++}catch(e){const t=e instanceof Error?e.message:String(e);console.error(`Failed to upload artifact ${r.name}: ${t}`)}}}if(i>0){console.log(`✓ Uploaded ${i} artifact(s)`)}return i}async function uploadSingleArtifact(e){const{testResultId:t,attachment:r,reportId:o,apiKey:i,apiUrl:s}=e;const a=await Promise.resolve().then(n.t.bind(n,455,23));const c=await Promise.resolve().then(n.t.bind(n,760,23));const u=c.resolve(r.path);try{await a.access(u)}catch{console.warn(`Artifact not found: ${c.basename(u)}`);return false}let l;let p;const d=c.basename(u);const f=r.contentType||"application/octet-stream";try{l=await a.readFile(u);p=l.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:o,apiKey:i,apiUrl:s,attachmentName:r.name,fileName:d,contentType:f,sizeBytes:p,fileBuffer:l})}catch(e){if(!(e instanceof LegacyUploadRequiredError)){throw e}return await uploadWithLegacyEndpoint({testResultId:t,attachmentName:r.name,reportId:o,apiKey:i,apiUrl:s,fileName:d,contentType:f,fileBuffer:l})}}class LegacyUploadRequiredError extends Error{constructor(){super("Legacy upload endpoint required")}}async function uploadWithPresignedUrl(e){const{testResultId:t,reportId:n,apiKey:r,apiUrl:o,attachmentName:i,fileName:s,contentType:a,sizeBytes:c,fileBuffer:u}=e;const l={testResultId:t,ctrfAttachmentName:i,fileName:s,contentType:a,sizeBytes:c};const p=await fetch(`${o}/api/v1/artifacts/${n}/upload-url`,{method:"POST",headers:{"X-API-KEY":r,"Content-Type":"application/json"},body:JSON.stringify(l)});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"]=a}const h=await fetch(d.uploadUrl,{method:"PUT",headers:f,body:u});if(!h.ok){const e=await h.text();throw new Error(`Direct artifact upload failed (HTTP ${h.status}): ${e}`)}const m={testResultId:t,ctrfAttachmentName:i,fileName:s,contentType:a,sizeBytes:c,storageKey:d.storageKey};const y=await fetch(`${o}/api/v1/artifacts/${n}/complete`,{method:"POST",headers:{"X-API-KEY":r,"Content-Type":"application/json"},body:JSON.stringify(m)});if(!y.ok){const e=await y.text();throw new Error(`Failed to finalize artifact upload (HTTP ${y.status}): ${e}`)}return true}async function uploadWithLegacyEndpoint(e){const{testResultId:t,attachmentName:n,reportId:r,apiKey:o,apiUrl:i,fileName:s,contentType:a,fileBuffer:c}=e;const u=new Blob([new Uint8Array(c)],{type:a});const l=new FormData;l.append("testResultId",t);l.append("ctrfAttachmentName",n);l.append("file",u,s);const p=await fetch(`${i}/api/v1/artifacts/${r}`,{method:"POST",headers:{"X-API-KEY":o},body:l});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 n=t.toLowerCase();return Object.keys(e).some((e=>e.toLowerCase()===n))}},18:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){if(r===undefined)r=n;var o=Object.getOwnPropertyDescriptor(t,n);if(!o||("get"in o?!t.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return t[n]}}}Object.defineProperty(e,r,o)}:function(e,t,n,r){if(r===undefined)r=n;e[r]=t[n]});var o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}:function(e,t){e["default"]=t});var i=this&&this.__importStar||function(){var ownKeys=function(e){ownKeys=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n))t[t.length]=n;return t};return ownKeys(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var n=ownKeys(e),i=0;i<n.length;i++)if(n[i]!=="default")r(t,e,n[i]);o(t,e);return t}}();Object.defineProperty(t,"__esModule",{value:true});const s=n(317);const a=i(n(928));const c=i(n(943));const u=n(617);const l=n(322);const p=n(148);const d="junit/pytest-junit.xml";function printUsage(){console.log(`\n@testream/pytest-reporter - Run pytest and upload results to Testream\n\nUsage:\n npx @testream/pytest-reporter --api-key <key> [options]\n\nRequired arguments (unless --no-upload):\n --api-key, -k API key for authentication\n\nOptional arguments:\n --project <path> Path to pytest project (defaults to current directory)\n --python <path> Python executable (default: python, fallback: python3)\n --junit-path <path> Skip running pytest, ingest existing JUnit XML path/glob\n\nGit context (auto-detected in CI):\n --branch Git branch name\n --commit-sha Git commit SHA\n --repository-url Git repository URL\n\nBuild metadata (auto-detected in CI):\n --build-name Build name/identifier\n --build-number Build number\n --build-url Build URL (CI pipeline URL)\n\nEnvironment metadata:\n --test-environment Test environment (e.g., staging, production, ci)\n --app-name Application name under test\n --app-version Application version under test\n --test-type Test type (e.g., unit, integration, e2e)\n\nUpload options:\n --no-upload Skip uploading CTRF results\n --fail-on-error Fail process if upload fails\n\nOther:\n --help, -h Show help message\n -- <args> Additional arguments to pass to pytest\n\nExamples:\n # Run pytest and upload\n npx @testream/pytest-reporter -k your-api-key\n\n # Ingest existing JUnit XML without running pytest\n npx @testream/pytest-reporter -k your-api-key --junit-path ./reports/junit.xml\n`)}function parseArgs(e){const t={uploadEnabled:true,failOnUploadError:false,continueOnTestFailure:true};const n=[];let r=false;for(let o=0;o<e.length;o++){const i=e[o];const s=e[o+1];if(i==="--"){r=true;continue}if(r){n.push(i);continue}switch(i){case"--help":case"-h":printUsage();process.exit(0);break;case"--api-key":case"-k":t.apiKey=s;o++;break;case"--project":t.projectPath=s;o++;break;case"--python":t.pythonExecutable=s;o++;break;case"--junit-path":t.junitPath=s;o++;break;case"--branch":t.branch=s;o++;break;case"--commit-sha":t.commitSha=s;o++;break;case"--repository-url":t.repositoryUrl=s;o++;break;case"--build-name":t.buildName=s;o++;break;case"--build-number":t.buildNumber=s;o++;break;case"--build-url":t.buildUrl=s;o++;break;case"--test-environment":t.testEnvironment=s;o++;break;case"--app-name":t.appName=s;o++;break;case"--app-version":t.appVersion=s;o++;break;case"--test-type":t.testType=s;o++;break;case"--no-upload":t.uploadEnabled=false;break;case"--fail-on-error":t.failOnUploadError=true;break;default:if(i.startsWith("-")){console.error(`Unknown argument: ${i}`);return null}break}}if(n.length>0){t.pytestArgs=n}if(!t.apiKey&&t.uploadEnabled){console.error("Error: Missing required argument: --api-key (unless --no-upload is set)");return null}return t}function isPytestTestFailureCode(e){return e===1}async function runPytest(e){const t=e.projectPath?a.resolve(e.projectPath):process.cwd();const n=a.resolve(t,d);await c.mkdir(a.dirname(n),{recursive:true});const r=["-m","pytest","--junitxml",n,"-o","junit_family=xunit1",...e.pytestArgs??[]];let o;if(e.pythonExecutable){o=[e.pythonExecutable]}else if(process.platform==="win32"){o=["python","py"]}else{o=["python","python3"]}let i=null;for(const a of o){console.info(`Running pytest with ${a}...`);console.info(` Command: ${a} -m pytest --junitxml ${n}`);if((e.pytestArgs??[]).length>0){console.info(` Additional pytest args: ${(e.pytestArgs??[]).length} provided`)}console.info(` Working directory: ${t}`);const o=await new Promise(((e,n)=>{const o=(0,s.spawn)(a,r,{cwd:t,stdio:["inherit","inherit","inherit"],shell:false});o.on("error",(e=>{n(e)}));o.on("close",(t=>{e(t)}))})).catch((e=>{i=e;return null}));if(i&&o===null){continue}try{await c.access(n)}catch{throw new Error(`Pytest run completed but no JUnit XML was found at: ${n}`)}return{junitPath:n,exitCode:o}}const u=i instanceof Error?i.message:"Failed to start python executable for pytest";throw new Error(u)}async function main(){const e=process.argv.slice(2);if(e.length===0){printUsage();process.exit(1)}const t=parseArgs(e);if(!t){process.exit(1)}let n=false;let r=0;try{let e=t.junitPath;if(e){console.info("Using existing JUnit XML file(s), tests will not be re-run...")}else{const o=await runPytest(t);e=o.junitPath;r=o.exitCode;if(o.exitCode&&o.exitCode!==0){n=isPytestTestFailureCode(o.exitCode);if(!n){throw new Error(`pytest exited with code ${o.exitCode}`)}}}const o=await performConversion(e,t.projectPath);const i=await performUploadIfEnabled(t,o);console.info("Test run ingestion completed");console.info(` JUnit source files: ${o.sourceFiles.length}`);console.info(` CTRF output: ${o.reportPath}`);console.info(` Tests: ${o.report.results.summary.tests}`);console.info(` Passed: ${o.report.results.summary.passed}`);console.info(` Failed: ${o.report.results.summary.failed}`);console.info(` Skipped: ${o.report.results.summary.skipped}`);if(!i)console.info("Upload disabled, skipping upload");if(n)process.exit(r||1)}catch(e){const t=e instanceof Error?e.message:String(e);console.error(`Error: ${t}`);process.exit(1)}}async function performConversion(e,t){const n=await(0,u.convertJUnitToCtrf)({junitPath:e});const r=a.resolve(t??process.cwd());const o=(0,l.createTestLocationCollector)();for(const e of n.sourceFiles){const t=await c.readFile(e,"utf-8");const n=(0,u.extractJUnitTestLocations)(t);(0,u.addJUnitTestLocationsToCollector)(o,n,{zeroBasedLineNumbers:true})}o.applyToReport(n.report);await(0,l.enrichReportWithSourceSnippets)(n.report,{sourceRoot:r});await(0,p.enrichReportWithPytestSourceSnippets)(n.report,{sourceRoot:r});n.report.results.tool.name="pytest";n.report.generatedBy="@testream/pytest-reporter";await c.writeFile(n.reportPath,JSON.stringify(n.report,null,2));return n}async function performUploadIfEnabled(e,t){const n=e.uploadEnabled!==false;if(!n||!e.apiKey)return undefined;const r=await(0,u.uploadCtrfReport)({report:t.report,apiKey:e.apiKey,apiUrl:e.apiUrl,branch:e.branch,commitSha:e.commitSha,repositoryUrl:e.repositoryUrl,buildName:e.buildName,buildNumber:e.buildNumber,buildUrl:e.buildUrl,testEnvironment:e.testEnvironment,appName:e.appName,appVersion:e.appVersion,testType:e.testType});if(!r.success&&e.failOnUploadError){throw new Error(r.error||"Upload failed")}return r}main()},148:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.enrichReportWithPytestSourceSnippets=enrichReportWithPytestSourceSnippets;const o=n(455);const i=r(n(760));const s=n(322);async function enrichReportWithPytestSourceSnippets(e,t){const n=(0,s.resolveSourceSnippetOptions)(t);await Promise.all(e.results.tests.map((async e=>{const{filePath:t,line:r}=e;if(e.snippet||!t||!r||!isPythonFile(t)){return}const o=await tryExtractPythonSnippet(t,r,n.sourceRoot,n.maxChars,n.maxLines);if(o){e.snippet=o}})))}async function tryExtractPythonSnippet(e,t,n,r,s){const a=i.default.resolve(n,e);if(!isInsideSourceRoot(a,n)){return undefined}let c;try{c=await(0,o.readFile)(a,"utf-8")}catch{return undefined}return extractPythonTestBlock(c,t,r,s)}function extractPythonTestBlock(e,t,n,r){const o=e.split(/\r?\n/);const i=Math.max(0,Math.min(o.length-1,t-1));const s=findPythonTestStart(o,i);if(s==null){return undefined}const a=includePythonDecorators(o,s);const c=getIndentWidth(o[s]);const u=[];for(let e=a;e<o.length&&u.length<r;e++){const t=o[e];if(e>s&&isPythonBlockBoundary(t,c)){break}u.push(t)}const l=u.join("\n").trim();if(!l){return undefined}return l.length<=n?l:l.slice(0,n)}function findPythonTestStart(e,t){for(let n=t;n>=0;n--){if(/^\s*@/.test(e[n])){const t=findDecoratedPythonTestDefinition(e,n);if(t!=null){return t}}if(isPythonTestDefinition(e[n])){return n}}return undefined}function findDecoratedPythonTestDefinition(e,t){for(let n=t+1;n<e.length;n++){if(/^\s*$/.test(e[n])||/^\s*@/.test(e[n])){continue}return isPythonTestDefinition(e[n])?n:undefined}return undefined}function isPythonTestDefinition(e){return/^\s*(?:async\s+)?def\s+test_\w*\s*\(/.test(e)}function includePythonDecorators(e,t){let n=t;while(n>0&&/^\s*@/.test(e[n-1])){n--}return n}function isPythonBlockBoundary(e,t){if(/^\s*$/.test(e)){return false}return getIndentWidth(e)<=t}function getIndentWidth(e){const t=/^\s*/.exec(e);return t?.[0].length??0}function isInsideSourceRoot(e,t){const n=i.default.relative(t,e);return n.length>0&&!n.startsWith("..")&&!i.default.isAbsolute(n)}function isPythonFile(e){return i.default.extname(e).toLowerCase()===".py"}},617:e=>{e.exports=require("@testream/junit-reporter")},317:e=>{e.exports=require("child_process")},943:e=>{e.exports=require("fs/promises")},455:e=>{e.exports=require("node:fs/promises")},760:e=>{e.exports=require("node:path")},928:e=>{e.exports=require("path")}};var t={};function __nccwpck_require__(n){var r=t[n];if(r!==undefined){return r.exports}var o=t[n]={exports:{}};var i=true;try{e[n].call(o.exports,o,o.exports,__nccwpck_require__);i=false}finally{if(i)delete t[n]}return o.exports}(()=>{var e=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;var t;__nccwpck_require__.t=function(n,r){if(r&1)n=this(n);if(r&8)return n;if(typeof n==="object"&&n){if(r&4&&n.__esModule)return n;if(r&16&&typeof n.then==="function")return n}var o=Object.create(null);__nccwpck_require__.r(o);var i={};t=t||[null,e({}),e([]),e(e)];for(var s=r&2&&n;typeof s=="object"&&!~t.indexOf(s);s=e(s)){Object.getOwnPropertyNames(s).forEach((e=>i[e]=()=>n[e]))}i["default"]=()=>n;__nccwpck_require__.d(o,i);return o}})();(()=>{__nccwpck_require__.d=(e,t)=>{for(var n in t){if(__nccwpck_require__.o(t,n)&&!__nccwpck_require__.o(e,n)){Object.defineProperty(e,n,{enumerable:true,get:t[n]})}}}})();(()=>{__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 n=__nccwpck_require__(18);module.exports=n})();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CTRFReport, SourceSnippetOptions } from "@jira-test-manager/shared-types";
|
|
2
|
+
/**
|
|
3
|
+
* pytest snippet extraction is Python-specific, so it stays local to this package.
|
|
4
|
+
* Shared snippet enrichment only handles the generic JS/TS-style test block format.
|
|
5
|
+
*/
|
|
6
|
+
export declare function enrichReportWithPytestSourceSnippets(report: CTRFReport, options?: SourceSnippetOptions): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=source-snippets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-snippets.d.ts","sourceRoot":"","sources":["../src/source-snippets.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACrB,MAAM,iCAAiC,CAAC;AAGzC;;;GAGG;AACH,wBAAsB,oCAAoC,CACxD,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JUnitReporterOptions } from
|
|
1
|
+
import type { JUnitReporterOptions } from "@testream/junit-reporter";
|
|
2
2
|
export interface PytestReporterOptions extends JUnitReporterOptions {
|
|
3
3
|
/** Path to the pytest project directory. Defaults to current directory */
|
|
4
4
|
projectPath?: string;
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testream/pytest-reporter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "CLI to run pytest with JUnit XML output and upload results to Testream",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "npm run build:bundle && npm run build:types",
|
|
8
|
+
"build": "npm run clean && npm run build:bundle && npm run build:types",
|
|
9
9
|
"build:bundle": "ncc build src/index.ts -o dist --minify -e @testream/junit-reporter",
|
|
10
10
|
"build:types": "tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
11
|
+
"clean": "rm -rf dist",
|
|
11
12
|
"format": "prettier --write '**/*.ts'",
|
|
12
13
|
"lint": "eslint src/**/*.ts",
|
|
13
|
-
"test": "
|
|
14
|
+
"test": "vitest run",
|
|
14
15
|
"all": "npm run format && npm run lint && npm run build",
|
|
15
16
|
"prepublishOnly": "npm run build"
|
|
16
17
|
},
|
|
@@ -53,7 +54,8 @@
|
|
|
53
54
|
"@vercel/ncc": "^0.38.1",
|
|
54
55
|
"eslint": "^8.56.0",
|
|
55
56
|
"prettier": "^3.1.1",
|
|
56
|
-
"typescript": "^5.3.3"
|
|
57
|
+
"typescript": "^5.3.3",
|
|
58
|
+
"vitest": "^4.1.9"
|
|
57
59
|
},
|
|
58
60
|
"engines": {
|
|
59
61
|
"node": ">=20.0.0"
|