diff --git a/dist/index.js b/dist/index.js index 8664ef2..3ec2f46 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,2 +1 @@ -#!/usr/bin/env node -(()=>{"use strict";var e={866:function(e,t,s){var r=this&&this.__awaiter||function(e,t,s,r){function adopt(e){return e instanceof s?e:new s((function(t){t(e)}))}return new(s||(s=Promise))((function(s,o){function fulfilled(e){try{step(r.next(e))}catch(e){o(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){o(e)}}function step(e){e.done?s(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:true});const o=s(317);const n=s(404);const u=s(834);const l="@devicecloud.dev/dcd";const executeCommand=(e,t=true)=>new Promise(((s,r)=>{let n="";const u=(0,o.spawn)(e,{shell:true});u.stdout.on("data",(e=>{const s=e.toString();n+=s;if(t){process.stderr.write(s)}}));u.stderr.on("data",(e=>{const s=e.toString();n+=s;if(t){process.stderr.write(s)}}));u.on("close",(e=>{s({output:n,exitCode:e!==null&&e!==void 0?e:0})}));u.on("error",(e=>{r(e)}))}));const getLatestDcdVersion=e=>r(void 0,void 0,void 0,(function*(){try{if(e){console.error("Using beta version of DCD CLI");return`${l}@beta`}const{output:t}=yield executeCommand(`npm view ${l} version`,false);const s=t.trim();console.error(`Latest DCD version from npm: ${s}`);return`${l}@${s}`}catch(e){console.error("Failed to fetch latest DCD version, falling back to >=5.0.0:",e);return`${l}@>=5.0.0`}}));const getTestStatus=(e,t,s,o)=>r(void 0,void 0,void 0,(function*(){try{let r=`npx --yes "${s}" status --json --upload-id ${e} --api-key ${(0,u.escapeShellValue)(t)}`;if(o){r+=` --api-url ${(0,u.escapeShellValue)(o)}`}const{output:n}=yield executeCommand(r,false);const l=n.split("\n");const i=l.findIndex((e=>{const t=e.trim();return t.startsWith("{")||t.startsWith("[")}));if(i===-1){throw new Error("No JSON found in status output")}const a=l.slice(i).join("\n");return JSON.parse(a)}catch(e){console.error("Failed to get test status:",e);return null}}));const run=()=>r(void 0,void 0,void 0,(function*(){var e;try{const t=(0,n.getEnv)();const r=yield getLatestDcdVersion(t.useBeta);const o=[`--apiKey ${(0,u.escapeShellValue)(t.apiKey)}`,"--quiet"];if(t.apiUrl){o.push(`--api-url ${(0,u.escapeShellValue)(t.apiUrl)}`)}if(t.buildUrl){o.push(`--app-url ${(0,u.escapeShellValue)(t.buildUrl)}`)}for(const e of t.metadata){o.push(`--metadata ${(0,u.escapeShellValue)(e)}`)}const l=process.argv.slice(2).map(u.escapeShellValue).join(" ");const i=`npx --yes "${r}" cloud ${o.join(" ")} ${l}`.trim();process.env.DCD_CI_PROVIDER="eas";try{process.env.DCD_CI_WRAPPER_VERSION=s(330).rE}catch(e){}let a="";let c=null;try{const{output:e,exitCode:t}=yield executeCommand(i);a=e;if(t===1){throw new Error("DeviceCloud CLI failed to run - check your parameters or contact support")}}finally{c=((e=a===null||a===void 0?void 0:a.match(/https:\/\/(?:dev\.)?console\.devicecloud\.dev\/results\?upload=([a-zA-Z0-9-]+)/))===null||e===void 0?void 0:e[1])||null}if(!c){throw new Error("Failed to get upload ID from console URL")}const p=yield getTestStatus(c,t.apiKey,r,t.apiUrl);if(p){(0,u.setOutput)("console_url",p.consoleUrl||"");(0,u.setOutput)("app_binary_id",p.appBinaryId||"");(0,u.setOutput)("upload_status",p.status||"PENDING");const e=(p.tests||[]).map((e=>({name:e.name,status:e.status})));(0,u.setOutput)("flow_results",JSON.stringify(e));if(p.status==="PASSED"){console.error("Successfully completed test run.");process.exit(0)}else if(p.status==="FAILED"||p.status==="CANCELLED"){console.error(`Test run ${p.status}. Check flow results: ${p.consoleUrl}`);process.exit(1)}else{console.error(`Test run finished with status: ${p.status}`);process.exit(0)}}else{(0,u.setOutput)("upload_status","ERROR");(0,u.setOutput)("flow_results","[]");throw new Error("Failed to get test status")}}catch(e){const t=typeof e==="string"?e:e.message;console.error(t);process.exit(1)}}));run()},404:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.getEnv=getEnv;const s=[["DCD_EAS_BUILD_ID","eas_build_id"],["DCD_EAS_PLATFORM","eas_platform"],["DCD_EAS_PROFILE","eas_profile"],["DCD_EAS_APP_VERSION","eas_app_version"],["DCD_GH_SHA","gh_sha"],["DCD_GH_BRANCH","gh_branch"],["DCD_GH_PR_NUMBER","gh_pr_number"],["DCD_GH_PR_URL","gh_pr_url"],["DCD_GH_REPO","gh_repo"],["DCD_GH_RUN_ID","gh_run_id"]];function getEnv(){const e=process.env.DEVICE_CLOUD_API_KEY;if(!e){throw new Error("DEVICE_CLOUD_API_KEY is not set. Pass it via the step `env:` block: "+"DEVICE_CLOUD_API_KEY: ${{ secrets.DEVICE_CLOUD_API_KEY }}")}const t=[];for(const[e,r]of s){const s=process.env[e];if(s){t.push(`${r}=${s}`)}}return{apiKey:e,apiUrl:process.env.DEVICE_CLOUD_API_URL||undefined,buildUrl:process.env.DCD_EAS_BUILD_URL||undefined,useBeta:process.env.DCD_USE_BETA==="true",metadata:t}}},834:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.setOutput=setOutput;t.escapeShellValue=escapeShellValue;function setOutput(e,t){const s=t.replace(/\r?\n/g," ");process.stdout.write(`set-output ${e} ${s}\n`)}function escapeShellValue(e){return e.replace(/(["\\'$`!\s\[\]{}()&|;<>*?#^~])/g,"\\$1")}},317:e=>{e.exports=require("child_process")},330:e=>{e.exports={rE:"1.1.0"}}};var t={};function __nccwpck_require__(s){var r=t[s];if(r!==undefined){return r.exports}var o=t[s]={exports:{}};var n=true;try{e[s].call(o.exports,o,o.exports,__nccwpck_require__);n=false}finally{if(n)delete t[s]}return o.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var s=__nccwpck_require__(866);module.exports=s})(); \ No newline at end of file +#!/usr/bin/env node (()=>{"use strict";var e={866:function(e,t,s){var r=this&&this.__awaiter||function(e,t,s,r){function adopt(e){return e instanceof s?e:new s((function(t){t(e)}))}return new(s||(s=Promise))((function(s,o){function fulfilled(e){try{step(r.next(e))}catch(e){o(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){o(e)}}function step(e){e.done?s(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:true});const o=s(317);const n=s(404);const u=s(834);const l="@devicecloud.dev/dcd";const executeCommand=(e,t=true)=>new Promise(((s,r)=>{let n="";const u=(0,o.spawn)(e,{shell:true});u.stdout.on("data",(e=>{const s=e.toString();n+=s;if(t){process.stderr.write(s)}}));u.stderr.on("data",(e=>{const s=e.toString();n+=s;if(t){process.stderr.write(s)}}));u.on("close",(e=>{s({output:n,exitCode:e!==null&&e!==void 0?e:0})}));u.on("error",(e=>{r(e)}))}));const getLatestDcdVersion=e=>r(void 0,void 0,void 0,(function*(){try{if(e){console.error("Using beta version of DCD CLI");return`${l}@beta`}const{output:t}=yield executeCommand(`npm view ${l} version`,false);const s=t.trim();console.error(`Latest DCD version from npm: ${s}`);return`${l}@${s}`}catch(e){console.error("Failed to fetch latest DCD version, falling back to >=5.0.0:",e);return`${l}@>=5.0.0`}}));const getTestStatus=(e,t,s,o)=>r(void 0,void 0,void 0,(function*(){try{let r=`npx --yes "${s}" status --json --upload-id ${e} --api-key ${(0,u.escapeShellValue)(t)}`;if(o){r+=` --api-url ${(0,u.escapeShellValue)(o)}`}const{output:n}=yield executeCommand(r,false);const l=n.split("\n");const a=l.findIndex((e=>{const t=e.trim();return t.startsWith("{")||t.startsWith("[")}));if(a===-1){throw new Error("No JSON found in status output")}const i=l.slice(a).join("\n");return JSON.parse(i)}catch(e){console.error("Failed to get test status:",e);return null}}));const run=()=>r(void 0,void 0,void 0,(function*(){var e;try{const t=(0,n.getEnv)();const r=yield getLatestDcdVersion(t.useBeta);const o=[`--apiKey ${(0,u.escapeShellValue)(t.apiKey)}`,"--quiet"];if(t.apiUrl){o.push(`--api-url ${(0,u.escapeShellValue)(t.apiUrl)}`)}if(t.buildUrl){o.push(`--app-url ${(0,u.escapeShellValue)(t.buildUrl)}`)}for(const e of t.metadata){o.push(`--metadata ${(0,u.escapeShellValue)(e)}`)}const l=process.argv.slice(2).map(u.escapeShellValue).join(" ");const a=`npx --yes "${r}" cloud ${o.join(" ")} ${l}`.trim();process.env.DCD_CI_PROVIDER="eas";try{process.env.DCD_CI_WRAPPER_VERSION=s(330).rE}catch(e){}let i="";let c=null;try{const{output:e,exitCode:t}=yield executeCommand(a);i=e;if(t===1){throw new Error("DeviceCloud CLI failed to run - check your parameters or contact support")}}finally{c=((e=i===null||i===void 0?void 0:i.match(/https:\/\/(?:dev\.)?console\.devicecloud\.dev\/results\?upload=([a-zA-Z0-9-]+)/))===null||e===void 0?void 0:e[1])||null}if(!c){throw new Error("Failed to get upload ID from console URL")}const p=yield getTestStatus(c,t.apiKey,r,t.apiUrl);if(p){(0,u.setOutput)("console_url",p.consoleUrl||"");(0,u.setOutput)("app_binary_id",p.appBinaryId||"");(0,u.setOutput)("upload_status",p.status||"PENDING");const e=(p.tests||[]).map((e=>({name:e.name,status:e.status})));(0,u.setOutput)("flow_results",JSON.stringify(e));if(p.status==="PASSED"){console.error("Successfully completed test run.");process.exit(0)}else if(p.status==="FAILED"||p.status==="CANCELLED"){console.error(`Test run ${p.status}. Check flow results: ${p.consoleUrl}`);process.exit(1)}else{console.error(`Test run finished with status: ${p.status}`);process.exit(0)}}else{(0,u.setOutput)("upload_status","ERROR");(0,u.setOutput)("flow_results","[]");throw new Error("Failed to get test status")}}catch(e){const t=typeof e==="string"?e:e.message;console.error(t);process.exit(1)}}));run()},404:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.getEnv=getEnv;const s=[["DCD_EAS_BUILD_ID","eas_build_id"],["DCD_EAS_PLATFORM","eas_platform"],["DCD_EAS_PROFILE","eas_profile"],["DCD_EAS_APP_VERSION","eas_app_version"],["DCD_GH_SHA","gh_sha"],["DCD_GH_BRANCH","gh_branch"],["DCD_GH_PR_NUMBER","gh_pr_number"],["DCD_GH_PR_URL","gh_pr_url"],["DCD_GH_REPO","gh_repo"],["DCD_GH_RUN_ID","gh_run_id"],["DCD_CHECK_NAME","gh_check_name"]];function getEnv(){const e=process.env.DEVICE_CLOUD_API_KEY;if(!e){throw new Error("DEVICE_CLOUD_API_KEY is not set. Pass it via the step `env:` block: "+"DEVICE_CLOUD_API_KEY: ${{ secrets.DEVICE_CLOUD_API_KEY }}")}const t=[];for(const[e,r]of s){const s=process.env[e];if(s){t.push(`${r}=${s}`)}}return{apiKey:e,apiUrl:process.env.DEVICE_CLOUD_API_URL||undefined,buildUrl:process.env.DCD_EAS_BUILD_URL||undefined,useBeta:process.env.DCD_USE_BETA==="true",metadata:t}}},834:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.setOutput=setOutput;t.escapeShellValue=escapeShellValue;function setOutput(e,t){const s=t.replace(/\r?\n/g," ");process.stdout.write(`set-output ${e} ${s}\n`)}function escapeShellValue(e){return e.replace(/(["\\'$`!\s\[\]{}()&|;<>*?#^~])/g,"\\$1")}},317:e=>{e.exports=require("child_process")},330:e=>{e.exports={rE:"1.1.0"}}};var t={};function __nccwpck_require__(s){var r=t[s];if(r!==undefined){return r.exports}var o=t[s]={exports:{}};var n=true;try{e[s].call(o.exports,o,o.exports,__nccwpck_require__);n=false}finally{if(n)delete t[s]}return o.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var s=__nccwpck_require__(866);module.exports=s})(); \ No newline at end of file diff --git a/examples/android.yml b/examples/android.yml index f007ccd..bfe6202 100644 --- a/examples/android.yml +++ b/examples/android.yml @@ -19,6 +19,7 @@ jobs: DCD_EAS_APP_VERSION: ${{ needs.build_android.outputs.app_version }} DCD_GH_SHA: ${{ github.sha }} DCD_GH_BRANCH: ${{ github.ref_name }} + DCD_CHECK_NAME: Android outputs: console_url: ${{ steps.dcd.outputs.console_url }} status: ${{ steps.dcd.outputs.upload_status }} diff --git a/examples/ios.yml b/examples/ios.yml index ae9432e..7c4b210 100644 --- a/examples/ios.yml +++ b/examples/ios.yml @@ -19,6 +19,7 @@ jobs: DCD_EAS_APP_VERSION: ${{ needs.build_ios.outputs.app_version }} DCD_GH_SHA: ${{ github.sha }} DCD_GH_BRANCH: ${{ github.ref_name }} + DCD_CHECK_NAME: iOS outputs: console_url: ${{ steps.dcd.outputs.console_url }} status: ${{ steps.dcd.outputs.upload_status }} diff --git a/src/methods/env.test.ts b/src/methods/env.test.ts index 7285f6e..63bf858 100644 --- a/src/methods/env.test.ts +++ b/src/methods/env.test.ts @@ -37,6 +37,15 @@ describe('getEnv', () => { expect(metadata).toContain('gh_branch=main'); }); + it('maps DCD_CHECK_NAME so each job posts its own GitHub check', () => { + // Two jobs on one commit otherwise post identically named checks, which + // branch protection can only hold as a single required entry. + vi.stubEnv('DEVICE_CLOUD_API_KEY', 'k'); + vi.stubEnv('DCD_CHECK_NAME', 'iOS smoke'); + + expect(getEnv().metadata).toContain('gh_check_name=iOS smoke'); + }); + it('skips metadata entries whose source env var is unset or empty', () => { vi.stubEnv('DEVICE_CLOUD_API_KEY', 'k'); vi.stubEnv('DCD_EAS_BUILD_ID', 'build-123'); diff --git a/src/methods/env.ts b/src/methods/env.ts index 98b405f..c7d44ed 100644 --- a/src/methods/env.ts +++ b/src/methods/env.ts @@ -22,6 +22,11 @@ const metadataMappings: Array<[envVar: string, metadataKey: string]> = [ ['DCD_GH_PR_URL', 'gh_pr_url'], ['DCD_GH_REPO', 'gh_repo'], ['DCD_GH_RUN_ID', 'gh_run_id'], + // Names the GitHub check this run posts ("DeviceCloud / iOS"). Set it per job + // when a commit is tested more than once — GitHub matches required checks by + // name, so runs sharing a name share one branch-protection gate, and it + // follows whichever finished last. Keep the value fixed for a given job. + ['DCD_CHECK_NAME', 'gh_check_name'], ]; export function getEnv(): EasEnv {