@trackunit/iris-app 2.3.3 → 2.3.5
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/CHANGELOG.md +14 -0
- package/package.json +5 -5
- package/src/executors/submit/executor.js +115 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 2.3.5 (2026-08-04)
|
|
2
|
+
|
|
3
|
+
### 🧱 Updated Dependencies
|
|
4
|
+
|
|
5
|
+
- Updated iris-app-build-utilities to 2.2.4
|
|
6
|
+
- Updated iris-app-api to 2.2.4
|
|
7
|
+
- Updated react-vite-test-setup to 0.0.90
|
|
8
|
+
- Updated react-graphql-tools to 1.14.91
|
|
9
|
+
- Updated shared-utils to 1.15.92
|
|
10
|
+
|
|
11
|
+
## 2.3.4 (2026-08-04)
|
|
12
|
+
|
|
13
|
+
This was a version bump only for iris-app to align it with other projects, there were no code changes.
|
|
14
|
+
|
|
1
15
|
## 2.3.3 (2026-08-04)
|
|
2
16
|
|
|
3
17
|
### 🧱 Updated Dependencies
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/iris-app",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"generators": "./generators.json",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"@nx/react": "23.1.0",
|
|
24
24
|
"@npmcli/arborist": "^9.1.9",
|
|
25
25
|
"win-ca": "^3.5.1",
|
|
26
|
-
"@trackunit/iris-app-build-utilities": "2.2.
|
|
27
|
-
"@trackunit/react-graphql-tools": "1.14.
|
|
28
|
-
"@trackunit/shared-utils": "1.15.
|
|
29
|
-
"@trackunit/iris-app-api": "2.2.
|
|
26
|
+
"@trackunit/iris-app-build-utilities": "2.2.4",
|
|
27
|
+
"@trackunit/react-graphql-tools": "1.14.91",
|
|
28
|
+
"@trackunit/shared-utils": "1.15.92",
|
|
29
|
+
"@trackunit/iris-app-api": "2.2.4",
|
|
30
30
|
"tslib": "^2.6.2",
|
|
31
31
|
"@clack/prompts": "^1.0.0",
|
|
32
32
|
"@npm/types": "^1.0.2",
|
|
@@ -15,12 +15,81 @@ const irisAppServerSettings_1 = require("../utils/irisAppServerSettings");
|
|
|
15
15
|
class NpmConflictError extends Error {
|
|
16
16
|
}
|
|
17
17
|
class NpmGeneralError extends Error {
|
|
18
|
+
constructor(message, statusCode, code, type) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.type = type;
|
|
23
|
+
}
|
|
18
24
|
}
|
|
19
25
|
class ApproveError extends Error {
|
|
26
|
+
constructor(message, statusCode, code) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.statusCode = statusCode;
|
|
29
|
+
this.code = code;
|
|
30
|
+
}
|
|
20
31
|
}
|
|
21
32
|
function sleep(time) {
|
|
22
33
|
return new Promise(resolve => setTimeout(resolve, time));
|
|
23
34
|
}
|
|
35
|
+
// Errno-style codes that indicate the request never reached the registry at all
|
|
36
|
+
// (DNS/connection/timeout failures at the transport layer), as opposed to local
|
|
37
|
+
// validation errors (e.g. libnpmpublish's EPRIVATE/EBADSEMVER/EUSAGE) which carry
|
|
38
|
+
// a code but are permanent and can never succeed on retry. Kept aligned with
|
|
39
|
+
// make-fetch-happen's own RETRY_ERRORS list (used by the submit path) plus the
|
|
40
|
+
// undici error codes doApproveApp's native fetch can raise.
|
|
41
|
+
//
|
|
42
|
+
// ENOTFOUND (DNS failure) is deliberately included even though make-fetch-happen
|
|
43
|
+
// itself excludes it as "bad hostname, or offline" - a DNS blip during a VPN/
|
|
44
|
+
// network transition is common enough in practice that failing fast here would
|
|
45
|
+
// trade one wasted-wait problem for developers hitting spurious permanent
|
|
46
|
+
// failures on flaky networks. This is a judgment call, not a clear-cut default.
|
|
47
|
+
const RETRYABLE_TRANSPORT_CODES = new Set([
|
|
48
|
+
"ENOTFOUND",
|
|
49
|
+
"ECONNRESET",
|
|
50
|
+
"ECONNREFUSED",
|
|
51
|
+
"ETIMEDOUT",
|
|
52
|
+
"EAI_AGAIN",
|
|
53
|
+
"ERR_SOCKET_TIMEOUT",
|
|
54
|
+
"EADDRINUSE",
|
|
55
|
+
// from @npmcli/agent, surfaced through make-fetch-happen
|
|
56
|
+
"ECONNECTIONTIMEOUT",
|
|
57
|
+
"EIDLETIMEOUT",
|
|
58
|
+
"ERESPONSETIMEOUT",
|
|
59
|
+
"ETRANSFERTIMEOUT",
|
|
60
|
+
// doApproveApp uses Node's native fetch (undici), which raises its own
|
|
61
|
+
// codes distinct from the classic Node errno codes above.
|
|
62
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
63
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
64
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
65
|
+
"UND_ERR_SOCKET", // e.g. "other side closed" on a reused keep-alive connection
|
|
66
|
+
]);
|
|
67
|
+
/**
|
|
68
|
+
* Decide whether a failed submit/approve attempt is worth retrying.
|
|
69
|
+
*
|
|
70
|
+
* This is an allowlist, not a denylist: only failures with a real chance of
|
|
71
|
+
* succeeding on a later attempt are retried - 5xx responses, 408/429 (timeout/
|
|
72
|
+
* rate-limited), a transport-level failure that never reached the registry at
|
|
73
|
+
* all (identified by a known transient errno code), or a minipass-fetch request
|
|
74
|
+
* timeout (which surfaces with the generic code FETCH_ERROR, distinguishable
|
|
75
|
+
* only via its `type: "request-timeout"`). Everything else - permanent 4xx
|
|
76
|
+
* errors, local validation errors with an unrelated code (e.g. EBADSEMVER), or
|
|
77
|
+
* any other unrecognized error shape - is treated as non-retryable by default.
|
|
78
|
+
*
|
|
79
|
+
* @param {number} [statusCode] The HTTP status code of the failed attempt, if any.
|
|
80
|
+
* @param {string} [code] The error's errno-style code, if any (no statusCode implies no HTTP response was received).
|
|
81
|
+
* @param {string} [type] The error's `type` field, if any. Only consulted when `code === "FETCH_ERROR"`, since that's the one code minipass-fetch's FetchError uses generically for several failure kinds - `type` is what distinguishes a timeout from the others.
|
|
82
|
+
* @returns {boolean} Whether the attempt should be retried.
|
|
83
|
+
*/
|
|
84
|
+
function isRetryableError(statusCode, code, type) {
|
|
85
|
+
if (statusCode !== undefined) {
|
|
86
|
+
return statusCode >= 500 || statusCode === 408 || statusCode === 429;
|
|
87
|
+
}
|
|
88
|
+
if (code === undefined) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return RETRYABLE_TRANSPORT_CODES.has(code) || (code === "FETCH_ERROR" && type === "request-timeout");
|
|
92
|
+
}
|
|
24
93
|
/**
|
|
25
94
|
* Submit the package to the npm registry.
|
|
26
95
|
*
|
|
@@ -72,16 +141,24 @@ async function doUploadAppWithRetry(accessToken, manifest, tarData, settings, re
|
|
|
72
141
|
console.error("❌ Unable to ship app package. Cannot submit over existing version.");
|
|
73
142
|
return { success: false };
|
|
74
143
|
}
|
|
144
|
+
if (!(e instanceof NpmGeneralError)) {
|
|
145
|
+
// An error shape we don't recognize (e.g. a bug elsewhere in this function) -
|
|
146
|
+
// log the full error/stack rather than a bare message so it stays debuggable.
|
|
147
|
+
console.error("❌ Unable to ship app package. Got error from Iris App SDK repository.", e);
|
|
148
|
+
return { success: false };
|
|
149
|
+
}
|
|
150
|
+
if (!isRetryableError(e.statusCode, e.code, e.type)) {
|
|
151
|
+
console.error("❌ Unable to ship app package.", e.message);
|
|
152
|
+
return { success: false };
|
|
153
|
+
}
|
|
154
|
+
if (retryCount > 1) {
|
|
155
|
+
console.error("⚠️ Unable to ship app package. Got error from Iris App SDK repository. Retrying submit...", e.message);
|
|
156
|
+
await sleep(30000);
|
|
157
|
+
return await doUploadAppWithRetry(accessToken, manifest, tarData, settings, retryCount - 1);
|
|
158
|
+
}
|
|
75
159
|
else {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await sleep(30000);
|
|
79
|
-
return await doUploadAppWithRetry(accessToken, manifest, tarData, settings, retryCount - 1);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
console.error("❌ Unable to ship app package. Got error from Iris App SDK repository.", e);
|
|
83
|
-
return { success: false };
|
|
84
|
-
}
|
|
160
|
+
console.error("❌ Unable to ship app package. Got error from Iris App SDK repository.", e.message);
|
|
161
|
+
return { success: false };
|
|
85
162
|
}
|
|
86
163
|
}
|
|
87
164
|
}
|
|
@@ -102,6 +179,9 @@ async function doUploadApp(accessToken, manifest, tarData, settings) {
|
|
|
102
179
|
else {
|
|
103
180
|
return {
|
|
104
181
|
ok: false,
|
|
182
|
+
statusCode: err.statusCode,
|
|
183
|
+
code: err.code,
|
|
184
|
+
type: err.type,
|
|
105
185
|
headers: new Headers(err.headers),
|
|
106
186
|
statusText: `Error`,
|
|
107
187
|
text: () => Promise.resolve(`${err.name} ${err.message}`),
|
|
@@ -130,7 +210,7 @@ async function doUploadApp(accessToken, manifest, tarData, settings) {
|
|
|
130
210
|
" " +
|
|
131
211
|
(await submitResult.text()) +
|
|
132
212
|
". Trace ID: " +
|
|
133
|
-
submitResult.headers.get("trace-id"));
|
|
213
|
+
submitResult.headers.get("trace-id"), "statusCode" in submitResult ? submitResult.statusCode : undefined, "code" in submitResult ? submitResult.code : undefined, "type" in submitResult ? submitResult.type : undefined);
|
|
134
214
|
}
|
|
135
215
|
}
|
|
136
216
|
async function promptUserForInput(query) {
|
|
@@ -150,19 +230,21 @@ async function doApproveAppWithRetry(token, manifest, settings, retryCount) {
|
|
|
150
230
|
return await doApproveApp(token, manifest, settings);
|
|
151
231
|
}
|
|
152
232
|
catch (e) {
|
|
153
|
-
if (e instanceof ApproveError) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
233
|
+
if (!(e instanceof ApproveError)) {
|
|
234
|
+
console.error("❌ Unable to approve app package. Got error from Iris App SDK repository.", e);
|
|
235
|
+
return { success: false };
|
|
236
|
+
}
|
|
237
|
+
if (!isRetryableError(e.statusCode, e.code)) {
|
|
238
|
+
console.error("❌ Unable to approve app package.", e.message);
|
|
239
|
+
return { success: false };
|
|
240
|
+
}
|
|
241
|
+
if (retryCount > 1) {
|
|
242
|
+
console.error("⚠️ Unable to approve app package. Got approve error from Iris App SDK repository. Retrying approve...", e.message);
|
|
243
|
+
await sleep(30000);
|
|
244
|
+
return await doApproveAppWithRetry(token, manifest, settings, retryCount - 1);
|
|
163
245
|
}
|
|
164
246
|
else {
|
|
165
|
-
console.error("❌ Unable to approve app package. Got error from Iris App SDK repository.", e);
|
|
247
|
+
console.error("❌ Unable to approve app package. Got approve error from Iris App SDK repository.", e.message);
|
|
166
248
|
return { success: false };
|
|
167
249
|
}
|
|
168
250
|
}
|
|
@@ -170,18 +252,29 @@ async function doApproveAppWithRetry(token, manifest, settings, retryCount) {
|
|
|
170
252
|
async function doApproveApp(token, manifest, settings) {
|
|
171
253
|
if (process.env.TU_APPROVE === "true") {
|
|
172
254
|
console.log(`🖋️ Approving the app...`);
|
|
255
|
+
// A rejection here (e.g. DNS/connection/timeout failure) never produced an HTTP
|
|
256
|
+
// response, so it's re-thrown as an ApproveError with no statusCode - carrying
|
|
257
|
+
// the underlying cause's errno code (if any) so isRetryableError can recognize
|
|
258
|
+
// genuine transport failures, and the cause's message so the log doesn't just
|
|
259
|
+
// say "fetch failed" with no indication of what actually broke.
|
|
173
260
|
const approveResult = await fetch(new URL(`${manifest.name}@${manifest.version}`, settings.approvalUrl), {
|
|
174
261
|
method: "PUT",
|
|
175
262
|
headers: {
|
|
176
263
|
Authorization: `Bearer ${token}`,
|
|
177
264
|
},
|
|
265
|
+
}).catch((err) => {
|
|
266
|
+
const cause = err.cause instanceof Error ? err.cause : undefined;
|
|
267
|
+
const causeCode = cause && "code" in cause ? cause.code : undefined;
|
|
268
|
+
const code = typeof causeCode === "string" ? causeCode : undefined;
|
|
269
|
+
const reason = cause ? `${err.message}: ${cause.message}` : err.message;
|
|
270
|
+
throw new ApproveError(reason, undefined, code);
|
|
178
271
|
});
|
|
179
272
|
if (approveResult.ok) {
|
|
180
273
|
console.log(`✅ Approved version ${manifest.version} of ${manifest.name}.`);
|
|
181
274
|
return { success: true };
|
|
182
275
|
}
|
|
183
276
|
else {
|
|
184
|
-
throw new ApproveError(`${approveResult.statusText}, ${await approveResult.text()}, Trace ID: ${approveResult.headers.get("trace-id")}
|
|
277
|
+
throw new ApproveError(`${approveResult.statusText}, ${await approveResult.text()}, Trace ID: ${approveResult.headers.get("trace-id")}`, approveResult.status);
|
|
185
278
|
}
|
|
186
279
|
}
|
|
187
280
|
else {
|