@qawolf/ci-sdk 2.8.0 → 3.1.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.
- package/README.md +281 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +227 -12
- package/dist/index.js.map +4 -4
- package/dist/lib/sdk/domain/attemptDeploy/index.d.ts +4 -0
- package/dist/lib/sdk/domain/detectProviderDeploymentId/index.d.ts +49 -0
- package/dist/lib/sdk/domain/pollCiGreenlight/index.d.ts +4 -0
- package/dist/lib/sdk/domain/reportDeployment/index.d.ts +4 -0
- package/dist/lib/sdk/domain/reportDeployment/types.d.ts +50 -0
- package/dist/lib/sdk/index.d.ts +35 -0
- package/dist/lib/sdk/publicApi/callContract.d.ts +39 -0
- package/package.json +11 -11
- package/dist/index.cjs +0 -1185
- package/dist/index.cjs.map +0 -7
package/dist/index.js
CHANGED
|
@@ -1,7 +1,89 @@
|
|
|
1
|
-
/* Built with @qawolf/platform-builder@1.2.
|
|
1
|
+
/* Built with @qawolf/platform-builder@1.2.7 */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
4
|
|
|
5
|
+
// src/lib/sdk/domain/detectProviderDeploymentId/index.ts
|
|
6
|
+
var ciSystems = [
|
|
7
|
+
{
|
|
8
|
+
ci: "github",
|
|
9
|
+
isActive: /* @__PURE__ */ __name((env) => env.GITHUB_ACTIONS === "true", "isActive"),
|
|
10
|
+
// GITHUB_RUN_ID is stable across re-runs and GITHUB_RUN_ATTEMPT increments,
|
|
11
|
+
// and both are shared by every job in the workflow, so GITHUB_JOB separates
|
|
12
|
+
// jobs that deploy in parallel. GITHUB_JOB names the job definition, which
|
|
13
|
+
// every leg of a matrix shares, so those legs need a discriminator.
|
|
14
|
+
variables: ["GITHUB_RUN_ID", "GITHUB_RUN_ATTEMPT", "GITHUB_JOB"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
ci: "gitlab",
|
|
18
|
+
isActive: /* @__PURE__ */ __name((env) => env.GITLAB_CI === "true", "isActive"),
|
|
19
|
+
// Retrying a job keeps the pipeline id and mints a new job id.
|
|
20
|
+
variables: ["CI_PIPELINE_ID", "CI_JOB_ID"]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
ci: "circleci",
|
|
24
|
+
isActive: /* @__PURE__ */ __name((env) => env.CIRCLECI === "true", "isActive"),
|
|
25
|
+
// A rerun keeps the workflow id and allocates a new build number. A job
|
|
26
|
+
// run with `parallelism` shares both across its containers, so those need
|
|
27
|
+
// a discriminator.
|
|
28
|
+
variables: ["CIRCLE_WORKFLOW_ID", "CIRCLE_BUILD_NUM"]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
ci: "buildkite",
|
|
32
|
+
isActive: /* @__PURE__ */ __name((env) => env.BUILDKITE === "true", "isActive"),
|
|
33
|
+
// BUILDKITE_BUILD_ID covers the whole build, so BUILDKITE_JOB_ID separates
|
|
34
|
+
// the jobs within it and BUILDKITE_RETRY_COUNT separates a job's retries.
|
|
35
|
+
variables: [
|
|
36
|
+
"BUILDKITE_BUILD_ID",
|
|
37
|
+
"BUILDKITE_JOB_ID",
|
|
38
|
+
"BUILDKITE_RETRY_COUNT"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
ci: "jenkins",
|
|
43
|
+
// JENKINS_URL is unset on some agent configurations.
|
|
44
|
+
isActive: /* @__PURE__ */ __name((env) => env.JENKINS_URL !== void 0 || env.JENKINS_HOME !== void 0, "isActive"),
|
|
45
|
+
// BUILD_TAG is `jenkins-${JOB_NAME}-${BUILD_NUMBER}`, and Jenkins allocates
|
|
46
|
+
// a fresh BUILD_NUMBER for every externally visible re-run — Rebuild,
|
|
47
|
+
// Naginator, Replay and Restart from Stage — so it needs no attempt
|
|
48
|
+
// counter. An in-pipeline `retry {}` reuses it, but reports nothing new.
|
|
49
|
+
// A declarative `matrix` or `parallel` block runs its branches inside one
|
|
50
|
+
// build and so shares BUILD_TAG; those branches need a discriminator.
|
|
51
|
+
variables: ["BUILD_TAG"]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
ci: "bitbucket",
|
|
55
|
+
isActive: /* @__PURE__ */ __name((env) => env.BITBUCKET_WORKSPACE !== void 0 || env.BITBUCKET_BUILD_NUMBER !== void 0, "isActive"),
|
|
56
|
+
// Rerunning a whole pipeline mints a new build number, but rerunning only
|
|
57
|
+
// the failed steps keeps it and increments the step's run number instead.
|
|
58
|
+
// Parallel steps share both, so BITBUCKET_STEP_UUID separates them.
|
|
59
|
+
variables: [
|
|
60
|
+
"BITBUCKET_BUILD_NUMBER",
|
|
61
|
+
"BITBUCKET_STEP_UUID",
|
|
62
|
+
"BITBUCKET_STEP_RUN_NUMBER"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
function detectProviderDeploymentId({
|
|
67
|
+
discriminator,
|
|
68
|
+
env = process.env
|
|
69
|
+
} = {}) {
|
|
70
|
+
const system = ciSystems.find((candidate) => candidate.isActive(env));
|
|
71
|
+
if (!system) return { outcome: "unsupported" };
|
|
72
|
+
const missingVariables = system.variables.filter((name) => !env[name]);
|
|
73
|
+
if (missingVariables.length > 0)
|
|
74
|
+
return { ci: system.ci, missingVariables, outcome: "missing-variables" };
|
|
75
|
+
const composed = system.variables.map((name) => env[name]).join("-");
|
|
76
|
+
return {
|
|
77
|
+
ci: system.ci,
|
|
78
|
+
// A colon separates the discriminator because a job id can itself contain
|
|
79
|
+
// a hyphen, so joining with one would let job "deploy-web" collide with
|
|
80
|
+
// job "deploy" discriminated by "web".
|
|
81
|
+
id: discriminator ? `${composed}:${discriminator}` : composed,
|
|
82
|
+
outcome: "detected"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
__name(detectProviderDeploymentId, "detectProviderDeploymentId");
|
|
86
|
+
|
|
5
87
|
// src/lib/other.ts
|
|
6
88
|
function sleep(ms) {
|
|
7
89
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -38,7 +120,7 @@ var defaultServiceBase = "https://app.qawolf.com/";
|
|
|
38
120
|
// package.json
|
|
39
121
|
var package_default = {
|
|
40
122
|
name: "@qawolf/ci-sdk",
|
|
41
|
-
version: "
|
|
123
|
+
version: "3.1.0",
|
|
42
124
|
private: false,
|
|
43
125
|
description: "A simple SDK for interacting with QAWolf in CI scripts.",
|
|
44
126
|
keywords: [],
|
|
@@ -53,33 +135,33 @@ var package_default = {
|
|
|
53
135
|
exports: {
|
|
54
136
|
".": {
|
|
55
137
|
types: "./dist/index.d.ts",
|
|
56
|
-
|
|
57
|
-
import: "./dist/index.js",
|
|
58
|
-
default: "./dist/index.js"
|
|
138
|
+
import: "./dist/index.js"
|
|
59
139
|
}
|
|
60
140
|
},
|
|
61
141
|
scripts: {
|
|
62
|
-
build: "platform-builder --config platformBuilder.config.ts
|
|
142
|
+
build: "platform-builder --config platformBuilder.config.ts",
|
|
63
143
|
"lint:ci": "npm run lint:first-half; FIRST_HALF_EXIT=$?; npm run lint:second-half && exit $FIRST_HALF_EXIT",
|
|
64
144
|
"lint:dev": "npm run lint:first-half",
|
|
65
|
-
"lint:first-half": "
|
|
66
|
-
"lint:second-half": "
|
|
145
|
+
"lint:first-half": "oxlint . --fix --quiet --report-unused-disable-directives --type-aware && oxfmt --write .",
|
|
146
|
+
"lint:second-half": "rev-dep config run",
|
|
67
147
|
prepublishOnly: "nx run ci-sdk:build",
|
|
68
148
|
test: 'NODE_OPTIONS="--experimental-vm-modules" NODE_NO_WARNINGS=1 jest',
|
|
69
149
|
"tsc:check": "tsc --noEmit"
|
|
70
150
|
},
|
|
71
151
|
dependencies: {
|
|
72
|
-
"@qawolf/
|
|
152
|
+
"@qawolf/api-contracts": ">=0.68.0",
|
|
153
|
+
"@qawolf/ci-utils": "^2.0.0",
|
|
73
154
|
"@sinonjs/fake-timers": "^10.3.0",
|
|
74
|
-
tslib: "^2.6.2"
|
|
155
|
+
tslib: "^2.6.2",
|
|
156
|
+
zod: "^4.1.11"
|
|
75
157
|
},
|
|
76
158
|
devDependencies: {
|
|
77
|
-
"@qawolf/platform-builder": "^1.2.
|
|
159
|
+
"@qawolf/platform-builder": "^1.2.7",
|
|
78
160
|
tsx: "^4.19.4",
|
|
79
161
|
typescript: "^5.9.3"
|
|
80
162
|
},
|
|
81
163
|
engines: {
|
|
82
|
-
node: ">=
|
|
164
|
+
node: ">=24.14.1 <25"
|
|
83
165
|
}
|
|
84
166
|
};
|
|
85
167
|
|
|
@@ -1113,6 +1195,127 @@ async function pollCiGreenlightStatus(deps, apiConfig, {
|
|
|
1113
1195
|
}
|
|
1114
1196
|
__name(pollCiGreenlightStatus, "pollCiGreenlightStatus");
|
|
1115
1197
|
|
|
1198
|
+
// src/lib/sdk/domain/reportDeployment/index.ts
|
|
1199
|
+
import { publicContractsV1 } from "@qawolf/api-contracts";
|
|
1200
|
+
|
|
1201
|
+
// src/lib/sdk/publicApi/callContract.ts
|
|
1202
|
+
function isRecord2(value) {
|
|
1203
|
+
return typeof value === "object" && value !== null;
|
|
1204
|
+
}
|
|
1205
|
+
__name(isRecord2, "isRecord");
|
|
1206
|
+
function isSuperJsonEnvelope(data) {
|
|
1207
|
+
return "json" in data && Object.keys(data).every((key) => key === "json" || key === "meta");
|
|
1208
|
+
}
|
|
1209
|
+
__name(isSuperJsonEnvelope, "isSuperJsonEnvelope");
|
|
1210
|
+
function unwrapTrpcEnvelope(body) {
|
|
1211
|
+
if (!isRecord2(body) || !isRecord2(body.result)) return void 0;
|
|
1212
|
+
const data = body.result.data;
|
|
1213
|
+
return isRecord2(data) && isSuperJsonEnvelope(data) ? data.json : data;
|
|
1214
|
+
}
|
|
1215
|
+
__name(unwrapTrpcEnvelope, "unwrapTrpcEnvelope");
|
|
1216
|
+
function readTrpcError(body) {
|
|
1217
|
+
const wrapped = isRecord2(body) ? body.error : void 0;
|
|
1218
|
+
const error = isRecord2(wrapped) && isRecord2(wrapped.json) ? wrapped.json : wrapped;
|
|
1219
|
+
if (!isRecord2(error)) return { eventId: void 0, message: void 0 };
|
|
1220
|
+
const data = isRecord2(error.data) ? error.data : void 0;
|
|
1221
|
+
return {
|
|
1222
|
+
eventId: typeof data?.eventId === "string" ? data.eventId : void 0,
|
|
1223
|
+
message: typeof error.message === "string" ? error.message : void 0
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
__name(readTrpcError, "readTrpcError");
|
|
1227
|
+
function parseJson(text) {
|
|
1228
|
+
if (text === void 0) return void 0;
|
|
1229
|
+
try {
|
|
1230
|
+
return JSON.parse(text);
|
|
1231
|
+
} catch {
|
|
1232
|
+
return void 0;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
__name(parseJson, "parseJson");
|
|
1236
|
+
async function readHttpError(response, contractName, log) {
|
|
1237
|
+
const bodyText = await response.text().catch(() => void 0);
|
|
1238
|
+
const { eventId, message } = readTrpcError(parseJson(bodyText));
|
|
1239
|
+
const statusText = response.status >= 500 ? "Server Error" : response.statusText || "Unknown Error";
|
|
1240
|
+
const eventIdSuffix = eventId ? ` (Event ID: ${eventId})` : "";
|
|
1241
|
+
const messageSuffix = message ? `: ${message}` : "";
|
|
1242
|
+
log.error(
|
|
1243
|
+
`\u274C [${contractName}] ${response.status} ${statusText}${eventIdSuffix}${messageSuffix}`
|
|
1244
|
+
);
|
|
1245
|
+
return {
|
|
1246
|
+
eventId,
|
|
1247
|
+
httpStatus: response.status,
|
|
1248
|
+
message,
|
|
1249
|
+
outcome: "http-error"
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
__name(readHttpError, "readHttpError");
|
|
1253
|
+
async function callPublicContract({ fetch: localFetch, log }, apiConfig, {
|
|
1254
|
+
contract,
|
|
1255
|
+
input
|
|
1256
|
+
}) {
|
|
1257
|
+
const procedurePath = `public.${contract.name}`;
|
|
1258
|
+
let bodyText;
|
|
1259
|
+
try {
|
|
1260
|
+
const response = await localFetch(
|
|
1261
|
+
new URL(`/api/trpc/${procedurePath}`, apiConfig.serviceBase),
|
|
1262
|
+
{
|
|
1263
|
+
// The server's superjson transformer reads the input from `json`.
|
|
1264
|
+
body: JSON.stringify({ json: input }),
|
|
1265
|
+
headers: {
|
|
1266
|
+
Authorization: `Bearer ${apiConfig.apiKey}`,
|
|
1267
|
+
"Content-Type": "application/json",
|
|
1268
|
+
"User-Agent": apiConfig.userAgent
|
|
1269
|
+
},
|
|
1270
|
+
method: "POST"
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
1273
|
+
if (!response.ok) return readHttpError(response, contract.name, log);
|
|
1274
|
+
bodyText = await response.text();
|
|
1275
|
+
} catch {
|
|
1276
|
+
log.error(`\u{1F6AB} Network error from ${contract.name}, aborting.`);
|
|
1277
|
+
return { outcome: "network-error" };
|
|
1278
|
+
}
|
|
1279
|
+
const parsed = contract.output.safeParse(
|
|
1280
|
+
unwrapTrpcEnvelope(parseJson(bodyText))
|
|
1281
|
+
);
|
|
1282
|
+
if (!parsed.success) {
|
|
1283
|
+
log.error(
|
|
1284
|
+
`\u{1F6AB} Invalid response from ${contract.name} (${parsed.error.issues[0]?.message ?? "the response body did not match the contract"}), aborting.`
|
|
1285
|
+
);
|
|
1286
|
+
return { outcome: "invalid-response-body" };
|
|
1287
|
+
}
|
|
1288
|
+
return { data: parsed.data, outcome: "ok" };
|
|
1289
|
+
}
|
|
1290
|
+
__name(callPublicContract, "callPublicContract");
|
|
1291
|
+
|
|
1292
|
+
// src/lib/sdk/domain/reportDeployment/index.ts
|
|
1293
|
+
async function reportDeployment(deps, apiConfig, params) {
|
|
1294
|
+
const result = await callPublicContract(deps, apiConfig, {
|
|
1295
|
+
contract: publicContractsV1.deployment.reportStatus,
|
|
1296
|
+
input: params
|
|
1297
|
+
});
|
|
1298
|
+
if (result.outcome !== "ok") return mapFailure(result);
|
|
1299
|
+
const { deployment } = result.data;
|
|
1300
|
+
deps.log.info(
|
|
1301
|
+
`\u2705 Deployment ${deployment.id} reported as ${deployment.status}: ${deployment.url}`
|
|
1302
|
+
);
|
|
1303
|
+
return { deployment, outcome: "reported" };
|
|
1304
|
+
}
|
|
1305
|
+
__name(reportDeployment, "reportDeployment");
|
|
1306
|
+
function mapFailure(result) {
|
|
1307
|
+
if (result.outcome !== "http-error") return { outcome: result.outcome };
|
|
1308
|
+
const { httpStatus, message } = result;
|
|
1309
|
+
if (httpStatus === 400) return { message, outcome: "invalid-request" };
|
|
1310
|
+
if (httpStatus === 401 || httpStatus === 403)
|
|
1311
|
+
return { message, outcome: "unauthorized" };
|
|
1312
|
+
if (httpStatus === 404) return { message, outcome: "not-found" };
|
|
1313
|
+
if (httpStatus === 409) return { message, outcome: "conflict" };
|
|
1314
|
+
if (httpStatus >= 500) return { httpStatus, outcome: "server-error" };
|
|
1315
|
+
return { httpStatus, message, outcome: "unexpected-status" };
|
|
1316
|
+
}
|
|
1317
|
+
__name(mapFailure, "mapFailure");
|
|
1318
|
+
|
|
1116
1319
|
// src/lib/sdk/index.ts
|
|
1117
1320
|
function makeQaWolfSdk({
|
|
1118
1321
|
apiKey,
|
|
@@ -1137,7 +1340,14 @@ We recommend 'undici' package for that purpose. See the Requirement section of o
|
|
|
1137
1340
|
};
|
|
1138
1341
|
const apiConfig = { apiKey, serviceBase, userAgent };
|
|
1139
1342
|
return {
|
|
1343
|
+
/**
|
|
1344
|
+
* @deprecated Reports through the legacy `deploy_success` webhook, which
|
|
1345
|
+
* triggers do not see. Use `deployment.reportStatus` instead.
|
|
1346
|
+
*/
|
|
1140
1347
|
attemptNotifyDeploy: attemptNotifyDeploy.bind(null, deps, apiConfig),
|
|
1348
|
+
deployment: {
|
|
1349
|
+
reportStatus: reportDeployment.bind(null, deps, apiConfig)
|
|
1350
|
+
},
|
|
1141
1351
|
generateSignedUrlForRunInputsExecutablesStorage: generateSignedUrlForRunInputsExecutablesStorage.bind(
|
|
1142
1352
|
null,
|
|
1143
1353
|
deps,
|
|
@@ -1150,11 +1360,16 @@ We recommend 'undici' package for that purpose. See the Requirement section of o
|
|
|
1150
1360
|
apiConfig
|
|
1151
1361
|
),
|
|
1152
1362
|
notifyTerminatedEphemeralEnvironment: notifyTerminatedEphemeralEnvironment.bind(null, deps, apiConfig),
|
|
1363
|
+
/**
|
|
1364
|
+
* @deprecated Polls a run id that only `attemptNotifyDeploy` returns, so it
|
|
1365
|
+
* keeps the pipeline on the legacy `deploy_success` webhook.
|
|
1366
|
+
*/
|
|
1153
1367
|
pollCiGreenlightStatus: pollCiGreenlightStatus.bind(null, deps, apiConfig)
|
|
1154
1368
|
};
|
|
1155
1369
|
}
|
|
1156
1370
|
__name(makeQaWolfSdk, "makeQaWolfSdk");
|
|
1157
1371
|
export {
|
|
1372
|
+
detectProviderDeploymentId,
|
|
1158
1373
|
makeQaWolfSdk
|
|
1159
1374
|
};
|
|
1160
1375
|
//# sourceMappingURL=index.js.map
|