Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"license": "MIT",
"private": true,
"scripts": {
"build": "run-p build:rollup build:vite build:webpack4 build:webpack5 build:esbuild",
"build:playground": "run-p build:rollup build:vite build:webpack4 build:webpack5 build:esbuild",
"build:rollup": "rollup --config rollup.config.js",
"build:vite": "vite build --config vite.config.js",
"build:webpack4": "node build-webpack4.js",
Expand Down
124 changes: 71 additions & 53 deletions packages/unplugin/src/sentry/api.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,86 @@
/* eslint-disable @typescript-eslint/no-empty-function */

import axios from "axios";
import { AxiosError } from "axios";
import { Options } from "../types";

const API_PATH = "api/0";

type SentryRequestOptions = Pick<Options, "authToken" | "url"> & {
method: string;
endpoint: string;
payload: unknown;
};

/**
* Generic function to call to make the actual request.
* Currently takes care of adding headers.
* Happy to change this to something more sophisticated.
*/
async function makeRequest(requestOptions: SentryRequestOptions) {
const { authToken, url, endpoint, method, payload } = requestOptions;

if (!authToken || !url || !endpoint) {
return Promise.reject();
}
// We need to ignore the import because the package.json is not part of the TS project as configured in tsconfig.json
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { version as unpluginVersion } from "../../package.json";

const response = await axios({
method,
url: `${url}/${API_PATH}/${endpoint}`,
data: payload,
headers: { Authorization: `Bearer ${authToken}`, "User-Agent": "sentry-unplugin" },
}).catch((error: AxiosError) => {
const msg = `Error: ${error.message}`;
throw new Error(msg);
});
const API_PATH = "/api/0";

return response;
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
const USER_AGENT = `sentry-unplugin/${unpluginVersion}`;
const sentryApiAxiosInstance = axios.create();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for this!

sentryApiAxiosInstance.interceptors.request.use((config) => {
return {
...config,
headers: {
...config.headers,
"User-Agent": USER_AGENT,
},
};
});

/* Just a wrapper to make POST requests */
async function makePostRequest(requestOptions: Omit<SentryRequestOptions, "method">) {
return makeRequest({ ...requestOptions, method: "POST" });
}
export async function createRelease({
release,
project,
org,
authToken,
sentryUrl,
}: {
release: string;
project: string;
org: string;
authToken: string;
sentryUrl: string;
}): Promise<void> {
// using the legacy endpoint here because the sentry webpack plugin only associates one project
// with the release. If we ever wanna support multiple projects in the unplugin,
// take a look at how sentry/cli calls the new endpoint:
// https://github.com/getsentry/sentry-cli/blob/4fa813549cd249e77ae6ba974d76e606a19f48de/src/api.rs#L769-L773
const requestUrl = `${sentryUrl}${API_PATH}/projects/${org}/${project}/releases/`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const requestUrl = `${sentryUrl}${API_PATH}/projects/${org}/${project}/releases/`;
// using the legacy endpoint here because the sentry webpack plugin only associates one project
// with the release. If we ever wanna support multiple projects in the unplugin,
// take a look at how sentry/cli calls the new endpoint:
// https://github.com/getsentry/sentry-cli/blob/4fa813549cd249e77ae6ba974d76e606a19f48de/src/api.rs#L769-L773
const requestUrl = `${sentryUrl}${API_PATH}/projects/${org}/${project}/releases/`;


export async function makeNewReleaseRequest(release: string, options: Options): Promise<string> {
const releasePayload = {
version: release,
projects: [options.project],
projects: [project],
dateStarted: new Date(),
dateReleased: new Date(), //TODO: figure out if these dates are set correctly
};

const orgSlug = options.org || "";
const projectSlug = options.project || "";
try {
await sentryApiAxiosInstance.post(requestUrl, releasePayload, {
headers: { Authorization: `Bearer ${authToken}` },
});
} catch (e) {
// TODO: Maybe do some more sopthisticated error handling here
throw new Error("Something went wrong while creating a release");
}
}

// using the legacy endpoint here because the sentry webpack plugin only associates one project
// with the release. If we ever wanna support multiple projects in the unplugin,
// take a look at how sentry/cli calls the new endpoint:
// https://github.com/getsentry/sentry-cli/blob/4fa813549cd249e77ae6ba974d76e606a19f48de/src/api.rs#L769-L773
const response = await makePostRequest({
authToken: options.authToken,
url: options.url,
endpoint: `projects/${orgSlug}/${projectSlug}/releases/`,
payload: releasePayload,
});
export async function deleteAllReleaseArtifacts({
org,
release,
sentryUrl,
authToken,
project,
}: {
org: string;
release: string;
sentryUrl: string;
authToken: string;
project?: string;
}): Promise<void> {
const requestUrl = project
? `${sentryUrl}${API_PATH}/projects/${org}/${project}/files/source-maps/?name=${release}` // legacy endpoint if users provide project
: `${sentryUrl}${API_PATH}/organizations/${org}/files/source-maps/?name=${release}`; // new endpoint

return response.status.toString();
try {
await sentryApiAxiosInstance.delete(requestUrl, {
headers: {
Authorization: `Bearer ${authToken}`,
},
});
} catch (e) {
// TODO: Maybe do some more sopthisticated error handling here
throw new Error("Something went wrong while cleaning previous release artifacts");
}
}
68 changes: 64 additions & 4 deletions packages/unplugin/src/sentry/facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { makeSentryCli } from "./cli";
import { Options } from "../types";
import SentryCli from "@sentry/cli";
import { makeNewReleaseRequest } from "./api";
import { createRelease, deleteAllReleaseArtifacts } from "./api";

export type SentryFacade = {
createNewRelease: () => Promise<string>;
Expand Down Expand Up @@ -38,7 +38,37 @@ export function makeSentryFacade(release: string, options: Options): SentryFacad
}

async function createNewRelease(release: string, options: Options): Promise<string> {
return makeNewReleaseRequest(release, options);
// TODO: pull these checks out of here and simplify them
if (options.authToken === undefined) {
// eslint-disable-next-line no-console
console.log('[Sentry-plugin] WARNING: Missing "authToken" option. Will not create release.');
return Promise.resolve("nothing to do here");
} else if (options.org === undefined) {
// eslint-disable-next-line no-console
console.log('[Sentry-plugin] WARNING: Missing "org" option. Will not create release.');
return Promise.resolve("nothing to do here");
} else if (options.url === undefined) {
// eslint-disable-next-line no-console
console.log('[Sentry-plugin] WARNING: Missing "url" option. Will not create release.');
return Promise.resolve("nothing to do here");
} else if (options.project === undefined) {
// eslint-disable-next-line no-console
console.log('[Sentry-plugin] WARNING: Missing "project" option. Will not create release.');
return Promise.resolve("nothing to do here");
}

await createRelease({
release,
authToken: options.authToken,
org: options.org,
project: options.project,
sentryUrl: options.url,
});

// eslint-disable-next-line no-console
console.log("[Sentry-plugin] Successfully created release.");

return Promise.resolve("nothing to do here");
}

async function uploadSourceMaps(
Expand Down Expand Up @@ -80,9 +110,39 @@ async function finalizeRelease(cli: SentryCli, release: string, options: Options
return Promise.resolve("nothing to do here");
}

async function cleanArtifacts(cli: SentryCli, release: string, options: Options): Promise<string> {
async function cleanArtifacts(_cli: SentryCli, release: string, options: Options): Promise<string> {
if (options.cleanArtifacts) {
return cli.releases.execute(["releases", "files", release, "delete", "--all"], true);
// TODO: pull these checks out of here and simplify them
if (options.authToken === undefined) {
// eslint-disable-next-line no-console
console.log(
'[Sentry-plugin] WARNING: Missing "authToken" option. Will not clean existing artifacts.'
);
return Promise.resolve("nothing to do here");
} else if (options.org === undefined) {
// eslint-disable-next-line no-console
console.log(
'[Sentry-plugin] WARNING: Missing "org" option. Will not clean existing artifacts.'
);
return Promise.resolve("nothing to do here");
} else if (options.url === undefined) {
// eslint-disable-next-line no-console
console.log(
'[Sentry-plugin] WARNING: Missing "url" option. Will not clean existing artifacts.'
);
return Promise.resolve("nothing to do here");
}

await deleteAllReleaseArtifacts({
authToken: options.authToken,
org: options.org,
release,
sentryUrl: options.url,
project: options.project,
});

// eslint-disable-next-line no-console
console.log("[Sentry-plugin] Successfully cleaned previous artifacts.");
}
return Promise.resolve("nothing to do here");
}
Expand Down
3 changes: 2 additions & 1 deletion packages/unplugin/src/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "sentry-unplugin-tsconfigs/base-config.json",
"include": ["./**/*"],
"include": ["./**/*", "../package.json"],
"compilerOptions": {
"esModuleInterop": true,
"resolveJsonModule": true, // needed to import package.json
"types": ["node"],
"lib": ["ES2020", "DOM"] // es2020 needed for "new Set()", DOM needed for various bundler types
}
Expand Down