@argos-ci/core 5.3.0 → 6.0.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 (2) hide show
  1. package/dist/index.mjs +111 -26
  2. package/package.json +6 -6
package/dist/index.mjs CHANGED
@@ -996,10 +996,7 @@ async function uploadFile(input) {
996
996
  const file = await readFile(input.path);
997
997
  const response = await fetch(input.url, {
998
998
  method: "PUT",
999
- headers: {
1000
- "Content-Type": input.contentType,
1001
- "Content-Length": file.length.toString()
1002
- },
999
+ headers: { "Content-Type": input.contentType },
1003
1000
  signal: AbortSignal.timeout(3e4),
1004
1001
  body: new Uint8Array(file)
1005
1002
  });
@@ -1020,28 +1017,116 @@ const chunk = (collection, size) => {
1020
1017
  return result;
1021
1018
  };
1022
1019
  //#endregion
1023
- //#region src/auth.ts
1020
+ //#region src/github-actions-oidc.ts
1021
+ /**
1022
+ * Check if GitHub Actions OIDC is available for auto-detection.
1023
+ */
1024
+ function isGitHubActionsOidcAvailable() {
1025
+ return process.env.GITHUB_ACTIONS === "true" && Boolean(process.env.ACTIONS_ID_TOKEN_REQUEST_URL) && Boolean(process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN) && !process.env.ARGOS_TOKEN;
1026
+ }
1027
+ async function fetchOidcToken(args) {
1028
+ if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) throw new Error(`ACTIONS_ID_TOKEN_REQUEST_URL not found`);
1029
+ if (!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN) throw new Error(`ACTIONS_ID_TOKEN_REQUEST_TOKEN not found`);
1030
+ const url = new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);
1031
+ url.searchParams.set("audience", args.audience);
1032
+ const response = await fetch(url.toString(), { headers: {
1033
+ Authorization: `Bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`,
1034
+ Accept: "application/json; api-version=2.0",
1035
+ "Content-Type": "application/json"
1036
+ } });
1037
+ if (!response.ok) throw new Error(`Failed to fetch GitHub Actions OIDC token: ${response.status} ${response.statusText}`);
1038
+ const data = await response.json();
1039
+ if (!data.value) throw new Error("Invalid GitHub Actions OIDC token response: missing 'value' field");
1040
+ return data.value;
1041
+ }
1042
+ /**
1043
+ * Exchange a GitHub Actions OIDC token for a short-lived Argos token.
1044
+ */
1045
+ async function exchangeGitHubActionsOidcToken(args) {
1046
+ const { apiBaseUrl, config } = args;
1047
+ const audience = new URL(apiBaseUrl).origin;
1048
+ const oidcToken = await fetchOidcToken({ audience });
1049
+ const result = await createClient({ baseUrl: apiBaseUrl }).POST("/auth/github-actions/oidc/exchange", { body: {
1050
+ oidcToken,
1051
+ repository: config.originalRepository ?? void 0,
1052
+ commit: config.commit,
1053
+ branch: config.branch,
1054
+ pullRequestNumber: config.prNumber ?? void 0
1055
+ } });
1056
+ if (result.error) throwAPIError(result.error);
1057
+ return result.data.token;
1058
+ }
1059
+ //#endregion
1060
+ //#region src/github-actions-tokenless.ts
1024
1061
  const base64Encode = (obj) => Buffer.from(JSON.stringify(obj), "utf8").toString("base64");
1025
1062
  /**
1026
- * Get the authentication token.
1063
+ * Check if GitHub Actions tokenless authentication is available for auto-detection.
1027
1064
  */
1028
- function getAuthToken(args) {
1029
- const { token, ciProvider, originalRepository: repository, jobId, runId, prNumber } = args;
1030
- if (token) return token;
1031
- switch (ciProvider) {
1032
- case "github-actions": {
1033
- if (!repository || !jobId || !runId) throw new Error(`Automatic GitHub Actions variables detection failed. Please add the 'ARGOS_TOKEN'`);
1034
- const [owner, repo] = repository.split("/");
1035
- return `tokenless-github-${base64Encode({
1036
- owner,
1037
- repository: repo,
1038
- jobId,
1039
- runId,
1040
- prNumber: prNumber ?? void 0
1041
- })}`;
1042
- }
1043
- default: throw new Error("Missing Argos repository token 'ARGOS_TOKEN'");
1065
+ function isGitHubActionsTokenlessAvailable(config) {
1066
+ return Boolean(config.ciProvider === "github-actions" && config.prHeadCommit && !process.env.ARGOS_TOKEN);
1067
+ }
1068
+ /**
1069
+ * Build a tokenless GitHub Actions bearer token from the CI environment.
1070
+ */
1071
+ function getTokenlessBearerToken(config) {
1072
+ const { originalRepository: repository, jobId, runId, prNumber } = config;
1073
+ if (!repository || !jobId || !runId) throw new Error(`Automatic GitHub Actions variables detection failed. Please set ARGOS_TOKEN.`);
1074
+ const [owner, repo] = repository.split("/");
1075
+ return `tokenless-github-${base64Encode({
1076
+ owner,
1077
+ repository: repo,
1078
+ jobId,
1079
+ runId,
1080
+ prNumber: prNumber ?? void 0
1081
+ })}`;
1082
+ }
1083
+ /**
1084
+ * Exchange a tokenless GitHub Actions bearer token for a short-lived Argos token.
1085
+ */
1086
+ async function exchangeGitHubActionsTokenlessToken(args) {
1087
+ const { apiBaseUrl, config } = args;
1088
+ if (!config.prHeadCommit) throw new Error(`GitHub PR head commit is required for tokenless authentication.`);
1089
+ const tokenlessToken = getTokenlessBearerToken(config);
1090
+ const result = await createClient({ baseUrl: apiBaseUrl }).POST("/auth/github-actions/tokenless/exchange", { body: {
1091
+ tokenlessToken,
1092
+ commit: config.prHeadCommit,
1093
+ branch: config.branch
1094
+ } });
1095
+ if (result.error) throwAPIError(result.error);
1096
+ return result.data.token;
1097
+ }
1098
+ //#endregion
1099
+ //#region src/auth.ts
1100
+ /**
1101
+ * Resolve the Argos authentication token.
1102
+ * Priority: ARGOS_TOKEN > GitHub Actions OIDC > GitHub Actions tokenless exchange.
1103
+ */
1104
+ async function resolveArgosToken(config) {
1105
+ if (config.token) {
1106
+ debug("Authenticated with ARGOS_TOKEN.");
1107
+ return config.token;
1108
+ }
1109
+ if (isGitHubActionsOidcAvailable()) {
1110
+ const token = await exchangeGitHubActionsOidcToken({
1111
+ apiBaseUrl: config.apiBaseUrl,
1112
+ config
1113
+ });
1114
+ debug("Authenticated with GitHub Actions OIDC.");
1115
+ debug(`Repository: ${config.originalRepository}`);
1116
+ debug(`Run: ${config.runId}`);
1117
+ return token;
1118
+ }
1119
+ if (isGitHubActionsTokenlessAvailable(config)) {
1120
+ const token = await exchangeGitHubActionsTokenlessToken({
1121
+ apiBaseUrl: config.apiBaseUrl,
1122
+ config
1123
+ });
1124
+ debug("Authenticated with GitHub Actions tokenless exchange.");
1125
+ debug(`Repository: ${config.originalRepository}`);
1126
+ debug(`Run: ${config.runId}`);
1127
+ return token;
1044
1128
  }
1129
+ throw new Error("Missing Argos repository token 'ARGOS_TOKEN'");
1045
1130
  }
1046
1131
  //#endregion
1047
1132
  //#region src/deploy.ts
@@ -1056,7 +1141,7 @@ async function deploy(params) {
1056
1141
  const { token: _token, ...debugParams } = params;
1057
1142
  debug("Starting deploy with params", debugParams);
1058
1143
  const config = await getConfigFromOptions(params);
1059
- const authToken = getAuthToken(config);
1144
+ const authToken = await resolveArgosToken(config);
1060
1145
  const apiClient = createClient({
1061
1146
  baseUrl: config.apiBaseUrl,
1062
1147
  authToken
@@ -1128,7 +1213,7 @@ async function deploy(params) {
1128
1213
  */
1129
1214
  async function finalize(params) {
1130
1215
  const config = await readConfig({ parallelNonce: params.parallel?.nonce });
1131
- const authToken = getAuthToken(config);
1216
+ const authToken = await resolveArgosToken(config);
1132
1217
  const apiClient = createClient({
1133
1218
  baseUrl: config.apiBaseUrl,
1134
1219
  authToken
@@ -1245,7 +1330,7 @@ function getSnapshotMimeType(filepath) {
1245
1330
  */
1246
1331
  async function skip(params) {
1247
1332
  const [config, argosSdk] = await Promise.all([getConfigFromOptions(params), getArgosCoreSDKIdentifier()]);
1248
- const authToken = getAuthToken(config);
1333
+ const authToken = await resolveArgosToken(config);
1249
1334
  const createBuildResponse = await createClient({
1250
1335
  baseUrl: config.apiBaseUrl,
1251
1336
  authToken
@@ -1282,7 +1367,7 @@ const CHUNK_SIZE = 10;
1282
1367
  async function upload(params) {
1283
1368
  debug("Starting upload with params", params);
1284
1369
  const [config, argosSdk] = await Promise.all([getConfigFromOptions(params), getArgosCoreSDKIdentifier()]);
1285
- const authToken = getAuthToken(config);
1370
+ const authToken = await resolveArgosToken(config);
1286
1371
  const apiClient = createClient({
1287
1372
  baseUrl: config.apiBaseUrl,
1288
1373
  authToken
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@argos-ci/core",
3
3
  "description": "Node.js SDK for visual testing with Argos.",
4
- "version": "5.3.0",
4
+ "version": "6.0.0",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -23,7 +23,7 @@
23
23
  "url": "https://github.com/argos-ci/argos-javascript/issues"
24
24
  },
25
25
  "engines": {
26
- "node": ">=20.0.0"
26
+ "node": ">=22.0.0"
27
27
  },
28
28
  "license": "MIT",
29
29
  "keywords": [
@@ -39,8 +39,8 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@argos-ci/api-client": "0.19.0",
43
- "@argos-ci/util": "3.4.0",
42
+ "@argos-ci/api-client": "0.20.0",
43
+ "@argos-ci/util": "4.0.0",
44
44
  "convict": "^6.2.5",
45
45
  "debug": "^4.4.3",
46
46
  "fast-glob": "^3.3.3",
@@ -56,7 +56,7 @@
56
56
  "@types/node": "catalog:",
57
57
  "@types/tmp": "^0.2.6",
58
58
  "@vercel/repository-dispatch": "^0.1.0",
59
- "msw": "^2.12.14",
59
+ "msw": "^2.14.5",
60
60
  "vitest": "catalog:"
61
61
  },
62
62
  "scripts": {
@@ -67,5 +67,5 @@
67
67
  "lint": "eslint .",
68
68
  "test": "vitest"
69
69
  },
70
- "gitHead": "9ff7241b23dce530910d0fffd113db23e2fbe949"
70
+ "gitHead": "357c71173989a18d98767bb8811272cb46ecf937"
71
71
  }