(e, phase)
| 155 | // 'parse' -> degit exited 0 but the downloaded package.json is missing/invalid |
| 156 | // 'copy' -> the staged tree downloaded fine but cpSync into INSTALL_ROOT failed |
| 157 | function _classifyChannel1Error(e, phase) { |
| 158 | if (phase === 'delete') { |
| 159 | var deleteEntry = e && e._evolverEntry ? String(e._evolverEntry) + ': ' : ''; |
| 160 | return _fail(FORCE_UPDATE_FAIL_CODES.DELETE_FAILED, deleteEntry + _errStr(e)); |
| 161 | } |
| 162 | if (phase === 'copy') { |
| 163 | var entry = e && e._evolverEntry ? String(e._evolverEntry) + ': ' : ''; |
| 164 | return _fail(FORCE_UPDATE_FAIL_CODES.COPY_FAILED, entry + _errStr(e)); |
| 165 | } |
| 166 | if (phase === 'parse') { |
| 167 | return _fail(FORCE_UPDATE_FAIL_CODES.DOWNLOAD_INCOMPLETE, |
| 168 | 'missing/invalid package.json in downloaded tree: ' + _errStr(e)); |
| 169 | } |
| 170 | // phase === 'degit' (the spawn). ENOENT here is the npx binary itself, not a |
| 171 | // file inside the download — that distinction is exactly why `phase` exists. |
| 172 | if (e && e.code === 'ENOENT') { |
| 173 | return _fail(FORCE_UPDATE_FAIL_CODES.NPX_NOT_FOUND, _errStr(e)); |
| 174 | } |
| 175 | // execFileSync timeout kills the child with SIGTERM (and sets .killed); some |
| 176 | // platforms surface ETIMEDOUT instead. Either way it is a 60s timeout. |
| 177 | if (e && (e.killed || e.signal === 'SIGTERM' || e.code === 'ETIMEDOUT')) { |
| 178 | return _fail(FORCE_UPDATE_FAIL_CODES.DEGIT_TIMEOUT, |
| 179 | 'degit timed out after 60s' + (e.signal ? ' (signal=' + e.signal + ')' : '')); |
| 180 | } |
| 181 | // Generic degit/network/tag-not-found failure. degit prints the real reason |
| 182 | // ("could not find commit hash for v…", "could not resolve host") to stderr, |
| 183 | // so keep a tail of it. Redact + strip control chars HERE, before the tail |
| 184 | // slice: the downstream reporter redact (a2aProtocol.reportForceUpdateOutcome) |
| 185 | // runs after this, so slicing first could chop a token's prefix anchor and |
| 186 | // let the bare value slip past the prefix-anchored redact patterns. Stripping |
| 187 | // ANSI/NUL/newlines also keeps the persisted error free of terminal-injection |
| 188 | // sequences and log-line noise. |
| 189 | var detail = _errStr(e); |
| 190 | var stderr = ''; |
| 191 | if (e && e.stderr != null) { |
| 192 | try { |
| 193 | var redactString = require('./gep/sanitize').redactString; |
| 194 | stderr = redactString(String(e.stderr)).replace(/[\x00-\x1f\x7f]/g, ' ').trim(); |
| 195 | } catch (_) { |
| 196 | // sanitize unavailable — still strip control chars so logs stay clean. |
| 197 | stderr = String(e.stderr).replace(/[\x00-\x1f\x7f]/g, ' ').trim(); |
| 198 | } |
| 199 | } |
| 200 | if (stderr) detail += ' | stderr=' + stderr.slice(-300); |
| 201 | return _fail(FORCE_UPDATE_FAIL_CODES.DEGIT_FAILED, detail); |
| 202 | } |
| 203 | |
| 204 | function _withFallbackFailure(primaryFailure, fallbackFailure) { |
| 205 | if (!primaryFailure) return fallbackFailure; |
no test coverage detected