@@ -108,7 +108,7 @@ jobs:
108108 - name : Fetch ToDesktop build metadata by ID
109109 env :
110110 BUILD_ID : ${{ steps.build_id.outputs.build_id }}
111- run : todesktop builds "${BUILD_ID}" --config=todesktop.json --format=json -- exit > todesktop-build.json
111+ run : todesktop builds "${BUILD_ID}" --config=todesktop.json --exit > todesktop-build.raw 2>&1
112112
113113 - name : Extract build details
114114 id : extract
@@ -117,40 +117,41 @@ jobs:
117117 node <<'NODE'
118118 const fs = require('node:fs')
119119
120- const parseBuildOutput = (filePath) => {
121- const raw = fs.readFileSync(filePath, 'utf8')
122- const start = raw.indexOf('[')
123- const end = raw.lastIndexOf(']')
124- const jsonText =
125- start !== -1 && end !== -1 && end > start
126- ? raw.slice(start, end + 1)
127- : raw.slice(raw.indexOf('{'), raw.lastIndexOf('}') + 1)
128-
129- let parsed
130- try {
131- parsed = JSON.parse(jsonText)
132- } catch (err) {
133- console.error(`Failed to parse ToDesktop build JSON output from ${filePath}`)
134- console.error(raw.slice(0, 1000))
135- throw err
120+ const parseBuildOutput = (raw, expectedBuildId) => {
121+ const buildIdPattern =
122+ /https?:\/\/(?:app\.todesktop\.com\/apps|dl\.todesktop\.com)\/[^/\s]+\/builds\/([A-Za-z0-9_-]+)/g
123+ const buildIds = [...new Set([...raw.matchAll(buildIdPattern)].map((match) => match[1]))].filter(
124+ Boolean
125+ )
126+ if (expectedBuildId && buildIds.length > 0 && !buildIds.includes(expectedBuildId)) {
127+ throw new Error(
128+ `Build ID mismatch in raw CLI output. Expected ${expectedBuildId}, found ${buildIds.join(', ')}`
129+ )
136130 }
137131
138- if (Array.isArray(parsed)) {
139- if (parsed.length === 0) {
140- throw new Error(`No builds returned in ${filePath}`)
141- }
142- return parsed[0]
132+ const buildId = expectedBuildId || buildIds[0] || ''
133+ if (!buildId) {
134+ throw new Error('Could not extract a ToDesktop build ID from metadata output.')
143135 }
144136
145- if (parsed && typeof parsed === 'object') {
146- return parsed
137+ const appUrl = [...raw.matchAll(/https?:\/\/app\.todesktop\.com\/apps\/[^/\s]+\/builds\/[A-Za-z0-9_-]+/g)]
138+ .map((match) => match[0])
139+ .at(-1)
140+ const downloadUrl = [...raw.matchAll(/https?:\/\/dl\.todesktop\.com\/[^/\s]+\/builds\/[A-Za-z0-9_-]+/g)]
141+ .map((match) => match[0])
142+ .at(-1)
143+
144+ return {
145+ id: buildId,
146+ status: /Build complete!/i.test(raw) ? 'succeeded' : 'unknown',
147+ standardUniversalDownloadUrl: downloadUrl || '',
148+ __appUrl: appUrl || ''
147149 }
148-
149- throw new Error(`Unexpected ToDesktop build output shape in ${filePath}`)
150150 }
151151
152152 const expectedBuildId = process.env.EXPECTED_BUILD_ID || ''
153- const build = parseBuildOutput('todesktop-build.json')
153+ const rawBuildOutput = fs.readFileSync('todesktop-build.raw', 'utf8')
154+ const build = parseBuildOutput(rawBuildOutput, expectedBuildId)
154155 const safeUrl = (value) => (typeof value === 'string' ? value : '')
155156 const macUrl = safeUrl(build?.mac?.standardDownloadUrl)
156157 const windowsUrl = safeUrl(build?.windows?.standardDownloadUrl)
@@ -235,7 +236,9 @@ jobs:
235236
236237 const todesktopConfig = JSON.parse(fs.readFileSync('todesktop.json', 'utf8'))
237238 const appId = todesktopConfig.id
238- const buildPageUrl = `https://app.todesktop.com/apps/${appId}/builds/${buildId}`
239+ const configBuildPageUrl =
240+ appId && buildId ? `https://app.todesktop.com/apps/${appId}/builds/${buildId}` : ''
241+ const buildPageUrl = safeUrl(build?.__appUrl) || configBuildPageUrl
239242
240243 fs.writeFileSync(
241244 'release-assets.json',
@@ -265,7 +268,8 @@ jobs:
265268 fs.appendFileSync(output, `windows_url=${windowsUrl}\n`)
266269 fs.appendFileSync(output, `linux_url=${linuxUrl}\n`)
267270
268- if (status !== 'succeeded') {
271+ const successStatuses = new Set(['succeeded', 'success', 'finished', 'complete', 'completed'])
272+ if (!successStatuses.has(String(status).toLowerCase())) {
269273 throw new Error(`ToDesktop build did not succeed (status=${status})`)
270274 }
271275 NODE
0 commit comments