@qawolf/ci-sdk 2.1.0 → 2.5.0

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.
Files changed (42) hide show
  1. package/README.md +15 -0
  2. package/dist/index.cjs +401 -184
  3. package/dist/index.cjs.map +7 -1
  4. package/dist/index.d.ts +4 -534
  5. package/dist/index.js +376 -182
  6. package/dist/index.js.map +7 -1
  7. package/dist/lib/api/ci-greenlight-bug-data.d.ts +67 -0
  8. package/dist/lib/api/ci-greenlight.d.ts +76 -0
  9. package/dist/lib/api/generate-signed-url-for-run-inputs-executables.d.ts +4 -0
  10. package/dist/lib/api/generate-signed-url-for-team-storage.d.ts +4 -0
  11. package/dist/lib/api/index.d.ts +2 -0
  12. package/dist/lib/api/log.d.ts +6 -0
  13. package/dist/lib/api/signed-url.d.ts +11 -0
  14. package/dist/lib/api-types.d.ts +5 -0
  15. package/dist/lib/other.d.ts +5 -0
  16. package/dist/lib/sdk/defaults/fetch.d.ts +1 -0
  17. package/dist/lib/sdk/defaults/log.d.ts +2 -0
  18. package/dist/lib/sdk/defaults/serviceBase.d.ts +1 -0
  19. package/dist/lib/sdk/defaults/userAgent.d.ts +1 -0
  20. package/dist/lib/sdk/dependencies.d.ts +7 -0
  21. package/dist/lib/sdk/domain/attemptDeploy/deployConfig.d.ts +37 -0
  22. package/dist/lib/sdk/domain/attemptDeploy/deploySuccessWireResponse.d.ts +39 -0
  23. package/dist/lib/sdk/domain/attemptDeploy/handleSuccessResponse.d.ts +3 -0
  24. package/dist/lib/sdk/domain/attemptDeploy/index.d.ts +4 -0
  25. package/dist/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.d.ts +3 -0
  26. package/dist/lib/sdk/domain/attemptDeploy/parseDeploySuccessWireResponse.d.ts +2 -0
  27. package/dist/lib/sdk/domain/attemptDeploy/types.d.ts +129 -0
  28. package/dist/lib/sdk/domain/generateSignedUrls/generate-signed-url-for-run-inputs-executables.d.ts +4 -0
  29. package/dist/lib/sdk/domain/generateSignedUrls/generate-signed-url-for-team-storage.d.ts +16 -0
  30. package/dist/lib/sdk/domain/generateSignedUrls/types.d.ts +28 -0
  31. package/dist/lib/sdk/domain/notifyTerminatedEphemeralEnvironment/index.d.ts +4 -0
  32. package/dist/lib/sdk/domain/notifyTerminatedEphemeralEnvironment/types.d.ts +48 -0
  33. package/dist/lib/sdk/domain/pollCiGreenlight/buildCompletedRunResult.d.ts +8 -0
  34. package/dist/lib/sdk/domain/pollCiGreenlight/consts.d.ts +7 -0
  35. package/dist/lib/sdk/domain/pollCiGreenlight/index.d.ts +4 -0
  36. package/dist/lib/sdk/domain/pollCiGreenlight/iterator.d.ts +43 -0
  37. package/dist/lib/sdk/domain/pollCiGreenlight/logging.d.ts +25 -0
  38. package/dist/lib/sdk/domain/pollCiGreenlight/mapIteratorAbortToGreenlightStatus.d.ts +7 -0
  39. package/dist/lib/sdk/domain/pollCiGreenlight/types.d.ts +145 -0
  40. package/dist/lib/sdk/index.d.ts +26 -0
  41. package/package.json +13 -12
  42. package/dist/index.d.cts +0 -534
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* Built with @qawolf/platform-builder@1.2.2 */
1
2
  var __defProp = Object.defineProperty;
2
3
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
4
 
@@ -12,10 +13,7 @@ function pluralize(count) {
12
13
  __name(pluralize, "pluralize");
13
14
  var buildFetchWithTimeout = /* @__PURE__ */ __name((fetch, defaultTimeoutMs) => async (...args) => {
14
15
  const signal = args[1]?.signal ?? AbortSignal.timeout(defaultTimeoutMs);
15
- return fetch(args[0], {
16
- ...args[1],
17
- signal
18
- });
16
+ return fetch(args[0], { ...args[1], signal });
19
17
  }, "buildFetchWithTimeout");
20
18
 
21
19
  // src/lib/sdk/defaults/fetch.ts
@@ -39,13 +37,61 @@ var defaultServiceBase = "https://app.qawolf.com/";
39
37
 
40
38
  // package.json
41
39
  var package_default = {
42
- version: "2.1.0"};
40
+ name: "@qawolf/ci-sdk",
41
+ version: "2.5.0",
42
+ private: false,
43
+ description: "A simple SDK for interacting with QAWolf in CI scripts.",
44
+ keywords: [],
45
+ license: "MIT",
46
+ author: "Jules Sam. Randolph<jules@qawolf.com>",
47
+ files: [
48
+ "/dist",
49
+ "/README.md"
50
+ ],
51
+ type: "module",
52
+ sideEffects: false,
53
+ exports: {
54
+ ".": {
55
+ types: "./dist/index.d.ts",
56
+ require: "./dist/index.cjs",
57
+ import: "./dist/index.js",
58
+ default: "./dist/index.js"
59
+ }
60
+ },
61
+ scripts: {
62
+ build: "platform-builder --config platformBuilder.config.ts --config platformBuilder.cjs.config.ts",
63
+ "lint:ci": "npm run lint:first-half; FIRST_HALF_EXIT=$?; npm run lint:second-half && exit $FIRST_HALF_EXIT",
64
+ "lint:dev": "npm run lint:first-half",
65
+ "lint:first-half": "eslint . --fix --quiet && oxfmt --write .",
66
+ "lint:second-half": "dpdm --exit-code circular:1 --no-tree --no-warning --transform --include='.*' --exclude='/(dist|node_modules)/' '**/*'",
67
+ prepublishOnly: "nx run ci-sdk:build",
68
+ test: 'NODE_OPTIONS="--experimental-vm-modules" NODE_NO_WARNINGS=1 jest',
69
+ "tsc:check": "tsc --noEmit"
70
+ },
71
+ dependencies: {
72
+ "@qawolf/ci-utils": "^1.1.2",
73
+ "@sinonjs/fake-timers": "^10.3.0",
74
+ tslib: "^2.6.2"
75
+ },
76
+ devDependencies: {
77
+ "@qawolf/platform-builder": "^1.2.2",
78
+ tsx: "^4.19.4",
79
+ typescript: "^5.9.3"
80
+ },
81
+ engines: {
82
+ node: ">=20"
83
+ }
84
+ };
43
85
 
44
86
  // src/lib/sdk/defaults/userAgent.ts
45
87
  var defaultUserAgent = `ci-sdk/${package_default.version}`;
46
88
 
47
89
  // src/lib/api/log.ts
48
- async function logFetchError({ log, methodName, response }) {
90
+ async function logFetchError({
91
+ log,
92
+ methodName,
93
+ response
94
+ }) {
49
95
  if (response.ok) return;
50
96
  const { eventId, failureMessage } = await (async () => {
51
97
  try {
@@ -70,7 +116,9 @@ async function logFetchError({ log, methodName, response }) {
70
116
  const eventIdSuffix = eventId ? ` (Event ID: ${eventId})` : "";
71
117
  const messageSuffix = failureMessage ? `: ${failureMessage}` : "";
72
118
  const statusText = response.status >= 500 ? "Server Error" : response.statusText || "Unknown Error";
73
- log.error(`\u274C [${methodName}] ${response.status} ${statusText}${eventIdSuffix}${messageSuffix}`);
119
+ log.error(
120
+ `\u274C [${methodName}] ${response.status} ${statusText}${eventIdSuffix}${messageSuffix}`
121
+ );
74
122
  }
75
123
  __name(logFetchError, "logFetchError");
76
124
 
@@ -80,6 +128,7 @@ async function handleSuccessResponse(data, log) {
80
128
  const skippedTriggers = data.skippedTriggers ?? [];
81
129
  const skippedDeployment = data.skippedDeployment;
82
130
  const isPreviewDeployment = environment && !environment.isStatic;
131
+ if (data.warning) log.warn(`\u26A0\uFE0F ${data.warning}`);
83
132
  if (environment) {
84
133
  const logPrefix = environment.isStatic ? "Static environment" : "Preview environment";
85
134
  log.info(`${logPrefix}: ${environment.alias} (id: ${environment.id})`);
@@ -87,13 +136,13 @@ async function handleSuccessResponse(data, log) {
87
136
  if (results.length === 0) {
88
137
  const skipMessages = [
89
138
  ...skippedTriggers.map((trigger) => trigger.message),
90
- ...skippedDeployment ? [
91
- skippedDeployment.message
92
- ] : []
139
+ ...skippedDeployment ? [skippedDeployment.message] : []
93
140
  ];
94
141
  const skipDetail = skipMessages.length > 0 ? ` ${skipMessages.join(" ")}` : "";
95
142
  if (isPreviewDeployment) {
96
- log.info(`\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`);
143
+ log.info(
144
+ `\u2705 Deployment notification was successful. No run was created for this deployment.${skipDetail}`
145
+ );
97
146
  return {
98
147
  environmentId: environment.id,
99
148
  outcome: "success",
@@ -108,7 +157,9 @@ async function handleSuccessResponse(data, log) {
108
157
  skippedTriggers
109
158
  };
110
159
  }
111
- log.warn(skippedDeployment ? `\u26A0\uFE0F No run was created for this deployment.${skipDetail}` : `\u26A0\uFE0F No trigger matched the request. Please make sure there is at least one active test in the QA Wolf environment, or contact support.`);
160
+ log.warn(
161
+ skippedDeployment ? `\u26A0\uFE0F No run was created for this deployment.${skipDetail}` : `\u26A0\uFE0F No trigger matched the request. Please make sure there is at least one active test in the QA Wolf environment, or contact support.`
162
+ );
112
163
  return {
113
164
  outcome: "skipped",
114
165
  skippedDeployment
@@ -116,45 +167,43 @@ async function handleSuccessResponse(data, log) {
116
167
  }
117
168
  if (!environment) {
118
169
  log.error(`\u{1F6AB} No environment matched the request. Please contact support.`);
119
- return {
120
- failReason: "no-environment",
121
- outcome: "failed"
122
- };
170
+ return { failReason: "no-environment", outcome: "failed" };
123
171
  }
124
172
  const unrecognizedResult = results.find((result) => "unrecognized" in result);
125
173
  if (unrecognizedResult) {
126
- log.error(`\u{1F6AB} The deployment response contained an unrecognized result. Please upgrade @qawolf/ci-sdk.`);
127
- return {
128
- failReason: "unknown-result",
129
- outcome: "failed"
130
- };
174
+ log.error(
175
+ `\u{1F6AB} The deployment response contained an unrecognized result. Please upgrade @qawolf/ci-sdk.`
176
+ );
177
+ return { failReason: "unknown-result", outcome: "failed" };
131
178
  }
132
179
  const failedTrigger = results.find((result) => "failureReason" in result);
133
180
  if (failedTrigger) {
134
- log.error(`\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureReason}". Please contact support.`);
135
- return {
136
- failReason: "run-could-not-be-created",
137
- outcome: "failed"
138
- };
181
+ log.error(
182
+ `\u{1F6AB} Run could not be created. Reason: "${failedTrigger.failureMessage ?? failedTrigger.failureReason}". Please contact support.`
183
+ );
184
+ return { failReason: "run-could-not-be-created", outcome: "failed" };
139
185
  }
140
186
  const duplicateSuite = results.find((result) => "duplicateSuiteId" in result);
141
187
  if (duplicateSuite) {
142
- log.info(`\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`);
188
+ log.info(
189
+ `\u2705 Deployment notification was successful. Previous run ID: ${duplicateSuite.duplicateSuiteId} already exists for this deployment.`
190
+ );
143
191
  return {
144
192
  environmentId: environment.id,
145
193
  outcome: "success",
146
194
  runId: duplicateSuite.duplicateSuiteId
147
195
  };
148
196
  }
149
- const successfulTrigger = results.find((result) => "createdSuiteId" in result);
197
+ const successfulTrigger = results.find(
198
+ (result) => "createdSuiteId" in result
199
+ );
150
200
  if (!successfulTrigger) {
151
201
  log.error(`\u{1F6AB} Run could not be created. Please contact support.`);
152
- return {
153
- failReason: "run-could-not-be-created",
154
- outcome: "failed"
155
- };
202
+ return { failReason: "run-could-not-be-created", outcome: "failed" };
156
203
  }
157
- log.info(`\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`);
204
+ log.info(
205
+ `\u2705 Deployment notification was successful. Run created with ID: ${successfulTrigger.createdSuiteId}`
206
+ );
158
207
  return {
159
208
  environmentId: environment.id,
160
209
  outcome: "success",
@@ -165,29 +214,28 @@ __name(handleSuccessResponse, "handleSuccessResponse");
165
214
 
166
215
  // src/lib/sdk/domain/attemptDeploy/mapDeploySuccessResponse.ts
167
216
  function mapSuiteResult(result) {
168
- if ("created_suite_id" in result) {
169
- return {
170
- createdSuiteId: result.created_suite_id,
171
- triggerId: result.trigger_id
172
- };
173
- }
174
- if ("failure_reason" in result) {
175
- return {
176
- failureReason: result.failure_reason,
177
- triggerId: result.trigger_id
178
- };
179
- }
180
- if ("duplicate_suite_id" in result) {
181
- return {
182
- duplicateSuiteId: result.duplicate_suite_id,
183
- skipReason: result.skip_reason,
184
- triggerId: result.trigger_id
185
- };
217
+ switch (result.outcome) {
218
+ case "created":
219
+ return {
220
+ createdSuiteId: result.created_suite_id,
221
+ triggerId: result.trigger_id
222
+ };
223
+ case "failed":
224
+ return {
225
+ failureCode: result.failure_code,
226
+ failureMessage: result.failure_message,
227
+ failureReason: result.failure_reason,
228
+ triggerId: result.trigger_id
229
+ };
230
+ case "skipped":
231
+ return {
232
+ duplicateSuiteId: result.duplicate_suite_id,
233
+ skipReason: result.skip_reason,
234
+ triggerId: result.trigger_id
235
+ };
236
+ default:
237
+ return { triggerId: result.trigger_id, unrecognized: true };
186
238
  }
187
- return {
188
- triggerId: result.trigger_id,
189
- unrecognized: true
190
- };
191
239
  }
192
240
  __name(mapSuiteResult, "mapSuiteResult");
193
241
  function mapDeploySuccessResponse(wire) {
@@ -203,7 +251,8 @@ function mapDeploySuccessResponse(wire) {
203
251
  message: trigger.message,
204
252
  reason: trigger.reason,
205
253
  triggerId: trigger.trigger_id
206
- }))
254
+ })),
255
+ warning: wire.warning
207
256
  };
208
257
  }
209
258
  __name(mapDeploySuccessResponse, "mapDeploySuccessResponse");
@@ -213,22 +262,32 @@ function isRecord(value) {
213
262
  return typeof value === "object" && value !== null && !Array.isArray(value);
214
263
  }
215
264
  __name(isRecord, "isRecord");
265
+ function isOptionalString(value) {
266
+ return value === void 0 || typeof value === "string";
267
+ }
268
+ __name(isOptionalString, "isOptionalString");
216
269
  function isWireSuiteResult(value) {
217
270
  if (!isRecord(value)) return false;
218
271
  if (typeof value.trigger_id !== "string") return false;
219
- if ("created_suite_id" in value) return typeof value.created_suite_id === "string";
220
- if ("failure_reason" in value) return typeof value.failure_reason === "string";
221
- if ("duplicate_suite_id" in value) {
222
- return typeof value.duplicate_suite_id === "string" && typeof value.skip_reason === "string";
272
+ if (!isOptionalString(value.outcome)) return false;
273
+ switch (value.outcome) {
274
+ case "created":
275
+ return typeof value.created_suite_id === "string";
276
+ case "failed":
277
+ return typeof value.failure_reason === "string" && isOptionalString(value.failure_code) && isOptionalString(value.failure_message);
278
+ case "skipped":
279
+ return typeof value.duplicate_suite_id === "string" && typeof value.skip_reason === "string";
280
+ default:
281
+ return true;
223
282
  }
224
- return true;
225
283
  }
226
284
  __name(isWireSuiteResult, "isWireSuiteResult");
227
285
  function isEnvironment(value) {
228
286
  if (!isRecord(value)) return false;
229
287
  if (typeof value.id !== "string") return false;
230
288
  if (typeof value.isStatic !== "boolean") return false;
231
- if (value.alias !== void 0 && typeof value.alias !== "string") return false;
289
+ if (value.alias !== void 0 && typeof value.alias !== "string")
290
+ return false;
232
291
  return true;
233
292
  }
234
293
  __name(isEnvironment, "isEnvironment");
@@ -244,10 +303,15 @@ function isSkippedTrigger(value) {
244
303
  __name(isSkippedTrigger, "isSkippedTrigger");
245
304
  function isDeploySuccessWireResponse(value) {
246
305
  if (!isRecord(value)) return false;
247
- if (value.environment !== void 0 && !isEnvironment(value.environment)) return false;
248
- if (!Array.isArray(value.results) || !value.results.every(isWireSuiteResult)) return false;
249
- if (value.skipped_deployment !== void 0 && !isSkippedDeployment(value.skipped_deployment)) return false;
250
- if (value.skipped_triggers !== void 0 && (!Array.isArray(value.skipped_triggers) || !value.skipped_triggers.every(isSkippedTrigger))) return false;
306
+ if (value.environment !== void 0 && !isEnvironment(value.environment))
307
+ return false;
308
+ if (!Array.isArray(value.results) || !value.results.every(isWireSuiteResult))
309
+ return false;
310
+ if (value.skipped_deployment !== void 0 && !isSkippedDeployment(value.skipped_deployment))
311
+ return false;
312
+ if (value.skipped_triggers !== void 0 && (!Array.isArray(value.skipped_triggers) || !value.skipped_triggers.every(isSkippedTrigger)))
313
+ return false;
314
+ if (!isOptionalString(value.warning)) return false;
251
315
  return true;
252
316
  }
253
317
  __name(isDeploySuccessWireResponse, "isDeploySuccessWireResponse");
@@ -262,34 +326,38 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
262
326
  let response;
263
327
  let bodyText;
264
328
  try {
265
- response = await localFetch(new URL("/api/webhooks/deploy_success", apiConfig.serviceBase), {
266
- body: JSON.stringify({
267
- branch: config.branch,
268
- commit_url: config.commitUrl,
269
- deduplication_key: config.deduplicationKey,
270
- deployment_type: config.deploymentType,
271
- deployment_url: config.deploymentUrl,
272
- ..."ephemeralEnvironment" in config ? {
273
- ephemeral_environment: config.ephemeralEnvironment
274
- } : "hostingService" in config && {
275
- hosting_service: config.hostingService,
276
- merge_request_number: config.hostingService === "GitLab" ? config.mergeRequestNumber : void 0,
277
- pull_request_number: config.hostingService === "GitHub" ? config.pullRequestNumber : void 0,
278
- repository: config.repository
329
+ response = await localFetch(
330
+ new URL("/api/webhooks/deploy_success", apiConfig.serviceBase),
331
+ {
332
+ body: JSON.stringify({
333
+ branch: config.branch,
334
+ commit_url: config.commitUrl,
335
+ deduplication_key: config.deduplicationKey,
336
+ deployment_type: config.deploymentType,
337
+ deployment_url: config.deploymentUrl,
338
+ ..."ephemeralEnvironment" in config ? {
339
+ ephemeral_environment: config.ephemeralEnvironment
340
+ } : "hostingService" in config && {
341
+ hosting_service: config.hostingService,
342
+ merge_request_number: config.hostingService === "GitLab" ? config.mergeRequestNumber : void 0,
343
+ pull_request_number: config.hostingService === "GitHub" ? config.pullRequestNumber : void 0,
344
+ repository: config.repository
345
+ },
346
+ sha: config.sha,
347
+ variables: config.variables,
348
+ workspaceId: config.workspaceId
349
+ }),
350
+ headers: {
351
+ Authorization: `Bearer ${apiConfig.apiKey}`,
352
+ "Content-Type": "application/json",
353
+ "User-Agent": apiConfig.userAgent
279
354
  },
280
- sha: config.sha,
281
- variables: config.variables
282
- }),
283
- headers: {
284
- Authorization: `Bearer ${apiConfig.apiKey}`,
285
- "Content-Type": "application/json",
286
- "User-Agent": apiConfig.userAgent
287
- },
288
- method: "POST",
289
- // Override default timeout to 5 minutes. This gives extra room
290
- // for both (1) waiting for reconciliation (2) creating the run.
291
- signal: AbortSignal.timeout(5 * 60 * 1e3)
292
- });
355
+ method: "POST",
356
+ // Override default timeout to 5 minutes. This gives extra room
357
+ // for both (1) waiting for reconciliation (2) creating the run.
358
+ signal: AbortSignal.timeout(5 * 60 * 1e3)
359
+ }
360
+ );
293
361
  if (!response.ok) return handleErrorResponse(response, log);
294
362
  bodyText = await response.text();
295
363
  } catch {
@@ -308,7 +376,10 @@ async function attemptNotifyDeploy(deps, apiConfig, config) {
308
376
  }
309
377
  const wire = parseDeploySuccessWireResponse(rawBody);
310
378
  if (wire === "invalid") {
311
- return abortInvalidResponseBody(log, "the response body did not match the expected shape");
379
+ return abortInvalidResponseBody(
380
+ log,
381
+ "the response body did not match the expected shape"
382
+ );
312
383
  }
313
384
  const data = mapDeploySuccessResponse(wire);
314
385
  return handleSuccessResponse(data, log);
@@ -344,17 +415,45 @@ async function handleErrorResponse(response, log) {
344
415
  }
345
416
  __name(handleErrorResponse, "handleErrorResponse");
346
417
 
418
+ // src/lib/api/signed-url.ts
419
+ var signedUrlPaths = {
420
+ runInputsExecutables: "/api/v0/run-inputs-executables-signed-urls",
421
+ teamStorage: "/api/v0/team-storage-signed-url"
422
+ };
423
+ function buildSignedUrl({
424
+ destinationFilePath,
425
+ endpoint,
426
+ serviceBase,
427
+ workspaceId
428
+ }) {
429
+ const url = new URL(signedUrlPaths[endpoint], serviceBase);
430
+ if (destinationFilePath !== void 0)
431
+ url.searchParams.set("file", destinationFilePath);
432
+ if (workspaceId !== void 0)
433
+ url.searchParams.set("workspaceId", workspaceId);
434
+ return url;
435
+ }
436
+ __name(buildSignedUrl, "buildSignedUrl");
437
+
347
438
  // src/lib/api/generate-signed-url-for-run-inputs-executables.ts
348
- async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath }, { fetch: localFetch, log }) {
439
+ async function callGenerateSignedUrlForRunInputsExecutablesStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
349
440
  try {
350
- const response = await localFetch(new URL(`${serviceBase}/api/v0/run-inputs-executables-signed-urls?file=${encodeURIComponent(destinationFilePath)}`), {
351
- headers: {
352
- Authorization: `Bearer ${apiKey}`,
353
- "Content-Type": "application/json",
354
- "User-Agent": userAgent
355
- },
356
- method: "GET"
357
- });
441
+ const response = await localFetch(
442
+ buildSignedUrl({
443
+ destinationFilePath,
444
+ endpoint: "runInputsExecutables",
445
+ serviceBase,
446
+ workspaceId
447
+ }),
448
+ {
449
+ headers: {
450
+ Authorization: `Bearer ${apiKey}`,
451
+ "Content-Type": "application/json",
452
+ "User-Agent": userAgent
453
+ },
454
+ method: "GET"
455
+ }
456
+ );
358
457
  if (response.status === 200) {
359
458
  const json = await response.json();
360
459
  return {
@@ -395,18 +494,28 @@ async function generateSignedUrlForRunInputsExecutablesStorage(deps, apiConfig,
395
494
  success: false
396
495
  };
397
496
  }
398
- const responseState = await callGenerateSignedUrlForRunInputsExecutablesStorage(apiConfig, config, deps);
497
+ const responseState = await callGenerateSignedUrlForRunInputsExecutablesStorage(
498
+ apiConfig,
499
+ config,
500
+ deps
501
+ );
399
502
  if (!responseState.success) {
400
503
  let abortReason = "XXX-other-http-code";
401
504
  if (responseState.httpStatus >= 500) {
402
505
  abortReason = "5XX-server-error";
403
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`);
506
+ log.error(
507
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`
508
+ );
404
509
  } else {
405
510
  if (responseState.httpStatus === 400) abortReason = "400-invalid-request";
406
- else if (responseState.httpStatus === 401) abortReason = "401-invalid-credentials";
511
+ else if (responseState.httpStatus === 401)
512
+ abortReason = "401-invalid-credentials";
407
513
  else if (responseState.httpStatus === 403) abortReason = "403-forbidden";
408
- else if (responseState.httpStatus === 0) abortReason = "client-network-error";
409
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`);
514
+ else if (responseState.httpStatus === 0)
515
+ abortReason = "client-network-error";
516
+ log.error(
517
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`
518
+ );
410
519
  }
411
520
  return {
412
521
  abortReason,
@@ -425,16 +534,24 @@ async function generateSignedUrlForRunInputsExecutablesStorage(deps, apiConfig,
425
534
  __name(generateSignedUrlForRunInputsExecutablesStorage, "generateSignedUrlForRunInputsExecutablesStorage");
426
535
 
427
536
  // src/lib/api/generate-signed-url-for-team-storage.ts
428
- async function callGenerateSignedUrlForTempTeamStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath }, { fetch: localFetch, log }) {
537
+ async function callGenerateSignedUrlForTempTeamStorage({ apiKey, serviceBase, userAgent }, { destinationFilePath, workspaceId }, { fetch: localFetch, log }) {
429
538
  try {
430
- const response = await localFetch(new URL(`${serviceBase}/api/v0/team-storage-signed-url?file=${destinationFilePath}`), {
431
- headers: {
432
- Authorization: `Bearer ${apiKey}`,
433
- "Content-Type": "application/json",
434
- "User-Agent": userAgent
435
- },
436
- method: "GET"
437
- });
539
+ const response = await localFetch(
540
+ buildSignedUrl({
541
+ destinationFilePath,
542
+ endpoint: "teamStorage",
543
+ serviceBase,
544
+ workspaceId
545
+ }),
546
+ {
547
+ headers: {
548
+ Authorization: `Bearer ${apiKey}`,
549
+ "Content-Type": "application/json",
550
+ "User-Agent": userAgent
551
+ },
552
+ method: "GET"
553
+ }
554
+ );
438
555
  if (response.status === 200) {
439
556
  const json = await response.json();
440
557
  return {
@@ -475,18 +592,28 @@ async function generateSignedUrlForTempTeamStorage(deps, apiConfig, config) {
475
592
  success: false
476
593
  };
477
594
  }
478
- const responseState = await callGenerateSignedUrlForTempTeamStorage(apiConfig, config, deps);
595
+ const responseState = await callGenerateSignedUrlForTempTeamStorage(
596
+ apiConfig,
597
+ config,
598
+ deps
599
+ );
479
600
  if (!responseState.success) {
480
601
  let abortReason = "XXX-other-http-code";
481
602
  if (responseState.httpStatus >= 500) {
482
603
  abortReason = "5XX-server-error";
483
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`);
604
+ log.error(
605
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: aborting.`
606
+ );
484
607
  } else {
485
608
  if (responseState.httpStatus === 400) abortReason = "400-invalid-request";
486
- else if (responseState.httpStatus === 401) abortReason = "401-invalid-credentials";
609
+ else if (responseState.httpStatus === 401)
610
+ abortReason = "401-invalid-credentials";
487
611
  else if (responseState.httpStatus === 403) abortReason = "403-forbidden";
488
- else if (responseState.httpStatus === 0) abortReason = "client-network-error";
489
- log.error(`\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`);
612
+ else if (responseState.httpStatus === 0)
613
+ abortReason = "client-network-error";
614
+ log.error(
615
+ `\u{1F6AB} Unrecoverable error (status ${responseState.httpStatus}) when generating signed upload url: ${responseState.errorMessage ? `${responseState.errorMessage}.` : ""} aborting.`
616
+ );
490
617
  }
491
618
  return {
492
619
  abortReason,
@@ -508,21 +635,22 @@ __name(generateSignedUrlForTempTeamStorage, "generateSignedUrlForTempTeamStorage
508
635
  var notifyTerminatedEphemeralEnvironment = /* @__PURE__ */ __name(async (deps, apiConfig, config) => {
509
636
  const { fetch: localFetch, log } = deps;
510
637
  try {
511
- const response = await localFetch(new URL("/api/webhooks/environment_terminated", apiConfig.serviceBase), {
512
- body: JSON.stringify(config),
513
- headers: {
514
- Authorization: `Bearer ${apiConfig.apiKey}`,
515
- "Content-Type": "application/json",
516
- "User-Agent": apiConfig.userAgent
517
- },
518
- method: "POST"
519
- });
638
+ const response = await localFetch(
639
+ new URL("/api/webhooks/environment_terminated", apiConfig.serviceBase),
640
+ {
641
+ body: JSON.stringify(config),
642
+ headers: {
643
+ Authorization: `Bearer ${apiConfig.apiKey}`,
644
+ "Content-Type": "application/json",
645
+ "User-Agent": apiConfig.userAgent
646
+ },
647
+ method: "POST"
648
+ }
649
+ );
520
650
  if (!response.ok) return handleErrorResponse2(response, log);
521
- const { environmentId } = await response.json();
522
- return {
523
- environmentId,
524
- outcome: "success"
525
- };
651
+ const { environmentId, warning } = await response.json();
652
+ if (warning) log.warn(`\u26A0\uFE0F ${warning}`);
653
+ return { environmentId, outcome: "success" };
526
654
  } catch {
527
655
  log.error(`\u{1F6AB} Network error from terminate_preview_environment, aborting.`);
528
656
  return {
@@ -555,14 +683,9 @@ __name(handleErrorResponse2, "handleErrorResponse");
555
683
 
556
684
  // src/lib/sdk/domain/pollCiGreenlight/buildCompletedRunResult.ts
557
685
  function buildCompletedRunResult(status) {
558
- if (status.greenlight) return {
559
- greenlightStatus: status,
560
- outcome: "success"
561
- };
562
- else return {
563
- greenlightStatus: status,
564
- outcome: "failed"
565
- };
686
+ if (status.greenlight)
687
+ return { greenlightStatus: status, outcome: "success" };
688
+ else return { greenlightStatus: status, outcome: "failed" };
566
689
  }
567
690
  __name(buildCompletedRunResult, "buildCompletedRunResult");
568
691
 
@@ -571,20 +694,32 @@ var pollCiGreenlightDefaults = {
571
694
  abortOnSuperseded: false,
572
695
  maxRetries: 10,
573
696
  pollTimeout: 120 * 60 * 1e3,
697
+ // 2 hours
574
698
  retryInterval: 10 * 1e3,
699
+ // 10 seconds
575
700
  runInProgressInterval: 30 * 1e3,
701
+ // 30 seconds
576
702
  runUnderReviewInterval: 60 * 1e3
703
+ // 60 seconds
577
704
  };
578
705
 
579
706
  // src/lib/api/ci-greenlight.ts
580
- async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, { runId }, { fetch: localFetch, log }) {
707
+ async function fetchCiGreenlightStatus({ apiKey, serviceBase, userAgent }, {
708
+ runId
709
+ }, { fetch: localFetch, log }) {
581
710
  try {
582
- const resp = await localFetch(new URL(`/api/v0/ci-greenlight/${encodeURIComponent(runId)}`, serviceBase), {
583
- headers: {
584
- Authorization: `Bearer ${apiKey}`,
585
- "User-Agent": userAgent
711
+ const resp = await localFetch(
712
+ new URL(
713
+ `/api/v0/ci-greenlight/${encodeURIComponent(runId)}`,
714
+ serviceBase
715
+ ),
716
+ {
717
+ headers: {
718
+ Authorization: `Bearer ${apiKey}`,
719
+ "User-Agent": userAgent
720
+ }
586
721
  }
587
- });
722
+ );
588
723
  if (resp.ok) {
589
724
  const greenlightStatus = await resp.json();
590
725
  return {
@@ -634,14 +769,22 @@ __name(fetchCiGreenlightStatus, "fetchCiGreenlightStatus");
634
769
  // src/lib/sdk/domain/pollCiGreenlight/iterator.ts
635
770
  async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...pollConfig }) {
636
771
  const { log } = deps;
637
- const { maxRetries = pollCiGreenlightDefaults.maxRetries, pollTimeout = pollCiGreenlightDefaults.pollTimeout, retryInterval = pollCiGreenlightDefaults.retryInterval, runInProgressInterval = pollCiGreenlightDefaults.runInProgressInterval, runUnderReviewInterval = pollCiGreenlightDefaults.runUnderReviewInterval } = pollConfig;
772
+ const {
773
+ maxRetries = pollCiGreenlightDefaults.maxRetries,
774
+ pollTimeout = pollCiGreenlightDefaults.pollTimeout,
775
+ retryInterval = pollCiGreenlightDefaults.retryInterval,
776
+ runInProgressInterval = pollCiGreenlightDefaults.runInProgressInterval,
777
+ runUnderReviewInterval = pollCiGreenlightDefaults.runUnderReviewInterval
778
+ } = pollConfig;
638
779
  let retries = 0;
639
780
  let previousStatus = void 0;
640
781
  const startedAt = Date.now();
641
782
  while (Date.now() - startedAt <= pollTimeout) {
642
- const responseState = await fetchCiGreenlightStatus(apiConfig, {
643
- runId
644
- }, deps);
783
+ const responseState = await fetchCiGreenlightStatus(
784
+ apiConfig,
785
+ { runId },
786
+ deps
787
+ );
645
788
  const elapsedMs = Date.now() - startedAt;
646
789
  if (!responseState.ok && !responseState.canRetry) {
647
790
  yield {
@@ -688,7 +831,8 @@ async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...
688
831
  }
689
832
  const retryLabel = `${httpStatus ? ` (status ${httpStatus})` : ""}, will try again... (${retries}/${maxRetries})`;
690
833
  if (errorType === "network") log.warn(`\u274C Network error${retryLabel}`);
691
- else if (errorType === "not-found") log.info(`\u23F3 Run not found${retryLabel}`);
834
+ else if (errorType === "not-found")
835
+ log.info(`\u23F3 Run not found${retryLabel}`);
692
836
  else if (errorType === "server") log.warn(`\u274C Server error${retryLabel}`);
693
837
  await sleep(retryInterval);
694
838
  continue;
@@ -725,7 +869,9 @@ async function* makePollCiGreenlightStatusIterator(deps, apiConfig, { runId, ...
725
869
  await sleep(runUnderReviewInterval);
726
870
  break;
727
871
  default:
728
- throw Error(`Unexpected run stage: ${greenlightStatus}`);
872
+ throw Error(
873
+ `Unexpected run stage: ${greenlightStatus}`
874
+ );
729
875
  }
730
876
  }
731
877
  yield {
@@ -742,12 +888,19 @@ __name(makePollCiGreenlightStatusIterator, "makePollCiGreenlightStatusIterator")
742
888
  function buildSuccessLogMessage(status) {
743
889
  const { nonBlockingBugsCount, relevantRunUrl } = status;
744
890
  const urlInfo = `See ${relevantRunUrl}`;
745
- return `\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)} found` : ""}.
891
+ return `\u2705 Run passed and no blocking bugs found${nonBlockingBugsCount > 0 ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(
892
+ nonBlockingBugsCount
893
+ )} found` : ""}.
746
894
  ${urlInfo}`;
747
895
  }
748
896
  __name(buildSuccessLogMessage, "buildSuccessLogMessage");
749
897
  function buildFailedLogMessage(status) {
750
- const { blockingBugsCount, nonBlockingBugsCount, relevantRunWithBugsUrl, reproducedBugs } = status;
898
+ const {
899
+ blockingBugsCount,
900
+ nonBlockingBugsCount,
901
+ relevantRunWithBugsUrl,
902
+ reproducedBugs
903
+ } = status;
751
904
  const blockingBugUrls = reproducedBugs.filter((bug) => bug.isBlocking).map((bug) => bug.applicationUrl);
752
905
  const nonBlockingBugUrls = reproducedBugs.filter((bug) => !bug.isBlocking).map((bug) => bug.applicationUrl);
753
906
  const blockingBugList = blockingBugUrls.map((url) => `
@@ -757,7 +910,11 @@ function buildFailedLogMessage(status) {
757
910
  const bugsListInText = `${blockingBugUrls.length > 0 ? `
758
911
  BLOCKING BUGS:${blockingBugList}` : ""}${nonBlockingBugUrls.length > 0 ? `
759
912
  NON BLOCKING BUGS:${nonBlockingBugList}` : ""}`;
760
- return `\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(blockingBugsCount)} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(nonBlockingBugsCount)}` : ""}.
913
+ return `\u274C Run failed and ${blockingBugsCount} blocking bug${pluralize(
914
+ blockingBugsCount
915
+ )} found${nonBlockingBugsCount ? ` and ${nonBlockingBugsCount} non-blocking bug${pluralize(
916
+ nonBlockingBugsCount
917
+ )}` : ""}.
761
918
  See ${relevantRunWithBugsUrl}
762
919
  You may review the priority of certain bugs to mark these non-blocking and retry the job.
763
920
  ` + bugsListInText;
@@ -788,7 +945,9 @@ ${urlInfo}`;
788
945
  case "underReview":
789
946
  return `\u{1F575}\uFE0F Run is under review... ${urlInfo}`;
790
947
  case "completed":
791
- throw Error("buildRunStageLogMessage should not be called for completed stage");
948
+ throw Error(
949
+ "buildRunStageLogMessage should not be called for completed stage"
950
+ );
792
951
  default:
793
952
  throw Error(`Unexpected run stage: ${runStage}`);
794
953
  }
@@ -801,21 +960,27 @@ function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
801
960
  const { abortReason, httpStatus } = iteration;
802
961
  switch (abortReason) {
803
962
  case "4XX-client-error":
804
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
963
+ log.error(
964
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
965
+ );
805
966
  return {
806
967
  abortReason,
807
968
  httpStatus,
808
969
  outcome: "aborted"
809
970
  };
810
971
  case "5XX-server-error":
811
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
972
+ log.error(
973
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
974
+ );
812
975
  return {
813
976
  abortReason,
814
977
  httpStatus,
815
978
  outcome: "aborted"
816
979
  };
817
980
  case "404-run-not-found":
818
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
981
+ log.error(
982
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
983
+ );
819
984
  return {
820
985
  abortReason,
821
986
  httpStatus,
@@ -843,7 +1008,9 @@ function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
843
1008
  outcome: "aborted"
844
1009
  };
845
1010
  case "XXX-other-http-code":
846
- log.error(`\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`);
1011
+ log.error(
1012
+ `\u{1F6AB} Unrecoverable error (status ${httpStatus}) from CI greenlight, aborting.`
1013
+ );
847
1014
  return {
848
1015
  abortReason,
849
1016
  httpStatus,
@@ -856,15 +1023,23 @@ function mapIteratorAbortToGreenlightStatus(iteration, pollTimeout, deps) {
856
1023
  __name(mapIteratorAbortToGreenlightStatus, "mapIteratorAbortToGreenlightStatus");
857
1024
 
858
1025
  // src/lib/sdk/domain/pollCiGreenlight/index.ts
859
- async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"), runId, ...pollConfig }) {
1026
+ async function pollCiGreenlightStatus(deps, apiConfig, {
1027
+ onRunStageChanged = /* @__PURE__ */ __name(() => void 0, "onRunStageChanged"),
1028
+ runId,
1029
+ ...pollConfig
1030
+ }) {
860
1031
  const { log } = deps;
861
- const { abortOnSuperseded = pollCiGreenlightDefaults.abortOnSuperseded, pollTimeout = pollCiGreenlightDefaults.pollTimeout } = pollConfig;
1032
+ const {
1033
+ abortOnSuperseded = pollCiGreenlightDefaults.abortOnSuperseded,
1034
+ pollTimeout = pollCiGreenlightDefaults.pollTimeout
1035
+ } = pollConfig;
862
1036
  const iterator = makePollCiGreenlightStatusIterator(deps, apiConfig, {
863
1037
  runId,
864
1038
  ...pollConfig
865
1039
  });
866
1040
  for await (const lastPoll of iterator) {
867
- if (lastPoll.isAborted) return mapIteratorAbortToGreenlightStatus(lastPoll, pollTimeout, deps);
1041
+ if (lastPoll.isAborted)
1042
+ return mapIteratorAbortToGreenlightStatus(lastPoll, pollTimeout, deps);
868
1043
  const { previousStatus, stageChanged, status } = lastPoll;
869
1044
  if (stageChanged) {
870
1045
  try {
@@ -877,7 +1052,10 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
877
1052
  const isSuperseded = rootRunId !== relevantRunId;
878
1053
  const shouldAbortOnSuperseded = isSuperseded && abortOnSuperseded;
879
1054
  if (shouldAbortOnSuperseded) {
880
- const message = buildSupersededAbortLogMessage(rootRunUrl, relevantRunUrl);
1055
+ const message = buildSupersededAbortLogMessage(
1056
+ rootRunUrl,
1057
+ relevantRunUrl
1058
+ );
881
1059
  log.warn(message);
882
1060
  return {
883
1061
  abortReason: "superseded-run",
@@ -886,7 +1064,10 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
886
1064
  };
887
1065
  }
888
1066
  if (isSuperseded) {
889
- const message = buildSupersededContinueLogMessage(rootRunUrl, relevantRunUrl);
1067
+ const message = buildSupersededContinueLogMessage(
1068
+ rootRunUrl,
1069
+ relevantRunUrl
1070
+ );
890
1071
  log.info(message);
891
1072
  }
892
1073
  switch (runStage) {
@@ -926,10 +1107,19 @@ async function pollCiGreenlightStatus(deps, apiConfig, { onRunStageChanged = /*
926
1107
  __name(pollCiGreenlightStatus, "pollCiGreenlightStatus");
927
1108
 
928
1109
  // src/lib/sdk/index.ts
929
- function makeQaWolfSdk({ apiKey, serviceBase = defaultServiceBase, userAgent = defaultUserAgent }, { fetch = defaultFetch, log = defaultLogDriver } = {}) {
1110
+ function makeQaWolfSdk({
1111
+ apiKey,
1112
+ serviceBase = defaultServiceBase,
1113
+ userAgent = defaultUserAgent
1114
+ }, {
1115
+ fetch = defaultFetch,
1116
+ log = defaultLogDriver
1117
+ } = {}) {
930
1118
  if (typeof fetch !== "function") {
931
- throw Error(`QA Wolf CI-SDK requires fetch to be defined. Make sure you are using NodeJS 18+, OR pass a fetch polyfill to the makeQaWolfSdk function.
932
- We recommend 'undici' package for that purpose. See the Requirement section of our README for more guidance.`);
1119
+ throw Error(
1120
+ `QA Wolf CI-SDK requires fetch to be defined. Make sure you are using NodeJS 18+, OR pass a fetch polyfill to the makeQaWolfSdk function.
1121
+ We recommend 'undici' package for that purpose. See the Requirement section of our README for more guidance.`
1122
+ );
933
1123
  }
934
1124
  const deps = {
935
1125
  // Versions of fetch may have different default timeouts. For consistency, we explicitly set it to 2 minutes here.
@@ -938,22 +1128,26 @@ We recommend 'undici' package for that purpose. See the Requirement section of o
938
1128
  fetch: buildFetchWithTimeout(fetch, 12e4),
939
1129
  log
940
1130
  };
941
- const apiConfig = {
942
- apiKey,
943
- serviceBase,
944
- userAgent
945
- };
1131
+ const apiConfig = { apiKey, serviceBase, userAgent };
946
1132
  return {
947
1133
  attemptNotifyDeploy: attemptNotifyDeploy.bind(null, deps, apiConfig),
948
- generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(null, deps, apiConfig),
1134
+ generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(
1135
+ null,
1136
+ deps,
1137
+ apiConfig
1138
+ ),
949
1139
  generateSignedUrlForTempTeamStorage: generateSignedUrlForTempTeamStorage.bind(null, deps, apiConfig),
950
- makePollCiGreenlightStatusIterator: makePollCiGreenlightStatusIterator.bind(null, deps, apiConfig),
1140
+ makePollCiGreenlightStatusIterator: makePollCiGreenlightStatusIterator.bind(
1141
+ null,
1142
+ deps,
1143
+ apiConfig
1144
+ ),
951
1145
  notifyTerminatedEphemeralEnvironment: notifyTerminatedEphemeralEnvironment.bind(null, deps, apiConfig),
952
1146
  pollCiGreenlightStatus: pollCiGreenlightStatus.bind(null, deps, apiConfig)
953
1147
  };
954
1148
  }
955
1149
  __name(makeQaWolfSdk, "makeQaWolfSdk");
956
-
957
- export { makeQaWolfSdk };
1150
+ export {
1151
+ makeQaWolfSdk
1152
+ };
958
1153
  //# sourceMappingURL=index.js.map
959
- //# sourceMappingURL=index.js.map