diff --git a/.changeset/rare-cats-reflect.md b/.changeset/rare-cats-reflect.md new file mode 100644 index 00000000..483ee292 --- /dev/null +++ b/.changeset/rare-cats-reflect.md @@ -0,0 +1,8 @@ +--- +"@codecov/bundler-plugin-core": patch +"@codecov/rollup-plugin": patch +"@codecov/vite-plugin": patch +"@codecov/webpack-plugin": patch +--- + +Add Sentry in to collect traces and metrics" diff --git a/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v3.config.cjs b/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v3.config.cjs index 883a2cd5..33c81e34 100644 --- a/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v3.config.cjs +++ b/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v3.config.cjs @@ -17,6 +17,7 @@ module.exports = defineConfig({ resolve(), // tells Rollup how to find date-fns in node_modules commonjs(), // converts date-fns to ES modules codecovRollupPlugin({ + telemetry: false, enableBundleAnalysis: true, bundleName: "test-rollup-v3", uploadToken: "test-token", diff --git a/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v4.config.cjs b/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v4.config.cjs index 6edb5c72..dc0fab23 100644 --- a/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v4.config.cjs +++ b/integration-tests/fixtures/generate-bundle-stats/rollup/rollup-v4.config.cjs @@ -17,6 +17,7 @@ module.exports = defineConfig({ resolve(), // tells Rollup how to find date-fns in node_modules commonjs(), // converts date-fns to ES modules codecovRollupPlugin({ + telemetry: false, enableBundleAnalysis: true, bundleName: "test-rollup-v4", uploadToken: "test-token", diff --git a/integration-tests/fixtures/generate-bundle-stats/vite/vite-v4.config.ts b/integration-tests/fixtures/generate-bundle-stats/vite/vite-v4.config.ts index b6ca5c9b..a47c7beb 100644 --- a/integration-tests/fixtures/generate-bundle-stats/vite/vite-v4.config.ts +++ b/integration-tests/fixtures/generate-bundle-stats/vite/vite-v4.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ }, plugins: [ codecovVitePlugin({ + telemetry: false, enableBundleAnalysis: true, bundleName: "test-vite-v4", uploadToken: "test-token", diff --git a/integration-tests/fixtures/generate-bundle-stats/vite/vite-v5.config.ts b/integration-tests/fixtures/generate-bundle-stats/vite/vite-v5.config.ts index 69abf5c5..fcc47a04 100644 --- a/integration-tests/fixtures/generate-bundle-stats/vite/vite-v5.config.ts +++ b/integration-tests/fixtures/generate-bundle-stats/vite/vite-v5.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ }, plugins: [ codecovVitePlugin({ + telemetry: false, enableBundleAnalysis: true, bundleName: "test-vite-v5", uploadToken: "test-token", diff --git a/integration-tests/fixtures/generate-bundle-stats/webpack/webpack-v5.config.cjs b/integration-tests/fixtures/generate-bundle-stats/webpack/webpack-v5.config.cjs index 822a8760..0f64e7d4 100644 --- a/integration-tests/fixtures/generate-bundle-stats/webpack/webpack-v5.config.cjs +++ b/integration-tests/fixtures/generate-bundle-stats/webpack/webpack-v5.config.cjs @@ -13,6 +13,7 @@ module.exports = { mode: "production", plugins: [ codecovWebpackPlugin({ + telemetry: false, enableBundleAnalysis: true, bundleName: "test-webpack-v5", uploadToken: "test-token", diff --git a/packages/bundler-plugin-core/build.config.ts b/packages/bundler-plugin-core/build.config.ts index 725888c7..1517b17d 100644 --- a/packages/bundler-plugin-core/build.config.ts +++ b/packages/bundler-plugin-core/build.config.ts @@ -1,5 +1,6 @@ import { defineBuildConfig } from "unbuild"; import { codecovRollupPlugin } from "codecovProdRollupPlugin"; +import packageJson from "./package.json"; export default defineBuildConfig({ entries: ["./src/index"], @@ -16,13 +17,19 @@ export default defineBuildConfig({ esbuild: { minify: true, }, + replace: { + preventAssignment: true, + values: { + __PACKAGE_VERSION__: JSON.stringify(packageJson.version), + }, + }, }, hooks: { "rollup:options": (_ctx, opts) => { if (process.env.PLUGIN_CODECOV_TOKEN && Array.isArray(opts.plugins)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call opts.plugins = [ ...opts.plugins, + // eslint-disable-next-line @typescript-eslint/no-unsafe-call codecovRollupPlugin({ enableBundleAnalysis: typeof process.env.PLUGIN_CODECOV_TOKEN === "string", diff --git a/packages/bundler-plugin-core/package.json b/packages/bundler-plugin-core/package.json index e440a243..e6608c09 100644 --- a/packages/bundler-plugin-core/package.json +++ b/packages/bundler-plugin-core/package.json @@ -36,6 +36,7 @@ "test:unit:ci": "jest --coverage" }, "dependencies": { + "@sentry/node": "^7.100.1", "chalk": "4.1.2", "semver": "^7.5.4", "unplugin": "^1.6.0", diff --git a/packages/bundler-plugin-core/src/bundle-analysis/__tests__/bundleAnalysisPluginFactory.test.ts b/packages/bundler-plugin-core/src/bundle-analysis/__tests__/bundleAnalysisPluginFactory.test.ts index 47f118dd..6d560e1d 100644 --- a/packages/bundler-plugin-core/src/bundle-analysis/__tests__/bundleAnalysisPluginFactory.test.ts +++ b/packages/bundler-plugin-core/src/bundle-analysis/__tests__/bundleAnalysisPluginFactory.test.ts @@ -1,3 +1,4 @@ +import { type UnpluginContextMeta } from "unplugin"; import { bundleAnalysisPluginFactory } from "../bundleAnalysisPluginFactory"; describe("bundleAnalysisPluginFactory", () => { @@ -10,12 +11,18 @@ describe("bundleAnalysisPluginFactory", () => { enableBundleAnalysis: true, retryCount: 3, uploadToken: "test-token", + telemetry: false, }, bundleAnalysisUploadPlugin: () => ({ version: "1", name: "plugin-name", pluginVersion: "1.0.0", }), + unpluginMetaContext: {} as UnpluginContextMeta, + sentryMetrics: undefined, + handleRecoverableError() { + return; + }, }); expect(plugin).toMatchSnapshot(); diff --git a/packages/bundler-plugin-core/src/bundle-analysis/bundleAnalysisPluginFactory.ts b/packages/bundler-plugin-core/src/bundle-analysis/bundleAnalysisPluginFactory.ts index 9fad99d9..b7a63dac 100644 --- a/packages/bundler-plugin-core/src/bundle-analysis/bundleAnalysisPluginFactory.ts +++ b/packages/bundler-plugin-core/src/bundle-analysis/bundleAnalysisPluginFactory.ts @@ -1,24 +1,32 @@ -import { type UnpluginOptions } from "unplugin"; import { type BundleAnalysisUploadPlugin, type Output, type ProviderUtilInputs, type UploadOverrides, } from "../types.ts"; +import { type UnpluginContextMeta, type UnpluginOptions } from "unplugin"; import { getPreSignedURL } from "../utils/getPreSignedURL.ts"; +import { uploadStats } from "../utils/uploadStats.ts"; +import { type SentryMetrics } from "../sentry.ts"; import { type NormalizedOptions } from "../utils/normalizeOptions.ts"; import { detectProvider } from "../utils/provider.ts"; -import { uploadStats } from "../utils/uploadStats.ts"; import { sendSentryBundleStats } from "../utils/sentryUtils.ts"; +import { createGauge } from "../utils/fetchWithRetry.ts"; interface BundleAnalysisUploadPluginArgs { options: NormalizedOptions; + unpluginMetaContext: UnpluginContextMeta; bundleAnalysisUploadPlugin: BundleAnalysisUploadPlugin; + sentryMetrics: SentryMetrics; + handleRecoverableError: (error: unknown) => void; } export const bundleAnalysisPluginFactory = ({ options, + unpluginMetaContext, bundleAnalysisUploadPlugin, + sentryMetrics, + handleRecoverableError, }: BundleAnalysisUploadPluginArgs): UnpluginOptions => { const output: Output = { version: "1", @@ -67,25 +75,62 @@ export const bundleAnalysisPluginFactory = ({ const provider = await detectProvider(inputs); let url = ""; + const gauge = createGauge({ + bundler: unpluginMetaContext.framework, + sentryMetrics, + }); + const getPreSignedURLStart = Date.now(); try { url = await getPreSignedURL({ - apiURL: options?.apiUrl ?? "https://api.codecov.io", + apiURL: options?.apiUrl, uploadToken: options?.uploadToken, serviceParams: provider, retryCount: options?.retryCount, + gauge, + }); + sentryMetrics?.increment("request_presigned_url.success", 1, "none", { + bundler: unpluginMetaContext.framework, }); } catch (error) { + sentryMetrics?.increment("request_presigned_url.error", 1, "none", { + bundler: unpluginMetaContext.framework, + }); + + handleRecoverableError(error); return; + } finally { + sentryMetrics?.distribution( + "request_presigned_url", + Date.now() - getPreSignedURLStart, + "millisecond", + { bundler: unpluginMetaContext.framework }, + ); } + const uploadStart = Date.now(); try { await uploadStats({ preSignedUrl: url, bundleName: output.bundleName, message: JSON.stringify(output), retryCount: options?.retryCount, + gauge, }); - } catch {} + sentryMetrics?.increment("upload_bundle_stats.success", 1, "none", { + bundler: unpluginMetaContext.framework, + }); + } catch (error) { + sentryMetrics?.increment("upload_bundle_stats.error", 1); + handleRecoverableError(error); + return; + } finally { + sentryMetrics?.distribution( + "upload_bundle_stats", + Date.now() - uploadStart, + "millisecond", + { bundler: unpluginMetaContext.framework }, + ); + } }, }; }; diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index d7cb0d39..c438372c 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -14,7 +14,12 @@ import { import { red } from "./utils/logging.ts"; import { normalizePath } from "./utils/normalizePath.ts"; import { bundleAnalysisPluginFactory } from "./bundle-analysis/bundleAnalysisPluginFactory.ts"; -import { normalizeOptions } from "./utils/normalizeOptions.ts"; +import { + normalizeOptions, + type NormalizedOptions, +} from "./utils/normalizeOptions.ts"; +import { createSentryInstance } from "./sentry.ts"; +import { telemetryPlugin } from "./plugins/telemetry.ts"; const NODE_VERSION_RANGE = ">=18.18.0"; @@ -37,6 +42,8 @@ function codecovUnpluginFactory({ return []; } + const options = normalizedOptions.options; + if (!satisfies(process.version, NODE_VERSION_RANGE)) { red( `Codecov ${unpluginMetaContext.framework} bundler plugin requires Node.js ${NODE_VERSION_RANGE}. You are using Node.js ${process.version}. Please upgrade your Node.js version.`, @@ -45,12 +52,64 @@ function codecovUnpluginFactory({ return plugins; } - const options = normalizedOptions.options; + const { sentryHub, sentryMetrics, sentryClient } = createSentryInstance( + options, + unpluginMetaContext.framework, + ); + + const sentrySession = sentryHub?.startSession(); + sentryHub?.captureSession(); + + let sentEndSession = false; // Just to prevent infinite loops with beforeExit, which is called whenever the event loop empties out + // We also need to manually end sessions on errors because beforeExit is not called on crashes + process.on("beforeExit", () => { + if (!sentEndSession) { + sentryHub?.endSession(); + sentEndSession = true; + } + }); + + function handleRecoverableError(unknownError: unknown) { + if (sentrySession) { + sentrySession.status = "abnormal"; + try { + if (options.errorHandler) { + try { + if (unknownError instanceof Error) { + options.errorHandler(unknownError); + } else { + options.errorHandler(new Error("An unknown error occurred")); + } + } catch (e) { + sentrySession.status = "crashed"; + throw e; + } + } else { + sentrySession.status = "crashed"; + throw unknownError; + } + } finally { + sentryHub?.endSession(); + } + } + } + + plugins.push( + telemetryPlugin({ + sentryClient, + sentryHub, + shouldSendTelemetry: options.telemetry, + }), + ); + if (options?.enableBundleAnalysis) { plugins.push( bundleAnalysisPluginFactory({ options, + unpluginMetaContext, bundleAnalysisUploadPlugin, + sentryMetrics, + handleRecoverableError, }), ); } @@ -68,6 +127,7 @@ export type { ProviderUtilInputs, UploadOverrides, Output, + NormalizedOptions, }; export { normalizePath, codecovUnpluginFactory, red }; diff --git a/packages/bundler-plugin-core/src/plugins/telemetry.ts b/packages/bundler-plugin-core/src/plugins/telemetry.ts new file mode 100644 index 00000000..9a14509e --- /dev/null +++ b/packages/bundler-plugin-core/src/plugins/telemetry.ts @@ -0,0 +1,31 @@ +import { type Hub, type NodeClient } from "@sentry/node"; +import { type UnpluginOptions } from "unplugin"; +import { dim } from "../utils/logging"; +import { safeFlushTelemetry } from "../sentry"; + +interface TelemetryPluginOptions { + sentryHub?: Hub; + sentryClient?: NodeClient; + shouldSendTelemetry: boolean; +} + +export function telemetryPlugin({ + sentryHub, + sentryClient, + shouldSendTelemetry, +}: TelemetryPluginOptions): UnpluginOptions { + return { + name: "codecov-telemetry-plugin", + async buildStart() { + if (shouldSendTelemetry && sentryHub && sentryClient) { + dim( + "Sending error and performance telemetry data to Sentry. To disable telemetry, set `options.telemetry` to `false`.", + ); + sentryHub + .startTransaction({ name: "Codecov Bundler Plugin execution" }) + .finish(); + await safeFlushTelemetry(sentryClient); + } + }, + }; +} diff --git a/packages/bundler-plugin-core/src/sentry.ts b/packages/bundler-plugin-core/src/sentry.ts new file mode 100644 index 00000000..09737586 --- /dev/null +++ b/packages/bundler-plugin-core/src/sentry.ts @@ -0,0 +1,149 @@ +import { + defaultStackParser, + makeNodeTransport, + NodeClient, + Hub, + metrics, +} from "@sentry/node"; +import { type Options } from "./types"; +import { type NormalizedOptions } from "./utils/normalizeOptions"; +import { type Primitive } from "zod"; + +export type SentryClient = ReturnType< + typeof createSentryInstance +>["sentryClient"]; + +export type SentryMetrics = ReturnType< + typeof createSentryInstance +>["sentryMetrics"]; + +export const createSentryInstance = ( + options: NormalizedOptions, + bundler: string, +) => { + const telemetry = options.telemetry ?? true; + + if (telemetry === false || !!options.dryRun) { + return { + sentryClient: undefined, + sentryHub: undefined, + sentryMetrics: undefined, + }; + } + + const client = new NodeClient({ + dsn: "https://942e283ea612c29cc3371c6d27f57e58@o26192.ingest.sentry.io/4506739665207296", + + _experiments: { + metricsAggregator: true, + }, + + tracesSampleRate: 1, + sampleRate: 1, + + debug: true, + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + // @ts-expect-error this value is being replaced by rollup + release: __PACKAGE_VERSION__ as string, + integrations: [metrics.metricsAggregatorIntegration()], + tracePropagationTargets: ["api.codecov.io", "stage-api.codecov.dev"], + + stackParser: defaultStackParser, + + beforeSend: (event) => { + event.exception?.values?.forEach((exception) => { + delete exception.stacktrace; + }); + + delete event.server_name; + return event; + }, + + beforeSendTransaction: (event) => { + delete event.server_name; + return event; + }, + + transport: (nodeTransportOptions) => { + const nodeTransport = makeNodeTransport(nodeTransportOptions); + return { + flush: (timeout) => nodeTransport.flush(timeout), + send: async (request) => { + if (telemetry) { + return nodeTransport.send(request); + } + return undefined; + }, + }; + }, + }); + + const hub = new Hub(client); + + setTelemetryDataOnHub(options, hub, bundler); + + // increment the counter for the bundler + client.metricsAggregator?.add("c", `bundler-${bundler}`, 1); + + type MetricFunction = ( + key: string, + value: number, + unit?: string, + tags?: Record, + ) => void; + + const gauge: MetricFunction = (key, value, unit, tags) => + client.metricsAggregator?.add("g", key, value, unit, tags); + const distribution: MetricFunction = (key, value, unit, tags) => + client.metricsAggregator?.add("d", key, value, unit, tags); + const increment: MetricFunction = (key, value, unit, tags) => + client.metricsAggregator?.add("c", key, value, unit, tags); + const set: MetricFunction = (key, value, unit, tags) => + client.metricsAggregator?.add("s", key, value, unit, tags); + + const sentryMetrics = { + distribution, + increment, + gauge, + set, + }; + + return { sentryClient: client, sentryHub: hub, sentryMetrics }; +}; + +export const setTelemetryDataOnHub = ( + options: Options, + hub: Hub, + bundler: string, +) => { + const telemetry = options.telemetry ?? true; + + if (telemetry === false) { + return false; + } + + if ( + options.apiUrl !== "https://api.codecov.io" || + // @ts-expect-error need to ensure that values belong to Codecov + options.apiUrl !== "https://stage-api.codecov.dev" + ) { + return false; + } + + hub.setTag("bundle-analysis", !!options.enableBundleAnalysis); + hub.setTag("node", process.version); + hub.setTag("platform", process.platform); + hub.setTag("bundler", bundler); + + return; +}; + +export const safeFlushTelemetry = async (sentryClient: NodeClient) => { + try { + await sentryClient.flush(2000); + } catch { + // Noop when flushing fails. + // We don't even need to log anything because there's likely nothing the user can do and they likely will not care. + } +}; diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 4fc9350c..d1e9751c 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -105,6 +105,35 @@ export interface Options { /** Override values for passing custom information to API. */ uploadOverrides?: UploadOverrides; + /* If set to true, internal plugin errors and performance data will be sent to Sentry. + * + * At Codecov we like to use Sentry ourselves to deliver faster and more stable products. We're + * very careful of what we're sending. We won't collect anything other than error and high-level + * performance data. We will never collect your code or any details of the projects in which + * you're using this plugin. + * + * Defaults to `true`. + */ + telemetry?: boolean; + + /** + * When an error occurs during release creation or sourcemaps upload, the plugin will call this + * function. + * + * By default, the plugin will simply throw an error, thereby stopping the bundling process. If an + * `errorHandler` callback is provided, compilation will continue, unless an error is thrown in + * the provided callback. + * + * To allow compilation to continue but still emit a warning, set this option to the following: + * + * ```js + * (err) => { + * console.warn(err); + * } + * ``` + */ + errorHandler?: (err: Error) => void; + sentry?: { /** * Only send bundle stats to sentry (used within sentry bundler plugin). diff --git a/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts b/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts index 91a152b8..3e913924 100644 --- a/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts +++ b/packages/bundler-plugin-core/src/utils/__tests__/normalizeOptions.test.ts @@ -24,6 +24,7 @@ const tests: Test[] = [ success: true, options: { bundleName: "test-bundle", + telemetry: true, apiUrl: "https://api.codecov.io", dryRun: false, retryCount: 3, @@ -40,6 +41,7 @@ const tests: Test[] = [ dryRun: true, retryCount: 1, enableBundleAnalysis: true, + telemetry: false, uploadToken: "test-token", uploadOverrides: { branch: "test-branch", @@ -58,6 +60,7 @@ const tests: Test[] = [ dryRun: true, retryCount: 1, enableBundleAnalysis: true, + telemetry: false, uploadToken: "test-token", uploadOverrides: { branch: "test-branch", diff --git a/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts b/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts index 7ff3fe28..bf3bf6f0 100644 --- a/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts +++ b/packages/bundler-plugin-core/src/utils/fetchWithRetry.ts @@ -1,13 +1,30 @@ +import { type SentryMetrics } from "../sentry.ts"; import { BadResponseError } from "../errors/BadResponseError"; import { DEFAULT_RETRY_DELAY } from "./constants"; import { delay } from "./delay"; import { debug, red } from "./logging"; +interface CreateGaugeArgs { + bundler: string; + sentryMetrics: SentryMetrics; +} + +export type Gauge = ReturnType; + +export const createGauge = + ({ bundler, sentryMetrics }: CreateGaugeArgs) => + (name: string, count: number) => { + sentryMetrics?.gauge(`fetch.${name}`, count, "none", { + bundler, + }); + }; + interface FetchWithRetryArgs { url: string; retryCount: number; requestData: RequestInit; name?: string; + gauge?: Gauge; } export const fetchWithRetry = async ({ @@ -15,12 +32,14 @@ export const fetchWithRetry = async ({ retryCount, requestData, name, + gauge, }: FetchWithRetryArgs) => { let response = new Response(null, { status: 400 }); + let retryCounter = 0; for (let i = 0; i < retryCount + 1; i++) { try { - debug(`Attempting to fetch ${name}, attempt: ${i}`); + debug(`Attempting to fetch \`${name}\`, attempt: ${i}`); await delay(DEFAULT_RETRY_DELAY * i); response = await fetch(url, requestData); @@ -29,8 +48,9 @@ export const fetchWithRetry = async ({ } break; } catch (err) { - debug(`${name} fetch attempt ${i} failed`); + debug(`\`${name}\` fetch attempt ${i} failed`); const isLastAttempt = i + 1 === retryCount; + retryCounter = i; if (isLastAttempt) { red(`${name} failed after ${i} attempts`); @@ -38,10 +58,16 @@ export const fetchWithRetry = async ({ if (!(err instanceof BadResponseError)) { throw err; } + if (gauge && name) { + gauge(name, retryCounter + 1); + } return response; } } } + if (gauge && name) { + gauge(name, retryCounter + 1); + } return response; }; diff --git a/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts b/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts index 2c96c17c..f28d47b2 100644 --- a/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts +++ b/packages/bundler-plugin-core/src/utils/getPreSignedURL.ts @@ -5,7 +5,7 @@ import { NoUploadTokenError } from "../errors/NoUploadTokenError.ts"; import { UploadLimitReachedError } from "../errors/UploadLimitReachedError.ts"; import { type ProviderServiceParams } from "../types.ts"; import { DEFAULT_RETRY_COUNT } from "./constants.ts"; -import { fetchWithRetry } from "./fetchWithRetry.ts"; +import { type Gauge, fetchWithRetry } from "./fetchWithRetry.ts"; import { green, red } from "./logging.ts"; import { preProcessBody } from "./preProcessBody.ts"; @@ -14,6 +14,7 @@ interface GetPreSignedURLArgs { uploadToken?: string; serviceParams: Partial; retryCount?: number; + gauge?: Gauge; } const PreSignedURLSchema = z.object({ @@ -25,6 +26,7 @@ export const getPreSignedURL = async ({ uploadToken, serviceParams, retryCount = DEFAULT_RETRY_COUNT, + gauge, }: GetPreSignedURLArgs) => { if (!uploadToken) { red("No upload token found"); @@ -38,7 +40,8 @@ export const getPreSignedURL = async ({ response = await fetchWithRetry({ url, retryCount, - name: "`get-pre-signed-url`", + gauge, + name: "get-pre-signed-url", requestData: { method: "POST", headers: { diff --git a/packages/bundler-plugin-core/src/utils/normalizeOptions.ts b/packages/bundler-plugin-core/src/utils/normalizeOptions.ts index 813ece7a..18609d22 100644 --- a/packages/bundler-plugin-core/src/utils/normalizeOptions.ts +++ b/packages/bundler-plugin-core/src/utils/normalizeOptions.ts @@ -80,6 +80,9 @@ const optionsSchemaFactory = (options: Options) => .string({ invalid_type_error: "`uploadToken` must be a string." }) .optional(), uploadOverrides: UploadOverridesSchema.optional(), + telemetry: z + .boolean({ invalid_type_error: "`telemetry` must be a boolean." }) + .default(true), sentry: z .object({ sentryOnly: z diff --git a/packages/bundler-plugin-core/src/utils/uploadStats.ts b/packages/bundler-plugin-core/src/utils/uploadStats.ts index 39dd4aac..0f7a443b 100644 --- a/packages/bundler-plugin-core/src/utils/uploadStats.ts +++ b/packages/bundler-plugin-core/src/utils/uploadStats.ts @@ -2,7 +2,7 @@ import { ReadableStream, TextEncoderStream } from "node:stream/web"; import { FailedUploadError } from "../errors/FailedUploadError"; import { green, red } from "./logging"; -import { fetchWithRetry } from "./fetchWithRetry"; +import { type Gauge, fetchWithRetry } from "./fetchWithRetry"; import { DEFAULT_RETRY_COUNT } from "./constants"; import { UploadLimitReachedError } from "../errors/UploadLimitReachedError"; import { FailedFetchError } from "../errors/FailedFetchError"; @@ -12,6 +12,7 @@ interface UploadStatsArgs { bundleName: string; preSignedUrl: string; retryCount?: number; + gauge?: Gauge; } export async function uploadStats({ @@ -19,6 +20,7 @@ export async function uploadStats({ bundleName, preSignedUrl, retryCount = DEFAULT_RETRY_COUNT, + gauge, }: UploadStatsArgs) { const iterator = message[Symbol.iterator](); const stream = new ReadableStream({ @@ -38,7 +40,8 @@ export async function uploadStats({ response = await fetchWithRetry({ url: preSignedUrl, retryCount, - name: "`upload-stats`", + name: "upload-stats", + gauge, requestData: { method: "PUT", headers: { diff --git a/packages/rollup-plugin/build.config.ts b/packages/rollup-plugin/build.config.ts index cc19d72e..b2ed0303 100644 --- a/packages/rollup-plugin/build.config.ts +++ b/packages/rollup-plugin/build.config.ts @@ -1,5 +1,6 @@ import { defineBuildConfig } from "unbuild"; import { codecovRollupPlugin } from "codecovProdRollupPlugin"; +import packageJson from "./package.json"; export default defineBuildConfig({ entries: ["./src/index"], @@ -17,13 +18,19 @@ export default defineBuildConfig({ esbuild: { minify: true, }, + replace: { + preventAssignment: true, + values: { + __PACKAGE_VERSION__: JSON.stringify(packageJson.version), + }, + }, }, hooks: { "rollup:options": (_ctx, opts) => { if (process.env.PLUGIN_CODECOV_TOKEN && Array.isArray(opts.plugins)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call opts.plugins = [ ...opts.plugins, + // eslint-disable-next-line @typescript-eslint/no-unsafe-call codecovRollupPlugin({ enableBundleAnalysis: typeof process.env.PLUGIN_CODECOV_TOKEN === "string", diff --git a/packages/vite-plugin/build.config.ts b/packages/vite-plugin/build.config.ts index 28f9af35..46823a1c 100644 --- a/packages/vite-plugin/build.config.ts +++ b/packages/vite-plugin/build.config.ts @@ -1,5 +1,6 @@ import { defineBuildConfig } from "unbuild"; import { codecovRollupPlugin } from "codecovProdRollupPlugin"; +import packageJson from "./package.json"; export default defineBuildConfig({ entries: ["./src/index"], @@ -17,6 +18,12 @@ export default defineBuildConfig({ esbuild: { minify: true, }, + replace: { + preventAssignment: true, + values: { + __PACKAGE_VERSION__: JSON.stringify(packageJson.version), + }, + }, }, hooks: { "rollup:options": (_ctx, opts) => { diff --git a/packages/webpack-plugin/build.config.ts b/packages/webpack-plugin/build.config.ts index 913bf3ff..887f3c58 100644 --- a/packages/webpack-plugin/build.config.ts +++ b/packages/webpack-plugin/build.config.ts @@ -1,5 +1,6 @@ import { defineBuildConfig } from "unbuild"; import { codecovRollupPlugin } from "codecovProdRollupPlugin"; +import packageJson from "./package.json"; export default defineBuildConfig({ entries: ["./src/index"], @@ -16,6 +17,12 @@ export default defineBuildConfig({ esbuild: { minify: true, }, + replace: { + preventAssignment: true, + values: { + __PACKAGE_VERSION__: JSON.stringify(packageJson.version), + }, + }, }, hooks: { "rollup:options": (_ctx, opts) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 312949b0..dc512c1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,6 +238,9 @@ importers: packages/bundler-plugin-core: dependencies: + '@sentry/node': + specifier: ^7.100.1 + version: 7.100.1 chalk: specifier: 4.1.2 version: 4.1.2 @@ -268,7 +271,7 @@ importers: version: 7.5.6 codecovProdRollupPlugin: specifier: npm:@codecov/rollup-plugin@0.0.1-beta.2 - version: link:../rollup-plugin + version: /@codecov/rollup-plugin@0.0.1-beta.2(rollup@4.9.6) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.11.15)(ts-node@10.9.2) @@ -311,7 +314,7 @@ importers: version: 20.11.15 codecovProdRollupPlugin: specifier: npm:@codecov/rollup-plugin@0.0.1-beta.2 - version: 'link:' + version: /@codecov/rollup-plugin@0.0.1-beta.2(rollup@4.9.6) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.11.15)(ts-node@10.9.2) @@ -351,7 +354,7 @@ importers: version: 20.11.15 codecovProdRollupPlugin: specifier: npm:@codecov/rollup-plugin@0.0.1-beta.2 - version: link:../rollup-plugin + version: /@codecov/rollup-plugin@0.0.1-beta.2(rollup@4.9.6) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.11.15)(ts-node@10.9.2) @@ -394,7 +397,7 @@ importers: version: 5.28.5(@swc/core@1.3.107) codecovProdRollupPlugin: specifier: npm:@codecov/rollup-plugin@0.0.1-beta.2 - version: link:../rollup-plugin + version: /@codecov/rollup-plugin@0.0.1-beta.2(rollup@4.9.6) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.10.0)(ts-node@10.9.2) @@ -1136,6 +1139,26 @@ packages: prettier: 2.8.8 dev: true + /@codecov/bundler-plugin-core@0.0.1-beta.2: + resolution: {integrity: sha512-OMyjg8w1cWoHF5X6F5JvFOpV9E6AMZOlX3c8oKqAMijZrKGkpQme656gRlWtAM8bNIVmZiJ09YvdB/1MNeDqlQ==} + engines: {node: '>=18.0.0'} + dependencies: + chalk: 4.1.2 + semver: 7.5.4 + unplugin: 1.6.0 + zod: 3.22.4 + dev: true + + /@codecov/rollup-plugin@0.0.1-beta.2(rollup@4.9.6): + resolution: {integrity: sha512-gUeKPK7vCICO+PxnhQojtx72n4o9vBjYQYGaHfdM0Z6qEYGhs/C6P3KMMrUq4pT4+dn6M88J2NrtZwzN0beOVw==} + engines: {node: '>=18.0.0'} + peerDependencies: + rollup: 3.x || 4.x + dependencies: + '@codecov/bundler-plugin-core': 0.0.1-beta.2 + rollup: 4.9.6 + dev: true + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -2375,6 +2398,45 @@ packages: resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==} dev: true + /@sentry-internal/tracing@7.100.1: + resolution: {integrity: sha512-+u9RRf5eL3StiyiRyAHZmdkAR7GTSGx4Mt4Lmi5NEtCcWlTGZ1QgW2r8ZbhouVmTiJkjhQgYCyej3cojtazeJg==} + engines: {node: '>=8'} + dependencies: + '@sentry/core': 7.100.1 + '@sentry/types': 7.100.1 + '@sentry/utils': 7.100.1 + dev: false + + /@sentry/core@7.100.1: + resolution: {integrity: sha512-f+ItUge/o9AjlveQq0ZUbQauKlPH1FIJbC1TRaYLJ4KNfOdrsh8yZ29RmWv0cFJ/e+FGTr603gWpRPObF5rM8Q==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.100.1 + '@sentry/utils': 7.100.1 + dev: false + + /@sentry/node@7.100.1: + resolution: {integrity: sha512-jB6tBLr7BpgdE2SlYZu343vvpa5jMFnqyFlprr+jdDu/ayNF4idB0qFwQe8p4C6LI6M/MNDRLVOgPBiCjjZSpw==} + engines: {node: '>=8'} + dependencies: + '@sentry-internal/tracing': 7.100.1 + '@sentry/core': 7.100.1 + '@sentry/types': 7.100.1 + '@sentry/utils': 7.100.1 + dev: false + + /@sentry/types@7.100.1: + resolution: {integrity: sha512-fLM+LedHuKzOd8IhXBqaQuym+AA519MGjeczBa5kGakes/BbAsUMwsNfjsKQedp7Kh44RgYF99jwoRPK2oDrXw==} + engines: {node: '>=8'} + dev: false + + /@sentry/utils@7.100.1: + resolution: {integrity: sha512-Ve6dXr1o6xiBe3VCoJgiutmBKrugryI65EZAbYto5XI+t+PjiLLf9wXtEMF24ZrwImo4Lv3E9Uqza+fWkEbw6A==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.100.1 + dev: false + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true @@ -8780,7 +8842,6 @@ packages: chokidar: 3.5.3 webpack-sources: 3.2.3 webpack-virtual-modules: 0.6.1 - dev: false /untyped@1.4.0: resolution: {integrity: sha512-Egkr/s4zcMTEuulcIb7dgURS6QpN7DyqQYdf+jBtiaJvQ+eRsrtWUoX84SbvQWuLkXsOjM+8sJC9u6KoMK/U7Q==} @@ -9001,7 +9062,6 @@ packages: /webpack-virtual-modules@0.6.1: resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==} - dev: false /webpack@5.90.0(@swc/core@1.3.107): resolution: {integrity: sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w==} @@ -9309,4 +9369,3 @@ packages: /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - dev: false