Skip to content

Commit 676e582

Browse files
authored
feat: Upload Stats Information (#14)
Initial implementation of uploading stats information.
1 parent c720138 commit 676e582

30 files changed

+874
-430
lines changed

codecov.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ component_management:
1515
- component_id: package_rollup
1616
name: Rollup plugin
1717
paths:
18-
- packages/plugin-rollup/**
18+
- packages/rollup-plugin/**
1919
- component_id: package_vite
2020
name: Vite plugin
2121
paths:
22-
- packages/plugin-vite/**
22+
- packages/vite-plugin/**
2323
- component_id: package_webpack
2424
name: Webpack plugin
2525
paths:
26-
- packages/plugin-webpack/**
26+
- packages/webpack-plugin/**

packages/bundler-plugin-core/jest.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ const config: Config = {
1212
"!test/helpers.ts",
1313
],
1414
moduleNameMapper: {
15-
"^@/(.*)$": "<rootDir>/src/$1",
1615
"^@test-utils/(.*)$": "<rootDir>/test-utils/$1",
1716
},
1817
transform: {

packages/bundler-plugin-core/src/bundle-analysis/bundleAnalysisPluginFactory.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
type UploadOverrides,
88
} from "../types.ts";
99
import { type UnpluginOptions } from "unplugin";
10-
import { debug } from "../utils/logging.ts";
10+
import { getPreSignedURL } from "../utils/getPreSignedURL.ts";
11+
import { uploadStats } from "../utils/uploadStats.ts";
1112

1213
interface BundleAnalysisUploadPluginArgs {
1314
userOptions: Options;
@@ -25,7 +26,7 @@ export const bundleAnalysisPluginFactory = ({
2526

2627
const { pluginVersion, version, ...pluginOpts } = bundleAnalysisUploadPlugin({
2728
output,
28-
uploaderOverrides: userOptions?.uploaderOverrides,
29+
userOptions,
2930
});
3031

3132
output.version = version;
@@ -43,11 +44,34 @@ export const bundleAnalysisPluginFactory = ({
4344
output.duration = Date.now() - (output.builtAt ?? 0);
4445
},
4546
writeBundle: async () => {
47+
// don't need to do anything here if dryRun is true
48+
if (userOptions?.dryRun) return;
49+
4650
const args: UploadOverrides = userOptions.uploaderOverrides ?? {};
4751
const envs = process.env;
4852
const inputs: ProviderUtilInputs = { envs, args };
4953
const provider = await detectProvider(inputs);
50-
debug(`\nprovider: ${JSON.stringify(provider, null, 2)}`);
54+
55+
let sendStats = true;
56+
let url = "";
57+
try {
58+
url = await getPreSignedURL({
59+
apiURL: "http://localhost:3000",
60+
globalUploadToken: "123",
61+
serviceParams: provider,
62+
});
63+
} catch (error) {
64+
sendStats = false;
65+
}
66+
67+
try {
68+
if (sendStats) {
69+
await uploadStats({
70+
preSignedUrl: url,
71+
message: JSON.stringify(output),
72+
});
73+
}
74+
} catch {}
5175
},
5276
};
5377
};

packages/bundler-plugin-core/src/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
type UploadOverrides,
1212
} from "./types.ts";
1313

14-
import { jsonSchema } from "./schemas.ts";
1514
import { red } from "./utils/logging.ts";
1615
import { bundleAnalysisPluginFactory } from "./bundle-analysis/bundleAnalysisPluginFactory.ts";
1716

@@ -31,7 +30,8 @@ export function codecovUnpluginFactory({
3130
red(
3231
`Codecov ${unpluginMetaContext.framework} bundler plugin requires Node.js ${NODE_VERSION_RANGE}. You are using Node.js ${process.version}. Please upgrade your Node.js version.`,
3332
);
34-
process.exit(1);
33+
34+
return plugins;
3535
}
3636

3737
if (userOptions?.enableBundleAnalysis) {
@@ -56,4 +56,3 @@ export type {
5656
ProviderUtilInputs,
5757
UploadOverrides,
5858
};
59-
export { jsonSchema };

packages/bundler-plugin-core/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export interface Output {
4646

4747
export interface BundleAnalysisUploadPluginArgs {
4848
output: Output;
49-
uploaderOverrides?: UploadOverrides;
49+
userOptions: Options;
5050
}
5151

5252
export interface Options {

packages/bundler-plugin-core/src/utils/__tests__/fetchWithRetry.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { setupServer } from "msw/node";
33

44
import { fetchWithRetry } from "../fetchWithRetry";
55

6-
jest.mock("@/utils/delay");
6+
jest.mock("../delay.ts");
77

88
const server = setupServer();
99

packages/bundler-plugin-core/src/utils/__tests__/uploadStats.test.ts

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { setupServer } from "msw/node";
33

44
import { uploadStats } from "../uploadStats";
55
import { FailedUploadError } from "../../errors/FailedUploadError";
6+
import { FailedFetchError } from "../../errors/FailedFetchError";
7+
import { UploadLimitReachedError } from "../../errors/UploadLimitReachedError";
68

79
const server = setupServer();
810

@@ -20,10 +22,11 @@ afterAll(() => {
2022

2123
interface SetupArgs {
2224
sendError?: boolean;
25+
status?: number;
2326
}
2427

2528
describe("uploadStats", () => {
26-
function setup({ sendError = false }: SetupArgs) {
29+
function setup({ sendError = false, status = 200 }: SetupArgs) {
2730
const consoleSpy = jest
2831
.spyOn(console, "log")
2932
.mockImplementation(() => null);
@@ -40,7 +43,7 @@ describe("uploadStats", () => {
4043
}
4144

4245
if (!sendError) {
43-
return HttpResponse.json({}, { status: 200 });
46+
return HttpResponse.json({}, { status });
4447
}
4548

4649
return HttpResponse.error();
@@ -52,33 +55,69 @@ describe("uploadStats", () => {
5255
};
5356
}
5457

55-
describe("on a successful upload", () => {
56-
it("returns a 200", async () => {
57-
setup({});
58-
const preSignedUrl = "http://localhost/upload/stats/";
59-
const message = JSON.stringify({ some: "cool", stats: true });
58+
describe("on a successful request", () => {
59+
it("returns true", async () => {
60+
setup({ sendError: false });
6061

61-
const response = await uploadStats({ message, preSignedUrl });
62+
const data = await uploadStats({
63+
message: "cool-message",
64+
preSignedUrl: "http://localhost/upload/stats/",
65+
});
6266

63-
expect(response.status).toEqual(200);
67+
expect(data).toBeTruthy();
6468
});
6569
});
6670

67-
describe("on a failed upload", () => {
68-
it("throws a FailedUploadError", async () => {
69-
const { consoleSpy } = setup({ sendError: true });
70-
const preSignedUrl = "http://localhost/upload/stats/";
71-
const message = JSON.stringify({ some: "cool", stats: true });
72-
73-
let error;
74-
try {
75-
await uploadStats({ message, preSignedUrl });
76-
} catch (e) {
77-
error = e;
78-
}
79-
80-
expect(consoleSpy).toHaveBeenCalled();
81-
expect(error).toBeInstanceOf(FailedUploadError);
71+
describe("on an unsuccessful request", () => {
72+
describe("fetch fails", () => {
73+
it("throws a FailedFetchError", async () => {
74+
setup({ sendError: true });
75+
76+
let error;
77+
try {
78+
await uploadStats({ message: "cool-message", preSignedUrl: "" });
79+
} catch (e) {
80+
error = e;
81+
}
82+
83+
expect(error).toBeInstanceOf(FailedFetchError);
84+
});
85+
});
86+
87+
describe("upload limit is reached", () => {
88+
it("throws an UploadLimitReachedError", async () => {
89+
setup({ status: 429, sendError: false });
90+
91+
let error;
92+
try {
93+
await uploadStats({
94+
message: "cool-message",
95+
preSignedUrl: "http://localhost/upload/stats/",
96+
});
97+
} catch (e) {
98+
error = e;
99+
}
100+
101+
expect(error).toBeInstanceOf(UploadLimitReachedError);
102+
});
103+
});
104+
105+
describe("response is not ok", () => {
106+
it("throws a FailedUploadError", async () => {
107+
setup({ sendError: false, status: 400 });
108+
109+
let error;
110+
try {
111+
await uploadStats({
112+
message: "cool-message",
113+
preSignedUrl: "http://localhost/upload/stats/",
114+
});
115+
} catch (e) {
116+
error = e;
117+
}
118+
119+
expect(error).toBeInstanceOf(FailedUploadError);
120+
});
82121
});
83122
});
84123
});

packages/bundler-plugin-core/src/utils/fetchWithRetry.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,28 @@ interface FetchWithRetryArgs {
66
url: string;
77
retryCount: number;
88
requestData: RequestInit;
9+
name?: string;
910
}
1011

1112
export const fetchWithRetry = async ({
1213
url,
1314
retryCount,
1415
requestData,
16+
name,
1517
}: FetchWithRetryArgs) => {
1618
let response = new Response(null, { status: 400 });
1719

1820
for (let i = 0; i < retryCount + 1; i++) {
1921
try {
20-
debug(`Attempting to fetch number: ${i}`);
22+
debug(`Attempting to fetch ${name}, attempt: ${i}`);
2123
await delay(DEFAULT_RETRY_DELAY * i);
2224
response = await fetch(url, requestData);
2325
break;
2426
} catch (err) {
25-
debug(`Fetch attempt number ${i} failed`);
27+
debug(`${name} fetch attempt ${i} failed`);
2628
const isLastAttempt = i + 1 === retryCount;
2729
if (isLastAttempt) {
28-
red(`Fetch failed after ${i} attempts`);
30+
red(`${name} failed after ${i} attempts`);
2931
throw err;
3032
}
3133
}

packages/bundler-plugin-core/src/utils/getPreSignedURL.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { UploadLimitReachedError } from "../errors/UploadLimitReachedError.ts";
66
import { type ProviderServiceParams } from "../types.ts";
77
import { DEFAULT_RETRY_COUNT } from "./constants.ts";
88
import { fetchWithRetry } from "./fetchWithRetry.ts";
9-
import { red, yellow } from "./logging.ts";
9+
import { green, red, yellow } from "./logging.ts";
1010
import { preProcessBody } from "./preProcessBody.ts";
1111

1212
interface GetPreSignedURLArgs {
@@ -44,6 +44,7 @@ export const getPreSignedURL = async ({
4444
response = await fetchWithRetry({
4545
url,
4646
retryCount,
47+
name: "`get-pre-signed-url`",
4748
requestData: {
4849
method: "POST",
4950
headers: {
@@ -54,43 +55,44 @@ export const getPreSignedURL = async ({
5455
},
5556
});
5657
} catch (e) {
57-
red(`Failed to get pre-signed URL: ${e}`);
58-
throw new FailedFetchError("Failed to get pre-signed URL");
58+
red("Failed to fetch pre-signed URL");
59+
throw new FailedFetchError("Failed to fetch pre-signed URL");
5960
}
6061

6162
if (response.status === 429) {
62-
red(`Upload limit reached`);
63+
red("Upload limit reached");
6364
throw new UploadLimitReachedError("Upload limit reached");
6465
}
6566

6667
if (!response.ok) {
67-
red(`Failed to get pre-signed URL`);
68+
red("Failed to get pre-signed URL, bad response");
6869
throw new FailedFetchError("Failed to get pre-signed URL");
6970
}
7071

7172
let data;
7273
try {
7374
data = await response.json();
7475
} catch (e) {
75-
red(`Failed to get pre-signed URL`);
76-
throw new FailedFetchError("Failed to get pre-signed URL");
76+
red("Failed to parse pre-signed URL body");
77+
throw new FailedFetchError("Failed to parse pre-signed URL body");
7778
}
7879

7980
const parsedData = PreSignedURLSchema.safeParse(data);
8081

8182
if (!parsedData.success) {
82-
red(`Failed to get pre-signed URL`);
83-
throw new FailedFetchError("Failed to get pre-signed URL");
83+
red("Failed to validate pre-signed URL");
84+
throw new FailedFetchError("Failed to validate pre-signed URL");
8485
}
8586

87+
green(`Successfully pre-signed URL fetched`);
8688
return parsedData.data.url;
8789
};
8890

8991
const getToken = (
9092
globalUploadToken: string | undefined,
9193
repoToken: string | undefined,
9294
) => {
93-
if (globalUploadToken && !repoToken) {
95+
if (globalUploadToken && repoToken) {
9496
yellow(
9597
"Both globalUploadToken and repoToken found, Using globalUploadToken",
9698
);

packages/bundler-plugin-core/src/utils/providers/__tests__/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ describe("CI Providers", () => {
5555
args: {
5656
...createEmptyArgs(),
5757
...{
58+
branch: "main",
5859
sha: "123",
5960
slug: "testOrg/testRepo",
6061
},

0 commit comments

Comments
 (0)