@argos-ci/core 5.2.1 → 5.2.2-alpha.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/dist/index.cjs CHANGED
@@ -12,3 +12,13 @@ exports.readConfig = async (...args) => {
12
12
  const { readConfig } = await import("./index.mjs");
13
13
  return readConfig(...args);
14
14
  };
15
+
16
+ exports.deploy = async (...args) => {
17
+ const { deploy } = await import("./index.mjs");
18
+ return deploy(...args);
19
+ };
20
+
21
+ exports.skip = async (...args) => {
22
+ const { skip } = await import("./index.mjs");
23
+ return skip(...args);
24
+ };
package/dist/index.d.mts CHANGED
@@ -139,6 +139,52 @@ declare function getConfigFromOptions({
139
139
  } | false | undefined;
140
140
  }): Promise<Config>;
141
141
  //#endregion
142
+ //#region src/deploy.d.ts
143
+ interface DeployParameters {
144
+ /**
145
+ * Directory containing the static files to deploy.
146
+ */
147
+ root: string;
148
+ /**
149
+ * Argos repository access token.
150
+ */
151
+ token?: string;
152
+ /**
153
+ * Base URL of the Argos API.
154
+ * @default "https://api.argos-ci.com/v2/"
155
+ */
156
+ apiBaseUrl?: string;
157
+ /**
158
+ * Git commit SHA of the deployment.
159
+ */
160
+ commit?: string;
161
+ /**
162
+ * Git branch name of the deployment.
163
+ */
164
+ branch?: string;
165
+ /**
166
+ * Pull request number associated with the deployment.
167
+ */
168
+ prNumber?: number;
169
+ /**
170
+ * Deployment environment.
171
+ * @default "preview"
172
+ */
173
+ environment?: "preview" | "production";
174
+ }
175
+ /**
176
+ * Deploy a static site (e.g. Storybook) to Argos.
177
+ */
178
+ declare function deploy(params: DeployParameters): Promise<{
179
+ id: string;
180
+ status: "pending" | "ready" | "error";
181
+ environment: "preview" | "production";
182
+ branch: string;
183
+ commitSha: string;
184
+ url: string;
185
+ createdAt: string;
186
+ }>;
187
+ //#endregion
142
188
  //#region src/finalize.d.ts
143
189
  type FinalizeParameters = {
144
190
  parallel?: {
@@ -322,4 +368,4 @@ declare function skip(params: SkipParameters): Promise<{
322
368
  build: ArgosAPISchema.components["schemas"]["Build"];
323
369
  }>;
324
370
  //#endregion
325
- export { Config, FinalizeParameters, UploadParameters, finalize, getConfigFromOptions, readConfig, skip, upload };
371
+ export { Config, DeployParameters, FinalizeParameters, UploadParameters, deploy, finalize, getConfigFromOptions, readConfig, skip, upload };
package/dist/index.mjs CHANGED
@@ -3,16 +3,16 @@ import convict from "convict";
3
3
  import { execSync } from "node:child_process";
4
4
  import createDebug from "debug";
5
5
  import { createReadStream, existsSync, readFileSync } from "node:fs";
6
- import { createClient, throwAPIError } from "@argos-ci/api-client";
7
- import { basename, extname, resolve } from "node:path";
6
+ import { readFile, stat } from "node:fs/promises";
7
+ import { basename, extname, join, resolve } from "node:path";
8
8
  import glob from "fast-glob";
9
+ import mime from "mime-types";
10
+ import { createClient, throwAPIError } from "@argos-ci/api-client";
11
+ import { createHash } from "node:crypto";
9
12
  import { promisify } from "node:util";
10
13
  import sharp from "sharp";
11
14
  import tmp from "tmp";
12
- import { createHash } from "node:crypto";
13
- import { readFile } from "node:fs/promises";
14
15
  import { getPlaywrightTracePath, readMetadata, readVersionFromPackage } from "@argos-ci/util";
15
- import mime from "mime-types";
16
16
  //#region src/debug.ts
17
17
  const KEY = "@argos-ci/core";
18
18
  const debug = createDebug(KEY);
@@ -978,6 +978,48 @@ async function getConfigFromOptions({ parallel, ...options }) {
978
978
  });
979
979
  }
980
980
  //#endregion
981
+ //#region src/hashing.ts
982
+ const hashFile = async (filepath) => {
983
+ const fileStream = createReadStream(filepath);
984
+ const hash = createHash("sha256");
985
+ await new Promise((resolve, reject) => {
986
+ fileStream.on("error", reject);
987
+ hash.on("error", reject);
988
+ hash.on("finish", resolve);
989
+ fileStream.pipe(hash);
990
+ });
991
+ return hash.digest("hex");
992
+ };
993
+ //#endregion
994
+ //#region src/s3.ts
995
+ async function uploadFile(input) {
996
+ const file = await readFile(input.path);
997
+ const response = await fetch(input.url, {
998
+ method: "PUT",
999
+ headers: {
1000
+ "Content-Type": input.contentType,
1001
+ "Content-Length": file.length.toString()
1002
+ },
1003
+ signal: AbortSignal.timeout(3e4),
1004
+ body: new Uint8Array(file)
1005
+ });
1006
+ if (!response.ok) throw new Error(`Failed to upload file to ${input.url}: ${response.status} ${response.statusText}`);
1007
+ }
1008
+ //#endregion
1009
+ //#region src/util/chunk.ts
1010
+ /**
1011
+ * Split an array into chunks of a given size.
1012
+ */
1013
+ const chunk = (collection, size) => {
1014
+ const result = [];
1015
+ for (let x = 0; x < Math.ceil(collection.length / size); x++) {
1016
+ const start = x * size;
1017
+ const end = start + size;
1018
+ result.push(collection.slice(start, end));
1019
+ }
1020
+ return result;
1021
+ };
1022
+ //#endregion
981
1023
  //#region src/auth.ts
982
1024
  const base64Encode = (obj) => Buffer.from(JSON.stringify(obj), "utf8").toString("base64");
983
1025
  /**
@@ -1002,6 +1044,84 @@ function getAuthToken(args) {
1002
1044
  }
1003
1045
  }
1004
1046
  //#endregion
1047
+ //#region src/deploy.ts
1048
+ const CHUNK_SIZE$1 = 10;
1049
+ function getContentType(filePath) {
1050
+ return mime.lookup(filePath) || "application/octet-stream";
1051
+ }
1052
+ /**
1053
+ * Deploy a static site (e.g. Storybook) to Argos.
1054
+ */
1055
+ async function deploy(params) {
1056
+ const { token: _token, ...debugParams } = params;
1057
+ debug("Starting deploy with params", debugParams);
1058
+ const config = await getConfigFromOptions(params);
1059
+ const authToken = getAuthToken(config);
1060
+ const apiClient = createClient({
1061
+ baseUrl: config.apiBaseUrl,
1062
+ authToken
1063
+ });
1064
+ debug("Listing files in", params.root);
1065
+ const relativePaths = await glob("**/*", {
1066
+ cwd: params.root,
1067
+ onlyFiles: true,
1068
+ dot: true
1069
+ });
1070
+ if (relativePaths.length === 0) throw new Error(`No files found in directory: ${params.root}`);
1071
+ debug(`Found ${relativePaths.length} files`);
1072
+ const files = await Promise.all(relativePaths.map(async (relativePath) => {
1073
+ const absolutePath = join(params.root, relativePath);
1074
+ const [hash, stats] = await Promise.all([hashFile(absolutePath), stat(absolutePath)]);
1075
+ return {
1076
+ absolutePath,
1077
+ path: relativePath,
1078
+ hash,
1079
+ size: stats.size,
1080
+ contentType: getContentType(absolutePath)
1081
+ };
1082
+ }));
1083
+ const filesByPath = new Map(files.map((file) => [file.path, file]));
1084
+ debug("Creating deployment");
1085
+ const createResponse = await apiClient.POST("/deployments", { body: {
1086
+ commit: config.commit ?? null,
1087
+ branch: config.branch ?? null,
1088
+ prNumber: config.prNumber ?? null,
1089
+ environment: params.environment,
1090
+ files: files.map(({ path, hash, size, contentType }) => ({
1091
+ path,
1092
+ hash,
1093
+ size,
1094
+ contentType
1095
+ }))
1096
+ } });
1097
+ if (createResponse.error) throwAPIError(createResponse.error);
1098
+ const { deploymentId, uploadFiles: filesToUpload } = createResponse.data;
1099
+ debug(`Deployment created: ${deploymentId}, files to upload: ${filesToUpload.length}`);
1100
+ const uploadChunks = chunk(filesToUpload.map(({ path, uploadUrl }) => {
1101
+ const file = filesByPath.get(path);
1102
+ if (!file) throw new Error(`Invariant: file not found for path: ${path}`);
1103
+ return {
1104
+ url: uploadUrl,
1105
+ path: file.absolutePath,
1106
+ contentType: file.contentType
1107
+ };
1108
+ }), CHUNK_SIZE$1);
1109
+ for (let i = 0; i < uploadChunks.length; i++) {
1110
+ const uploadChunk = uploadChunks[i];
1111
+ if (!uploadChunk) continue;
1112
+ debug(`Uploading chunk ${i + 1}/${uploadChunks.length}`);
1113
+ await Promise.all(uploadChunk.map(({ url, path, contentType }) => uploadFile({
1114
+ url,
1115
+ path,
1116
+ contentType
1117
+ })));
1118
+ }
1119
+ debug("Finalizing deployment");
1120
+ const finalizeResponse = await apiClient.POST("/deployments/{deploymentId}/finalize", { params: { path: { deploymentId } } });
1121
+ if (finalizeResponse.error) throwAPIError(finalizeResponse.error);
1122
+ return finalizeResponse.data;
1123
+ }
1124
+ //#endregion
1005
1125
  //#region src/finalize.ts
1006
1126
  /**
1007
1127
  * Finalize pending builds.
@@ -1100,48 +1220,6 @@ async function optimizeScreenshot(filepath) {
1100
1220
  }
1101
1221
  }
1102
1222
  //#endregion
1103
- //#region src/hashing.ts
1104
- const hashFile = async (filepath) => {
1105
- const fileStream = createReadStream(filepath);
1106
- const hash = createHash("sha256");
1107
- await new Promise((resolve, reject) => {
1108
- fileStream.on("error", reject);
1109
- hash.on("error", reject);
1110
- hash.on("finish", resolve);
1111
- fileStream.pipe(hash);
1112
- });
1113
- return hash.digest("hex");
1114
- };
1115
- //#endregion
1116
- //#region src/s3.ts
1117
- async function uploadFile(input) {
1118
- const file = await readFile(input.path);
1119
- const response = await fetch(input.url, {
1120
- method: "PUT",
1121
- headers: {
1122
- "Content-Type": input.contentType,
1123
- "Content-Length": file.length.toString()
1124
- },
1125
- signal: AbortSignal.timeout(3e4),
1126
- body: new Uint8Array(file)
1127
- });
1128
- if (!response.ok) throw new Error(`Failed to upload file to ${input.url}: ${response.status} ${response.statusText}`);
1129
- }
1130
- //#endregion
1131
- //#region src/util/chunk.ts
1132
- /**
1133
- * Split an array into chunks of a given size.
1134
- */
1135
- const chunk = (collection, size) => {
1136
- const result = [];
1137
- for (let x = 0; x < Math.ceil(collection.length / size); x++) {
1138
- const start = x * size;
1139
- const end = start + size;
1140
- result.push(collection.slice(start, end));
1141
- }
1142
- return result;
1143
- };
1144
- //#endregion
1145
1223
  //#region src/version.ts
1146
1224
  const require = createRequire(import.meta.url);
1147
1225
  /**
@@ -1386,4 +1464,4 @@ function formatPreviewUrl(url, formatter) {
1386
1464
  return new URL(urlObj.pathname + urlObj.search + urlObj.hash, formatter.baseUrl).href;
1387
1465
  }
1388
1466
  //#endregion
1389
- export { finalize, getConfigFromOptions, readConfig, skip, upload };
1467
+ export { deploy, finalize, getConfigFromOptions, readConfig, skip, upload };
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.2.1",
4
+ "version": "5.2.2-alpha.0+4f1cfa9",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -39,8 +39,8 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@argos-ci/api-client": "0.18.0",
43
- "@argos-ci/util": "3.4.0",
42
+ "@argos-ci/api-client": "0.18.1-alpha.0+4f1cfa9",
43
+ "@argos-ci/util": "3.4.1-alpha.11+4f1cfa9",
44
44
  "convict": "^6.2.5",
45
45
  "debug": "^4.4.3",
46
46
  "fast-glob": "^3.3.3",
@@ -67,5 +67,5 @@
67
67
  "lint": "eslint .",
68
68
  "test": "vitest"
69
69
  },
70
- "gitHead": "906c65c34de9cadf237cee1bf14a925dd960b6c6"
70
+ "gitHead": "4f1cfa9db40cef760779b553b622524abcf6b199"
71
71
  }